diff options
7 files changed, 367 insertions, 240 deletions
diff --git a/core/java/android/credentials/CredentialManager.java b/core/java/android/credentials/CredentialManager.java index 9140d02e223d..c2a0062b43e8 100644 --- a/core/java/android/credentials/CredentialManager.java +++ b/core/java/android/credentials/CredentialManager.java @@ -355,7 +355,11 @@ public final class CredentialManager { * Sets a list of all user configurable credential providers registered on the system. This API * is intended for settings apps. * - * @param providers the list of enabled providers + * @param primaryProviders the primary providers that user selected for saving credentials. In + * the most case, there should be only one primary provider, However, + * if there are more than one CredentialProviderService in the same APK, + * they should be passed in altogether. + * @param providers the list of enabled providers. * @param userId the user ID to configure credential manager for * @param executor the callback will take place on this {@link Executor} * @param callback the callback invoked when the request succeeds or fails @@ -363,6 +367,7 @@ public final class CredentialManager { */ @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void setEnabledProviders( + @NonNull List<String> primaryProviders, @NonNull List<String> providers, int userId, @CallbackExecutor @NonNull Executor executor, @@ -370,9 +375,11 @@ public final class CredentialManager { requireNonNull(executor, "executor must not be null"); requireNonNull(callback, "callback must not be null"); requireNonNull(providers, "providers must not be null"); + requireNonNull(primaryProviders, "primaryProviders must not be null"); try { mService.setEnabledProviders( + primaryProviders, providers, userId, new SetEnabledProvidersTransport(executor, callback)); } catch (RemoteException e) { e.rethrowFromSystemServer(); diff --git a/core/java/android/credentials/CredentialProviderInfo.java b/core/java/android/credentials/CredentialProviderInfo.java index c224f01055e1..d66b8f00a2e9 100644 --- a/core/java/android/credentials/CredentialProviderInfo.java +++ b/core/java/android/credentials/CredentialProviderInfo.java @@ -46,6 +46,7 @@ public final class CredentialProviderInfo implements Parcelable { @Nullable private CharSequence mSettingsSubtitle = null; private final boolean mIsSystemProvider; private final boolean mIsEnabled; + private final boolean mIsPrimary; /** * Constructs an information instance of the credential provider. @@ -58,6 +59,7 @@ public final class CredentialProviderInfo implements Parcelable { mIsSystemProvider = builder.mIsSystemProvider; mSettingsSubtitle = builder.mSettingsSubtitle; mIsEnabled = builder.mIsEnabled; + mIsPrimary = builder.mIsPrimary; mOverrideLabel = builder.mOverrideLabel; } @@ -108,6 +110,15 @@ public final class CredentialProviderInfo implements Parcelable { return mIsEnabled; } + /** + * Returns whether the provider is set as primary by the user. + * + * @hide + */ + public boolean isPrimary() { + return mIsPrimary; + } + /** Returns the settings subtitle. */ @Nullable public CharSequence getSettingsSubtitle() { @@ -125,6 +136,7 @@ public final class CredentialProviderInfo implements Parcelable { dest.writeTypedObject(mServiceInfo, flags); dest.writeBoolean(mIsSystemProvider); dest.writeBoolean(mIsEnabled); + dest.writeBoolean(mIsPrimary); TextUtils.writeToParcel(mOverrideLabel, dest, flags); TextUtils.writeToParcel(mSettingsSubtitle, dest, flags); @@ -149,6 +161,9 @@ public final class CredentialProviderInfo implements Parcelable { + "isEnabled=" + mIsEnabled + ", " + + "isPrimary=" + + mIsPrimary + + ", " + "overrideLabel=" + mOverrideLabel + ", " @@ -164,6 +179,7 @@ public final class CredentialProviderInfo implements Parcelable { mServiceInfo = in.readTypedObject(ServiceInfo.CREATOR); mIsSystemProvider = in.readBoolean(); mIsEnabled = in.readBoolean(); + mIsPrimary = in.readBoolean(); mOverrideLabel = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); mSettingsSubtitle = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); @@ -193,6 +209,7 @@ public final class CredentialProviderInfo implements Parcelable { private boolean mIsSystemProvider = false; @Nullable private CharSequence mSettingsSubtitle = null; private boolean mIsEnabled = false; + private boolean mIsPrimary = false; @Nullable private CharSequence mOverrideLabel = null; /** @@ -248,6 +265,20 @@ public final class CredentialProviderInfo implements Parcelable { return this; } + /** + * Sets whether it is set as primary by the user. + * + * <p>Primary provider will be used for saving credentials by default. In most cases, there + * should only one primary provider exist. However, if there are multiple credential + * providers exist in the same package, all of them will be marked as primary. + * + * @hide + */ + public @NonNull Builder setPrimary(boolean isPrimary) { + mIsPrimary = isPrimary; + return this; + } + /** Builds a new {@link CredentialProviderInfo} instance. */ public @NonNull CredentialProviderInfo build() { return new CredentialProviderInfo(this); diff --git a/core/java/android/credentials/ICredentialManager.aidl b/core/java/android/credentials/ICredentialManager.aidl index b779c56035d3..dec729f4a19f 100644 --- a/core/java/android/credentials/ICredentialManager.aidl +++ b/core/java/android/credentials/ICredentialManager.aidl @@ -47,7 +47,7 @@ interface ICredentialManager { @nullable ICancellationSignal clearCredentialState(in ClearCredentialStateRequest request, in IClearCredentialStateCallback callback, String callingPackage); - void setEnabledProviders(in List<String> providers, in int userId, in ISetEnabledProvidersCallback callback); + void setEnabledProviders(in List<String> primaryProviders, in List<String> providers, in int userId, in ISetEnabledProvidersCallback callback); void registerCredentialDescription(in RegisterCredentialDescriptionRequest request, String callingPackage); diff --git a/core/java/android/service/credentials/CredentialProviderInfoFactory.java b/core/java/android/service/credentials/CredentialProviderInfoFactory.java index 751c675b28f4..b196b064a670 100644 --- a/core/java/android/service/credentials/CredentialProviderInfoFactory.java +++ b/core/java/android/service/credentials/CredentialProviderInfoFactory.java @@ -75,13 +75,12 @@ public final class CredentialProviderInfoFactory { /** * Constructs an information instance of the credential provider. * - * @param context the context object + * @param context the context object * @param serviceComponent the serviceComponent of the provider service - * @param userId the android userId for which the current process is running + * @param userId the android userId for which the current process is running * @param isSystemProvider whether this provider is a system provider * @throws PackageManager.NameNotFoundException If provider service is not found - * @throws SecurityException If provider does not require the relevant - * permission + * @throws SecurityException If provider does not require the relevant permission */ public static CredentialProviderInfo create( @NonNull Context context, @@ -94,21 +93,20 @@ public final class CredentialProviderInfoFactory { getServiceInfoOrThrow(serviceComponent, userId), isSystemProvider, /* disableSystemAppVerificationForTests= */ false, - /* isEnabled= */ false); + /* isEnabled= */ false, + /* isPrimary= */ false); } /** * Constructs an information instance of the credential provider. * - * @param context the context object - * @param serviceInfo the service info for the provider app. This must - * be retrieved from the - * {@code PackageManager} - * @param isSystemProvider whether the provider app is a system provider + * @param context the context object + * @param serviceInfo the service info for the provider app. This must be retrieved from the + * {@code PackageManager} + * @param isSystemProvider whether the provider app is a system provider * @param disableSystemAppVerificationForTests whether to disable system app permission - * verification so that tests can install system - * providers - * @param isEnabled whether the user enabled this provider + * verification so that tests can install system providers + * @param isEnabled whether the user enabled this provider * @throws SecurityException If provider does not require the relevant permission */ public static CredentialProviderInfo create( @@ -116,7 +114,8 @@ public final class CredentialProviderInfoFactory { @NonNull ServiceInfo serviceInfo, boolean isSystemProvider, boolean disableSystemAppVerificationForTests, - boolean isEnabled) + boolean isEnabled, + boolean isPrimary) throws SecurityException { verifyProviderPermission(serviceInfo); if (isSystemProvider) { @@ -131,6 +130,7 @@ public final class CredentialProviderInfoFactory { return populateMetadata(context, serviceInfo) .setSystemProvider(isSystemProvider) .setEnabled(isEnabled) + .setPrimary(isPrimary) .build(); } @@ -168,7 +168,9 @@ public final class CredentialProviderInfoFactory { Slog.w(TAG, "Context is null in isSystemProviderWithValidPermission"); return false; } - return PermissionUtils.hasPermission(context, serviceInfo.packageName, + return PermissionUtils.hasPermission( + context, + serviceInfo.packageName, Manifest.permission.PROVIDE_DEFAULT_ENABLED_CREDENTIAL_SERVICE); } @@ -181,8 +183,11 @@ public final class CredentialProviderInfoFactory { if (disableSystemAppVerificationForTests) { Bundle metadata = serviceInfo.metaData; if (metadata == null) { - Slog.w(TAG, "metadata is null while reading " - + "TEST_SYSTEM_PROVIDER_META_DATA_KEY: " + serviceInfo); + Slog.w( + TAG, + "metadata is null while reading " + + "TEST_SYSTEM_PROVIDER_META_DATA_KEY: " + + serviceInfo); return false; } return metadata.getBoolean( @@ -215,8 +220,10 @@ public final class CredentialProviderInfoFactory { // 3. Stop if we are missing data. if (resources == null) { - Slog.w(TAG, "Resources are null for the serviceInfo being processed: " - + serviceInfo.getComponentName()); + Slog.w( + TAG, + "Resources are null for the serviceInfo being processed: " + + serviceInfo.getComponentName()); return builder; } @@ -408,7 +415,7 @@ public final class CredentialProviderInfoFactory { si, /* isSystemProvider= */ true, disableSystemAppVerificationForTests, - enabledServices.contains(si.getComponentName())); + enabledServices.contains(si.getComponentName()), false); if (cpi.isSystemProvider()) { providerInfos.add(cpi); } else { @@ -446,7 +453,8 @@ public final class CredentialProviderInfoFactory { @NonNull Context context, int userId, int providerFilter, - Set<ComponentName> enabledServices) { + Set<ComponentName> enabledServices, + Set<String> primaryServices) { requireNonNull(context, "context must not be null"); // Get the device policy. @@ -459,7 +467,11 @@ public final class CredentialProviderInfoFactory { context, pp, disableSystemAppVerificationForTests, providerFilter); generator.addUserProviders( getUserProviders( - context, userId, disableSystemAppVerificationForTests, enabledServices)); + context, + userId, + disableSystemAppVerificationForTests, + enabledServices, + primaryServices)); generator.addSystemProviders( getAvailableSystemServices( context, userId, disableSystemAppVerificationForTests, enabledServices)); @@ -475,7 +487,8 @@ public final class CredentialProviderInfoFactory { @NonNull Context context, int userId, int providerFilter, - Set<ComponentName> enabledServices) { + Set<ComponentName> enabledServices, + Set<String> primaryServices) { requireNonNull(context, "context must not be null"); // Get the device policy. @@ -488,7 +501,11 @@ public final class CredentialProviderInfoFactory { context, pp, disableSystemAppVerificationForTests, providerFilter); generator.addUserProviders( getUserProviders( - context, userId, disableSystemAppVerificationForTests, enabledServices)); + context, + userId, + disableSystemAppVerificationForTests, + enabledServices, + primaryServices)); generator.addSystemProviders( getAvailableSystemServices( context, userId, disableSystemAppVerificationForTests, enabledServices)); @@ -581,7 +598,8 @@ public final class CredentialProviderInfoFactory { @NonNull Context context, @UserIdInt int userId, boolean disableSystemAppVerificationForTests, - Set<ComponentName> enabledServices) { + Set<ComponentName> enabledServices, + Set<String> primaryServices) { final List<CredentialProviderInfo> services = new ArrayList<>(); final List<ResolveInfo> resolveInfos = context.getPackageManager() @@ -603,7 +621,9 @@ public final class CredentialProviderInfoFactory { serviceInfo, /* isSystemProvider= */ false, disableSystemAppVerificationForTests, - enabledServices.contains(serviceInfo.getComponentName())); + enabledServices.contains(serviceInfo.getComponentName()), + primaryServices.contains( + serviceInfo.getComponentName().flattenToString())); if (!cpi.isSystemProvider()) { services.add(cpi); } diff --git a/core/tests/coretests/src/android/credentials/CredentialManagerTest.java b/core/tests/coretests/src/android/credentials/CredentialManagerTest.java index b0d5240c61ba..dc4c2527cc03 100644 --- a/core/tests/coretests/src/android/credentials/CredentialManagerTest.java +++ b/core/tests/coretests/src/android/credentials/CredentialManagerTest.java @@ -56,22 +56,20 @@ import java.util.concurrent.Executor; @RunWith(MockitoJUnitRunner.class) public class CredentialManagerTest { - @Mock - private ICredentialManager mMockCredentialManagerService; + @Mock private ICredentialManager mMockCredentialManagerService; - @Mock - private Activity mMockActivity; + @Mock private Activity mMockActivity; private static final int TEST_USER_ID = 1; private static final CredentialProviderInfo TEST_CREDENTIAL_PROVIDER_INFO = - new CredentialProviderInfo.Builder(new ServiceInfo()) - .setSystemProvider(true) - .setOverrideLabel("test") - .addCapabilities(Arrays.asList("passkey")) - .setEnabled(true) - .build(); + new CredentialProviderInfo.Builder(new ServiceInfo()) + .setSystemProvider(true) + .setOverrideLabel("test") + .addCapabilities(Arrays.asList("passkey")) + .setEnabled(true) + .build(); private static final List<CredentialProviderInfo> TEST_CREDENTIAL_PROVIDER_INFO_LIST = - Arrays.asList(TEST_CREDENTIAL_PROVIDER_INFO); + Arrays.asList(TEST_CREDENTIAL_PROVIDER_INFO); private GetCredentialRequest mGetRequest; private CreateCredentialRequest mCreateRequest; @@ -112,27 +110,43 @@ public class CredentialManagerTest { @Before public void setup() { - mGetRequest = new GetCredentialRequest.Builder(Bundle.EMPTY).addCredentialOption( - new CredentialOption(Credential.TYPE_PASSWORD_CREDENTIAL, Bundle.EMPTY, - Bundle.EMPTY, false)).build(); - mCreateRequest = new CreateCredentialRequest.Builder( - Credential.TYPE_PASSWORD_CREDENTIAL, - Bundle.EMPTY, Bundle.EMPTY) - .setIsSystemProviderRequired(false) - .setAlwaysSendAppInfoToProvider(false) - .build(); + mGetRequest = + new GetCredentialRequest.Builder(Bundle.EMPTY) + .addCredentialOption( + new CredentialOption( + Credential.TYPE_PASSWORD_CREDENTIAL, + Bundle.EMPTY, + Bundle.EMPTY, + false)) + .build(); + mCreateRequest = + new CreateCredentialRequest.Builder( + Credential.TYPE_PASSWORD_CREDENTIAL, Bundle.EMPTY, Bundle.EMPTY) + .setIsSystemProviderRequired(false) + .setAlwaysSendAppInfoToProvider(false) + .build(); mClearRequest = new ClearCredentialStateRequest(Bundle.EMPTY); - final Slice slice = new Slice.Builder(Uri.parse("foo://bar"), null).addText("some text", - null, List.of(Slice.HINT_TITLE)).build(); - mRegisterRequest = new RegisterCredentialDescriptionRequest( - new CredentialDescription(Credential.TYPE_PASSWORD_CREDENTIAL, - new HashSet<>(List.of("{ \"foo\": \"bar\" }")), - List.of(new CredentialEntry(Credential.TYPE_PASSWORD_CREDENTIAL, slice)))); - mUnregisterRequest = new UnregisterCredentialDescriptionRequest( - new CredentialDescription(Credential.TYPE_PASSWORD_CREDENTIAL, - new HashSet<>(List.of("{ \"foo\": \"bar\" }")), - List.of(new CredentialEntry(Credential.TYPE_PASSWORD_CREDENTIAL, slice)))); + final Slice slice = + new Slice.Builder(Uri.parse("foo://bar"), null) + .addText("some text", null, List.of(Slice.HINT_TITLE)) + .build(); + mRegisterRequest = + new RegisterCredentialDescriptionRequest( + new CredentialDescription( + Credential.TYPE_PASSWORD_CREDENTIAL, + new HashSet<>(List.of("{ \"foo\": \"bar\" }")), + List.of( + new CredentialEntry( + Credential.TYPE_PASSWORD_CREDENTIAL, slice)))); + mUnregisterRequest = + new UnregisterCredentialDescriptionRequest( + new CredentialDescription( + Credential.TYPE_PASSWORD_CREDENTIAL, + new HashSet<>(List.of("{ \"foo\": \"bar\" }")), + List.of( + new CredentialEntry( + Credential.TYPE_PASSWORD_CREDENTIAL, slice)))); final Context context = InstrumentationRegistry.getInstrumentation().getContext(); mCredentialManager = new CredentialManager(context, mMockCredentialManagerService); @@ -143,56 +157,63 @@ public class CredentialManagerTest { @Test public void testGetCredential_nullRequest() { GetCredentialRequest nullRequest = null; - assertThrows(NullPointerException.class, - () -> mCredentialManager.getCredential(mMockActivity, nullRequest, null, mExecutor, - result -> { - })); + assertThrows( + NullPointerException.class, + () -> + mCredentialManager.getCredential( + mMockActivity, nullRequest, null, mExecutor, result -> {})); } @Test public void testGetCredential_nullActivity() { - assertThrows(NullPointerException.class, - () -> mCredentialManager.getCredential(null, mGetRequest, null, mExecutor, - result -> { - })); + assertThrows( + NullPointerException.class, + () -> + mCredentialManager.getCredential( + null, mGetRequest, null, mExecutor, result -> {})); } @Test public void testGetCredential_nullExecutor() { - assertThrows(NullPointerException.class, - () -> mCredentialManager.getCredential(mMockActivity, mGetRequest, null, null, - result -> { - })); + assertThrows( + NullPointerException.class, + () -> + mCredentialManager.getCredential( + mMockActivity, mGetRequest, null, null, result -> {})); } @Test public void testGetCredential_nullCallback() { - assertThrows(NullPointerException.class, - () -> mCredentialManager.getCredential(mMockActivity, mGetRequest, null, null, - null)); + assertThrows( + NullPointerException.class, + () -> + mCredentialManager.getCredential( + mMockActivity, mGetRequest, null, null, null)); } @Test public void testGetCredential_noCredential() throws RemoteException { - ArgumentCaptor<IGetCredentialCallback> callbackCaptor = ArgumentCaptor.forClass( - IGetCredentialCallback.class); - ArgumentCaptor<GetCredentialException> errorCaptor = ArgumentCaptor.forClass( - GetCredentialException.class); + ArgumentCaptor<IGetCredentialCallback> callbackCaptor = + ArgumentCaptor.forClass(IGetCredentialCallback.class); + ArgumentCaptor<GetCredentialException> errorCaptor = + ArgumentCaptor.forClass(GetCredentialException.class); - OutcomeReceiver<GetCredentialResponse, GetCredentialException> callback = mock( - OutcomeReceiver.class); + OutcomeReceiver<GetCredentialResponse, GetCredentialException> callback = + mock(OutcomeReceiver.class); - when(mMockCredentialManagerService.executeGetCredential(any(), callbackCaptor.capture(), - any())).thenReturn(mock(ICancellationSignal.class)); + when(mMockCredentialManagerService.executeGetCredential( + any(), callbackCaptor.capture(), any())) + .thenReturn(mock(ICancellationSignal.class)); mCredentialManager.getCredential(mMockActivity, mGetRequest, null, mExecutor, callback); verify(mMockCredentialManagerService).executeGetCredential(any(), any(), eq(mPackageName)); - callbackCaptor.getValue().onError(GetCredentialException.TYPE_NO_CREDENTIAL, - "no credential found"); + callbackCaptor + .getValue() + .onError(GetCredentialException.TYPE_NO_CREDENTIAL, "no credential found"); verify(callback).onError(errorCaptor.capture()); - assertThat(errorCaptor.getValue().getType()).isEqualTo( - GetCredentialException.TYPE_NO_CREDENTIAL); + assertThat(errorCaptor.getValue().getType()) + .isEqualTo(GetCredentialException.TYPE_NO_CREDENTIAL); } @Test @@ -200,9 +221,8 @@ public class CredentialManagerTest { final CancellationSignal cancellation = new CancellationSignal(); cancellation.cancel(); - mCredentialManager.getCredential(mMockActivity, mGetRequest, cancellation, mExecutor, - result -> { - }); + mCredentialManager.getCredential( + mMockActivity, mGetRequest, cancellation, mExecutor, result -> {}); verify(mMockCredentialManagerService, never()).executeGetCredential(any(), any(), any()); } @@ -212,14 +232,14 @@ public class CredentialManagerTest { final ICancellationSignal serviceSignal = mock(ICancellationSignal.class); final CancellationSignal cancellation = new CancellationSignal(); - OutcomeReceiver<GetCredentialResponse, GetCredentialException> callback = mock( - OutcomeReceiver.class); + OutcomeReceiver<GetCredentialResponse, GetCredentialException> callback = + mock(OutcomeReceiver.class); - when(mMockCredentialManagerService.executeGetCredential(any(), any(), any())).thenReturn( - serviceSignal); + when(mMockCredentialManagerService.executeGetCredential(any(), any(), any())) + .thenReturn(serviceSignal); - mCredentialManager.getCredential(mMockActivity, mGetRequest, cancellation, mExecutor, - callback); + mCredentialManager.getCredential( + mMockActivity, mGetRequest, cancellation, mExecutor, callback); verify(mMockCredentialManagerService).executeGetCredential(any(), any(), eq(mPackageName)); @@ -231,16 +251,17 @@ public class CredentialManagerTest { public void testGetCredential_success() throws RemoteException { final Credential cred = new Credential(Credential.TYPE_PASSWORD_CREDENTIAL, Bundle.EMPTY); - ArgumentCaptor<IGetCredentialCallback> callbackCaptor = ArgumentCaptor.forClass( - IGetCredentialCallback.class); - ArgumentCaptor<GetCredentialResponse> responseCaptor = ArgumentCaptor.forClass( - GetCredentialResponse.class); + ArgumentCaptor<IGetCredentialCallback> callbackCaptor = + ArgumentCaptor.forClass(IGetCredentialCallback.class); + ArgumentCaptor<GetCredentialResponse> responseCaptor = + ArgumentCaptor.forClass(GetCredentialResponse.class); - OutcomeReceiver<GetCredentialResponse, GetCredentialException> callback = mock( - OutcomeReceiver.class); + OutcomeReceiver<GetCredentialResponse, GetCredentialException> callback = + mock(OutcomeReceiver.class); - when(mMockCredentialManagerService.executeGetCredential(any(), callbackCaptor.capture(), - any())).thenReturn(mock(ICancellationSignal.class)); + when(mMockCredentialManagerService.executeGetCredential( + any(), callbackCaptor.capture(), any())) + .thenReturn(mock(ICancellationSignal.class)); mCredentialManager.getCredential(mMockActivity, mGetRequest, null, mExecutor, callback); verify(mMockCredentialManagerService).executeGetCredential(any(), any(), eq(mPackageName)); @@ -252,33 +273,38 @@ public class CredentialManagerTest { @Test public void testCreateCredential_nullRequest() { - assertThrows(NullPointerException.class, - () -> mCredentialManager.createCredential(mMockActivity, null, null, mExecutor, - result -> { - })); + assertThrows( + NullPointerException.class, + () -> + mCredentialManager.createCredential( + mMockActivity, null, null, mExecutor, result -> {})); } @Test public void testCreateCredential_nullActivity() { - assertThrows(NullPointerException.class, - () -> mCredentialManager.createCredential(null, mCreateRequest, null, mExecutor, - result -> { - })); + assertThrows( + NullPointerException.class, + () -> + mCredentialManager.createCredential( + null, mCreateRequest, null, mExecutor, result -> {})); } @Test public void testCreateCredential_nullExecutor() { - assertThrows(NullPointerException.class, - () -> mCredentialManager.createCredential(mMockActivity, mCreateRequest, null, null, - result -> { - })); + assertThrows( + NullPointerException.class, + () -> + mCredentialManager.createCredential( + mMockActivity, mCreateRequest, null, null, result -> {})); } @Test public void testCreateCredential_nullCallback() { - assertThrows(NullPointerException.class, - () -> mCredentialManager.createCredential(mMockActivity, mCreateRequest, null, - mExecutor, null)); + assertThrows( + NullPointerException.class, + () -> + mCredentialManager.createCredential( + mMockActivity, mCreateRequest, null, mExecutor, null)); } @Test @@ -286,9 +312,8 @@ public class CredentialManagerTest { final CancellationSignal cancellation = new CancellationSignal(); cancellation.cancel(); - mCredentialManager.createCredential(mMockActivity, mCreateRequest, cancellation, mExecutor, - result -> { - }); + mCredentialManager.createCredential( + mMockActivity, mCreateRequest, cancellation, mExecutor, result -> {}); verify(mMockCredentialManagerService, never()).executeCreateCredential(any(), any(), any()); } @@ -298,17 +323,17 @@ public class CredentialManagerTest { final ICancellationSignal serviceSignal = mock(ICancellationSignal.class); final CancellationSignal cancellation = new CancellationSignal(); - OutcomeReceiver<CreateCredentialResponse, CreateCredentialException> callback = mock( - OutcomeReceiver.class); + OutcomeReceiver<CreateCredentialResponse, CreateCredentialException> callback = + mock(OutcomeReceiver.class); - when(mMockCredentialManagerService.executeCreateCredential(any(), any(), any())).thenReturn( - serviceSignal); + when(mMockCredentialManagerService.executeCreateCredential(any(), any(), any())) + .thenReturn(serviceSignal); - mCredentialManager.createCredential(mMockActivity, mCreateRequest, cancellation, mExecutor, - callback); + mCredentialManager.createCredential( + mMockActivity, mCreateRequest, cancellation, mExecutor, callback); - verify(mMockCredentialManagerService).executeCreateCredential(any(), any(), - eq(mPackageName)); + verify(mMockCredentialManagerService) + .executeCreateCredential(any(), any(), eq(mPackageName)); cancellation.cancel(); verify(serviceSignal).cancel(); @@ -316,26 +341,27 @@ public class CredentialManagerTest { @Test public void testCreateCredential_failed() throws RemoteException { - ArgumentCaptor<ICreateCredentialCallback> callbackCaptor = ArgumentCaptor.forClass( - ICreateCredentialCallback.class); - ArgumentCaptor<CreateCredentialException> errorCaptor = ArgumentCaptor.forClass( - CreateCredentialException.class); - - OutcomeReceiver<CreateCredentialResponse, CreateCredentialException> callback = mock( - OutcomeReceiver.class); - - when(mMockCredentialManagerService.executeCreateCredential(any(), callbackCaptor.capture(), - any())).thenReturn(mock(ICancellationSignal.class)); - mCredentialManager.createCredential(mMockActivity, mCreateRequest, null, mExecutor, - callback); - verify(mMockCredentialManagerService).executeCreateCredential(any(), any(), - eq(mPackageName)); + ArgumentCaptor<ICreateCredentialCallback> callbackCaptor = + ArgumentCaptor.forClass(ICreateCredentialCallback.class); + ArgumentCaptor<CreateCredentialException> errorCaptor = + ArgumentCaptor.forClass(CreateCredentialException.class); + + OutcomeReceiver<CreateCredentialResponse, CreateCredentialException> callback = + mock(OutcomeReceiver.class); + + when(mMockCredentialManagerService.executeCreateCredential( + any(), callbackCaptor.capture(), any())) + .thenReturn(mock(ICancellationSignal.class)); + mCredentialManager.createCredential( + mMockActivity, mCreateRequest, null, mExecutor, callback); + verify(mMockCredentialManagerService) + .executeCreateCredential(any(), any(), eq(mPackageName)); callbackCaptor.getValue().onError(CreateCredentialException.TYPE_UNKNOWN, "unknown error"); verify(callback).onError(errorCaptor.capture()); - assertThat(errorCaptor.getValue().getType()).isEqualTo( - CreateCredentialException.TYPE_UNKNOWN); + assertThat(errorCaptor.getValue().getType()) + .isEqualTo(CreateCredentialException.TYPE_UNKNOWN); } @Test @@ -343,20 +369,21 @@ public class CredentialManagerTest { final Bundle responseData = new Bundle(); responseData.putString("foo", "bar"); - ArgumentCaptor<ICreateCredentialCallback> callbackCaptor = ArgumentCaptor.forClass( - ICreateCredentialCallback.class); - ArgumentCaptor<CreateCredentialResponse> responseCaptor = ArgumentCaptor.forClass( - CreateCredentialResponse.class); + ArgumentCaptor<ICreateCredentialCallback> callbackCaptor = + ArgumentCaptor.forClass(ICreateCredentialCallback.class); + ArgumentCaptor<CreateCredentialResponse> responseCaptor = + ArgumentCaptor.forClass(CreateCredentialResponse.class); - OutcomeReceiver<CreateCredentialResponse, CreateCredentialException> callback = mock( - OutcomeReceiver.class); + OutcomeReceiver<CreateCredentialResponse, CreateCredentialException> callback = + mock(OutcomeReceiver.class); - when(mMockCredentialManagerService.executeCreateCredential(any(), callbackCaptor.capture(), - any())).thenReturn(mock(ICancellationSignal.class)); - mCredentialManager.createCredential(mMockActivity, mCreateRequest, null, mExecutor, - callback); - verify(mMockCredentialManagerService).executeCreateCredential(any(), any(), - eq(mPackageName)); + when(mMockCredentialManagerService.executeCreateCredential( + any(), callbackCaptor.capture(), any())) + .thenReturn(mock(ICancellationSignal.class)); + mCredentialManager.createCredential( + mMockActivity, mCreateRequest, null, mExecutor, callback); + verify(mMockCredentialManagerService) + .executeCreateCredential(any(), any(), eq(mPackageName)); callbackCaptor.getValue().onResponse(new CreateCredentialResponse(responseData)); verify(callback).onResult(responseCaptor.capture()); @@ -366,23 +393,27 @@ public class CredentialManagerTest { @Test public void testClearCredentialState_nullRequest() { - assertThrows(NullPointerException.class, - () -> mCredentialManager.clearCredentialState(null, null, mExecutor, result -> { - })); + assertThrows( + NullPointerException.class, + () -> mCredentialManager.clearCredentialState(null, null, mExecutor, result -> {})); } @Test public void testClearCredentialState_nullExecutor() { - assertThrows(NullPointerException.class, - () -> mCredentialManager.clearCredentialState(mClearRequest, null, null, result -> { - })); + assertThrows( + NullPointerException.class, + () -> + mCredentialManager.clearCredentialState( + mClearRequest, null, null, result -> {})); } @Test public void testClearCredentialState_nullCallback() { - assertThrows(NullPointerException.class, - () -> mCredentialManager.clearCredentialState(mClearRequest, null, mExecutor, - null)); + assertThrows( + NullPointerException.class, + () -> + mCredentialManager.clearCredentialState( + mClearRequest, null, mExecutor, null)); } @Test @@ -390,8 +421,8 @@ public class CredentialManagerTest { final CancellationSignal cancellation = new CancellationSignal(); cancellation.cancel(); - mCredentialManager.clearCredentialState(mClearRequest, cancellation, mExecutor, result -> { - }); + mCredentialManager.clearCredentialState( + mClearRequest, cancellation, mExecutor, result -> {}); verify(mMockCredentialManagerService, never()).clearCredentialState(any(), any(), any()); } @@ -403,8 +434,8 @@ public class CredentialManagerTest { OutcomeReceiver<Void, ClearCredentialStateException> callback = mock(OutcomeReceiver.class); - when(mMockCredentialManagerService.clearCredentialState(any(), any(), any())).thenReturn( - serviceSignal); + when(mMockCredentialManagerService.clearCredentialState(any(), any(), any())) + .thenReturn(serviceSignal); mCredentialManager.clearCredentialState(mClearRequest, cancellation, mExecutor, callback); @@ -416,35 +447,38 @@ public class CredentialManagerTest { @Test public void testClearCredential_failed() throws RemoteException { - ArgumentCaptor<IClearCredentialStateCallback> callbackCaptor = ArgumentCaptor.forClass( - IClearCredentialStateCallback.class); - ArgumentCaptor<ClearCredentialStateException> errorCaptor = ArgumentCaptor.forClass( - ClearCredentialStateException.class); + ArgumentCaptor<IClearCredentialStateCallback> callbackCaptor = + ArgumentCaptor.forClass(IClearCredentialStateCallback.class); + ArgumentCaptor<ClearCredentialStateException> errorCaptor = + ArgumentCaptor.forClass(ClearCredentialStateException.class); OutcomeReceiver<Void, ClearCredentialStateException> callback = mock(OutcomeReceiver.class); - when(mMockCredentialManagerService.clearCredentialState(any(), callbackCaptor.capture(), - any())).thenReturn(mock(ICancellationSignal.class)); + when(mMockCredentialManagerService.clearCredentialState( + any(), callbackCaptor.capture(), any())) + .thenReturn(mock(ICancellationSignal.class)); mCredentialManager.clearCredentialState(mClearRequest, null, mExecutor, callback); verify(mMockCredentialManagerService).clearCredentialState(any(), any(), eq(mPackageName)); - callbackCaptor.getValue().onError(ClearCredentialStateException.TYPE_UNKNOWN, - "unknown error"); + callbackCaptor + .getValue() + .onError(ClearCredentialStateException.TYPE_UNKNOWN, "unknown error"); verify(callback).onError(errorCaptor.capture()); - assertThat(errorCaptor.getValue().getType()).isEqualTo( - ClearCredentialStateException.TYPE_UNKNOWN); + assertThat(errorCaptor.getValue().getType()) + .isEqualTo(ClearCredentialStateException.TYPE_UNKNOWN); } @Test public void testClearCredential_success() throws RemoteException { - ArgumentCaptor<IClearCredentialStateCallback> callbackCaptor = ArgumentCaptor.forClass( - IClearCredentialStateCallback.class); + ArgumentCaptor<IClearCredentialStateCallback> callbackCaptor = + ArgumentCaptor.forClass(IClearCredentialStateCallback.class); OutcomeReceiver<Void, ClearCredentialStateException> callback = mock(OutcomeReceiver.class); - when(mMockCredentialManagerService.clearCredentialState(any(), callbackCaptor.capture(), - any())).thenReturn(mock(ICancellationSignal.class)); + when(mMockCredentialManagerService.clearCredentialState( + any(), callbackCaptor.capture(), any())) + .thenReturn(mock(ICancellationSignal.class)); mCredentialManager.clearCredentialState(mClearRequest, null, mExecutor, callback); verify(mMockCredentialManagerService).clearCredentialState(any(), any(), eq(mPackageName)); @@ -464,27 +498,32 @@ public class CredentialManagerTest { @Test public void testGetCredentialProviderServices_systemProviders() throws RemoteException { - verifyGetCredentialProviderServices(CredentialManager.PROVIDER_FILTER_SYSTEM_PROVIDERS_ONLY); + verifyGetCredentialProviderServices( + CredentialManager.PROVIDER_FILTER_SYSTEM_PROVIDERS_ONLY); } @Test public void testGetCredentialProviderServicesForTesting_allProviders() throws RemoteException { - verifyGetCredentialProviderServicesForTesting(CredentialManager.PROVIDER_FILTER_ALL_PROVIDERS); + verifyGetCredentialProviderServicesForTesting( + CredentialManager.PROVIDER_FILTER_ALL_PROVIDERS); } @Test public void testGetCredentialProviderServicesForTesting_userProviders() throws RemoteException { - verifyGetCredentialProviderServicesForTesting(CredentialManager.PROVIDER_FILTER_USER_PROVIDERS_ONLY); + verifyGetCredentialProviderServicesForTesting( + CredentialManager.PROVIDER_FILTER_USER_PROVIDERS_ONLY); } @Test - public void testGetCredentialProviderServicesForTesting_systemProviders() throws RemoteException { - verifyGetCredentialProviderServicesForTesting(CredentialManager.PROVIDER_FILTER_SYSTEM_PROVIDERS_ONLY); + public void testGetCredentialProviderServicesForTesting_systemProviders() + throws RemoteException { + verifyGetCredentialProviderServicesForTesting( + CredentialManager.PROVIDER_FILTER_SYSTEM_PROVIDERS_ONLY); } private void verifyGetCredentialProviderServices(int testFilter) throws RemoteException { - when(mMockCredentialManagerService.getCredentialProviderServices( - TEST_USER_ID, testFilter)).thenReturn(TEST_CREDENTIAL_PROVIDER_INFO_LIST); + when(mMockCredentialManagerService.getCredentialProviderServices(TEST_USER_ID, testFilter)) + .thenReturn(TEST_CREDENTIAL_PROVIDER_INFO_LIST); List<CredentialProviderInfo> output = mCredentialManager.getCredentialProviderServices(TEST_USER_ID, testFilter); @@ -492,9 +531,10 @@ public class CredentialManagerTest { assertThat(output).containsExactlyElementsIn(TEST_CREDENTIAL_PROVIDER_INFO_LIST); } - private void verifyGetCredentialProviderServicesForTesting(int testFilter) throws RemoteException { - when(mMockCredentialManagerService.getCredentialProviderServicesForTesting( - testFilter)).thenReturn(TEST_CREDENTIAL_PROVIDER_INFO_LIST); + private void verifyGetCredentialProviderServicesForTesting(int testFilter) + throws RemoteException { + when(mMockCredentialManagerService.getCredentialProviderServicesForTesting(testFilter)) + .thenReturn(TEST_CREDENTIAL_PROVIDER_INFO_LIST); List<CredentialProviderInfo> output = mCredentialManager.getCredentialProviderServicesForTesting(testFilter); @@ -504,41 +544,45 @@ public class CredentialManagerTest { @Test public void testSetEnabledProviders_nullProviders() { - assertThrows(NullPointerException.class, - () -> mCredentialManager.setEnabledProviders(null, 0, mExecutor, response -> { - })); - + assertThrows( + NullPointerException.class, + () -> + mCredentialManager.setEnabledProviders( + null, null, 0, mExecutor, response -> {})); } @Test public void testSetEnabledProviders_nullExecutor() { - assertThrows(NullPointerException.class, - () -> mCredentialManager.setEnabledProviders(List.of("foo"), 0, null, response -> { - })); - + assertThrows( + NullPointerException.class, + () -> + mCredentialManager.setEnabledProviders( + List.of("foo"), List.of("foo"), 0, null, response -> {})); } @Test public void testSetEnabledProviders_nullCallback() { - assertThrows(NullPointerException.class, - () -> mCredentialManager.setEnabledProviders(List.of("foo"), 0, mExecutor, null)); - + assertThrows( + NullPointerException.class, + () -> + mCredentialManager.setEnabledProviders( + List.of("foo"), List.of("foo"), 0, mExecutor, null)); } @Test public void testSetEnabledProviders_failed() throws RemoteException { OutcomeReceiver<Void, SetEnabledProvidersException> callback = mock(OutcomeReceiver.class); - ArgumentCaptor<ISetEnabledProvidersCallback> callbackCaptor = ArgumentCaptor.forClass( - ISetEnabledProvidersCallback.class); - ArgumentCaptor<SetEnabledProvidersException> errorCaptor = ArgumentCaptor.forClass( - SetEnabledProvidersException.class); + ArgumentCaptor<ISetEnabledProvidersCallback> callbackCaptor = + ArgumentCaptor.forClass(ISetEnabledProvidersCallback.class); + ArgumentCaptor<SetEnabledProvidersException> errorCaptor = + ArgumentCaptor.forClass(SetEnabledProvidersException.class); final List<String> providers = List.of("foo", "bar"); final int userId = 0; - mCredentialManager.setEnabledProviders(providers, userId, mExecutor, callback); - verify(mMockCredentialManagerService).setEnabledProviders(eq(providers), eq(0), - callbackCaptor.capture()); + mCredentialManager.setEnabledProviders(providers, providers, userId, mExecutor, callback); + verify(mMockCredentialManagerService) + .setEnabledProviders(eq(providers), eq(providers), eq(0), callbackCaptor.capture()); final String errorType = "unknown"; final String errorMessage = "Unknown error"; @@ -553,15 +597,18 @@ public class CredentialManagerTest { public void testSetEnabledProviders_success() throws RemoteException { OutcomeReceiver<Void, SetEnabledProvidersException> callback = mock(OutcomeReceiver.class); - ArgumentCaptor<ISetEnabledProvidersCallback> callbackCaptor = ArgumentCaptor.forClass( - ISetEnabledProvidersCallback.class); + ArgumentCaptor<ISetEnabledProvidersCallback> callbackCaptor = + ArgumentCaptor.forClass(ISetEnabledProvidersCallback.class); final List<String> providers = List.of("foo", "bar"); + final List<String> primaryProviders = List.of("foo"); final int userId = 0; - mCredentialManager.setEnabledProviders(providers, userId, mExecutor, callback); + mCredentialManager.setEnabledProviders( + primaryProviders, providers, userId, mExecutor, callback); - verify(mMockCredentialManagerService).setEnabledProviders(eq(providers), eq(0), - callbackCaptor.capture()); + verify(mMockCredentialManagerService) + .setEnabledProviders( + eq(primaryProviders), eq(providers), eq(0), callbackCaptor.capture()); callbackCaptor.getValue().onResponse(); verify(callback).onResult(any()); @@ -569,27 +616,29 @@ public class CredentialManagerTest { @Test public void testRegisterCredentialDescription_nullRequest() { - assertThrows(NullPointerException.class, + assertThrows( + NullPointerException.class, () -> mCredentialManager.registerCredentialDescription(null)); } @Test public void testRegisterCredentialDescription_success() throws RemoteException { mCredentialManager.registerCredentialDescription(mRegisterRequest); - verify(mMockCredentialManagerService).registerCredentialDescription(same(mRegisterRequest), - eq(mPackageName)); + verify(mMockCredentialManagerService) + .registerCredentialDescription(same(mRegisterRequest), eq(mPackageName)); } @Test public void testUnregisterCredentialDescription_nullRequest() { - assertThrows(NullPointerException.class, + assertThrows( + NullPointerException.class, () -> mCredentialManager.unregisterCredentialDescription(null)); } @Test public void testUnregisterCredentialDescription_success() throws RemoteException { mCredentialManager.unregisterCredentialDescription(mUnregisterRequest); - verify(mMockCredentialManagerService).unregisterCredentialDescription( - same(mUnregisterRequest), eq(mPackageName)); + verify(mMockCredentialManagerService) + .unregisterCredentialDescription(same(mUnregisterRequest), eq(mPackageName)); } } diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java index 6793800364de..08d7d5b872ac 100644 --- a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java +++ b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java @@ -18,7 +18,6 @@ package com.android.server.credentials; import static android.Manifest.permission.CREDENTIAL_MANAGER_SET_ALLOWED_PROVIDERS; import static android.Manifest.permission.CREDENTIAL_MANAGER_SET_ORIGIN; -import static android.Manifest.permission.LAUNCH_CREDENTIAL_SELECTOR; import static android.content.Context.CREDENTIAL_SERVICE; import static android.content.pm.PackageManager.PERMISSION_GRANTED; @@ -46,7 +45,6 @@ import android.credentials.ISetEnabledProvidersCallback; import android.credentials.PrepareGetCredentialResponseInternal; import android.credentials.RegisterCredentialDescriptionRequest; import android.credentials.UnregisterCredentialDescriptionRequest; -import android.credentials.ui.IntentFactory; import android.os.Binder; import android.os.CancellationSignal; import android.os.IBinder; @@ -69,6 +67,7 @@ import com.android.server.infra.AbstractMasterSystemService; import com.android.server.infra.SecureSettingsServiceNameResolver; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashSet; @@ -282,6 +281,18 @@ public final class CredentialManagerService } } + private Set<String> getPrimaryProvidersForUserId(int userId) { + final int resolvedUserId = ActivityManager.handleIncomingUser( + Binder.getCallingPid(), Binder.getCallingUid(), + userId, false, false, + "getPrimaryProvidersForUserId", null); + SecureSettingsServiceNameResolver resolver = new SecureSettingsServiceNameResolver( + mContext, Settings.Secure.CREDENTIAL_SERVICE_PRIMARY, + /* isMultipleMode= */ true); + String[] serviceNames = resolver.readServiceNameList(resolvedUserId); + return new HashSet<String>(Arrays.asList(serviceNames)); + } + @GuardedBy("mLock") private List<CredentialManagerServiceImpl> getCredentialProviderServicesLocked(int userId) { List<CredentialManagerServiceImpl> concatenatedServices = new ArrayList<>(); @@ -689,7 +700,8 @@ public final class CredentialManagerService @Override public void setEnabledProviders( - List<String> providers, int userId, ISetEnabledProvidersCallback callback) { + List<String> primaryProviders, List<String> providers, int userId, + ISetEnabledProvidersCallback callback) { if (!hasWriteSecureSettingsPermission()) { try { callback.onError( @@ -710,17 +722,24 @@ public final class CredentialManagerService "setEnabledProviders", null); - String storedValue = String.join(":", providers); - if (!Settings.Secure.putStringForUser( - getContext().getContentResolver(), - Settings.Secure.CREDENTIAL_SERVICE, - storedValue, - userId)) { - Slog.e(TAG, "Failed to store setting containing enabled providers"); + boolean writeEnabledStatus = + Settings.Secure.putStringForUser(getContext().getContentResolver(), + Settings.Secure.CREDENTIAL_SERVICE, + String.join(":", providers), + userId); + + boolean writePrimaryStatus = + Settings.Secure.putStringForUser(getContext().getContentResolver(), + Settings.Secure.CREDENTIAL_SERVICE_PRIMARY, + String.join(":", primaryProviders), + userId); + + if (!writeEnabledStatus || !writePrimaryStatus) { + Slog.e(TAG, "Failed to store setting containing enabled or primary providers"); try { callback.onError( "failed_setting_store", - "Failed to store setting containing enabled providers"); + "Failed to store setting containing enabled or primary providers"); } catch (RemoteException e) { Slog.e(TAG, "Issue with invoking error response: ", e); return; @@ -734,10 +753,6 @@ public final class CredentialManagerService Slog.e(TAG, "Issue with invoking response: ", e); // TODO: Propagate failure } - - // Send an intent to the UI that we have new enabled providers. - getContext().sendBroadcast(IntentFactory.createProviderUpdateIntent(), - LAUNCH_CREDENTIAL_SELECTOR); } @Override @@ -785,7 +800,8 @@ public final class CredentialManagerService verifyGetProvidersPermission(); return CredentialProviderInfoFactory.getCredentialProviderServices( - mContext, userId, providerFilter, getEnabledProviders()); + mContext, userId, providerFilter, getEnabledProviders(), + getPrimaryProvidersForUserId(userId)); } @Override @@ -795,7 +811,8 @@ public final class CredentialManagerService final int userId = UserHandle.getCallingUserId(); return CredentialProviderInfoFactory.getCredentialProviderServicesForTesting( - mContext, userId, providerFilter, getEnabledProviders()); + mContext, userId, providerFilter, getEnabledProviders(), + getPrimaryProvidersForUserId(userId)); } @Override diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java b/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java index c2b51029778e..b3812c9138de 100644 --- a/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java +++ b/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java @@ -36,6 +36,7 @@ import android.service.credentials.CredentialProviderInfoFactory; import android.util.Slog; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.UUID; @@ -154,7 +155,9 @@ public class CredentialManagerUi { mContext, mUserId, CredentialManager.PROVIDER_FILTER_USER_PROVIDERS_ONLY, - mEnabledProviders); + mEnabledProviders, + // Don't need primary providers here. + new HashSet<String>()); List<DisabledProviderData> disabledProviderDataList = allProviders.stream() .filter(provider -> !provider.isEnabled()) |