1 /*
2  * Copyright (C) 2022 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.service.credentials;
18 
19 import android.Manifest;
20 import android.annotation.NonNull;
21 import android.annotation.Nullable;
22 import android.annotation.RequiresPermission;
23 import android.content.pm.ParceledListSlice;
24 import android.os.Parcel;
25 import android.os.Parcelable;
26 
27 import com.android.internal.util.Preconditions;
28 
29 import java.util.ArrayList;
30 import java.util.List;
31 import java.util.Objects;
32 
33 /**
34  * Response to a {@link BeginCreateCredentialRequest}.
35  */
36 public final class BeginCreateCredentialResponse implements Parcelable {
37     private final @NonNull ParceledListSlice<CreateEntry> mCreateEntries;
38     private final @Nullable RemoteEntry mRemoteCreateEntry;
39 
40     /**
41      * Creates an empty response instance, to be used when there are no {@link CreateEntry}
42      * to return.
43      */
BeginCreateCredentialResponse()44     public BeginCreateCredentialResponse() {
45         this(/*createEntries=*/new ParceledListSlice<>(new ArrayList<>()),
46                 /*remoteCreateEntry=*/null);
47     }
48 
BeginCreateCredentialResponse(@onNull Parcel in)49     private BeginCreateCredentialResponse(@NonNull Parcel in) {
50         mCreateEntries = in.readParcelable(
51                 null, android.content.pm.ParceledListSlice.class);
52         mRemoteCreateEntry = in.readTypedObject(RemoteEntry.CREATOR);
53     }
54 
55     @Override
writeToParcel(@onNull Parcel dest, int flags)56     public void writeToParcel(@NonNull Parcel dest, int flags) {
57         dest.writeParcelable(mCreateEntries, flags);
58         dest.writeTypedObject(mRemoteCreateEntry, flags);
59     }
60 
61     @Override
describeContents()62     public int describeContents() {
63         return 0;
64     }
65 
66     public static final @NonNull Creator<BeginCreateCredentialResponse> CREATOR =
67             new Creator<BeginCreateCredentialResponse>() {
68                 @Override
69                 public BeginCreateCredentialResponse createFromParcel(@NonNull Parcel in) {
70                     return new BeginCreateCredentialResponse(in);
71                 }
72 
73                 @Override
74                 public BeginCreateCredentialResponse[] newArray(int size) {
75                     return new BeginCreateCredentialResponse[size];
76                 }
77             };
78 
BeginCreateCredentialResponse( @onNull ParceledListSlice<CreateEntry> createEntries, @Nullable RemoteEntry remoteCreateEntry)79     /* package-private */ BeginCreateCredentialResponse(
80             @NonNull ParceledListSlice<CreateEntry> createEntries,
81             @Nullable RemoteEntry remoteCreateEntry) {
82         this.mCreateEntries = createEntries;
83         com.android.internal.util.AnnotationValidations.validate(
84                 NonNull.class, null, mCreateEntries);
85         this.mRemoteCreateEntry = remoteCreateEntry;
86     }
87 
88     /** Returns the list of create entries to be displayed on the UI. */
getCreateEntries()89     public @NonNull List<CreateEntry> getCreateEntries() {
90         return mCreateEntries.getList();
91     }
92 
93     /** Returns the remote create entry to be displayed on the UI. */
getRemoteCreateEntry()94     public @Nullable RemoteEntry getRemoteCreateEntry() {
95         return mRemoteCreateEntry;
96     }
97 
98     /**
99      * A builder for {@link BeginCreateCredentialResponse}
100      */
101     @SuppressWarnings("WeakerAccess") /* synthetic access */
102     public static final class Builder {
103         private @NonNull List<CreateEntry> mCreateEntries = new ArrayList<>();
104         private @Nullable RemoteEntry mRemoteCreateEntry;
105 
106         /**
107          * Sets the list of create entries to be shown on the UI.
108          *
109          * @throws IllegalArgumentException If {@code createEntries} is empty.
110          * @throws NullPointerException If {@code createEntries} is null, or any of its elements
111          * are null.
112          */
setCreateEntries(@onNull List<CreateEntry> createEntries)113         public @NonNull Builder setCreateEntries(@NonNull List<CreateEntry> createEntries) {
114             Preconditions.checkCollectionNotEmpty(createEntries, "createEntries");
115             mCreateEntries = Preconditions.checkCollectionElementsNotNull(
116                     createEntries, "createEntries");
117             return this;
118         }
119 
120         /**
121          * Adds an entry to the list of create entries to be shown on the UI.
122          *
123          * @throws NullPointerException If {@code createEntry} is null.
124          */
addCreateEntry(@onNull CreateEntry createEntry)125         public @NonNull Builder addCreateEntry(@NonNull CreateEntry createEntry) {
126             mCreateEntries.add(Objects.requireNonNull(createEntry));
127             return this;
128         }
129 
130         /**
131          * Sets a remote create entry to be shown on the UI. Provider must set this entry if they
132          * wish to create the credential on a different device.
133          *
134          * <p> When constructing the {@link CreateEntry} object, the {@code pendingIntent} must be
135          * set such that it leads to an activity that can provide UI to fulfill the request on
136          * a remote device. When user selects this {@code remoteCreateEntry}, the system will
137          * invoke the {@code pendingIntent} set on the {@link CreateEntry}.
138          *
139          * <p> Once the remote credential flow is complete, the {@link android.app.Activity}
140          * result should be set to {@link android.app.Activity#RESULT_OK} and an extra with the
141          * {@link CredentialProviderService#EXTRA_CREATE_CREDENTIAL_RESPONSE} key should be populated
142          * with a {@link android.credentials.CreateCredentialResponse} object.
143          *
144          * <p> Note that as a provider service you will only be able to set a remote entry if :
145          * - Provider service possesses the
146          * {@link Manifest.permission#PROVIDE_REMOTE_CREDENTIALS} permission.
147          * - Provider service is configured as the provider that can provide remote entries.
148          *
149          * If the above conditions are not met, setting back {@link BeginCreateCredentialResponse}
150          * on the callback from {@link CredentialProviderService#onBeginCreateCredential}
151          * will throw a {@link SecurityException}.
152          */
153         @RequiresPermission(Manifest.permission.PROVIDE_REMOTE_CREDENTIALS)
setRemoteCreateEntry(@ullable RemoteEntry remoteCreateEntry)154         public @NonNull Builder setRemoteCreateEntry(@Nullable RemoteEntry remoteCreateEntry) {
155             mRemoteCreateEntry = remoteCreateEntry;
156             return this;
157         }
158 
159         /**
160          * Builds a new instance of {@link BeginCreateCredentialResponse}.
161          */
build()162         public @NonNull BeginCreateCredentialResponse build() {
163             return new BeginCreateCredentialResponse(
164                     new ParceledListSlice<>(mCreateEntries),
165                     mRemoteCreateEntry);
166         }
167     }
168 }
169