Loading services/core/java/com/android/server/compat/CompatChange.java 0 → 100644 +139 −0 Original line number Diff line number Diff line /* * 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. */ package com.android.server.compat; import android.annotation.Nullable; import android.compat.annotation.EnabledAfter; import android.content.pm.ApplicationInfo; import java.util.HashMap; import java.util.Map; /** * Represents the state of a single compatibility change. * * <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)}. * * <p>Note, this class is not thread safe so callers must ensure thread safety. */ public final class CompatChange { private final long mChangeId; @Nullable private final String mName; private final int mEnableAfterTargetSdk; private final boolean mDisabled; private Map<String, Boolean> mPackageOverrides; public CompatChange(long changeId) { this(changeId, null, -1, false); } /** * @param changeId Unique ID for the change. See {@link android.compat.Compatibility}. * @param name Short descriptive name. * @param enableAfterTargetSdk {@code targetSdkVersion} restriction. See {@link EnabledAfter}; * -1 if the change is always enabled. * @param disabled If {@code true}, overrides any {@code enableAfterTargetSdk} set. */ public CompatChange(long changeId, @Nullable String name, int enableAfterTargetSdk, boolean disabled) { mChangeId = changeId; mName = name; mEnableAfterTargetSdk = enableAfterTargetSdk; mDisabled = disabled; } long getId() { return mChangeId; } @Nullable String getName() { return mName; } /** * Force the enabled state of this change for a given package name. The change will only take * effect after that packages process is killed and restarted. * * <p>Note, this method is not thread safe so callers must ensure thread safety. * * @param pname Package name to enable the change for. * @param enabled Whether or not to enable the change. */ void addPackageOverride(String pname, boolean enabled) { if (mPackageOverrides == null) { mPackageOverrides = new HashMap<>(); } mPackageOverrides.put(pname, enabled); } /** * Remove any package override for the given package name, restoring the default behaviour. * * <p>Note, this method is not thread safe so callers must ensure thread safety. * * @param pname Package name to reset to defaults for. */ void removePackageOverride(String pname) { if (mPackageOverrides != null) { mPackageOverrides.remove(pname); } } /** * Find if this change is enabled for the given package, taking into account any overrides that * exist. * * @param app Info about the app in question * @return {@code true} if the change should be enabled for the package. */ boolean isEnabled(ApplicationInfo app) { if (mPackageOverrides != null && mPackageOverrides.containsKey(app.packageName)) { return mPackageOverrides.get(app.packageName); } if (mDisabled) { return false; } if (mEnableAfterTargetSdk != -1) { return app.targetSdkVersion > mEnableAfterTargetSdk; } return true; } @Override public String toString() { StringBuilder sb = new StringBuilder("ChangeId(") .append(mChangeId); if (mName != null) { sb.append("; name=").append(mName); } if (mEnableAfterTargetSdk != -1) { sb.append("; enableAfterTargetSdk=").append(mEnableAfterTargetSdk); } if (mDisabled) { sb.append("; disabled"); } if (mPackageOverrides != null && mPackageOverrides.size() > 0) { sb.append("; packageOverrides=").append(mPackageOverrides); } return sb.append(")").toString(); } } services/core/java/com/android/server/compat/CompatConfig.java 0 → 100644 +164 −0 Original line number Diff line number Diff line /* * 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. */ package com.android.server.compat; import android.content.pm.ApplicationInfo; import android.text.TextUtils; import android.util.LongArray; import android.util.LongSparseArray; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; /** * This class maintains state relating to platform compatibility changes. * * <p>It stores the default configuration for each change, and any per-package overrides that have * been configured. */ public final class CompatConfig { private static final CompatConfig sInstance = new CompatConfig(); @GuardedBy("mChanges") private final LongSparseArray<CompatChange> mChanges = new LongSparseArray<>(); @VisibleForTesting public CompatConfig() { } /** * @return The static instance of this class to be used within the system server. */ public static CompatConfig get() { return sInstance; } /** * Add a change. This is intended to be used by code that reads change config from the * filesystem. This should be done at system startup time. * * @param change The change to add. Any change with the same ID will be overwritten. */ public void addChange(CompatChange change) { synchronized (mChanges) { mChanges.put(change.getId(), change); } } /** * Retrieves the set of disabled changes for a given app. Any change ID not in the returned * array is by default enabled for the app. * * @param app The app in question * @return A sorted long array of change IDs. We use a primitive array to minimize memory * footprint: Every app process will store this array statically so we aim to reduce * overhead as much as possible. */ public long[] getDisabledChanges(ApplicationInfo app) { LongArray disabled = new LongArray(); synchronized (mChanges) { for (int i = 0; i < mChanges.size(); ++i) { CompatChange c = mChanges.valueAt(i); if (!c.isEnabled(app)) { disabled.add(c.getId()); } } } // Note: we don't need to explicitly sort the array, as the behaviour of LongSparseArray // (mChanges) ensures it's already sorted. return disabled.toArray(); } /** * Look up a change ID by name. * * @param name Name of the change to look up * @return The change ID, or {@code -1} if no change with that name exists. */ public long lookupChangeId(String name) { synchronized (mChanges) { for (int i = 0; i < mChanges.size(); ++i) { if (TextUtils.equals(mChanges.valueAt(i).getName(), name)) { return mChanges.keyAt(i); } } } return -1; } /** * Find if a given change is enabled for a given application. * * @param changeId The ID of the change in question * @param app App to check for * @return {@code true} if the change is enabled for this app. Also returns {@code true} if the * change ID is not known, as unknown changes are enabled by default. */ public boolean isChangeEnabled(long changeId, ApplicationInfo app) { synchronized (mChanges) { CompatChange c = mChanges.get(changeId); if (c == null) { // we know nothing about this change: default behaviour is enabled. return true; } return c.isEnabled(app); } } /** * Overrides the enabled state for a given change and app. This method is intended to be used * *only* for debugging purposes, ultimately invoked either by an adb command, or from some * developer settings UI. * * <p>Note, package overrides are not persistent and will be lost on system or runtime restart. * * @param changeId The ID of the change to be overridden. Note, this call will succeed even if * this change is not known; it will only have any affect if any code in the * platform is gated on the ID given. * @param packageName The app package name to override the change for. * @param enabled If the change should be enabled or disabled. */ public void addOverride(long changeId, String packageName, boolean enabled) { synchronized (mChanges) { CompatChange c = mChanges.get(changeId); if (c == null) { c = new CompatChange(changeId); addChange(c); } c.addPackageOverride(packageName, enabled); } } /** * Removes an override previously added via {@link #addOverride(long, String, boolean)}. This * restores the default behaviour for the given change and app, once any app processes have been * restarted. * * @param changeId The ID of the change that was overridden. * @param packageName The app package name that was overridden. */ public void removeOverride(long changeId, String packageName) { synchronized (mChanges) { CompatChange c = mChanges.get(changeId); if (c != null) { c.removePackageOverride(packageName); } } } } services/core/java/com/android/server/compat/PlatformCompat.java 0 → 100644 +67 −0 Original line number Diff line number Diff line /* * 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. */ package com.android.server.compat; import android.content.pm.ApplicationInfo; import android.util.Slog; /** * System server internal API for gating and reporting compatibility changes. */ public class PlatformCompat { private static final String TAG = "Compatibility"; /** * Reports that a compatibility change is affecting an app process now. * * <p>Note: for changes that are gated using {@link #isChangeEnabled(long, ApplicationInfo)}, * you do not need to call this API directly. The change will be reported for you in the case * that {@link #isChangeEnabled(long, ApplicationInfo)} returns {@code true}. * * @param changeId The ID of the compatibility change taking effect. * @param appInfo Representing the affected app. */ public static void reportChange(long changeId, ApplicationInfo appInfo) { Slog.d(TAG, "Compat change reported: " + changeId + "; UID " + appInfo.uid); // TODO log via StatsLog } /** * Query if a given compatibility change is enabled for an app process. This method should * be called when implementing functionality on behalf of the affected app. * * <p>If this method returns {@code true}, the calling code should implement the compatibility * change, resulting in differing behaviour compared to earlier releases. If this method returns * {@code false}, the calling code should behave as it did in earlier releases. * * <p>When this method returns {@code true}, it will also report the change as * {@link #reportChange(long, ApplicationInfo)} would, so there is no need to call that method * directly. * * @param changeId The ID of the compatibility change in question. * @param appInfo Representing the app in question. * @return {@code true} if the change is enabled for the current app. */ public static boolean isChangeEnabled(long changeId, ApplicationInfo appInfo) { if (CompatConfig.get().isChangeEnabled(changeId, appInfo)) { reportChange(changeId, appInfo); return true; } return false; } } services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java 0 → 100644 +145 −0 Original line number Diff line number Diff line /* * 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. */ package com.android.server.compat; import static com.google.common.truth.Truth.assertThat; import android.content.pm.ApplicationInfo; import androidx.test.runner.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; @RunWith(AndroidJUnit4.class) public class CompatConfigTest { private ApplicationInfo makeAppInfo(String pName, int targetSdkVersion) { ApplicationInfo ai = new ApplicationInfo(); ai.packageName = pName; ai.targetSdkVersion = targetSdkVersion; return ai; } @Test public void testUnknownChangeEnabled() { CompatConfig pc = new CompatConfig(); assertThat(pc.isChangeEnabled(1234L, makeAppInfo("com.some.package", 1))).isTrue(); } @Test public void testDisabledChangeDisabled() { CompatConfig pc = new CompatConfig(); pc.addChange(new CompatChange(1234L, "MY_CHANGE", -1, true)); assertThat(pc.isChangeEnabled(1234L, makeAppInfo("com.some.package", 1))).isFalse(); } @Test public void testTargetSdkChangeDisabled() { CompatConfig pc = new CompatConfig(); pc.addChange(new CompatChange(1234L, "MY_CHANGE", 2, false)); assertThat(pc.isChangeEnabled(1234L, makeAppInfo("com.some.package", 2))).isFalse(); } @Test public void testTargetSdkChangeEnabled() { CompatConfig pc = new CompatConfig(); pc.addChange(new CompatChange(1234L, "MY_CHANGE", 2, false)); assertThat(pc.isChangeEnabled(1234L, makeAppInfo("com.some.package", 3))).isTrue(); } @Test public void testDisabledOverrideTargetSdkChange() { CompatConfig pc = new CompatConfig(); pc.addChange(new CompatChange(1234L, "MY_CHANGE", 2, true)); assertThat(pc.isChangeEnabled(1234L, makeAppInfo("com.some.package", 3))).isFalse(); } @Test public void testGetDisabledChanges() { CompatConfig pc = new CompatConfig(); pc.addChange(new CompatChange(1234L, "MY_CHANGE", -1, true)); pc.addChange(new CompatChange(2345L, "OTHER_CHANGE", -1, false)); assertThat(pc.getDisabledChanges( makeAppInfo("com.some.package", 2))).asList().containsExactly(1234L); } @Test public void testGetDisabledChangesSorted() { CompatConfig pc = new CompatConfig(); pc.addChange(new CompatChange(1234L, "MY_CHANGE", 2, true)); pc.addChange(new CompatChange(123L, "OTHER_CHANGE", 2, true)); pc.addChange(new CompatChange(12L, "THIRD_CHANGE", 2, true)); assertThat(pc.getDisabledChanges( makeAppInfo("com.some.package", 2))).asList().containsExactly(12L, 123L, 1234L); } @Test public void testPackageOverrideEnabled() { CompatConfig pc = new CompatConfig(); pc.addChange(new CompatChange(1234L, "MY_CHANGE", -1, true)); // disabled pc.addOverride(1234L, "com.some.package", true); assertThat(pc.isChangeEnabled(1234L, makeAppInfo("com.some.package", 2))).isTrue(); assertThat(pc.isChangeEnabled(1234L, makeAppInfo("com.other.package", 2))).isFalse(); } @Test public void testPackageOverrideDisabled() { CompatConfig pc = new CompatConfig(); pc.addChange(new CompatChange(1234L, "MY_CHANGE", -1, false)); pc.addOverride(1234L, "com.some.package", false); assertThat(pc.isChangeEnabled(1234L, makeAppInfo("com.some.package", 2))).isFalse(); assertThat(pc.isChangeEnabled(1234L, makeAppInfo("com.other.package", 2))).isTrue(); } @Test public void testPackageOverrideUnknownPackage() { CompatConfig pc = new CompatConfig(); pc.addOverride(1234L, "com.some.package", false); assertThat(pc.isChangeEnabled(1234L, makeAppInfo("com.some.package", 2))).isFalse(); assertThat(pc.isChangeEnabled(1234L, makeAppInfo("com.other.package", 2))).isTrue(); } @Test public void testPackageOverrideUnknownChange() { CompatConfig pc = new CompatConfig(); assertThat(pc.isChangeEnabled(1234L, makeAppInfo("com.some.package", 1))).isTrue(); } @Test public void testRemovePackageOverride() { CompatConfig pc = new CompatConfig(); pc.addChange(new CompatChange(1234L, "MY_CHANGE", -1, false)); pc.addOverride(1234L, "com.some.package", false); pc.removeOverride(1234L, "com.some.package"); assertThat(pc.isChangeEnabled(1234L, makeAppInfo("com.some.package", 2))).isTrue(); } @Test public void testLookupChangeId() { CompatConfig pc = new CompatConfig(); pc.addChange(new CompatChange(1234L, "MY_CHANGE", -1, false)); pc.addChange(new CompatChange(2345L, "ANOTHER_CHANGE", -1, false)); assertThat(pc.lookupChangeId("MY_CHANGE")).isEqualTo(1234L); } @Test public void testLookupChangeIdNotPresent() { CompatConfig pc = new CompatConfig(); assertThat(pc.lookupChangeId("MY_CHANGE")).isEqualTo(-1L); } } Loading
services/core/java/com/android/server/compat/CompatChange.java 0 → 100644 +139 −0 Original line number Diff line number Diff line /* * 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. */ package com.android.server.compat; import android.annotation.Nullable; import android.compat.annotation.EnabledAfter; import android.content.pm.ApplicationInfo; import java.util.HashMap; import java.util.Map; /** * Represents the state of a single compatibility change. * * <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)}. * * <p>Note, this class is not thread safe so callers must ensure thread safety. */ public final class CompatChange { private final long mChangeId; @Nullable private final String mName; private final int mEnableAfterTargetSdk; private final boolean mDisabled; private Map<String, Boolean> mPackageOverrides; public CompatChange(long changeId) { this(changeId, null, -1, false); } /** * @param changeId Unique ID for the change. See {@link android.compat.Compatibility}. * @param name Short descriptive name. * @param enableAfterTargetSdk {@code targetSdkVersion} restriction. See {@link EnabledAfter}; * -1 if the change is always enabled. * @param disabled If {@code true}, overrides any {@code enableAfterTargetSdk} set. */ public CompatChange(long changeId, @Nullable String name, int enableAfterTargetSdk, boolean disabled) { mChangeId = changeId; mName = name; mEnableAfterTargetSdk = enableAfterTargetSdk; mDisabled = disabled; } long getId() { return mChangeId; } @Nullable String getName() { return mName; } /** * Force the enabled state of this change for a given package name. The change will only take * effect after that packages process is killed and restarted. * * <p>Note, this method is not thread safe so callers must ensure thread safety. * * @param pname Package name to enable the change for. * @param enabled Whether or not to enable the change. */ void addPackageOverride(String pname, boolean enabled) { if (mPackageOverrides == null) { mPackageOverrides = new HashMap<>(); } mPackageOverrides.put(pname, enabled); } /** * Remove any package override for the given package name, restoring the default behaviour. * * <p>Note, this method is not thread safe so callers must ensure thread safety. * * @param pname Package name to reset to defaults for. */ void removePackageOverride(String pname) { if (mPackageOverrides != null) { mPackageOverrides.remove(pname); } } /** * Find if this change is enabled for the given package, taking into account any overrides that * exist. * * @param app Info about the app in question * @return {@code true} if the change should be enabled for the package. */ boolean isEnabled(ApplicationInfo app) { if (mPackageOverrides != null && mPackageOverrides.containsKey(app.packageName)) { return mPackageOverrides.get(app.packageName); } if (mDisabled) { return false; } if (mEnableAfterTargetSdk != -1) { return app.targetSdkVersion > mEnableAfterTargetSdk; } return true; } @Override public String toString() { StringBuilder sb = new StringBuilder("ChangeId(") .append(mChangeId); if (mName != null) { sb.append("; name=").append(mName); } if (mEnableAfterTargetSdk != -1) { sb.append("; enableAfterTargetSdk=").append(mEnableAfterTargetSdk); } if (mDisabled) { sb.append("; disabled"); } if (mPackageOverrides != null && mPackageOverrides.size() > 0) { sb.append("; packageOverrides=").append(mPackageOverrides); } return sb.append(")").toString(); } }
services/core/java/com/android/server/compat/CompatConfig.java 0 → 100644 +164 −0 Original line number Diff line number Diff line /* * 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. */ package com.android.server.compat; import android.content.pm.ApplicationInfo; import android.text.TextUtils; import android.util.LongArray; import android.util.LongSparseArray; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; /** * This class maintains state relating to platform compatibility changes. * * <p>It stores the default configuration for each change, and any per-package overrides that have * been configured. */ public final class CompatConfig { private static final CompatConfig sInstance = new CompatConfig(); @GuardedBy("mChanges") private final LongSparseArray<CompatChange> mChanges = new LongSparseArray<>(); @VisibleForTesting public CompatConfig() { } /** * @return The static instance of this class to be used within the system server. */ public static CompatConfig get() { return sInstance; } /** * Add a change. This is intended to be used by code that reads change config from the * filesystem. This should be done at system startup time. * * @param change The change to add. Any change with the same ID will be overwritten. */ public void addChange(CompatChange change) { synchronized (mChanges) { mChanges.put(change.getId(), change); } } /** * Retrieves the set of disabled changes for a given app. Any change ID not in the returned * array is by default enabled for the app. * * @param app The app in question * @return A sorted long array of change IDs. We use a primitive array to minimize memory * footprint: Every app process will store this array statically so we aim to reduce * overhead as much as possible. */ public long[] getDisabledChanges(ApplicationInfo app) { LongArray disabled = new LongArray(); synchronized (mChanges) { for (int i = 0; i < mChanges.size(); ++i) { CompatChange c = mChanges.valueAt(i); if (!c.isEnabled(app)) { disabled.add(c.getId()); } } } // Note: we don't need to explicitly sort the array, as the behaviour of LongSparseArray // (mChanges) ensures it's already sorted. return disabled.toArray(); } /** * Look up a change ID by name. * * @param name Name of the change to look up * @return The change ID, or {@code -1} if no change with that name exists. */ public long lookupChangeId(String name) { synchronized (mChanges) { for (int i = 0; i < mChanges.size(); ++i) { if (TextUtils.equals(mChanges.valueAt(i).getName(), name)) { return mChanges.keyAt(i); } } } return -1; } /** * Find if a given change is enabled for a given application. * * @param changeId The ID of the change in question * @param app App to check for * @return {@code true} if the change is enabled for this app. Also returns {@code true} if the * change ID is not known, as unknown changes are enabled by default. */ public boolean isChangeEnabled(long changeId, ApplicationInfo app) { synchronized (mChanges) { CompatChange c = mChanges.get(changeId); if (c == null) { // we know nothing about this change: default behaviour is enabled. return true; } return c.isEnabled(app); } } /** * Overrides the enabled state for a given change and app. This method is intended to be used * *only* for debugging purposes, ultimately invoked either by an adb command, or from some * developer settings UI. * * <p>Note, package overrides are not persistent and will be lost on system or runtime restart. * * @param changeId The ID of the change to be overridden. Note, this call will succeed even if * this change is not known; it will only have any affect if any code in the * platform is gated on the ID given. * @param packageName The app package name to override the change for. * @param enabled If the change should be enabled or disabled. */ public void addOverride(long changeId, String packageName, boolean enabled) { synchronized (mChanges) { CompatChange c = mChanges.get(changeId); if (c == null) { c = new CompatChange(changeId); addChange(c); } c.addPackageOverride(packageName, enabled); } } /** * Removes an override previously added via {@link #addOverride(long, String, boolean)}. This * restores the default behaviour for the given change and app, once any app processes have been * restarted. * * @param changeId The ID of the change that was overridden. * @param packageName The app package name that was overridden. */ public void removeOverride(long changeId, String packageName) { synchronized (mChanges) { CompatChange c = mChanges.get(changeId); if (c != null) { c.removePackageOverride(packageName); } } } }
services/core/java/com/android/server/compat/PlatformCompat.java 0 → 100644 +67 −0 Original line number Diff line number Diff line /* * 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. */ package com.android.server.compat; import android.content.pm.ApplicationInfo; import android.util.Slog; /** * System server internal API for gating and reporting compatibility changes. */ public class PlatformCompat { private static final String TAG = "Compatibility"; /** * Reports that a compatibility change is affecting an app process now. * * <p>Note: for changes that are gated using {@link #isChangeEnabled(long, ApplicationInfo)}, * you do not need to call this API directly. The change will be reported for you in the case * that {@link #isChangeEnabled(long, ApplicationInfo)} returns {@code true}. * * @param changeId The ID of the compatibility change taking effect. * @param appInfo Representing the affected app. */ public static void reportChange(long changeId, ApplicationInfo appInfo) { Slog.d(TAG, "Compat change reported: " + changeId + "; UID " + appInfo.uid); // TODO log via StatsLog } /** * Query if a given compatibility change is enabled for an app process. This method should * be called when implementing functionality on behalf of the affected app. * * <p>If this method returns {@code true}, the calling code should implement the compatibility * change, resulting in differing behaviour compared to earlier releases. If this method returns * {@code false}, the calling code should behave as it did in earlier releases. * * <p>When this method returns {@code true}, it will also report the change as * {@link #reportChange(long, ApplicationInfo)} would, so there is no need to call that method * directly. * * @param changeId The ID of the compatibility change in question. * @param appInfo Representing the app in question. * @return {@code true} if the change is enabled for the current app. */ public static boolean isChangeEnabled(long changeId, ApplicationInfo appInfo) { if (CompatConfig.get().isChangeEnabled(changeId, appInfo)) { reportChange(changeId, appInfo); return true; } return false; } }
services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java 0 → 100644 +145 −0 Original line number Diff line number Diff line /* * 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. */ package com.android.server.compat; import static com.google.common.truth.Truth.assertThat; import android.content.pm.ApplicationInfo; import androidx.test.runner.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; @RunWith(AndroidJUnit4.class) public class CompatConfigTest { private ApplicationInfo makeAppInfo(String pName, int targetSdkVersion) { ApplicationInfo ai = new ApplicationInfo(); ai.packageName = pName; ai.targetSdkVersion = targetSdkVersion; return ai; } @Test public void testUnknownChangeEnabled() { CompatConfig pc = new CompatConfig(); assertThat(pc.isChangeEnabled(1234L, makeAppInfo("com.some.package", 1))).isTrue(); } @Test public void testDisabledChangeDisabled() { CompatConfig pc = new CompatConfig(); pc.addChange(new CompatChange(1234L, "MY_CHANGE", -1, true)); assertThat(pc.isChangeEnabled(1234L, makeAppInfo("com.some.package", 1))).isFalse(); } @Test public void testTargetSdkChangeDisabled() { CompatConfig pc = new CompatConfig(); pc.addChange(new CompatChange(1234L, "MY_CHANGE", 2, false)); assertThat(pc.isChangeEnabled(1234L, makeAppInfo("com.some.package", 2))).isFalse(); } @Test public void testTargetSdkChangeEnabled() { CompatConfig pc = new CompatConfig(); pc.addChange(new CompatChange(1234L, "MY_CHANGE", 2, false)); assertThat(pc.isChangeEnabled(1234L, makeAppInfo("com.some.package", 3))).isTrue(); } @Test public void testDisabledOverrideTargetSdkChange() { CompatConfig pc = new CompatConfig(); pc.addChange(new CompatChange(1234L, "MY_CHANGE", 2, true)); assertThat(pc.isChangeEnabled(1234L, makeAppInfo("com.some.package", 3))).isFalse(); } @Test public void testGetDisabledChanges() { CompatConfig pc = new CompatConfig(); pc.addChange(new CompatChange(1234L, "MY_CHANGE", -1, true)); pc.addChange(new CompatChange(2345L, "OTHER_CHANGE", -1, false)); assertThat(pc.getDisabledChanges( makeAppInfo("com.some.package", 2))).asList().containsExactly(1234L); } @Test public void testGetDisabledChangesSorted() { CompatConfig pc = new CompatConfig(); pc.addChange(new CompatChange(1234L, "MY_CHANGE", 2, true)); pc.addChange(new CompatChange(123L, "OTHER_CHANGE", 2, true)); pc.addChange(new CompatChange(12L, "THIRD_CHANGE", 2, true)); assertThat(pc.getDisabledChanges( makeAppInfo("com.some.package", 2))).asList().containsExactly(12L, 123L, 1234L); } @Test public void testPackageOverrideEnabled() { CompatConfig pc = new CompatConfig(); pc.addChange(new CompatChange(1234L, "MY_CHANGE", -1, true)); // disabled pc.addOverride(1234L, "com.some.package", true); assertThat(pc.isChangeEnabled(1234L, makeAppInfo("com.some.package", 2))).isTrue(); assertThat(pc.isChangeEnabled(1234L, makeAppInfo("com.other.package", 2))).isFalse(); } @Test public void testPackageOverrideDisabled() { CompatConfig pc = new CompatConfig(); pc.addChange(new CompatChange(1234L, "MY_CHANGE", -1, false)); pc.addOverride(1234L, "com.some.package", false); assertThat(pc.isChangeEnabled(1234L, makeAppInfo("com.some.package", 2))).isFalse(); assertThat(pc.isChangeEnabled(1234L, makeAppInfo("com.other.package", 2))).isTrue(); } @Test public void testPackageOverrideUnknownPackage() { CompatConfig pc = new CompatConfig(); pc.addOverride(1234L, "com.some.package", false); assertThat(pc.isChangeEnabled(1234L, makeAppInfo("com.some.package", 2))).isFalse(); assertThat(pc.isChangeEnabled(1234L, makeAppInfo("com.other.package", 2))).isTrue(); } @Test public void testPackageOverrideUnknownChange() { CompatConfig pc = new CompatConfig(); assertThat(pc.isChangeEnabled(1234L, makeAppInfo("com.some.package", 1))).isTrue(); } @Test public void testRemovePackageOverride() { CompatConfig pc = new CompatConfig(); pc.addChange(new CompatChange(1234L, "MY_CHANGE", -1, false)); pc.addOverride(1234L, "com.some.package", false); pc.removeOverride(1234L, "com.some.package"); assertThat(pc.isChangeEnabled(1234L, makeAppInfo("com.some.package", 2))).isTrue(); } @Test public void testLookupChangeId() { CompatConfig pc = new CompatConfig(); pc.addChange(new CompatChange(1234L, "MY_CHANGE", -1, false)); pc.addChange(new CompatChange(2345L, "ANOTHER_CHANGE", -1, false)); assertThat(pc.lookupChangeId("MY_CHANGE")).isEqualTo(1234L); } @Test public void testLookupChangeIdNotPresent() { CompatConfig pc = new CompatConfig(); assertThat(pc.lookupChangeId("MY_CHANGE")).isEqualTo(-1L); } }