FillResponse.java revision fe35e69d964dbd09cc8e6390e41ca9347baea108
17bd2fbcdcfeb9014c16fd78446c34a4eef489626Yuichi Araki/*
27bd2fbcdcfeb9014c16fd78446c34a4eef489626Yuichi Araki * Copyright (C) 2016 The Android Open Source Project
37bd2fbcdcfeb9014c16fd78446c34a4eef489626Yuichi Araki *
47bd2fbcdcfeb9014c16fd78446c34a4eef489626Yuichi Araki * Licensed under the Apache License, Version 2.0 (the "License");
57bd2fbcdcfeb9014c16fd78446c34a4eef489626Yuichi Araki * you may not use this file except in compliance with the License.
67bd2fbcdcfeb9014c16fd78446c34a4eef489626Yuichi Araki * You may obtain a copy of the License at
77bd2fbcdcfeb9014c16fd78446c34a4eef489626Yuichi Araki *
87bd2fbcdcfeb9014c16fd78446c34a4eef489626Yuichi Araki *      http://www.apache.org/licenses/LICENSE-2.0
97bd2fbcdcfeb9014c16fd78446c34a4eef489626Yuichi Araki *
107bd2fbcdcfeb9014c16fd78446c34a4eef489626Yuichi Araki * Unless required by applicable law or agreed to in writing, software
117bd2fbcdcfeb9014c16fd78446c34a4eef489626Yuichi Araki * distributed under the License is distributed on an "AS IS" BASIS,
127bd2fbcdcfeb9014c16fd78446c34a4eef489626Yuichi Araki * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
137bd2fbcdcfeb9014c16fd78446c34a4eef489626Yuichi Araki * See the License for the specific language governing permissions and
147bd2fbcdcfeb9014c16fd78446c34a4eef489626Yuichi Araki * limitations under the License.
157bd2fbcdcfeb9014c16fd78446c34a4eef489626Yuichi Araki */
167bd2fbcdcfeb9014c16fd78446c34a4eef489626Yuichi Arakipackage android.service.autofill;
177bd2fbcdcfeb9014c16fd78446c34a4eef489626Yuichi Araki
187bd2fbcdcfeb9014c16fd78446c34a4eef489626Yuichi Arakiimport static android.view.autofill.Helper.DEBUG;
197bd2fbcdcfeb9014c16fd78446c34a4eef489626Yuichi Araki
207bd2fbcdcfeb9014c16fd78446c34a4eef489626Yuichi Arakiimport android.annotation.NonNull;
217bd2fbcdcfeb9014c16fd78446c34a4eef489626Yuichi Arakiimport android.annotation.Nullable;
227bd2fbcdcfeb9014c16fd78446c34a4eef489626Yuichi Arakiimport android.content.IntentSender;
237bd2fbcdcfeb9014c16fd78446c34a4eef489626Yuichi Arakiimport android.os.Bundle;
247bd2fbcdcfeb9014c16fd78446c34a4eef489626Yuichi Arakiimport android.os.Parcel;
257bd2fbcdcfeb9014c16fd78446c34a4eef489626Yuichi Arakiimport android.os.Parcelable;
267bd2fbcdcfeb9014c16fd78446c34a4eef489626Yuichi Arakiimport android.util.ArraySet;
277bd2fbcdcfeb9014c16fd78446c34a4eef489626Yuichi Arakiimport android.view.autofill.AutoFillId;
287bd2fbcdcfeb9014c16fd78446c34a4eef489626Yuichi Arakiimport android.view.autofill.AutoFillManager;
297bd2fbcdcfeb9014c16fd78446c34a4eef489626Yuichi Arakiimport android.widget.RemoteViews;
307bd2fbcdcfeb9014c16fd78446c34a4eef489626Yuichi Araki
317bd2fbcdcfeb9014c16fd78446c34a4eef489626Yuichi Arakiimport java.util.ArrayList;
327bd2fbcdcfeb9014c16fd78446c34a4eef489626Yuichi Araki
337bd2fbcdcfeb9014c16fd78446c34a4eef489626Yuichi Araki/**
347bd2fbcdcfeb9014c16fd78446c34a4eef489626Yuichi Araki * Response for a {@link
357bd2fbcdcfeb9014c16fd78446c34a4eef489626Yuichi Araki * AutoFillService#onFillRequest(android.app.assist.AssistStructure,
367bd2fbcdcfeb9014c16fd78446c34a4eef489626Yuichi Araki * Bundle, android.os.CancellationSignal, FillCallback)}.
377bd2fbcdcfeb9014c16fd78446c34a4eef489626Yuichi Araki *
387bd2fbcdcfeb9014c16fd78446c34a4eef489626Yuichi Araki * <p>The response typically contains one or more {@link Dataset}s, each representing a set of
397bd2fbcdcfeb9014c16fd78446c34a4eef489626Yuichi Araki * fields that can be auto-filled together, and the Android system displays a dataset picker UI
407bd2fbcdcfeb9014c16fd78446c34a4eef489626Yuichi Araki * affordance that the user must use before the {@link android.app.Activity} is filled with
417bd2fbcdcfeb9014c16fd78446c34a4eef489626Yuichi Araki * the dataset.
427bd2fbcdcfeb9014c16fd78446c34a4eef489626Yuichi Araki *
437bd2fbcdcfeb9014c16fd78446c34a4eef489626Yuichi Araki * <p>For example, for a login page with username/password where the user only has one account in
447bd2fbcdcfeb9014c16fd78446c34a4eef489626Yuichi Araki * the response could be:
457bd2fbcdcfeb9014c16fd78446c34a4eef489626Yuichi Araki *
467bd2fbcdcfeb9014c16fd78446c34a4eef489626Yuichi Araki * <pre class="prettyprint">
477bd2fbcdcfeb9014c16fd78446c34a4eef489626Yuichi Araki *  new FillResponse.Builder()
487bd2fbcdcfeb9014c16fd78446c34a4eef489626Yuichi Araki *      .add(new Dataset.Builder()
497bd2fbcdcfeb9014c16fd78446c34a4eef489626Yuichi Araki *          .setPresentation(createPresentation())
507bd2fbcdcfeb9014c16fd78446c34a4eef489626Yuichi Araki *          .setTextFieldValue(id1, "homer")
517bd2fbcdcfeb9014c16fd78446c34a4eef489626Yuichi Araki *          .setTextFieldValue(id2, "D'OH!")
527bd2fbcdcfeb9014c16fd78446c34a4eef489626Yuichi Araki *          .build())
537bd2fbcdcfeb9014c16fd78446c34a4eef489626Yuichi Araki *      .build();
547bd2fbcdcfeb9014c16fd78446c34a4eef489626Yuichi Araki * </pre>
557bd2fbcdcfeb9014c16fd78446c34a4eef489626Yuichi Araki *
567bd2fbcdcfeb9014c16fd78446c34a4eef489626Yuichi Araki * <p>If the user had 2 accounts, each with its own user-provided names, the response could be:
577bd2fbcdcfeb9014c16fd78446c34a4eef489626Yuichi Araki *
587bd2fbcdcfeb9014c16fd78446c34a4eef489626Yuichi Araki * <pre class="prettyprint">
597bd2fbcdcfeb9014c16fd78446c34a4eef489626Yuichi Araki *  new FillResponse.Builder()
607bd2fbcdcfeb9014c16fd78446c34a4eef489626Yuichi Araki *      .add(new Dataset.Builder()
617bd2fbcdcfeb9014c16fd78446c34a4eef489626Yuichi Araki *          .setPresentation(createFirstPresentation())
627bd2fbcdcfeb9014c16fd78446c34a4eef489626Yuichi Araki *          .setTextFieldValue(id1, "homer")
637bd2fbcdcfeb9014c16fd78446c34a4eef489626Yuichi Araki *          .setTextFieldValue(id2, "D'OH!")
647bd2fbcdcfeb9014c16fd78446c34a4eef489626Yuichi Araki *          .build())
65 *      .add(new Dataset.Builder()
66 *          .setPresentation(createSecondPresentation())
67 *          .setTextFieldValue(id1, "elbarto")
68 *          .setTextFieldValue(id2, "cowabonga")
69 *          .build())
70 *      .build();
71 * </pre>
72 *
73 * <p>If the user does not have any data associated with this {@link android.app.Activity} but
74 * the service wants to offer the user the option to save the data that was entered, then the
75 * service could populate the response with {@code savableIds} instead of {@link Dataset}s:
76 *
77 * <pre class="prettyprint">
78 *  new FillResponse.Builder()
79 *      .addSavableFields(id1, id2)
80 *      .build();
81 * </pre>
82 *
83 * <p>Similarly, there might be cases where the user data on the service is enough to populate some
84 * fields but not all, and the service would still be interested on saving the other fields. In this
85 * scenario, the service could populate the response with both {@link Dataset}s and {@code
86 * savableIds}:
87 *
88 * <pre class="prettyprint">
89 *   new FillResponse.Builder()
90 *       .add(new Dataset.Builder(")
91 *          .setPresentation(createPresentation())
92 *          .setTextFieldValue(id1, "Homer")                  // first name
93 *          .setTextFieldValue(id2, "Simpson")                // last name
94 *          .setTextFieldValue(id3, "742 Evergreen Terrace")  // street
95 *          .setTextFieldValue(id4, "Springfield")            // city
96 *          .build())
97 *       .addSavableFields(id5, id6) // state and zipcode
98 *       .build();
99 *
100 * </pre>
101 *
102 * <p>Notice that the ids that are part of a dataset (ids 1 to 4, in this example) are automatically
103 * added to the {@code savableIds} list.
104 *
105 * <p>If the service has multiple {@link Dataset}s for different sections of the activity,
106 * for example, a user section for which there are two datasets followed by an address
107 * section for which there are two datasets for each user user, then it should "partition"
108 * the activity in sections and populate the response with just a subset of the data that would
109 * fulfill the first section (the name in our example); then once the user fills the first
110 * section and taps a field from the next section (the address in our example), the Android
111 * system would issue another request for that section, and so on. Note that if the user
112 * chooses to populate the first section with a service provided dataset, the subsequent request
113 * would contain the populated values so you don't try to provide suggestions for the first
114 * section but ony for the second one based on the context of what was already filled. For
115 * example, the first response could be:
116 *
117 * <pre class="prettyprint">
118 *  new FillResponse.Builder()
119 *      .add(new Dataset.Builder()
120 *          .setPresentation(createFirstPresentation())
121 *          .setTextFieldValue(id1, "Homer")
122 *          .setTextFieldValue(id2, "Simpson")
123 *          .build())
124 *      .add(new Dataset.Builder()
125 *          .setPresentation(createSecondPresentation())
126 *          .setTextFieldValue(id1, "Bart")
127 *          .setTextFieldValue(id2, "Simpson")
128 *          .build())
129 *      .build();
130 * </pre>
131 *
132 * <p>Then after the user picks the second dataset and taps the street field to
133 * trigger another auto-fill request, the second response could be:
134 *
135 * <pre class="prettyprint">
136 *  new FillResponse.Builder()
137 *      .add(new Dataset.Builder()
138 *          .setPresentation(createThirdPresentation())
139 *          .setTextFieldValue(id3, "742 Evergreen Terrace")
140 *          .setTextFieldValue(id4, "Springfield")
141 *          .build())
142 *      .add(new Dataset.Builder()
143 *          .setPresentation(createFourthPresentation())
144 *          .setTextFieldValue(id3, "Springfield Power Plant")
145 *          .setTextFieldValue(id4, "Springfield")
146 *          .build())
147 *      .build();
148 * </pre>
149 *
150 * <p>The service could require user authentication at the {@link FillResponse} or the
151 * {@link Dataset} level, prior to auto-filling an activity - see {@link FillResponse.Builder
152 * #setAuthentication(IntentSender)} and {@link Dataset.Builder#setAuthentication(IntentSender)}.
153 * It is recommended that you encrypt only the sensitive data but leave the labels unencrypted
154 * which would allow you to provide a dataset presentation views with labels and if the user
155 * chooses one of them challenge the user to authenticate. For example, if the user has a
156 * home and a work address the Home and Work labels could be stored unencrypted as they don't
157 * have any sensitive data while the address data is in an encrypted storage. If the user
158 * chooses Home, then the platform will start your authentication flow. If you encrypt all
159 * data and require auth at the response level the user will have to interact with the fill
160 * UI to trigger a request for the datasets (as they don't see the presentation views for the
161 * possible options) which will start your auth flow and after successfully authenticating
162 * the user will be presented with the Home and Work options to pick one. Hence, you have
163 * flexibility how to implement your auth while storing labels non-encrypted and data
164 * encrypted provides a better user experience.</p>
165 */
166public final class FillResponse implements Parcelable {
167
168    private final ArrayList<Dataset> mDatasets;
169    private final ArraySet<AutoFillId> mSavableIds;
170    private final Bundle mExtras;
171    private final RemoteViews mPresentation;
172    private final IntentSender mAuthentication;
173
174    private FillResponse(@NonNull Builder builder) {
175        mDatasets = builder.mDatasets;
176        mSavableIds = builder.mSavableIds;
177        mExtras = builder.mExtras;
178        mPresentation = builder.mPresentation;
179        mAuthentication = builder.mAuthentication;
180    }
181
182    /** @hide */
183    public @Nullable Bundle getExtras() {
184        return mExtras;
185    }
186
187    /** @hide */
188    public @Nullable ArrayList<Dataset> getDatasets() {
189        return mDatasets;
190    }
191
192    /** @hide */
193    public @Nullable ArraySet<AutoFillId> getSavableIds() {
194        return mSavableIds;
195    }
196
197    /** @hide */
198    public @Nullable RemoteViews getPresentation() {
199        return mPresentation;
200    }
201
202    /** @hide */
203    public @Nullable IntentSender getAuthentication() {
204        return mAuthentication;
205    }
206
207    /**
208     * Builder for {@link FillResponse} objects. You must to provide at least
209     * one dataset or set an authentication intent with a presentation view.
210     */
211    public static final class Builder {
212        private ArrayList<Dataset> mDatasets;
213        private ArraySet<AutoFillId> mSavableIds;
214        private Bundle mExtras;
215        private RemoteViews mPresentation;
216        private IntentSender mAuthentication;
217        private boolean mDestroyed;
218
219        /**
220         * Sets the presentation used to visualize this response. You should
221         * set this only if you need an authentication as this is the only
222         * case the response needs to be presented to the user.
223         *
224         * @param presentation The presentation view.
225         *
226         * @return This builder.
227         *
228         * @see #setAuthentication(IntentSender)
229         */
230        public @NonNull
231        FillResponse.Builder setPresentation(@Nullable RemoteViews presentation) {
232            mPresentation = presentation;
233            return this;
234        }
235
236        /**
237         * Requires a fill response authentication before auto-filling the activity with
238         * any data set in this response.
239         *
240         * <p>This is typically useful when a user interaction is required to unlock their
241         * data vault if you encrypt the data set labels and data set data. It is recommended
242         * to encrypt only the sensitive data and not the data set labels which would allow
243         * auth on the data set level leading to a better user experience. Note that if you
244         * use sensitive data as a label, for example an email address, then it should also
245         * be encrypted. The provided {@link android.app.PendingIntent intent} must be an
246         * activity which implements your authentication flow. Also if you provide an auth
247         * intent you also need to specify the presentation view to be shown in the fill UI
248         * for the user to trigger your authentication flow.</p>
249         *
250         * <p>When a user triggers auto-fill, the system launches the provided intent
251         * whose extras will have the {@link AutoFillManager#EXTRA_ASSIST_STRUCTURE screen
252         * content}. Once you complete your authentication flow you should set the activity
253         * result to {@link android.app.Activity#RESULT_OK} and provide the fully populated
254         * {@link FillResponse response} by setting it to the {@link
255         * AutoFillManager#EXTRA_AUTHENTICATION_RESULT} extra.
256         * For example, if you provided an empty {@link FillResponse resppnse} because the
257         * user's data was locked and marked that the response needs an authentication then
258         * in the response returned if authentication succeeds you need to provide all
259         * available data sets some of which may need to be further authenticated, for
260         * example a credit card whose CVV needs to be entered.</p>
261         *
262         * <p></><strong>Note:</strong> Do not make the provided pending intent
263         * immutable by using {@link android.app.PendingIntent#FLAG_IMMUTABLE} as the
264         * platform needs to fill in the authentication arguments.</p>
265         *
266         * @param authentication Intent to an activity with your authentication flow.
267         * @return This builder.
268         *
269         * @see android.app.PendingIntent#getIntentSender()
270         * @see #setPresentation(RemoteViews)
271         */
272        public @NonNull Builder setAuthentication(@Nullable IntentSender authentication) {
273            throwIfDestroyed();
274            mAuthentication = authentication;
275            return this;
276        }
277
278        /**
279         * Adds a new {@link Dataset} to this response.
280         *
281         * @return This builder.
282         */
283        public@NonNull Builder addDataset(@Nullable Dataset dataset) {
284            throwIfDestroyed();
285            if (dataset == null) {
286                return this;
287            }
288            if (mDatasets == null) {
289                mDatasets = new ArrayList<>();
290            }
291            if (!mDatasets.add(dataset)) {
292                return this;
293            }
294            if (dataset.getFieldIds() != null) {
295                final int fieldCount = dataset.getFieldIds().size();
296                for (int i = 0; i < fieldCount; i++) {
297                    final AutoFillId id = dataset.getFieldIds().get(i);
298                    if (mSavableIds == null) {
299                        mSavableIds = new ArraySet<>();
300                    }
301                    mSavableIds.add(id);
302                }
303            }
304            return this;
305        }
306
307        /**
308         * Adds ids of additional fields that the service would be interested to save (through
309         * {@link AutoFillService#onSaveRequest(
310         * android.app.assist.AssistStructure, Bundle, SaveCallback)})
311         * but were not indirectly set through {@link #addDataset(Dataset)}.
312         *
313         * @param ids The savable ids.
314         * @return This builder.
315         *
316         * @see FillResponse
317         */
318        public @NonNull Builder addSavableFields(@Nullable AutoFillId... ids) {
319            throwIfDestroyed();
320            if (ids == null) {
321                return this;
322            }
323            for (AutoFillId id : ids) {
324                if (mSavableIds == null) {
325                    mSavableIds = new ArraySet<>();
326                }
327                mSavableIds.add(id);
328            }
329            return this;
330        }
331
332        /**
333         * Sets a {@link Bundle} that will be passed to subsequent APIs that
334         * manipulate this response. For example, they are passed to subsequent
335         * calls to {@link AutoFillService#onFillRequest(
336         * android.app.assist.AssistStructure, Bundle, android.os.CancellationSignal,
337         * FillCallback)} and {@link AutoFillService#onSaveRequest(
338         * android.app.assist.AssistStructure, Bundle, SaveCallback)}.
339         *
340         * @param extras The response extras.
341         * @return This builder.
342         */
343        public Builder setExtras(Bundle extras) {
344            throwIfDestroyed();
345            mExtras = extras;
346            return this;
347        }
348
349        /**
350         * Builds a new {@link FillResponse} instance. You must provide at least
351         * one dataset or some savable ids or an authentication with a presentation
352         * view.
353         *
354         * @return A built response.
355         */
356        public FillResponse build() {
357            throwIfDestroyed();
358            if (mAuthentication == null ^ mPresentation == null) {
359                throw new IllegalArgumentException("authentication and presentation"
360                        + " must be both non-null or null");
361            }
362            if (mAuthentication == null && mDatasets == null && mSavableIds == null) {
363                throw new IllegalArgumentException("need to provide at least one"
364                        + " data set or savable ids or an authentication with a presentation");
365            }
366            mDestroyed = true;
367            return new FillResponse(this);
368        }
369
370        private void throwIfDestroyed() {
371            if (mDestroyed) {
372                throw new IllegalStateException("Already called #build()");
373            }
374        }
375    }
376
377    /////////////////////////////////////
378    //  Object "contract" methods. //
379    /////////////////////////////////////
380    @Override
381    public String toString() {
382        if (!DEBUG) return super.toString();
383        return new StringBuilder(
384                "FillResponse: [datasets=").append(mDatasets)
385                .append(", savableIds=").append(mSavableIds)
386                .append(", hasExtras=").append(mExtras != null)
387                .append(", hasPresentation=").append(mPresentation != null)
388                .append(", hasAuthentication=").append(mAuthentication != null)
389                .toString();
390    }
391
392    /////////////////////////////////////
393    //  Parcelable "contract" methods. //
394    /////////////////////////////////////
395
396    @Override
397    public int describeContents() {
398        return 0;
399    }
400
401    @Override
402    public void writeToParcel(Parcel parcel, int flags) {
403        parcel.writeTypedArrayList(mDatasets, flags);
404        parcel.writeTypedArraySet(mSavableIds, flags);
405        parcel.writeParcelable(mExtras, flags);
406        parcel.writeParcelable(mPresentation, flags);
407        parcel.writeParcelable(mAuthentication, flags);
408    }
409
410    public static final Parcelable.Creator<FillResponse> CREATOR =
411            new Parcelable.Creator<FillResponse>() {
412        @Override
413        public FillResponse createFromParcel(Parcel parcel) {
414            // Always go through the builder to ensure the data ingested by
415            // the system obeys the contract of the builder to avoid attacks
416            // using specially crafted parcels.
417            final Builder builder = new Builder();
418            final ArrayList<Dataset> datasets = parcel.readTypedArrayList(null);
419            final int datasetCount = (datasets != null) ? datasets.size() : 0;
420            for (int i = 0; i < datasetCount; i++) {
421                builder.addDataset(datasets.get(i));
422            }
423            final ArraySet<AutoFillId> fillIds = parcel.readTypedArraySet(null);
424            final int fillIdCount = (fillIds != null) ? fillIds.size() : 0;
425            for (int i = 0; i < fillIdCount; i++) {
426                builder.addSavableFields(fillIds.valueAt(i));
427            }
428            builder.setExtras(parcel.readParcelable(null));
429            builder.setPresentation(parcel.readParcelable(null));
430            builder.setAuthentication(parcel.readParcelable(null));
431            return builder.build();
432        }
433
434        @Override
435        public FillResponse[] newArray(int size) {
436            return new FillResponse[size];
437        }
438    };
439}
440