Propagate ancellation signal to cred man service

If developer cancels the get/create request, we propagate
to the remote cancellation signal and clear the session.
This change includes manually checking before sending back a
response to the develoeper, before invoking the UI and before processing
the repsonse of UI, and then propagating the cancellation signal to the
providers.

Could follow up with adding a listener that proactively cancels the
session in addition to the hook points checked manually.

Test: Built & deployed locally
Bug: 264944428

Change-Id: Ia4c0c4d0e758433400d4f086b482fdb273120968
This commit is contained in:
Reema Bajwa
2023-01-22 23:17:44 +00:00
parent ceaf48655e
commit 53dacd6f2d
7 changed files with 118 additions and 36 deletions

View File

@@ -23,6 +23,7 @@ import android.credentials.ClearCredentialStateRequest;
import android.credentials.IClearCredentialStateCallback;
import android.credentials.ui.ProviderData;
import android.credentials.ui.RequestInfo;
import android.os.CancellationSignal;
import android.os.RemoteException;
import android.service.credentials.CallingAppInfo;
import android.service.credentials.CredentialProviderInfo;
@@ -41,9 +42,9 @@ public final class ClearRequestSession extends RequestSession<ClearCredentialSta
public ClearRequestSession(Context context, int userId, int callingUid,
IClearCredentialStateCallback callback, ClearCredentialStateRequest request,
CallingAppInfo callingAppInfo) {
CallingAppInfo callingAppInfo, CancellationSignal cancellationSignal) {
super(context, userId, callingUid, request, callback, RequestInfo.TYPE_UNDEFINED,
callingAppInfo);
callingAppInfo, cancellationSignal);
}
/**
@@ -111,6 +112,12 @@ public final class ClearRequestSession extends RequestSession<ClearCredentialSta
private void respondToClientWithResponseAndFinish() {
Log.i(TAG, "respondToClientWithResponseAndFinish");
if (isSessionCancelled()) {
// TODO: Differentiate btw cancelled and false
logApiCalled(RequestType.CLEAR_CREDENTIALS, /* isSuccessful */ true);
finishSession(/*propagateCancellation=*/true);
return;
}
try {
mClientCallback.onSuccess();
logApiCalled(RequestType.CLEAR_CREDENTIALS, /* isSuccessful */ true);
@@ -118,18 +125,24 @@ public final class ClearRequestSession extends RequestSession<ClearCredentialSta
Log.i(TAG, "Issue while propagating the response to the client");
logApiCalled(RequestType.CLEAR_CREDENTIALS, /* isSuccessful */ false);
}
finishSession();
finishSession(/*propagateCancellation=*/false);
}
private void respondToClientWithErrorAndFinish(String errorType, String errorMsg) {
Log.i(TAG, "respondToClientWithErrorAndFinish");
if (isSessionCancelled()) {
// TODO: Differentiate btw cancelled and false
logApiCalled(RequestType.CLEAR_CREDENTIALS, /* isSuccessful */ true);
finishSession(/*propagateCancellation=*/true);
return;
}
try {
mClientCallback.onError(errorType, errorMsg);
} catch (RemoteException e) {
e.printStackTrace();
}
logApiCalled(RequestType.CLEAR_CREDENTIALS, /* isSuccessful */ false);
finishSession();
finishSession(/*propagateCancellation=*/false);
}
private void processResponses() {

View File

@@ -27,6 +27,7 @@ import android.credentials.CredentialManager;
import android.credentials.ICreateCredentialCallback;
import android.credentials.ui.ProviderData;
import android.credentials.ui.RequestInfo;
import android.os.CancellationSignal;
import android.os.RemoteException;
import android.service.credentials.CallingAppInfo;
import android.service.credentials.CredentialProviderInfo;
@@ -47,9 +48,10 @@ public final class CreateRequestSession extends RequestSession<CreateCredentialR
CreateRequestSession(@NonNull Context context, int userId, int callingUid,
CreateCredentialRequest request,
ICreateCredentialCallback callback,
CallingAppInfo callingAppInfo) {
CallingAppInfo callingAppInfo,
CancellationSignal cancellationSignal) {
super(context, userId, callingUid, request, callback, RequestInfo.TYPE_CREATE,
callingAppInfo);
callingAppInfo, cancellationSignal);
}
/**
@@ -119,6 +121,12 @@ public final class CreateRequestSession extends RequestSession<CreateCredentialR
private void respondToClientWithResponseAndFinish(CreateCredentialResponse response) {
Log.i(TAG, "respondToClientWithResponseAndFinish");
if (isSessionCancelled()) {
// TODO: Differentiate btw cancelled and false
logApiCalled(RequestType.CREATE_CREDENTIALS, /* isSuccessful */ true);
finishSession(/*propagateCancellation=*/true);
return;
}
try {
mClientCallback.onResponse(response);
logApiCalled(RequestType.CREATE_CREDENTIALS, /* isSuccessful */ true);
@@ -126,18 +134,24 @@ public final class CreateRequestSession extends RequestSession<CreateCredentialR
Log.i(TAG, "Issue while responding to client: " + e.getMessage());
logApiCalled(RequestType.CREATE_CREDENTIALS, /* isSuccessful */ false);
}
finishSession();
finishSession(/*propagateCancellation=*/false);
}
private void respondToClientWithErrorAndFinish(String errorType, String errorMsg) {
Log.i(TAG, "respondToClientWithErrorAndFinish");
if (isSessionCancelled()) {
// TODO: Differentiate btw cancelled and false
logApiCalled(RequestType.CREATE_CREDENTIALS, /* isSuccessful */ true);
finishSession(/*propagateCancellation=*/true);
return;
}
try {
mClientCallback.onError(errorType, errorMsg);
} catch (RemoteException e) {
Log.i(TAG, "Issue while responding to client: " + e.getMessage());
}
logApiCalled(RequestType.CREATE_CREDENTIALS, /* isSuccessful */ false);
finishSession();
finishSession(/*propagateCancellation=*/false);
}
@Override

