Merge "Adding authentication entry and remote entry piping Test: Built & deployed locally Bug: 253155340"

This commit is contained in:
Reema Bajwa
2022-11-17 05:13:49 +00:00
committed by Android (Google) Code Review
13 changed files with 485 additions and 237 deletions

View File

@@ -17,7 +17,10 @@
package android.credentials.ui;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.PendingIntent;
import android.app.slice.Slice;
import android.content.Intent;
import android.net.Uri;
import android.os.Parcel;
import android.os.Parcelable;
@@ -85,6 +88,8 @@ public class Entry implements Parcelable {
@NonNull private final String mKey;
@NonNull private final String mSubkey;
@Nullable private PendingIntent mPendingIntent;
@Nullable private Intent mFrameworkExtrasIntent;
@NonNull
private final Slice mSlice;
@@ -100,14 +105,29 @@ public class Entry implements Parcelable {
AnnotationValidations.validate(NonNull.class, null, mSubkey);
mSlice = slice;
AnnotationValidations.validate(NonNull.class, null, mSlice);
mPendingIntent = in.readTypedObject(PendingIntent.CREATOR);
mFrameworkExtrasIntent = in.readTypedObject(Intent.CREATOR);
}
/** Constructor to be used for an entry that does not require further activities
* to be invoked when selected.
*/
public Entry(@NonNull String key, @NonNull String subkey, @NonNull Slice slice) {
mKey = key;
mSubkey = subkey;
mSlice = slice;
}
/** Constructor to be used for an entry that requires a pending intent to be invoked
* when clicked.
*/
public Entry(@NonNull String key, @NonNull String subkey, @NonNull Slice slice,
@NonNull PendingIntent pendingIntent, @Nullable Intent intent) {
this(key, subkey, slice);
mPendingIntent = pendingIntent;
mFrameworkExtrasIntent = intent;
}
/**
* Returns the identifier of this entry that's unique within the context of the CredentialManager
* request.
@@ -133,11 +153,23 @@ public class Entry implements Parcelable {
return mSlice;
}
@Nullable
public PendingIntent getPendingIntent() {
return mPendingIntent;
}
@Nullable
public Intent getFrameworkExtrasIntent() {
return mFrameworkExtrasIntent;
}
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeString8(mKey);
dest.writeString8(mSubkey);
mSlice.writeToParcel(dest, flags);
mPendingIntent.writeToParcel(dest, flags);
mFrameworkExtrasIntent.writeToParcel(dest, flags);
}
@Override

View File

@@ -0,0 +1,79 @@
/*
* 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 android.credentials.ui;
import android.annotation.Nullable;
import android.content.Intent;
import android.os.Parcel;
import android.os.Parcelable;
import androidx.annotation.NonNull;
/**
* Response from a provider's pending intent
*
* @hide
*/
public final class ProviderPendingIntentResponse implements Parcelable {
private final int mResultCode;
@Nullable
private final Intent mResultData;
public ProviderPendingIntentResponse(int resultCode, @Nullable Intent resultData) {
mResultCode = resultCode;
mResultData = resultData;
}
protected ProviderPendingIntentResponse(Parcel in) {
mResultCode = in.readInt();
mResultData = in.readTypedObject(Intent.CREATOR);
}
public static final Creator<ProviderPendingIntentResponse> CREATOR =
new Creator<ProviderPendingIntentResponse>() {
@Override
public ProviderPendingIntentResponse createFromParcel(Parcel in) {
return new ProviderPendingIntentResponse(in);
}
@Override
public ProviderPendingIntentResponse[] newArray(int size) {
return new ProviderPendingIntentResponse[size];
}
};
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeSerializable(mResultCode);
dest.writeTypedObject(mResultData, flags);
}
/** Returns the result code associated with this pending intent activity result. */
public int getResultCode() {
return mResultCode;
}
/** Returns the result data associated with this pending intent activity result. */
@NonNull public Intent getResultData() {
return mResultData;
}
}

View File

