FillResponse.java revision 013efe173e56612a910ebd8576480ce4ef005e3c
1/*
2 * Copyright (C) 2016 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
17package android.service.autofill;
18
19import static android.view.autofill.Helper.DEBUG;
20
21import android.annotation.NonNull;
22import android.annotation.Nullable;
23import android.content.IntentSender;
24import android.os.Bundle;
25import android.os.Parcel;
26import android.os.Parcelable;
27import android.view.autofill.AutofillId;
28import android.view.autofill.AutofillManager;
29import android.widget.RemoteViews;
30
31import java.util.ArrayList;
32
33/**
34 * Response for a {@link
35 * AutofillService#onFillRequest(android.app.assist.AssistStructure,
36 * Bundle, int, android.os.CancellationSignal, FillCallback)}.
37 *
38 * <p>The response typically contains one or more {@link Dataset}s, each representing a set of
39 * fields that can be autofilled together, and the Android system displays a dataset picker UI
40 * affordance that the user must use before the {@link android.app.Activity} is filled with
41 * the dataset.
42 *
43 * <p>For example, for a login page with username/password where the user only has one account in
44 * the response could be:
45 *
46 * <pre class="prettyprint">
47 *  new FillResponse.Builder()
48 *      .add(new Dataset.Builder(createPresentation())
49 *          .setValue(id1, AutofillValue.forText("homer"))
50 *          .setValue(id2, AutofillValue.forText("D'OH!"))
51 *          .build())
52 *      .build();
53 * </pre>
54 *
55 * <p>If the user had 2 accounts, each with its own user-provided names, the response could be:
56 *
57 * <pre class="prettyprint">
58 *  new FillResponse.Builder()
59 *      .add(new Dataset.Builder(createFirstPresentation())
60 *          .setValue(id1, AutofillValue.forText("homer"))
61 *          .setValue(id2, AutofillValue.forText("D'OH!"))
62 *          .build())
63 *      .add(new Dataset.Builder(createSecondPresentation())
64 *          .setValue(id1, AutofillValue.forText("elbarto")
65 *          .setValue(id2, AutofillValue.forText("cowabonga")
66 *          .build())
67 *      .build();
68 * </pre>
69 *
70 * If the service is interested on saving the user-edited data back, it must set a {@link SaveInfo}
71 * in the {@link FillResponse}. Typically, the {@link SaveInfo} contains the same ids as the
72 * {@link Dataset}, but other combinations are possible - see {@link SaveInfo} for more details
73 *
74 * <p>If the service has multiple {@link Dataset}s for different sections of the activity,
75 * for example, a user section for which there are two datasets followed by an address
76 * section for which there are two datasets for each user user, then it should "partition"
77 * the activity in sections and populate the response with just a subset of the data that would
78 * fulfill the first section (the name in our example); then once the user fills the first
79 * section and taps a field from the next section (the address in our example), the Android
80 * system would issue another request for that section, and so on. Note that if the user
81 * chooses to populate the first section with a service provided dataset, the subsequent request
82 * would contain the populated values so you don't try to provide suggestions for the first
83 * section but ony for the second one based on the context of what was already filled. For
84 * example, the first response could be:
85 *
86 * <pre class="prettyprint">
87 *  new FillResponse.Builder()
88 *      .add(new Dataset.Builder(createFirstPresentation())
89 *          .setValue(id1, AutofillValue.forText("Homer"))
90 *          .setValue(id2, AutofillValue.forText("Simpson"))
91 *          .build())
92 *      .add(new Dataset.Builder(createSecondPresentation())
93 *          .setValue(id1, AutofillValue.forText("Bart"))
94 *          .setValue(id2, AutofillValue.forText("Simpson"))
95 *          .build())
96 *      .build();
97 * </pre>
98 *
99 * <p>Then after the user picks the second dataset and taps the street field to
100 * trigger another autofill request, the second response could be:
101 *
102 * <pre class="prettyprint">
103 *  new FillResponse.Builder()
104 *      .add(new Dataset.Builder(createThirdPresentation())
105 *          .setValue(id3, AutofillValue.forText("742 Evergreen Terrace"))
106 *          .setValue(id4, AutofillValue.forText("Springfield"))
107 *          .build())
108 *      .add(new Dataset.Builder(createFourthPresentation())
109 *          .setValue(id3, AutofillValue.forText("Springfield Power Plant"))
110 *          .setValue(id4, AutofillValue.forText("Springfield"))
111 *          .build())
112 *      .build();
113 * </pre>
114 *
115 * <p>The service could require user authentication at the {@link FillResponse} or the
116 * {@link Dataset} level, prior to autofilling an activity - see
117 * {@link FillResponse.Builder#setAuthentication(AutofillId[], IntentSender, RemoteViews)} and
118 * {@link Dataset.Builder#setAuthentication(IntentSender)}.
119 *
120 * <p>It is recommended that you encrypt only the sensitive data but leave the labels unencrypted
121 * which would allow you to provide a dataset presentation views with labels and if the user
122 * chooses one of them challenge the user to authenticate. For example, if the user has a
123 * home and a work address the Home and Work labels could be stored unencrypted as they don't
124 * have any sensitive data while the address data is in an encrypted storage. If the user
125 * chooses Home, then the platform will start your authentication flow. If you encrypt all
126 * data and require auth at the response level the user will have to interact with the fill
127 * UI to trigger a request for the datasets (as they don't see the presentation views for the
128 * possible options) which will start your auth flow and after successfully authenticating
129 * the user will be presented with the Home and Work options to pick one. Hence, you have
130 * flexibility how to implement your auth while storing labels non-encrypted and data
131 * encrypted provides a better user experience.
132 */
133public final class FillResponse implements Parcelable {
134
135    private final @Nullable ArrayList<Dataset> mDatasets;
136    private final @Nullable SaveInfo mSaveInfo;
137    private final @Nullable Bundle mClientState;
138    private final @Nullable RemoteViews mPresentation;
139    private final @Nullable IntentSender mAuthentication;
140    private final @Nullable AutofillId[] mAuthenticationIds;
141
142    private FillResponse(@NonNull Builder builder) {
143        mDatasets = builder.mDatasets;
144        mSaveInfo = builder.mSaveInfo;
145        mClientState = builder.mCLientState;
146        mPresentation = builder.mPresentation;
147        mAuthentication = builder.mAuthentication;
148        mAuthenticationIds = builder.mAuthenticationIds;
149    }
150
151    /** @hide */
152    public @Nullable Bundle getClientState() {
153        return mClientState;
154    }
155
156    /** @hide */
157    public @Nullable ArrayList<Dataset> getDatasets() {
158        return mDatasets;
159    }
160
161    /** @hide */
162    public @Nullable SaveInfo getSaveInfo() {
163        return mSaveInfo;
164    }
165
166    /** @hide */
167    public @Nullable RemoteViews getPresentation() {
168        return mPresentation;
169    }
170
171    /** @hide */
172    public @Nullable IntentSender getAuthentication() {
173        return mAuthentication;
174    }
175
176    /** @hide */
177    public @Nullable AutofillId[] getAuthenticationIds() {
178        return mAuthenticationIds;
179    }
180
181    /**
182     * Builder for {@link FillResponse} objects. You must to provide at least
183     * one dataset or set an authentication intent with a presentation view.
184     */
185    public static final class Builder {
186        private ArrayList<Dataset> mDatasets;
187        private SaveInfo mSaveInfo;
188        private Bundle mCLientState;
189        private RemoteViews mPresentation;
190        private IntentSender mAuthentication;
191        private AutofillId[] mAuthenticationIds;
192        private boolean mDestroyed;
193
194        /**
195         * Requires a fill response authentication before autofilling the activity with
196         * any data set in this response.
197         *
198         * <p>This is typically useful when a user interaction is required to unlock their
199         * data vault if you encrypt the data set labels and data set data. It is recommended
200         * to encrypt only the sensitive data and not the data set labels which would allow
201         * auth on the data set level leading to a better user experience. Note that if you
202         * use sensitive data as a label, for example an email address, then it should also
203         * be encrypted. The provided {@link android.app.PendingIntent intent} must be an
204         * activity which implements your authentication flow. Also if you provide an auth
205         * intent you also need to specify the presentation view to be shown in the fill UI
206         * for the user to trigger your authentication flow.
207         *
208         * <p>When a user triggers autofill, the system launches the provided intent
209         * whose extras will have the {@link AutofillManager#EXTRA_ASSIST_STRUCTURE screen
210         * content}. Once you complete your authentication flow you should set the activity
211         * result to {@link android.app.Activity#RESULT_OK} and provide the fully populated
212         * {@link FillResponse response} by setting it to the {@link
213         * AutofillManager#EXTRA_AUTHENTICATION_RESULT} extra.
214         * For example, if you provided an empty {@link FillResponse resppnse} because the
215         * user's data was locked and marked that the response needs an authentication then
216         * in the response returned if authentication succeeds you need to provide all
217         * available data sets some of which may need to be further authenticated, for
218         * example a credit card whose CVV needs to be entered.
219         *
220         * <p>If you provide an authentication intent you must also provide a presentation
221         * which is used to visualize visualize the response for triggering the authentication
222         * flow.
223         *
224         * <p></><strong>Note:</strong> Do not make the provided pending intent
225         * immutable by using {@link android.app.PendingIntent#FLAG_IMMUTABLE} as the
226         * platform needs to fill in the authentication arguments.
227         *
228         * @param authentication Intent to an activity with your authentication flow.
229         * @param presentation The presentation to visualize the response.
230         * @param ids id of Views that when focused will display the authentication UI affordance.
231         *
232         * @return This builder.
233         * @see android.app.PendingIntent#getIntentSender()
234         */
235        public @NonNull Builder setAuthentication(@NonNull AutofillId[] ids,
236                @Nullable IntentSender authentication, @Nullable RemoteViews presentation) {
237            throwIfDestroyed();
238            // TODO(b/33197203): assert ids is not null nor empty once old version is removed
239            if (authentication == null ^ presentation == null) {
240                throw new IllegalArgumentException("authentication and presentation"
241                        + " must be both non-null or null");
242            }
243            mAuthentication = authentication;
244            mPresentation = presentation;
245            mAuthenticationIds = ids;
246            return this;
247        }
248
249        /**
250         * TODO(b/33197203): will be removed once clients use the version that takes ids
251         * @hide
252         * @deprecated
253         */
254        @Deprecated
255        public @NonNull Builder setAuthentication(@Nullable IntentSender authentication,
256                @Nullable RemoteViews presentation) {
257            return setAuthentication(null, authentication, presentation);
258        }
259
260        /**
261         * Adds a new {@link Dataset} to this response.
262         *
263         * @return This builder.
264         */
265        public @NonNull Builder addDataset(@Nullable Dataset dataset) {
266            throwIfDestroyed();
267            if (dataset == null) {
268                return this;
269            }
270            if (mDatasets == null) {
271                mDatasets = new ArrayList<>();
272            }
273            if (!mDatasets.add(dataset)) {
274                return this;
275            }
276            return this;
277        }
278
279        /**
280         * Sets the {@link SaveInfo} associated with this response.
281         *
282         * <p>See {@link FillResponse} for more info.
283         *
284         * @return This builder.
285         */
286        public @NonNull Builder setSaveInfo(@NonNull SaveInfo saveInfo) {
287            throwIfDestroyed();
288            mSaveInfo = saveInfo;
289            return this;
290        }
291
292        @Deprecated
293        public Builder setExtras(@Nullable Bundle extras) {
294            throwIfDestroyed();
295            mCLientState = extras;
296            return this;
297        }
298
299        /**
300         * Sets a {@link Bundle state} that will be passed to subsequent APIs that
301         * manipulate this response. For example, they are passed to subsequent
302         * calls to {@link AutofillService#onFillRequest(
303         * android.app.assist.AssistStructure, Bundle, int,
304         * android.os.CancellationSignal, FillCallback)} and {@link AutofillService#onSaveRequest(
305         * android.app.assist.AssistStructure, Bundle, SaveCallback)}. You can use
306         * this to store intermediate state that is persistent across multiple
307         * fill requests and the subsequent save request.
308         *
309         * <p>If this method is called on multiple {@link FillResponse} objects for the same
310         * activity, just the latest bundle is passed back to the service.
311         *
312         * <p>Once a {@link AutofillService#onSaveRequest(SaveRequest, SaveCallback)
313         * save request} is made the client state is cleared.
314         *
315         * @param clientState The custom client state.
316         * @return This builder.
317         */
318        public Builder setClientState(@Nullable Bundle clientState) {
319            throwIfDestroyed();
320            mCLientState = clientState;
321            return this;
322        }
323
324        /**
325         * Builds a new {@link FillResponse} instance. You must provide at least
326         * one dataset or some savable ids or an authentication with a presentation
327         * view.
328         *
329         * @return A built response.
330         */
331        public FillResponse build() {
332            throwIfDestroyed();
333
334            if (mAuthentication == null && mDatasets == null && mSaveInfo == null) {
335                throw new IllegalArgumentException("need to provide at least one DataSet or a "
336                        + "SaveInfo or an authentication with a presentation");
337            }
338            mDestroyed = true;
339            return new FillResponse(this);
340        }
341
342        private void throwIfDestroyed() {
343            if (mDestroyed) {
344                throw new IllegalStateException("Already called #build()");
345            }
346        }
347    }
348
349    /////////////////////////////////////
350    // Object "contract" methods. //
351    /////////////////////////////////////
352    @Override
353    public String toString() {
354        if (!DEBUG) return super.toString();
355
356        return new StringBuilder(
357                "FillResponse: [datasets=").append(mDatasets)
358                .append(", saveInfo=").append(mSaveInfo)
359                .append(", clientState=").append(mClientState != null)
360                .append(", hasPresentation=").append(mPresentation != null)
361                .append(", hasAuthentication=").append(mAuthentication != null)
362                .append(", authenticationSize=").append(mAuthenticationIds != null
363                        ? mAuthenticationIds.length : "N/A")
364                .toString();
365    }
366
367    /////////////////////////////////////
368    // Parcelable "contract" methods. //
369    /////////////////////////////////////
370
371    @Override
372    public int describeContents() {
373        return 0;
374    }
375
376    @Override
377    public void writeToParcel(Parcel parcel, int flags) {
378        parcel.writeTypedArrayList(mDatasets, flags);
379        parcel.writeParcelable(mSaveInfo, flags);
380        parcel.writeParcelable(mClientState, flags);
381        parcel.writeParcelableArray(mAuthenticationIds, flags);
382        parcel.writeParcelable(mAuthentication, flags);
383        parcel.writeParcelable(mPresentation, flags);
384    }
385
386    public static final Parcelable.Creator<FillResponse> CREATOR =
387            new Parcelable.Creator<FillResponse>() {
388        @Override
389        public FillResponse createFromParcel(Parcel parcel) {
390            // Always go through the builder to ensure the data ingested by
391            // the system obeys the contract of the builder to avoid attacks
392            // using specially crafted parcels.
393            final Builder builder = new Builder();
394            final ArrayList<Dataset> datasets = parcel.readTypedArrayList(null);
395            final int datasetCount = (datasets != null) ? datasets.size() : 0;
396            for (int i = 0; i < datasetCount; i++) {
397                builder.addDataset(datasets.get(i));
398            }
399            builder.setSaveInfo(parcel.readParcelable(null));
400            builder.setExtras(parcel.readParcelable(null));
401            builder.setAuthentication(parcel.readParcelableArray(null, AutofillId.class),
402                    parcel.readParcelable(null), parcel.readParcelable(null));
403            return builder.build();
404        }
405
406        @Override
407        public FillResponse[] newArray(int size) {
408            return new FillResponse[size];
409        }
410    };
411}
412