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