diff options
| author | 2022-12-01 17:14:45 +0000 | |
|---|---|---|
| committer | 2022-12-12 23:23:31 +0000 | |
| commit | 40b6bceb91134bd914d95d0fc42d805dfe151a89 (patch) | |
| tree | 1c1cde02b9c4d4628927f5f204388000a02a76f8 | |
| parent | c5deccb2c8571e619e94c7e9a9e497902fca2659 (diff) | |
Add ListEnabledProviders API method for Credential Manager
Adds ListEnabledProviders API which is a SystemApi used
by Settings and Chrome to list the enabled and user
visible installed providers.
Test: make
Bug: 253157179
CTS-Coverage-Bug: 247549381
Change-Id: I25ef3a294f051855c0a4ae104d1111f1e8b8529d
8 files changed, 380 insertions, 52 deletions
diff --git a/core/java/android/credentials/CredentialManager.java b/core/java/android/credentials/CredentialManager.java index a578956fedd5..49e849523de2 100644 --- a/core/java/android/credentials/CredentialManager.java +++ b/core/java/android/credentials/CredentialManager.java @@ -197,6 +197,48 @@ public final class CredentialManager { } /** + * Gets a list of all user configurable credential providers registered on the system. This API + * is intended for browsers and settings apps. + * + * @param cancellationSignal an optional signal that allows for cancelling this call + * @param executor the callback will take place on this {@link Executor} + * @param callback the callback invoked when the request succeeds or fails + * @hide + */ + @RequiresPermission( + allOf = { + android.Manifest.permission.LIST_ENABLED_CREDENTIAL_PROVIDERS, + android.Manifest.permission.QUERY_ALL_PACKAGES + }) + public void listEnabledProviders( + @Nullable CancellationSignal cancellationSignal, + @CallbackExecutor @NonNull Executor executor, + @NonNull + OutcomeReceiver<ListEnabledProvidersResponse, ListEnabledProvidersException> + callback) { + requireNonNull(executor, "executor must not be null"); + requireNonNull(callback, "callback must not be null"); + + if (cancellationSignal != null && cancellationSignal.isCanceled()) { + Log.w(TAG, "listEnabledProviders already canceled"); + return; + } + + ICancellationSignal cancelRemote = null; + try { + cancelRemote = + mService.listEnabledProviders( + new ListEnabledProvidersTransport(executor, callback)); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + + if (cancellationSignal != null && cancelRemote != null) { + cancellationSignal.setRemote(cancelRemote); + } + } + + /** * Sets a list of all user configurable credential providers registered on the system. This API * is intended for settings apps. * @@ -331,6 +373,33 @@ public final class CredentialManager { } } + private static class ListEnabledProvidersTransport extends IListEnabledProvidersCallback.Stub { + // TODO: listen for cancellation to release callback. + + private final Executor mExecutor; + private final OutcomeReceiver<ListEnabledProvidersResponse, ListEnabledProvidersException> + mCallback; + + private ListEnabledProvidersTransport( + Executor executor, + OutcomeReceiver<ListEnabledProvidersResponse, ListEnabledProvidersException> + callback) { + mExecutor = executor; + mCallback = callback; + } + + @Override + public void onResponse(ListEnabledProvidersResponse response) { + mExecutor.execute(() -> mCallback.onResult(response)); + } + + @Override + public void onError(String errorType, String message) { + mExecutor.execute( + () -> mCallback.onError(new ListEnabledProvidersException(errorType, message))); + } + } + private static class SetEnabledProvidersTransport extends ISetEnabledProvidersCallback.Stub { // TODO: listen for cancellation to release callback. diff --git a/core/java/android/credentials/ICredentialManager.aidl b/core/java/android/credentials/ICredentialManager.aidl index d8c4d894a439..c3ca03dcdfd2 100644 --- a/core/java/android/credentials/ICredentialManager.aidl +++ b/core/java/android/credentials/ICredentialManager.aidl @@ -24,6 +24,7 @@ import android.credentials.GetCredentialRequest; import android.credentials.IClearCredentialStateCallback; import android.credentials.ICreateCredentialCallback; import android.credentials.IGetCredentialCallback; +import android.credentials.IListEnabledProvidersCallback; import android.credentials.ISetEnabledProvidersCallback; import android.os.ICancellationSignal; @@ -40,5 +41,7 @@ interface ICredentialManager { @nullable ICancellationSignal clearCredentialState(in ClearCredentialStateRequest request, in IClearCredentialStateCallback callback, String callingPackage); + @nullable ICancellationSignal listEnabledProviders(in IListEnabledProvidersCallback callback); + void setEnabledProviders(in List<String> providers, in int userId, in ISetEnabledProvidersCallback callback); } diff --git a/core/java/android/credentials/IListEnabledProvidersCallback.aidl b/core/java/android/credentials/IListEnabledProvidersCallback.aidl new file mode 100644 index 000000000000..3a8e25ed954a --- /dev/null +++ b/core/java/android/credentials/IListEnabledProvidersCallback.aidl @@ -0,0 +1,29 @@ +/* + * Copyright 2022 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 android.credentials; + +import android.credentials.ListEnabledProvidersResponse; + +/** + * Listener for an listEnabledProviders request. + * + * @hide + */ +interface IListEnabledProvidersCallback { + oneway void onResponse(in ListEnabledProvidersResponse response); + oneway void onError(String errorType, String message); +}
\ No newline at end of file diff --git a/core/java/android/credentials/ListEnabledProvidersException.java b/core/java/android/credentials/ListEnabledProvidersException.java new file mode 100644 index 000000000000..c12c65672664 --- /dev/null +++ b/core/java/android/credentials/ListEnabledProvidersException.java @@ -0,0 +1,74 @@ +/* + * Copyright 2022 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 android.credentials; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.CancellationSignal; +import android.os.OutcomeReceiver; + +import com.android.internal.util.Preconditions; + +/** + * Represents an error encountered during the {@link + * CredentialManager#listEnabledProviders(CancellationSignal Executor, OutcomeReceiver)} operation. + * + * @hide + */ +public class ListEnabledProvidersException extends Exception { + + @NonNull public final String errorType; + + /** + * Constructs a {@link ListEnabledProvidersException}. + * + * @throws IllegalArgumentException If errorType is empty. + */ + public ListEnabledProvidersException(@NonNull String errorType, @Nullable String message) { + this(errorType, message, null); + } + + /** + * Constructs a {@link ListEnabledProvidersException}. + * + * @throws IllegalArgumentException If errorType is empty. + */ + public ListEnabledProvidersException( + @NonNull String errorType, @Nullable String message, @Nullable Throwable cause) { + super(message, cause); + this.errorType = + Preconditions.checkStringNotEmpty(errorType, "errorType must not be empty"); + } + + /** + * Constructs a {@link ListEnabledProvidersException}. + * + * @throws IllegalArgumentException If errorType is empty. + */ + public ListEnabledProvidersException(@NonNull String errorType, @Nullable Throwable cause) { + this(errorType, null, cause); + } + + /** + * Constructs a {@link ListEnabledProvidersException}. + * + * @throws IllegalArgumentException If errorType is empty. + */ + public ListEnabledProvidersException(@NonNull String errorType) { + this(errorType, null, null); + } +} diff --git a/core/java/android/credentials/ListEnabledProvidersResponse.aidl b/core/java/android/credentials/ListEnabledProvidersResponse.aidl new file mode 100644 index 000000000000..759bf48bf57a --- /dev/null +++ b/core/java/android/credentials/ListEnabledProvidersResponse.aidl @@ -0,0 +1,19 @@ +/* + * Copyright 2022 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 android.credentials; + +parcelable ListEnabledProvidersResponse;
\ No newline at end of file diff --git a/core/java/android/credentials/ListEnabledProvidersResponse.java b/core/java/android/credentials/ListEnabledProvidersResponse.java new file mode 100644 index 000000000000..532adf7f0c50 --- /dev/null +++ b/core/java/android/credentials/ListEnabledProvidersResponse.java @@ -0,0 +1,85 @@ +/* + * Copyright 2022 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 android.credentials; + +import android.annotation.NonNull; +import android.os.Parcel; +import android.os.Parcelable; + +import com.android.internal.util.Preconditions; + +import java.util.List; +import java.util.Objects; + +/** + * Response from Credential Manager listing the providers that are enabled and available to the + * user. + * + * @hide + */ +public final class ListEnabledProvidersResponse implements Parcelable { + + /** List of providers. */ + @NonNull private final List<String> mProviders; + + /** + * Creates a {@link ListEnabledProvidersResponse} with a list of providers. + * + * @throws NullPointerException If args are null. + */ + public static @NonNull ListEnabledProvidersResponse create(@NonNull List<String> providers) { + Objects.requireNonNull(providers, "providers must not be null"); + Preconditions.checkCollectionElementsNotNull(providers, /* valueName= */ "providers"); + return new ListEnabledProvidersResponse(providers); + } + + private ListEnabledProvidersResponse(@NonNull List<String> providers) { + mProviders = providers; + } + + private ListEnabledProvidersResponse(@NonNull Parcel in) { + mProviders = in.createStringArrayList(); + } + + public static final @NonNull Creator<ListEnabledProvidersResponse> CREATOR = + new Creator<ListEnabledProvidersResponse>() { + @Override + public ListEnabledProvidersResponse createFromParcel(Parcel in) { + return new ListEnabledProvidersResponse(in); + } + + @Override + public ListEnabledProvidersResponse[] newArray(int size) { + return new ListEnabledProvidersResponse[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeStringList(mProviders); + } + + /** Returns the list of flattened Credential Manager provider component names as strings. */ + public @NonNull List<String> getProviderComponentNames() { + return mProviders; + } +} diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 09181cebd3f2..585368678357 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -3863,6 +3863,11 @@ <permission android:name="android.permission.REQUEST_UNIQUE_ID_ATTESTATION" android:protectionLevel="signature" /> + <!-- Allows an application to get enabled credential manager providers. + @hide --> + <permission android:name="android.permission.LIST_ENABLED_CREDENTIAL_PROVIDERS" + android:protectionLevel="signature|privileged" /> + <!-- ========================================= --> <!-- Permissions for special development tools --> <!-- ========================================= --> diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java index af4e7d72e275..a92e4a18d609 100644 --- a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java +++ b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java @@ -31,6 +31,8 @@ import android.credentials.IClearCredentialStateCallback; import android.credentials.ICreateCredentialCallback; import android.credentials.ICredentialManager; import android.credentials.IGetCredentialCallback; +import android.credentials.IListEnabledProvidersCallback; +import android.credentials.ListEnabledProvidersResponse; import android.credentials.ISetEnabledProvidersCallback; import android.os.Binder; import android.os.CancellationSignal; @@ -55,20 +57,23 @@ import java.util.stream.Collectors; /** * Entry point service for credential management. * - * <p>This service provides the {@link ICredentialManager} implementation and keeps a list of - * {@link CredentialManagerServiceImpl} per user; the real work is done by - * {@link CredentialManagerServiceImpl} itself. + * <p>This service provides the {@link ICredentialManager} implementation and keeps a list of {@link + * CredentialManagerServiceImpl} per user; the real work is done by {@link + * CredentialManagerServiceImpl} itself. */ -public final class CredentialManagerService extends - AbstractMasterSystemService<CredentialManagerService, CredentialManagerServiceImpl> { +public final class CredentialManagerService + extends AbstractMasterSystemService< + CredentialManagerService, CredentialManagerServiceImpl> { private static final String TAG = "CredManSysService"; public CredentialManagerService(@NonNull Context context) { - super(context, - new SecureSettingsServiceNameResolver(context, Settings.Secure.CREDENTIAL_SERVICE, - /*isMultipleMode=*/true), - null, PACKAGE_UPDATE_POLICY_REFRESH_EAGER); + super( + context, + new SecureSettingsServiceNameResolver( + context, Settings.Secure.CREDENTIAL_SERVICE, /* isMultipleMode= */ true), + null, + PACKAGE_UPDATE_POLICY_REFRESH_EAGER); } @Override @@ -77,12 +82,14 @@ public final class CredentialManagerService extends } @Override // from AbstractMasterSystemService - protected CredentialManagerServiceImpl newServiceLocked(@UserIdInt int resolvedUserId, - boolean disabled) { + protected CredentialManagerServiceImpl newServiceLocked( + @UserIdInt int resolvedUserId, boolean disabled) { // This method should not be called for CredentialManagerService as it is configured to use // multiple services. - Slog.w(TAG, "Should not be here - CredentialManagerService is configured to use " - + "multiple services"); + Slog.w( + TAG, + "Should not be here - CredentialManagerService is configured to use " + + "multiple services"); return null; } @@ -92,8 +99,8 @@ public final class CredentialManagerService extends } @Override // from AbstractMasterSystemService - protected List<CredentialManagerServiceImpl> newServiceListLocked(int resolvedUserId, - boolean disabled, String[] serviceNames) { + protected List<CredentialManagerServiceImpl> newServiceListLocked( + int resolvedUserId, boolean disabled, String[] serviceNames) { if (serviceNames == null || serviceNames.length == 0) { Slog.i(TAG, "serviceNames sent in newServiceListLocked is null, or empty"); return new ArrayList<>(); @@ -105,8 +112,8 @@ public final class CredentialManagerService extends continue; } try { - serviceList.add(new CredentialManagerServiceImpl(this, mLock, resolvedUserId, - serviceName)); + serviceList.add( + new CredentialManagerServiceImpl(this, mLock, resolvedUserId, serviceName)); } catch (PackageManager.NameNotFoundException | SecurityException e) { Log.i(TAG, "Unable to add serviceInfo : " + e.getMessage()); } @@ -130,19 +137,20 @@ public final class CredentialManagerService extends } } - private List<ProviderSession> initiateProviderSessions(RequestSession session, - List<String> requestOptions) { + private List<ProviderSession> initiateProviderSessions( + RequestSession session, List<String> requestOptions) { List<ProviderSession> providerSessions = new ArrayList<>(); // Invoke all services of a user to initiate a provider session - runForUser((service) -> { - if (service.isServiceCapable(requestOptions)) { - ProviderSession providerSession = service - .initiateProviderSessionForRequest(session); - if (providerSession != null) { - providerSessions.add(providerSession); - } - } - }); + runForUser( + (service) -> { + if (service.isServiceCapable(requestOptions)) { + ProviderSession providerSession = + service.initiateProviderSessionForRequest(session); + if (providerSession != null) { + providerSessions.add(providerSession); + } + } + }); return providerSessions; } @@ -157,25 +165,33 @@ public final class CredentialManagerService extends ICancellationSignal cancelTransport = CancellationSignal.createTransport(); // New request session, scoped for this request only. - final GetRequestSession session = new GetRequestSession(getContext(), - UserHandle.getCallingUserId(), - callback, - request, - callingPackage); + final GetRequestSession session = + new GetRequestSession( + getContext(), + UserHandle.getCallingUserId(), + callback, + request, + callingPackage); // Initiate all provider sessions List<ProviderSession> providerSessions = - initiateProviderSessions(session, request.getGetCredentialOptions() - .stream().map(GetCredentialOption::getType) - .collect(Collectors.toList())); + initiateProviderSessions( + session, + request.getGetCredentialOptions().stream() + .map(GetCredentialOption::getType) + .collect(Collectors.toList())); // TODO : Return error when no providers available // Iterate over all provider sessions and invoke the request - providerSessions.forEach(providerGetSession -> { - providerGetSession.getRemoteCredentialService().onBeginGetCredentials( - (BeginGetCredentialsRequest) providerGetSession.getProviderRequest(), - /*callback=*/providerGetSession); - }); + providerSessions.forEach( + providerGetSession -> { + providerGetSession + .getRemoteCredentialService() + .onBeginGetCredentials( + (BeginGetCredentialsRequest) + providerGetSession.getProviderRequest(), + /* callback= */ providerGetSession); + }); return cancelTransport; } @@ -189,11 +205,13 @@ public final class CredentialManagerService extends ICancellationSignal cancelTransport = CancellationSignal.createTransport(); // New request session, scoped for this request only. - final CreateRequestSession session = new CreateRequestSession(getContext(), - UserHandle.getCallingUserId(), - request, - callback, - callingPackage); + final CreateRequestSession session = + new CreateRequestSession( + getContext(), + UserHandle.getCallingUserId(), + request, + callback, + callingPackage); // Initiate all provider sessions List<ProviderSession> providerSessions = @@ -201,12 +219,38 @@ public final class CredentialManagerService extends // TODO : Return error when no providers available // Iterate over all provider sessions and invoke the request - providerSessions.forEach(providerCreateSession -> { - providerCreateSession.getRemoteCredentialService().onCreateCredential( - (BeginCreateCredentialRequest) - providerCreateSession.getProviderRequest(), - /*callback=*/providerCreateSession); - }); + providerSessions.forEach( + providerCreateSession -> { + providerCreateSession + .getRemoteCredentialService() + .onCreateCredential( + (BeginCreateCredentialRequest) + providerCreateSession.getProviderRequest(), + /* callback= */ providerCreateSession); + }); + return cancelTransport; + } + + @Override + public ICancellationSignal listEnabledProviders(IListEnabledProvidersCallback callback) { + Log.i(TAG, "listEnabledProviders"); + ICancellationSignal cancelTransport = CancellationSignal.createTransport(); + + List<String> enabledProviders = new ArrayList<>(); + runForUser( + (service) -> { + enabledProviders.add( + service.getServiceInfo().getComponentName().flattenToString()); + }); + + // Call the callback. + try { + callback.onResponse(ListEnabledProvidersResponse.create(enabledProviders)); + } catch (RemoteException e) { + Log.i(TAG, "Issue with invoking response: " + e.getMessage()); + // TODO: Propagate failure + } + return cancelTransport; } |