diff --git a/core/java/android/app/compat/CompatChanges.java b/core/java/android/app/compat/CompatChanges.java index 28b73406b877b4a95f3fd01b2a771e360c896fc1..ab38832458d6d1a9a211f7819ac58676e5d61279 100644 --- a/core/java/android/app/compat/CompatChanges.java +++ b/core/java/android/app/compat/CompatChanges.java @@ -20,8 +20,16 @@ import android.annotation.NonNull; import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.compat.Compatibility; +import android.content.Context; +import android.os.RemoteException; +import android.os.ServiceManager; import android.os.UserHandle; +import com.android.internal.compat.CompatibilityOverrideConfig; +import com.android.internal.compat.IPlatformCompat; + +import java.util.Map; + /** * CompatChanges APIs - to be used by platform code only (including mainline * modules). @@ -89,4 +97,25 @@ public final class CompatChanges { return QUERY_CACHE.query(ChangeIdStateQuery.byUid(changeId, uid)); } + /** + * Set an app compat override for a given package. This will check whether the caller is allowed + * to perform this operation on the given apk and build. Only the installer package is allowed + * to set overrides on a non-debuggable final build and a non-test apk. + * + * @param packageName The package name of the app in question. + * @param overrides A map from changeId to the override applied for this change id. + * @hide + */ + @RequiresPermission(android.Manifest.permission.OVERRIDE_COMPAT_CHANGE_CONFIG) + public static void setPackageOverride(String packageName, + Map<Long, PackageOverride> overrides) { + IPlatformCompat platformCompat = IPlatformCompat.Stub.asInterface( + ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE)); + CompatibilityOverrideConfig config = new CompatibilityOverrideConfig(overrides); + try { + platformCompat.setOverridesFromInstaller(config, packageName); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + } } diff --git a/core/java/android/app/compat/PackageOverride.java b/core/java/android/app/compat/PackageOverride.java new file mode 100644 index 0000000000000000000000000000000000000000..9f97cd41128a3ca6fe57f000899456631e61a07c --- /dev/null +++ b/core/java/android/app/compat/PackageOverride.java @@ -0,0 +1,211 @@ +/* + * Copyright (C) 2021 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. + */ + +package android.app.compat; + +import android.annotation.IntDef; +import android.os.Parcel; +import android.os.Parcelable; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * An app compat override applied to a given package and change id pairing. + * + * A package override contains a list of version ranges with the desired boolean value of + * the override for the app in this version range. Ranges can be open ended in either direction. + * An instance of PackageOverride gets created via {@link Builder} and is immutable once created. + * + * @hide + */ +public class PackageOverride implements Parcelable { + + @IntDef({ + VALUE_UNDEFINED, + VALUE_ENABLED, + VALUE_DISABLED + }) + @Retention(RetentionPolicy.SOURCE) + /** @hide */ + public @interface EvaluatedOverride { + } + + /** + * Return value of {@link #evaluate(long)} and {@link #evaluateForAllVersions()} indicating that + * this PackageOverride does not define the value of the override for the given version. + * @hide + */ + public static final int VALUE_UNDEFINED = 0; + /** + * Return value of {@link #evaluate(long)} and {@link #evaluateForAllVersions()} indicating that + * the override evaluates to {@code true} for the given version. + * @hide + */ + public static final int VALUE_ENABLED = 1; + /** + * Return value of {@link #evaluate(long)} and {@link #evaluateForAllVersions()} indicating that + * the override evaluates to {@code fakse} for the given version. + * @hide + */ + public static final int VALUE_DISABLED = 2; + + private final long mMinVersionCode; + private final long mMaxVersionCode; + private final boolean mEnabled; + + private PackageOverride(long minVersionCode, + long maxVersionCode, + boolean enabled) { + this.mMinVersionCode = minVersionCode; + this.mMaxVersionCode = maxVersionCode; + this.mEnabled = enabled; + } + + private PackageOverride(Parcel in) { + this(in.readLong(), in.readLong(), in.readBoolean()); + } + + /** + * Evaluate the override for the given {@code versionCode}. If no override is defined for + * the specified version code, {@link #VALUE_UNDEFINED} is returned. + * @hide + */ + public @EvaluatedOverride int evaluate(long versionCode) { + if (versionCode >= mMinVersionCode && versionCode <= mMaxVersionCode) { + return mEnabled ? VALUE_ENABLED : VALUE_DISABLED; + } + return VALUE_UNDEFINED; + } + + /** + * Evaluate the override independent of version code, i.e. only return an evaluated value if + * this range covers all versions, otherwise {@link #VALUE_UNDEFINED} is returned. + * @hide + */ + public int evaluateForAllVersions() { + if (mMinVersionCode == Long.MIN_VALUE && mMaxVersionCode == Long.MAX_VALUE) { + return mEnabled ? VALUE_ENABLED : VALUE_DISABLED; + } + return VALUE_UNDEFINED; + } + + /** Returns the minimum version code the override applies to. */ + public long getMinVersionCode() { + return mMinVersionCode; + } + + /** Returns the minimum version code the override applies from. */ + public long getMaxVersionCode() { + return mMaxVersionCode; + } + + /** Returns the enabled value for the override. */ + public boolean getEnabled() { + return mEnabled; + } + + /** @hide */ + @Override + public int describeContents() { + return 0; + } + + /** @hide */ + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeLong(mMinVersionCode); + dest.writeLong(mMaxVersionCode); + dest.writeBoolean(mEnabled); + } + + /** @hide */ + @Override + public String toString() { + if (mMinVersionCode == Long.MIN_VALUE && mMaxVersionCode == Long.MAX_VALUE) { + return Boolean.toString(mEnabled); + } + return String.format("[%d,%d,%b]", mMinVersionCode, mMaxVersionCode, mEnabled); + } + + /** @hide */ + public static final Creator<PackageOverride> CREATOR = + new Creator<PackageOverride>() { + + @Override + public PackageOverride createFromParcel(Parcel in) { + return new PackageOverride(in); + } + + @Override + public PackageOverride[] newArray(int size) { + return new PackageOverride[size]; + } + }; + + /** + * Builder to construct a PackageOverride. + */ + public static class Builder { + private long mMinVersionCode = Long.MIN_VALUE; + private long mMaxVersionCode = Long.MAX_VALUE; + private boolean mEnabled; + + /** + * Sets the minimum version code the override should apply from. + * + * default value: {@code Long.MIN_VALUE}. + */ + public Builder setMinVersionCode(long minVersionCode) { + mMinVersionCode = minVersionCode; + return this; + } + + /** + * Sets the maximum version code the override should apply to. + * + * default value: {@code Long.MAX_VALUE}. + */ + public Builder setMaxVersionCode(long maxVersionCode) { + mMaxVersionCode = maxVersionCode; + return this; + } + + /** + * Sets whether the override should be enabled for the given version range. + * + * default value: {@code false}. + */ + public Builder setEnabled(boolean enabled) { + mEnabled = enabled; + return this; + } + + /** + * Build the {@link PackageOverride}. + * + * @throws IllegalArgumentException if {@code minVersionCode} is larger than + * {@code maxVersionCode}. + */ + public PackageOverride build() { + if (mMinVersionCode > mMaxVersionCode) { + throw new IllegalArgumentException("minVersionCode must not be larger than " + + "maxVersionCode"); + } + return new PackageOverride(mMinVersionCode, mMaxVersionCode, mEnabled); + } + }; +} diff --git a/core/java/com/android/internal/compat/CompatibilityOverrideConfig.aidl b/core/java/com/android/internal/compat/CompatibilityOverrideConfig.aidl new file mode 100644 index 0000000000000000000000000000000000000000..5d02a29edcd58dbd201ad55bd0ae3fde167763e0 --- /dev/null +++ b/core/java/com/android/internal/compat/CompatibilityOverrideConfig.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2021 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. + */ + +package com.android.internal.compat; + +parcelable CompatibilityOverrideConfig; diff --git a/core/java/com/android/internal/compat/CompatibilityOverrideConfig.java b/core/java/com/android/internal/compat/CompatibilityOverrideConfig.java new file mode 100644 index 0000000000000000000000000000000000000000..1c222a73eabc9ef74b92b60d578c8966631a3cf5 --- /dev/null +++ b/core/java/com/android/internal/compat/CompatibilityOverrideConfig.java @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2021 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. + */ + +package com.android.internal.compat; + + +import android.app.compat.PackageOverride; +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.HashMap; +import java.util.Map; + +/** + * Parcelable containing compat config overrides for a given application. + * @hide + */ +public final class CompatibilityOverrideConfig implements Parcelable { + public final Map<Long, PackageOverride> overrides; + + public CompatibilityOverrideConfig(Map<Long, PackageOverride> overrides) { + this.overrides = overrides; + } + + private CompatibilityOverrideConfig(Parcel in) { + int keyCount = in.readInt(); + overrides = new HashMap<>(); + for (int i = 0; i < keyCount; i++) { + long key = in.readLong(); + PackageOverride override = in.readParcelable(PackageOverride.class.getClassLoader()); + overrides.put(key, override); + } + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(overrides.size()); + for (Long key : overrides.keySet()) { + dest.writeLong(key); + dest.writeParcelable(overrides.get(key), 0); + } + } + + public static final Creator<CompatibilityOverrideConfig> CREATOR = + new Creator<CompatibilityOverrideConfig>() { + + @Override + public CompatibilityOverrideConfig createFromParcel(Parcel in) { + return new CompatibilityOverrideConfig(in); + } + + @Override + public CompatibilityOverrideConfig[] newArray(int size) { + return new CompatibilityOverrideConfig[size]; + } + }; +} diff --git a/core/java/com/android/internal/compat/IPlatformCompat.aidl b/core/java/com/android/internal/compat/IPlatformCompat.aidl index a5eb5f607c129400258a45b5969cf95ef750db81..60213e4411c39cd84844931e2cde500e960eb36e 100644 --- a/core/java/com/android/internal/compat/IPlatformCompat.aidl +++ b/core/java/com/android/internal/compat/IPlatformCompat.aidl @@ -21,6 +21,7 @@ import com.android.internal.compat.IOverrideValidator; import java.util.Map; parcelable CompatibilityChangeConfig; +parcelable CompatibilityOverrideConfig; parcelable CompatibilityChangeInfo; /** * Platform private API for talking with the PlatformCompat service. @@ -149,6 +150,17 @@ interface IPlatformCompat { */ void setOverrides(in CompatibilityChangeConfig overrides, in String packageName); + /** + * Adds overrides to compatibility changes. + * + * <p>Kills the app to allow the changes to take effect. + * + * @param overrides parcelable containing the compat change overrides to be applied + * @param packageName the package name of the app whose changes will be overridden + * @throws SecurityException if overriding changes is not permitted + */ + void setOverridesFromInstaller(in CompatibilityOverrideConfig overrides, in String packageName); + /** * Adds overrides to compatibility changes. * diff --git a/services/core/java/com/android/server/compat/CompatChange.java b/services/core/java/com/android/server/compat/CompatChange.java index e3757dfc6a5939bcafc1cbc47deb7bc669179bdf..df83df9a73fb8fbfff00048b8afc5a9c831d4907 100644 --- a/services/core/java/com/android/server/compat/CompatChange.java +++ b/services/core/java/com/android/server/compat/CompatChange.java @@ -16,15 +16,24 @@ package com.android.server.compat; +import static android.app.compat.PackageOverride.VALUE_DISABLED; +import static android.app.compat.PackageOverride.VALUE_ENABLED; +import static android.app.compat.PackageOverride.VALUE_UNDEFINED; + import android.annotation.Nullable; +import android.app.compat.PackageOverride; import android.compat.annotation.ChangeId; import android.compat.annotation.EnabledSince; +import android.content.Context; import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; import com.android.internal.compat.CompatibilityChangeInfo; +import com.android.internal.compat.OverrideAllowedState; import com.android.server.compat.config.Change; import com.android.server.compat.overrides.ChangeOverrides; import com.android.server.compat.overrides.OverrideValue; +import com.android.server.compat.overrides.RawOverrideValue; import java.util.HashMap; import java.util.List; @@ -36,7 +45,7 @@ import java.util.Map; * <p>A compatibility change has a default setting, determined by the {@code enableAfterTargetSdk} * and {@code disabled} constructor parameters. If a change is {@code disabled}, this overrides any * target SDK criteria set. These settings can be overridden for a specific package using - * {@link #addPackageOverride(String, boolean)}. + * {@link #addPackageOverrideInternal(String, boolean)}. * * <p>Note, this class is not thread safe so callers must ensure thread safety. */ @@ -63,8 +72,8 @@ public final class CompatChange extends CompatibilityChangeInfo { ChangeListener mListener = null; - private Map<String, Boolean> mPackageOverrides; - private Map<String, Boolean> mDeferredOverrides; + private Map<String, Boolean> mEvaluatedOverrides; + private Map<String, PackageOverride> mRawOverrides; public CompatChange(long changeId) { this(changeId, null, -1, -1, false, false, null, false); @@ -113,18 +122,26 @@ public final class CompatChange extends CompatibilityChangeInfo { * @param pname Package name to enable the change for. * @param enabled Whether or not to enable the change. */ - void addPackageOverride(String pname, boolean enabled) { + private void addPackageOverrideInternal(String pname, boolean enabled) { if (getLoggingOnly()) { throw new IllegalArgumentException( "Can't add overrides for a logging only change " + toString()); } - if (mPackageOverrides == null) { - mPackageOverrides = new HashMap<>(); + if (mEvaluatedOverrides == null) { + mEvaluatedOverrides = new HashMap<>(); } - mPackageOverrides.put(pname, enabled); + mEvaluatedOverrides.put(pname, enabled); notifyListener(pname); } + private void removePackageOverrideInternal(String pname) { + if (mEvaluatedOverrides != null) { + if (mEvaluatedOverrides.remove(pname) != null) { + notifyListener(pname); + } + } + } + /** * Tentatively set the state of this change for a given package name. * The override will only take effect after that package is installed, if applicable. @@ -132,17 +149,19 @@ public final class CompatChange extends CompatibilityChangeInfo { * <p>Note, this method is not thread safe so callers must ensure thread safety. * * @param packageName Package name to tentatively enable the change for. - * @param enabled Whether or not to enable the change. + * @param override The package override to be set */ - void addPackageDeferredOverride(String packageName, boolean enabled) { + void addPackageOverride(String packageName, PackageOverride override, + OverrideAllowedState allowedState, Context context) { if (getLoggingOnly()) { throw new IllegalArgumentException( "Can't add overrides for a logging only change " + toString()); } - if (mDeferredOverrides == null) { - mDeferredOverrides = new HashMap<>(); + if (mRawOverrides == null) { + mRawOverrides = new HashMap<>(); } - mDeferredOverrides.put(packageName, enabled); + mRawOverrides.put(packageName, override); + recheckOverride(packageName, allowedState, context); } /** @@ -157,24 +176,44 @@ public final class CompatChange extends CompatibilityChangeInfo { * @return {@code true} if the recheck yielded a result that requires invalidating caches * (a deferred override was consolidated or a regular override was removed). */ - boolean recheckOverride(String packageName, boolean allowed) { - // A deferred override now is allowed by the policy, so promote it to a regular override. - if (hasDeferredOverride(packageName) && allowed) { - boolean overrideValue = mDeferredOverrides.remove(packageName); - addPackageOverride(packageName, overrideValue); - return true; + boolean recheckOverride(String packageName, OverrideAllowedState allowedState, + Context context) { + boolean allowed = (allowedState.state == OverrideAllowedState.ALLOWED); + + Long version = null; + try { + ApplicationInfo applicationInfo = context.getPackageManager().getApplicationInfo( + packageName, 0); + version = applicationInfo.longVersionCode; + } catch (PackageManager.NameNotFoundException e) { + // Do nothing } - // A previously set override is no longer allowed by the policy, so make it deferred. - if (hasOverride(packageName) && !allowed) { - boolean overrideValue = mPackageOverrides.remove(packageName); - addPackageDeferredOverride(packageName, overrideValue); - // Notify because the override was removed. - notifyListener(packageName); - return true; + + // If the app is not installed or no longer has raw overrides, evaluate to false + if (version == null || !hasRawOverride(packageName) || !allowed) { + removePackageOverrideInternal(packageName); + return false; } - return false; + + // Evaluate the override based on its version + int overrideValue = mRawOverrides.get(packageName).evaluate(version); + switch (overrideValue) { + case VALUE_UNDEFINED: + removePackageOverrideInternal(packageName); + break; + case VALUE_ENABLED: + addPackageOverrideInternal(packageName, true); + break; + case VALUE_DISABLED: + addPackageOverrideInternal(packageName, false); + break; + } + return true; } + boolean hasPackageOverride(String pname) { + return mRawOverrides != null && mRawOverrides.containsKey(pname); + } /** * Remove any package override for the given package name, restoring the default behaviour. * @@ -182,15 +221,13 @@ public final class CompatChange extends CompatibilityChangeInfo { * * @param pname Package name to reset to defaults for. */ - void removePackageOverride(String pname) { - if (mPackageOverrides != null) { - if (mPackageOverrides.remove(pname) != null) { - notifyListener(pname); - } - } - if (mDeferredOverrides != null) { - mDeferredOverrides.remove(pname); + boolean removePackageOverride(String pname, OverrideAllowedState allowedState, + Context context) { + if (mRawOverrides != null && (mRawOverrides.remove(pname) != null)) { + recheckOverride(pname, allowedState, context); + return true; } + return false; } /** @@ -204,8 +241,8 @@ public final class CompatChange extends CompatibilityChangeInfo { if (app == null) { return defaultValue(); } - if (mPackageOverrides != null && mPackageOverrides.containsKey(app.packageName)) { - return mPackageOverrides.get(app.packageName); + if (mEvaluatedOverrides != null && mEvaluatedOverrides.containsKey(app.packageName)) { + return mEvaluatedOverrides.get(app.packageName); } if (getDisabled()) { return false; @@ -223,8 +260,16 @@ public final class CompatChange extends CompatibilityChangeInfo { * @return {@code true} if the change should be enabled for the package. */ boolean willBeEnabled(String packageName) { - if (hasDeferredOverride(packageName)) { - return mDeferredOverrides.get(packageName); + if (hasRawOverride(packageName)) { + int eval = mRawOverrides.get(packageName).evaluateForAllVersions(); + switch (eval) { + case VALUE_ENABLED: + return true; + case VALUE_DISABLED: + return false; + case VALUE_UNDEFINED: + return defaultValue(); + } } return defaultValue(); } @@ -243,8 +288,8 @@ public final class CompatChange extends CompatibilityChangeInfo { * @param packageName name of the package * @return true if there is such override */ - boolean hasOverride(String packageName) { - return mPackageOverrides != null && mPackageOverrides.containsKey(packageName); + private boolean hasOverride(String packageName) { + return mEvaluatedOverrides != null && mEvaluatedOverrides.containsKey(packageName); } /** @@ -252,65 +297,77 @@ public final class CompatChange extends CompatibilityChangeInfo { * @param packageName name of the package * @return true if there is such a deferred override */ - boolean hasDeferredOverride(String packageName) { - 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(); + private boolean hasRawOverride(String packageName) { + return mRawOverrides != null && mRawOverrides.containsKey(packageName); } void loadOverrides(ChangeOverrides changeOverrides) { - if (mDeferredOverrides == null) { - mDeferredOverrides = new HashMap<>(); + if (mRawOverrides == null) { + mRawOverrides = new HashMap<>(); } - mDeferredOverrides.clear(); - for (OverrideValue override : changeOverrides.getDeferred().getOverrideValue()) { - mDeferredOverrides.put(override.getPackageName(), override.getEnabled()); + mRawOverrides.clear(); + + if (mEvaluatedOverrides == null) { + mEvaluatedOverrides = new HashMap<>(); } + mEvaluatedOverrides.clear(); - if (mPackageOverrides == null) { - mPackageOverrides = new HashMap<>(); + // Load deferred overrides for backwards compatibility + if (changeOverrides.getDeferred() != null) { + for (OverrideValue override : changeOverrides.getDeferred().getOverrideValue()) { + mRawOverrides.put(override.getPackageName(), + new PackageOverride.Builder().setEnabled( + override.getEnabled()).build()); + } + } + + // Load validated overrides. For backwards compatibility, we also add them to raw overrides. + if (changeOverrides.getValidated() != null) { + for (OverrideValue override : changeOverrides.getValidated().getOverrideValue()) { + mEvaluatedOverrides.put(override.getPackageName(), override.getEnabled()); + mRawOverrides.put(override.getPackageName(), + new PackageOverride.Builder().setEnabled( + override.getEnabled()).build()); + } } - mPackageOverrides.clear(); - for (OverrideValue override : changeOverrides.getValidated().getOverrideValue()) { - mPackageOverrides.put(override.getPackageName(), override.getEnabled()); + + // Load raw overrides + if (changeOverrides.getRaw() != null) { + for (RawOverrideValue override : changeOverrides.getRaw().getRawOverrideValue()) { + PackageOverride packageOverride = new PackageOverride.Builder() + .setMinVersionCode(override.getMinVersionCode()) + .setMaxVersionCode(override.getMaxVersionCode()) + .setEnabled(override.getEnabled()) + .build(); + mRawOverrides.put(override.getPackageName(), packageOverride); + } } } ChangeOverrides saveOverrides() { - if (!hasAnyDeferredOverride() && !hasAnyPackageOverride()) { + if (mRawOverrides == null || mRawOverrides.isEmpty()) { 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(); + ChangeOverrides.Raw rawOverrides = new ChangeOverrides.Raw(); + List<RawOverrideValue> rawList = rawOverrides.getRawOverrideValue(); + if (mRawOverrides != null) { + for (Map.Entry<String, PackageOverride> entry : mRawOverrides.entrySet()) { + RawOverrideValue override = new RawOverrideValue(); override.setPackageName(entry.getKey()); - override.setEnabled(entry.getValue()); - deferredList.add(override); + override.setMinVersionCode(entry.getValue().getMinVersionCode()); + override.setMaxVersionCode(entry.getValue().getMaxVersionCode()); + override.setEnabled(entry.getValue().getEnabled()); + rawList.add(override); } } - changeOverrides.setDeferred(deferredOverrides); + changeOverrides.setRaw(rawOverrides); + ChangeOverrides.Validated validatedOverrides = new ChangeOverrides.Validated(); List<OverrideValue> validatedList = validatedOverrides.getOverrideValue(); - if (mPackageOverrides != null) { - for (Map.Entry<String, Boolean> entry : mPackageOverrides.entrySet()) { + if (mEvaluatedOverrides != null) { + for (Map.Entry<String, Boolean> entry : mEvaluatedOverrides.entrySet()) { OverrideValue override = new OverrideValue(); override.setPackageName(entry.getKey()); override.setEnabled(entry.getValue()); @@ -337,11 +394,11 @@ public final class CompatChange extends CompatibilityChangeInfo { if (getLoggingOnly()) { sb.append("; loggingOnly"); } - if (mPackageOverrides != null && mPackageOverrides.size() > 0) { - sb.append("; packageOverrides=").append(mPackageOverrides); + if (mEvaluatedOverrides != null && mEvaluatedOverrides.size() > 0) { + sb.append("; packageOverrides=").append(mEvaluatedOverrides); } - if (mDeferredOverrides != null && mDeferredOverrides.size() > 0) { - sb.append("; deferredOverrides=").append(mDeferredOverrides); + if (mRawOverrides != null && mRawOverrides.size() > 0) { + sb.append("; rawOverrides=").append(mRawOverrides); } if (getOverridable()) { sb.append("; overridable"); diff --git a/services/core/java/com/android/server/compat/CompatConfig.java b/services/core/java/com/android/server/compat/CompatConfig.java index 6b77b9d4ce39723c1df9bab990ed31ff0c4ba0e5..422991e082a9a7323728877a976db0fe2463b26e 100644 --- a/services/core/java/com/android/server/compat/CompatConfig.java +++ b/services/core/java/com/android/server/compat/CompatConfig.java @@ -17,6 +17,7 @@ package com.android.server.compat; import android.app.compat.ChangeIdStateCache; +import android.app.compat.PackageOverride; import android.compat.Compatibility.ChangeConfig; import android.content.Context; import android.content.pm.ApplicationInfo; @@ -31,6 +32,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.compat.AndroidBuildClassifier; import com.android.internal.compat.CompatibilityChangeConfig; import com.android.internal.compat.CompatibilityChangeInfo; +import com.android.internal.compat.CompatibilityOverrideConfig; import com.android.internal.compat.IOverrideValidator; import com.android.internal.compat.OverrideAllowedState; import com.android.server.compat.config.Change; @@ -70,11 +72,13 @@ final class CompatConfig { private final LongSparseArray<CompatChange> mChanges = new LongSparseArray<>(); private final OverrideValidatorImpl mOverrideValidator; + private Context mContext; private File mOverridesFile; @VisibleForTesting CompatConfig(AndroidBuildClassifier androidBuildClassifier, Context context) { mOverrideValidator = new OverrideValidatorImpl(androidBuildClassifier, context, this); + mContext = context; } static CompatConfig create(AndroidBuildClassifier androidBuildClassifier, Context context) { @@ -210,17 +214,33 @@ final class CompatConfig { * @throws IllegalStateException if overriding is not allowed */ boolean addOverride(long changeId, String packageName, boolean enabled) { - boolean alreadyKnown = addOverrideUnsafe(changeId, packageName, enabled); + boolean alreadyKnown = addOverrideUnsafe(changeId, packageName, + new PackageOverride.Builder().setEnabled(enabled).build()); saveOverrides(); invalidateCache(); return alreadyKnown; } /** - * Unsafe version of {@link #addOverride(long, String, boolean)}. - * It does not invalidate the cache nor save the overrides. + * Overrides the enabled state for a given change and app. + * + * <p>Note, package overrides are not persistent and will be lost on system or runtime restart. + * + * @param overrides list of overrides to default changes config. + * @param packageName app for which the overrides will be applied. */ - private boolean addOverrideUnsafe(long changeId, String packageName, boolean enabled) { + void addOverrides(CompatibilityOverrideConfig overrides, String packageName) { + synchronized (mChanges) { + for (Long changeId : overrides.overrides.keySet()) { + addOverrideUnsafe(changeId, packageName, overrides.overrides.get(changeId)); + } + saveOverrides(); + invalidateCache(); + } + } + + private boolean addOverrideUnsafe(long changeId, String packageName, + PackageOverride overrides) { boolean alreadyKnown = true; OverrideAllowedState allowedState = mOverrideValidator.getOverrideAllowedState(changeId, packageName); @@ -232,17 +252,8 @@ final class CompatConfig { c = new CompatChange(changeId); addChange(c); } - switch (allowedState.state) { - case OverrideAllowedState.ALLOWED: - c.addPackageOverride(packageName, enabled); - break; - case OverrideAllowedState.DEFERRED_VERIFICATION: - c.addPackageDeferredOverride(packageName, enabled); - break; - default: - throw new IllegalStateException("Should only be able to override changes that " - + "are allowed or can be deferred."); - } + c.addPackageOverride(packageName, overrides, allowedState, mContext); + invalidateCache(); } return alreadyKnown; } @@ -311,47 +322,20 @@ final class CompatConfig { * 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); if (c != null) { - // Always allow removing a deferred override. - if (c.hasDeferredOverride(packageName)) { - c.removePackageOverride(packageName); - overrideExists = true; - } else if (c.hasOverride(packageName)) { - // Regular overrides need to pass the policy. - overrideExists = true; - OverrideAllowedState allowedState = - mOverrideValidator.getOverrideAllowedState(changeId, packageName); + OverrideAllowedState allowedState = + mOverrideValidator.getOverrideAllowedState(changeId, packageName); + if (c.hasPackageOverride(packageName)) { allowedState.enforce(changeId, packageName); - c.removePackageOverride(packageName); + c.removePackageOverride(packageName, allowedState, mContext); + invalidateCache(); + return true; } } } - return overrideExists; - } - - /** - * Overrides the enabled state for a given change and app. - * - * <p>Note: package overrides are not persistent and will be lost on system or runtime restart. - * - * @param overrides list of overrides to default changes config - * @param packageName app for which the overrides will be applied - */ - void addOverrides(CompatibilityChangeConfig overrides, String packageName) { - synchronized (mChanges) { - for (Long changeId : overrides.enabledChanges()) { - addOverrideUnsafe(changeId, packageName, true); - } - for (Long changeId : overrides.disabledChanges()) { - addOverrideUnsafe(changeId, packageName, false); - - } - saveOverrides(); - invalidateCache(); - } + return false; } /** @@ -402,7 +386,8 @@ final class CompatConfig { int enableTargetSdkChangesForPackage(String packageName, int targetSdkVersion) { long[] changes = getAllowedChangesSinceTargetSdkForPackage(packageName, targetSdkVersion); for (long changeId : changes) { - addOverrideUnsafe(changeId, packageName, true); + addOverrideUnsafe(changeId, packageName, + new PackageOverride.Builder().setEnabled(true).build()); } saveOverrides(); invalidateCache(); @@ -418,7 +403,8 @@ final class CompatConfig { int disableTargetSdkChangesForPackage(String packageName, int targetSdkVersion) { long[] changes = getAllowedChangesSinceTargetSdkForPackage(packageName, targetSdkVersion); for (long changeId : changes) { - addOverrideUnsafe(changeId, packageName, false); + addOverrideUnsafe(changeId, packageName, + new PackageOverride.Builder().setEnabled(false).build()); } saveOverrides(); invalidateCache(); @@ -615,8 +601,7 @@ final class CompatConfig { CompatChange c = mChanges.valueAt(idx); OverrideAllowedState allowedState = mOverrideValidator.getOverrideAllowedState(c.getId(), packageName); - boolean allowedOverride = (allowedState.state == OverrideAllowedState.ALLOWED); - shouldInvalidateCache |= c.recheckOverride(packageName, allowedOverride); + shouldInvalidateCache |= c.recheckOverride(packageName, allowedState, mContext); } if (shouldInvalidateCache) { invalidateCache(); diff --git a/services/core/java/com/android/server/compat/PlatformCompat.java b/services/core/java/com/android/server/compat/PlatformCompat.java index 6b2a1c950e38270405504f9c985255dc2545d910..edfc8b8f31b05e6f171340c990fc95679ff40977 100644 --- a/services/core/java/com/android/server/compat/PlatformCompat.java +++ b/services/core/java/com/android/server/compat/PlatformCompat.java @@ -25,6 +25,7 @@ import static android.os.Process.SYSTEM_UID; import android.annotation.UserIdInt; import android.app.ActivityManager; import android.app.IActivityManager; +import android.app.compat.PackageOverride; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; @@ -43,6 +44,7 @@ import com.android.internal.compat.AndroidBuildClassifier; import com.android.internal.compat.ChangeReporter; import com.android.internal.compat.CompatibilityChangeConfig; import com.android.internal.compat.CompatibilityChangeInfo; +import com.android.internal.compat.CompatibilityOverrideConfig; import com.android.internal.compat.IOverrideValidator; import com.android.internal.compat.IPlatformCompat; import com.android.internal.util.DumpUtils; @@ -51,6 +53,8 @@ import com.android.server.LocalServices; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; /** * System server internal API for gating and reporting compatibility changes. @@ -161,6 +165,22 @@ public class PlatformCompat extends IPlatformCompat.Stub { @Override public void setOverrides(CompatibilityChangeConfig overrides, String packageName) { checkCompatChangeOverridePermission(); + Map<Long, PackageOverride> overridesMap = new HashMap<>(); + for (long change : overrides.enabledChanges()) { + overridesMap.put(change, new PackageOverride.Builder().setEnabled(true).build()); + } + for (long change : overrides.disabledChanges()) { + overridesMap.put(change, new PackageOverride.Builder().setEnabled(false) + .build()); + } + mCompatConfig.addOverrides(new CompatibilityOverrideConfig(overridesMap), packageName); + killPackage(packageName); + } + + @Override + public void setOverridesFromInstaller(CompatibilityOverrideConfig overrides, + String packageName) { + checkCompatChangeOverridePermission(); mCompatConfig.addOverrides(overrides, packageName); killPackage(packageName); } @@ -168,7 +188,15 @@ public class PlatformCompat extends IPlatformCompat.Stub { @Override public void setOverridesForTest(CompatibilityChangeConfig overrides, String packageName) { checkCompatChangeOverridePermission(); - mCompatConfig.addOverrides(overrides, packageName); + Map<Long, PackageOverride> overridesMap = new HashMap<>(); + for (long change : overrides.enabledChanges()) { + overridesMap.put(change, new PackageOverride.Builder().setEnabled(true).build()); + } + for (long change : overrides.disabledChanges()) { + overridesMap.put(change, new PackageOverride.Builder().setEnabled(false) + .build()); + } + mCompatConfig.addOverrides(new CompatibilityOverrideConfig(overridesMap), packageName); } @Override diff --git a/services/core/xsd/platform-compat/overrides/platform-compat-overrides.xsd b/services/core/xsd/platform-compat/overrides/platform-compat-overrides.xsd index e27e1b8ca89dbefe7b68385e3472d5b5f271f152..1406dbb12e029fd8c003bb02007b088b121a1c51 100644 --- a/services/core/xsd/platform-compat/overrides/platform-compat-overrides.xsd +++ b/services/core/xsd/platform-compat/overrides/platform-compat-overrides.xsd @@ -27,6 +27,13 @@ <xs:attribute type="xs:boolean" name="enabled" use="required" /> </xs:complexType> + <xs:complexType name="raw-override-value"> + <xs:attribute type="xs:string" name="packageName" use="required" /> + <xs:attribute type="xs:long" name="minVersionCode" /> + <xs:attribute type="xs:long" name="maxVersionCode" /> + <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"> @@ -43,6 +50,13 @@ </xs:sequence> </xs:complexType> </xs:element> + <xs:element name="raw"> + <xs:complexType> + <xs:sequence> + <xs:element name="raw-override-value" type="raw-override-value" maxOccurs="unbounded" minOccurs="0" /> + </xs:sequence> + </xs:complexType> + </xs:element> </xs:complexType> <xs:element name="overrides"> diff --git a/services/core/xsd/platform-compat/overrides/schema/current.txt b/services/core/xsd/platform-compat/overrides/schema/current.txt index 08b82072747b5530549df1ab5abac45f3b82c6c8..a5ccffcfbb2bb2b280ab67585dcf962668c66059 100644 --- a/services/core/xsd/platform-compat/overrides/schema/current.txt +++ b/services/core/xsd/platform-compat/overrides/schema/current.txt @@ -5,9 +5,11 @@ package com.android.server.compat.overrides { 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.Raw getRaw(); 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 setRaw(com.android.server.compat.overrides.ChangeOverrides.Raw); method public void setValidated(com.android.server.compat.overrides.ChangeOverrides.Validated); } @@ -16,6 +18,11 @@ package com.android.server.compat.overrides { method public java.util.List<com.android.server.compat.overrides.OverrideValue> getOverrideValue(); } + public static class ChangeOverrides.Raw { + ctor public ChangeOverrides.Raw(); + method public java.util.List<com.android.server.compat.overrides.RawOverrideValue> getRawOverrideValue(); + } + public static class ChangeOverrides.Validated { ctor public ChangeOverrides.Validated(); method public java.util.List<com.android.server.compat.overrides.OverrideValue> getOverrideValue(); @@ -34,6 +41,18 @@ package com.android.server.compat.overrides { method public java.util.List<com.android.server.compat.overrides.ChangeOverrides> getChangeOverrides(); } + public class RawOverrideValue { + ctor public RawOverrideValue(); + method public boolean getEnabled(); + method public long getMaxVersionCode(); + method public long getMinVersionCode(); + method public String getPackageName(); + method public void setEnabled(boolean); + method public void setMaxVersionCode(long); + method public void setMinVersionCode(long); + method public void setPackageName(String); + } + 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; diff --git a/services/tests/servicestests/src/com/android/server/compat/ApplicationInfoBuilder.java b/services/tests/servicestests/src/com/android/server/compat/ApplicationInfoBuilder.java index d0767ccb6f87889cc827b85101cd73070c3acb26..c165c661a6254684829dbf1a6033c5797ccbea72 100644 --- a/services/tests/servicestests/src/com/android/server/compat/ApplicationInfoBuilder.java +++ b/services/tests/servicestests/src/com/android/server/compat/ApplicationInfoBuilder.java @@ -22,6 +22,7 @@ class ApplicationInfoBuilder { private boolean mIsDebuggable; private int mTargetSdk; private String mPackageName; + private long mVersionCode; private ApplicationInfoBuilder() { mTargetSdk = -1; @@ -46,6 +47,11 @@ class ApplicationInfoBuilder { return this; } + ApplicationInfoBuilder withVersionCode(Long versionCode) { + mVersionCode = versionCode; + return this; + } + ApplicationInfo build() { final ApplicationInfo applicationInfo = new ApplicationInfo(); if (mIsDebuggable) { @@ -53,6 +59,7 @@ class ApplicationInfoBuilder { } applicationInfo.packageName = mPackageName; applicationInfo.targetSdkVersion = mTargetSdk; + applicationInfo.longVersionCode = mVersionCode; return applicationInfo; } } 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 a53ff9bc7fdc2deb4383fa712db41e7ad2dbbdae..8b0e948579fbf26a8c845359ff823ba91379a841 100644 --- a/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java +++ b/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java @@ -18,6 +18,7 @@ package com.android.server.compat; import static com.google.common.truth.Truth.assertThat; +import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.anyInt; import static org.mockito.Mockito.eq; import static org.mockito.Mockito.mock; @@ -25,6 +26,7 @@ import static org.mockito.Mockito.when; import static org.testng.Assert.assertThrows; import android.app.compat.ChangeIdStateCache; +import android.app.compat.PackageOverride; import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; @@ -33,6 +35,7 @@ import android.content.pm.PackageManager.NameNotFoundException; import androidx.test.runner.AndroidJUnit4; import com.android.internal.compat.AndroidBuildClassifier; +import com.android.internal.compat.CompatibilityOverrideConfig; import org.junit.Before; import org.junit.Test; @@ -46,6 +49,7 @@ import java.io.IOException; import java.io.OutputStream; import java.nio.file.Files; import java.nio.file.Paths; +import java.util.Collections; import java.util.UUID; @RunWith(AndroidJUnit4.class) @@ -83,6 +87,8 @@ public class CompatConfigTest { when(mBuildClassifier.isDebuggableBuild()).thenReturn(true); when(mBuildClassifier.isFinalBuild()).thenReturn(false); ChangeIdStateCache.disable(); + when(mPackageManager.getApplicationInfo(anyString(), anyInt())) + .thenThrow(new NameNotFoundException()); } @Test @@ -163,6 +169,10 @@ public class CompatConfigTest { CompatConfig compatConfig = CompatConfigBuilder.create(mBuildClassifier, mContext) .addDisabledChangeWithId(1234L) .build(); + ApplicationInfo info = ApplicationInfoBuilder.create() + .withPackageName("com.some.package").build(); + when(mPackageManager.getApplicationInfo(eq("com.some.package"), anyInt())) + .thenReturn(info); compatConfig.addOverride(1234L, "com.some.package", true); @@ -177,6 +187,10 @@ public class CompatConfigTest { CompatConfig compatConfig = CompatConfigBuilder.create(mBuildClassifier, mContext) .addEnabledChangeWithId(1234L) .build(); + ApplicationInfo info = ApplicationInfoBuilder.create() + .withPackageName("com.some.package").build(); + when(mPackageManager.getApplicationInfo(eq("com.some.package"), anyInt())) + .thenReturn(info); compatConfig.addOverride(1234L, "com.some.package", false); @@ -191,6 +205,10 @@ public class CompatConfigTest { CompatConfig compatConfig = new CompatConfig(mBuildClassifier, mContext); compatConfig.forceNonDebuggableFinalForTest(false); + ApplicationInfo info = ApplicationInfoBuilder.create() + .withPackageName("com.some.package").build(); + when(mPackageManager.getApplicationInfo(eq("com.some.package"), anyInt())) + .thenReturn(info); compatConfig.addOverride(1234L, "com.some.package", false); @@ -264,6 +282,71 @@ public class CompatConfigTest { assertThat(compatConfig.isChangeEnabled(1234L, applicationInfo)).isTrue(); } + @Test + public void testOverrideWithAppVersion() throws Exception { + ApplicationInfo applicationInfo = ApplicationInfoBuilder.create() + .withPackageName("com.installed.foo") + .withVersionCode(100L) + .debuggable().build(); + when(mPackageManager.getApplicationInfo(eq("com.installed.foo"), anyInt())) + .thenReturn(applicationInfo); + CompatConfig compatConfig = CompatConfigBuilder.create(mBuildClassifier, mContext) + .addDisabledChangeWithId(1234L).build(); + when(mBuildClassifier.isDebuggableBuild()).thenReturn(false); + when(mBuildClassifier.isFinalBuild()).thenReturn(true); + + // Add override that doesn't include the installed app version + CompatibilityOverrideConfig config = new CompatibilityOverrideConfig( + Collections.singletonMap(1234L, + new PackageOverride.Builder() + .setMaxVersionCode(99L) + .setEnabled(true) + .build())); + compatConfig.addOverrides(config, "com.installed.foo"); + assertThat(compatConfig.isChangeEnabled(1234L, applicationInfo)).isFalse(); + + // Add override that does include the installed app version + config = new CompatibilityOverrideConfig( + Collections.singletonMap(1234L, + new PackageOverride.Builder() + .setMinVersionCode(100L) + .setMaxVersionCode(100L) + .setEnabled(true) + .build())); + compatConfig.addOverrides(config, "com.installed.foo"); + assertThat(compatConfig.isChangeEnabled(1234L, applicationInfo)).isTrue(); + } + + @Test + public void testApplyDeferredOverridesAfterInstallingAppVersion() throws Exception { + ApplicationInfo applicationInfo = ApplicationInfoBuilder.create() + .withPackageName("com.notinstalled.foo") + .withVersionCode(100L) + .debuggable().build(); + when(mPackageManager.getApplicationInfo(eq("com.notinstalled.foo"), anyInt())) + .thenThrow(new NameNotFoundException()); + CompatConfig compatConfig = CompatConfigBuilder.create(mBuildClassifier, mContext) + .addDisabledChangeWithId(1234L).build(); + when(mBuildClassifier.isDebuggableBuild()).thenReturn(false); + when(mBuildClassifier.isFinalBuild()).thenReturn(true); + + // Add override before the app is available. + CompatibilityOverrideConfig config = new CompatibilityOverrideConfig( + Collections.singletonMap(1234L, new PackageOverride.Builder() + .setMaxVersionCode(99L) + .setEnabled(true) + .build())); + compatConfig.addOverrides(config, "com.notinstalled.foo"); + assertThat(compatConfig.isChangeEnabled(1234L, applicationInfo)).isFalse(); + + // Pretend the app is now installed. + when(mPackageManager.getApplicationInfo(eq("com.notinstalled.foo"), anyInt())) + .thenReturn(applicationInfo); + + compatConfig.recheckOverrides("com.notinstalled.foo"); + assertThat(compatConfig.isChangeEnabled(1234L, applicationInfo)).isFalse(); + } + @Test public void testApplyDeferredOverrideClearsOverrideAfterUninstall() throws Exception { ApplicationInfo applicationInfo = ApplicationInfoBuilder.create() @@ -384,6 +467,8 @@ public class CompatConfigTest { ApplicationInfo applicationInfo = ApplicationInfoBuilder.create() .withPackageName("com.some.package") .build(); + when(mPackageManager.getApplicationInfo(eq("com.some.package"), anyInt())) + .thenReturn(applicationInfo); assertThat(compatConfig.addOverride(1234L, "com.some.package", false)).isTrue(); assertThat(compatConfig.isChangeEnabled(1234L, applicationInfo)).isFalse(); @@ -404,6 +489,8 @@ public class CompatConfigTest { .withPackageName("foo.bar") .withTargetSdk(2) .build(); + when(mPackageManager.getApplicationInfo(eq("foo.bar"), anyInt())) + .thenReturn(applicationInfo); assertThat(compatConfig.isChangeEnabled(3, applicationInfo)).isFalse(); assertThat(compatConfig.isChangeEnabled(4, applicationInfo)).isFalse(); @@ -425,7 +512,8 @@ public class CompatConfigTest { .withPackageName("foo.bar") .withTargetSdk(2) .build(); - + when(mPackageManager.getApplicationInfo(eq("foo.bar"), anyInt())) + .thenReturn(applicationInfo); assertThat(compatConfig.enableTargetSdkChangesForPackage("foo.bar", 3)).isEqualTo(1); assertThat(compatConfig.isChangeEnabled(3, applicationInfo)).isTrue(); assertThat(compatConfig.isChangeEnabled(4, applicationInfo)).isFalse(); @@ -533,22 +621,114 @@ public class CompatConfigTest { + " <override-value packageName=\"foo.bar\" enabled=\"true\">\n" + " </override-value>\n" + " </validated>\n" - + " <deferred>\n" - + " </deferred>\n" + + " <raw>\n" + + " <raw-override-value packageName=\"foo.bar\" " + + "minVersionCode=\"-9223372036854775808\" " + + "maxVersionCode=\"9223372036854775807\" enabled=\"true\">\n" + + " </raw-override-value>\n" + + " </raw>\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" + + " <raw>\n" + + " <raw-override-value packageName=\"bar.baz\" " + + "minVersionCode=\"-9223372036854775808\" " + + "maxVersionCode=\"9223372036854775807\" enabled=\"false\">\n" + + " </raw-override-value>\n" + + " </raw>\n" + + " </change-overrides>\n" + + "</overrides>\n"); + } + + @Test + public void testSaveOverridesWithRanges() 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); + + compatConfig.addOverrides(new CompatibilityOverrideConfig(Collections.singletonMap(1L, + new PackageOverride.Builder() + .setMinVersionCode(99L) + .setMaxVersionCode(101L) + .setEnabled(true) + .build())), "foo.bar"); + + assertThat(readFile(overridesFile)).isEqualTo("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" + + "<overrides>\n" + + " <change-overrides changeId=\"1\">\n" + + " <validated>\n" + + " </validated>\n" + + " <raw>\n" + + " <raw-override-value packageName=\"foo.bar\" " + + "minVersionCode=\"99\" maxVersionCode=\"101\" enabled=\"true\">\n" + + " </raw-override-value>\n" + + " </raw>\n" + " </change-overrides>\n" + "</overrides>\n"); } @Test - public void testLoadOverrides() throws Exception { + public void testLoadOverridesRaw() 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>\n" + + " <change-overrides changeId=\"1\">\n" + + " <validated>\n" + + " <override-value packageName=\"foo.bar\" enabled=\"true\">\n" + + " </override-value>\n" + + " </validated>\n" + + " <raw>\n" + + " <raw-override-value packageName=\"foo.bar\" " + + "minVersionCode=\"-9223372036854775808\" " + + "maxVersionCode=\"9223372036854775807\" enabled=\"true\">\n" + + " </raw-override-value>\n" + + " </raw>\n" + + " </change-overrides>\n" + + " <change-overrides changeId=\"2\">\n" + + " <validated>\n" + + " </validated>\n" + + " <raw>\n" + + " <raw-override-value packageName=\"bar.baz\" " + + "minVersionCode=\"-9223372036854775808\" " + + "maxVersionCode=\"9223372036854775807\" enabled=\"false\">\n" + + " </raw-override-value>\n" + + " </raw>\n" + + " </change-overrides>\n" + + "</overrides>\n"; + 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") + .withVersionCode(100L) + .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(); + + compatConfig.recheckOverrides("foo.bar"); + assertThat(compatConfig.isChangeEnabled(1L, applicationInfo)).isTrue(); + } + + @Test + public void testLoadOverridesDeferred() throws Exception { File tempDir = createTempDir(); File overridesFile = new File(tempDir, "overrides.xml"); // Change 1 is enabled for foo.bar (validated) diff --git a/services/tests/servicestests/src/com/android/server/compat/PlatformCompatTest.java b/services/tests/servicestests/src/com/android/server/compat/PlatformCompatTest.java index a1b2dc8bd82db09e453aba43219f05d2a12ba19d..799b06734b5464e25b9b0c12b2ef6d9acc199a32 100644 --- a/services/tests/servicestests/src/com/android/server/compat/PlatformCompatTest.java +++ b/services/tests/servicestests/src/com/android/server/compat/PlatformCompatTest.java @@ -196,6 +196,9 @@ public class PlatformCompatTest { mPlatformCompat.registerListener(1, mListener1); mPlatformCompat.registerListener(2, mListener1); + when(mPackageManager.getApplicationInfo(eq(PACKAGE_NAME), anyInt())) + .thenReturn(ApplicationInfoBuilder.create().withPackageName(PACKAGE_NAME).build()); + mPlatformCompat.setOverrides( CompatibilityChangeConfigBuilder.create().enable(1L).disable(2L).build(), PACKAGE_NAME); @@ -208,6 +211,9 @@ public class PlatformCompatTest { mPlatformCompat.registerListener(1, mListener1); mPlatformCompat.registerListener(2, mListener1); + when(mPackageManager.getApplicationInfo(eq(PACKAGE_NAME), anyInt())) + .thenReturn(ApplicationInfoBuilder.create().withPackageName(PACKAGE_NAME).build()); + mPlatformCompat.setOverrides( CompatibilityChangeConfigBuilder.create().enable(1L).disable(2L).build(), PACKAGE_NAME); @@ -219,6 +225,9 @@ public class PlatformCompatTest { public void testListenerCalledOnSetOverridesTwoListeners() throws Exception { mPlatformCompat.registerListener(1, mListener1); + when(mPackageManager.getApplicationInfo(eq(PACKAGE_NAME), anyInt())) + .thenReturn(ApplicationInfoBuilder.create().withPackageName(PACKAGE_NAME).build()); + mPlatformCompat.setOverrides( CompatibilityChangeConfigBuilder.create().enable(1L).disable(2L).build(), PACKAGE_NAME); @@ -244,6 +253,9 @@ public class PlatformCompatTest { mPlatformCompat.registerListener(1, mListener1); mPlatformCompat.registerListener(2, mListener1); + when(mPackageManager.getApplicationInfo(eq(PACKAGE_NAME), anyInt())) + .thenReturn(ApplicationInfoBuilder.create().withPackageName(PACKAGE_NAME).build()); + mPlatformCompat.setOverrides( CompatibilityChangeConfigBuilder.create().enable(1L).disable(2L).build(), PACKAGE_NAME); @@ -252,9 +264,12 @@ public class PlatformCompatTest { } @Test - public void testListenerCalledOnSetOverridesTwoListenersForTest() throws Exception { + public void testListenerCalledOnSetOverridesForTestTwoListeners() throws Exception { mPlatformCompat.registerListener(1, mListener1); + when(mPackageManager.getApplicationInfo(eq(PACKAGE_NAME), anyInt())) + .thenReturn(ApplicationInfoBuilder.create().withPackageName(PACKAGE_NAME).build()); + mPlatformCompat.setOverrides( CompatibilityChangeConfigBuilder.create().enable(1L).disable(2L).build(), PACKAGE_NAME); @@ -280,6 +295,9 @@ public class PlatformCompatTest { mPlatformCompat.registerListener(1, mListener1); mPlatformCompat.registerListener(2, mListener2); + when(mPackageManager.getApplicationInfo(eq(PACKAGE_NAME), anyInt())) + .thenReturn(ApplicationInfoBuilder.create().withPackageName(PACKAGE_NAME).build()); + mPlatformCompat.setOverrides( CompatibilityChangeConfigBuilder.create().enable(1L).build(), PACKAGE_NAME); @@ -299,6 +317,9 @@ public class PlatformCompatTest { mPlatformCompat.registerListener(1, mListener1); mPlatformCompat.registerListener(2, mListener2); + when(mPackageManager.getApplicationInfo(eq(PACKAGE_NAME), anyInt())) + .thenReturn(ApplicationInfoBuilder.create().withPackageName(PACKAGE_NAME).build()); + mPlatformCompat.setOverrides( CompatibilityChangeConfigBuilder.create().enable(1L).disable(2L).build(), PACKAGE_NAME); @@ -318,6 +339,9 @@ public class PlatformCompatTest { mPlatformCompat.registerListener(1, mListener1); mPlatformCompat.registerListener(2, mListener2); + when(mPackageManager.getApplicationInfo(eq(PACKAGE_NAME), anyInt())) + .thenReturn(ApplicationInfoBuilder.create().withPackageName(PACKAGE_NAME).build()); + mPlatformCompat.setOverrides( CompatibilityChangeConfigBuilder.create().enable(1L).build(), PACKAGE_NAME); @@ -336,6 +360,9 @@ public class PlatformCompatTest { public void testListenerCalledOnClearOverrideDoesntExist() throws Exception { mPlatformCompat.registerListener(1, mListener1); + when(mPackageManager.getApplicationInfo(eq(PACKAGE_NAME), anyInt())) + .thenReturn(ApplicationInfoBuilder.create().withPackageName(PACKAGE_NAME).build()); + mPlatformCompat.clearOverride(1, PACKAGE_NAME); // Listener not called when a non existing override is removed. verify(mListener1, never()).onCompatChange(PACKAGE_NAME); diff --git a/tests/PlatformCompatGating/src/com/android/tests/gating/PlatformCompatCommandNotInstalledTest.kt b/tests/PlatformCompatGating/src/com/android/tests/gating/PlatformCompatCommandNotInstalledTest.kt index eb04f6907748221187275215b593f79fa830b069..ac9e6817a230de997241fb48d1137115d0891ba0 100644 --- a/tests/PlatformCompatGating/src/com/android/tests/gating/PlatformCompatCommandNotInstalledTest.kt +++ b/tests/PlatformCompatGating/src/com/android/tests/gating/PlatformCompatCommandNotInstalledTest.kt @@ -76,11 +76,11 @@ class PlatformCompatCommandNotInstalledTest { Params(enableDisable = null, targetSdk = 29, result = false), Params(enableDisable = null, targetSdk = 30, result = true), - Params(enableDisable = true, targetSdk = 29, result = true), + Params(enableDisable = true, targetSdk = 29, result = false), Params(enableDisable = true, targetSdk = 30, result = true), Params(enableDisable = false, targetSdk = 29, result = false), - Params(enableDisable = false, targetSdk = 30, result = false) + Params(enableDisable = false, targetSdk = 30, result = true) ) }