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 from a credential provider, containing credential entries and other associated
35  * data to be shown on the account selector UI.
36  */
37 public final class BeginGetCredentialResponse implements Parcelable {
38     /** List of credential entries to be displayed on the UI. */
39     private final @NonNull ParceledListSlice<CredentialEntry> mCredentialEntries;
40 
41     /** List of authentication entries to be displayed on the UI. */
42     private final @NonNull ParceledListSlice<Action> mAuthenticationEntries;
43 
44     /** List of provider actions to be displayed on the UI. */
45     private final @NonNull ParceledListSlice<Action> mActions;
46 
47     /** Remote credential entry to get the response from a different device. */
48     private final @Nullable RemoteEntry mRemoteCredentialEntry;
49 
50     /**
51      * Creates an empty response instance, to be used when there are no {@link CredentialEntry},
52      * or {@link Action} to return.
53      */
BeginGetCredentialResponse()54     public BeginGetCredentialResponse() {
55         this(/*credentialEntries=*/new ParceledListSlice<>(new ArrayList<>()),
56                 /*authenticationEntries=*/new ParceledListSlice<>(new ArrayList<>()),
57                 /*actions=*/new ParceledListSlice<>(new ArrayList<>()),
58                 /*remoteCredentialEntry=*/null);
59     }
60 
BeginGetCredentialResponse( @onNull ParceledListSlice<CredentialEntry> credentialEntries, @NonNull ParceledListSlice<Action> authenticationEntries, @NonNull ParceledListSlice<Action> actions, @Nullable RemoteEntry remoteCredentialEntry)61     private BeginGetCredentialResponse(
62             @NonNull ParceledListSlice<CredentialEntry> credentialEntries,
63             @NonNull ParceledListSlice<Action> authenticationEntries,
64             @NonNull ParceledListSlice<Action> actions,
65             @Nullable RemoteEntry remoteCredentialEntry) {
66         mCredentialEntries = credentialEntries;
67         mAuthenticationEntries = authenticationEntries;
68         mActions = actions;
69         mRemoteCredentialEntry = remoteCredentialEntry;
70     }
71 
BeginGetCredentialResponse(@onNull Parcel in)72     private BeginGetCredentialResponse(@NonNull Parcel in) {
73         mCredentialEntries = in.readParcelable(
74                 null, android.content.pm.ParceledListSlice.class);
75         mAuthenticationEntries = in.readParcelable(
76                 null, android.content.pm.ParceledListSlice.class);
77         mActions = in.readParcelable(
78                 null, android.content.pm.ParceledListSlice.class);
79         mRemoteCredentialEntry = in.readTypedObject(RemoteEntry.CREATOR);
80     }
81 
82     public static final @NonNull Creator<BeginGetCredentialResponse> CREATOR =
83             new Creator<BeginGetCredentialResponse>() {
84                 @Override
85                 public BeginGetCredentialResponse createFromParcel(@NonNull Parcel in) {
86                     return new BeginGetCredentialResponse(in);
87                 }
88 
89                 @Override
90                 public BeginGetCredentialResponse[] newArray(int size) {
91                     return new BeginGetCredentialResponse[size];
92                 }
93             };
94 
95     @Override
describeContents()96     public int describeContents() {
97         return 0;
98     }
99 
100     @Override
writeToParcel(@onNull Parcel dest, int flags)101     public void writeToParcel(@NonNull Parcel dest, int flags) {
102         dest.writeParcelable(mCredentialEntries, flags);
103         dest.writeParcelable(mAuthenticationEntries, flags);
104         dest.writeParcelable(mActions, flags);
105         dest.writeTypedObject(mRemoteCredentialEntry, flags);
106     }
107 
108     /**
109      * Returns the list of credential entries to be displayed on the UI.
110      */
getCredentialEntries()111     public @NonNull List<CredentialEntry> getCredentialEntries() {
112         return mCredentialEntries.getList();
113     }
114 
115     /**
116      * Returns the list of authentication entries to be displayed on the UI.
117      */
getAuthenticationActions()118     public @NonNull List<Action> getAuthenticationActions() {
119         return mAuthenticationEntries.getList();
120     }
121 
122     /**
123      * Returns the list of actions to be displayed on the UI.
124      */
getActions()125     public @NonNull List<Action> getActions() {
126 
127         return mActions.getList();
128     }
129 
130     /**
131      * Returns the remote credential entry to be displayed on the UI.
132      */
getRemoteCredentialEntry()133     public @Nullable RemoteEntry getRemoteCredentialEntry() {
134         return mRemoteCredentialEntry;
135     }
136 
137     /**
138      * Builds an instance of {@link BeginGetCredentialResponse}.
139      */
140     public static final class Builder {
141         private List<CredentialEntry> mCredentialEntries = new ArrayList<>();
142 
143         private List<Action> mAuthenticationEntries = new ArrayList<>();
144         private List<Action> mActions = new ArrayList<>();
145         private RemoteEntry mRemoteCredentialEntry;
146 
147         /**
148          * Sets a remote credential entry to be shown on the UI. Provider must set this if they
149          * wish to get the credential from a different device.
150          *
151          * <p> When constructing the {@link CredentialEntry} object, the {@code pendingIntent}
152          * must be set such that it leads to an activity that can provide UI to fulfill the request
153          * on a remote device. When user selects this {@code remoteCredentialEntry}, the system will
154          * invoke the {@code pendingIntent} set on the {@link CredentialEntry}.
155          *
156          * <p> Once the remote credential flow is complete, the {@link android.app.Activity}
157          * result should be set to {@link android.app.Activity#RESULT_OK} and an extra with the
158          * {@link CredentialProviderService#EXTRA_GET_CREDENTIAL_RESPONSE} key should be populated
159          * with a {@link android.credentials.Credential} object.
160          *
161          * <p> Note that as a provider service you will only be able to set a remote entry if :
162          * - Provider service possesses the
163          * {@link Manifest.permission#PROVIDE_REMOTE_CREDENTIALS} permission.
164          * - Provider service is configured as the provider that can provide remote entries.
165          *
166          * If the above conditions are not met, setting back {@link BeginGetCredentialResponse}
167          * on the callback from {@link CredentialProviderService#onBeginGetCredential} will
168          * throw a {@link SecurityException}.
169          */
170         @RequiresPermission(Manifest.permission.PROVIDE_REMOTE_CREDENTIALS)
setRemoteCredentialEntry(@ullable RemoteEntry remoteCredentialEntry)171         public @NonNull Builder setRemoteCredentialEntry(@Nullable RemoteEntry
172                 remoteCredentialEntry) {
173             mRemoteCredentialEntry = remoteCredentialEntry;
174             return this;
175         }
176 
177         /**
178          * Adds a {@link CredentialEntry} to the list of entries to be displayed on
179          * the UI.
180          *
181          * @throws NullPointerException If the {@code credentialEntry} is null.
182          */
addCredentialEntry(@onNull CredentialEntry credentialEntry)183         public @NonNull Builder addCredentialEntry(@NonNull CredentialEntry credentialEntry) {
184             mCredentialEntries.add(Objects.requireNonNull(credentialEntry));
185             return this;
186         }
187 
188         /**
189          * Add an authentication entry to be shown on the UI. Providers must set this entry if
190          * the corresponding account is locked and no underlying credentials can be returned.
191          *
192          * <p> When the user selects this {@code authenticationAction}, the system invokes the
193          * corresponding {@code pendingIntent}.
194          * Once the authentication action activity is launched, and the user is authenticated,
195          * providers should create another response with {@link BeginGetCredentialResponse} using
196          * this time adding the unlocked credentials in the form of {@link CredentialEntry}'s.
197          *
198          * <p>The new response object must be set on the authentication activity's
199          * result. The result code should be set to {@link android.app.Activity#RESULT_OK} and
200          * the {@link CredentialProviderService#EXTRA_BEGIN_GET_CREDENTIAL_RESPONSE} extra
201          * should be set with the new fully populated {@link BeginGetCredentialResponse} object.
202          *
203          * @throws NullPointerException If {@code authenticationAction} is null.
204          */
addAuthenticationAction(@onNull Action authenticationAction)205         public @NonNull Builder addAuthenticationAction(@NonNull Action authenticationAction) {
206             mAuthenticationEntries.add(Objects.requireNonNull(authenticationAction));
207             return this;
208         }
209 
210         /**
211          * Adds an {@link Action} to the list of actions to be displayed on
212          * the UI.
213          *
214          * <p> An {@code action} must be used for independent user actions,
215          * such as opening the app, intenting directly into a certain app activity etc. The
216          * {@code pendingIntent} set with the {@code action} must invoke the corresponding
217          * activity.
218          *
219          * @throws NullPointerException If {@code action} is null.
220          */
addAction(@onNull Action action)221         public @NonNull Builder addAction(@NonNull Action action) {
222             mActions.add(Objects.requireNonNull(action, "action must not be null"));
223             return this;
224         }
225 
226         /**
227          * Sets the list of actions to be displayed on the UI.
228          *
229          * @throws NullPointerException If {@code actions} is null, or any of its elements
230          *                              is null.
231          */
setActions(@onNull List<Action> actions)232         public @NonNull Builder setActions(@NonNull List<Action> actions) {
233             mActions = Preconditions.checkCollectionElementsNotNull(actions,
234                     "actions");
235             return this;
236         }
237 
238         /**
239          * Sets the list of credential entries to be displayed on the
240          * account selector UI.
241          *
242          * @throws NullPointerException If {@code credentialEntries} is null, or any of its
243          *                              elements is null.
244          */
setCredentialEntries( @onNull List<CredentialEntry> credentialEntries)245         public @NonNull Builder setCredentialEntries(
246                 @NonNull List<CredentialEntry> credentialEntries) {
247             mCredentialEntries = Preconditions.checkCollectionElementsNotNull(
248                     credentialEntries,
249                     "credentialEntries");
250             return this;
251         }
252 
253         /**
254          * Sets the list of authentication entries to be displayed on the
255          * account selector UI.
256          *
257          * @throws NullPointerException If {@code authenticationEntries} is null, or any of its
258          *                              elements is null.
259          */
setAuthenticationActions( @onNull List<Action> authenticationActions)260         public @NonNull Builder setAuthenticationActions(
261                 @NonNull List<Action> authenticationActions) {
262             mAuthenticationEntries = Preconditions.checkCollectionElementsNotNull(
263                     authenticationActions,
264                     "authenticationActions");
265             return this;
266         }
267 
268         /**
269          * Builds a {@link BeginGetCredentialResponse} instance.
270          */
build()271         public @NonNull BeginGetCredentialResponse build() {
272             return new BeginGetCredentialResponse(
273                     new ParceledListSlice<>(mCredentialEntries),
274                     new ParceledListSlice<>(mAuthenticationEntries),
275                             new ParceledListSlice<>(mActions),
276                     mRemoteCredentialEntry);
277         }
278     }
279 }
280