Add CredentialDescription registry API.
Bug: 260629338 CTS-Coverage-Bug: 265212839 API-Coverage-Bug: 265212839 Test: Local Build & Deployment Change-Id: I6c30468eacb48425f6670b4d60bdc71d57a7e648
This commit is contained in:
22
core/java/android/credentials/CredentialDescription.aidl
Normal file
22
core/java/android/credentials/CredentialDescription.aidl
Normal file
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* Copyright 2023 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package android.credentials;
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
parcelable CredentialDescription;
|
||||
139
core/java/android/credentials/CredentialDescription.java
Normal file
139
core/java/android/credentials/CredentialDescription.java
Normal file
@@ -0,0 +1,139 @@
|
||||
/*
|
||||
* Copyright (C) 2023 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package android.credentials;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
import android.service.credentials.CredentialEntry;
|
||||
|
||||
import com.android.internal.util.AnnotationValidations;
|
||||
import com.android.internal.util.Preconditions;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Represents the type and contained data fields of a {@link Credential}.
|
||||
* @hide
|
||||
*/
|
||||
public final class CredentialDescription implements Parcelable {
|
||||
|
||||
/**
|
||||
* The credential type.
|
||||
*/
|
||||
@NonNull
|
||||
private final String mType;
|
||||
|
||||
/**
|
||||
* The flattened JSON string that will be matched with requests.
|
||||
*/
|
||||
@NonNull
|
||||
private final String mFlattenedRequestString;
|
||||
|
||||
/**
|
||||
* The entry to be used in the UI.
|
||||
*/
|
||||
@NonNull
|
||||
private final List<CredentialEntry> mCredentialEntries;
|
||||
|
||||
/**
|
||||
* Constructs a {@link CredentialDescription}.
|
||||
*
|
||||
* @param type the type of the credential returned.
|
||||
* @param flattenedRequestString flattened JSON string that will be matched with requests.
|
||||
* @param credentialEntries a list of {@link CredentialEntry}s that have been returned
|
||||
* to the developer upon credential creation.
|
||||
*
|
||||
* @throws IllegalArgumentException If type is empty.
|
||||
*/
|
||||
public CredentialDescription(@NonNull String type,
|
||||
@NonNull String flattenedRequestString,
|
||||
@NonNull List<CredentialEntry> credentialEntries) {
|
||||
mType = Preconditions.checkStringNotEmpty(type, "type must not be empty");
|
||||
mFlattenedRequestString = Preconditions.checkStringNotEmpty(flattenedRequestString);
|
||||
mCredentialEntries = Objects.requireNonNull(credentialEntries);
|
||||
}
|
||||
|
||||
private CredentialDescription(@NonNull Parcel in) {
|
||||
String type = in.readString8();
|
||||
String flattenedRequestString = in.readString();
|
||||
List<CredentialEntry> entries = new ArrayList<>();
|
||||
in.readTypedList(entries, CredentialEntry.CREATOR);
|
||||
|
||||
mType = type;
|
||||
AnnotationValidations.validate(android.annotation.NonNull.class, null, mType);
|
||||
mFlattenedRequestString = flattenedRequestString;
|
||||
AnnotationValidations.validate(android.annotation.NonNull.class, null,
|
||||
mFlattenedRequestString);
|
||||
mCredentialEntries = entries;
|
||||
AnnotationValidations.validate(android.annotation.NonNull.class, null,
|
||||
mCredentialEntries);
|
||||
}
|
||||
|
||||
public static final @NonNull Parcelable.Creator<CredentialDescription> CREATOR =
|
||||
new Parcelable.Creator<CredentialDescription>() {
|
||||
@Override
|
||||
public CredentialDescription createFromParcel(Parcel in) {
|
||||
return new CredentialDescription(in);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CredentialDescription[] newArray(int size) {
|
||||
return new CredentialDescription[size];
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(@NonNull Parcel dest, int flags) {
|
||||
dest.writeString(mType);
|
||||
dest.writeString(mFlattenedRequestString);
|
||||
dest.writeTypedList(mCredentialEntries, flags);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public String getType() {
|
||||
return mType;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public String getFlattenedRequestString() {
|
||||
return mFlattenedRequestString;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public List<CredentialEntry> getCredentialEntries() {
|
||||
return mCredentialEntries;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(mType, mFlattenedRequestString);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
return Objects.equals(mType, ((CredentialDescription) obj).getType())
|
||||
&& Objects.equals(mFlattenedRequestString, ((CredentialDescription) obj).getType());
|
||||
}
|
||||
}
|
||||
@@ -62,6 +62,14 @@ public final class CredentialManager {
|
||||
public static final String DEVICE_CONFIG_ENABLE_CREDENTIAL_MANAGER =
|
||||
"enable_credential_manager";
|
||||
|
||||
/**
|
||||
* Flag to enable and disable Credential Description api.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
private static final String DEVICE_CONFIG_ENABLE_CREDENTIAL_DESC_API =
|
||||
"enable_credential_description_api";
|
||||
|
||||
/**
|
||||
* @hide instantiated by ContextImpl.
|
||||
*/
|
||||
@@ -294,6 +302,112 @@ public final class CredentialManager {
|
||||
true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the credential description api is enabled.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public static boolean isCredentialDescriptionApiEnabled() {
|
||||
return DeviceConfig.getBoolean(
|
||||
DeviceConfig.NAMESPACE_CREDENTIAL, DEVICE_CONFIG_ENABLE_CREDENTIAL_DESC_API, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a {@link CredentialDescription} for an actively provisioned {@link Credential}
|
||||
* a CredentialProvider has. This registry will then be used by
|
||||
* {@link #executeGetCredential(GetCredentialRequest, Activity,
|
||||
* CancellationSignal, Executor, OutcomeReceiver)} to determine where to
|
||||
* fetch the requested {@link Credential} from.
|
||||
*
|
||||
*
|
||||
* @param request the request data
|
||||
* @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
|
||||
*
|
||||
* @throws {@link UnsupportedOperationException} if the feature has not been enabled.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public void registerCredentialDescription(
|
||||
@NonNull RegisterCredentialDescriptionRequest request,
|
||||
@Nullable CancellationSignal cancellationSignal,
|
||||
@CallbackExecutor @NonNull Executor executor,
|
||||
@NonNull OutcomeReceiver<Void, RegisterCredentialDescriptionException> callback) {
|
||||
|
||||
if (!isCredentialDescriptionApiEnabled()) {
|
||||
throw new UnsupportedOperationException("This API is not currently supported.");
|
||||
}
|
||||
|
||||
requireNonNull(executor, "executor must not be null");
|
||||
requireNonNull(callback, "callback must not be null");
|
||||
|
||||
if (cancellationSignal != null && cancellationSignal.isCanceled()) {
|
||||
Log.w(TAG, "executeCreateCredential already canceled");
|
||||
return;
|
||||
}
|
||||
|
||||
ICancellationSignal cancelRemote = null;
|
||||
try {
|
||||
cancelRemote = mService.registerCredentialDescription(request,
|
||||
new RegisterCredentialDescriptionTransport(executor, callback),
|
||||
mContext.getOpPackageName());
|
||||
} catch (RemoteException e) {
|
||||
e.rethrowFromSystemServer();
|
||||
}
|
||||
|
||||
if (cancellationSignal != null && cancelRemote != null) {
|
||||
cancellationSignal.setRemote(cancelRemote);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Unregisters a {@link CredentialDescription} for an actively provisioned {@link Credential}
|
||||
* that has been registered previously.
|
||||
*
|
||||
*
|
||||
* @param request the request data
|
||||
* @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
|
||||
*
|
||||
* @throws {@link UnsupportedOperationException} if the feature has not been enabled.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public void unRegisterCredentialDescription(
|
||||
@NonNull UnregisterCredentialDescriptionRequest request,
|
||||
@Nullable CancellationSignal cancellationSignal,
|
||||
@CallbackExecutor @NonNull Executor executor,
|
||||
@NonNull OutcomeReceiver<Void, UnregisterCredentialDescriptionException> callback) {
|
||||
|
||||
if (!isCredentialDescriptionApiEnabled()) {
|
||||
throw new UnsupportedOperationException("This API is not currently supported.");
|
||||
}
|
||||
|
||||
requireNonNull(executor, "executor must not be null");
|
||||
requireNonNull(callback, "callback must not be null");
|
||||
|
||||
if (cancellationSignal != null && cancellationSignal.isCanceled()) {
|
||||
Log.w(TAG, "executeCreateCredential already canceled");
|
||||
return;
|
||||
}
|
||||
|
||||
ICancellationSignal cancelRemote = null;
|
||||
try {
|
||||
cancelRemote = mService.unRegisterCredentialDescription(request,
|
||||
new UnregisterCredentialDescriptionTransport(executor, callback),
|
||||
mContext.getOpPackageName());
|
||||
} catch (RemoteException e) {
|
||||
e.rethrowFromSystemServer();
|
||||
}
|
||||
|
||||
if (cancellationSignal != null && cancelRemote != null) {
|
||||
cancellationSignal.setRemote(cancelRemote);
|
||||
}
|
||||
}
|
||||
|
||||
private static class GetCredentialTransport extends IGetCredentialCallback.Stub {
|
||||
// TODO: listen for cancellation to release callback.
|
||||
|
||||
@@ -455,4 +569,54 @@ public final class CredentialManager {
|
||||
() -> mCallback.onError(new SetEnabledProvidersException(errorType, message)));
|
||||
}
|
||||
}
|
||||
|
||||
private static class RegisterCredentialDescriptionTransport
|
||||
extends IRegisterCredentialDescriptionCallback.Stub {
|
||||
|
||||
private final Executor mExecutor;
|
||||
private final OutcomeReceiver<Void, RegisterCredentialDescriptionException> mCallback;
|
||||
|
||||
private RegisterCredentialDescriptionTransport(Executor executor,
|
||||
OutcomeReceiver<Void, RegisterCredentialDescriptionException> callback) {
|
||||
mExecutor = executor;
|
||||
mCallback = callback;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResponse() {
|
||||
mCallback.onResult(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(String errorCode, String message) {
|
||||
mExecutor.execute(
|
||||
() -> mCallback.onError(new RegisterCredentialDescriptionException(errorCode,
|
||||
message)));
|
||||
}
|
||||
}
|
||||
|
||||
private static class UnregisterCredentialDescriptionTransport
|
||||
extends IUnregisterCredentialDescriptionCallback.Stub {
|
||||
|
||||
private final Executor mExecutor;
|
||||
private final OutcomeReceiver<Void, UnregisterCredentialDescriptionException> mCallback;
|
||||
|
||||
private UnregisterCredentialDescriptionTransport(Executor executor,
|
||||
OutcomeReceiver<Void, UnregisterCredentialDescriptionException> callback) {
|
||||
mExecutor = executor;
|
||||
mCallback = callback;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResponse() {
|
||||
mCallback.onResult(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(String errorCode, String message) {
|
||||
mExecutor.execute(
|
||||
() -> mCallback.onError(new UnregisterCredentialDescriptionException(errorCode,
|
||||
message)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,10 +21,14 @@ import java.util.List;
|
||||
import android.credentials.ClearCredentialStateRequest;
|
||||
import android.credentials.CreateCredentialRequest;
|
||||
import android.credentials.GetCredentialRequest;
|
||||
import android.credentials.RegisterCredentialDescriptionRequest;
|
||||
import android.credentials.UnregisterCredentialDescriptionRequest;
|
||||
import android.credentials.IClearCredentialStateCallback;
|
||||
import android.credentials.ICreateCredentialCallback;
|
||||
import android.credentials.IGetCredentialCallback;
|
||||
import android.credentials.IListEnabledProvidersCallback;
|
||||
import android.credentials.IRegisterCredentialDescriptionCallback;
|
||||
import android.credentials.IUnregisterCredentialDescriptionCallback;
|
||||
import android.credentials.ISetEnabledProvidersCallback;
|
||||
import android.os.ICancellationSignal;
|
||||
|
||||
@@ -44,4 +48,9 @@ interface ICredentialManager {
|
||||
@nullable ICancellationSignal listEnabledProviders(in IListEnabledProvidersCallback callback);
|
||||
|
||||
void setEnabledProviders(in List<String> providers, in int userId, in ISetEnabledProvidersCallback callback);
|
||||
|
||||
@nullable ICancellationSignal registerCredentialDescription(in RegisterCredentialDescriptionRequest request, in IRegisterCredentialDescriptionCallback callback, String callingPackage);
|
||||
|
||||
@nullable ICancellationSignal unRegisterCredentialDescription(in UnregisterCredentialDescriptionRequest request, in IUnregisterCredentialDescriptionCallback callback, String callingPackage);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright 2023 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package android.credentials;
|
||||
|
||||
/**
|
||||
* Listener for an registerCredentialDescription request.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
interface IRegisterCredentialDescriptionCallback {
|
||||
oneway void onResponse();
|
||||
oneway void onError(String errorCode, String message);
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright 2023 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package android.credentials;
|
||||
|
||||
/**
|
||||
* Listener for an registerCredentialDescription request.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
interface IUnregisterCredentialDescriptionCallback {
|
||||
oneway void onResponse();
|
||||
oneway void onError(String errorCode, String message);
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
* Copyright 2023 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package android.credentials;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.os.CancellationSignal;
|
||||
import android.os.OutcomeReceiver;
|
||||
|
||||
import com.android.internal.util.Preconditions;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
/**
|
||||
* Represents an error encountered during the {@link
|
||||
* CredentialManager#registerCredentialDescription(RegisterCredentialDescriptionRequest,
|
||||
* CancellationSignal, Executor, OutcomeReceiver)} operation.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public class RegisterCredentialDescriptionException extends Exception {
|
||||
|
||||
@NonNull public final String errorType;
|
||||
|
||||
/**
|
||||
* Constructs a {@link RegisterCredentialDescriptionException}.
|
||||
*
|
||||
* @throws IllegalArgumentException If errorType is empty.
|
||||
*/
|
||||
public RegisterCredentialDescriptionException(@NonNull String errorType,
|
||||
@Nullable String message) {
|
||||
this(errorType, message, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a {@link RegisterCredentialDescriptionException}.
|
||||
*
|
||||
* @throws IllegalArgumentException If errorType is empty.
|
||||
*/
|
||||
public RegisterCredentialDescriptionException(
|
||||
@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 RegisterCredentialDescriptionException}.
|
||||
*
|
||||
* @throws IllegalArgumentException If errorType is empty.
|
||||
*/
|
||||
public RegisterCredentialDescriptionException(@NonNull String errorType,
|
||||
@Nullable Throwable cause) {
|
||||
this(errorType, null, cause);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a {@link RegisterCredentialDescriptionException}.
|
||||
*
|
||||
* @throws IllegalArgumentException If errorType is empty.
|
||||
*/
|
||||
public RegisterCredentialDescriptionException(@NonNull String errorType) {
|
||||
this(errorType, null, null);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
* Copyright 2023 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package android.credentials;
|
||||
|
||||
parcelable RegisterCredentialDescriptionRequest;
|
||||
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
* Copyright (C) 2023 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package android.credentials;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.content.ComponentName;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import com.android.internal.util.AnnotationValidations;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
|
||||
/**
|
||||
* A request to register a {@link ComponentName} that contains an actively provisioned
|
||||
* {@link Credential} represented by a {@link CredentialDescription}.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public final class RegisterCredentialDescriptionRequest implements Parcelable {
|
||||
|
||||
public static final String FLATTENED_REQUEST_STRING_KEY = "flattened_request_string";
|
||||
|
||||
@NonNull
|
||||
private final List<CredentialDescription> mCredentialDescriptions;
|
||||
|
||||
public RegisterCredentialDescriptionRequest(
|
||||
@NonNull CredentialDescription credentialDescription) {
|
||||
mCredentialDescriptions = Arrays.asList(requireNonNull(credentialDescription));
|
||||
}
|
||||
|
||||
public RegisterCredentialDescriptionRequest(
|
||||
@NonNull List<CredentialDescription> credentialDescriptions) {
|
||||
mCredentialDescriptions = new ArrayList<>(requireNonNull(credentialDescriptions));
|
||||
}
|
||||
|
||||
private RegisterCredentialDescriptionRequest(@NonNull Parcel in) {
|
||||
List<CredentialDescription> credentialDescriptions = new ArrayList<>();
|
||||
in.readTypedList(credentialDescriptions, CredentialDescription.CREATOR);
|
||||
|
||||
mCredentialDescriptions = new ArrayList<>();
|
||||
AnnotationValidations.validate(android.annotation.NonNull.class, null,
|
||||
credentialDescriptions);
|
||||
mCredentialDescriptions.addAll(credentialDescriptions);
|
||||
}
|
||||
|
||||
public static final @NonNull Parcelable.Creator<RegisterCredentialDescriptionRequest> CREATOR =
|
||||
new Parcelable.Creator<RegisterCredentialDescriptionRequest>() {
|
||||
@Override
|
||||
public RegisterCredentialDescriptionRequest createFromParcel(Parcel in) {
|
||||
return new RegisterCredentialDescriptionRequest(in);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RegisterCredentialDescriptionRequest[] newArray(int size) {
|
||||
return new RegisterCredentialDescriptionRequest[size];
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(@NonNull Parcel dest, int flags) {
|
||||
dest.writeTypedList(mCredentialDescriptions, flags);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public List<CredentialDescription> getCredentialDescriptions() {
|
||||
return mCredentialDescriptions;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
* Copyright 2023 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package android.credentials;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.os.CancellationSignal;
|
||||
import android.os.OutcomeReceiver;
|
||||
|
||||
import com.android.internal.util.Preconditions;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
/**
|
||||
* Represents an error encountered during the {@link
|
||||
* CredentialManager#registerCredentialDescription(RegisterCredentialDescriptionRequest,
|
||||
* CancellationSignal, Executor, OutcomeReceiver)} operation.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public class UnregisterCredentialDescriptionException extends Exception {
|
||||
|
||||
@NonNull public final String errorType;
|
||||
|
||||
/**
|
||||
* Constructs a {@link RegisterCredentialDescriptionException}.
|
||||
*
|
||||
* @throws IllegalArgumentException If errorType is empty.
|
||||
*/
|
||||
public UnregisterCredentialDescriptionException(@NonNull String errorType,
|
||||
@Nullable String message) {
|
||||
this(errorType, message, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a {@link RegisterCredentialDescriptionException}.
|
||||
*
|
||||
* @throws IllegalArgumentException If errorType is empty.
|
||||
*/
|
||||
public UnregisterCredentialDescriptionException(
|
||||
@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 RegisterCredentialDescriptionException}.
|
||||
*
|
||||
* @throws IllegalArgumentException If errorType is empty.
|
||||
*/
|
||||
public UnregisterCredentialDescriptionException(@NonNull String errorType,
|
||||
@Nullable Throwable cause) {
|
||||
this(errorType, null, cause);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a {@link RegisterCredentialDescriptionException}.
|
||||
*
|
||||
* @throws IllegalArgumentException If errorType is empty.
|
||||
*/
|
||||
public UnregisterCredentialDescriptionException(@NonNull String errorType) {
|
||||
this(errorType, null, null);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
* Copyright 2023 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package android.credentials;
|
||||
|
||||
parcelable UnregisterCredentialDescriptionRequest;
|
||||
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* Copyright (C) 2023 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package android.credentials;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.content.ComponentName;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import com.android.internal.util.AnnotationValidations;
|
||||
|
||||
|
||||
/**
|
||||
* A request to unregister a {@link ComponentName} that contains an actively provisioned
|
||||
* {@link Credential} represented by a {@link CredentialDescription}. *
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public final class UnregisterCredentialDescriptionRequest implements Parcelable {
|
||||
|
||||
@NonNull
|
||||
private final CredentialDescription mCredentialDescription;
|
||||
|
||||
public UnregisterCredentialDescriptionRequest(@NonNull CredentialDescription
|
||||
credentialDescription) {
|
||||
mCredentialDescription = requireNonNull(credentialDescription);
|
||||
}
|
||||
|
||||
private UnregisterCredentialDescriptionRequest(@NonNull Parcel in) {
|
||||
CredentialDescription credentialDescription =
|
||||
CredentialDescription.CREATOR.createFromParcel(in);
|
||||
|
||||
mCredentialDescription = credentialDescription;
|
||||
AnnotationValidations.validate(android.annotation.NonNull.class, null,
|
||||
credentialDescription);
|
||||
}
|
||||
|
||||
public static final @NonNull Parcelable.Creator<UnregisterCredentialDescriptionRequest>
|
||||
CREATOR = new Parcelable.Creator<UnregisterCredentialDescriptionRequest>() {
|
||||
@Override
|
||||
public UnregisterCredentialDescriptionRequest createFromParcel(Parcel in) {
|
||||
return new UnregisterCredentialDescriptionRequest(in);
|
||||
}
|
||||
|
||||
@Override
|
||||
public UnregisterCredentialDescriptionRequest[] newArray(int size) {
|
||||
return new UnregisterCredentialDescriptionRequest[size];
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(@NonNull Parcel dest, int flags) {
|
||||
mCredentialDescription.writeToParcel(dest, flags);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public CredentialDescription getCredentialDescription() {
|
||||
return mCredentialDescription;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
/*
|
||||
* Copyright (C) 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 com.android.server.credentials;
|
||||
|
||||
import android.credentials.CredentialDescription;
|
||||
import android.credentials.IRegisterCredentialDescriptionCallback;
|
||||
import android.credentials.IUnregisterCredentialDescriptionCallback;
|
||||
import android.credentials.RegisterCredentialDescriptionRequest;
|
||||
import android.credentials.UnregisterCredentialDescriptionRequest;
|
||||
import android.os.RemoteException;
|
||||
import android.util.SparseArray;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/** Contains information on what CredentialProvider has what provisioned Credential. */
|
||||
public class CredentialDescriptionRegistry {
|
||||
|
||||
private static final int MAX_ALLOWED_CREDENTIAL_DESCRIPTIONS = 128;
|
||||
private static SparseArray<CredentialDescriptionRegistry> sCredentialDescriptionSessionPerUser;
|
||||
|
||||
static {
|
||||
sCredentialDescriptionSessionPerUser = new SparseArray<>();
|
||||
}
|
||||
|
||||
// TODO(b/265992655): add a way to update CredentialRegistry when a user is removed.
|
||||
/** Get and/or create a {@link CredentialDescription} for the given user id. */
|
||||
public static CredentialDescriptionRegistry forUser(int userId) {
|
||||
CredentialDescriptionRegistry session =
|
||||
sCredentialDescriptionSessionPerUser.get(userId, null);
|
||||
|
||||
if (session == null) {
|
||||
session = new CredentialDescriptionRegistry();
|
||||
sCredentialDescriptionSessionPerUser.put(userId, session);
|
||||
}
|
||||
return session;
|
||||
}
|
||||
|
||||
private Map<String, Set<CredentialDescription>> mCredentialDescriptions;
|
||||
|
||||
private CredentialDescriptionRegistry() {
|
||||
this.mCredentialDescriptions = new HashMap<>();
|
||||
}
|
||||
|
||||
/** Handle the given {@link RegisterCredentialDescriptionRequest} by creating
|
||||
* the appropriate package name mapping. */
|
||||
public void executeRegisterRequest(RegisterCredentialDescriptionRequest request,
|
||||
String callingPackageName,
|
||||
IRegisterCredentialDescriptionCallback callback) {
|
||||
|
||||
if (!mCredentialDescriptions.containsKey(callingPackageName)
|
||||
&& mCredentialDescriptions.size() <= MAX_ALLOWED_CREDENTIAL_DESCRIPTIONS) {
|
||||
mCredentialDescriptions.put(callingPackageName, new HashSet<>());
|
||||
}
|
||||
|
||||
mCredentialDescriptions.get(callingPackageName)
|
||||
.addAll(request.getCredentialDescriptions());
|
||||
|
||||
try {
|
||||
callback.onResponse();
|
||||
} catch (RemoteException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
/** Handle the given {@link UnregisterCredentialDescriptionRequest} by creating
|
||||
* the appropriate package name mapping. */
|
||||
public void executeUnregisterRequest(
|
||||
UnregisterCredentialDescriptionRequest request,
|
||||
String callingPackageName,
|
||||
IUnregisterCredentialDescriptionCallback callback) {
|
||||
|
||||
if (mCredentialDescriptions.containsKey(callingPackageName)) {
|
||||
mCredentialDescriptions.get(callingPackageName)
|
||||
.remove(request.getCredentialDescription());
|
||||
}
|
||||
|
||||
try {
|
||||
callback.onResponse();
|
||||
} catch (RemoteException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns package names of CredentialProviders that can satisfy a given
|
||||
* {@link CredentialDescription}. */
|
||||
public Set<String> filterCredentials(String flatRequestString) {
|
||||
|
||||
Set<String> result = new HashSet<>();
|
||||
|
||||
for (String componentName: mCredentialDescriptions.keySet()) {
|
||||
Set<CredentialDescription> currentSet = mCredentialDescriptions.get(componentName);
|
||||
for (CredentialDescription containedDescription: currentSet) {
|
||||
if (flatRequestString.equals(containedDescription.getFlattenedRequestString())) {
|
||||
result.add(componentName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void evictProviderWithPackageName(String packageName) {
|
||||
if (mCredentialDescriptions.containsKey(packageName)) {
|
||||
mCredentialDescriptions.remove(packageName);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -36,14 +36,19 @@ import android.credentials.ICreateCredentialCallback;
|
||||
import android.credentials.ICredentialManager;
|
||||
import android.credentials.IGetCredentialCallback;
|
||||
import android.credentials.IListEnabledProvidersCallback;
|
||||
import android.credentials.IRegisterCredentialDescriptionCallback;
|
||||
import android.credentials.ISetEnabledProvidersCallback;
|
||||
import android.credentials.IUnregisterCredentialDescriptionCallback;
|
||||
import android.credentials.ListEnabledProvidersResponse;
|
||||
import android.credentials.RegisterCredentialDescriptionRequest;
|
||||
import android.credentials.UnregisterCredentialDescriptionRequest;
|
||||
import android.credentials.ui.IntentFactory;
|
||||
import android.os.Binder;
|
||||
import android.os.CancellationSignal;
|
||||
import android.os.ICancellationSignal;
|
||||
import android.os.RemoteException;
|
||||
import android.os.UserHandle;
|
||||
import android.provider.DeviceConfig;
|
||||
import android.provider.Settings;
|
||||
import android.service.credentials.BeginCreateCredentialRequest;
|
||||
import android.service.credentials.BeginGetCredentialRequest;
|
||||
@@ -59,9 +64,13 @@ import com.android.server.infra.AbstractMasterSystemService;
|
||||
import com.android.server.infra.SecureSettingsServiceNameResolver;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* Entry point service for credential management.
|
||||
@@ -75,6 +84,8 @@ public final class CredentialManagerService
|
||||
CredentialManagerService, CredentialManagerServiceImpl> {
|
||||
|
||||
private static final String TAG = "CredManSysService";
|
||||
private static final String DEVICE_CONFIG_ENABLE_CREDENTIAL_DESC_API =
|
||||
"enable_credential_description_api";
|
||||
|
||||
private final Context mContext;
|
||||
|
||||
@@ -164,6 +175,7 @@ public final class CredentialManagerService
|
||||
if (services == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
CredentialManagerServiceImpl serviceToBeRemoved = null;
|
||||
for (CredentialManagerServiceImpl service : services) {
|
||||
if (service != null) {
|
||||
@@ -180,10 +192,14 @@ public final class CredentialManagerService
|
||||
}
|
||||
if (serviceToBeRemoved != null) {
|
||||
removeServiceFromCache(serviceToBeRemoved, userId);
|
||||
CredentialDescriptionRegistry.forUser(userId)
|
||||
.evictProviderWithPackageName(serviceToBeRemoved.getServicePackageName());
|
||||
}
|
||||
// TODO("Iterate over system services and remove if needed")
|
||||
}
|
||||
|
||||
|
||||
|
||||
@GuardedBy("mLock")
|
||||
private List<CredentialManagerServiceImpl> getOrConstructSystemServiceListLock(
|
||||
int resolvedUserId) {
|
||||
@@ -223,6 +239,53 @@ public final class CredentialManagerService
|
||||
concatenatedServices.addAll(getOrConstructSystemServiceListLock(userId));
|
||||
return concatenatedServices;
|
||||
}
|
||||
public static boolean isCredentialDescriptionApiEnabled() {
|
||||
return DeviceConfig.getBoolean(
|
||||
DeviceConfig.NAMESPACE_CREDENTIAL, DEVICE_CONFIG_ENABLE_CREDENTIAL_DESC_API, false);
|
||||
}
|
||||
|
||||
@SuppressWarnings("GuardedBy") // ErrorProne requires initiateProviderSessionForRequestLocked
|
||||
// to be guarded by 'service.mLock', which is the same as mLock.
|
||||
private List<ProviderSession> initiateProviderSessionsWithActiveContainers(
|
||||
RequestSession session,
|
||||
List<String> requestOptions, Set<ComponentName> activeCredentialContainers) {
|
||||
List<ProviderSession> providerSessions = new ArrayList<>();
|
||||
// Invoke all services of a user to initiate a provider session
|
||||
runForUser((service) -> {
|
||||
if (activeCredentialContainers.contains(service.getComponentName())) {
|
||||
ProviderSession providerSession = service
|
||||
.initiateProviderSessionForRequestLocked(session, requestOptions);
|
||||
if (providerSession != null) {
|
||||
providerSessions.add(providerSession);
|
||||
}
|
||||
}
|
||||
});
|
||||
return providerSessions;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private Set<String> getMatchingProviders(GetCredentialRequest request) {
|
||||
// Session for active/provisioned credential descriptions;
|
||||
CredentialDescriptionRegistry registry = CredentialDescriptionRegistry
|
||||
.forUser(UserHandle.getCallingUserId());
|
||||
|
||||
// All requested credential descriptions based on the given request.
|
||||
Set<String> requestedCredentialDescriptions =
|
||||
request.getGetCredentialOptions().stream().map(
|
||||
getCredentialOption -> getCredentialOption
|
||||
.getCredentialRetrievalData()
|
||||
.getString(RegisterCredentialDescriptionRequest
|
||||
.FLATTENED_REQUEST_STRING_KEY))
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
// All requested credential descriptions based on the given request.
|
||||
return requestedCredentialDescriptions.stream()
|
||||
.map(registry::filterCredentials)
|
||||
.flatMap(
|
||||
(Function<Set<String>, Stream<String>>)
|
||||
Collection::stream)
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
@SuppressWarnings("GuardedBy") // ErrorProne requires initiateProviderSessionForRequestLocked
|
||||
// to be guarded by 'service.mLock', which is the same as mLock.
|
||||
@@ -282,11 +345,11 @@ public final class CredentialManagerService
|
||||
|
||||
// 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()));
|
||||
|
||||
if (providerSessions.isEmpty()) {
|
||||
try {
|
||||
@@ -316,7 +379,7 @@ public final class CredentialManagerService
|
||||
ICreateCredentialCallback callback,
|
||||
String callingPackage) {
|
||||
Log.i(TAG, "starting executeCreateCredential with callingPackage: " + callingPackage);
|
||||
// TODO : Implement cancellation
|
||||
|
||||
ICancellationSignal cancelTransport = CancellationSignal.createTransport();
|
||||
|
||||
// New request session, scoped for this request only.
|
||||
@@ -478,5 +541,77 @@ public final class CredentialManagerService
|
||||
});
|
||||
return cancelTransport;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ICancellationSignal registerCredentialDescription(
|
||||
RegisterCredentialDescriptionRequest request,
|
||||
IRegisterCredentialDescriptionCallback callback, String callingPackage) {
|
||||
Log.i(TAG, "registerCredentialDescription");
|
||||
ICancellationSignal cancelTransport = CancellationSignal.createTransport();
|
||||
|
||||
|
||||
List<CredentialProviderInfo> services =
|
||||
CredentialProviderInfo.getAvailableServices(mContext,
|
||||
UserHandle.getCallingUserId());
|
||||
|
||||
List<String> providers = services.stream()
|
||||
.map(credentialProviderInfo
|
||||
-> credentialProviderInfo.getServiceInfo().packageName).toList();
|
||||
if (!providers.contains(callingPackage)) {
|
||||
try {
|
||||
callback.onError("UNKNOWN",
|
||||
"Not an existing provider.");
|
||||
} catch (RemoteException e) {
|
||||
Log.i(
|
||||
TAG,
|
||||
"Issue invoking onError on IRegisterCredentialDescriptionCallback "
|
||||
+ "callback: "
|
||||
+ e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
CredentialDescriptionRegistry session = CredentialDescriptionRegistry
|
||||
.forUser(UserHandle.getCallingUserId());
|
||||
|
||||
session.executeRegisterRequest(request, callingPackage, callback);
|
||||
|
||||
return cancelTransport;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ICancellationSignal unRegisterCredentialDescription(
|
||||
UnregisterCredentialDescriptionRequest request,
|
||||
IUnregisterCredentialDescriptionCallback callback, String callingPackage) {
|
||||
Log.i(TAG, "registerCredentialDescription");
|
||||
ICancellationSignal cancelTransport = CancellationSignal.createTransport();
|
||||
|
||||
List<CredentialProviderInfo> services =
|
||||
CredentialProviderInfo.getAvailableServices(mContext,
|
||||
UserHandle.getCallingUserId());
|
||||
|
||||
List<String> providers = services.stream()
|
||||
.map(credentialProviderInfo
|
||||
-> credentialProviderInfo.getServiceInfo().packageName).toList();
|
||||
|
||||
if (!providers.contains(callingPackage)) {
|
||||
try {
|
||||
callback.onError("UNKNOWN",
|
||||
"Not an existing provider.");
|
||||
} catch (RemoteException e) {
|
||||
Log.i(
|
||||
TAG,
|
||||
"Issue invoking onError on IRegisterCredentialDescriptionCallback "
|
||||
+ "callback: "
|
||||
+ e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
CredentialDescriptionRegistry session = CredentialDescriptionRegistry
|
||||
.forUser(UserHandle.getCallingUserId());
|
||||
|
||||
session.executeUnregisterRequest(request, callingPackage, callback);
|
||||
|
||||
return cancelTransport;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user