diff --git a/core/java/android/credentials/ui/Entry.java b/core/java/android/credentials/ui/Entry.java index 6fa331bbdc545..5c07e6e94131d 100644 --- a/core/java/android/credentials/ui/Entry.java +++ b/core/java/android/credentials/ui/Entry.java @@ -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 diff --git a/core/java/android/credentials/ui/ProviderPendingIntentResponse.java b/core/java/android/credentials/ui/ProviderPendingIntentResponse.java new file mode 100644 index 0000000000000..420956f69b7f3 --- /dev/null +++ b/core/java/android/credentials/ui/ProviderPendingIntentResponse.java @@ -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 CREATOR = + new Creator() { + @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; + } +} diff --git a/core/java/android/credentials/ui/UserSelectionDialogResult.java b/core/java/android/credentials/ui/UserSelectionDialogResult.java index 6025d78d1baee..0e8e7b6f06a50 100644 --- a/core/java/android/credentials/ui/UserSelectionDialogResult.java +++ b/core/java/android/credentials/ui/UserSelectionDialogResult.java @@ -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 diff --git a/core/java/android/service/credentials/CredentialProviderService.java b/core/java/android/service/credentials/CredentialProviderService.java index 6f3e786ffc4e8..24b7c3c439d4d 100644 --- a/core/java/android/service/credentials/CredentialProviderService.java +++ b/core/java/android/service/credentials/CredentialProviderService.java @@ -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"; diff --git a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt index 33fb154f44f0d..df797436baf69 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt @@ -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 { // 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 diff --git a/services/credentials/java/com/android/server/credentials/CreateRequestSession.java b/services/credentials/java/com/android/server/credentials/CreateRequestSession.java index e07bc7761156b..06d8e6280bef8 100644 --- a/services/credentials/java/com/android/server/credentials/CreateRequestSession.java +++ b/services/credentials/java/com/android/server/credentials/CreateRequestSession.java @@ -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 { + ICreateCredentialCallback> + implements ProviderSession.ProviderInternalCallback { private static final String TAG = "CreateRequestSession"; CreateRequestSession(@NonNull Context context, int userId, @@ -72,4 +76,29 @@ public final class CreateRequestSession extends RequestSession { + IGetCredentialCallback> + implements ProviderSession.ProviderInternalCallback { private static final String TAG = "GetRequestSession"; public GetRequestSession(Context context, int userId, @@ -67,23 +66,6 @@ public final class GetRequestSession extends RequestSession providerDataList) { mHandler.post(() -> mCredentialManagerUi.show(RequestInfo.newGetRequestInfo( @@ -91,9 +73,26 @@ public final class GetRequestSession extends RequestSession prepareUiSaveEntries(@NonNull List 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 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 } } diff --git a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java index 362d98167462e..d63cdebe0e1b3 100644 --- a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java +++ b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java @@ -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 mUiCredentialEntries = new HashMap<>(); @NonNull private final Map mUiActionsEntries = new HashMap<>(); - private Action mAuthenticationAction = null; + @Nullable + private Pair 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 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(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 prepareUiCredentialEntries(@NonNull @@ -200,19 +253,28 @@ public final class ProviderGetSession extends ProviderSession populateUiActionEntries(@Nullable List actions) { + private List prepareUiActionEntries(@Nullable List actions) { List 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 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; - } -} diff --git a/services/credentials/java/com/android/server/credentials/ProviderSession.java b/services/credentials/java/com/android/server/credentials/ProviderSession.java index 14a9157548631..4a07f0a4e305c 100644 --- a/services/credentials/java/com/android/server/credentials/ProviderSession.java +++ b/services/credentials/java/com/android/server/credentials/ProviderSession.java @@ -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 The request to be sent to the provider * @param The response to be expected from the provider */ -public abstract class ProviderSession implements RemoteCredentialService.ProviderCallbacks, - 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 + implements RemoteCredentialService.ProviderCallbacks { + // 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 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 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 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 the type of the final response expected */ - public interface ProviderInternalCallback { - /** - * Called when status changes. - */ + public interface ProviderInternalCallback { + /** 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 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 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 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); } diff --git a/services/credentials/java/com/android/server/credentials/RequestSession.java b/services/credentials/java/com/android/server/credentials/RequestSession.java index 056d0e8718be8..71fc67ce5afd1 100644 --- a/services/credentials/java/com/android/server/credentials/RequestSession.java +++ b/services/credentials/java/com/android/server/credentials/RequestSession.java @@ -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 implements CredentialManagerUi.CredentialManagerUiCallback, - ProviderSession.ProviderInternalCallback { +abstract class RequestSession implements CredentialManagerUi.CredentialManagerUiCallback{ private static final String TAG = "RequestSession"; // TODO: Revise access levels of attributes @@ -89,7 +88,7 @@ abstract class RequestSession 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 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)) {