Merge "Add SetEnabledProviders API method for Credential Manager"
This commit is contained in:
@@ -21,6 +21,7 @@ import static java.util.Objects.requireNonNull;
|
||||
import android.annotation.CallbackExecutor;
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.annotation.RequiresPermission;
|
||||
import android.annotation.SystemService;
|
||||
import android.app.Activity;
|
||||
import android.app.PendingIntent;
|
||||
@@ -32,6 +33,7 @@ import android.os.OutcomeReceiver;
|
||||
import android.os.RemoteException;
|
||||
import android.util.Log;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
/**
|
||||
@@ -40,9 +42,9 @@ import java.util.concurrent.Executor;
|
||||
* <p>Note that an application should call the Jetpack CredentialManager apis instead of directly
|
||||
* calling these framework apis.
|
||||
*
|
||||
* <p>The CredentialManager apis launch framework UI flows for a user to
|
||||
* register a new credential or to consent to a saved credential from supported credential
|
||||
* providers, which can then be used to authenticate to the app.
|
||||
* <p>The CredentialManager apis launch framework UI flows for a user to register a new credential
|
||||
* or to consent to a saved credential from supported credential providers, which can then be used
|
||||
* to authenticate to the app.
|
||||
*/
|
||||
@SystemService(Context.CREDENTIAL_SERVICE)
|
||||
public final class CredentialManager {
|
||||
@@ -76,8 +78,7 @@ public final class CredentialManager {
|
||||
@NonNull Activity activity,
|
||||
@Nullable CancellationSignal cancellationSignal,
|
||||
@CallbackExecutor @NonNull Executor executor,
|
||||
@NonNull OutcomeReceiver<
|
||||
GetCredentialResponse, GetCredentialException> callback) {
|
||||
@NonNull OutcomeReceiver<GetCredentialResponse, GetCredentialException> callback) {
|
||||
requireNonNull(request, "request must not be null");
|
||||
requireNonNull(activity, "activity must not be null");
|
||||
requireNonNull(executor, "executor must not be null");
|
||||
@@ -90,10 +91,11 @@ public final class CredentialManager {
|
||||
|
||||
ICancellationSignal cancelRemote = null;
|
||||
try {
|
||||
cancelRemote = mService.executeGetCredential(
|
||||
request,
|
||||
new GetCredentialTransport(activity, executor, callback),
|
||||
mContext.getOpPackageName());
|
||||
cancelRemote =
|
||||
mService.executeGetCredential(
|
||||
request,
|
||||
new GetCredentialTransport(activity, executor, callback),
|
||||
mContext.getOpPackageName());
|
||||
} catch (RemoteException e) {
|
||||
e.rethrowFromSystemServer();
|
||||
}
|
||||
@@ -106,8 +108,8 @@ public final class CredentialManager {
|
||||
/**
|
||||
* Launches the necessary flows to register an app credential for the user.
|
||||
*
|
||||
* <p>The execution can potentially launch UI flows to collect user consent to creating
|
||||
* or storing the new credential, etc.
|
||||
* <p>The execution can potentially launch UI flows to collect user consent to creating or
|
||||
* storing the new credential, etc.
|
||||
*
|
||||
* @param request the request specifying type(s) of credentials to get from the user
|
||||
* @param activity the activity used to launch any UI needed
|
||||
@@ -120,8 +122,8 @@ public final class CredentialManager {
|
||||
@NonNull Activity activity,
|
||||
@Nullable CancellationSignal cancellationSignal,
|
||||
@CallbackExecutor @NonNull Executor executor,
|
||||
@NonNull OutcomeReceiver<
|
||||
CreateCredentialResponse, CreateCredentialException> callback) {
|
||||
@NonNull
|
||||
OutcomeReceiver<CreateCredentialResponse, CreateCredentialException> callback) {
|
||||
requireNonNull(request, "request must not be null");
|
||||
requireNonNull(activity, "activity must not be null");
|
||||
requireNonNull(executor, "executor must not be null");
|
||||
@@ -134,9 +136,11 @@ public final class CredentialManager {
|
||||
|
||||
ICancellationSignal cancelRemote = null;
|
||||
try {
|
||||
cancelRemote = mService.executeCreateCredential(request,
|
||||
new CreateCredentialTransport(activity, executor, callback),
|
||||
mContext.getOpPackageName());
|
||||
cancelRemote =
|
||||
mService.executeCreateCredential(
|
||||
request,
|
||||
new CreateCredentialTransport(activity, executor, callback),
|
||||
mContext.getOpPackageName());
|
||||
} catch (RemoteException e) {
|
||||
e.rethrowFromSystemServer();
|
||||
}
|
||||
@@ -149,10 +153,10 @@ public final class CredentialManager {
|
||||
/**
|
||||
* Clears the current user credential state from all credential providers.
|
||||
*
|
||||
* You should invoked this api after your user signs out of your app to notify all credential
|
||||
* <p>You should invoked this api after your user signs out of your app to notify all credential
|
||||
* providers that any stored credential session for the given app should be cleared.
|
||||
*
|
||||
* A credential provider may have stored an active credential session and use it to limit
|
||||
* <p>A credential provider may have stored an active credential session and use it to limit
|
||||
* sign-in options for future get-credential calls. For example, it may prioritize the active
|
||||
* credential over any other available credential. When your user explicitly signs out of your
|
||||
* app and in order to get the holistic sign-in options the next time, you should call this API
|
||||
@@ -178,9 +182,11 @@ public final class CredentialManager {
|
||||
|
||||
ICancellationSignal cancelRemote = null;
|
||||
try {
|
||||
cancelRemote = mService.clearCredentialState(request,
|
||||
new ClearCredentialStateTransport(executor, callback),
|
||||
mContext.getOpPackageName());
|
||||
cancelRemote =
|
||||
mService.clearCredentialState(
|
||||
request,
|
||||
new ClearCredentialStateTransport(executor, callback),
|
||||
mContext.getOpPackageName());
|
||||
} catch (RemoteException e) {
|
||||
e.rethrowFromSystemServer();
|
||||
}
|
||||
@@ -190,15 +196,44 @@ 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 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
|
||||
* @hide
|
||||
*/
|
||||
@RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
|
||||
public void setEnabledProviders(
|
||||
@NonNull List<String> providers,
|
||||
int userId,
|
||||
@CallbackExecutor @NonNull Executor executor,
|
||||
@NonNull OutcomeReceiver<Void, SetEnabledProvidersException> callback) {
|
||||
requireNonNull(executor, "executor must not be null");
|
||||
requireNonNull(callback, "callback must not be null");
|
||||
requireNonNull(providers, "providers must not be null");
|
||||
|
||||
try {
|
||||
mService.setEnabledProviders(
|
||||
providers, userId, new SetEnabledProvidersTransport(executor, callback));
|
||||
} catch (RemoteException e) {
|
||||
e.rethrowFromSystemServer();
|
||||
}
|
||||
}
|
||||
|
||||
private static class GetCredentialTransport extends IGetCredentialCallback.Stub {
|
||||
// TODO: listen for cancellation to release callback.
|
||||
|
||||
private final Activity mActivity;
|
||||
private final Executor mExecutor;
|
||||
private final OutcomeReceiver<
|
||||
GetCredentialResponse, GetCredentialException> mCallback;
|
||||
private final OutcomeReceiver<GetCredentialResponse, GetCredentialException> mCallback;
|
||||
|
||||
private GetCredentialTransport(Activity activity, Executor executor,
|
||||
private GetCredentialTransport(
|
||||
Activity activity,
|
||||
Executor executor,
|
||||
OutcomeReceiver<GetCredentialResponse, GetCredentialException> callback) {
|
||||
mActivity = activity;
|
||||
mExecutor = executor;
|
||||
@@ -210,8 +245,10 @@ public final class CredentialManager {
|
||||
try {
|
||||
mActivity.startIntentSender(pendingIntent.getIntentSender(), null, 0, 0, 0);
|
||||
} catch (IntentSender.SendIntentException e) {
|
||||
Log.e(TAG, "startIntentSender() failed for intent:"
|
||||
+ pendingIntent.getIntentSender(), e);
|
||||
Log.e(
|
||||
TAG,
|
||||
"startIntentSender() failed for intent:" + pendingIntent.getIntentSender(),
|
||||
e);
|
||||
// TODO: propagate the error.
|
||||
}
|
||||
}
|
||||
@@ -233,10 +270,12 @@ public final class CredentialManager {
|
||||
|
||||
private final Activity mActivity;
|
||||
private final Executor mExecutor;
|
||||
private final OutcomeReceiver<
|
||||
CreateCredentialResponse, CreateCredentialException> mCallback;
|
||||
private final OutcomeReceiver<CreateCredentialResponse, CreateCredentialException>
|
||||
mCallback;
|
||||
|
||||
private CreateCredentialTransport(Activity activity, Executor executor,
|
||||
private CreateCredentialTransport(
|
||||
Activity activity,
|
||||
Executor executor,
|
||||
OutcomeReceiver<CreateCredentialResponse, CreateCredentialException> callback) {
|
||||
mActivity = activity;
|
||||
mExecutor = executor;
|
||||
@@ -248,8 +287,10 @@ public final class CredentialManager {
|
||||
try {
|
||||
mActivity.startIntentSender(pendingIntent.getIntentSender(), null, 0, 0, 0);
|
||||
} catch (IntentSender.SendIntentException e) {
|
||||
Log.e(TAG, "startIntentSender() failed for intent:"
|
||||
+ pendingIntent.getIntentSender(), e);
|
||||
Log.e(
|
||||
TAG,
|
||||
"startIntentSender() failed for intent:" + pendingIntent.getIntentSender(),
|
||||
e);
|
||||
// TODO: propagate the error.
|
||||
}
|
||||
}
|
||||
@@ -266,15 +307,14 @@ public final class CredentialManager {
|
||||
}
|
||||
}
|
||||
|
||||
private static class ClearCredentialStateTransport
|
||||
extends IClearCredentialStateCallback.Stub {
|
||||
private static class ClearCredentialStateTransport extends IClearCredentialStateCallback.Stub {
|
||||
// TODO: listen for cancellation to release callback.
|
||||
|
||||
private final Executor mExecutor;
|
||||
private final OutcomeReceiver<Void, ClearCredentialStateException> mCallback;
|
||||
|
||||
private ClearCredentialStateTransport(Executor executor,
|
||||
OutcomeReceiver<Void, ClearCredentialStateException> callback) {
|
||||
private ClearCredentialStateTransport(
|
||||
Executor executor, OutcomeReceiver<Void, ClearCredentialStateException> callback) {
|
||||
mExecutor = executor;
|
||||
mCallback = callback;
|
||||
}
|
||||
@@ -290,4 +330,32 @@ public final class CredentialManager {
|
||||
() -> mCallback.onError(new ClearCredentialStateException(errorType, message)));
|
||||
}
|
||||
}
|
||||
|
||||
private static class SetEnabledProvidersTransport extends ISetEnabledProvidersCallback.Stub {
|
||||
// TODO: listen for cancellation to release callback.
|
||||
|
||||
private final Executor mExecutor;
|
||||
private final OutcomeReceiver<Void, SetEnabledProvidersException> mCallback;
|
||||
|
||||
private SetEnabledProvidersTransport(
|
||||
Executor executor, OutcomeReceiver<Void, SetEnabledProvidersException> callback) {
|
||||
mExecutor = executor;
|
||||
mCallback = callback;
|
||||
}
|
||||
|
||||
public void onResponse(Void result) {
|
||||
mExecutor.execute(() -> mCallback.onResult(result));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResponse() {
|
||||
mExecutor.execute(() -> mCallback.onResult(null));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(String errorType, String message) {
|
||||
mExecutor.execute(
|
||||
() -> mCallback.onError(new SetEnabledProvidersException(errorType, message)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,12 +16,15 @@
|
||||
|
||||
package android.credentials;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import android.credentials.ClearCredentialStateRequest;
|
||||
import android.credentials.CreateCredentialRequest;
|
||||
import android.credentials.GetCredentialRequest;
|
||||
import android.credentials.IClearCredentialStateCallback;
|
||||
import android.credentials.ICreateCredentialCallback;
|
||||
import android.credentials.IGetCredentialCallback;
|
||||
import android.credentials.ISetEnabledProvidersCallback;
|
||||
import android.os.ICancellationSignal;
|
||||
|
||||
/**
|
||||
@@ -36,4 +39,6 @@ interface ICredentialManager {
|
||||
@nullable ICancellationSignal executeCreateCredential(in CreateCredentialRequest request, in ICreateCredentialCallback callback, String callingPackage);
|
||||
|
||||
@nullable ICancellationSignal clearCredentialState(in ClearCredentialStateRequest request, in IClearCredentialStateCallback callback, String callingPackage);
|
||||
|
||||
void setEnabledProviders(in List<String> providers, in int userId, in ISetEnabledProvidersCallback callback);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* Listener for an setEnabledProviders request.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
interface ISetEnabledProvidersCallback {
|
||||
oneway void onResponse();
|
||||
oneway void onError(String errorType, String message);
|
||||
}
|
||||
@@ -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#setEnabledProviders(CancellationSignal Executor, OutcomeReceiver)} operation.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public class SetEnabledProvidersException extends Exception {
|
||||
|
||||
@NonNull public final String errorType;
|
||||
|
||||
/**
|
||||
* Constructs a {@link SetEnabledProvidersException}.
|
||||
*
|
||||
* @throws IllegalArgumentException If errorType is empty.
|
||||
*/
|
||||
public SetEnabledProvidersException(@NonNull String errorType, @Nullable String message) {
|
||||
this(errorType, message, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a {@link SetEnabledProvidersException}.
|
||||
*
|
||||
* @throws IllegalArgumentException If errorType is empty.
|
||||
*/
|
||||
public SetEnabledProvidersException(
|
||||
@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 SetEnabledProvidersException}.
|
||||
*
|
||||
* @throws IllegalArgumentException If errorType is empty.
|
||||
*/
|
||||
public SetEnabledProvidersException(@NonNull String errorType, @Nullable Throwable cause) {
|
||||
this(errorType, null, cause);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a {@link SetEnabledProvidersException}.
|
||||
*
|
||||
* @throws IllegalArgumentException If errorType is empty.
|
||||
*/
|
||||
public SetEnabledProvidersException(@NonNull String errorType) {
|
||||
this(errorType, null, null);
|
||||
}
|
||||
}
|
||||
@@ -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 SetEnabledProvidersRequest;
|
||||
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* Sets the enabled list of credential manager providers.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public final class SetEnabledProvidersRequest implements Parcelable {
|
||||
|
||||
/** List of providers. */
|
||||
@NonNull private final List<String> mProviders;
|
||||
|
||||
/**
|
||||
* Creates a {@link SetEnabledProvidersRequest} with a list of providers. The list is made up of
|
||||
* strings that are flattened component names of the service that is the credman provider.
|
||||
*
|
||||
* @throws NullPointerException If args are null.
|
||||
*/
|
||||
public SetEnabledProvidersRequest(@NonNull List<String> providers) {
|
||||
Objects.requireNonNull(providers, "providers must not be null");
|
||||
Preconditions.checkCollectionElementsNotNull(providers, /* valueName= */ "providers");
|
||||
mProviders = providers;
|
||||
}
|
||||
|
||||
private SetEnabledProvidersRequest(@NonNull Parcel in) {
|
||||
mProviders = in.createStringArrayList();
|
||||
}
|
||||
|
||||
public static final @NonNull Creator<SetEnabledProvidersRequest> CREATOR =
|
||||
new Creator<SetEnabledProvidersRequest>() {
|
||||
@Override
|
||||
public SetEnabledProvidersRequest createFromParcel(Parcel in) {
|
||||
return new SetEnabledProvidersRequest(in);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SetEnabledProvidersRequest[] newArray(int size) {
|
||||
return new SetEnabledProvidersRequest[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;
|
||||
}
|
||||
}
|
||||
@@ -20,6 +20,7 @@ import static android.content.Context.CREDENTIAL_SERVICE;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.UserIdInt;
|
||||
import android.app.ActivityManager;
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.credentials.ClearCredentialStateRequest;
|
||||
@@ -30,9 +31,11 @@ import android.credentials.IClearCredentialStateCallback;
|
||||
import android.credentials.ICreateCredentialCallback;
|
||||
import android.credentials.ICredentialManager;
|
||||
import android.credentials.IGetCredentialCallback;
|
||||
import android.credentials.ISetEnabledProvidersCallback;
|
||||
import android.os.Binder;
|
||||
import android.os.CancellationSignal;
|
||||
import android.os.ICancellationSignal;
|
||||
import android.os.RemoteException;
|
||||
import android.os.UserHandle;
|
||||
import android.provider.Settings;
|
||||
import android.service.credentials.BeginCreateCredentialRequest;
|
||||
@@ -207,6 +210,47 @@ public final class CredentialManagerService extends
|
||||
return cancelTransport;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEnabledProviders(
|
||||
List<String> providers, int userId, ISetEnabledProvidersCallback callback) {
|
||||
Log.i(TAG, "setEnabledProviders");
|
||||
|
||||
userId =
|
||||
ActivityManager.handleIncomingUser(
|
||||
Binder.getCallingPid(),
|
||||
Binder.getCallingUid(),
|
||||
userId,
|
||||
false,
|
||||
false,
|
||||
"setEnabledProviders",
|
||||
null);
|
||||
|
||||
String storedValue = String.join(":", providers);
|
||||
if (!Settings.Secure.putStringForUser(
|
||||
getContext().getContentResolver(),
|
||||
Settings.Secure.CREDENTIAL_SERVICE,
|
||||
storedValue,
|
||||
userId)) {
|
||||
Log.e(TAG, "Failed to store setting containing enabled providers");
|
||||
try {
|
||||
callback.onError(
|
||||
"failed_setting_store",
|
||||
"Failed to store setting containing enabled providers");
|
||||
} catch (RemoteException e) {
|
||||
Log.i(TAG, "Issue with invoking error response: " + e.getMessage());
|
||||
// TODO: Propagate failure
|
||||
}
|
||||
}
|
||||
|
||||
// Call the callback.
|
||||
try {
|
||||
callback.onResponse();
|
||||
} catch (RemoteException e) {
|
||||
Log.i(TAG, "Issue with invoking response: " + e.getMessage());
|
||||
// TODO: Propagate failure
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ICancellationSignal clearCredentialState(ClearCredentialStateRequest request,
|
||||
IClearCredentialStateCallback callback, String callingPackage) {
|
||||
|
||||
Reference in New Issue
Block a user