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
This commit is contained in:
Becca Hughes
2022-12-01 17:14:45 +00:00
parent c5deccb2c8
commit 40b6bceb91
8 changed files with 380 additions and 52 deletions

View File

@@ -196,6 +196,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.

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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);
}
}

View File

@@ -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;

View File

@@ -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;
}
}

View File

@@ -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 -->
<!-- ========================================= -->

View File

@@ -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;
}