2 * Copyright (C) 2016 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package android.service.autofill;
19 import static android.service.autofill.FillRequest.INVALID_REQUEST_ID;
20 import static android.view.autofill.Helper.sDebug;
22 import android.annotation.NonNull;
23 import android.annotation.Nullable;
24 import android.app.Activity;
25 import android.content.IntentSender;
26 import android.content.pm.ParceledListSlice;
27 import android.os.Bundle;
28 import android.os.Parcel;
29 import android.os.Parcelable;
30 import android.view.autofill.AutofillId;
31 import android.widget.RemoteViews;
33 import java.util.ArrayList;
34 import java.util.Arrays;
35 import java.util.List;
38 * Response for a {@link
39 * AutofillService#onFillRequest(FillRequest, android.os.CancellationSignal, FillCallback)}.
41 * <p>See the main {@link AutofillService} documentation for more details and examples.
43 public final class FillResponse implements Parcelable {
45 private final @Nullable ParceledListSlice<Dataset> mDatasets;
46 private final @Nullable SaveInfo mSaveInfo;
47 private final @Nullable Bundle mClientState;
48 private final @Nullable RemoteViews mPresentation;
49 private final @Nullable IntentSender mAuthentication;
50 private final @Nullable AutofillId[] mAuthenticationIds;
51 private final @Nullable AutofillId[] mIgnoredIds;
52 private int mRequestId;
54 private FillResponse(@NonNull Builder builder) {
55 mDatasets = (builder.mDatasets != null) ? new ParceledListSlice<>(builder.mDatasets) : null;
56 mSaveInfo = builder.mSaveInfo;
57 mClientState = builder.mCLientState;
58 mPresentation = builder.mPresentation;
59 mAuthentication = builder.mAuthentication;
60 mAuthenticationIds = builder.mAuthenticationIds;
61 mIgnoredIds = builder.mIgnoredIds;
62 mRequestId = INVALID_REQUEST_ID;
66 public @Nullable Bundle getClientState() {
71 public @Nullable List<Dataset> getDatasets() {
72 return (mDatasets != null) ? mDatasets.getList() : null;
76 public @Nullable SaveInfo getSaveInfo() {
81 public @Nullable RemoteViews getPresentation() {
86 public @Nullable IntentSender getAuthentication() {
87 return mAuthentication;
91 public @Nullable AutofillId[] getAuthenticationIds() {
92 return mAuthenticationIds;
96 public @Nullable AutofillId[] getIgnoredIds() {
101 * Associates a {@link FillResponse} to a request.
103 * <p>Set inside of the {@link FillCallback} code, not the {@link AutofillService}.
105 * @param requestId The id of the request to associate the response to.
109 public void setRequestId(int requestId) {
110 mRequestId = requestId;
114 public int getRequestId() {
119 * Builder for {@link FillResponse} objects. You must to provide at least
120 * one dataset or set an authentication intent with a presentation view.
122 public static final class Builder {
123 private ArrayList<Dataset> mDatasets;
124 private SaveInfo mSaveInfo;
125 private Bundle mCLientState;
126 private RemoteViews mPresentation;
127 private IntentSender mAuthentication;
128 private AutofillId[] mAuthenticationIds;
129 private AutofillId[] mIgnoredIds;
130 private boolean mDestroyed;
133 * Requires a fill response authentication before autofilling the screen with
134 * any data set in this response.
136 * <p>This is typically useful when a user interaction is required to unlock their
137 * data vault if you encrypt the data set labels and data set data. It is recommended
138 * to encrypt only the sensitive data and not the data set labels which would allow
139 * auth on the data set level leading to a better user experience. Note that if you
140 * use sensitive data as a label, for example an email address, then it should also
141 * be encrypted. The provided {@link android.app.PendingIntent intent} must be an
142 * {@link Activity} which implements your authentication flow. Also if you provide an auth
143 * intent you also need to specify the presentation view to be shown in the fill UI
144 * for the user to trigger your authentication flow.
146 * <p>When a user triggers autofill, the system launches the provided intent
147 * whose extras will have the
148 * {@link android.view.autofill.AutofillManager#EXTRA_ASSIST_STRUCTURE screen
149 * content} and your {@link android.view.autofill.AutofillManager#EXTRA_CLIENT_STATE
150 * client state}. Once you complete your authentication flow you should set the
151 * {@link Activity} result to {@link android.app.Activity#RESULT_OK} and provide the fully
152 * populated {@link FillResponse response} by setting it to the
153 * {@link android.view.autofill.AutofillManager#EXTRA_AUTHENTICATION_RESULT} extra.
154 * For example, if you provided an empty {@link FillResponse resppnse} because the
155 * user's data was locked and marked that the response needs an authentication then
156 * in the response returned if authentication succeeds you need to provide all
157 * available data sets some of which may need to be further authenticated, for
158 * example a credit card whose CVV needs to be entered.
160 * <p>If you provide an authentication intent you must also provide a presentation
161 * which is used to visualize visualize the response for triggering the authentication
164 * <p></><strong>Note:</strong> Do not make the provided pending intent
165 * immutable by using {@link android.app.PendingIntent#FLAG_IMMUTABLE} as the
166 * platform needs to fill in the authentication arguments.
168 * @param authentication Intent to an activity with your authentication flow.
169 * @param presentation The presentation to visualize the response.
170 * @param ids id of Views that when focused will display the authentication UI affordance.
172 * @return This builder.
173 * @throw {@link IllegalArgumentException} if {@code ids} is {@code null} or empty, or if
174 * neither {@code authentication} nor {@code presentation} is non-{@code null}.
176 * @see android.app.PendingIntent#getIntentSender()
178 public @NonNull Builder setAuthentication(@NonNull AutofillId[] ids,
179 @Nullable IntentSender authentication, @Nullable RemoteViews presentation) {
181 if (ids == null || ids.length == 0) {
182 throw new IllegalArgumentException("ids cannot be null or empry");
184 if (authentication == null ^ presentation == null) {
185 throw new IllegalArgumentException("authentication and presentation"
186 + " must be both non-null or null");
188 mAuthentication = authentication;
189 mPresentation = presentation;
190 mAuthenticationIds = ids;
195 * Specifies views that should not trigger new
196 * {@link AutofillService#onFillRequest(FillRequest, android.os.CancellationSignal,
197 * FillCallback)} requests.
199 * <p>This is typically used when the service cannot autofill the view; for example, a
200 * text field representing the result of a Captcha challenge.
202 public Builder setIgnoredIds(AutofillId...ids) {
208 * Adds a new {@link Dataset} to this response.
210 * <p><b>Note: </b> on Android {@link android.os.Build.VERSION_CODES#O}, the total number of
211 * datasets is limited by the Binder transaction size, so it's recommended to keep it
212 * small (in the range of 10-20 at most) and use pagination by adding a fake
213 * {@link Dataset.Builder#setAuthentication(IntentSender) authenticated dataset} at the end
214 * with a presentation string like "Next 10" that would return a new {@link FillResponse}
215 * with the next 10 datasets, and so on. This limitation was lifted on
216 * Android {@link android.os.Build.VERSION_CODES#O_MR1}, although the Binder transaction
217 * size can still be reached if each dataset itself is too big.
219 * @return This builder.
221 public @NonNull Builder addDataset(@Nullable Dataset dataset) {
223 if (dataset == null) {
226 if (mDatasets == null) {
227 mDatasets = new ArrayList<>();
229 if (!mDatasets.add(dataset)) {
236 * Sets the {@link SaveInfo} associated with this response.
238 * @return This builder.
240 public @NonNull Builder setSaveInfo(@NonNull SaveInfo saveInfo) {
242 mSaveInfo = saveInfo;
247 * Sets a {@link Bundle state} that will be passed to subsequent APIs that
248 * manipulate this response. For example, they are passed to subsequent
249 * calls to {@link AutofillService#onFillRequest(FillRequest, android.os.CancellationSignal,
250 * FillCallback)} and {@link AutofillService#onSaveRequest(SaveRequest, SaveCallback)}.
251 * You can use this to store intermediate state that is persistent across multiple
252 * fill requests and the subsequent save request.
254 * <p>If this method is called on multiple {@link FillResponse} objects for the same
255 * screen, just the latest bundle is passed back to the service.
257 * <p>Once a {@link AutofillService#onSaveRequest(SaveRequest, SaveCallback)
258 * save request} is made the client state is cleared.
260 * @param clientState The custom client state.
261 * @return This builder.
263 public Builder setClientState(@Nullable Bundle clientState) {
265 mCLientState = clientState;
270 * Builds a new {@link FillResponse} instance.
272 * <p>You must provide at least one dataset or some savable ids or an authentication with a
275 * @return A built response.
277 public FillResponse build() {
280 if (mAuthentication == null && mDatasets == null && mSaveInfo == null) {
281 throw new IllegalArgumentException("need to provide at least one DataSet or a "
282 + "SaveInfo or an authentication with a presentation");
285 return new FillResponse(this);
288 private void throwIfDestroyed() {
290 throw new IllegalStateException("Already called #build()");
295 /////////////////////////////////////
296 // Object "contract" methods. //
297 /////////////////////////////////////
299 public String toString() {
300 if (!sDebug) return super.toString();
302 // TODO: create a dump() method instead
303 return new StringBuilder(
304 "FillResponse : [mRequestId=" + mRequestId)
305 .append(", datasets=").append(mDatasets)
306 .append(", saveInfo=").append(mSaveInfo)
307 .append(", clientState=").append(mClientState != null)
308 .append(", hasPresentation=").append(mPresentation != null)
309 .append(", hasAuthentication=").append(mAuthentication != null)
310 .append(", authenticationIds=").append(Arrays.toString(mAuthenticationIds))
311 .append(", ignoredIds=").append(Arrays.toString(mIgnoredIds))
316 /////////////////////////////////////
317 // Parcelable "contract" methods. //
318 /////////////////////////////////////
321 public int describeContents() {
326 public void writeToParcel(Parcel parcel, int flags) {
327 parcel.writeParcelable(mDatasets, flags);
328 parcel.writeParcelable(mSaveInfo, flags);
329 parcel.writeParcelable(mClientState, flags);
330 parcel.writeParcelableArray(mAuthenticationIds, flags);
331 parcel.writeParcelable(mAuthentication, flags);
332 parcel.writeParcelable(mPresentation, flags);
333 parcel.writeParcelableArray(mIgnoredIds, flags);
334 parcel.writeInt(mRequestId);
337 public static final Parcelable.Creator<FillResponse> CREATOR =
338 new Parcelable.Creator<FillResponse>() {
340 public FillResponse createFromParcel(Parcel parcel) {
341 // Always go through the builder to ensure the data ingested by
342 // the system obeys the contract of the builder to avoid attacks
343 // using specially crafted parcels.
344 final Builder builder = new Builder();
345 final ParceledListSlice<Dataset> datasetSlice = parcel.readParcelable(null);
346 final List<Dataset> datasets = (datasetSlice != null) ? datasetSlice.getList() : null;
347 final int datasetCount = (datasets != null) ? datasets.size() : 0;
348 for (int i = 0; i < datasetCount; i++) {
349 builder.addDataset(datasets.get(i));
351 builder.setSaveInfo(parcel.readParcelable(null));
352 builder.setClientState(parcel.readParcelable(null));
354 // Sets authentication state.
355 final AutofillId[] authenticationIds = parcel.readParcelableArray(null,
357 final IntentSender authentication = parcel.readParcelable(null);
358 final RemoteViews presentation = parcel.readParcelable(null);
359 if (authenticationIds != null) {
360 builder.setAuthentication(authenticationIds, authentication, presentation);
363 builder.setIgnoredIds(parcel.readParcelableArray(null, AutofillId.class));
364 final FillResponse response = builder.build();
366 response.setRequestId(parcel.readInt());
372 public FillResponse[] newArray(int size) {
373 return new FillResponse[size];