Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit a89d00f8 authored by Reema Bajwa's avatar Reema Bajwa
Browse files

Remove service when package is modified and service is no longer valid

For system services that are configured in multi mode, i.e support connecting to multiple services,
this change modifies the onPackageModified logic and does the following:
- Gets all services from default service list that are valid
- Loops through the services in the cache and removes a service that is not present in the default service list
- Retains the same logic for services that are still valid

This change is needed because services that become invalid after a package is modified
are ignored in the logic today, which means that they stop showing up in the settings UI
but are still active in the cache. Hence if a provider proactivly disables the services (hence
hiding it from the UI), the user is unable to see it in the settings. Then, if the developer
enables it programmatically, the service is valid again and can start receiving requests
without the user getting a chance to view it in between. This change handles changes in
package modification such that if a service becomes invalid, it is cleared from the cache
so that if it is programmatically enabled again, the user has to explicitly go into
settings and enable it (put it in secure settings).

Note that this change also retains the old logic for single mode services.

Bug: 351809293
Flag: android.credentials.flags.package_update_fix_enabled
Test: Locally deployed and tested

Change-Id: I8b579ac91c3e8cf37379adba686961c574bce254
parent a42aeb70
Loading
Loading
Loading
Loading
+49 −1
Original line number Diff line number Diff line
@@ -25,6 +25,7 @@ import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.pm.UserInfo;
import android.credentials.flags.Flags;
import android.database.ContentObserver;
import android.net.Uri;
import android.os.Binder;
@@ -1173,8 +1174,19 @@ public abstract class AbstractMasterSystemService<M extends AbstractMasterSystem
                    final String[] serviceNames = mServiceNameResolver.getDefaultServiceNameList(
                            userId);
                    if (serviceNames != null) {
                        if (Flags.packageUpdateFixEnabled()) {
                            if (mServiceNameResolver.isConfiguredInMultipleMode()) {
                                // Remove any service that is in the cache but is no longer valid
                                // after this modification for this particular package
                                removeInvalidCachedServicesLocked(serviceNames, packageName,
                                        userId);
                            }
                        }

                        // Update services that are still valid
                        for (int i = 0; i < serviceNames.length; i++) {
                            peekAndUpdateCachedServiceLocked(packageName, userId, serviceNames[i]);
                            peekAndUpdateCachedServiceLocked(packageName, userId,
                                    serviceNames[i]);
                        }
                    }
                }