@@ -57,6 +57,7 @@ public class UserSelectionDialogResult extends BaseDialogResult implements Parce
@NonNull private final String mProviderId;
@NonNull private final String mEntryKey;
@NonNull private final String mEntrySubkey;
@Nullable private ProviderPendingIntentResponse mProviderPendingIntentResponse;
public UserSelectionDialogResult(
@NonNull IBinder requestToken, @NonNull String providerId,
@@ -67,6 +68,17 @@ public class UserSelectionDialogResult extends BaseDialogResult implements Parce
mEntrySubkey = entrySubkey;
}
public UserSelectionDialogResult(
@NonNull IBinder requestToken, @NonNull String providerId,
@NonNull String entryKey, @NonNull String entrySubkey,
@Nullable ProviderPendingIntentResponse providerPendingIntentResponse) {
super(requestToken);
mProviderId = providerId;
mEntryKey = entryKey;
mEntrySubkey = entrySubkey;
mProviderPendingIntentResponse = providerPendingIntentResponse;
}
/** Returns provider package name whose entry was selected by the user. */
@NonNull
public String getProviderId() {
@@ -85,6 +97,12 @@ public class UserSelectionDialogResult extends BaseDialogResult implements Parce
return mEntrySubkey;
}
/** Returns the pending intent response from the provider. */
@Nullable
public ProviderPendingIntentResponse getPendingIntentProviderResponse() {
return mProviderPendingIntentResponse;
}
protected UserSelectionDialogResult(@NonNull Parcel in) {
super(in);
String providerId = in.readString8();
@@ -97,6 +115,7 @@ public class UserSelectionDialogResult extends BaseDialogResult implements Parce
AnnotationValidations.validate(NonNull.class, null, mEntryKey);
mEntrySubkey = entrySubkey;
AnnotationValidations.validate(NonNull.class, null, mEntrySubkey);
mProviderPendingIntentResponse = in.readTypedObject(ProviderPendingIntentResponse.CREATOR);
}
@Override
@@ -105,6 +124,7 @@ public class UserSelectionDialogResult extends BaseDialogResult implements Parce
dest.writeString8(mProviderId);
dest.writeString8(mEntryKey);
dest.writeString8(mEntrySubkey);
dest.writeTypedObject(mProviderPendingIntentResponse, flags);
}
@Override

View File

