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

Commit a0c97c46 authored by Becca Hughes's avatar Becca Hughes
Browse files

Reland reset on package remove with autofill fix

Test: atest FrameworksServicesTests
Bug: 304815869
Change-Id: I719c5f9b80723967602321b82fbf889fd5e105f9
parent 29104e5e
Loading
Loading
Loading
Loading
+107 −2
Original line number Diff line number Diff line
@@ -65,6 +65,7 @@ import android.util.Slog;
import android.util.SparseArray;

import com.android.internal.annotations.GuardedBy;
import com.android.internal.content.PackageMonitor;
import com.android.server.credentials.metrics.ApiName;
import com.android.server.credentials.metrics.ApiStatus;
import com.android.server.infra.AbstractMasterSystemService;
@@ -101,6 +102,13 @@ public final class CredentialManagerService
    private static final String DEVICE_CONFIG_ENABLE_CREDENTIAL_DESC_API =
            "enable_credential_description_api";

    /**
     * Value stored in autofill pref when credential provider is primary. This is
     * used as a placeholder since a credman only provider will not have an
     * autofill service.
     */
    public static final String AUTOFILL_PLACEHOLDER_VALUE = "credential-provider";

    private final Context mContext;

    /** Cache of system service list per user id. */
@@ -194,6 +202,8 @@ public final class CredentialManagerService
    @SuppressWarnings("GuardedBy") // ErrorProne requires service.mLock which is the same
    // this.mLock
    protected void handlePackageRemovedMultiModeLocked(String packageName, int userId) {
        updateProvidersWhenPackageRemoved(mContext, packageName);

        List<CredentialManagerServiceImpl> services = peekServiceListForUserLocked(userId);
        if (services == null) {
            return;
@@ -216,8 +226,6 @@ public final class CredentialManagerService
        for (CredentialManagerServiceImpl serviceToBeRemoved : servicesToBeRemoved) {
            removeServiceFromCache(serviceToBeRemoved, userId);
            removeServiceFromSystemServicesCache(serviceToBeRemoved, userId);
            removeServiceFromMultiModeSettings(serviceToBeRemoved.getComponentName()
                    .flattenToString(), userId);
            CredentialDescriptionRegistry.forUser(userId)
                    .evictProviderWithPackageName(serviceToBeRemoved.getServicePackageName());
        }
@@ -1110,4 +1118,101 @@ public final class CredentialManagerService
            mRequestSessions.get(userId).put(token, requestSession);
        }
    }

    /** Updates the list of providers when an app is uninstalled. */
    public static void updateProvidersWhenPackageRemoved(Context context, String packageName) {
        // Get the current providers.
        String rawProviders =
                Settings.Secure.getStringForUser(
                    context.getContentResolver(),
                    Settings.Secure.CREDENTIAL_SERVICE_PRIMARY,
                    UserHandle.myUserId());
        if (rawProviders == null) {
            Slog.w(TAG, "settings key is null");
            return;
        }

        // Remove any providers from the primary setting that contain the package name
        // being removed.
        Set<String> primaryProviders =
                getStoredProviders(rawProviders, packageName);
        if (!Settings.Secure.putString(
                context.getContentResolver(),
                Settings.Secure.CREDENTIAL_SERVICE_PRIMARY,
                String.join(":", primaryProviders))) {
            Slog.w(TAG, "Failed to remove primary package: " + packageName);
            return;
        }

        // Read the autofill provider so we don't accidentally erase it.
        String autofillProvider =
                Settings.Secure.getStringForUser(
                    context.getContentResolver(),
                    Settings.Secure.AUTOFILL_SERVICE,
                    UserHandle.myUserId());

        // If there is an autofill provider and it is the placeholder indicating
        // that the currently selected primary provider does not support autofill
        // then we should wipe the setting to keep it in sync.
        if (autofillProvider != null && primaryProviders.isEmpty()) {
            if (autofillProvider.equals(AUTOFILL_PLACEHOLDER_VALUE)) {
                if (!Settings.Secure.putString(
                        context.getContentResolver(),
                        Settings.Secure.AUTOFILL_SERVICE,
                        "")) {
                    Slog.w(TAG, "Failed to remove autofill package: " + packageName);
                }
            } else {
                // If the existing autofill provider is from the app being removed
                // then erase the autofill service setting.
                ComponentName cn = ComponentName.unflattenFromString(autofillProvider);
                if (cn != null && cn.getPackageName().equals(packageName)) {
                   if (!Settings.Secure.putString(
                            context.getContentResolver(),
                            Settings.Secure.AUTOFILL_SERVICE,
                            "")) {
                        Slog.w(TAG, "Failed to remove autofill package: " + packageName);
                    }
                }
            }
        }

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

        // Remove any providers that belong to the removed app.
        Set<String> credentialProviders =
                getStoredProviders(rawCredentialProviders, packageName);
        if (!Settings.Secure.putString(
                context.getContentResolver(),
                Settings.Secure.CREDENTIAL_SERVICE,
                String.join(":", credentialProviders))) {
            Slog.w(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) {
        // 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<>();
        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.getPackageName().equals(packageName)) {
                providers.add(cn.flattenToString());
            }
        }

        return providers;
    }
}
+181 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2023 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.credentials;

