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

Commit 7bb2d6e2 authored by Becca Hughes's avatar Becca Hughes Committed by Android (Google) Code Review
Browse files

Merge "Reland reset on package remove with autofill fix" into main

parents f3cce711 a0c97c46
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());
        }
@@ -1114,4 +1122,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());
    }
}