Handling all UI event codes and exposing new type constant

This change exposes a new type constant for CreateCredentialException :TYPE_NO_CREATE_OPTIONS. To be used when the provider does not return any options for create in the query phase.

Test: built locally
API-Coverage-Bug: 247549381

Change-Id: I031348d0f16238371a64e9548fdebc06cd9715f9
This commit is contained in:
Reema Bajwa
2023-02-05 06:09:42 +00:00
parent 124299c81b
commit fe4de4366a
12 changed files with 103 additions and 42 deletions

View File

@@ -13465,7 +13465,7 @@ package android.credentials {
ctor public CreateCredentialException(@NonNull String);
method @NonNull public String getType();
field @NonNull public static final String TYPE_INTERRUPTED = "android.credentials.CreateCredentialException.TYPE_INTERRUPTED";
field @NonNull public static final String TYPE_NO_CREDENTIAL = "android.credentials.CreateCredentialException.TYPE_NO_CREDENTIAL";
field @NonNull public static final String TYPE_NO_CREATE_OPTIONS = "android.credentials.CreateCredentialException.TYPE_NO_CREATE_OPTIONS";
field @NonNull public static final String TYPE_UNKNOWN = "android.credentials.CreateCredentialException.TYPE_UNKNOWN";
field @NonNull public static final String TYPE_USER_CANCELED = "android.credentials.CreateCredentialException.TYPE_USER_CANCELED";
}
@@ -40242,6 +40242,7 @@ package android.service.credentials {
}
public final class BeginCreateCredentialResponse implements android.os.Parcelable {
ctor public BeginCreateCredentialResponse();
method public int describeContents();
method @NonNull public java.util.List<android.service.credentials.CreateEntry> getCreateEntries();
method @Nullable public android.service.credentials.CreateEntry getRemoteCreateEntry();
@@ -40283,6 +40284,7 @@ package android.service.credentials {
}
public final class BeginGetCredentialResponse implements android.os.Parcelable {
ctor public BeginGetCredentialResponse();
method public int describeContents();
method @NonNull public java.util.List<android.service.credentials.Action> getActions();
method @NonNull public java.util.List<android.service.credentials.Action> getAuthenticationActions();

View File

@@ -40,13 +40,13 @@ public class CreateCredentialException extends Exception {
"android.credentials.CreateCredentialException.TYPE_UNKNOWN";
/**
* The error type value for when no credential is available for the given {@link
* CredentialManager#createCredential(CreateCredentialRequest, Activity,
* The error type value for when no create options are available from any provider(s),
* for the given {@link CredentialManager#createCredential(CreateCredentialRequest, Activity,
* CancellationSignal, Executor, OutcomeReceiver)} request.
*/
@NonNull
public static final String TYPE_NO_CREDENTIAL =
"android.credentials.CreateCredentialException.TYPE_NO_CREDENTIAL";
public static final String TYPE_NO_CREATE_OPTIONS =
"android.credentials.CreateCredentialException.TYPE_NO_CREATE_OPTIONS";
/**
* The error type value for when the user intentionally cancelled the request.
*

View File

@@ -34,6 +34,14 @@ public final class BeginCreateCredentialResponse implements Parcelable {
private final @NonNull List<CreateEntry> mCreateEntries;
private final @Nullable CreateEntry mRemoteCreateEntry;
/**
* Creates an empty response instance, to be used when there are no {@link CreateEntry}
* to return.
*/
public BeginCreateCredentialResponse() {
this(/*createEntries=*/new ArrayList<>(), /*remoteCreateEntry=*/null);
}
private BeginCreateCredentialResponse(@NonNull Parcel in) {
List<CreateEntry> createEntries = new ArrayList<>();
in.readTypedList(createEntries, CreateEntry.CREATOR);
@@ -137,13 +145,8 @@ public final class BeginCreateCredentialResponse implements Parcelable {
/**
* Builds a new instance of {@link BeginCreateCredentialResponse}.
*
* @throws NullPointerException If {@code createEntries} is null.
* @throws IllegalArgumentException If {@code createEntries} is empty.
*/
public @NonNull BeginCreateCredentialResponse build() {
Preconditions.checkCollectionNotEmpty(mCreateEntries, "createEntries must "
+ "not be null, or empty");
return new BeginCreateCredentialResponse(mCreateEntries, mRemoteCreateEntry);
}
}

View File

@@ -44,6 +44,17 @@ public final class BeginGetCredentialResponse implements Parcelable {
/** Remote credential entry to get the response from a different device. */
private final @Nullable CredentialEntry mRemoteCredentialEntry;
/**
* Creates an empty response instance, to be used when there are no {@link CredentialEntry},
* or {@link Action} to return.
*/
public BeginGetCredentialResponse() {
this(/*credentialEntries=*/new ArrayList<>(),
/*authenticationActions=*/new ArrayList<>(),
/*actions=*/new ArrayList<>(),
/*remoteCredentialEntry=*/null);
}
private BeginGetCredentialResponse(@NonNull List<CredentialEntry> credentialEntries,
@NonNull List<Action> authenticationEntries, @NonNull List<Action> actions,
@Nullable CredentialEntry remoteCredentialEntry) {
@@ -243,16 +254,8 @@ public final class BeginGetCredentialResponse implements Parcelable {
/**
* Builds a {@link BeginGetCredentialResponse} instance.
*
* @throws IllegalStateException if {@code credentialEntries}, {@code actions}
* and {@code remoteCredentialEntry} are all null or empty.
*/
public @NonNull BeginGetCredentialResponse build() {
if (mCredentialEntries.isEmpty() && mActions.isEmpty()
&& mRemoteCredentialEntry == null && mAuthenticationEntries.isEmpty()) {
throw new IllegalStateException("must set either an authentication, "
+ "credential, action or remote entry");
}
return new BeginGetCredentialResponse(mCredentialEntries, mAuthenticationEntries,
mActions, mRemoteCredentialEntry);
}

View File

@@ -97,7 +97,7 @@ public final class CreateRequestSession extends RequestSession<CreateCredentialR
if (response != null) {
respondToClientWithResponseAndFinish(response);
} else {
respondToClientWithErrorAndFinish(CreateCredentialException.TYPE_NO_CREDENTIAL,
respondToClientWithErrorAndFinish(CreateCredentialException.TYPE_NO_CREATE_OPTIONS,
"Invalid response");
}
}
@@ -119,6 +119,12 @@ public final class CreateRequestSession extends RequestSession<CreateCredentialR
}
}
@Override
public void onUiSelectorInvocationFailure() {
respondToClientWithErrorAndFinish(CreateCredentialException.TYPE_NO_CREATE_OPTIONS,
"No create options available.");
}
private void respondToClientWithResponseAndFinish(CreateCredentialResponse response) {
Log.i(TAG, "respondToClientWithResponseAndFinish");
if (isSessionCancelled()) {
@@ -166,8 +172,8 @@ public final class CreateRequestSession extends RequestSession<CreateCredentialR
Log.i(TAG, "in onProviderStatusChanged - isUiInvocationNeeded");
getProviderDataAndInitiateUi();
} else {
respondToClientWithErrorAndFinish(CreateCredentialException.TYPE_NO_CREDENTIAL,
"No credentials available");
respondToClientWithErrorAndFinish(CreateCredentialException.TYPE_NO_CREATE_OPTIONS,
"No create options available.");
}
}
}

View File

@@ -472,8 +472,8 @@ public final class CredentialManagerService
if (providerSessions.isEmpty()) {
try {
callback.onError(
CreateCredentialException.TYPE_NO_CREDENTIAL,
"No credentials available on this device.");
CreateCredentialException.TYPE_NO_CREATE_OPTIONS,
"No create options available.");
} catch (RemoteException e) {
Log.i(
TAG,

View File

@@ -56,19 +56,29 @@ public class CredentialManagerUi {
};
private void handleUiResult(int resultCode, Bundle resultData) {
if (resultCode == UserSelectionDialogResult.RESULT_CODE_DIALOG_COMPLETE_WITH_SELECTION) {
UserSelectionDialogResult selection = UserSelectionDialogResult
.fromResultData(resultData);
if (selection != null) {
mCallbacks.onUiSelection(selection);
} else {
Slog.i(TAG, "No selection found in UI result");
}
} else if (resultCode == UserSelectionDialogResult.RESULT_CODE_DIALOG_USER_CANCELED) {
mCallbacks.onUiCancellation(/* isUserCancellation= */ true);
} else if (resultCode
== UserSelectionDialogResult.RESULT_CODE_CANCELED_AND_LAUNCHED_SETTINGS) {
mCallbacks.onUiCancellation(/* isUserCancellation= */ false);
switch (resultCode) {
case UserSelectionDialogResult.RESULT_CODE_DIALOG_COMPLETE_WITH_SELECTION:
UserSelectionDialogResult selection = UserSelectionDialogResult
.fromResultData(resultData);
if (selection != null) {
mCallbacks.onUiSelection(selection);
} else {
Slog.i(TAG, "No selection found in UI result");
}
break;
case UserSelectionDialogResult.RESULT_CODE_DIALOG_USER_CANCELED:
mCallbacks.onUiCancellation(/* isUserCancellation= */ true);
break;
case UserSelectionDialogResult.RESULT_CODE_CANCELED_AND_LAUNCHED_SETTINGS:
mCallbacks.onUiCancellation(/* isUserCancellation= */ false);
break;
case UserSelectionDialogResult.RESULT_CODE_DATA_PARSING_FAILURE:
mCallbacks.onUiSelectorInvocationFailure();
break;
default:
Slog.i(TAG, "Unknown error code returned from the UI");
mCallbacks.onUiSelectorInvocationFailure();
break;
}
}
@@ -80,6 +90,9 @@ public class CredentialManagerUi {
void onUiSelection(UserSelectionDialogResult selection);
/** Called when the UI is canceled without a successful provider result. */
void onUiCancellation(boolean isUserCancellation);
/** Called when the selector UI fails to come up (mostly due to parsing issue today). */
void onUiSelectorInvocationFailure();
}
public CredentialManagerUi(Context context, int userId,
CredentialManagerUiCallback callbacks) {

View File

@@ -146,6 +146,12 @@ public final class GetRequestSession extends RequestSession<GetCredentialRequest
}
}
@Override
public void onUiSelectorInvocationFailure() {
respondToClientWithErrorAndFinish(GetCredentialException.TYPE_NO_CREDENTIAL,
"No credentials to show on the selector.");
}
@Override
public void onProviderStatusChanged(ProviderSession.Status status,
ComponentName componentName) {

View File

@@ -165,7 +165,16 @@ public final class ProviderCreateSession extends ProviderSession<
private void onUpdateResponse(BeginCreateCredentialResponse response) {
Log.i(TAG, "updateResponse with save entries");
mProviderResponse = response;
updateStatusAndInvokeCallback(Status.SAVE_ENTRIES_RECEIVED);
if (isEmptyResponse(response)) {
updateStatusAndInvokeCallback(Status.EMPTY_RESPONSE);
} else {
updateStatusAndInvokeCallback(Status.SAVE_ENTRIES_RECEIVED);
}
}
private boolean isEmptyResponse(BeginCreateCredentialResponse response) {
return (response.getCreateEntries() == null || response.getCreateEntries().isEmpty())
&& response.getRemoteCreateEntry() == null;
}
@Override
@@ -280,7 +289,7 @@ public final class ProviderCreateSession extends ProviderSession<
ProviderPendingIntentResponse pendingIntentResponse) {
if (pendingIntentResponse == null) {
Log.i(TAG, "pendingIntentResponse is null");
return new CreateCredentialException(CreateCredentialException.TYPE_NO_CREDENTIAL);
return new CreateCredentialException(CreateCredentialException.TYPE_NO_CREATE_OPTIONS);
}
if (PendingIntentResultHandler.isValidResponse(pendingIntentResponse)) {
CreateCredentialException exception = PendingIntentResultHandler
@@ -292,7 +301,7 @@ public final class ProviderCreateSession extends ProviderSession<
} else if (PendingIntentResultHandler.isCancelledResponse(pendingIntentResponse)) {
return new CreateCredentialException(CreateCredentialException.TYPE_USER_CANCELED);
} else {
return new CreateCredentialException(CreateCredentialException.TYPE_NO_CREDENTIAL);
return new CreateCredentialException(CreateCredentialException.TYPE_NO_CREATE_OPTIONS);
}
return null;
}

View File

@@ -101,7 +101,6 @@ public final class ProviderGetSession extends ProviderSession<BeginGetCredential
Log.i(TAG, "Unable to create provider session");
return null;
}
private static BeginGetCredentialRequest constructQueryPhaseRequest(
android.credentials.GetCredentialRequest filteredRequest,
CallingAppInfo callingAppInfo,
@@ -433,7 +432,22 @@ public final class ProviderGetSession extends ProviderSession<BeginGetCredential
/** Updates the response being maintained in state by this provider session. */
private void onUpdateResponse(BeginGetCredentialResponse response) {
mProviderResponse = response;
updateStatusAndInvokeCallback(Status.CREDENTIALS_RECEIVED);
if (isEmptyResponse(response)) {
updateStatusAndInvokeCallback(Status.EMPTY_RESPONSE);
} else {
updateStatusAndInvokeCallback(Status.CREDENTIALS_RECEIVED);
}
}
private boolean isEmptyResponse(BeginGetCredentialResponse response) {
if ((response.getCredentialEntries() == null || response.getCredentialEntries().isEmpty())
&& (response.getAuthenticationActions() == null || response
.getAuthenticationActions().isEmpty())
&& (response.getActions() == null || response.getActions().isEmpty())
&& response.getRemoteCredentialEntry() == null) {
return true;
}
return false;
}
private void onUpdateEmptyResponse() {

View File

@@ -135,7 +135,7 @@ public abstract class ProviderSession<T, R>
PENDING_INTENT_INVOKED,
CREDENTIAL_RECEIVED_FROM_SELECTION,
SAVE_ENTRIES_RECEIVED, CANCELED,
NO_CREDENTIALS, COMPLETE
NO_CREDENTIALS, EMPTY_RESPONSE, COMPLETE
}
/** Converts exception to a provider session status. */

View File

@@ -144,6 +144,11 @@ abstract class RequestSession<T, U> implements CredentialManagerUi.CredentialMan
finishSession(/*propagateCancellation=*/false);
}
@Override
public void onUiSelectorInvocationFailure() {
Log.i(TAG, "onUiSelectorInvocationFailure");
}
protected void finishSession(boolean propagateCancellation) {
Log.i(TAG, "finishing session");
if (propagateCancellation) {