import static com.google.common.truth.Truth.assertThat;

import android.content.ComponentName;
import android.content.Context;
import android.os.UserHandle;
import android.provider.Settings;

import androidx.test.core.app.ApplicationProvider;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;

import java.security.cert.CertificateException;
import java.util.HashSet;
import java.util.Set;

/** atest FrameworksServicesTests:com.android.server.credentials.CredentialManagerServiceTest */
@RunWith(AndroidJUnit4.class)
@SmallTest
public final class CredentialManagerServiceTest {

    Context mContext = null;

    @Before
    public void setUp() throws CertificateException {
        mContext = ApplicationProvider.getApplicationContext();
    }

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

    @Test
    public void getStoredProviders_success() {
        Set<String> providers =
                CredentialManagerService.getStoredProviders(
                        "com.example.test/.TestActivity:com.example.test/.TestActivity2:"
                                + "com.example.test2/.TestActivity:blank",
                        "com.example.test");
        assertThat(providers.size()).isEqualTo(1);
        assertThat(providers.contains("com.example.test2/com.example.test2.TestActivity")).isTrue();
    }

    @Test
    public void onProviderRemoved_success() {
        setSettingsKey(
                Settings.Secure.AUTOFILL_SERVICE,
                CredentialManagerService.AUTOFILL_PLACEHOLDER_VALUE);
        setSettingsKey(
                Settings.Secure.CREDENTIAL_SERVICE,
                "com.example.test/com.example.test.TestActivity:com.example.test2/com.example.test2.TestActivity");
        setSettingsKey(
                Settings.Secure.CREDENTIAL_SERVICE_PRIMARY,
                "com.example.test/com.example.test.TestActivity");

        CredentialManagerService.updateProvidersWhenPackageRemoved(mContext, "com.example.test");

        assertThat(getSettingsKey(Settings.Secure.AUTOFILL_SERVICE)).isEqualTo("");
        assertThat(getSettingsKey(Settings.Secure.CREDENTIAL_SERVICE))
                .isEqualTo("com.example.test2/com.example.test2.TestActivity");
        assertThat(getSettingsKey(Settings.Secure.CREDENTIAL_SERVICE_PRIMARY)).isEqualTo("");
    }

