From 502e74b10a2cec7a8c8334e31db3bc62c65d41c4 Mon Sep 17 00:00:00 2001 From: tomnatan Date: Mon, 28 Jun 2021 10:16:32 +0000 Subject: [PATCH] Apply/remove overrides for a package when it's added/changed/removed. Bug: 190024878 Test: atest FrameworksMockingServicesTests:AppCompatOverridesServiceTest Test: atest FrameworksMockingServicesTests:AppCompatOverridesParserTest Change-Id: I270a63ae09503d0ce6368b0b82510b36ae85b546 --- .../overrides/AppCompatOverridesService.java | 149 ++++++++- .../AppCompatOverridesServiceTest.java | 310 +++++++++++++++++- 2 files changed, 448 insertions(+), 11 deletions(-) diff --git a/services/core/java/com/android/server/compat/overrides/AppCompatOverridesService.java b/services/core/java/com/android/server/compat/overrides/AppCompatOverridesService.java index a81b18b690c45..63ae1af42f086 100644 --- a/services/core/java/com/android/server/compat/overrides/AppCompatOverridesService.java +++ b/services/core/java/com/android/server/compat/overrides/AppCompatOverridesService.java @@ -16,6 +16,9 @@ package com.android.server.compat.overrides; +import static android.content.Intent.ACTION_PACKAGE_ADDED; +import static android.content.Intent.ACTION_PACKAGE_CHANGED; +import static android.content.Intent.ACTION_PACKAGE_REMOVED; import static android.content.pm.PackageManager.MATCH_ANY_USER; import static android.provider.DeviceConfig.NAMESPACE_APP_COMPAT_OVERRIDES; @@ -24,11 +27,16 @@ import static com.android.server.compat.overrides.AppCompatOverridesParser.FLAG_ import static java.util.Collections.emptySet; +import android.annotation.NonNull; import android.annotation.Nullable; import android.app.compat.PackageOverride; +import android.content.BroadcastReceiver; import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; +import android.net.Uri; import android.os.RemoteException; import android.os.ServiceManager; import android.provider.DeviceConfig; @@ -67,8 +75,9 @@ public final class AppCompatOverridesService { private final PackageManager mPackageManager; private final IPlatformCompat mPlatformCompat; private final List mSupportedNamespaces; - private final List mDeviceConfigListeners; private final AppCompatOverridesParser mOverridesParser; + private final PackageReceiver mPackageReceiver; + private final List mDeviceConfigListeners; private AppCompatOverridesService(Context context) { this(context, IPlatformCompat.Stub.asInterface( @@ -82,31 +91,42 @@ public final class AppCompatOverridesService { mPackageManager = mContext.getPackageManager(); mPlatformCompat = platformCompat; mSupportedNamespaces = supportedNamespaces; - mDeviceConfigListeners = new ArrayList<>(); mOverridesParser = new AppCompatOverridesParser(mPackageManager); + mPackageReceiver = new PackageReceiver(mContext); + mDeviceConfigListeners = new ArrayList<>(); + for (String namespace : mSupportedNamespaces) { + mDeviceConfigListeners.add(new DeviceConfigListener(mContext, namespace)); + } } @Override public void finalize() { unregisterDeviceConfigListeners(); + unregisterPackageReceiver(); } @VisibleForTesting void registerDeviceConfigListeners() { - for (String namespace : mSupportedNamespaces) { - DeviceConfigListener listener = new DeviceConfigListener(namespace); - DeviceConfig.addOnPropertiesChangedListener(namespace, mContext.getMainExecutor(), - listener); - mDeviceConfigListeners.add(listener); + for (DeviceConfigListener listener : mDeviceConfigListeners) { + listener.register(); } } private void unregisterDeviceConfigListeners() { for (DeviceConfigListener listener : mDeviceConfigListeners) { - DeviceConfig.removeOnPropertiesChangedListener(listener); + listener.unregister(); } } + @VisibleForTesting + void registerPackageReceiver() { + mPackageReceiver.register(); + } + + private void unregisterPackageReceiver() { + mPackageReceiver.unregister(); + } + /** * Same as {@link #applyOverrides(Properties, Map)} except all properties of the given {@code * namespace} are fetched via {@link DeviceConfig#getProperties}. @@ -139,6 +159,25 @@ public final class AppCompatOverridesService { } } + /** + * Applies all overrides in all supported namespaces for the given {@code packageName}. + */ + private void applyAllPackageOverrides(String packageName) { + Long versionCode = getVersionCodeOrNull(packageName); + if (versionCode == null) { + return; + } + + for (String namespace : mSupportedNamespaces) { + // We apply overrides for each namespace separately so that if there is a failure for + // one namespace, the other namespaces won't be affected. + applyPackageOverrides( + DeviceConfig.getString(namespace, packageName, /* defaultValue= */ ""), + packageName, versionCode, + getOverridesToRemove(namespace).getOrDefault(packageName, emptySet())); + } + } + /** * Calls {@link AppCompatOverridesParser#parsePackageOverrides} on the given arguments, adds the * resulting {@link PackageOverrides#overridesToAdd} via {@link @@ -154,6 +193,24 @@ public final class AppCompatOverridesService { removePackageOverrides(packageName, packageOverrides.overridesToRemove); } + /** + * Removes all owned overrides in all supported namespaces for the given {@code packageName}. + * + *

If a certain namespace doesn't have a package override flag for the given {@code + * packageName}, that namespace is skipped.

+ */ + private void removeAllPackageOverrides(String packageName) { + for (String namespace : mSupportedNamespaces) { + if (DeviceConfig.getString(namespace, packageName, /* defaultValue= */ "").isEmpty()) { + // No overrides for this package in this namespace. + continue; + } + // We remove overrides for each namespace separately so that if there is a failure for + // one namespace, the other namespaces won't be affected. + removePackageOverrides(packageName, getOwnedChangeIds(namespace)); + } + } + /** * Calls {@link IPlatformCompat#removeOverridesOnReleaseBuilds} on each package name and * respective change IDs in {@code overridesToRemove}. @@ -211,6 +268,10 @@ public final class AppCompatOverridesService { } } + private boolean isInstalledForAnyUser(String packageName) { + return getVersionCodeOrNull(packageName) != null; + } + @Nullable private Long getVersionCodeOrNull(String packageName) { try { @@ -218,7 +279,7 @@ public final class AppCompatOverridesService { MATCH_ANY_USER); return applicationInfo.longVersionCode; } catch (PackageManager.NameNotFoundException e) { - // Package isn't installed yet. + // Package isn't installed for any user. return null; } } @@ -239,6 +300,7 @@ public final class AppCompatOverridesService { public void onStart() { mService = new AppCompatOverridesService(getContext()); mService.registerDeviceConfigListeners(); + mService.registerPackageReceiver(); } } @@ -247,12 +309,23 @@ public final class AppCompatOverridesService { * namespace and adds/removes overrides according to the changed flags. */ private final class DeviceConfigListener implements DeviceConfig.OnPropertiesChangedListener { + private final Context mContext; private final String mNamespace; - private DeviceConfigListener(String namespace) { + private DeviceConfigListener(Context context, String namespace) { + mContext = context; mNamespace = namespace; } + private void register() { + DeviceConfig.addOnPropertiesChangedListener(mNamespace, mContext.getMainExecutor(), + this); + } + + private void unregister() { + DeviceConfig.removeOnPropertiesChangedListener(this); + } + @Override public void onPropertiesChanged(Properties properties) { boolean removeOverridesFlagChanged = properties.getKeyset().contains( @@ -276,4 +349,60 @@ public final class AppCompatOverridesService { } } } + + /** + * A {@link BroadcastReceiver} that listens on package added/changed/removed events and + * adds/removes overrides according to the corresponding Device Config flags. + */ + private final class PackageReceiver extends BroadcastReceiver { + private final Context mContext; + private final IntentFilter mIntentFilter; + + private PackageReceiver(Context context) { + mContext = context; + mIntentFilter = new IntentFilter(); + mIntentFilter.addAction(ACTION_PACKAGE_ADDED); + mIntentFilter.addAction(ACTION_PACKAGE_CHANGED); + mIntentFilter.addAction(ACTION_PACKAGE_REMOVED); + mIntentFilter.addDataScheme("package"); + } + + private void register() { + mContext.registerReceiverForAllUsers(this, mIntentFilter, /* broadcastPermission= */ + null, /* scheduler= */ null); + } + + private void unregister() { + mContext.unregisterReceiver(this); + } + + @Override + public void onReceive(@NonNull final Context context, @NonNull final Intent intent) { + Uri data = intent.getData(); + if (data == null) { + Slog.w(TAG, "Failed to get package name in package receiver"); + return; + } + String packageName = data.getSchemeSpecificPart(); + String action = intent.getAction(); + if (action == null) { + Slog.w(TAG, "Failed to get action in package receiver"); + return; + } + switch (action) { + case ACTION_PACKAGE_ADDED: + case ACTION_PACKAGE_CHANGED: + applyAllPackageOverrides(packageName); + break; + case ACTION_PACKAGE_REMOVED: + if (!isInstalledForAnyUser(packageName)) { + removeAllPackageOverrides(packageName); + } + break; + default: + Slog.w(TAG, "Unsupported action in package receiver: " + action); + break; + } + } + }; } diff --git a/services/tests/mockingservicestests/src/com/android/server/compat/overrides/AppCompatOverridesServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/compat/overrides/AppCompatOverridesServiceTest.java index d3f04f30a0b8e..312927206a801 100644 --- a/services/tests/mockingservicestests/src/com/android/server/compat/overrides/AppCompatOverridesServiceTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/compat/overrides/AppCompatOverridesServiceTest.java @@ -16,6 +16,11 @@ package com.android.server.compat.overrides; +import static android.content.Intent.ACTION_PACKAGE_ADDED; +import static android.content.Intent.ACTION_PACKAGE_CHANGED; +import static android.content.Intent.ACTION_PACKAGE_REMOVED; +import static android.content.Intent.ACTION_USER_SWITCHED; + import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; import static com.android.server.compat.overrides.AppCompatOverridesParser.FLAG_OWNED_CHANGE_IDS; import static com.android.server.compat.overrides.AppCompatOverridesParser.FLAG_REMOVE_OVERRIDES; @@ -24,17 +29,25 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.when; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.app.compat.PackageOverride; +import android.content.BroadcastReceiver; import android.content.Context; import android.content.ContextWrapper; +import android.content.Intent; +import android.content.IntentFilter; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; +import android.net.Uri; +import android.os.Handler; import android.os.RemoteException; import android.platform.test.annotations.Presubmit; import android.provider.DeviceConfig; @@ -73,7 +86,10 @@ import java.util.concurrent.Executor; @Presubmit public class AppCompatOverridesServiceTest { private static final String NAMESPACE_1 = "namespace_1"; - private static final List SUPPORTED_NAMESPACES = Arrays.asList(NAMESPACE_1); + private static final String NAMESPACE_2 = "namespace_2"; + private static final String NAMESPACE_3 = "namespace_3"; + private static final List SUPPORTED_NAMESPACES = Arrays.asList(NAMESPACE_1, + NAMESPACE_2, NAMESPACE_3); private static final String PACKAGE_1 = "com.android.test1"; private static final String PACKAGE_2 = "com.android.test2"; @@ -82,6 +98,7 @@ public class AppCompatOverridesServiceTest { private static final String PACKAGE_5 = "com.android.test5"; private MockContext mMockContext; + private BroadcastReceiver mPackageReceiver; private AppCompatOverridesService mService; @Mock @@ -112,6 +129,15 @@ public class AppCompatOverridesServiceTest { // Run on current thread return Runnable::run; } + + @Override + @Nullable + public Intent registerReceiverForAllUsers(@Nullable BroadcastReceiver receiver, + @NonNull IntentFilter filter, @Nullable String broadcastPermission, + @Nullable Handler scheduler) { + mPackageReceiver = receiver; + return null; + } } @Before @@ -120,6 +146,8 @@ public class AppCompatOverridesServiceTest { InstrumentationRegistry.getInstrumentation().getTargetContext()); mService = new AppCompatOverridesService(mMockContext, mPlatformCompat, SUPPORTED_NAMESPACES); + mService.registerPackageReceiver(); + assertThat(mPackageReceiver).isNotNull(); } @Test @@ -361,6 +389,282 @@ public class AppCompatOverridesServiceTest { any(CompatibilityOverridesToRemoveConfig.class), eq(PACKAGE_4)); } + @Test + public void packageReceiver_packageAddedIntentDataIsNull_doesNothing() throws Exception { + DeviceConfig.setProperties(new Properties.Builder(NAMESPACE_1) + .setString(PACKAGE_1, "101:::true").build()); + DeviceConfig.setProperties(new Properties.Builder(NAMESPACE_2) + .setString(PACKAGE_1, "201:::true").build()); + mockGetApplicationInfo(PACKAGE_1, /* versionCode= */ 0); + + mPackageReceiver.onReceive(mMockContext, new Intent(ACTION_PACKAGE_ADDED)); + + verify(mPlatformCompat, never()).putOverridesOnReleaseBuilds( + any(CompatibilityOverrideConfig.class), eq(PACKAGE_1)); + verify(mPlatformCompat, never()).removeOverridesOnReleaseBuilds( + any(CompatibilityOverridesToRemoveConfig.class), eq(PACKAGE_1)); + } + + @Test + public void packageReceiver_actionIsNull_doesNothing() throws Exception { + DeviceConfig.setProperties(new Properties.Builder(NAMESPACE_1) + .setString(PACKAGE_1, "101:::true").build()); + DeviceConfig.setProperties(new Properties.Builder(NAMESPACE_2) + .setString(PACKAGE_1, "201:::true").build()); + mockGetApplicationInfo(PACKAGE_1, /* versionCode= */ 0); + + mPackageReceiver.onReceive(mMockContext, + createPackageIntent(PACKAGE_1, /* action= */ null)); + + verify(mPlatformCompat, never()).putOverridesOnReleaseBuilds( + any(CompatibilityOverrideConfig.class), eq(PACKAGE_1)); + verify(mPlatformCompat, never()).removeOverridesOnReleaseBuilds( + any(CompatibilityOverridesToRemoveConfig.class), eq(PACKAGE_1)); + } + + @Test + public void packageReceiver_unsupportedAction_doesNothing() throws Exception { + DeviceConfig.setProperties(new Properties.Builder(NAMESPACE_1) + .setString(PACKAGE_1, "101:::true").build()); + DeviceConfig.setProperties(new Properties.Builder(NAMESPACE_2) + .setString(PACKAGE_1, "201:::true").build()); + mockGetApplicationInfo(PACKAGE_1, /* versionCode= */ 0); + + mPackageReceiver.onReceive(mMockContext, + createPackageIntent(PACKAGE_1, ACTION_USER_SWITCHED)); + + verify(mPlatformCompat, never()).putOverridesOnReleaseBuilds( + any(CompatibilityOverrideConfig.class), eq(PACKAGE_1)); + verify(mPlatformCompat, never()).removeOverridesOnReleaseBuilds( + any(CompatibilityOverridesToRemoveConfig.class), eq(PACKAGE_1)); + } + + @Test + public void packageReceiver_packageAddedIntentPackageNotInstalled_doesNothing() + throws Exception { + DeviceConfig.setProperties(new Properties.Builder(NAMESPACE_1) + .setString(PACKAGE_1, "101:::true").build()); + DeviceConfig.setProperties(new Properties.Builder(NAMESPACE_2) + .setString(PACKAGE_1, "201:::true").build()); + mockGetApplicationInfoNotInstalled(PACKAGE_1); + + mPackageReceiver.onReceive(mMockContext, + createPackageIntent(PACKAGE_1, ACTION_PACKAGE_ADDED)); + + verify(mPlatformCompat, never()).putOverridesOnReleaseBuilds( + any(CompatibilityOverrideConfig.class), eq(PACKAGE_1)); + verify(mPlatformCompat, never()).removeOverridesOnReleaseBuilds( + any(CompatibilityOverridesToRemoveConfig.class), eq(PACKAGE_1)); + } + + @Test + public void packageReceiver_packageAddedIntentNoOverridesForPackage_doesNothing() + throws Exception { + DeviceConfig.setProperties(new Properties.Builder(NAMESPACE_1) + .setString(PACKAGE_2, "101:::true").build()); + DeviceConfig.setProperties(new Properties.Builder(NAMESPACE_2) + .setString(PACKAGE_3, "201:::true").build()); + mockGetApplicationInfo(PACKAGE_1, /* versionCode= */ 0); + + mPackageReceiver.onReceive(mMockContext, + createPackageIntent(PACKAGE_1, ACTION_PACKAGE_ADDED)); + + verify(mPlatformCompat, never()).putOverridesOnReleaseBuilds( + any(CompatibilityOverrideConfig.class), eq(PACKAGE_1)); + verify(mPlatformCompat, never()).removeOverridesOnReleaseBuilds( + any(CompatibilityOverridesToRemoveConfig.class), eq(PACKAGE_1)); + } + + @Test + public void packageReceiver_packageAddedIntent_appliesOverridesFromAllNamespaces() + throws Exception { + DeviceConfig.setProperties(new Properties.Builder(NAMESPACE_1) + .setString(PACKAGE_1, "101:::true,103:::") + .setString(PACKAGE_2, "102:::false").build()); + DeviceConfig.setProperties(new Properties.Builder(NAMESPACE_2) + .setString(PACKAGE_3, "201:::false").build()); + DeviceConfig.setProperties(new Properties.Builder(NAMESPACE_3) + .setString(PACKAGE_1, "301:::true,302:::false") + .setString(PACKAGE_2, "302:::false").build()); + mockGetApplicationInfo(PACKAGE_1, /* versionCode= */ 0); + + mPackageReceiver.onReceive(mMockContext, + createPackageIntent(PACKAGE_1, ACTION_PACKAGE_ADDED)); + + verify(mPlatformCompat, times(2)).putOverridesOnReleaseBuilds( + mOverridesToAddConfigCaptor.capture(), eq(PACKAGE_1)); + verify(mPlatformCompat).removeOverridesOnReleaseBuilds( + mOverridesToRemoveConfigCaptor.capture(), eq(PACKAGE_1)); + List configs = mOverridesToAddConfigCaptor.getAllValues(); + assertThat(configs.get(0).overrides.keySet()).containsExactly(101L); + assertThat(configs.get(1).overrides.keySet()).containsExactly(301L, 302L); + assertThat(mOverridesToRemoveConfigCaptor.getValue().changeIds).containsExactly(103L); + } + + @Test + public void packageReceiver_packageChangedIntent_appliesOverrides() + throws Exception { + DeviceConfig.setProperties(new Properties.Builder(NAMESPACE_1) + .setString(PACKAGE_1, "101:::true,103:::").build()); + mockGetApplicationInfo(PACKAGE_1, /* versionCode= */ 0); + + mPackageReceiver.onReceive(mMockContext, + createPackageIntent(PACKAGE_1, ACTION_PACKAGE_CHANGED)); + + verify(mPlatformCompat).putOverridesOnReleaseBuilds( + mOverridesToAddConfigCaptor.capture(), eq(PACKAGE_1)); + verify(mPlatformCompat).removeOverridesOnReleaseBuilds( + mOverridesToRemoveConfigCaptor.capture(), eq(PACKAGE_1)); + assertThat(mOverridesToAddConfigCaptor.getValue().overrides.keySet()).containsExactly(101L); + assertThat(mOverridesToRemoveConfigCaptor.getValue().changeIds).containsExactly(103L); + } + + @Test + public void packageReceiver_packageAddedIntentRemoveOverridesSetForSomeNamespaces_skipsIds() + throws Exception { + DeviceConfig.setProperties(new Properties.Builder(NAMESPACE_1) + .setString(FLAG_REMOVE_OVERRIDES, PACKAGE_1 + "=103," + PACKAGE_2 + "=101") + .setString(PACKAGE_1, "101:::true,103:::") + .setString(PACKAGE_2, "102:::false").build()); + DeviceConfig.setProperties(new Properties.Builder(NAMESPACE_2) + .setString(PACKAGE_1, "201:::false").build()); + DeviceConfig.setProperties(new Properties.Builder(NAMESPACE_3) + .setString(FLAG_REMOVE_OVERRIDES, PACKAGE_1 + "=301," + PACKAGE_3 + "=302") + .setString(PACKAGE_1, "301:::true,302:::false,303:::") + .setString(PACKAGE_3, "302:::false").build()); + mockGetApplicationInfo(PACKAGE_1, /* versionCode= */ 0); + + mPackageReceiver.onReceive(mMockContext, + createPackageIntent(PACKAGE_1, ACTION_PACKAGE_ADDED)); + + verify(mPlatformCompat, times(3)).putOverridesOnReleaseBuilds( + mOverridesToAddConfigCaptor.capture(), eq(PACKAGE_1)); + verify(mPlatformCompat).removeOverridesOnReleaseBuilds( + mOverridesToRemoveConfigCaptor.capture(), eq(PACKAGE_1)); + List configs = mOverridesToAddConfigCaptor.getAllValues(); + assertThat(configs.get(0).overrides.keySet()).containsExactly(101L); + assertThat(configs.get(1).overrides.keySet()).containsExactly(201L); + assertThat(configs.get(2).overrides.keySet()).containsExactly(302L); + assertThat(mOverridesToRemoveConfigCaptor.getValue().changeIds).containsExactly(303L); + } + + @Test + public void packageReceiver_packageRemovedIntentNoOverridesForPackage_doesNothing() + throws Exception { + DeviceConfig.setProperties(new Properties.Builder(NAMESPACE_1) + .setString(FLAG_OWNED_CHANGE_IDS, "101,102") + .setString(PACKAGE_2, "101:::true").build()); + DeviceConfig.setProperties(new Properties.Builder(NAMESPACE_2) + .setString(FLAG_OWNED_CHANGE_IDS, "201,202") + .setString(PACKAGE_3, "201:::true").build()); + mockGetApplicationInfoNotInstalled(PACKAGE_1); + + mPackageReceiver.onReceive(mMockContext, + createPackageIntent(PACKAGE_1, ACTION_PACKAGE_REMOVED)); + + verify(mPlatformCompat, never()).putOverridesOnReleaseBuilds( + any(CompatibilityOverrideConfig.class), eq(PACKAGE_1)); + verify(mPlatformCompat, never()).removeOverridesOnReleaseBuilds( + any(CompatibilityOverridesToRemoveConfig.class), eq(PACKAGE_1)); + } + + @Test + public void packageReceiver_packageRemovedIntentPackageInstalledForAnotherUser_doesNothing() + throws Exception { + DeviceConfig.setProperties(new Properties.Builder(NAMESPACE_1) + .setString(FLAG_OWNED_CHANGE_IDS, "101,102,103") + .setString(PACKAGE_1, "101:::true,103:::").build()); + DeviceConfig.setProperties(new Properties.Builder(NAMESPACE_2) + .setString(FLAG_OWNED_CHANGE_IDS, "201,202") + .setString(PACKAGE_1, "202:::false").build()); + mockGetApplicationInfo(PACKAGE_1, /* versionCode= */ 0); + + mPackageReceiver.onReceive(mMockContext, + createPackageIntent(PACKAGE_1, ACTION_PACKAGE_REMOVED)); + + verify(mPlatformCompat, never()).putOverridesOnReleaseBuilds( + any(CompatibilityOverrideConfig.class), eq(PACKAGE_1)); + verify(mPlatformCompat, never()).removeOverridesOnReleaseBuilds( + any(CompatibilityOverridesToRemoveConfig.class), eq(PACKAGE_1)); + } + + @Test + public void packageReceiver_packageRemovedIntent_removesOwnedOverridesForNamespacesWithPackage() + throws Exception { + DeviceConfig.setProperties(new Properties.Builder(NAMESPACE_1) + .setString(FLAG_OWNED_CHANGE_IDS, "101,102,103") + .setString(PACKAGE_1, "101:::true,103:::") + .setString(PACKAGE_2, "102:::false").build()); + DeviceConfig.setProperties(new Properties.Builder(NAMESPACE_2) + .setString(FLAG_OWNED_CHANGE_IDS, "201") + .setString(PACKAGE_3, "201:::false").build()); + DeviceConfig.setProperties(new Properties.Builder(NAMESPACE_3) + .setString(FLAG_OWNED_CHANGE_IDS, "301,302") + .setString(PACKAGE_1, "302:::") + .setString(PACKAGE_2, "301:::true").build()); + mockGetApplicationInfoNotInstalled(PACKAGE_1); + + mPackageReceiver.onReceive(mMockContext, + createPackageIntent(PACKAGE_1, ACTION_PACKAGE_REMOVED)); + + verify(mPlatformCompat, never()).putOverridesOnReleaseBuilds( + any(CompatibilityOverrideConfig.class), eq(PACKAGE_1)); + verify(mPlatformCompat, times(2)).removeOverridesOnReleaseBuilds( + mOverridesToRemoveConfigCaptor.capture(), eq(PACKAGE_1)); + List configs = + mOverridesToRemoveConfigCaptor.getAllValues(); + assertThat(configs.get(0).changeIds).containsExactly(101L, 102L, 103L); + assertThat(configs.get(1).changeIds).containsExactly(301L, 302L); + } + + @Test + public void packageReceiver_packageRemovedIntentNoOwnedIdsForSomeNamespace_skipsNamespace() + throws Exception { + DeviceConfig.setProperties(new Properties.Builder(NAMESPACE_1) + .setString(FLAG_OWNED_CHANGE_IDS, "101,102") + .setString(PACKAGE_1, "101:::true").build()); + DeviceConfig.setProperties(new Properties.Builder(NAMESPACE_2) + .setString(PACKAGE_1, "201:::false").build()); + DeviceConfig.setProperties(new Properties.Builder(NAMESPACE_3) + .setString(FLAG_OWNED_CHANGE_IDS, "301") + .setString(PACKAGE_1, "301:::true").build()); + mockGetApplicationInfoNotInstalled(PACKAGE_1); + + mPackageReceiver.onReceive(mMockContext, + createPackageIntent(PACKAGE_1, ACTION_PACKAGE_REMOVED)); + + verify(mPlatformCompat, never()).putOverridesOnReleaseBuilds( + any(CompatibilityOverrideConfig.class), eq(PACKAGE_1)); + verify(mPlatformCompat, times(2)).removeOverridesOnReleaseBuilds( + mOverridesToRemoveConfigCaptor.capture(), eq(PACKAGE_1)); + List configs = + mOverridesToRemoveConfigCaptor.getAllValues(); + assertThat(configs.get(0).changeIds).containsExactly(101L, 102L); + assertThat(configs.get(1).changeIds).containsExactly(301L); + } + + @Test + public void packageReceiver_platformCompatThrowsExceptionForSomeNamespace_skipsFailedCall() + throws Exception { + DeviceConfig.setProperties(new Properties.Builder(NAMESPACE_1) + .setString(PACKAGE_1, "101:::true").build()); + DeviceConfig.setProperties(new Properties.Builder(NAMESPACE_2) + .setString(PACKAGE_1, "201:::false").build()); + DeviceConfig.setProperties(new Properties.Builder(NAMESPACE_3) + .setString(PACKAGE_1, "301:::true").build()); + mockGetApplicationInfo(PACKAGE_1, /* versionCode= */ 0); + doThrow(new RemoteException()).when(mPlatformCompat).putOverridesOnReleaseBuilds( + argThat(config -> config.overrides.containsKey(201L)), eq(PACKAGE_1)); + + mPackageReceiver.onReceive(mMockContext, + createPackageIntent(PACKAGE_1, ACTION_PACKAGE_ADDED)); + + verify(mPlatformCompat, times(3)).putOverridesOnReleaseBuilds( + any(CompatibilityOverrideConfig.class), eq(PACKAGE_1)); + verify(mPlatformCompat, never()).removeOverridesOnReleaseBuilds( + any(CompatibilityOverridesToRemoveConfig.class), eq(PACKAGE_1)); + } + private void mockGetApplicationInfo(String packageName, long versionCode) throws Exception { when(mPackageManager.getApplicationInfo(eq(packageName), anyInt())).thenReturn( @@ -377,4 +681,8 @@ public class AppCompatOverridesServiceTest { appInfo.longVersionCode = versionCode; return appInfo; } + + private Intent createPackageIntent(String packageName, @Nullable String action) { + return new Intent(action, Uri.parse("package:" + packageName)); + } }