View File

@@ -196,7 +196,6 @@ public final class CredentialManagerService
}
@GuardedBy("mLock")
private List<CredentialManagerServiceImpl> getOrConstructSystemServiceListLock(
int resolvedUserId) {
@@ -338,7 +337,8 @@ public final class CredentialManagerService
callingUid,
callback,
request,
constructCallingAppInfo(callingPackage, userId));
constructCallingAppInfo(callingPackage, userId),
CancellationSignal.fromTransport(cancelTransport));
// Initiate all provider sessions
List<ProviderSession> providerSessions =
@@ -360,8 +360,6 @@ public final class CredentialManagerService
+ e.getMessage());
}
}
// Iterate over all provider sessions and invoke the request
providerSessions.forEach(ProviderSession::invokeSession);
return cancelTransport;
}
@@ -385,7 +383,8 @@ public final class CredentialManagerService
callingUid,
request,
callback,
constructCallingAppInfo(callingPackage, userId));
constructCallingAppInfo(callingPackage, userId),
CancellationSignal.fromTransport(cancelTransport));
// Initiate all provider sessions
List<ProviderSession> providerSessions =
@@ -405,8 +404,7 @@ public final class CredentialManagerService
}
// Iterate over all provider sessions and invoke the request
providerSessions.forEach(
ProviderSession::invokeSession);
providerSessions.forEach(ProviderSession::invokeSession);
return cancelTransport;
}
@@ -497,7 +495,8 @@ public final class CredentialManagerService
callingUid,
callback,
request,
constructCallingAppInfo(callingPackage, userId));
constructCallingAppInfo(callingPackage, userId),
CancellationSignal.fromTransport(cancelTransport));
// Initiate all provider sessions
// TODO: Determine if provider needs to have clear capability in their manifest
@@ -518,8 +517,7 @@ public final class CredentialManagerService
}
// Iterate over all provider sessions and invoke the request
providerSessions.forEach(
ProviderSession::invokeSession);
providerSessions.forEach(ProviderSession::invokeSession);
return cancelTransport;
}