    @Test
    public void onProviderRemoved_notPrimaryRemoved_success() {
        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";

        setSettingsKey(
                Settings.Secure.AUTOFILL_SERVICE,
                CredentialManagerService.AUTOFILL_PLACEHOLDER_VALUE);
        setSettingsKey(Settings.Secure.CREDENTIAL_SERVICE, testCredentialValue);
        setSettingsKey(Settings.Secure.CREDENTIAL_SERVICE_PRIMARY, testCredentialPrimaryValue);

        CredentialManagerService.updateProvidersWhenPackageRemoved(mContext, "com.example.test3");

        // Since the provider removed was not a primary provider then we should do nothing.
        assertThat(getSettingsKey(Settings.Secure.AUTOFILL_SERVICE))
                .isEqualTo(CredentialManagerService.AUTOFILL_PLACEHOLDER_VALUE);
        assertCredentialPropertyEquals(
                getSettingsKey(Settings.Secure.CREDENTIAL_SERVICE), testCredentialValue);
        assertCredentialPropertyEquals(
                getSettingsKey(Settings.Secure.CREDENTIAL_SERVICE_PRIMARY),
                testCredentialPrimaryValue);
    }

    @Test
    public void onProviderRemoved_isAlsoAutofillProvider_success() {
        setSettingsKey(
                Settings.Secure.AUTOFILL_SERVICE,
                "com.example.test/com.example.test.AutofillProvider");
        setSettingsKey(
                Settings.Secure.CREDENTIAL_SERVICE,
                "com.example.test/com.example.test.TestActivity:com.example.test2/com.example.test2.TestActivity");
        setSettingsKey(
                Settings.Secure.CREDENTIAL_SERVICE_PRIMARY,
                "com.example.test/com.example.test.TestActivity");

        CredentialManagerService.updateProvidersWhenPackageRemoved(mContext, "com.example.test");

        assertThat(getSettingsKey(Settings.Secure.AUTOFILL_SERVICE)).isEqualTo("");
        assertThat(getSettingsKey(Settings.Secure.CREDENTIAL_SERVICE))
                .isEqualTo("com.example.test2/com.example.test2.TestActivity");
        assertThat(getSettingsKey(Settings.Secure.CREDENTIAL_SERVICE_PRIMARY)).isEqualTo("");
    }

    @Test
    public void onProviderRemoved_notPrimaryRemoved_isAlsoAutofillProvider_success() {
        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";
        final String testAutofillValue = "com.example.test/com.example.test.TestAutofillActivity";

        setSettingsKey(Settings.Secure.AUTOFILL_SERVICE, testAutofillValue);
        setSettingsKey(Settings.Secure.CREDENTIAL_SERVICE, testCredentialValue);
        setSettingsKey(Settings.Secure.CREDENTIAL_SERVICE_PRIMARY, testCredentialPrimaryValue);

        CredentialManagerService.updateProvidersWhenPackageRemoved(mContext, "com.example.test3");

        // Since the provider removed was not a primary provider then we should do nothing.
        assertCredentialPropertyEquals(
                getSettingsKey(Settings.Secure.AUTOFILL_SERVICE), testAutofillValue);
        assertCredentialPropertyEquals(
                getSettingsKey(Settings.Secure.CREDENTIAL_SERVICE), testCredentialValue);
        assertCredentialPropertyEquals(
                getSettingsKey(Settings.Secure.CREDENTIAL_SERVICE_PRIMARY),
                testCredentialPrimaryValue);
    }

    private void assertCredentialPropertyEquals(String actualValue, String newValue) {
        Set<ComponentName> actualValueSet = new HashSet<>();
        for (String rawComponentName : actualValue.split(":")) {
            ComponentName cn = ComponentName.unflattenFromString(rawComponentName);
            if (cn != null) {
                actualValueSet.add(cn);
            }
        }

        Set<ComponentName> newValueSet = new HashSet<>();
        for (String rawComponentName : newValue.split(":")) {
            ComponentName cn = ComponentName.unflattenFromString(rawComponentName);
            if (cn != null) {
                newValueSet.add(cn);
            }
        }

        assertThat(actualValueSet).isEqualTo(newValueSet);
    }

    private void setSettingsKey(String key, String value) {
        assertThat(Settings.Secure.putString(mContext.getContentResolver(), key, value)).isTrue();
    }

    private String getSettingsKey(String key) {
        return Settings.Secure.getStringForUser(
                mContext.getContentResolver(), key, UserHandle.myUserId());
    }
}