@@ -1230,6 +1242,36 @@ public abstract class AbstractMasterSystemService<M extends AbstractMasterSystem
        monitor.register(getContext(), null, UserHandle.ALL, true);
    }

    @GuardedBy("mLock")
    @SuppressWarnings("GuardedBy") // ErrorProne requires this.mLock for
    // handleServiceRemovedMultiModeLocked which is the same
    private void removeInvalidCachedServicesLocked(String[] validServices,
            String packageName, int userId) {
        visitServicesLocked((s) -> {
            ComponentName serviceComponentName = s
                    .getServiceComponentName();
            if (serviceComponentName != null && serviceComponentName
                    .getPackageName().equals(packageName)) {
                if (!serviceInValidServiceList(serviceComponentName,
                        validServices)) {
                    handleServiceRemovedMultiModeLocked(
                            serviceComponentName, userId);
                }
            }
        });
    }

    private boolean serviceInValidServiceList(ComponentName serviceComponentName,
            String[] serviceNames) {
        for (String service: serviceNames) {
            ComponentName componentName = ComponentName.unflattenFromString(service);
            if (componentName != null && componentName.equals(serviceComponentName)) {
                return true;
            }
        }
        return false;
    }

    @GuardedBy("mLock")
    @SuppressWarnings("unused")
    protected void onServicePackageDataClearedMultiModeLocked(String packageName, int userId) {
@@ -1245,6 +1287,12 @@ public abstract class AbstractMasterSystemService<M extends AbstractMasterSystem
        if (verbose) Slog.v(mTag, "handlePackageRemovedMultiModeLocked(" + userId + ")");
    }

    @GuardedBy("mLock")
    protected void handleServiceRemovedMultiModeLocked(ComponentName serviceComponentName,
            int userId) {
        if (verbose) Slog.v(mTag, "handleServiceRemovedMultiModeLocked(" + userId + ")");
    }

    @GuardedBy("mLock")
    protected void removeServiceFromCache(@NonNull S service, int userId) {
        if (mServicesCacheList.get(userId) != null) {
+1 −1
Original line number Diff line number Diff line
@@ -323,7 +323,7 @@ public abstract class AbstractPerUserSystemService<S extends AbstractPerUserSyst
     * if the service is disabled.
     */
    @Nullable
    public final ComponentName getServiceComponentName() {
    public ComponentName getServiceComponentName() {
        synchronized (mLock) {
            return mServiceInfo == null ? null : mServiceInfo.getComponentName();
        }
+212 −37
Original line number Diff line number Diff line
@@ -204,7 +204,7 @@ public final class CredentialManagerService
    @SuppressWarnings("GuardedBy") // ErrorProne requires service.mLock which is the same
    // this.mLock
    protected void handlePackageRemovedMultiModeLocked(String packageName, int userId) {
        updateProvidersWhenPackageRemoved(new SettingsWrapper(mContext), packageName);
        updateProvidersWhenPackageRemoved(new SettingsWrapper(mContext), packageName, userId);

        List<CredentialManagerServiceImpl> services = peekServiceListForUserLocked(userId);
        if (services == null) {
@@ -233,6 +233,47 @@ public final class CredentialManagerService
        }
    }

    @GuardedBy("mLock")
    @SuppressWarnings("GuardedBy") // ErrorProne requires service.mLock which is the same
    // this.mLock
    protected void handleServiceRemovedMultiModeLocked(ComponentName componentName, int userId) {
        updateProvidersWhenServiceRemoved(new SettingsWrapper(mContext), componentName, userId);

        List<CredentialManagerServiceImpl> services = peekServiceListForUserLocked(userId);
        if (services == null) {
            return;
        }

        List<CredentialManagerServiceImpl> servicesToBeRemoved = new ArrayList<>();
        for (CredentialManagerServiceImpl service : services) {
            if (service != null) {
                CredentialProviderInfo credentialProviderInfo = service.getCredentialProviderInfo();
                ComponentName serviceComponentName =
                        credentialProviderInfo.getServiceInfo().getComponentName();
                if (serviceComponentName != null && serviceComponentName.equals(componentName)) {
                    servicesToBeRemoved.add(service);
                }
            }
        }

        removeServicesLocked(servicesToBeRemoved, userId);
    }

    @GuardedBy("mLock")
    @SuppressWarnings("GuardedBy") // ErrorProne requires service.mLock which is the same
    // this.mLock
    private void removeServicesLocked(
            List<CredentialManagerServiceImpl> servicesToBeRemoved, int userId) {
        // Iterate over all the services to be removed, and remove them from the user configurable
        // services cache, the system services cache as well as the setting key-value pair.
        for (CredentialManagerServiceImpl serviceToBeRemoved : servicesToBeRemoved) {
            removeServiceFromCache(serviceToBeRemoved, userId);
            removeServiceFromSystemServicesCache(serviceToBeRemoved, userId);
            CredentialDescriptionRegistry.forUser(userId)
                    .evictProviderWithPackageName(serviceToBeRemoved.getServicePackageName());
        }
    }

    @GuardedBy("mLock")
    private void removeServiceFromSystemServicesCache(
            CredentialManagerServiceImpl serviceToBeRemoved, int userId) {
@@ -1136,27 +1177,123 @@ public final class CredentialManagerService
        }
    }

    /** Updates the list of providers when a particular service within an app is to be removed. */
    public static void updateProvidersWhenServiceRemoved(
            SettingsWrapper settingsWrapper, ComponentName componentName, int userId) {
        Slog.i(TAG, "updateProvidersWhenServiceRemoved for: "
                + componentName.flattenToString());

        // Get the current primary providers.
        String rawPrimaryProviders =
                settingsWrapper.getStringForUser(
                        Settings.Secure.CREDENTIAL_SERVICE_PRIMARY, userId);
        if (TextUtils.isEmpty(rawPrimaryProviders)) {
            Slog.w(TAG, "primary settings key is null");
        } else {
            // Remove any service from the primary setting that matches with the service
            // being removed, and set the filtered list back to the settings key.
            Set<String> filteredPrimaryProviders = getStoredProvidersExceptService(
                    rawPrimaryProviders, componentName);

            // If there are no more primary providers AND there is no autofill provider either,
            // that means all providers must be cleared as that is what the Settings UI app
            // displays to the user.If the autofill provider is present then UI shows that as the
            // preferred service and other credential provider services can continue to work.
            if (filteredPrimaryProviders.isEmpty()
                    && !isAutofillProviderPresent(settingsWrapper, userId)) {
                Slog.d(TAG, "Clearing all credential providers");
                if (clearAllCredentialProviders(settingsWrapper, userId)) {
                    return;
                }
                Slog.e(TAG, "Failed to clear all credential providers");
            }

            // Set the filtered primary providers to the settings key
            if (!settingsWrapper.putStringForUser(
                    Settings.Secure.CREDENTIAL_SERVICE_PRIMARY,
                    String.join(":", filteredPrimaryProviders),
                    UserHandle.myUserId(),
                    /* overrideableByRestore= */ true)) {
                Slog.e(TAG, "Failed to remove primary service: " + componentName);
                return;
            }
        }

        // Read the credential providers to remove any reference of the removed service.
        String rawCredentialProviders =
                settingsWrapper.getStringForUser(
                        Settings.Secure.CREDENTIAL_SERVICE, UserHandle.myUserId());

        // Remove any provider services that are same as the one being removed.
        Set<String> filteredCredentialProviders = getStoredProvidersExceptService(
                rawCredentialProviders, componentName);
        if (!settingsWrapper.putStringForUser(
                Settings.Secure.CREDENTIAL_SERVICE,
                String.join(":", filteredCredentialProviders),
                UserHandle.myUserId(),
                /* overrideableByRestore= */ true)) {
            Slog.e(TAG, "Failed to remove secondary service: " + componentName);
        }
    }

    private static boolean isAutofillProviderPresent(SettingsWrapper settingsWrapper, int userId) {
        // Read the autofill provider so we don't accidentally erase it.
        String autofillProvider =
                settingsWrapper.getStringForUser(
                        Settings.Secure.AUTOFILL_SERVICE, userId);

        return autofillProvider != null && !autofillProvider.isEmpty()
                && !isPlaceholderAutofillProvider(autofillProvider, settingsWrapper);
    }

    private static boolean isPlaceholderAutofillProvider(String autofillProvider,
            SettingsWrapper settingsWrapper) {

        // If there is an autofill provider and it is the credential autofill service indicating
        // that the currently selected primary provider does not support autofill
        // then we should keep as is
        String credentialAutofillService = settingsWrapper.mContext.getResources().getString(
                R.string.config_defaultCredentialManagerAutofillService);
        return autofillProvider != null && TextUtils.equals(
                autofillProvider, credentialAutofillService);
    }

    private static boolean clearAllCredentialProviders(SettingsWrapper settingsWrapper,
            int userId) {
        if (!settingsWrapper.putStringForUser(
                Settings.Secure.CREDENTIAL_SERVICE_PRIMARY,
                null,
                userId,
                /* overrideableByRestore= */ true)) {
            return false;
        }
        return settingsWrapper.putStringForUser(
                Settings.Secure.CREDENTIAL_SERVICE,
                null,
                userId,
                /* overrideableByRestore= */ true);
    }

    /** Updates the list of providers when an app is uninstalled. */
    public static void updateProvidersWhenPackageRemoved(
            SettingsWrapper settingsWrapper, String packageName) {
            SettingsWrapper settingsWrapper, String packageName, int userId) {
        Slog.i(TAG, "updateProvidersWhenPackageRemoved");

        // Get the current providers.
        String rawProviders =
                settingsWrapper.getStringForUser(
                        Settings.Secure.CREDENTIAL_SERVICE_PRIMARY, UserHandle.myUserId());
                        Settings.Secure.CREDENTIAL_SERVICE_PRIMARY, userId);
        if (rawProviders == null) {
            Slog.w(TAG, "settings key is null");
            return;
        }

        } else {
            // Remove any providers from the primary setting that contain the package name
            // being removed.
        Set<String> primaryProviders = getStoredProviders(rawProviders, packageName);
            Set<String> primaryProviders = getStoredProvidersExceptPackage(rawProviders,
                    packageName);
            if (!settingsWrapper.putStringForUser(
                    Settings.Secure.CREDENTIAL_SERVICE_PRIMARY,
                    String.join(":", primaryProviders),
                UserHandle.myUserId(),
                    userId,
                    /* overrideableByRestore= */ true)) {
                Slog.e(TAG, "Failed to remove primary package: " + packageName);
                return;
@@ -1165,7 +1302,7 @@ public final class CredentialManagerService
            // Read the autofill provider so we don't accidentally erase it.
            String autofillProvider =
                    settingsWrapper.getStringForUser(
                        Settings.Secure.AUTOFILL_SERVICE, UserHandle.myUserId());
                            Settings.Secure.AUTOFILL_SERVICE, userId);

            // If there is an autofill provider and it is the credential autofill service indicating
            // that the currently selected primary provider does not support autofill
@@ -1181,31 +1318,70 @@ public final class CredentialManagerService
                    if (!settingsWrapper.putStringForUser(
                            Settings.Secure.AUTOFILL_SERVICE,
                            "",
                        UserHandle.myUserId(),
                            userId,
                            /* overrideableByRestore= */ true)) {
                        Slog.e(TAG, "Failed to remove autofill package: " + packageName);
                    }
                }
            }

            // If there are no more primary providers AND there is no autofill provider either,
            // that means all providers must be cleared as that is what the Settings UI app
            // displays to the user.If the autofill provider is present then UI shows that as the
            // preferred service and other credential provider services can continue to work.
            if (primaryProviders.isEmpty() && !isAutofillProviderPresent(settingsWrapper, userId)) {
                Slog.d(TAG, "Clearing all credential providers");
                if (clearAllCredentialProviders(settingsWrapper, userId)) {
                    return;
                }
                Slog.e(TAG, "Failed to clear all credential providers");
            }
        }

        // Read the credential providers to remove any reference of the removed app.
        String rawCredentialProviders =
                settingsWrapper.getStringForUser(
                        Settings.Secure.CREDENTIAL_SERVICE, UserHandle.myUserId());
                        Settings.Secure.CREDENTIAL_SERVICE, userId);

        // Remove any providers that belong to the removed app.
        Set<String> credentialProviders = getStoredProviders(rawCredentialProviders, packageName);
        Set<String> credentialProviders = getStoredProvidersExceptPackage(
                rawCredentialProviders, packageName);
        if (!settingsWrapper.putStringForUser(
                Settings.Secure.CREDENTIAL_SERVICE,
                String.join(":", credentialProviders),
                UserHandle.myUserId(),
                userId,
                /* overrideableByRestore= */ true)) {
            Slog.e(TAG, "Failed to remove secondary package: " + packageName);
        }
    }

    /** Gets the list of stored providers from a string removing any mention of package name. */
    public static Set<String> getStoredProviders(String rawProviders, String packageName) {
    public static Set<String> getStoredProvidersExceptService(String rawProviders,
            ComponentName componentName) {
        // If the app being removed matches any of the package names from
        // this list then don't add it in the output.
        Set<String> providers = new HashSet<>();
        if (rawProviders == null || componentName == null) {
            return providers;
        }
        for (String rawComponentName : rawProviders.split(":")) {
            if (TextUtils.isEmpty(rawComponentName) || rawComponentName.equals("null")) {
                Slog.d(TAG, "provider component name is empty or null");
                continue;
            }

            ComponentName cn = ComponentName.unflattenFromString(rawComponentName);
            if (cn != null && !cn.equals(componentName)) {
                providers.add(cn.flattenToString());
            }
        }

        return providers;
    }

    /** Gets the list of stored providers from a string removing any mention of package name. */
    public static Set<String> getStoredProvidersExceptPackage(
            String rawProviders, String packageName) {
        // If the app being removed matches any of the package names from
        // this list then don't add it in the output.
        Set<String> providers = new HashSet<>();
@@ -1213,8 +1389,7 @@ public final class CredentialManagerService
            return providers;
        }
        for (String rawComponentName : rawProviders.split(":")) {
            if (TextUtils.isEmpty(rawComponentName)
                    || rawComponentName.equals("null")) {
            if (TextUtils.isEmpty(rawComponentName) || rawComponentName.equals("null")) {
                Slog.d(TAG, "provider component name is empty or null");
                continue;
            }
+7 −0
Original line number Diff line number Diff line
@@ -57,6 +57,13 @@ public final class CredentialManagerServiceImpl extends
        }
    }

    @Nullable
    @Override
    @GuardedBy("mLock")
    public ComponentName getServiceComponentName() {
        return getComponentName();
    }

    @GuardedBy("mLock")
    public ComponentName getComponentName() {
        return mInfo.getServiceInfo().getComponentName();
+13 −7
Original line number Diff line number Diff line
@@ -51,20 +51,22 @@ public final class CredentialManagerServiceTest {

    @Test
    public void getStoredProviders_emptyValue_success() {
        Set<String> providers = CredentialManagerService.getStoredProviders("", "");
        Set<String> providers = CredentialManagerService.getStoredProvidersExceptPackage(
                "", "");
        assertThat(providers.size()).isEqualTo(0);
    }

    @Test
    public void getStoredProviders_nullValue_success() {
        Set<String> providers = CredentialManagerService.getStoredProviders(null, null);
        Set<String> providers = CredentialManagerService.getStoredProvidersExceptPackage(
                null, null);
        assertThat(providers.size()).isEqualTo(0);
    }

    @Test
    public void getStoredProviders_success() {
        Set<String> providers =
                CredentialManagerService.getStoredProviders(
                CredentialManagerService.getStoredProvidersExceptPackage(
                        "com.example.test/.TestActivity:com.example.test/.TestActivity2:"
                                + "com.example.test2/.TestActivity:blank",
                        "com.example.test");
@@ -74,6 +76,7 @@ public final class CredentialManagerServiceTest {

    @Test
    public void onProviderRemoved_success() {
        int userId = UserHandle.myUserId();
        setSettingsKey(
                Settings.Secure.AUTOFILL_SERVICE,
                CredentialManagerService.AUTOFILL_PLACEHOLDER_VALUE);
@@ -85,7 +88,7 @@ public final class CredentialManagerServiceTest {
                "com.example.test/com.example.test.TestActivity");

        CredentialManagerService.updateProvidersWhenPackageRemoved(
                mSettingsWrapper, "com.example.test");
                mSettingsWrapper, "com.example.test", userId);

        assertThat(getSettingsKey(Settings.Secure.AUTOFILL_SERVICE)).isEqualTo("");
        assertThat(getSettingsKey(Settings.Secure.CREDENTIAL_SERVICE))
@@ -95,6 +98,7 @@ public final class CredentialManagerServiceTest {

    @Test
    public void onProviderRemoved_notPrimaryRemoved_success() {
        int userId = UserHandle.myUserId();
        final String testCredentialPrimaryValue = "com.example.test/com.example.test.TestActivity";
        final String testCredentialValue =
                "com.example.test/com.example.test.TestActivity:com.example.test2/com.example.test2.TestActivity";
@@ -106,7 +110,7 @@ public final class CredentialManagerServiceTest {
        setSettingsKey(Settings.Secure.CREDENTIAL_SERVICE_PRIMARY, testCredentialPrimaryValue);

        CredentialManagerService.updateProvidersWhenPackageRemoved(
                mSettingsWrapper, "com.example.test3");
                mSettingsWrapper, "com.example.test3", userId);

        // Since the provider removed was not a primary provider then we should do nothing.
        assertThat(getSettingsKey(Settings.Secure.AUTOFILL_SERVICE))
@@ -120,6 +124,7 @@ public final class CredentialManagerServiceTest {

    @Test
    public void onProviderRemoved_isAlsoAutofillProvider_success() {
        int userId = UserHandle.myUserId();
        setSettingsKey(
                Settings.Secure.AUTOFILL_SERVICE,
                "com.example.test/com.example.test.AutofillProvider");
@@ -131,7 +136,7 @@ public final class CredentialManagerServiceTest {
                "com.example.test/com.example.test.TestActivity");

        CredentialManagerService.updateProvidersWhenPackageRemoved(
                mSettingsWrapper, "com.example.test");
                mSettingsWrapper, "com.example.test", userId);

        assertThat(getSettingsKey(Settings.Secure.AUTOFILL_SERVICE)).isEqualTo("");
        assertThat(getSettingsKey(Settings.Secure.CREDENTIAL_SERVICE))
@@ -141,6 +146,7 @@ public final class CredentialManagerServiceTest {

    @Test
    public void onProviderRemoved_notPrimaryRemoved_isAlsoAutofillProvider_success() {
        int userId = UserHandle.myUserId();
        final String testCredentialPrimaryValue = "com.example.test/com.example.test.TestActivity";
        final String testCredentialValue =
                "com.example.test/com.example.test.TestActivity:com.example.test2/com.example.test2.TestActivity";
@@ -151,7 +157,7 @@ public final class CredentialManagerServiceTest {
        setSettingsKey(Settings.Secure.CREDENTIAL_SERVICE_PRIMARY, testCredentialPrimaryValue);

        CredentialManagerService.updateProvidersWhenPackageRemoved(
                mSettingsWrapper, "com.example.test3");
                mSettingsWrapper, "com.example.test3", userId);

        // Since the provider removed was not a primary provider then we should do nothing.
        assertCredentialPropertyEquals(