Merge "Allow retainning entries across selections"

This commit is contained in:
TreeHugger Robot
2023-02-13 22:02:10 +00:00
committed by Android (Google) Code Review
4 changed files with 445 additions and 236 deletions

View File

@@ -19,6 +19,7 @@ package com.android.server.credentials;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.credentials.CreateCredentialException;
@@ -56,14 +57,11 @@ public final class ProviderCreateSession extends ProviderSession<
// Key to be used as an entry key for a remote entry
private static final String REMOTE_ENTRY_KEY = "remote_entry_key";
@NonNull
private final Map<String, CreateEntry> mUiSaveEntries = new HashMap<>();
/** The complete request to be used in the second round. */
private final CreateCredentialRequest mCompleteRequest;
private CreateCredentialException mProviderException;
@Nullable protected Pair<String, CreateEntry> mUiRemoteEntry;
private final ProviderResponseDataHandler mProviderResponseDataHandler;
/** Creates a new provider session to be used by the request session. */
@Nullable public static ProviderCreateSession createNewSession(
@@ -88,7 +86,8 @@ public final class ProviderCreateSession extends ProviderSession<
createRequestSession.mClientAppInfo,
createRequestSession
.mClientRequest.alwaysSendAppInfoToProvider()),
providerCreateRequest
providerCreateRequest,
createRequestSession.mHybridService
);
}
Log.i(TAG, "Unable to create provider session");
@@ -131,23 +130,20 @@ public final class ProviderCreateSession extends ProviderSession<
@UserIdInt int userId,
@NonNull RemoteCredentialService remoteCredentialService,
@NonNull BeginCreateCredentialRequest beginCreateRequest,
@NonNull CreateCredentialRequest completeCreateRequest) {
@NonNull CreateCredentialRequest completeCreateRequest,
String hybridService) {
super(context, info, beginCreateRequest, callbacks, userId,
remoteCredentialService);
mCompleteRequest = completeCreateRequest;
setStatus(Status.PENDING);
}
/** Returns the save entry maintained in state by this provider session. */
public CreateEntry getUiSaveEntry(String entryId) {
return mUiSaveEntries.get(entryId);
mProviderResponseDataHandler = new ProviderResponseDataHandler(hybridService);
}
@Override
public void onProviderResponseSuccess(
@Nullable BeginCreateCredentialResponse response) {
Log.i(TAG, "in onProviderResponseSuccess");
onUpdateResponse(response);
onSetInitialRemoteResponse(response);
}
/** Called when the provider response resulted in a failure. */
@@ -171,21 +167,18 @@ public final class ProviderCreateSession extends ProviderSession<
}
}
private void onUpdateResponse(BeginCreateCredentialResponse response) {
Log.i(TAG, "updateResponse with save entries");
private void onSetInitialRemoteResponse(BeginCreateCredentialResponse response) {
Log.i(TAG, "onSetInitialRemoteResponse with save entries");
mProviderResponse = response;
if (isEmptyResponse(response)) {
mProviderResponseDataHandler.addResponseContent(response.getCreateEntries(),
response.getRemoteCreateEntry());
if (mProviderResponseDataHandler.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
@Nullable protected CreateCredentialProviderData prepareUiData()
throws IllegalArgumentException {
@@ -194,53 +187,37 @@ public final class ProviderCreateSession extends ProviderSession<
Log.i(TAG, "In prepareUiData not in uiInvokingStatus");
return null;
}
final BeginCreateCredentialResponse response = getProviderResponse();
if (response == null) {
Log.i(TAG, "In prepareUiData response null");
throw new IllegalStateException("Response must be in completion mode");
}
if (response.getCreateEntries() != null) {
if (mProviderResponse != null && !mProviderResponseDataHandler.isEmptyResponse()) {
Log.i(TAG, "In prepareUiData save entries not null");
return prepareUiProviderData(
prepareUiSaveEntries(response.getCreateEntries()),
prepareUiRemoteEntry(response.getRemoteCreateEntry()));
return mProviderResponseDataHandler.toCreateCredentialProviderData();
}
return null;
}
private Entry prepareUiRemoteEntry(CreateEntry remoteCreateEntry) {
if (remoteCreateEntry == null) {
return null;
}
String entryId = generateUniqueId();
Entry remoteEntry = new Entry(REMOTE_ENTRY_KEY, entryId, remoteCreateEntry.getSlice(),
setUpFillInIntent());
mUiRemoteEntry = new Pair<>(entryId, remoteCreateEntry);
return remoteEntry;
}
@Override
public void onUiEntrySelected(String entryType, String entryKey,
ProviderPendingIntentResponse providerPendingIntentResponse) {
switch (entryType) {
case SAVE_ENTRY_KEY:
if (mUiSaveEntries.containsKey(entryKey)) {
onSaveEntrySelected(providerPendingIntentResponse);
} else {
if (mProviderResponseDataHandler.getCreateEntry(entryKey) == null) {
Log.i(TAG, "Unexpected save entry key");
invokeCallbackOnInternalInvalidState();
return;
}
onCreateEntrySelected(providerPendingIntentResponse);
break;
case REMOTE_ENTRY_KEY:
if (mUiRemoteEntry.first.equals(entryKey)) {
onRemoteEntrySelected(providerPendingIntentResponse);
} else {
if (mProviderResponseDataHandler.getRemoteEntry(entryKey) == null) {
Log.i(TAG, "Unexpected remote entry key");
invokeCallbackOnInternalInvalidState();
return;
}
onRemoteEntrySelected(providerPendingIntentResponse);
break;
default:
Log.i(TAG, "Unsupported entry type selected");
invokeCallbackOnInternalInvalidState();
}
}
@@ -252,21 +229,6 @@ public final class ProviderCreateSession extends ProviderSession<
}
}
private List<Entry> prepareUiSaveEntries(@NonNull List<CreateEntry> saveEntries) {
Log.i(TAG, "in populateUiSaveEntries");
List<Entry> uiSaveEntries = new ArrayList<>();
// Populate the save entries
for (CreateEntry createEntry : saveEntries) {
String entryId = generateUniqueId();
mUiSaveEntries.put(entryId, createEntry);
Log.i(TAG, "in prepareUiProviderData creating ui entry with id " + entryId);
uiSaveEntries.add(new Entry(SAVE_ENTRY_KEY, entryId, createEntry.getSlice(),
setUpFillInIntent()));
}
return uiSaveEntries;
}
private Intent setUpFillInIntent() {
Intent intent = new Intent();
intent.putExtra(CredentialProviderService.EXTRA_CREATE_CREDENTIAL_REQUEST,
@@ -274,16 +236,7 @@ public final class ProviderCreateSession extends ProviderSession<
return intent;
}
private CreateCredentialProviderData prepareUiProviderData(List<Entry> saveEntries,
Entry remoteEntry) {
return new CreateCredentialProviderData.Builder(
mComponentName.flattenToString())
.setSaveEntries(saveEntries)
.setRemoteEntry(remoteEntry)
.build();
}
private void onSaveEntrySelected(ProviderPendingIntentResponse pendingIntentResponse) {
private void onCreateEntrySelected(ProviderPendingIntentResponse pendingIntentResponse) {
CreateCredentialException exception = maybeGetPendingIntentException(
pendingIntentResponse);
if (exception != null) {
@@ -297,7 +250,6 @@ public final class ProviderCreateSession extends ProviderSession<
pendingIntentResponse.getResultData());
if (credentialResponse != null) {
mCallbacks.onFinalResponseReceived(mComponentName, credentialResponse);
return;
} else {
Log.i(TAG, "onSaveEntrySelected - no response or error found in pending "
+ "intent response");
@@ -305,6 +257,12 @@ public final class ProviderCreateSession extends ProviderSession<
}
}
private void onRemoteEntrySelected(ProviderPendingIntentResponse pendingIntentResponse) {
// Response from remote entry should be dealt with similar to a response from a
// create entry
onCreateEntrySelected(pendingIntentResponse);
}
@Nullable
private CreateCredentialException maybeGetPendingIntentException(
ProviderPendingIntentResponse pendingIntentResponse) {
@@ -336,4 +294,91 @@ public final class ProviderCreateSession extends ProviderSession<
CreateCredentialException.TYPE_UNKNOWN,
null);
}
private class ProviderResponseDataHandler {
private final ComponentName mExpectedRemoteEntryProviderService;
@NonNull
private final Map<String, Pair<CreateEntry, Entry>> mUiCreateEntries = new HashMap<>();
@Nullable private Pair<String, Pair<CreateEntry, Entry>> mUiRemoteEntry = null;
ProviderResponseDataHandler(String hybridService) {
mExpectedRemoteEntryProviderService = ComponentName.unflattenFromString(hybridService);
}
public void addResponseContent(List<CreateEntry> createEntries,
CreateEntry remoteEntry) {
createEntries.forEach(this::addCreateEntry);
setRemoteEntry(remoteEntry);
}
public void addCreateEntry(CreateEntry createEntry) {
String id = generateUniqueId();
Entry entry = new Entry(SAVE_ENTRY_KEY,
id, createEntry.getSlice(), setUpFillInIntent());
mUiCreateEntries.put(id, new Pair<>(createEntry, entry));
}
public void setRemoteEntry(@Nullable CreateEntry remoteEntry) {
if (remoteEntry == null) {
mUiRemoteEntry = null;
return;
}
if (!mComponentName.equals(mExpectedRemoteEntryProviderService)) {
Log.i(TAG, "Remote entry being dropped as it is not from the service "
+ "configured by the OEM.");
return;
}
String id = generateUniqueId();
Entry entry = new Entry(REMOTE_ENTRY_KEY,
id, remoteEntry.getSlice(), setUpFillInIntent());
mUiRemoteEntry = new Pair<>(id, new Pair<>(remoteEntry, entry));
}
public CreateCredentialProviderData toCreateCredentialProviderData() {
return new CreateCredentialProviderData.Builder(
mComponentName.flattenToString())
.setSaveEntries(prepareUiCreateEntries())
.setRemoteEntry(prepareRemoteEntry())
.build();
}
private List<Entry> prepareUiCreateEntries() {
List<Entry> createEntries = new ArrayList<>();
for (String key : mUiCreateEntries.keySet()) {
createEntries.add(mUiCreateEntries.get(key).second);
}
return createEntries;
}
private Entry prepareRemoteEntry() {
if (mUiRemoteEntry == null || mUiRemoteEntry.first == null
|| mUiRemoteEntry.second == null) {
return null;
}
return mUiRemoteEntry.second.second;
}
private boolean isEmptyResponse() {
return mUiCreateEntries.isEmpty() && mUiRemoteEntry == null;
}
@Nullable
public CreateEntry getRemoteEntry(String entryKey) {
return mUiRemoteEntry == null || mUiRemoteEntry
.first == null || !mUiRemoteEntry.first.equals(entryKey)
|| mUiRemoteEntry.second == null
? null : mUiRemoteEntry.second.first;
}
@Nullable
public CreateEntry getCreateEntry(String entryKey) {
return mUiCreateEntries.get(entryKey) == null
? null : mUiCreateEntries.get(entryKey).first;
}
public boolean isEmptyResponse(BeginCreateCredentialResponse response) {
return (response.getCreateEntries() == null || response.getCreateEntries().isEmpty())
&& response.getRemoteCreateEntry() == null;
}
}
}

View File

@@ -19,6 +19,7 @@ package com.android.server.credentials;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.credentials.CredentialOption;
@@ -45,7 +46,7 @@ import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.Optional;
/**
* Central provider session that listens for provider callbacks, and maintains provider state.
@@ -69,14 +70,7 @@ public final class ProviderGetSession extends ProviderSession<BeginGetCredential
@NonNull
private final Map<String, CredentialOption> mBeginGetOptionToCredentialOptionMap;
@NonNull
private final Map<String, CredentialEntry> mUiCredentialEntries = new HashMap<>();
@NonNull
private final Map<String, Action> mUiActionsEntries = new HashMap<>();
@Nullable
private final Map<String, Action> mUiAuthenticationEntries = new HashMap<>();
@Nullable protected Pair<String, CredentialEntry> mUiRemoteEntry;
/** The complete request to be used in the second round. */
private final android.credentials.GetCredentialRequest mCompleteRequest;
@@ -84,6 +78,8 @@ public final class ProviderGetSession extends ProviderSession<BeginGetCredential
private GetCredentialException mProviderException;
private final ProviderResponseDataHandler mProviderResponseDataHandler;
/** Creates a new provider session to be used by the request session. */
@Nullable public static ProviderGetSession createNewSession(
Context context,
@@ -109,7 +105,8 @@ public final class ProviderGetSession extends ProviderSession<BeginGetCredential
beginGetOptionToCredentialOptionMap),
filteredRequest,
getRequestSession.mClientAppInfo,
beginGetOptionToCredentialOptionMap
beginGetOptionToCredentialOptionMap,
getRequestSession.mHybridService
);
}
Log.i(TAG, "Unable to create provider session");
@@ -137,7 +134,7 @@ public final class ProviderGetSession extends ProviderSession<BeginGetCredential
}
@Nullable
protected static android.credentials.GetCredentialRequest filterOptions(
private static android.credentials.GetCredentialRequest filterOptions(
List<String> providerCapabilities,
android.credentials.GetCredentialRequest clientRequest
) {
@@ -169,19 +166,22 @@ public final class ProviderGetSession extends ProviderSession<BeginGetCredential
BeginGetCredentialRequest beginGetRequest,
android.credentials.GetCredentialRequest completeGetRequest,
CallingAppInfo callingAppInfo,
Map<String, CredentialOption> beginGetOptionToCredentialOptionMap) {
Map<String, CredentialOption> beginGetOptionToCredentialOptionMap,
String hybridService) {
super(context, info, beginGetRequest, callbacks, userId, remoteCredentialService);
mCompleteRequest = completeGetRequest;
mCallingAppInfo = callingAppInfo;
setStatus(Status.PENDING);
mBeginGetOptionToCredentialOptionMap = new HashMap<>(beginGetOptionToCredentialOptionMap);
mProviderResponseDataHandler = new ProviderResponseDataHandler(
ComponentName.unflattenFromString(hybridService));
}
/** Called when the provider response has been updated by an external source. */
@Override // Callback from the remote provider
public void onProviderResponseSuccess(@Nullable BeginGetCredentialResponse response) {
Log.i(TAG, "in onProviderResponseSuccess");
onUpdateResponse(response);
onSetInitialRemoteResponse(response);
}
/** Called when the provider response resulted in a failure. */
@@ -207,18 +207,20 @@ public final class ProviderGetSession extends ProviderSession<BeginGetCredential
@Override // Selection call from the request provider
protected void onUiEntrySelected(String entryType, String entryKey,
ProviderPendingIntentResponse providerPendingIntentResponse) {
Log.i(TAG, "onUiEntrySelected with entryKey: " + entryKey);
switch (entryType) {
case CREDENTIAL_ENTRY_KEY:
CredentialEntry credentialEntry = mUiCredentialEntries.get(entryKey);
CredentialEntry credentialEntry = mProviderResponseDataHandler
.getCredentialEntry(entryKey);
if (credentialEntry == null) {
Log.i(TAG, "Unexpected credential entry key");
invokeCallbackOnInternalInvalidState();
return;
}
onCredentialEntrySelected(credentialEntry, providerPendingIntentResponse);
onCredentialEntrySelected(providerPendingIntentResponse);
break;
case ACTION_ENTRY_KEY:
Action actionEntry = mUiActionsEntries.get(entryKey);
Action actionEntry = mProviderResponseDataHandler.getActionEntry(entryKey);
if (actionEntry == null) {
Log.i(TAG, "Unexpected action entry key");
invokeCallbackOnInternalInvalidState();
@@ -227,16 +229,29 @@ public final class ProviderGetSession extends ProviderSession<BeginGetCredential
onActionEntrySelected(providerPendingIntentResponse);
break;
case AUTHENTICATION_ACTION_ENTRY_KEY:
Action authenticationEntry = mUiAuthenticationEntries.get(entryKey);
Action authenticationEntry = mProviderResponseDataHandler
.getAuthenticationAction(entryKey);
if (authenticationEntry == null) {
Log.i(TAG, "Unexpected authenticationEntry key");
invokeCallbackOnInternalInvalidState();
return;
}
onAuthenticationEntrySelected(providerPendingIntentResponse);
boolean additionalContentReceived =
onAuthenticationEntrySelected(providerPendingIntentResponse);
if (additionalContentReceived) {
Log.i(TAG, "Additional content received - removing authentication entry");
mProviderResponseDataHandler.removeAuthenticationAction(entryKey);
} else {
Log.i(TAG, "Additional content not received");
mProviderResponseDataHandler
.updateAuthEntryWithNoCredentialsReceived(entryKey);
}
if (!mProviderResponseDataHandler.isEmptyResponse()) {
updateStatusAndInvokeCallback(Status.CREDENTIALS_RECEIVED);
}
break;
case REMOTE_ENTRY_KEY:
if (mUiRemoteEntry.first.equals(entryKey)) {
if (mProviderResponseDataHandler.getRemoteEntry(entryKey) != null) {
onRemoteEntrySelected(providerPendingIntentResponse);
} else {
Log.i(TAG, "Unexpected remote entry key");
@@ -245,6 +260,7 @@ public final class ProviderGetSession extends ProviderSession<BeginGetCredential
break;
default:
Log.i(TAG, "Unsupported entry type selected");
invokeCallbackOnInternalInvalidState();
}
}
@@ -264,61 +280,11 @@ public final class ProviderGetSession extends ProviderSession<BeginGetCredential
+ mComponentName.flattenToString());
return null;
}
if (mProviderResponse == null) {
Log.i(TAG, "In prepareUiData response null");
throw new IllegalStateException("Response must be in completion mode");
if (mProviderResponse != null && !mProviderResponseDataHandler.isEmptyResponse()) {
return mProviderResponseDataHandler.toGetCredentialProviderData();
}
return prepareUiProviderData(prepareUiActionEntries(
mProviderResponse.getActions()),
prepareUiCredentialEntries(mProviderResponse.getCredentialEntries()),
prepareUiAuthenticationEntries(mProviderResponse.getAuthenticationActions()),
prepareUiRemoteEntry(mProviderResponse.getRemoteCredentialEntry()));
}
private Entry prepareUiRemoteEntry(CredentialEntry remoteCredentialEntry) {
if (remoteCredentialEntry == null) {
return null;
}
String entryId = generateUniqueId();
Entry remoteEntry = new Entry(REMOTE_ENTRY_KEY, entryId, remoteCredentialEntry.getSlice(),
setUpFillInIntent(remoteCredentialEntry.getType()));
mUiRemoteEntry = new Pair<>(entryId, remoteCredentialEntry);
return remoteEntry;
}
private List<AuthenticationEntry> prepareUiAuthenticationEntries(
@NonNull List<Action> authenticationEntries) {
List<AuthenticationEntry> authenticationUiEntries = new ArrayList<>();
// TODO: properly construct entries when they should have the unlocked status.
for (Action authenticationAction : authenticationEntries) {
String entryId = generateUniqueId();
mUiAuthenticationEntries.put(entryId, authenticationAction);
authenticationUiEntries.add(new AuthenticationEntry(
AUTHENTICATION_ACTION_ENTRY_KEY, entryId,
authenticationAction.getSlice(),
AuthenticationEntry.STATUS_LOCKED,
setUpFillInIntentForAuthentication()));
}
return authenticationUiEntries;
}
private List<Entry> prepareUiCredentialEntries(@NonNull
List<CredentialEntry> credentialEntries) {
Log.i(TAG, "in prepareUiProviderDataWithCredentials");
List<Entry> credentialUiEntries = new ArrayList<>();
// Populate the credential entries
for (CredentialEntry credentialEntry : credentialEntries) {
String entryId = generateUniqueId();
mUiCredentialEntries.put(entryId, credentialEntry);
Log.i(TAG, "in prepareUiProviderData creating ui entry with id " + entryId);
credentialUiEntries.add(new Entry(CREDENTIAL_ENTRY_KEY, entryId,
credentialEntry.getSlice(),
/*fillInIntent=*/setUpFillInIntent(credentialEntry
.getBeginGetCredentialOption().getId())));
}
return credentialUiEntries;
Log.i(TAG, "In prepareUiData response null");
return null;
}
private Intent setUpFillInIntent(@NonNull String id) {
@@ -335,68 +301,47 @@ public final class ProviderGetSession extends ProviderSession<BeginGetCredential
mCallingAppInfo, mBeginGetOptionToCredentialOptionMap.get(id)));
}
private Intent setUpFillInIntentForAuthentication() {
private Intent setUpFillInIntentWithQueryRequest() {
Intent intent = new Intent();
intent.putExtra(
CredentialProviderService
.EXTRA_BEGIN_GET_CREDENTIAL_REQUEST,
intent.putExtra(CredentialProviderService.EXTRA_BEGIN_GET_CREDENTIAL_REQUEST,
mProviderRequest);
return intent;
}
private List<Entry> prepareUiActionEntries(@Nullable List<Action> actions) {
List<Entry> actionEntries = new ArrayList<>();
for (Action action : actions) {
String entryId = UUID.randomUUID().toString();
mUiActionsEntries.put(entryId, action);
// TODO : Remove conversion of string to int after change in Entry class
actionEntries.add(new Entry(ACTION_ENTRY_KEY, entryId, action.getSlice()));
}
return actionEntries;
}
private GetCredentialProviderData prepareUiProviderData(List<Entry> actionEntries,
List<Entry> credentialEntries, List<AuthenticationEntry> authenticationActionEntries,
Entry remoteEntry) {
return new GetCredentialProviderData.Builder(
mComponentName.flattenToString()).setActionChips(actionEntries)
.setCredentialEntries(credentialEntries)
.setAuthenticationEntries(authenticationActionEntries)
.setRemoteEntry(remoteEntry)
.build();
}
private void onCredentialEntrySelected(CredentialEntry credentialEntry,
private void onRemoteEntrySelected(
ProviderPendingIntentResponse providerPendingIntentResponse) {
if (providerPendingIntentResponse != null) {
// Check if pending intent has an error
GetCredentialException exception = maybeGetPendingIntentException(
providerPendingIntentResponse);
if (exception != null) {
invokeCallbackWithError(exception.getType(),
exception.getMessage());
return;
}
onCredentialEntrySelected(providerPendingIntentResponse);
}
// Check if pending intent has a credential
GetCredentialResponse getCredentialResponse = PendingIntentResultHandler
.extractGetCredentialResponse(
providerPendingIntentResponse.getResultData());
if (getCredentialResponse != null) {
mCallbacks.onFinalResponseReceived(mComponentName,
getCredentialResponse);
return;
}
Log.i(TAG, "Pending intent response contains no credential, or error");
private void onCredentialEntrySelected(
ProviderPendingIntentResponse providerPendingIntentResponse) {
if (providerPendingIntentResponse == null) {
invokeCallbackOnInternalInvalidState();
return;
}
Log.i(TAG, "CredentialEntry does not have a credential or a pending intent result");
// Check if pending intent has an error
GetCredentialException exception = maybeGetPendingIntentException(
providerPendingIntentResponse);
if (exception != null) {
invokeCallbackWithError(exception.getType(), exception.getMessage());
return;
}
// Check if pending intent has a credential response
GetCredentialResponse getCredentialResponse = PendingIntentResultHandler
.extractGetCredentialResponse(
providerPendingIntentResponse.getResultData());
if (getCredentialResponse != null) {
mCallbacks.onFinalResponseReceived(mComponentName,
getCredentialResponse);
return;
}
Log.i(TAG, "Pending intent response contains no credential, or error");
invokeCallbackOnInternalInvalidState();
}
@Nullable
protected GetCredentialException maybeGetPendingIntentException(
private GetCredentialException maybeGetPendingIntentException(
ProviderPendingIntentResponse pendingIntentResponse) {
if (pendingIntentResponse == null) {
Log.i(TAG, "pendingIntentResponse is null");
@@ -417,13 +362,19 @@ public final class ProviderGetSession extends ProviderSession<BeginGetCredential
return null;
}
private void onAuthenticationEntrySelected(
/**
* Returns true if either an exception or a response is retrieved from the result.
* Returns false if the response is not set at all, or set to null, or empty.
*/
private boolean onAuthenticationEntrySelected(
@Nullable ProviderPendingIntentResponse providerPendingIntentResponse) {
//TODO: Other provider intent statuses
Log.i(TAG, "onAuthenticationEntrySelected");
// Authentication entry is expected to have a BeginGetCredentialResponse instance. If it
// does not have it, we remove the authentication entry and do not add any more content.
if (providerPendingIntentResponse == null) {
Log.i(TAG, "providerPendingIntentResponse is null");
onUpdateEmptyResponse();
return;
// Nothing received. This is equivalent to no content received.
return false;
}
GetCredentialException exception = maybeGetPendingIntentException(
@@ -431,51 +382,69 @@ public final class ProviderGetSession extends ProviderSession<BeginGetCredential
if (exception != null) {
invokeCallbackWithError(exception.getType(),
exception.getMessage());
return;
// Additional content received is in the form of an exception which ends the flow.
return true;
}
// Check if pending intent has the content
BeginGetCredentialResponse content = PendingIntentResultHandler
// Check if pending intent has the response. If yes, remove this auth entry and
// replace it with the response content received.
BeginGetCredentialResponse response = PendingIntentResultHandler
.extractResponseContent(providerPendingIntentResponse
.getResultData());
if (content != null) {
onUpdateResponse(content);
if (response != null && !mProviderResponseDataHandler.isEmptyResponse(response)) {
addToInitialRemoteResponse(response);
// Additional content received is in the form of new response content.
return true;
}
// No response or exception found.
return false;
}
private void addToInitialRemoteResponse(BeginGetCredentialResponse content) {
if (content == null) {
return;
}
mProviderResponseDataHandler.addResponseContent(
content.getCredentialEntries(),
content.getActions(),
content.getAuthenticationActions(),
content.getRemoteCredentialEntry()
);
}
/** Returns true if either an exception or a response is found. */
private void onActionEntrySelected(ProviderPendingIntentResponse
providerPendingIntentResponse) {
// Action entry is expected to either contain the final GetCredentialResponse, or it is
// also acceptable if it does not contain anything. In the second case, we re-show this
// action on the UI.
if (providerPendingIntentResponse == null) {
Log.i(TAG, "providerPendingIntentResponse is null");
return;
}
Log.i(TAG, "No error or respond found in pending intent response");
onUpdateEmptyResponse();
}
private void onActionEntrySelected(ProviderPendingIntentResponse
providerPendingIntentResponse) {
//TODO: Implement if any result expected after an action
GetCredentialException exception = maybeGetPendingIntentException(
providerPendingIntentResponse);
if (exception != null) {
invokeCallbackWithError(exception.getType(), exception.getMessage());
}
GetCredentialResponse response = PendingIntentResultHandler
.extractGetCredentialResponse(
providerPendingIntentResponse.getResultData());
if (response != null) {
mCallbacks.onFinalResponseReceived(mComponentName, response);
}
}
/** Updates the response being maintained in state by this provider session. */
private void onUpdateResponse(BeginGetCredentialResponse response) {
private void onSetInitialRemoteResponse(BeginGetCredentialResponse response) {
mProviderResponse = response;
if (isEmptyResponse(response)) {
addToInitialRemoteResponse(response);
if (mProviderResponseDataHandler.isEmptyResponse(response)) {
updateStatusAndInvokeCallback(Status.EMPTY_RESPONSE);
} else {
updateStatusAndInvokeCallback(Status.CREDENTIALS_RECEIVED);
return;
}
}
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() {
updateStatusAndInvokeCallback(Status.NO_CREDENTIALS);
updateStatusAndInvokeCallback(Status.CREDENTIALS_RECEIVED);
}
/**
@@ -486,4 +455,201 @@ public final class ProviderGetSession extends ProviderSession<BeginGetCredential
mCallbacks.onFinalErrorReceived(mComponentName,
GetCredentialException.TYPE_UNKNOWN, null);
}
private class ProviderResponseDataHandler {
private final ComponentName mExpectedRemoteEntryProviderService;
@NonNull
private final Map<String, Pair<CredentialEntry, Entry>> mUiCredentialEntries =
new HashMap<>();
@NonNull
private final Map<String, Pair<Action, Entry>> mUiActionsEntries = new HashMap<>();
@Nullable
private final Map<String, Pair<Action, AuthenticationEntry>> mUiAuthenticationEntries =
new HashMap<>();
@Nullable private Pair<String, Pair<CredentialEntry, Entry>> mUiRemoteEntry = null;
ProviderResponseDataHandler(ComponentName expectedRemoteEntryProviderService) {
mExpectedRemoteEntryProviderService = expectedRemoteEntryProviderService;
}
public void addResponseContent(List<CredentialEntry> credentialEntries,
List<Action> actions, List<Action> authenticationActions,
CredentialEntry remoteEntry) {
credentialEntries.forEach(this::addCredentialEntry);
actions.forEach(this::addAction);
authenticationActions.forEach(
authenticationAction -> addAuthenticationAction(authenticationAction,
AuthenticationEntry.STATUS_LOCKED));
setRemoteEntry(remoteEntry);
}
public void addCredentialEntry(CredentialEntry credentialEntry) {
String id = generateUniqueId();
Entry entry = new Entry(CREDENTIAL_ENTRY_KEY,
id, credentialEntry.getSlice(),
setUpFillInIntent(credentialEntry
.getBeginGetCredentialOption().getId()));
mUiCredentialEntries.put(id, new Pair<>(credentialEntry, entry));
}
public void addAction(Action action) {
String id = generateUniqueId();
Entry entry = new Entry(ACTION_ENTRY_KEY,
id, action.getSlice(),
setUpFillInIntentWithQueryRequest());
mUiActionsEntries.put(id, new Pair<>(action, entry));
}
public void addAuthenticationAction(Action authenticationAction,
@AuthenticationEntry.Status int status) {
Log.i(TAG, "In addAuthenticationAction");
String id = generateUniqueId();
Log.i(TAG, "In addAuthenticationAction, id : " + id);
AuthenticationEntry entry = new AuthenticationEntry(
AUTHENTICATION_ACTION_ENTRY_KEY,
id, authenticationAction.getSlice(),
status,
setUpFillInIntentWithQueryRequest());
mUiAuthenticationEntries.put(id, new Pair<>(authenticationAction, entry));
}
public void removeAuthenticationAction(String id) {
mUiAuthenticationEntries.remove(id);
}
public void setRemoteEntry(@Nullable CredentialEntry remoteEntry) {
if (remoteEntry == null) {
return;
}
if (!mComponentName.equals(mExpectedRemoteEntryProviderService)) {
Log.i(TAG, "Remote entry being dropped as it is not from the service "
+ "configured by the OEM.");
return;
}
String id = generateUniqueId();
Entry entry = new Entry(REMOTE_ENTRY_KEY,
id, remoteEntry.getSlice(), setUpFillInIntent(
remoteEntry.getBeginGetCredentialOption().getId()));
mUiRemoteEntry = new Pair<>(generateUniqueId(), new Pair<>(remoteEntry, entry));
}
public GetCredentialProviderData toGetCredentialProviderData() {
return new GetCredentialProviderData.Builder(
mComponentName.flattenToString()).setActionChips(prepareActionEntries())
.setCredentialEntries(prepareCredentialEntries())
.setAuthenticationEntries(prepareAuthenticationEntries())
.setRemoteEntry(prepareRemoteEntry())
.build();
}
private List<Entry> prepareActionEntries() {
List<Entry> actionEntries = new ArrayList<>();
for (String key : mUiActionsEntries.keySet()) {
actionEntries.add(mUiActionsEntries.get(key).second);
}
return actionEntries;
}
private List<AuthenticationEntry> prepareAuthenticationEntries() {
List<AuthenticationEntry> authEntries = new ArrayList<>();
for (String key : mUiAuthenticationEntries.keySet()) {
authEntries.add(mUiAuthenticationEntries.get(key).second);
}
return authEntries;
}
private List<Entry> prepareCredentialEntries() {
List<Entry> credEntries = new ArrayList<>();
for (String key : mUiCredentialEntries.keySet()) {
credEntries.add(mUiCredentialEntries.get(key).second);
}
return credEntries;
}
private Entry prepareRemoteEntry() {
if (mUiRemoteEntry == null || mUiRemoteEntry.first == null
|| mUiRemoteEntry.second == null) {
return null;
}
return mUiRemoteEntry.second.second;
}
private boolean isEmptyResponse() {
return mUiCredentialEntries.isEmpty() && mUiActionsEntries.isEmpty()
&& mUiAuthenticationEntries.isEmpty() && mUiRemoteEntry == null;
}
private boolean isEmptyResponse(BeginGetCredentialResponse response) {
return response.getCredentialEntries().isEmpty() && response.getActions().isEmpty()
&& response.getAuthenticationActions().isEmpty()
&& response.getRemoteCredentialEntry() == null;
}
@Nullable
public Action getAuthenticationAction(String entryKey) {
return mUiAuthenticationEntries.get(entryKey) == null ? null :
mUiAuthenticationEntries.get(entryKey).first;
}
@Nullable
public Action getActionEntry(String entryKey) {
return mUiActionsEntries.get(entryKey) == null
? null : mUiActionsEntries.get(entryKey).first;
}
@Nullable
public CredentialEntry getRemoteEntry(String entryKey) {
return mUiRemoteEntry.first.equals(entryKey) && mUiRemoteEntry.second != null
? mUiRemoteEntry.second.first : null;
}
@Nullable
public CredentialEntry getCredentialEntry(String entryKey) {
return mUiCredentialEntries.get(entryKey) == null
? null : mUiCredentialEntries.get(entryKey).first;
}
public void updateAuthEntryWithNoCredentialsReceived(String entryKey) {
updatePreviousMostRecentAuthEntry();
updateMostRecentAuthEntry(entryKey);
}
private void updateMostRecentAuthEntry(String entryKey) {
AuthenticationEntry previousAuthenticationEntry =
mUiAuthenticationEntries.get(entryKey).second;
Action previousAuthenticationAction = mUiAuthenticationEntries.get(entryKey).first;
mUiAuthenticationEntries.put(entryKey, new Pair<>(
previousAuthenticationAction,
copyAuthEntryAndChangeStatus(
previousAuthenticationEntry,
AuthenticationEntry.STATUS_UNLOCKED_BUT_EMPTY_MOST_RECENT)));
}
private void updatePreviousMostRecentAuthEntry() {
Optional<Map.Entry<String, Pair<Action, AuthenticationEntry>>>
previousMostRecentAuthEntry = mUiAuthenticationEntries
.entrySet().stream().filter(e -> e.getValue().second.getStatus()
== AuthenticationEntry.STATUS_UNLOCKED_BUT_EMPTY_MOST_RECENT)
.findFirst();
if (previousMostRecentAuthEntry.isEmpty()) {
Log.i(TAG, "In updatePreviousMostRecentAuthEntry - previous entry not found");
return;
}
String id = previousMostRecentAuthEntry.get().getKey();
mUiAuthenticationEntries.remove(id);
mUiAuthenticationEntries.put(id, new Pair<>(
previousMostRecentAuthEntry.get().getValue().first,
copyAuthEntryAndChangeStatus(
previousMostRecentAuthEntry.get().getValue().second,
AuthenticationEntry.STATUS_UNLOCKED_BUT_EMPTY_LESS_RECENT)));
}
private AuthenticationEntry copyAuthEntryAndChangeStatus(
AuthenticationEntry from, Integer toStatus) {
return new AuthenticationEntry(AUTHENTICATION_ACTION_ENTRY_KEY, from.getSubkey(),
from.getSlice(), toStatus,
from.getFrameworkExtrasIntent());
}
}
}

View File

@@ -66,8 +66,7 @@ public abstract class ProviderSession<T, R>
* on the credMan UI.
*/
public static boolean isUiInvokingStatus(Status status) {
return status == Status.CREDENTIALS_RECEIVED || status == Status.SAVE_ENTRIES_RECEIVED
|| status == Status.REQUIRES_AUTHENTICATION;
return status == Status.CREDENTIALS_RECEIVED || status == Status.SAVE_ENTRIES_RECEIVED;
}
/**
@@ -208,11 +207,6 @@ public abstract class ProviderSession<T, R>
}
}
protected void onRemoteEntrySelected(
ProviderPendingIntentResponse providerPendingIntentResponse) {
//TODO: Implement
}
/** Get the request to be sent to the provider. */
protected T getProviderRequest() {
return mProviderRequest;

View File

@@ -38,6 +38,7 @@ import android.service.credentials.CallingAppInfo;
import android.service.credentials.CredentialProviderInfo;
import android.util.Log;
import com.android.internal.R;
import com.android.internal.util.FrameworkStatsLog;
import com.android.server.credentials.metrics.CandidateProviderMetric;
import com.android.server.credentials.metrics.ChosenProviderMetric;
@@ -79,6 +80,7 @@ abstract class RequestSession<T, U> implements CredentialManagerUi.CredentialMan
protected final Map<String, ProviderSession> mProviders = new HashMap<>();
protected ChosenProviderMetric mChosenProviderMetric = new ChosenProviderMetric();
//TODO improve design to allow grouped metrics per request
protected final String mHybridService;
protected RequestSession(@NonNull Context context,
@UserIdInt int userId, int callingUid, @NonNull T clientRequest, U clientCallback,
@@ -97,6 +99,8 @@ abstract class RequestSession<T, U> implements CredentialManagerUi.CredentialMan
mRequestId = new Binder();
mCredentialManagerUi = new CredentialManagerUi(mContext,
mUserId, this);
mHybridService = context.getResources().getString(
R.string.config_defaultCredentialManagerHybridService);
}
public abstract ProviderSession initiateProviderSession(CredentialProviderInfo providerInfo,