diff --git a/core/api/current.txt b/core/api/current.txt index 272fab303911b..7e77915b298bf 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -40539,7 +40539,7 @@ package android.service.credentials { ctor public BeginCreateCredentialResponse(); method public int describeContents(); method @NonNull public java.util.List getCreateEntries(); - method @Nullable public android.service.credentials.CreateEntry getRemoteCreateEntry(); + method @Nullable public android.service.credentials.RemoteEntry getRemoteCreateEntry(); method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator CREATOR; } @@ -40549,7 +40549,7 @@ package android.service.credentials { method @NonNull public android.service.credentials.BeginCreateCredentialResponse.Builder addCreateEntry(@NonNull android.service.credentials.CreateEntry); method @NonNull public android.service.credentials.BeginCreateCredentialResponse build(); method @NonNull public android.service.credentials.BeginCreateCredentialResponse.Builder setCreateEntries(@NonNull java.util.List); - method @NonNull public android.service.credentials.BeginCreateCredentialResponse.Builder setRemoteCreateEntry(@Nullable android.service.credentials.CreateEntry); + method @NonNull public android.service.credentials.BeginCreateCredentialResponse.Builder setRemoteCreateEntry(@Nullable android.service.credentials.RemoteEntry); } public class BeginGetCredentialOption implements android.os.Parcelable { @@ -40584,7 +40584,7 @@ package android.service.credentials { method @NonNull public java.util.List getActions(); method @NonNull public java.util.List getAuthenticationActions(); method @NonNull public java.util.List getCredentialEntries(); - method @Nullable public android.service.credentials.CredentialEntry getRemoteCredentialEntry(); + method @Nullable public android.service.credentials.RemoteEntry getRemoteCredentialEntry(); method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator CREATOR; } @@ -40598,7 +40598,7 @@ package android.service.credentials { method @NonNull public android.service.credentials.BeginGetCredentialResponse.Builder setActions(@NonNull java.util.List); method @NonNull public android.service.credentials.BeginGetCredentialResponse.Builder setAuthenticationActions(@NonNull java.util.List); method @NonNull public android.service.credentials.BeginGetCredentialResponse.Builder setCredentialEntries(@NonNull java.util.List); - method @NonNull public android.service.credentials.BeginGetCredentialResponse.Builder setRemoteCredentialEntry(@Nullable android.service.credentials.CredentialEntry); + method @NonNull public android.service.credentials.BeginGetCredentialResponse.Builder setRemoteCredentialEntry(@Nullable android.service.credentials.RemoteEntry); } public final class CallingAppInfo implements android.os.Parcelable { @@ -40668,14 +40668,22 @@ package android.service.credentials { } public final class GetCredentialRequest implements android.os.Parcelable { - ctor public GetCredentialRequest(@NonNull android.service.credentials.CallingAppInfo, @NonNull android.credentials.CredentialOption); + ctor public GetCredentialRequest(@NonNull android.service.credentials.CallingAppInfo, @NonNull java.util.List); method public int describeContents(); method @NonNull public android.service.credentials.CallingAppInfo getCallingAppInfo(); - method @NonNull public android.credentials.CredentialOption getCredentialOption(); + method @NonNull public java.util.List getCredentialOptions(); method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator CREATOR; } + public class RemoteEntry implements android.os.Parcelable { + ctor public RemoteEntry(@NonNull android.app.slice.Slice); + method public int describeContents(); + method @NonNull public android.app.slice.Slice getSlice(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator CREATOR; + } + } package android.service.dreams { diff --git a/core/java/android/credentials/GetCredentialRequest.java b/core/java/android/credentials/GetCredentialRequest.java index 951cbe4e58c08..c58d2dcd2dbf1 100644 --- a/core/java/android/credentials/GetCredentialRequest.java +++ b/core/java/android/credentials/GetCredentialRequest.java @@ -149,7 +149,6 @@ public final class GetCredentialRequest implements Parcelable { mCredentialOptions = credentialOptions; AnnotationValidations.validate(NonNull.class, null, mCredentialOptions); - Bundle data = in.readBundle(); mData = data; AnnotationValidations.validate(NonNull.class, null, mData); diff --git a/core/java/android/service/credentials/BeginCreateCredentialResponse.java b/core/java/android/service/credentials/BeginCreateCredentialResponse.java index f0f954dda1827..eebd31c6fa968 100644 --- a/core/java/android/service/credentials/BeginCreateCredentialResponse.java +++ b/core/java/android/service/credentials/BeginCreateCredentialResponse.java @@ -32,7 +32,7 @@ import java.util.Objects; */ public final class BeginCreateCredentialResponse implements Parcelable { private final @NonNull List mCreateEntries; - private final @Nullable CreateEntry mRemoteCreateEntry; + private final @Nullable RemoteEntry mRemoteCreateEntry; /** * Creates an empty response instance, to be used when there are no {@link CreateEntry} @@ -46,7 +46,7 @@ public final class BeginCreateCredentialResponse implements Parcelable { List createEntries = new ArrayList<>(); in.readTypedList(createEntries, CreateEntry.CREATOR); mCreateEntries = createEntries; - mRemoteCreateEntry = in.readTypedObject(CreateEntry.CREATOR); + mRemoteCreateEntry = in.readTypedObject(RemoteEntry.CREATOR); } @Override @@ -75,7 +75,7 @@ public final class BeginCreateCredentialResponse implements Parcelable { /* package-private */ BeginCreateCredentialResponse( @NonNull List createEntries, - @Nullable CreateEntry remoteCreateEntry) { + @Nullable RemoteEntry remoteCreateEntry) { this.mCreateEntries = createEntries; com.android.internal.util.AnnotationValidations.validate( NonNull.class, null, mCreateEntries); @@ -88,7 +88,7 @@ public final class BeginCreateCredentialResponse implements Parcelable { } /** Returns the remote create entry to be displayed on the UI. */ - public @Nullable CreateEntry getRemoteCreateEntry() { + public @Nullable RemoteEntry getRemoteCreateEntry() { return mRemoteCreateEntry; } @@ -98,7 +98,7 @@ public final class BeginCreateCredentialResponse implements Parcelable { @SuppressWarnings("WeakerAccess") /* synthetic access */ public static final class Builder { private @NonNull List mCreateEntries = new ArrayList<>(); - private @Nullable CreateEntry mRemoteCreateEntry; + private @Nullable RemoteEntry mRemoteCreateEntry; /** * Sets the list of create entries to be shown on the UI. @@ -138,7 +138,7 @@ public final class BeginCreateCredentialResponse implements Parcelable { * {@link CredentialProviderService#EXTRA_CREATE_CREDENTIAL_RESPONSE} key should be populated * with a {@link android.credentials.CreateCredentialResponse} object. */ - public @NonNull Builder setRemoteCreateEntry(@Nullable CreateEntry remoteCreateEntry) { + public @NonNull Builder setRemoteCreateEntry(@Nullable RemoteEntry remoteCreateEntry) { mRemoteCreateEntry = remoteCreateEntry; return this; } diff --git a/core/java/android/service/credentials/BeginGetCredentialResponse.java b/core/java/android/service/credentials/BeginGetCredentialResponse.java index 3652742d4ff4d..97f5079393a0d 100644 --- a/core/java/android/service/credentials/BeginGetCredentialResponse.java +++ b/core/java/android/service/credentials/BeginGetCredentialResponse.java @@ -42,7 +42,7 @@ public final class BeginGetCredentialResponse implements Parcelable { private final @NonNull List mActions; /** Remote credential entry to get the response from a different device. */ - private final @Nullable CredentialEntry mRemoteCredentialEntry; + private final @Nullable RemoteEntry mRemoteCredentialEntry; /** * Creates an empty response instance, to be used when there are no {@link CredentialEntry}, @@ -57,7 +57,7 @@ public final class BeginGetCredentialResponse implements Parcelable { private BeginGetCredentialResponse(@NonNull List credentialEntries, @NonNull List authenticationEntries, @NonNull List actions, - @Nullable CredentialEntry remoteCredentialEntry) { + @Nullable RemoteEntry remoteCredentialEntry) { mCredentialEntries = new ArrayList<>(credentialEntries); mAuthenticationEntries = new ArrayList<>(authenticationEntries); mActions = new ArrayList<>(actions); @@ -74,7 +74,7 @@ public final class BeginGetCredentialResponse implements Parcelable { List actions = new ArrayList<>(); in.readTypedList(actions, Action.CREATOR); mActions = actions; - mRemoteCredentialEntry = in.readTypedObject(CredentialEntry.CREATOR); + mRemoteCredentialEntry = in.readTypedObject(RemoteEntry.CREATOR); } public static final @NonNull Creator CREATOR = @@ -127,7 +127,7 @@ public final class BeginGetCredentialResponse implements Parcelable { /** * Returns the remote credential entry to be displayed on the UI. */ - public @Nullable CredentialEntry getRemoteCredentialEntry() { + public @Nullable RemoteEntry getRemoteCredentialEntry() { return mRemoteCredentialEntry; } @@ -139,7 +139,7 @@ public final class BeginGetCredentialResponse implements Parcelable { private List mAuthenticationEntries = new ArrayList<>(); private List mActions = new ArrayList<>(); - private CredentialEntry mRemoteCredentialEntry; + private RemoteEntry mRemoteCredentialEntry; /** * Sets a remote credential entry to be shown on the UI. Provider must set this if they @@ -155,7 +155,7 @@ public final class BeginGetCredentialResponse implements Parcelable { * {@link CredentialProviderService#EXTRA_GET_CREDENTIAL_RESPONSE} key should be populated * with a {@link android.credentials.Credential} object. */ - public @NonNull Builder setRemoteCredentialEntry(@Nullable CredentialEntry + public @NonNull Builder setRemoteCredentialEntry(@Nullable RemoteEntry remoteCredentialEntry) { mRemoteCredentialEntry = remoteCredentialEntry; return this; diff --git a/core/java/android/service/credentials/GetCredentialRequest.java b/core/java/android/service/credentials/GetCredentialRequest.java index 4f13922caf15b..5bad9abb2eedc 100644 --- a/core/java/android/service/credentials/GetCredentialRequest.java +++ b/core/java/android/service/credentials/GetCredentialRequest.java @@ -23,14 +23,16 @@ import android.os.Parcelable; import com.android.internal.util.AnnotationValidations; +import java.util.ArrayList; +import java.util.List; import java.util.Objects; /** * Request for getting user's credential from a given credential provider. * - *

Provider will receive this request once the user selects a given {@link CredentialEntry} - * on the selector, that was sourced from provider's result to - * {@link CredentialProviderService#onBeginGetCredential}. + *

A credential provider will receive this request once the user selects a + * given {@link CredentialEntry}, or {@link RemoteEntry} on the selector, that was sourced + * from provider's initial response to {@link CredentialProviderService#onBeginGetCredential}. */ public final class GetCredentialRequest implements Parcelable { /** Calling package of the app requesting for credentials. */ @@ -38,24 +40,27 @@ public final class GetCredentialRequest implements Parcelable { private final CallingAppInfo mCallingAppInfo; /** - * Holds parameters to be used for retrieving a specific type of credential. + * Holds a list of options (parameters) to be used for retrieving a specific type of credential. */ @NonNull - private final CredentialOption mCredentialOption; + private final List mCredentialOptions; public GetCredentialRequest(@NonNull CallingAppInfo callingAppInfo, - @NonNull CredentialOption credentialOption) { + @NonNull List credentialOptions) { this.mCallingAppInfo = Objects.requireNonNull(callingAppInfo, "callingAppInfo must not be null"); - this.mCredentialOption = Objects.requireNonNull(credentialOption, - "credentialOption must not be null"); + this.mCredentialOptions = Objects.requireNonNull(credentialOptions, + "credentialOptions must not be null"); } private GetCredentialRequest(@NonNull Parcel in) { mCallingAppInfo = in.readTypedObject(CallingAppInfo.CREATOR); AnnotationValidations.validate(NonNull.class, null, mCallingAppInfo); - mCredentialOption = in.readTypedObject(CredentialOption.CREATOR); - AnnotationValidations.validate(NonNull.class, null, mCredentialOption); + + List credentialOptions = new ArrayList<>(); + in.readTypedList(credentialOptions, CredentialOption.CREATOR); + mCredentialOptions = credentialOptions; + AnnotationValidations.validate(NonNull.class, null, mCredentialOptions); } @NonNull public static final Creator CREATOR = @@ -79,7 +84,7 @@ public final class GetCredentialRequest implements Parcelable { @Override public void writeToParcel(@NonNull Parcel dest, int flags) { dest.writeTypedObject(mCallingAppInfo, flags); - dest.writeTypedObject(mCredentialOption, flags); + dest.writeTypedList(mCredentialOptions, flags); } /** @@ -91,10 +96,26 @@ public final class GetCredentialRequest implements Parcelable { } /** - * Returns the parameters needed to return a given type of credential. + * Returns a list of options containing parameters needed to return a given type of credential. + * This is part of the request that the credential provider receives after the user has + * selected an entry on a selector UI. + * + * When the user selects a {@link CredentialEntry} and the credential provider receives a + * {@link GetCredentialRequest}, this list is expected to contain a single + * {@link CredentialOption} only. A {@link CredentialEntry} is always created for a given + * {@link BeginGetCredentialOption}, and hence when the user selects it, the provider + * receives a corresponding {@link CredentialOption} that contains all the required parameters + * to actually retrieve the credential. + * + * When the user selects a {@link RemoteEntry} and the credential provider receives a + * {@link GetCredentialRequest}, this list may contain greater than a single + * {@link CredentialOption}, representing the number of options specified by the developer + * in the original {@link android.credentials.GetCredentialRequest}. This is because a + * {@link RemoteEntry} indicates that the entire request will be processed on a different + * device and is not tied to a particular option. */ @NonNull - public CredentialOption getCredentialOption() { - return mCredentialOption; + public List getCredentialOptions() { + return mCredentialOptions; } } diff --git a/core/java/android/service/credentials/RemoteEntry.java b/core/java/android/service/credentials/RemoteEntry.java new file mode 100644 index 0000000000000..716c00d726f3f --- /dev/null +++ b/core/java/android/service/credentials/RemoteEntry.java @@ -0,0 +1,97 @@ +/* + * 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.service.credentials; + +import android.annotation.NonNull; +import android.annotation.SuppressLint; +import android.app.PendingIntent; +import android.app.slice.Slice; +import android.os.Parcel; +import android.os.Parcelable; + +/** + * An entry to be shown on the UI. This entry represents remote execution of a get/create flow + * whereby credentials are retrieved from, or stored to a remote device. + * + *

If user selects this entry, the corresponding {@link PendingIntent} set on the + * {@code slice} as a {@link androidx.slice.core.SliceAction} will get invoked. + * Once the resulting activity fulfills the required user engagement, + * the {@link android.app.Activity} result should be set to {@link android.app.Activity#RESULT_OK}, + * and the result of the operation must be set as the activity result. + * + * For a get flow, invoked through {@link CredentialProviderService#onBeginGetCredential}, + * providers must set a {@link android.credentials.GetCredentialResponse} on the activity result, + * against the key {@link CredentialProviderService#EXTRA_GET_CREDENTIAL_RESPONSE}. + * + * For a creates flow, invoked through {@link CredentialProviderService#onBeginCreateCredential}, + * providers must set a {@link android.credentials.CreateCredentialResponse} on the activity + * result against the ket {@link CredentialProviderService#EXTRA_CREATE_CREDENTIAL_RESPONSE}. + * + *

Any class that extends this class must only add extra field values to the {@code slice} + * object passed into the constructor. Any other field will not be parceled through. If the + * derived class has custom parceling implementation, this class will not be able to unpack + * the parcel without having access to that implementation. + */ +@SuppressLint("ParcelNotFinal") +public class RemoteEntry implements Parcelable { + private final @NonNull Slice mSlice; + + private RemoteEntry(@NonNull Parcel in) { + mSlice = in.readTypedObject(Slice.CREATOR); + } + + @NonNull + public static final Creator CREATOR = new Creator() { + @Override + public RemoteEntry createFromParcel(@NonNull Parcel in) { + return new RemoteEntry(in); + } + + @Override + public RemoteEntry[] newArray(int size) { + return new RemoteEntry[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeTypedObject(mSlice, flags); + } + + /** + * Constructs a RemoteEntry to be displayed on the UI. + * + * @param slice the display content to be displayed on the UI, along with this entry + */ + public RemoteEntry( + @NonNull Slice slice) { + this.mSlice = slice; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mSlice); + } + + /** Returns the content to be displayed with this remote entry on the UI. */ + @NonNull + public Slice getSlice() { + return mSlice; + } +} diff --git a/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java b/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java index 3245c914b2b83..97b78116bbfac 100644 --- a/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java +++ b/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java @@ -35,6 +35,7 @@ import android.service.credentials.CreateCredentialRequest; import android.service.credentials.CreateEntry; import android.service.credentials.CredentialProviderInfo; import android.service.credentials.CredentialProviderService; +import android.service.credentials.RemoteEntry; import android.util.Log; import android.util.Pair; import android.util.Slog; @@ -301,14 +302,14 @@ public final class ProviderCreateSession extends ProviderSession< @NonNull private final Map> mUiCreateEntries = new HashMap<>(); - @Nullable private Pair> mUiRemoteEntry = null; + @Nullable private Pair> mUiRemoteEntry = null; ProviderResponseDataHandler(String hybridService) { mExpectedRemoteEntryProviderService = ComponentName.unflattenFromString(hybridService); } public void addResponseContent(List createEntries, - CreateEntry remoteEntry) { + RemoteEntry remoteEntry) { createEntries.forEach(this::addCreateEntry); setRemoteEntry(remoteEntry); } @@ -319,7 +320,7 @@ public final class ProviderCreateSession extends ProviderSession< mUiCreateEntries.put(id, new Pair<>(createEntry, entry)); } - public void setRemoteEntry(@Nullable CreateEntry remoteEntry) { + public void setRemoteEntry(@Nullable RemoteEntry remoteEntry) { if (remoteEntry == null) { mUiRemoteEntry = null; return; @@ -363,7 +364,7 @@ public final class ProviderCreateSession extends ProviderSession< return mUiCreateEntries.isEmpty() && mUiRemoteEntry == null; } @Nullable - public CreateEntry getRemoteEntry(String entryKey) { + public RemoteEntry getRemoteEntry(String entryKey) { return mUiRemoteEntry == null || mUiRemoteEntry .first == null || !mUiRemoteEntry.first.equals(entryKey) || mUiRemoteEntry.second == null diff --git a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java index 6498b6afe2080..ee813e90ddfed 100644 --- a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java +++ b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java @@ -38,6 +38,7 @@ import android.service.credentials.CredentialEntry; import android.service.credentials.CredentialProviderInfo; import android.service.credentials.CredentialProviderService; import android.service.credentials.GetCredentialRequest; +import android.service.credentials.RemoteEntry; import android.util.Log; import android.util.Pair; import android.util.Slog; @@ -299,7 +300,7 @@ public final class ProviderGetSession extends ProviderSession> mUiAuthenticationEntries = new HashMap<>(); - @Nullable private Pair> mUiRemoteEntry = null; + @Nullable private Pair> mUiRemoteEntry = null; ProviderResponseDataHandler(ComponentName expectedRemoteEntryProviderService) { mExpectedRemoteEntryProviderService = expectedRemoteEntryProviderService; @@ -480,7 +481,7 @@ public final class ProviderGetSession extends ProviderSession credentialEntries, List actions, List authenticationActions, - CredentialEntry remoteEntry) { + RemoteEntry remoteEntry) { credentialEntries.forEach(this::addCredentialEntry); actions.forEach(this::addAction); authenticationActions.forEach( @@ -522,7 +523,7 @@ public final class ProviderGetSession extends ProviderSession(generateUniqueId(), new Pair<>(remoteEntry, entry)); } @@ -604,7 +604,7 @@ public final class ProviderGetSession extends ProviderSession