View File

@@ -25,6 +25,7 @@ import android.credentials.GetCredentialResponse;
import android.credentials.IGetCredentialCallback;
import android.credentials.ui.ProviderData;
import android.credentials.ui.RequestInfo;
import android.os.CancellationSignal;
import android.os.RemoteException;
import android.service.credentials.CallingAppInfo;
import android.service.credentials.CredentialProviderInfo;
@@ -43,8 +44,9 @@ public final class GetRequestSession extends RequestSession<GetCredentialRequest
public GetRequestSession(Context context, int userId, int callingUid,
IGetCredentialCallback callback, GetCredentialRequest request,
CallingAppInfo callingAppInfo) {
super(context, userId, callingUid, request, callback, RequestInfo.TYPE_GET, callingAppInfo);
CallingAppInfo callingAppInfo, CancellationSignal cancellationSignal) {
super(context, userId, callingUid, request, callback, RequestInfo.TYPE_GET,
callingAppInfo, cancellationSignal);
}
/**
@@ -102,6 +104,12 @@ public final class GetRequestSession extends RequestSession<GetCredentialRequest
}
private void respondToClientWithResponseAndFinish(GetCredentialResponse response) {
if (isSessionCancelled()) {
// TODO: Differentiate btw cancelled and false
logApiCalled(RequestType.GET_CREDENTIALS, /* isSuccessful */ false);
finishSession(/*propagateCancellation=*/true);
return;
}
try {
mClientCallback.onResponse(response);
logApiCalled(RequestType.GET_CREDENTIALS, /* isSuccessful */ true);
@@ -109,18 +117,22 @@ public final class GetRequestSession extends RequestSession<GetCredentialRequest
Log.i(TAG, "Issue while responding to client with a response : " + e.getMessage());
logApiCalled(RequestType.GET_CREDENTIALS, /* isSuccessful */ false);
}
finishSession();
finishSession(/*propagateCancellation=*/false);
}
private void respondToClientWithErrorAndFinish(String errorType, String errorMsg) {
if (isSessionCancelled()) {
logApiCalled(RequestType.GET_CREDENTIALS, /* isSuccessful */ false);
finishSession(/*propagateCancellation=*/true);
return;
}
try {
mClientCallback.onError(errorType, errorMsg);
} catch (RemoteException e) {
Log.i(TAG, "Issue while responding to client with error : " + e.getMessage());
}
logApiCalled(RequestType.GET_CREDENTIALS, /* isSuccessful */ false);
finishSession();
finishSession(/*propagateCancellation=*/false);
}
@Override

View File

@@ -23,8 +23,11 @@ import android.content.Context;
import android.credentials.Credential;
import android.credentials.ui.ProviderData;
import android.credentials.ui.ProviderPendingIntentResponse;
import android.os.ICancellationSignal;
import android.os.RemoteException;
import android.service.credentials.CredentialEntry;
import android.service.credentials.CredentialProviderInfo;
import android.util.Log;
import android.util.Pair;
import java.util.UUID;
@@ -49,6 +52,7 @@ public abstract class ProviderSession<T, R>
@NonNull protected Status mStatus = Status.NOT_STARTED;
@NonNull protected final ProviderInternalCallback mCallbacks;
@Nullable protected Credential mFinalCredentialResponse;
@Nullable protected ICancellationSignal mProviderCancellationSignal;
@NonNull protected final T mProviderRequest;
@Nullable protected R mProviderResponse;
@NonNull protected Boolean mProviderResponseSet = false;
@@ -151,6 +155,18 @@ public abstract class ProviderSession<T, R>
return mFinalCredentialResponse;
}
/** Propagates cancellation signal to the remote provider service. */
public void cancelProviderRemoteSession() {
try {
if (mProviderCancellationSignal != null) {
mProviderCancellationSignal.cancel();
}
setStatus(Status.CANCELED);
} catch (RemoteException e) {
Log.i(TAG, "Issue while cancelling provider session: " + e.getMessage());
}
}
protected void setStatus(@NonNull Status status) {
mStatus = status;
}