@@ -43,8 +43,13 @@ import java.util.Objects;
public abstract class CredentialProviderService extends Service {
/** Extra to be used by provider to populate the credential when ending the activity started
* through the {@code pendingIntent} on the selected {@link SaveEntry}. **/
public static final String EXTRA_SAVE_CREDENTIAL =
"android.service.credentials.extra.SAVE_CREDENTIAL";
public static final String EXTRA_CREATE_CREDENTIAL_RESPONSE =
"android.service.credentials.extra.CREATE_CREDENTIAL_RESPONSE";
/** Extra to be used by provider to populate the {@link CredentialsDisplayContent} when
* an authentication action entry is selected. **/
public static final String EXTRA_GET_CREDENTIALS_DISPLAY_CONTENT =
"android.service.credentials.extra.GET_CREDENTIALS_DISPLAY_CONTENT";
/**
* Provider must read the value against this extra to receive the complete create credential
@@ -53,6 +58,10 @@ public abstract class CredentialProviderService extends Service {
public static final String EXTRA_CREATE_CREDENTIAL_REQUEST_PARAMS =
"android.service.credentials.extra.CREATE_CREDENTIAL_REQUEST_PARAMS";
/** Extra to be used by the provider when setting the credential result. */
public static final String EXTRA_GET_CREDENTIAL =
"android.service.credentials.extra.GET_CREDENTIAL";
private static final String TAG = "CredProviderService";
public static final String CAPABILITY_META_DATA_KEY = "android.credentials.capabilities";

View File

@@ -16,6 +16,7 @@
package com.android.credentialmanager
import android.content.ComponentName
import android.content.Context
import android.content.pm.PackageManager
import android.credentials.ui.Entry
@@ -146,9 +147,17 @@ class CreateFlowUtils {
): List<com.android.credentialmanager.createflow.EnabledProviderInfo> {
// TODO: get from the actual service info
val packageManager = context.packageManager
return providerDataList.map {
val componentName = ComponentName.unflattenFromString(it.providerFlattenedComponentName)
var packageName = componentName?.packageName
if (componentName == null) {
// TODO: Remove once test data is fixed
packageName = it.providerFlattenedComponentName
}
val pkgInfo = packageManager
.getPackageInfo(it.providerFlattenedComponentName,
.getPackageInfo(packageName!!,
PackageManager.PackageInfoFlags.of(0))
com.android.credentialmanager.createflow.EnabledProviderInfo(
// TODO: decide what to do when failed to load a provider icon

View File

@@ -18,12 +18,15 @@ package com.android.server.credentials;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.ComponentName;
import android.content.Context;
import android.credentials.CreateCredentialRequest;
import android.credentials.CreateCredentialResponse;
import android.credentials.CredentialManager;
import android.credentials.ICreateCredentialCallback;
import android.credentials.ui.ProviderData;
import android.credentials.ui.RequestInfo;
import android.os.RemoteException;
import android.service.credentials.CredentialProviderInfo;
import android.util.Log;
@@ -35,7 +38,8 @@ import java.util.ArrayList;
* provider(s) state maintained in {@link ProviderCreateSession}.
*/
public final class CreateRequestSession extends RequestSession<CreateCredentialRequest,
ICreateCredentialCallback> {
ICreateCredentialCallback>
implements ProviderSession.ProviderInternalCallback<CreateCredentialResponse> {
private static final String TAG = "CreateRequestSession";
CreateRequestSession(@NonNull Context context, int userId,
@@ -72,4 +76,29 @@ public final class CreateRequestSession extends RequestSession<CreateCredentialR
mRequestId, mClientRequest, mIsFirstUiTurn, mClientCallingPackage),
providerDataList));
}
private void respondToClientAndFinish(CreateCredentialResponse response) {
Log.i(TAG, "respondToClientAndFinish");
try {
mClientCallback.onResponse(response);
} catch (RemoteException e) {
e.printStackTrace();
}
finishSession();
}
@Override
public void onProviderStatusChanged(ProviderSession.Status status,
ComponentName componentName) {
super.onProviderStatusChanged(status, componentName);
}
@Override
public void onFinalResponseReceived(ComponentName componentName,
CreateCredentialResponse response) {
Log.i(TAG, "onFinalCredentialReceived from: " + componentName.flattenToString());
if (response != null) {
respondToClientAndFinish(response);
}
}
}

View File

@@ -17,16 +17,14 @@
package com.android.server.credentials;
import android.annotation.Nullable;
import android.content.ComponentName;
import android.content.Context;
import android.credentials.Credential;
import android.credentials.GetCredentialRequest;
import android.credentials.GetCredentialResponse;
import android.credentials.IGetCredentialCallback;
import android.credentials.ui.ProviderData;
import android.credentials.ui.RequestInfo;
import android.credentials.ui.UserSelectionDialogResult;
import android.os.RemoteException;
import android.service.credentials.CredentialEntry;
import android.service.credentials.CredentialProviderInfo;
import android.util.Log;
@@ -37,7 +35,8 @@ import java.util.ArrayList;
* responses from providers, and the UX app, and updates the provider(S) state.
*/
public final class GetRequestSession extends RequestSession<GetCredentialRequest,
IGetCredentialCallback> {
IGetCredentialCallback>
implements ProviderSession.ProviderInternalCallback<GetCredentialResponse> {
private static final String TAG = "GetRequestSession";
public GetRequestSession(Context context, int userId,
@@ -67,23 +66,6 @@ public final class GetRequestSession extends RequestSession<GetCredentialRequest
return providerGetSession;
}
// TODO: Override for this method not needed once get selection logic is
// moved to ProviderGetSession
@Override
public void onUiSelection(UserSelectionDialogResult selection) {
String providerId = selection.getProviderId();
ProviderGetSession providerSession = (ProviderGetSession) mProviders.get(providerId);
if (providerSession != null) {
CredentialEntry credentialEntry = providerSession.getCredentialEntry(
selection.getEntrySubkey());
if (credentialEntry != null && credentialEntry.getCredential() != null) {
respondToClientAndFinish(credentialEntry.getCredential());
}
// TODO : Handle action chips and authentication selection
}
// TODO : finish session and respond to client if provider not found
}
@Override
protected void launchUiWithProviderData(ArrayList<ProviderData> providerDataList) {
mHandler.post(() -> mCredentialManagerUi.show(RequestInfo.newGetRequestInfo(
@@ -91,9 +73,26 @@ public final class GetRequestSession extends RequestSession<GetCredentialRequest
providerDataList));
}
private void respondToClientAndFinish(Credential credential) {
@Override // from provider session
public void onProviderStatusChanged(ProviderSession.Status status,
ComponentName componentName) {
super.onProviderStatusChanged(status, componentName);
}
@Override
public void onFinalResponseReceived(ComponentName componentName,
GetCredentialResponse response) {
Log.i(TAG, "onFinalCredentialReceived from: " + componentName.flattenToString());
if (response != null) {
respondToClientAndFinish(response);
}
}
private void respondToClientAndFinish(GetCredentialResponse response) {
Log.i(TAG, "respondToClientAndFinish");
try {
mClientCallback.onResponse(new GetCredentialResponse(credential));
mClientCallback.onResponse(response);
} catch (RemoteException e) {
e.printStackTrace();
}

View File

@@ -0,0 +1,69 @@
/*
* 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.app.Activity;
import android.content.Intent;
import android.credentials.CreateCredentialResponse;
import android.credentials.Credential;
import android.credentials.ui.ProviderPendingIntentResponse;
import android.service.credentials.CredentialProviderService;
import android.service.credentials.CredentialsDisplayContent;
/**
* Helper class for setting up pending intent, and extracting objects from it.
*
* @hide
*/
public class PendingIntentResultHandler {
/** Returns true if the result is successful and may contain result extras. */
public static boolean isSuccessfulResponse(
ProviderPendingIntentResponse pendingIntentResponse) {
//TODO: Differentiate based on extra_error in the resultData
return pendingIntentResponse.getResultCode() == Activity.RESULT_OK;
}
/** Extracts the {@link CredentialsDisplayContent} object added to the result data. */
public static CredentialsDisplayContent extractCredentialsDisplayContent(Intent resultData) {
if (resultData == null) {
return null;
}
return resultData.getParcelableExtra(
CredentialProviderService.EXTRA_GET_CREDENTIALS_DISPLAY_CONTENT,
CredentialsDisplayContent.class);
}
/** Extracts the {@link CreateCredentialResponse} object added to the result data. */
public static CreateCredentialResponse extractCreateCredentialResponse(Intent resultData) {
if (resultData == null) {
return null;
}
return resultData.getParcelableExtra(
CredentialProviderService.EXTRA_CREATE_CREDENTIAL_RESPONSE,
CreateCredentialResponse.class);
}
/** Extracts the {@link Credential} object added to the result data. */
public static Credential extractCredential(Intent resultData) {
if (resultData == null) {
return null;
}
return resultData.getParcelableExtra(
CredentialProviderService.EXTRA_GET_CREDENTIAL,
Credential.class);
}
}

View File

@@ -19,10 +19,12 @@ package com.android.server.credentials;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.app.PendingIntent;
import android.content.Context;
import android.credentials.Credential;
import android.content.Intent;
import android.credentials.ui.CreateCredentialProviderData;
import android.credentials.ui.Entry;
import android.credentials.ui.ProviderPendingIntentResponse;
import android.os.Bundle;
import android.service.credentials.CreateCredentialRequest;
import android.service.credentials.CreateCredentialResponse;
@@ -166,33 +168,28 @@ public final class ProviderCreateSession extends ProviderSession<
}
@Override
public void onProviderIntentResult(Bundle resultData) {
Credential credential = resultData.getParcelable(
CredentialProviderService.EXTRA_SAVE_CREDENTIAL,
Credential.class);
if (credential == null) {
Log.i(TAG, "Credential returned from intent is null");
return;
public void onUiEntrySelected(String entryType, String entryKey,
ProviderPendingIntentResponse providerPendingIntentResponse) {
switch (entryType) {
case SAVE_ENTRY_KEY:
if (mUiSaveEntries.containsKey(entryKey)) {
onSaveEntrySelected(providerPendingIntentResponse);
} else {
//TODO: Handle properly
Log.i(TAG, "Unexpected save entry key");
}
break;
case REMOTE_ENTRY_KEY:
if (mUiRemoteEntry.first.equals(entryKey)) {
onRemoteEntrySelected(providerPendingIntentResponse);
} else {
//TODO: Handle properly
Log.i(TAG, "Unexpected remote entry key");
}
break;
default:
Log.i(TAG, "Unsupported entry type selected");
}
updateFinalCredentialResponse(credential);
}
@Override
public void onUiEntrySelected(String entryType, String entryKey) {
if (entryType.equals(SAVE_ENTRY_KEY)) {
SaveEntry saveEntry = mUiSaveEntries.get(entryKey);
if (saveEntry == null) {
Log.i(TAG, "Save entry not found");
return;
}
// TODO: Uncomment when pending intent works
// onSaveEntrySelected(saveEntry);
}
}
@Override
public void onProviderIntentCancelled() {
//TODO (Implement)
}
private List<Entry> prepareUiSaveEntries(@NonNull List<SaveEntry> saveEntries) {
@@ -204,14 +201,17 @@ public final class ProviderCreateSession extends ProviderSession<
String entryId = generateEntryId();
mUiSaveEntries.put(entryId, saveEntry);
Log.i(TAG, "in prepareUiProviderData creating ui entry with id " + entryId);
uiSaveEntries.add(new Entry(SAVE_ENTRY_KEY, entryId, saveEntry.getSlice()));
uiSaveEntries.add(new Entry(SAVE_ENTRY_KEY, entryId, saveEntry.getSlice(),
saveEntry.getPendingIntent(), setUpFillInIntent(saveEntry.getPendingIntent())));
}
return uiSaveEntries;
}
private void updateFinalCredentialResponse(@NonNull Credential credential) {
mFinalCredentialResponse = credential;
updateStatusAndInvokeCallback(Status.CREDENTIAL_RECEIVED_FROM_INTENT);
private Intent setUpFillInIntent(PendingIntent pendingIntent) {
Intent intent = pendingIntent.getIntent();
intent.putExtra(CredentialProviderService.EXTRA_CREATE_CREDENTIAL_REQUEST_PARAMS,
mCompleteRequest.getData());
return intent;
}
private CreateCredentialProviderData prepareUiProviderData(List<Entry> saveEntries,
@@ -223,9 +223,20 @@ public final class ProviderCreateSession extends ProviderSession<
.build();
}
private void onSaveEntrySelected(SaveEntry saveEntry) {
mProviderIntentController.setupAndInvokePendingIntent(saveEntry.getPendingIntent(),
mProviderRequest);
setStatus(Status.PENDING_INTENT_INVOKED);
private void onSaveEntrySelected(ProviderPendingIntentResponse pendingIntentResponse) {
if (pendingIntentResponse == null) {
return;
//TODO: Handle failure if pending intent is null
}
if (PendingIntentResultHandler.isSuccessfulResponse(pendingIntentResponse)) {
android.credentials.CreateCredentialResponse credentialResponse =
PendingIntentResultHandler.extractCreateCredentialResponse(
pendingIntentResponse.getResultData());
if (credentialResponse != null) {
mCallbacks.onFinalResponseReceived(mComponentName, credentialResponse);
return;
}
}
//TODO: Handle failure case is pending intent response does not have a credential
}
}

View File

@@ -20,16 +20,20 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.content.Context;
import android.credentials.Credential;
import android.credentials.GetCredentialOption;
import android.credentials.GetCredentialResponse;
import android.credentials.ui.Entry;
import android.credentials.ui.GetCredentialProviderData;
import android.os.Bundle;
import android.credentials.ui.ProviderPendingIntentResponse;
import android.service.credentials.Action;
import android.service.credentials.CredentialEntry;
import android.service.credentials.CredentialProviderInfo;
import android.service.credentials.CredentialsDisplayContent;
import android.service.credentials.GetCredentialsRequest;
import android.service.credentials.GetCredentialsResponse;
import android.util.Log;
import android.util.Pair;
import android.util.Slog;
import java.util.ArrayList;
@@ -53,11 +57,17 @@ public final class ProviderGetSession extends ProviderSession<GetCredentialsRequ
// Key to be used as an entry key for a credential entry
private static final String CREDENTIAL_ENTRY_KEY = "credential_key";
// Key to be used as the entry key for an action entry
private static final String ACTION_ENTRY_KEY = "action_key";
// Key to be used as the entry key for the authentication entry
private static final String AUTHENTICATION_ACTION_ENTRY_KEY = "authentication_action_key";
@NonNull
private final Map<String, CredentialEntry> mUiCredentialEntries = new HashMap<>();
@NonNull
private final Map<String, Action> mUiActionsEntries = new HashMap<>();
private Action mAuthenticationAction = null;
@Nullable
private Pair<String, Action> mUiAuthenticationAction = null;
/** Creates a new provider session to be used by the request session. */
@Nullable public static ProviderGetSession createNewSession(
@@ -85,7 +95,8 @@ public final class ProviderGetSession extends ProviderSession<GetCredentialsRequ
List<GetCredentialOption> filteredOptions = new ArrayList<>();
for (GetCredentialOption option : clientRequest.getGetCredentialOptions()) {
if (providerCapabilities.contains(option.getType())) {
Log.i(TAG, "In createProviderRequest - capability found : " + option.getType());
Log.i(TAG, "In createProviderRequest - capability found : "
+ option.getType());
filteredOptions.add(option);
} else {
Log.i(TAG, "In createProviderRequest - capability not "
@@ -139,19 +150,47 @@ public final class ProviderGetSession extends ProviderSession<GetCredentialsRequ
}
}
@Override // Callback from the provider intent controller class
public void onProviderIntentResult(Bundle resultData) {
// TODO : Implement
}
@Override
public void onProviderIntentCancelled() {
// TODO : Implement
}
@Override // Selection call from the request provider
protected void onUiEntrySelected(String entryType, String entryId) {
// TODO: Implement
protected void onUiEntrySelected(String entryType, String entryKey,
ProviderPendingIntentResponse providerPendingIntentResponse) {
switch (entryType) {
case CREDENTIAL_ENTRY_KEY:
CredentialEntry credentialEntry = mUiCredentialEntries.get(entryKey);
if (credentialEntry == null) {
Log.i(TAG, "Credential entry not found");
//TODO: Handle properly
return;
}
onCredentialEntrySelected(credentialEntry, providerPendingIntentResponse);
break;
case ACTION_ENTRY_KEY:
Action actionEntry = mUiActionsEntries.get(entryKey);
if (actionEntry == null) {
Log.i(TAG, "Action entry not found");
//TODO: Handle properly
return;
}
onActionEntrySelected(providerPendingIntentResponse);
break;
case AUTHENTICATION_ACTION_ENTRY_KEY:
if (mUiAuthenticationAction.first.equals(entryKey)) {
onAuthenticationEntrySelected(providerPendingIntentResponse);
} else {
//TODO: Handle properly
Log.i(TAG, "Authentication entry not found");
}
break;
case REMOTE_ENTRY_KEY:
if (mUiRemoteEntry.first.equals(entryKey)) {
onRemoteEntrySelected(providerPendingIntentResponse);
} else {
//TODO: Handle properly
Log.i(TAG, "Remote entry not found");
}
break;
default:
Log.i(TAG, "Unsupported entry type selected");
}
}
@Override // Call from request session to data to be shown on the UI
@@ -162,32 +201,46 @@ public final class ProviderGetSession extends ProviderSession<GetCredentialsRequ
+ mComponentName.flattenToString());
return null;
}
GetCredentialsResponse response = getProviderResponse();
if (response == null) {
if (mProviderResponse == null) {
Log.i(TAG, "In prepareUiData response null");
throw new IllegalStateException("Response must be in completion mode");
}
if (response.getAuthenticationAction() != null) {
if (mProviderResponse.getAuthenticationAction() != null) {
Log.i(TAG, "In prepareUiData - top level authentication mode");
return prepareUiProviderData(null, null,
prepareUiAuthenticationActionEntry(response.getAuthenticationAction()),
prepareUiAuthenticationAction(mProviderResponse.getAuthenticationAction()),
/*remoteEntry=*/null);
}
if (response.getCredentialsDisplayContent() != null){
if (mProviderResponse.getCredentialsDisplayContent() != null) {
Log.i(TAG, "In prepareUiData displayContent not null");
return prepareUiProviderData(populateUiActionEntries(
response.getCredentialsDisplayContent().getActions()),
prepareUiCredentialEntries(response.getCredentialsDisplayContent()
return prepareUiProviderData(prepareUiActionEntries(
mProviderResponse.getCredentialsDisplayContent().getActions()),
prepareUiCredentialEntries(mProviderResponse.getCredentialsDisplayContent()
.getCredentialEntries()),
/*authenticationActionEntry=*/null, /*remoteEntry=*/null);
/*authenticationAction=*/null,
prepareUiRemoteEntry(mProviderResponse
.getCredentialsDisplayContent().getRemoteCredentialEntry()));
}
return null;
}
private Entry prepareUiAuthenticationActionEntry(@NonNull Action authenticationAction) {
private Entry prepareUiRemoteEntry(Action remoteCredentialEntry) {
if (remoteCredentialEntry == null) {
return null;
}
String entryId = generateEntryId();
mUiActionsEntries.put(entryId, authenticationAction);
return new Entry(ACTION_ENTRY_KEY, entryId, authenticationAction.getSlice());
Entry remoteEntry = new Entry(REMOTE_ENTRY_KEY, entryId, remoteCredentialEntry.getSlice());
mUiRemoteEntry = new Pair<>(entryId, remoteCredentialEntry);
return remoteEntry;
}
private Entry prepareUiAuthenticationAction(@NonNull Action authenticationAction) {
String entryId = generateEntryId();
Entry authEntry = new Entry(
AUTHENTICATION_ACTION_ENTRY_KEY, entryId, authenticationAction.getSlice(),
authenticationAction.getPendingIntent(), /*fillInIntent=*/null);
mUiAuthenticationAction = new Pair<>(entryId, authenticationAction);
return authEntry;
}
private List<Entry> prepareUiCredentialEntries(@NonNull
@@ -200,19 +253,28 @@ public final class ProviderGetSession extends ProviderSession<GetCredentialsRequ
String entryId = generateEntryId();
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()));
if (credentialEntry.getPendingIntent() != null) {
credentialUiEntries.add(new Entry(CREDENTIAL_ENTRY_KEY, entryId,
credentialEntry.getSlice(), credentialEntry.getPendingIntent(),
/*fillInIntent=*/null));
} else if (credentialEntry.getCredential() != null) {
credentialUiEntries.add(new Entry(CREDENTIAL_ENTRY_KEY, entryId,
credentialEntry.getSlice()));
} else {
Log.i(TAG, "No credential or pending intent. Should not happen.");
}
}
return credentialUiEntries;
}
private List<Entry> populateUiActionEntries(@Nullable List<Action> actions) {
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()));
actionEntries.add(new Entry(ACTION_ENTRY_KEY, entryId, action.getSlice(),
action.getPendingIntent(), /*fillInIntent=*/null));
}
return actionEntries;
}
@@ -224,16 +286,61 @@ public final class ProviderGetSession extends ProviderSession<GetCredentialsRequ
mComponentName.flattenToString()).setActionChips(actionEntries)
.setCredentialEntries(credentialEntries)
.setAuthenticationEntry(authenticationActionEntry)
.setRemoteEntry(remoteEntry)
.build();
}
private void onCredentialEntrySelected(CredentialEntry credentialEntry,
ProviderPendingIntentResponse providerPendingIntentResponse) {
if (credentialEntry.getCredential() != null) {
mCallbacks.onFinalResponseReceived(mComponentName, new GetCredentialResponse(
credentialEntry.getCredential()));
return;
} else if (providerPendingIntentResponse != null) {
if (PendingIntentResultHandler.isSuccessfulResponse(providerPendingIntentResponse)) {
Credential credential = PendingIntentResultHandler.extractCredential(
providerPendingIntentResponse.getResultData());
if (credential != null) {
mCallbacks.onFinalResponseReceived(mComponentName,
new GetCredentialResponse(credential));
return;
}
}
// TODO: Handle other pending intent statuses
}
Log.i(TAG, "CredentialEntry does not have a credential or a pending intent result");
// TODO: Propagate failure to client
}
private void onAuthenticationEntrySelected(
@Nullable ProviderPendingIntentResponse providerPendingIntentResponse) {
if (providerPendingIntentResponse != null) {
if (PendingIntentResultHandler.isSuccessfulResponse(providerPendingIntentResponse)) {
CredentialsDisplayContent content = PendingIntentResultHandler
.extractCredentialsDisplayContent(providerPendingIntentResponse
.getResultData());
if (content != null) {
onUpdateResponse(GetCredentialsResponse.createWithDisplayContent(content));
return;
}
}
//TODO: Other provider intent statuses
}
Log.i(TAG, "Display content not present in pending intent result");
// TODO: Propagate error to client
}
private void onActionEntrySelected(ProviderPendingIntentResponse
providerPendingIntentResponse) {
//TODO: Implement if any result expected after an action
}
/** Updates the response being maintained in state by this provider session. */
private void onUpdateResponse(GetCredentialsResponse response) {
mProviderResponse = response;
if (response.getAuthenticationAction() != null) {
Log.i(TAG , "updateResponse with authentication entry");
// TODO validate authentication action
mAuthenticationAction = response.getAuthenticationAction();
updateStatusAndInvokeCallback(Status.REQUIRES_AUTHENTICATION);
} else if (response.getCredentialsDisplayContent() != null) {
Log.i(TAG , "updateResponse with credentialEntries");

View File

@@ -1,117 +0,0 @@
/*
* 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.annotation.NonNull;
import android.annotation.SuppressLint;
import android.annotation.UserIdInt;
import android.app.Activity;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.IntentSender;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Parcel;
import android.os.ResultReceiver;
import android.service.credentials.CreateCredentialRequest;
import android.service.credentials.CredentialProviderService;
import android.util.Log;
/**
* Class that invokes providers' pending intents and listens to the responses.
*/
@SuppressLint("LongLogTag")
public class ProviderIntentController {
private static final String TAG = "ProviderIntentController";
/**
* Interface to be implemented by any class that wishes to get callbacks from the UI.
*/
public interface ProviderIntentControllerCallback {
/** Called when the user makes a selection. */
void onProviderIntentResult(Bundle resultData);
/** Called when the user cancels the UI. */
void onProviderIntentCancelled();
}
private final int mUserId;
private final Context mContext;
private final ProviderIntentControllerCallback mCallback;
private final ResultReceiver mResultReceiver = new ResultReceiver(
new Handler(Looper.getMainLooper())) {
@Override
protected void onReceiveResult(int resultCode, Bundle resultData) {
Log.i(TAG, "onReceiveResult in providerIntentController");
if (resultCode == Activity.RESULT_OK) {
Log.i(TAG, "onReceiveResult - ACTIVITYOK");
mCallback.onProviderIntentResult(resultData);
} else if (resultCode == Activity.RESULT_CANCELED) {
Log.i(TAG, "onReceiveResult - RESULTCANCELED");
mCallback.onProviderIntentCancelled();
}
// Drop unknown result
}
};
public ProviderIntentController(@UserIdInt int userId,
Context context,
ProviderIntentControllerCallback callback) {
mUserId = userId;
mContext = context;
mCallback = callback;
}
/** Sets up the request data and invokes the given pending intent. */
public void setupAndInvokePendingIntent(@NonNull PendingIntent pendingIntent,
CreateCredentialRequest request) {
Log.i(TAG, "in invokePendingIntent");
setupIntent(pendingIntent, request);
Log.i(TAG, "in invokePendingIntent receiver set up");
Log.i(TAG, "creator package: " + pendingIntent.getIntentSender()
.getCreatorPackage());
try {
mContext.startIntentSender(pendingIntent.getIntentSender(),
null, 0, 0, 0);
} catch (IntentSender.SendIntentException e) {
Log.i(TAG, "Error while invoking pending intent");
}
}
private void setupIntent(PendingIntent pendingIntent, CreateCredentialRequest request) {
pendingIntent.getIntent().putExtra(Intent.EXTRA_RESULT_RECEIVER,
toIpcFriendlyResultReceiver(mResultReceiver));
pendingIntent.getIntent().putExtra(
CredentialProviderService.EXTRA_CREATE_CREDENTIAL_REQUEST_PARAMS,
request.getData());
}
private <T extends ResultReceiver> ResultReceiver toIpcFriendlyResultReceiver(
T resultReceiver) {
final Parcel parcel = Parcel.obtain();
resultReceiver.writeToParcel(parcel, 0);
parcel.setDataPosition(0);
final ResultReceiver ipcFriendly = ResultReceiver.CREATOR.createFromParcel(parcel);
parcel.recycle();
return ipcFriendly;
}
}

View File

@@ -22,9 +22,11 @@ import android.content.ComponentName;
import android.content.Context;
import android.credentials.Credential;
import android.credentials.ui.ProviderData;
import android.os.Bundle;
import android.credentials.ui.ProviderPendingIntentResponse;
import android.service.credentials.Action;
import android.service.credentials.CredentialProviderException;
import android.service.credentials.CredentialProviderInfo;
import android.util.Pair;
import java.util.UUID;
@@ -33,10 +35,10 @@ import java.util.UUID;
* @param <T> The request to be sent to the provider
* @param <R> The response to be expected from the provider
*/
public abstract class ProviderSession<T, R> implements RemoteCredentialService.ProviderCallbacks<R>,
ProviderIntentController.ProviderIntentControllerCallback {
// Key to be used as the entry key for an action entry
protected static final String ACTION_ENTRY_KEY = "action_key";
public abstract class ProviderSession<T, R>
implements RemoteCredentialService.ProviderCallbacks<R> {
// Key to be used as an entry key for a remote entry
protected static final String REMOTE_ENTRY_KEY = "remote_entry_key";
@NonNull protected final Context mContext;
@NonNull protected final ComponentName mComponentName;
@@ -45,17 +47,18 @@ public abstract class ProviderSession<T, R> implements RemoteCredentialService.P
@NonNull protected final int mUserId;
@NonNull protected Status mStatus = Status.NOT_STARTED;
@NonNull protected final ProviderInternalCallback mCallbacks;
@NonNull protected final ProviderIntentController mProviderIntentController;
@Nullable protected Credential mFinalCredentialResponse;
@NonNull protected final T mProviderRequest;
@Nullable protected R mProviderResponse;
@Nullable protected Pair<String, Action> mUiRemoteEntry;
/**
* Returns true if the given status reflects that the provider state is ready to be shown
* on the credMan UI.
*/
public static boolean isUiInvokingStatus(Status status) {
return status == Status.CREDENTIALS_RECEIVED || status == Status.SAVE_ENTRIES_RECEIVED;
return status == Status.CREDENTIALS_RECEIVED || status == Status.SAVE_ENTRIES_RECEIVED
|| status == Status.REQUIRES_AUTHENTICATION;
}
/**
@@ -86,12 +89,14 @@ public abstract class ProviderSession<T, R> implements RemoteCredentialService.P
* Interface to be implemented by any class that wishes to get a callback when a particular
* provider session's status changes. Typically, implemented by the {@link RequestSession}
* class.
* @param <V> the type of the final response expected
*/
public interface ProviderInternalCallback {
/**
* Called when status changes.
*/
public interface ProviderInternalCallback<V> {
/** Called when status changes. */
void onProviderStatusChanged(Status status, ComponentName componentName);
/** Called when the final credential to be returned to the client has been received. */
void onFinalResponseReceived(ComponentName componentName, V response);
}
protected ProviderSession(@NonNull Context context, @NonNull CredentialProviderInfo info,
@@ -106,7 +111,6 @@ public abstract class ProviderSession<T, R> implements RemoteCredentialService.P
mUserId = userId;
mComponentName = info.getServiceInfo().getComponentName();
mRemoteCredentialService = remoteCredentialService;
mProviderIntentController = new ProviderIntentController(userId, context, this);
}
/** Provider status at various states of the request session. */
@@ -164,6 +168,11 @@ public abstract class ProviderSession<T, R> implements RemoteCredentialService.P
mCallbacks.onProviderStatusChanged(status, mComponentName);
}
protected void onRemoteEntrySelected(
ProviderPendingIntentResponse providerPendingIntentResponse) {
//TODO: Implement
}
/** Get the request to be sent to the provider. */
protected T getProviderRequest() {
return mProviderRequest;
@@ -179,12 +188,6 @@ public abstract class ProviderSession<T, R> implements RemoteCredentialService.P
@Nullable protected abstract ProviderData prepareUiData();
/** Should be overridden to handle the selected entry from the UI. */
protected abstract void onUiEntrySelected(String entryType, String entryId);
@Override
public abstract void onProviderIntentResult(Bundle resultData);
@Override
public abstract void onProviderIntentCancelled();
protected abstract void onUiEntrySelected(String entryType, String entryId,
ProviderPendingIntentResponse providerPendingIntentResponse);
}

View File

@@ -37,8 +37,7 @@ import java.util.Map;
* Base class of a request session, that listens to UI events. This class must be extended
* every time a new response type is expected from the providers.
*/
abstract class RequestSession<T, U> implements CredentialManagerUi.CredentialManagerUiCallback,
ProviderSession.ProviderInternalCallback {
abstract class RequestSession<T, U> implements CredentialManagerUi.CredentialManagerUiCallback{
private static final String TAG = "RequestSession";
// TODO: Revise access levels of attributes
@@ -89,7 +88,7 @@ abstract class RequestSession<T, U> implements CredentialManagerUi.CredentialMan
}
Log.i(TAG, "Provider session found");
providerSession.onUiEntrySelected(selection.getEntryKey(),
selection.getEntrySubkey());
selection.getEntrySubkey(), selection.getPendingIntentProviderResponse());
}
@Override // from CredentialManagerUiCallbacks
@@ -98,8 +97,7 @@ abstract class RequestSession<T, U> implements CredentialManagerUi.CredentialMan
finishSession();
}
@Override // from provider session
public void onProviderStatusChanged(ProviderSession.Status status,
protected void onProviderStatusChanged(ProviderSession.Status status,
ComponentName componentName) {
Log.i(TAG, "in onStatusChanged with status: " + status);
if (ProviderSession.isTerminatingStatus(status)) {