diff --git a/core/java/android/service/credentials/Action.java b/core/java/android/service/credentials/Action.java new file mode 100644 index 0000000000000..186b2a60c4304 --- /dev/null +++ b/core/java/android/service/credentials/Action.java @@ -0,0 +1,98 @@ +/* + * 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.app.PendingIntent; +import android.app.slice.Slice; +import android.os.Parcel; +import android.os.Parcelable; + +import androidx.annotation.NonNull; + +import java.util.Objects; + +/** + * An action defined by the provider that intents into the provider's app for specific + * user actions. + * + * @hide + */ +public final class Action implements Parcelable { + /** Info to be displayed with this action on the UI. */ + private final @NonNull Slice mInfo; + /** + * The pending intent to be invoked when the user selects this action. + */ + private final @NonNull PendingIntent mPendingIntent; + + /** + * Constructs an action to be displayed on the UI. + * + * @param actionInfo The info to be displayed along with this action. + * @param pendingIntent The intent to be invoked when the user selects this action. + * @throws NullPointerException If {@code actionInfo}, or {@code pendingIntent} is null. + */ + public Action(@NonNull Slice actionInfo, @NonNull PendingIntent pendingIntent) { + Objects.requireNonNull(actionInfo, "actionInfo must not be null"); + Objects.requireNonNull(pendingIntent, "pendingIntent must not be null"); + mInfo = actionInfo; + mPendingIntent = pendingIntent; + } + + private Action(@NonNull Parcel in) { + mInfo = in.readParcelable(Slice.class.getClassLoader(), Slice.class); + mPendingIntent = in.readParcelable(PendingIntent.class.getClassLoader(), + PendingIntent.class); + } + + public static final @NonNull Creator CREATOR = new Creator() { + @Override + public Action createFromParcel(@NonNull Parcel in) { + return new Action(in); + } + + @Override + public Action[] newArray(int size) { + return new Action[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + mInfo.writeToParcel(dest, flags); + mPendingIntent.writeToParcel(dest, flags); + } + + /** + * Returns the action info as a {@link Slice} object, to be displayed on the UI. + */ + public @NonNull Slice getActionInfo() { + return mInfo; + } + + /** + * Returns the {@link PendingIntent} to be invoked when the action is selected. + */ + public @NonNull PendingIntent getPendingIntent() { + return mPendingIntent; + } +} diff --git a/core/java/android/service/credentials/Credential.java b/core/java/android/service/credentials/Credential.java new file mode 100644 index 0000000000000..7d5da8a7c4e0b --- /dev/null +++ b/core/java/android/service/credentials/Credential.java @@ -0,0 +1,100 @@ +/* + * 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.os.Bundle; +import android.os.Parcel; +import android.os.Parcelable; + +import androidx.annotation.NonNull; + +import static java.util.Objects.requireNonNull; + +import com.android.internal.util.Preconditions; + +/** + * A Credential object that contains type specific data that is returned from the credential + * provider to the framework. Framework then converts it to an app facing representation and + * returns to the calling app. + * + * @hide + */ +public final class Credential implements Parcelable { + /** The type of this credential. */ + private final @NonNull String mType; + + /** The data associated with this credential. */ + private final @NonNull Bundle mData; + + /** + * Constructs a credential object. + * + * @param type The type of the credential. + * @param data The data of the credential that is passed back to the framework, and eventually + * to the calling app. + * @throws NullPointerException If {@code data} is null. + * @throws IllegalArgumentException If {@code type} is null or empty. + */ + public Credential(@NonNull String type, @NonNull Bundle data) { + Preconditions.checkStringNotEmpty(type, "type must not be null, or empty"); + requireNonNull(data, "data must not be null"); + this.mType = type; + this.mData = data; + } + + private Credential(@NonNull Parcel in) { + mType = in.readString16NoHelper(); + mData = in.readBundle(); + } + + /** + * Returns the type of the credential. + */ + public @NonNull String getType() { + return mType; + } + + /** + * Returns the data associated with the credential. + */ + public @NonNull Bundle getData() { + return mData; + } + + public static final @NonNull Creator CREATOR = new Creator() { + @Override + public Credential createFromParcel(@NonNull Parcel in) { + return new Credential(in); + } + + @Override + public Credential[] newArray(int size) { + return new Credential[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeString8(mType); + dest.writeBundle(mData); + } +} diff --git a/core/java/android/service/credentials/CredentialEntry.java b/core/java/android/service/credentials/CredentialEntry.java new file mode 100644 index 0000000000000..b49215a08bfb7 --- /dev/null +++ b/core/java/android/service/credentials/CredentialEntry.java @@ -0,0 +1,216 @@ +/* + * 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.Nullable; +import android.app.PendingIntent; +import android.app.slice.Slice; +import android.os.Parcel; +import android.os.Parcelable; + +import androidx.annotation.NonNull; + +import com.android.internal.util.Preconditions; + +import java.util.Objects; + +/** + * A credential entry that is displayed on the account selector UI. Each entry corresponds to + * something that the user can select. + * + * @hide + */ +public final class CredentialEntry implements Parcelable { + /** The type of the credential entry to be shown on the UI. */ + private final @NonNull String mType; + + /** The info to be displayed along with this credential entry on the UI. */ + private final @NonNull Slice mInfo; + + /** The pending intent to be invoked when this credential entry is selected. */ + private final @Nullable PendingIntent mPendingIntent; + + /** + * The underlying credential to be returned to the app when the user selects + * this credential entry. + */ + private final @Nullable Credential mCredential; + + /** A flag denoting whether auto-select is enabled for this entry. */ + private final @NonNull boolean mAutoSelectAllowed; + + private CredentialEntry(@NonNull String type, @NonNull Slice entryInfo, + @Nullable PendingIntent pendingIntent, @Nullable Credential credential, + @NonNull boolean autoSeletAllowed) { + mType = type; + mInfo = entryInfo; + mPendingIntent = pendingIntent; + mCredential = credential; + mAutoSelectAllowed = autoSeletAllowed; + } + + private CredentialEntry(@NonNull Parcel in) { + mType = in.readString(); + mInfo = in.readParcelable(Slice.class.getClassLoader(), Slice.class); + mPendingIntent = in.readParcelable(PendingIntent.class.getClassLoader(), + PendingIntent.class); + mCredential = in.readParcelable(Credential.class.getClassLoader(), + Credential.class); + mAutoSelectAllowed = in.readBoolean(); + } + + public static final @NonNull Creator CREATOR = + new Creator() { + @Override + public CredentialEntry createFromParcel(@NonNull Parcel in) { + return new CredentialEntry(in); + } + + @Override + public CredentialEntry[] newArray(int size) { + return new CredentialEntry[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeString8(mType); + mInfo.writeToParcel(dest, flags); + mPendingIntent.writeToParcel(dest, flags); + mCredential.writeToParcel(dest, flags); + dest.writeBoolean(mAutoSelectAllowed); + } + + /** + * Returns the specific credential type of the entry. + */ + public @NonNull String getType() { + return mType; + } + + /** + * Returns the UI info to be displayed for this entry. + */ + public @NonNull Slice getInfo() { + return mInfo; + } + + /** + * Returns the pending intent to be invoked if the user selects this entry. + */ + public @Nullable PendingIntent getPendingIntent() { + return mPendingIntent; + } + + /** + * Returns the credential associated with this entry. + */ + public @Nullable Credential getCredential() { + return mCredential; + } + + /** + * Returns whether this entry can be auto selected if it is the only option for the user. + */ + public @NonNull boolean isAutoSelectAllowed() { + return mAutoSelectAllowed; + } + + /** + * Builder for {@link CredentialEntry}. + */ + public static final class Builder { + private String mType; + private Slice mInfo; + private PendingIntent mPendingIntent; + private Credential mCredential; + private boolean mAutoSelectAllowed = false; + + /** + * Builds the instance. + * @param type The type of credential underlying this credential entry. + * @param info The info to be displayed with this entry on the UI. + * + * @throws IllegalArgumentException If {@code type} is null or empty. + * @throws NullPointerException If {@code info} is null. + */ + public Builder(@NonNull String type, @NonNull Slice info) { + mType = Preconditions.checkStringNotEmpty(type, "type must not be " + + "null, or empty"); + mInfo = Objects.requireNonNull(info, "info must not be null"); + } + + /** + * Sets the pendingIntent to be invoked if the user selects this entry. + * + * @throws IllegalStateException If {@code credential} is already set. Must either set the + * {@code credential}, or the {@code pendingIntent}. + */ + public @NonNull Builder setPendingIntent(@Nullable PendingIntent pendingIntent) { + Preconditions.checkState(pendingIntent != null && mCredential != null, + "credential is already set. Cannot set both the pendingIntent " + + "and the credential"); + mPendingIntent = pendingIntent; + return this; + } + + /** + * Sets the credential to be used, if the user selects this entry. + * + * @throws IllegalStateException If {@code pendingIntent} is already set. Must either set + * the {@code pendingIntent}, or the {@code credential}. + */ + public @NonNull Builder setCredential(@Nullable Credential credential) { + Preconditions.checkState(credential != null && mPendingIntent != null, + "pendingIntent is already set. Cannot set both the " + + "pendingIntent and the credential"); + mCredential = credential; + return this; + } + + /** + * Sets whether the entry is allowed to be auto selected by the framework. + * The default value is set to false. + */ + public @NonNull Builder setAutoSelectAllowed(@NonNull boolean autoSelectAllowed) { + mAutoSelectAllowed = autoSelectAllowed; + return this; + } + + /** + * Creates a new {@link CredentialEntry} instance. + * + * @throws NullPointerException If {@code info} is null. + * @throws IllegalArgumentException If {@code type} is null, or empty. + * @throws IllegalStateException If neither {@code pendingIntent} nor {@code credential} + * is set, or if both are set. + */ + public @NonNull CredentialEntry build() { + Preconditions.checkState(mPendingIntent == null && mCredential == null, + "Either pendingIntent or credential must be set"); + Preconditions.checkState(mPendingIntent != null && mCredential != null, + "Cannot set both the pendingIntent and credential"); + return new CredentialEntry(mType, mInfo, mPendingIntent, + mCredential, mAutoSelectAllowed); + } + } +} diff --git a/core/java/android/service/credentials/CredentialProviderService.java b/core/java/android/service/credentials/CredentialProviderService.java new file mode 100644 index 0000000000000..e2a495d209501 --- /dev/null +++ b/core/java/android/service/credentials/CredentialProviderService.java @@ -0,0 +1,91 @@ +/* + * 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 static com.android.internal.util.function.pooled.PooledLambda.obtainMessage; + +import android.annotation.CallSuper; +import android.annotation.NonNull; +import android.app.Service; +import android.content.Intent; +import android.os.CancellationSignal; +import android.os.Handler; +import android.os.IBinder; +import android.os.ICancellationSignal; +import android.os.Looper; +import android.os.RemoteException; +import android.util.Log; + +import java.util.Objects; + +/** + * Main service to be extended by credential providers, in order to return user credentials + * to the framework. + * + * @hide + */ +public abstract class CredentialProviderService extends Service { + private static final String TAG = "CredProviderService"; + private Handler mHandler; + + public static final String SERVICE_INTERFACE = + "android.service.credentials.CredentialProviderService"; + + @CallSuper + @Override + public void onCreate() { + super.onCreate(); + mHandler = new Handler(Looper.getMainLooper(), null, true); + } + + @Override + public final @NonNull IBinder onBind(@NonNull Intent intent) { + if (SERVICE_INTERFACE.equals(intent.getAction())) { + return mInterface.asBinder(); + } + Log.i(TAG, "Failed to bind with intent: " + intent); + return null; + } + + private final ICredentialProviderService mInterface = new ICredentialProviderService.Stub() { + @Override + public void onGetCredentials(GetCredentialsRequest request, ICancellationSignal transport, + IGetCredentialsCallback callback) throws RemoteException { + Objects.requireNonNull(request); + Objects.requireNonNull(callback); + + mHandler.sendMessage(obtainMessage( + CredentialProviderService::onGetCredentials, + CredentialProviderService.this, request, + CancellationSignal.fromTransport(transport), + new GetCredentialsCallback(callback) + )); + } + }; + + /** + * Called by the android system to retrieve user credentials from the connected provider + * service. + * @param request The credential request for the provider to handle. + * @param cancellationSignal Signal for providers to listen to any cancellation requests from + * the android system. + * @param callback Object used to relay the response of the credentials request. + */ + public abstract void onGetCredentials(@NonNull GetCredentialsRequest request, + @NonNull CancellationSignal cancellationSignal, + @NonNull GetCredentialsCallback callback); +} diff --git a/core/java/android/service/credentials/CredentialsDisplayContent.java b/core/java/android/service/credentials/CredentialsDisplayContent.java new file mode 100644 index 0000000000000..106f322c4ab4d --- /dev/null +++ b/core/java/android/service/credentials/CredentialsDisplayContent.java @@ -0,0 +1,188 @@ +/* + * 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.Nullable; +import android.os.Parcel; +import android.os.Parcelable; + +import androidx.annotation.NonNull; + +import com.android.internal.util.Preconditions; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +/** + * Content to be displayed on the account selector UI, including credential entries, + * actions etc. + * + * @hide + */ +public final class CredentialsDisplayContent implements Parcelable { + /** Header to be displayed on the UI. */ + private final @Nullable CharSequence mHeader; + + /** List of credential entries to be displayed on the UI. */ + private final @NonNull List mCredentialEntries; + + /** List of provider actions to be displayed on the UI. */ + private final @NonNull List mActions; + + private CredentialsDisplayContent(@Nullable CharSequence header, + @NonNull List credentialEntries, + @NonNull List actions) { + mHeader = header; + mCredentialEntries = credentialEntries; + mActions = actions; + } + + private CredentialsDisplayContent(@NonNull Parcel in) { + mHeader = in.readCharSequence(); + mCredentialEntries = in.createTypedArrayList(CredentialEntry.CREATOR); + mActions = in.createTypedArrayList(Action.CREATOR); + } + + public static final @NonNull Creator CREATOR = + new Creator() { + @Override + public CredentialsDisplayContent createFromParcel(@NonNull Parcel in) { + return new CredentialsDisplayContent(in); + } + + @Override + public CredentialsDisplayContent[] newArray(int size) { + return new CredentialsDisplayContent[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeCharSequence(mHeader); + dest.writeTypedList(mCredentialEntries); + dest.writeTypedList(mActions); + } + + /** + * Returns the header to be displayed on the UI. + */ + public @Nullable CharSequence getHeader() { + return mHeader; + } + + /** + * Returns the list of credential entries to be displayed on the UI. + */ + public @NonNull List getCredentialEntries() { + return mCredentialEntries; + } + + /** + * Returns the list of actions to be displayed on the UI. + */ + public @NonNull List getActions() { + return mActions; + } + + /** + * Builds an instance of {@link CredentialsDisplayContent}. + */ + public static final class Builder { + private CharSequence mHeader = null; + private List mCredentialEntries = new ArrayList<>(); + private List mActions = new ArrayList<>(); + + /** + * Sets the header to be displayed on the UI. + */ + public @NonNull Builder setHeader(@Nullable CharSequence header) { + mHeader = header; + return this; + } + + /** + * Adds a {@link CredentialEntry} to the list of entries to be displayed on + * the UI. + * + * @throws NullPointerException If the {@code credentialEntry} is null. + */ + public @NonNull Builder addCredentialEntry(@NonNull CredentialEntry credentialEntry) { + mCredentialEntries.add(Objects.requireNonNull(credentialEntry)); + return this; + } + + /** + * Adds an {@link Action} to the list of actions to be displayed on + * the UI. + * + * @throws NullPointerException If {@code action} is null. + */ + public @NonNull Builder addAction(@NonNull Action action) { + mActions.add(Objects.requireNonNull(action, "action must not be null")); + return this; + } + + /** + * Sets the list of actions to be displayed on the UI. + * + * @throws NullPointerException If {@code actions} is null, or any of its elements + * is null. + */ + public @NonNull Builder setActions(@NonNull List actions) { + mActions = Preconditions.checkCollectionElementsNotNull(actions, + "actions"); + return this; + } + + /** + * Sets the list of credential entries to be displayed on the + * account selector UI. + * + * @throws NullPointerException If {@code credentialEntries} is null, or any of its + * elements is null. + */ + public @NonNull Builder setCredentialEntries( + @NonNull List credentialEntries) { + mCredentialEntries = Preconditions.checkCollectionElementsNotNull( + credentialEntries, + "credentialEntries"); + return this; + } + + /** + * Builds a {@link GetCredentialsResponse} instance. + * + * @throws NullPointerException If {@code credentialEntries} is null. + * @throws IllegalStateException if both {@code credentialEntries} and + * {@code actions} are empty. + */ + public @NonNull CredentialsDisplayContent build() { + if (mCredentialEntries != null && mCredentialEntries.isEmpty() + && mActions != null && mActions.isEmpty()) { + throw new IllegalStateException("credentialEntries and actions must not both " + + "be empty"); + } + return new CredentialsDisplayContent(mHeader, mCredentialEntries, mActions); + } + } +} diff --git a/core/java/android/service/credentials/GetCredentialOption.java b/core/java/android/service/credentials/GetCredentialOption.java new file mode 100644 index 0000000000000..c6cda1d1abf6f --- /dev/null +++ b/core/java/android/service/credentials/GetCredentialOption.java @@ -0,0 +1,96 @@ +/* + * 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.os.Bundle; +import android.os.Parcel; +import android.os.Parcelable; + +import com.android.internal.util.Preconditions; + +import static java.util.Objects.requireNonNull; + +/** + * A type specific credential request, containing the associated data to be used for + * retrieving credentials. + * + * @hide + */ +public final class GetCredentialOption implements Parcelable { + /** The type of credential requested. */ + private final @NonNull String mType; + + /** The data associated with the request. */ + private final @NonNull Bundle mData; + + /** + * Constructs a new instance of {@link GetCredentialOption} + * + * @throws IllegalArgumentException If {@code type} string is null or empty. + * @throws NullPointerException If {@code data} is null. + */ + public GetCredentialOption(@NonNull String type, @NonNull Bundle data) { + Preconditions.checkStringNotEmpty(type, "type must not be null, or empty"); + requireNonNull(data, "data must not be null"); + mType = type; + mData = data; + } + + /** + * Returns the data associated with this credential request option. + */ + public @NonNull Bundle getData() { + return mData; + } + + /** + * Returns the type associated with this credential request option. + */ + public @NonNull String getType() { + return mType; + } + + private GetCredentialOption(@NonNull Parcel in) { + mType = in.readString16NoHelper(); + mData = in.readBundle(); + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeString16NoHelper(mType); + dest.writeBundle(mData); + } + + @Override + public int describeContents() { + return 0; + } + + public static final @NonNull Creator CREATOR = + new Creator() { + @Override + public GetCredentialOption createFromParcel(@NonNull Parcel in) { + return new GetCredentialOption(in); + } + + @Override + public GetCredentialOption[] newArray(int size) { + return new GetCredentialOption[size]; + } + }; +} diff --git a/core/java/android/service/credentials/GetCredentialsCallback.java b/core/java/android/service/credentials/GetCredentialsCallback.java new file mode 100644 index 0000000000000..0e81b8f14fe48 --- /dev/null +++ b/core/java/android/service/credentials/GetCredentialsCallback.java @@ -0,0 +1,65 @@ +/* + * 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.Nullable; +import android.os.RemoteException; +import android.util.Log; + +/** + * Callback to be invoked as a response to {@link GetCredentialsRequest}. + * + * @hide + */ +public final class GetCredentialsCallback { + + private static final String TAG = "GetCredentialsCallback"; + + private final IGetCredentialsCallback mCallback; + + /** @hide */ + public GetCredentialsCallback(@NonNull IGetCredentialsCallback callback) { + mCallback = callback; + } + + /** + * Invoked on a successful response for {@link GetCredentialsRequest} + * @param response The response from the credential provider. + */ + public void onSuccess(@Nullable GetCredentialsResponse response) { + try { + mCallback.onSuccess(response); + } catch (RemoteException e) { + e.rethrowAsRuntimeException(); + } + } + + /** + * Invoked on a failure response for {@link GetCredentialsRequest} + * @param errorCode The code defining the kind of error. + * @param message The message corresponding to the failure. + */ + public void onFailure(int errorCode, @Nullable CharSequence message) { + Log.w(TAG, "onFailure: " + message); + try { + mCallback.onFailure(errorCode, message); + } catch (RemoteException e) { + e.rethrowAsRuntimeException(); + } + } +} diff --git a/core/java/android/service/credentials/GetCredentialsRequest.aidl b/core/java/android/service/credentials/GetCredentialsRequest.aidl new file mode 100644 index 0000000000000..b309d698e7de7 --- /dev/null +++ b/core/java/android/service/credentials/GetCredentialsRequest.aidl @@ -0,0 +1,3 @@ +package android.service.credentials; + +parcelable GetCredentialsRequest; \ No newline at end of file diff --git a/core/java/android/service/credentials/GetCredentialsRequest.java b/core/java/android/service/credentials/GetCredentialsRequest.java new file mode 100644 index 0000000000000..cf7c2834f75f6 --- /dev/null +++ b/core/java/android/service/credentials/GetCredentialsRequest.java @@ -0,0 +1,156 @@ +/* + * 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.os.Parcel; +import android.os.Parcelable; + +import androidx.annotation.NonNull; + +import com.android.internal.util.Preconditions; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +/** + * Request for getting user's credentials from a given credential provider. + * + * @hide + */ +public final class GetCredentialsRequest implements Parcelable { + /** Calling package of the app requesting for credentials. */ + private final @NonNull String mCallingPackage; + + /** + * List of credential options. Each {@link GetCredentialOption} object holds parameters to + * be used for retrieving specific type of credentials. + */ + private final @NonNull List mGetCredentialOptions; + + private GetCredentialsRequest(@NonNull String callingPackage, + @NonNull List getCredentialOptions) { + this.mCallingPackage = callingPackage; + this.mGetCredentialOptions = getCredentialOptions; + } + + private GetCredentialsRequest(@NonNull Parcel in) { + mCallingPackage = in.readString16NoHelper(); + mGetCredentialOptions = in.createTypedArrayList(GetCredentialOption.CREATOR); + } + + public static final @NonNull Creator CREATOR = + new Creator() { + @Override + public GetCredentialsRequest createFromParcel(Parcel in) { + return new GetCredentialsRequest(in); + } + + @Override + public GetCredentialsRequest[] newArray(int size) { + return new GetCredentialsRequest[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeString16NoHelper(mCallingPackage); + dest.writeTypedList(mGetCredentialOptions); + } + + /** + * Returns the calling package of the app requesting credentials. + */ + public @NonNull String getCallingPackage() { + return mCallingPackage; + } + + /** + * Returns the list of type specific credential options to return credentials for. + */ + public @NonNull List getGetCredentialOptions() { + return mGetCredentialOptions; + } + + /** + * Builder for {@link GetCredentialsRequest}. + */ + public static final class Builder { + private String mCallingPackage; + private List mGetCredentialOptions = new ArrayList<>(); + + /** + * Creates a new builder. + * @param callingPackage The calling package of the app requesting credentials. + * + * @throws IllegalArgumentException If {@code callingPackag}e is null or empty. + */ + public Builder(@NonNull String callingPackage) { + mCallingPackage = Preconditions.checkStringNotEmpty(callingPackage); + } + + /** + * Sets the list of credential options. + * + * @throws NullPointerException If {@code getCredentialOptions} itself or any of its + * elements is null. + * @throws IllegalArgumentException If {@code getCredentialOptions} is empty. + */ + public @NonNull Builder setGetCredentialOptions( + @NonNull List getCredentialOptions) { + Preconditions.checkCollectionNotEmpty(mGetCredentialOptions, + "getCredentialOptions"); + Preconditions.checkCollectionElementsNotNull(mGetCredentialOptions, + "getCredentialOptions"); + mGetCredentialOptions = getCredentialOptions; + return this; + } + + /** + * Adds a single {@link GetCredentialOption} object to the list of credential options. + * + * @throws NullPointerException If {@code getCredentialOption} is null. + */ + public @NonNull Builder addGetCredentialOption( + @NonNull GetCredentialOption getCredentialOption) { + Objects.requireNonNull(getCredentialOption, + "getCredentialOption must not be null"); + mGetCredentialOptions.add(getCredentialOption); + return this; + } + + /** + * Builds a new {@link GetCredentialsRequest} instance. + * + * @throws NullPointerException If {@code getCredentialOptions} is null. + * @throws IllegalArgumentException If {@code getCredentialOptions} is empty, or if + * {@code callingPackage} is null or empty. + */ + public @NonNull GetCredentialsRequest build() { + Preconditions.checkStringNotEmpty(mCallingPackage, + "Must set the calling package"); + Preconditions.checkCollectionNotEmpty(mGetCredentialOptions, + "getCredentialOptions"); + return new GetCredentialsRequest(mCallingPackage, mGetCredentialOptions); + } + } +} diff --git a/core/java/android/service/credentials/GetCredentialsResponse.aidl b/core/java/android/service/credentials/GetCredentialsResponse.aidl new file mode 100644 index 0000000000000..0d8c6357a7155 --- /dev/null +++ b/core/java/android/service/credentials/GetCredentialsResponse.aidl @@ -0,0 +1,3 @@ +package android.service.credentials; + +parcelable GetCredentialsResponse; \ No newline at end of file diff --git a/core/java/android/service/credentials/GetCredentialsResponse.java b/core/java/android/service/credentials/GetCredentialsResponse.java new file mode 100644 index 0000000000000..293867ba55b28 --- /dev/null +++ b/core/java/android/service/credentials/GetCredentialsResponse.java @@ -0,0 +1,128 @@ +/* + * 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.Nullable; +import android.os.Parcel; +import android.os.Parcelable; + +import androidx.annotation.NonNull; + +import java.util.Objects; + +/** + * Response from a credential provider, containing credential entries and other associated + * data to be shown on the account selector UI. + * + * @hide + */ +public final class GetCredentialsResponse implements Parcelable { + /** Content to be used for the UI. */ + private final @Nullable CredentialsDisplayContent mCredentialsDisplayContent; + + /** + * Authentication action that must be launched and completed before showing any content + * from the provider. + */ + private final @Nullable Action mAuthenticationAction; + + /** + * Creates a {@link GetCredentialsRequest} instance with an authentication action set. + * Providers must use this method when no content can be shown before authentication. + * + * @throws NullPointerException If {@code authenticationAction} is null. + */ + public static @NonNull GetCredentialsResponse createWithAuthentication( + @NonNull Action authenticationAction) { + Objects.requireNonNull(authenticationAction, + "authenticationAction must not be null"); + return new GetCredentialsResponse(null, authenticationAction); + } + + /** + * Creates a {@link GetCredentialsRequest} instance with display content to be shown on the UI. + * Providers must use this method when there is content to be shown without top level + * authentication required. + * + * @throws NullPointerException If {@code credentialsDisplayContent} is null. + */ + public static @NonNull GetCredentialsResponse createWithDisplayContent( + @NonNull CredentialsDisplayContent credentialsDisplayContent) { + Objects.requireNonNull(credentialsDisplayContent, + "credentialsDisplayContent must not be null"); + return new GetCredentialsResponse(credentialsDisplayContent, null); + } + + private GetCredentialsResponse(@Nullable CredentialsDisplayContent credentialsDisplayContent, + @Nullable Action authenticationAction) { + mCredentialsDisplayContent = credentialsDisplayContent; + mAuthenticationAction = authenticationAction; + } + + private GetCredentialsResponse(@NonNull Parcel in) { + mCredentialsDisplayContent = in.readParcelable(CredentialsDisplayContent.class + .getClassLoader(), CredentialsDisplayContent.class); + mAuthenticationAction = in.readParcelable(Action.class.getClassLoader(), Action.class); + } + + public static final @NonNull Creator CREATOR = + new Creator() { + @Override + public GetCredentialsResponse createFromParcel(Parcel in) { + return new GetCredentialsResponse(in); + } + + @Override + public GetCredentialsResponse[] newArray(int size) { + return new GetCredentialsResponse[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeParcelable(mCredentialsDisplayContent, flags); + dest.writeParcelable(mAuthenticationAction, flags); + } + + /** + * Returns whether the response contains a top level authentication action. + */ + public @NonNull boolean isAuthenticationActionSet() { + return mAuthenticationAction != null; + } + + /** + * Returns the authentication action to be invoked before any other content + * can be shown to the user. + */ + public @NonNull Action getAuthenticationAction() { + return mAuthenticationAction; + } + + /** + * Returns the credentialDisplayContent that does not require authentication, and + * can be shown to the user on the account selector UI. + */ + public @NonNull CredentialsDisplayContent getCredentialsDisplayContent() { + return mCredentialsDisplayContent; + } +} diff --git a/core/java/android/service/credentials/ICredentialProviderService.aidl b/core/java/android/service/credentials/ICredentialProviderService.aidl new file mode 100644 index 0000000000000..0b2a9d64174e7 --- /dev/null +++ b/core/java/android/service/credentials/ICredentialProviderService.aidl @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2016 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.os.ICancellationSignal; +import android.service.credentials.GetCredentialsRequest; +import android.service.credentials.IGetCredentialsCallback; + +/** + * Interface from the system to a credential provider service. + * + * @hide + */ +oneway interface ICredentialProviderService { + void onGetCredentials(in GetCredentialsRequest request, in ICancellationSignal transport, in IGetCredentialsCallback callback); +} \ No newline at end of file diff --git a/core/java/android/service/credentials/IGetCredentialsCallback.aidl b/core/java/android/service/credentials/IGetCredentialsCallback.aidl new file mode 100644 index 0000000000000..6e20c555af602 --- /dev/null +++ b/core/java/android/service/credentials/IGetCredentialsCallback.aidl @@ -0,0 +1,13 @@ +package android.service.credentials; + +import android.service.credentials.GetCredentialsResponse; + +/** + * Interface from the system to a credential provider service. + * + * @hide + */ +oneway interface IGetCredentialsCallback { + void onSuccess(in GetCredentialsResponse response); + void onFailure(int errorCode, in CharSequence message); +} \ No newline at end of file