Add provider facing CREATE APIs for Credential Manager.
Credential Manager is a set of APIs that allows developers to get and
create credentials. These credentials are sourced from providers that
are registered with the android framework.
Test: Built and Deployed on device locally.
Bug: 247545196
CTS-Coverage-Bug: 247549381
Change-Id: I640259d5c803260740633f489a8bcf3a4168b652
diff --git a/core/java/android/service/credentials/CreateCredentialCallback.java b/core/java/android/service/credentials/CreateCredentialCallback.java
new file mode 100644
index 0000000..6108eea
--- /dev/null
+++ b/core/java/android/service/credentials/CreateCredentialCallback.java
@@ -0,0 +1,64 @@
+/*
+ * 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 CreateCredentialRequest}.
+ *
+ * @hide
+ */
+public final class CreateCredentialCallback {
+ private static final String TAG = "CreateCredentialCallback";
+
+ private final ICreateCredentialCallback mCallback;
+
+ /** @hide */
+ public CreateCredentialCallback(@NonNull ICreateCredentialCallback callback) {
+ mCallback = callback;
+ }
+
+ /**
+ * Invoked on a successful response for {@link CreateCredentialRequest}
+ * @param response The response from the credential provider.
+ */
+ public void onSuccess(@NonNull CreateCredentialResponse response) {
+ try {
+ mCallback.onSuccess(response);
+ } catch (RemoteException e) {
+ e.rethrowAsRuntimeException();
+ }
+ }
+
+ /**
+ * Invoked on a failure response for {@link CreateCredentialRequest}
+ * @param errorCode The code defining the type 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/CreateCredentialRequest.aidl b/core/java/android/service/credentials/CreateCredentialRequest.aidl
new file mode 100644
index 0000000..eb7fba9
--- /dev/null
+++ b/core/java/android/service/credentials/CreateCredentialRequest.aidl
@@ -0,0 +1,3 @@
+package android.service.credentials;
+
+parcelable CreateCredentialRequest;
\ No newline at end of file
diff --git a/core/java/android/service/credentials/CreateCredentialRequest.java b/core/java/android/service/credentials/CreateCredentialRequest.java
new file mode 100644
index 0000000..ac11e04b
--- /dev/null
+++ b/core/java/android/service/credentials/CreateCredentialRequest.java
@@ -0,0 +1,103 @@
+/*
+ * 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 com.android.internal.util.Preconditions;
+
+import java.util.Objects;
+
+/**
+ * Request for creating a credential.
+ *
+ * @hide
+ */
+public final class CreateCredentialRequest implements Parcelable {
+ private final @NonNull String mCallingPackage;
+ private final @NonNull String mType;
+ private final @NonNull Bundle mData;
+
+ /**
+ * Constructs a new instance.
+ *
+ * @throws IllegalArgumentException If {@code callingPackage}, or {@code type} string is
+ * null or empty.
+ * @throws NullPointerException If {@code data} is null.
+ */
+ public CreateCredentialRequest(@NonNull String callingPackage,
+ @NonNull String type, @NonNull Bundle data) {
+ mCallingPackage = Preconditions.checkStringNotEmpty(callingPackage,
+ "callingPackage must not be null or empty");
+ mType = Preconditions.checkStringNotEmpty(type,
+ "type must not be null or empty");
+ mData = Objects.requireNonNull(data, "data must not be null");
+ }
+
+ private CreateCredentialRequest(@NonNull Parcel in) {
+ mCallingPackage = in.readString8();
+ mType = in.readString8();
+ mData = in.readBundle();
+ }
+
+ public static final @NonNull Creator<CreateCredentialRequest> CREATOR =
+ new Creator<CreateCredentialRequest>() {
+ @Override
+ public CreateCredentialRequest createFromParcel(@NonNull Parcel in) {
+ return new CreateCredentialRequest(in);
+ }
+
+ @Override
+ public CreateCredentialRequest[] newArray(int size) {
+ return new CreateCredentialRequest[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeString8(mCallingPackage);
+ dest.writeString8(mType);
+ dest.writeBundle(mData);
+ }
+
+ /** Returns the calling package of the calling app. */
+ @NonNull
+ public String getCallingPackage() {
+ return mCallingPackage;
+ }
+
+ /** Returns the type of the credential to be created. */
+ @NonNull
+ public String getType() {
+ return mType;
+ }
+
+ /** Returns the data to be used while creating the credential. */
+ @NonNull
+ public Bundle getData() {
+ return mData;
+ }
+}
diff --git a/core/java/android/service/credentials/CreateCredentialResponse.aidl b/core/java/android/service/credentials/CreateCredentialResponse.aidl
new file mode 100644
index 0000000..73c9147
--- /dev/null
+++ b/core/java/android/service/credentials/CreateCredentialResponse.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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;
+
+parcelable CreateCredentialResponse;
diff --git a/core/java/android/service/credentials/CreateCredentialResponse.java b/core/java/android/service/credentials/CreateCredentialResponse.java
new file mode 100644
index 0000000..f2ad7272
--- /dev/null
+++ b/core/java/android/service/credentials/CreateCredentialResponse.java
@@ -0,0 +1,140 @@
+/*
+ * 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;
+
+/**
+ * Response to a {@link CreateCredentialRequest}.
+ *
+ * @hide
+ */
+public final class CreateCredentialResponse implements Parcelable {
+ private final @Nullable CharSequence mHeader;
+ private final @NonNull List<SaveEntry> mSaveEntries;
+
+ private CreateCredentialResponse(@NonNull Parcel in) {
+ mHeader = in.readCharSequence();
+ mSaveEntries = in.createTypedArrayList(SaveEntry.CREATOR);
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeCharSequence(mHeader);
+ dest.writeTypedList(mSaveEntries);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ public static final @NonNull Creator<CreateCredentialResponse> CREATOR =
+ new Creator<CreateCredentialResponse>() {
+ @Override
+ public CreateCredentialResponse createFromParcel(@NonNull Parcel in) {
+ return new CreateCredentialResponse(in);
+ }
+
+ @Override
+ public CreateCredentialResponse[] newArray(int size) {
+ return new CreateCredentialResponse[size];
+ }
+ };
+
+ /* package-private */ CreateCredentialResponse(
+ @Nullable CharSequence header,
+ @NonNull List<SaveEntry> saveEntries) {
+ this.mHeader = header;
+ this.mSaveEntries = saveEntries;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mSaveEntries);
+ }
+
+ /** Returns the header to be displayed on the UI. */
+ public @Nullable CharSequence getHeader() {
+ return mHeader;
+ }
+
+ /** Returns the list of save entries to be displayed on the UI. */
+ public @NonNull List<SaveEntry> getSaveEntries() {
+ return mSaveEntries;
+ }
+
+ /**
+ * A builder for {@link CreateCredentialResponse}
+ */
+ @SuppressWarnings("WeakerAccess")
+ public static final class Builder {
+
+ private @Nullable CharSequence mHeader;
+ private @NonNull List<SaveEntry> mSaveEntries = new ArrayList<>();
+
+ /** Sets the header to be displayed on the UI. */
+ public @NonNull Builder setHeader(@Nullable CharSequence header) {
+ mHeader = header;
+ return this;
+ }
+
+ /**
+ * Sets the list of save entries to be shown on the UI.
+ *
+ * @throws IllegalArgumentException If {@code saveEntries} is empty.
+ * @throws NullPointerException If {@code saveEntries} is null, or any of its elements
+ * are null.
+ */
+ public @NonNull Builder setSaveEntries(@NonNull List<SaveEntry> saveEntries) {
+ Preconditions.checkCollectionNotEmpty(saveEntries, "saveEntries");
+ mSaveEntries = Preconditions.checkCollectionElementsNotNull(
+ saveEntries, "saveEntries");
+ return this;
+ }
+
+ /**
+ * Adds an entry to the list of save entries to be shown on the UI.
+ *
+ * @throws NullPointerException If {@code saveEntry} is null.
+ */
+ public @NonNull Builder addSaveEntry(@NonNull SaveEntry saveEntry) {
+ mSaveEntries.add(Objects.requireNonNull(saveEntry));
+ return this;
+ }
+
+ /**
+ * Builds the instance.
+ *
+ * @throws IllegalArgumentException If {@code saveEntries} is empty.
+ */
+ public @NonNull CreateCredentialResponse build() {
+ Preconditions.checkCollectionNotEmpty(mSaveEntries, "saveEntries must "
+ + "not be empty");
+ return new CreateCredentialResponse(
+ mHeader,
+ mSaveEntries);
+ }
+ }
+}
diff --git a/core/java/android/service/credentials/CredentialProviderService.java b/core/java/android/service/credentials/CredentialProviderService.java
index e2a495d..1fe89df 100644
--- a/core/java/android/service/credentials/CredentialProviderService.java
+++ b/core/java/android/service/credentials/CredentialProviderService.java
@@ -66,6 +66,7 @@
public void onGetCredentials(GetCredentialsRequest request, ICancellationSignal transport,
IGetCredentialsCallback callback) throws RemoteException {
Objects.requireNonNull(request);
+ Objects.requireNonNull(transport);
Objects.requireNonNull(callback);
mHandler.sendMessage(obtainMessage(
@@ -75,6 +76,22 @@
new GetCredentialsCallback(callback)
));
}
+
+ @Override
+ public void onCreateCredential(CreateCredentialRequest request,
+ ICancellationSignal transport, ICreateCredentialCallback callback)
+ throws RemoteException {
+ Objects.requireNonNull(request);
+ Objects.requireNonNull(transport);
+ Objects.requireNonNull(callback);
+
+ mHandler.sendMessage(obtainMessage(
+ CredentialProviderService::onCreateCredential,
+ CredentialProviderService.this, request,
+ CancellationSignal.fromTransport(transport),
+ new CreateCredentialCallback(callback)
+ ));
+ }
};
/**
@@ -88,4 +105,15 @@
public abstract void onGetCredentials(@NonNull GetCredentialsRequest request,
@NonNull CancellationSignal cancellationSignal,
@NonNull GetCredentialsCallback callback);
+
+ /**
+ * Called by the android system to create a credential.
+ * @param request The credential creation 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 credential creation request.
+ */
+ public abstract void onCreateCredential(@NonNull CreateCredentialRequest request,
+ @NonNull CancellationSignal cancellationSignal,
+ @NonNull CreateCredentialCallback callback);
}
diff --git a/core/java/android/service/credentials/GetCredentialsCallback.java b/core/java/android/service/credentials/GetCredentialsCallback.java
index 0e81b8f..42a7394 100644
--- a/core/java/android/service/credentials/GetCredentialsCallback.java
+++ b/core/java/android/service/credentials/GetCredentialsCallback.java
@@ -41,7 +41,7 @@
* Invoked on a successful response for {@link GetCredentialsRequest}
* @param response The response from the credential provider.
*/
- public void onSuccess(@Nullable GetCredentialsResponse response) {
+ public void onSuccess(@NonNull GetCredentialsResponse response) {
try {
mCallback.onSuccess(response);
} catch (RemoteException e) {
diff --git a/core/java/android/service/credentials/ICreateCredentialCallback.aidl b/core/java/android/service/credentials/ICreateCredentialCallback.aidl
new file mode 100644
index 0000000..4cc76a4
--- /dev/null
+++ b/core/java/android/service/credentials/ICreateCredentialCallback.aidl
@@ -0,0 +1,13 @@
+package android.service.credentials;
+
+import android.service.credentials.CreateCredentialResponse;
+
+/**
+ * Interface from the system to a credential provider service.
+ *
+ * @hide
+ */
+oneway interface ICreateCredentialCallback {
+ void onSuccess(in CreateCredentialResponse request);
+ void onFailure(int errorCode, in CharSequence message);
+}
\ No newline at end of file
diff --git a/core/java/android/service/credentials/ICredentialProviderService.aidl b/core/java/android/service/credentials/ICredentialProviderService.aidl
index 0b2a9d6..c68430c 100644
--- a/core/java/android/service/credentials/ICredentialProviderService.aidl
+++ b/core/java/android/service/credentials/ICredentialProviderService.aidl
@@ -18,7 +18,9 @@
import android.os.ICancellationSignal;
import android.service.credentials.GetCredentialsRequest;
+import android.service.credentials.CreateCredentialRequest;
import android.service.credentials.IGetCredentialsCallback;
+import android.service.credentials.ICreateCredentialCallback;
/**
* Interface from the system to a credential provider service.
@@ -27,4 +29,5 @@
*/
oneway interface ICredentialProviderService {
void onGetCredentials(in GetCredentialsRequest request, in ICancellationSignal transport, in IGetCredentialsCallback callback);
-}
\ No newline at end of file
+ void onCreateCredential(in CreateCredentialRequest request, in ICancellationSignal transport, in ICreateCredentialCallback callback);
+}
diff --git a/core/java/android/service/credentials/SaveEntry.java b/core/java/android/service/credentials/SaveEntry.java
new file mode 100644
index 0000000..28fec30
--- /dev/null
+++ b/core/java/android/service/credentials/SaveEntry.java
@@ -0,0 +1,162 @@
+/*
+ * 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;
+
+/**
+ * An entry to be shown on the UI. This entry represents where the credential to be created will
+ * be stored. Examples include user's account, family group etc.
+ *
+ * @hide
+ */
+public final class SaveEntry implements Parcelable {
+ private final @NonNull Slice mInfo;
+ private final @Nullable PendingIntent mPendingIntent;
+ private final @Nullable Credential mCredential;
+
+ private SaveEntry(@NonNull Parcel in) {
+ mInfo = in.readParcelable(Slice.class.getClassLoader(), Slice.class);
+ mPendingIntent = in.readParcelable(PendingIntent.class.getClassLoader(),
+ PendingIntent.class);
+ mCredential = in.readParcelable(Credential.class.getClassLoader(), Credential.class);
+ }
+
+ public static final @NonNull Creator<SaveEntry> CREATOR = new Creator<SaveEntry>() {
+ @Override
+ public SaveEntry createFromParcel(@NonNull Parcel in) {
+ return new SaveEntry(in);
+ }
+
+ @Override
+ public SaveEntry[] newArray(int size) {
+ return new SaveEntry[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ mInfo.writeToParcel(dest, flags);
+ mPendingIntent.writeToParcel(dest, flags);
+ mCredential.writeToParcel(dest, flags);
+ }
+
+ /* package-private */ SaveEntry(
+ @NonNull Slice info,
+ @Nullable PendingIntent pendingIntent,
+ @Nullable Credential credential) {
+ this.mInfo = info;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mInfo);
+ this.mPendingIntent = pendingIntent;
+ this.mCredential = credential;
+ }
+
+ /** Returns the info to be displayed with this save entry on the UI. */
+ public @NonNull Slice getInfo() {
+ return mInfo;
+ }
+
+ /** Returns the pendingIntent to be invoked when this save entry on the UI is selectcd. */
+ public @Nullable PendingIntent getPendingIntent() {
+ return mPendingIntent;
+ }
+
+ /** Returns the credential produced by the {@link CreateCredentialRequest}. */
+ public @Nullable Credential getCredential() {
+ return mCredential;
+ }
+
+ /**
+ * A builder for {@link SaveEntry}.
+ */
+ public static final class Builder {
+
+ private @NonNull Slice mInfo;
+ private @Nullable PendingIntent mPendingIntent;
+ private @Nullable Credential mCredential;
+
+ /**
+ * Builds the instance.
+ * @param info The info to be displayed with this save entry.
+ *
+ * @throws NullPointerException If {@code info} is null.
+ */
+ public Builder(@NonNull Slice info) {
+ mInfo = Objects.requireNonNull(info, "info must not be null");
+ }
+
+ /**
+ * Sets the pendingIntent to be invoked when this entry is selected by the user.
+ *
+ * @throws IllegalStateException If {@code credential} is already set. Must only set either
+ * {@code credential}, or the {@code pendingIntent}.
+ */
+ public @NonNull Builder setPendingIntent(@Nullable PendingIntent pendingIntent) {
+ Preconditions.checkState(pendingIntent != null
+ && mCredential != null, "credential is already set. Must only set "
+ + "either the pendingIntent or the credential");
+ mPendingIntent = pendingIntent;
+ return this;
+ }
+
+ /**
+ * Sets the credential to be returned when this entry is selected by the user.
+ *
+ * @throws IllegalStateException If {@code pendingIntent} is already set. Must only
+ * set either the {@code pendingIntent}, or {@code credential}.
+ */
+ public @NonNull Builder setCredential(@Nullable Credential credential) {
+ Preconditions.checkState(credential != null && mPendingIntent != null,
+ "pendingIntent is already set. Must only set either the credential "
+ + "or the pendingIntent");
+ mCredential = credential;
+ return this;
+ }
+
+ /**
+ * Builds the instance.
+ *
+ * @throws IllegalStateException if both {@code pendingIntent} and {@code credential}
+ * are null.
+ */
+ public @NonNull SaveEntry build() {
+ Preconditions.checkState(mPendingIntent == null && mCredential == null,
+ "pendingIntent and credential both must not be null. Must set "
+ + "either the pendingIntnet or the credential");
+ return new SaveEntry(
+ mInfo,
+ mPendingIntent,
+ mCredential);
+ }
+ }
+}