summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--services/credentials/java/com/android/server/credentials/CredentialManagerService.java109
-rw-r--r--services/tests/servicestests/src/com/android/server/credentials/CredentialManagerServiceTest.java181
2 files changed, 288 insertions, 2 deletions
diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
index a4adf5866f3d..fd8f20cabac7 100644
--- a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
+++ b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
@@ -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;
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/credentials/CredentialManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/credentials/CredentialManagerServiceTest.java
new file mode 100644
index 000000000000..fd1abff8610b
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/credentials/CredentialManagerServiceTest.java
@@ -0,0 +1,181 @@
+/*
+ * 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());
+ }
+}