Merge "Add provider facing CREATE APIs for Credential Manager."
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);
+        }
+    }
+}