Loading services/credentials/java/com/android/server/credentials/CredentialManagerService.java +68 −34 Original line number Diff line number Diff line Loading @@ -27,6 +27,7 @@ import android.annotation.Nullable; import android.annotation.UserIdInt; import android.app.ActivityManager; import android.content.ComponentName; import android.content.ContentResolver; import android.content.Context; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; Loading Loading @@ -201,7 +202,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(mContext, packageName); updateProvidersWhenPackageRemoved(new SettingsWrapper(mContext), packageName); List<CredentialManagerServiceImpl> services = peekServiceListForUserLocked(userId); if (services == null) { Loading Loading @@ -1134,13 +1135,14 @@ public final class CredentialManagerService } /** Updates the list of providers when an app is uninstalled. */ public static void updateProvidersWhenPackageRemoved(Context context, String packageName) { public static void updateProvidersWhenPackageRemoved( SettingsWrapper settingsWrapper, String packageName) { Slog.i(TAG, "updateProvidersWhenPackageRemoved"); // Get the current providers. String rawProviders = Settings.Secure.getStringForUser( context.getContentResolver(), Settings.Secure.CREDENTIAL_SERVICE_PRIMARY, UserHandle.myUserId()); settingsWrapper.getStringForUser( Settings.Secure.CREDENTIAL_SERVICE_PRIMARY, UserHandle.myUserId()); if (rawProviders == null) { Slog.w(TAG, "settings key is null"); return; Loading @@ -1148,44 +1150,44 @@ public final class CredentialManagerService // 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(), Set<String> primaryProviders = getStoredProviders(rawProviders, packageName); if (!settingsWrapper.putStringForUser( Settings.Secure.CREDENTIAL_SERVICE_PRIMARY, String.join(":", primaryProviders))) { Slog.w(TAG, "Failed to remove primary package: " + packageName); String.join(":", primaryProviders), UserHandle.myUserId(), /* overrideableByRestore= */ true)) { Slog.e(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()); settingsWrapper.getStringForUser( 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(), if (!settingsWrapper.putStringForUser( Settings.Secure.AUTOFILL_SERVICE, "")) { Slog.w(TAG, "Failed to remove autofill package: " + packageName); "", UserHandle.myUserId(), /* overrideableByRestore= */ true)) { Slog.e(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(), if (!settingsWrapper.putStringForUser( Settings.Secure.AUTOFILL_SERVICE, "")) { Slog.w(TAG, "Failed to remove autofill package: " + packageName); "", UserHandle.myUserId(), /* overrideableByRestore= */ true)) { Slog.e(TAG, "Failed to remove autofill package: " + packageName); } } } Loading @@ -1193,19 +1195,17 @@ public final class CredentialManagerService // 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()); settingsWrapper.getStringForUser( 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(), Set<String> credentialProviders = getStoredProviders(rawCredentialProviders, packageName); if (!settingsWrapper.putStringForUser( Settings.Secure.CREDENTIAL_SERVICE, String.join(":", credentialProviders))) { Slog.w(TAG, "Failed to remove secondary package: " + packageName); String.join(":", credentialProviders), UserHandle.myUserId(), /* overrideableByRestore= */ true)) { Slog.e(TAG, "Failed to remove secondary package: " + packageName); } } Loading @@ -1232,4 +1232,38 @@ public final class CredentialManagerService return providers; } /** A wrapper class that can be used by tests for intercepting reads/writes. */ public static class SettingsWrapper { private final Context mContext; public SettingsWrapper(@NonNull Context context) { this.mContext = context; } ContentResolver getContentResolver() { return mContext.getContentResolver(); } /** Retrieves the string value of a system setting */ public String getStringForUser(String name, int userHandle) { return Settings.Secure.getStringForUser(getContentResolver(), name, userHandle); } /** Updates the string value of a system setting */ public boolean putStringForUser( String name, String value, int userHandle, boolean overrideableByRestore) { return Settings.Secure.putStringForUser( getContentResolver(), name, value, null, false, userHandle, overrideableByRestore); } } } services/tests/servicestests/src/com/android/server/credentials/CredentialManagerServiceTest.java +40 −9 Original line number Diff line number Diff line Loading @@ -22,6 +22,7 @@ import android.content.Context; import android.os.UserHandle; import android.provider.Settings; import androidx.annotation.NonNull; import androidx.test.core.app.ApplicationProvider; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; Loading @@ -40,10 +41,12 @@ import java.util.Set; public final class CredentialManagerServiceTest { Context mContext = null; MockSettingsWrapper mSettingsWrapper = null; @Before public void setUp() throws CertificateException { mContext = ApplicationProvider.getApplicationContext(); mSettingsWrapper = new MockSettingsWrapper(mContext); } @Test Loading Loading @@ -81,7 +84,8 @@ public final class CredentialManagerServiceTest { Settings.Secure.CREDENTIAL_SERVICE_PRIMARY, "com.example.test/com.example.test.TestActivity"); CredentialManagerService.updateProvidersWhenPackageRemoved(mContext, "com.example.test"); CredentialManagerService.updateProvidersWhenPackageRemoved( mSettingsWrapper, "com.example.test"); assertThat(getSettingsKey(Settings.Secure.AUTOFILL_SERVICE)).isEqualTo(""); assertThat(getSettingsKey(Settings.Secure.CREDENTIAL_SERVICE)) Loading @@ -101,7 +105,8 @@ public final class CredentialManagerServiceTest { setSettingsKey(Settings.Secure.CREDENTIAL_SERVICE, testCredentialValue); setSettingsKey(Settings.Secure.CREDENTIAL_SERVICE_PRIMARY, testCredentialPrimaryValue); CredentialManagerService.updateProvidersWhenPackageRemoved(mContext, "com.example.test3"); CredentialManagerService.updateProvidersWhenPackageRemoved( mSettingsWrapper, "com.example.test3"); // Since the provider removed was not a primary provider then we should do nothing. assertThat(getSettingsKey(Settings.Secure.AUTOFILL_SERVICE)) Loading @@ -125,7 +130,8 @@ public final class CredentialManagerServiceTest { Settings.Secure.CREDENTIAL_SERVICE_PRIMARY, "com.example.test/com.example.test.TestActivity"); CredentialManagerService.updateProvidersWhenPackageRemoved(mContext, "com.example.test"); CredentialManagerService.updateProvidersWhenPackageRemoved( mSettingsWrapper, "com.example.test"); assertThat(getSettingsKey(Settings.Secure.AUTOFILL_SERVICE)).isEqualTo(""); assertThat(getSettingsKey(Settings.Secure.CREDENTIAL_SERVICE)) Loading @@ -144,7 +150,8 @@ public final class CredentialManagerServiceTest { setSettingsKey(Settings.Secure.CREDENTIAL_SERVICE, testCredentialValue); setSettingsKey(Settings.Secure.CREDENTIAL_SERVICE_PRIMARY, testCredentialPrimaryValue); CredentialManagerService.updateProvidersWhenPackageRemoved(mContext, "com.example.test3"); CredentialManagerService.updateProvidersWhenPackageRemoved( mSettingsWrapper, "com.example.test3"); // Since the provider removed was not a primary provider then we should do nothing. assertCredentialPropertyEquals( Loading Loading @@ -176,12 +183,36 @@ public final class CredentialManagerServiceTest { assertThat(actualValueSet).isEqualTo(newValueSet); } private void setSettingsKey(String key, String value) { assertThat(Settings.Secure.putString(mContext.getContentResolver(), key, value)).isTrue(); private void setSettingsKey(String name, String value) { assertThat( mSettingsWrapper.putStringForUser( name, value, UserHandle.myUserId(), true)) .isTrue(); } private String getSettingsKey(String key) { return Settings.Secure.getStringForUser( mContext.getContentResolver(), key, UserHandle.myUserId()); private String getSettingsKey(String name) { return mSettingsWrapper.getStringForUser(name, UserHandle.myUserId()); } private static final class MockSettingsWrapper extends CredentialManagerService.SettingsWrapper { MockSettingsWrapper(@NonNull Context context) { super(context); } /** Updates the string value of a system setting */ @Override public boolean putStringForUser( String name, String value, int userHandle, boolean overrideableByRestore) { // This will ensure that when the settings putStringForUser method is called by // CredentialManagerService that the overrideableByRestore bit is true. assertThat(overrideableByRestore).isTrue(); return Settings.Secure.putStringForUser(getContentResolver(), name, value, userHandle); } } } Loading
services/credentials/java/com/android/server/credentials/CredentialManagerService.java +68 −34 Original line number Diff line number Diff line Loading @@ -27,6 +27,7 @@ import android.annotation.Nullable; import android.annotation.UserIdInt; import android.app.ActivityManager; import android.content.ComponentName; import android.content.ContentResolver; import android.content.Context; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; Loading Loading @@ -201,7 +202,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(mContext, packageName); updateProvidersWhenPackageRemoved(new SettingsWrapper(mContext), packageName); List<CredentialManagerServiceImpl> services = peekServiceListForUserLocked(userId); if (services == null) { Loading Loading @@ -1134,13 +1135,14 @@ public final class CredentialManagerService } /** Updates the list of providers when an app is uninstalled. */ public static void updateProvidersWhenPackageRemoved(Context context, String packageName) { public static void updateProvidersWhenPackageRemoved( SettingsWrapper settingsWrapper, String packageName) { Slog.i(TAG, "updateProvidersWhenPackageRemoved"); // Get the current providers. String rawProviders = Settings.Secure.getStringForUser( context.getContentResolver(), Settings.Secure.CREDENTIAL_SERVICE_PRIMARY, UserHandle.myUserId()); settingsWrapper.getStringForUser( Settings.Secure.CREDENTIAL_SERVICE_PRIMARY, UserHandle.myUserId()); if (rawProviders == null) { Slog.w(TAG, "settings key is null"); return; Loading @@ -1148,44 +1150,44 @@ public final class CredentialManagerService // 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(), Set<String> primaryProviders = getStoredProviders(rawProviders, packageName); if (!settingsWrapper.putStringForUser( Settings.Secure.CREDENTIAL_SERVICE_PRIMARY, String.join(":", primaryProviders))) { Slog.w(TAG, "Failed to remove primary package: " + packageName); String.join(":", primaryProviders), UserHandle.myUserId(), /* overrideableByRestore= */ true)) { Slog.e(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()); settingsWrapper.getStringForUser( 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(), if (!settingsWrapper.putStringForUser( Settings.Secure.AUTOFILL_SERVICE, "")) { Slog.w(TAG, "Failed to remove autofill package: " + packageName); "", UserHandle.myUserId(), /* overrideableByRestore= */ true)) { Slog.e(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(), if (!settingsWrapper.putStringForUser( Settings.Secure.AUTOFILL_SERVICE, "")) { Slog.w(TAG, "Failed to remove autofill package: " + packageName); "", UserHandle.myUserId(), /* overrideableByRestore= */ true)) { Slog.e(TAG, "Failed to remove autofill package: " + packageName); } } } Loading @@ -1193,19 +1195,17 @@ public final class CredentialManagerService // 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()); settingsWrapper.getStringForUser( 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(), Set<String> credentialProviders = getStoredProviders(rawCredentialProviders, packageName); if (!settingsWrapper.putStringForUser( Settings.Secure.CREDENTIAL_SERVICE, String.join(":", credentialProviders))) { Slog.w(TAG, "Failed to remove secondary package: " + packageName); String.join(":", credentialProviders), UserHandle.myUserId(), /* overrideableByRestore= */ true)) { Slog.e(TAG, "Failed to remove secondary package: " + packageName); } } Loading @@ -1232,4 +1232,38 @@ public final class CredentialManagerService return providers; } /** A wrapper class that can be used by tests for intercepting reads/writes. */ public static class SettingsWrapper { private final Context mContext; public SettingsWrapper(@NonNull Context context) { this.mContext = context; } ContentResolver getContentResolver() { return mContext.getContentResolver(); } /** Retrieves the string value of a system setting */ public String getStringForUser(String name, int userHandle) { return Settings.Secure.getStringForUser(getContentResolver(), name, userHandle); } /** Updates the string value of a system setting */ public boolean putStringForUser( String name, String value, int userHandle, boolean overrideableByRestore) { return Settings.Secure.putStringForUser( getContentResolver(), name, value, null, false, userHandle, overrideableByRestore); } } }
services/tests/servicestests/src/com/android/server/credentials/CredentialManagerServiceTest.java +40 −9 Original line number Diff line number Diff line Loading @@ -22,6 +22,7 @@ import android.content.Context; import android.os.UserHandle; import android.provider.Settings; import androidx.annotation.NonNull; import androidx.test.core.app.ApplicationProvider; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; Loading @@ -40,10 +41,12 @@ import java.util.Set; public final class CredentialManagerServiceTest { Context mContext = null; MockSettingsWrapper mSettingsWrapper = null; @Before public void setUp() throws CertificateException { mContext = ApplicationProvider.getApplicationContext(); mSettingsWrapper = new MockSettingsWrapper(mContext); } @Test Loading Loading @@ -81,7 +84,8 @@ public final class CredentialManagerServiceTest { Settings.Secure.CREDENTIAL_SERVICE_PRIMARY, "com.example.test/com.example.test.TestActivity"); CredentialManagerService.updateProvidersWhenPackageRemoved(mContext, "com.example.test"); CredentialManagerService.updateProvidersWhenPackageRemoved( mSettingsWrapper, "com.example.test"); assertThat(getSettingsKey(Settings.Secure.AUTOFILL_SERVICE)).isEqualTo(""); assertThat(getSettingsKey(Settings.Secure.CREDENTIAL_SERVICE)) Loading @@ -101,7 +105,8 @@ public final class CredentialManagerServiceTest { setSettingsKey(Settings.Secure.CREDENTIAL_SERVICE, testCredentialValue); setSettingsKey(Settings.Secure.CREDENTIAL_SERVICE_PRIMARY, testCredentialPrimaryValue); CredentialManagerService.updateProvidersWhenPackageRemoved(mContext, "com.example.test3"); CredentialManagerService.updateProvidersWhenPackageRemoved( mSettingsWrapper, "com.example.test3"); // Since the provider removed was not a primary provider then we should do nothing. assertThat(getSettingsKey(Settings.Secure.AUTOFILL_SERVICE)) Loading @@ -125,7 +130,8 @@ public final class CredentialManagerServiceTest { Settings.Secure.CREDENTIAL_SERVICE_PRIMARY, "com.example.test/com.example.test.TestActivity"); CredentialManagerService.updateProvidersWhenPackageRemoved(mContext, "com.example.test"); CredentialManagerService.updateProvidersWhenPackageRemoved( mSettingsWrapper, "com.example.test"); assertThat(getSettingsKey(Settings.Secure.AUTOFILL_SERVICE)).isEqualTo(""); assertThat(getSettingsKey(Settings.Secure.CREDENTIAL_SERVICE)) Loading @@ -144,7 +150,8 @@ public final class CredentialManagerServiceTest { setSettingsKey(Settings.Secure.CREDENTIAL_SERVICE, testCredentialValue); setSettingsKey(Settings.Secure.CREDENTIAL_SERVICE_PRIMARY, testCredentialPrimaryValue); CredentialManagerService.updateProvidersWhenPackageRemoved(mContext, "com.example.test3"); CredentialManagerService.updateProvidersWhenPackageRemoved( mSettingsWrapper, "com.example.test3"); // Since the provider removed was not a primary provider then we should do nothing. assertCredentialPropertyEquals( Loading Loading @@ -176,12 +183,36 @@ public final class CredentialManagerServiceTest { assertThat(actualValueSet).isEqualTo(newValueSet); } private void setSettingsKey(String key, String value) { assertThat(Settings.Secure.putString(mContext.getContentResolver(), key, value)).isTrue(); private void setSettingsKey(String name, String value) { assertThat( mSettingsWrapper.putStringForUser( name, value, UserHandle.myUserId(), true)) .isTrue(); } private String getSettingsKey(String key) { return Settings.Secure.getStringForUser( mContext.getContentResolver(), key, UserHandle.myUserId()); private String getSettingsKey(String name) { return mSettingsWrapper.getStringForUser(name, UserHandle.myUserId()); } private static final class MockSettingsWrapper extends CredentialManagerService.SettingsWrapper { MockSettingsWrapper(@NonNull Context context) { super(context); } /** Updates the string value of a system setting */ @Override public boolean putStringForUser( String name, String value, int userHandle, boolean overrideableByRestore) { // This will ensure that when the settings putStringForUser method is called by // CredentialManagerService that the overrideableByRestore bit is true. assertThat(overrideableByRestore).isTrue(); return Settings.Secure.putStringForUser(getContentResolver(), name, value, userHandle); } } }