View File

@@ -110,7 +110,7 @@ public class RemoteCredentialService extends ServiceConnector.Impl<ICredentialPr
* @param callback the callback to be used to send back the provider response to the
* {@link ProviderGetSession} class that maintains provider state
*/
public void onBeginGetCredential(@NonNull BeginGetCredentialRequest request,
public ICancellationSignal onBeginGetCredential(@NonNull BeginGetCredentialRequest request,
ProviderCallbacks<BeginGetCredentialResponse> callback) {
Log.i(TAG, "In onGetCredentials in RemoteCredentialService");
AtomicReference<ICancellationSignal> cancellationSink = new AtomicReference<>();
@@ -149,6 +149,8 @@ public class RemoteCredentialService extends ServiceConnector.Impl<ICredentialPr
futureRef.set(connectThenExecute);
connectThenExecute.whenComplete((result, error) -> Handler.getMain().post(() ->
handleExecutionResponse(result, error, cancellationSink, callback)));
return cancellationSink.get();
}
/** Main entry point to be called for executing a beginCreateCredential call on the remote
@@ -157,7 +159,7 @@ public class RemoteCredentialService extends ServiceConnector.Impl<ICredentialPr
* @param callback the callback to be used to send back the provider response to the
* {@link ProviderCreateSession} class that maintains provider state
*/
public void onCreateCredential(@NonNull BeginCreateCredentialRequest request,
public ICancellationSignal onCreateCredential(@NonNull BeginCreateCredentialRequest request,
ProviderCallbacks<BeginCreateCredentialResponse> callback) {
Log.i(TAG, "In onCreateCredential in RemoteCredentialService");
AtomicReference<ICancellationSignal> cancellationSink = new AtomicReference<>();
@@ -196,6 +198,8 @@ public class RemoteCredentialService extends ServiceConnector.Impl<ICredentialPr
futureRef.set(connectThenExecute);
connectThenExecute.whenComplete((result, error) -> Handler.getMain().post(() ->
handleExecutionResponse(result, error, cancellationSink, callback)));
return cancellationSink.get();
}
/** Main entry point to be called for executing a clearCredentialState call on the remote
@@ -204,7 +208,7 @@ public class RemoteCredentialService extends ServiceConnector.Impl<ICredentialPr
* @param callback the callback to be used to send back the provider response to the
* {@link ProviderClearSession} class that maintains provider state
*/
public void onClearCredentialState(@NonNull ClearCredentialStateRequest request,
public ICancellationSignal onClearCredentialState(@NonNull ClearCredentialStateRequest request,
ProviderCallbacks<Void> callback) {
Log.i(TAG, "In onClearCredentialState in RemoteCredentialService");
AtomicReference<ICancellationSignal> cancellationSink = new AtomicReference<>();
@@ -243,6 +247,8 @@ public class RemoteCredentialService extends ServiceConnector.Impl<ICredentialPr
futureRef.set(connectThenExecute);
connectThenExecute.whenComplete((result, error) -> Handler.getMain().post(() ->
handleExecutionResponse(result, error, cancellationSink, callback)));
return cancellationSink.get();
}
private <T> void handleExecutionResponse(T result,

View File

@@ -29,6 +29,7 @@ import android.content.Context;
import android.credentials.ui.ProviderData;
import android.credentials.ui.UserSelectionDialogResult;
import android.os.Binder;
import android.os.CancellationSignal;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
@@ -83,13 +84,16 @@ abstract class RequestSession<T, U> implements CredentialManagerUi.CredentialMan
private final int mCallingUid;
@NonNull
protected final CallingAppInfo mClientAppInfo;
@NonNull
protected final CancellationSignal mCancellationSignal;
protected final Map<String, ProviderSession> mProviders = new HashMap<>();
protected RequestSession(@NonNull Context context,
@UserIdInt int userId, int callingUid, @NonNull T clientRequest, U clientCallback,
@NonNull String requestType,
CallingAppInfo callingAppInfo) {
CallingAppInfo callingAppInfo,
CancellationSignal cancellationSignal) {
mContext = context;
mUserId = userId;
mCallingUid = callingUid;
@@ -97,6 +101,7 @@ abstract class RequestSession<T, U> implements CredentialManagerUi.CredentialMan
mClientCallback = clientCallback;
mRequestType = requestType;
mClientAppInfo = callingAppInfo;
mCancellationSignal = cancellationSignal;
mHandler = new Handler(Looper.getMainLooper(), null, true);
mRequestId = new Binder();
mCredentialManagerUi = new CredentialManagerUi(mContext,
@@ -112,6 +117,10 @@ abstract class RequestSession<T, U> implements CredentialManagerUi.CredentialMan
@Override // from CredentialManagerUiCallbacks
public void onUiSelection(UserSelectionDialogResult selection) {
if (isSessionCancelled()) {
finishSession(/*propagateCancellation=*/true);
return;
}
String providerId = selection.getProviderId();
Log.i(TAG, "onUiSelection, providerId: " + providerId);
ProviderSession providerSession = mProviders.get(providerId);
@@ -127,18 +136,19 @@ abstract class RequestSession<T, U> implements CredentialManagerUi.CredentialMan
@Override // from CredentialManagerUiCallbacks
public void onUiCancellation(boolean isUserCancellation) {
Log.i(TAG, "Ui canceled. Canceled by user: " + isUserCancellation);
if (isSessionCancelled()) {
finishSession(/*propagateCancellation=*/true);
return;
}
// User canceled the activity
finishSession();
finishSession(/*propagateCancellation=*/false);
}
protected void finishSession() {
protected void finishSession(boolean propagateCancellation) {
Log.i(TAG, "finishing session");
clearProviderSessions();
}
protected void clearProviderSessions() {
Log.i(TAG, "Clearing sessions");
//TODO: Implement
if (propagateCancellation) {
mProviders.values().forEach(ProviderSession::cancelProviderRemoteSession);
}
mProviders.clear();
}
@@ -178,6 +188,10 @@ abstract class RequestSession<T, U> implements CredentialManagerUi.CredentialMan
isSuccessful ? METRICS_API_STATUS_SUCCESS : METRICS_API_STATUS_FAILURE);
}
protected boolean isSessionCancelled() {
return mCancellationSignal.isCanceled();
}
/**
* Returns true if at least one provider is ready for UI invocation, and no
* provider is pending a response.
@@ -197,6 +211,11 @@ abstract class RequestSession<T, U> implements CredentialManagerUi.CredentialMan
Log.i(TAG, "In getProviderDataAndInitiateUi");
Log.i(TAG, "In getProviderDataAndInitiateUi providers size: " + mProviders.size());
if (isSessionCancelled()) {
finishSession(/*propagateCancellation=*/true);
return;
}
ArrayList<ProviderData> providerDataList = new ArrayList<>();
for (ProviderSession session : mProviders.values()) {
Log.i(TAG, "preparing data for : " + session.getComponentName());
@@ -208,7 +227,11 @@ abstract class RequestSession<T, U> implements CredentialManagerUi.CredentialMan
}
if (!providerDataList.isEmpty()) {
Log.i(TAG, "provider list not empty about to initiate ui");
launchUiWithProviderData(providerDataList);
if (isSessionCancelled()) {
Log.i(TAG, "In getProviderDataAndInitiateUi but session has been cancelled");
} else {
launchUiWithProviderData(providerDataList);
}
}
}
}