SaveInfo.java revision 2ef19c1d73f89ca4718b5a8f0c2e7221621e844f
1/*
2 * Copyright (C) 2017 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.sDebug;
20
21import android.annotation.IntDef;
22import android.annotation.NonNull;
23import android.annotation.Nullable;
24import android.app.Activity;
25import android.content.IntentSender;
26import android.os.Parcel;
27import android.os.Parcelable;
28import android.util.DebugUtils;
29import android.view.autofill.AutofillId;
30import android.view.autofill.AutofillManager;
31import android.view.autofill.AutofillValue;
32
33import com.android.internal.util.Preconditions;
34
35import java.lang.annotation.Retention;
36import java.lang.annotation.RetentionPolicy;
37import java.util.Arrays;
38
39/**
40 * Information used to indicate that an {@link AutofillService} is interested on saving the
41 * user-inputed data for future use, through a
42 * {@link AutofillService#onSaveRequest(SaveRequest, SaveCallback)}
43 * call.
44 *
45 * <p>A {@link SaveInfo} is always associated with a {@link FillResponse}, and it contains at least
46 * two pieces of information:
47 *
48 * <ol>
49 *   <li>The type(s) of user data (like password or credit card info) that would be saved.
50 *   <li>The minimum set of views (represented by their {@link AutofillId}) that need to be changed
51 *       to trigger a save request.
52 * </ol>
53 *
54 * <p>Typically, the {@link SaveInfo} contains the same {@code id}s as the {@link Dataset}:
55 *
56 * <pre class="prettyprint">
57 *   new FillResponse.Builder()
58 *       .addDataset(new Dataset.Builder()
59 *           .setValue(id1, AutofillValue.forText("homer"), createPresentation("homer")) // username
60 *           .setValue(id2, AutofillValue.forText("D'OH!"), createPresentation("password for homer")) // password
61 *           .build())
62 *       .setSaveInfo(new SaveInfo.Builder(
63 *           SaveInfo.SAVE_DATA_TYPE_USERNAME | SaveInfo.SAVE_DATA_TYPE_PASSWORD,
64 *           new AutofillId[] { id1, id2 }).build())
65 *       .build();
66 * </pre>
67 *
68 * <p>The save type flags are used to display the appropriate strings in the save UI affordance.
69 * You can pass multiple values, but try to keep it short if possible. In the above example, just
70 * {@code SaveInfo.SAVE_DATA_TYPE_PASSWORD} would be enough.
71 *
72 * <p>There might be cases where the {@link AutofillService} knows how to fill the screen,
73 * but the user has no data for it. In that case, the {@link FillResponse} should contain just the
74 * {@link SaveInfo}, but no {@link Dataset Datasets}:
75 *
76 * <pre class="prettyprint">
77 *   new FillResponse.Builder()
78 *       .setSaveInfo(new SaveInfo.Builder(SaveInfo.SAVE_DATA_TYPE_PASSWORD,
79 *           new AutofillId[] { id1, id2 }).build())
80 *       .build();
81 * </pre>
82 *
83 * <p>There might be cases where the user data in the {@link AutofillService} is enough
84 * to populate some fields but not all, and the service would still be interested on saving the
85 * other fields. In that case, the service could set the
86 * {@link SaveInfo.Builder#setOptionalIds(AutofillId[])} as well:
87 *
88 * <pre class="prettyprint">
89 *   new FillResponse.Builder()
90 *       .addDataset(new Dataset.Builder()
91 *           .setValue(id1, AutofillValue.forText("742 Evergreen Terrace"),
92 *               createPresentation("742 Evergreen Terrace")) // street
93 *           .setValue(id2, AutofillValue.forText("Springfield"),
94 *               createPresentation("Springfield")) // city
95 *           .build())
96 *       .setSaveInfo(new SaveInfo.Builder(SaveInfo.SAVE_DATA_TYPE_ADDRESS,
97 *           new AutofillId[] { id1, id2 }) // street and  city
98 *           .setOptionalIds(new AutofillId[] { id3, id4 }) // state and zipcode
99 *           .build())
100 *       .build();
101 * </pre>
102 *
103 * <p>The {@link AutofillService#onSaveRequest(SaveRequest, SaveCallback)} can be triggered after
104 * any of the following events:
105 * <ul>
106 *   <li>The {@link Activity} finishes.
107 *   <li>The app explicitly called {@link AutofillManager#commit()}.
108 *   <li>All required views became invisible (if the {@link SaveInfo} was created with the
109 *       {@link #FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE} flag).
110 * </ul>
111 *
112 * <p>But it is only triggered when all conditions below are met:
113 * <ul>
114 *   <li>The {@link SaveInfo} associated with the {@link FillResponse} is not {@code null}.
115 *   <li>The {@link AutofillValue}s of all required views (as set by the {@code requiredIds} passed
116 *       to the {@link SaveInfo.Builder} constructor are not empty.
117 *   <li>The {@link AutofillValue} of at least one view (be it required or optional) has changed
118 *       (i.e., it's neither the same value passed in a {@link Dataset}, nor the initial value
119 *       presented in the view).
120 *   <li>The user explicitly tapped the UI affordance asking to save data for autofill.
121 * </ul>
122 *
123 * <p>The service can also customize some aspects of the save UI affordance:
124 * <ul>
125 *   <li>Add a subtitle by calling {@link Builder#setDescription(CharSequence)}.
126 *   <li>Customize the button used to reject the save request by calling
127 *       {@link Builder#setNegativeAction(int, IntentSender)}.
128 * </ul>
129 */
130public final class SaveInfo implements Parcelable {
131
132    /**
133     * Type used when the service can save the contents of a screen, but cannot describe what
134     * the content is for.
135     */
136    public static final int SAVE_DATA_TYPE_GENERIC = 0x0;
137
138    /**
139     * Type used when the {@link FillResponse} represents user credentials that have a password.
140     */
141    public static final int SAVE_DATA_TYPE_PASSWORD = 0x01;
142
143    /**
144     * Type used on when the {@link FillResponse} represents a physical address (such as street,
145     * city, state, etc).
146     */
147    public static final int SAVE_DATA_TYPE_ADDRESS = 0x02;
148
149    /**
150     * Type used when the {@link FillResponse} represents a credit card.
151     */
152    public static final int SAVE_DATA_TYPE_CREDIT_CARD = 0x04;
153
154    /**
155     * Type used when the {@link FillResponse} represents just an username, without a password.
156     */
157    public static final int SAVE_DATA_TYPE_USERNAME = 0x08;
158
159    /**
160     * Type used when the {@link FillResponse} represents just an email address, without a password.
161     */
162    public static final int SAVE_DATA_TYPE_EMAIL_ADDRESS = 0x10;
163
164    /**
165     * Style for the negative button of the save UI to cancel the
166     * save operation. In this case, the user tapping the negative
167     * button signals that they would prefer to not save the filled
168     * content.
169     */
170    public static final int NEGATIVE_BUTTON_STYLE_CANCEL = 0;
171
172    /**
173     * Style for the negative button of the save UI to reject the
174     * save operation. This could be useful if the user needs to
175     * opt-in your service and the save prompt is an advertisement
176     * of the potential value you can add to the user. In this
177     * case, the user tapping the negative button sends a strong
178     * signal that the feature may not be useful and you may
179     * consider some backoff strategy.
180     */
181    public static final int NEGATIVE_BUTTON_STYLE_REJECT = 1;
182
183    /** @hide */
184    @IntDef(
185        value = {
186                NEGATIVE_BUTTON_STYLE_CANCEL,
187                NEGATIVE_BUTTON_STYLE_REJECT})
188    @Retention(RetentionPolicy.SOURCE)
189    @interface NegativeButtonStyle{}
190
191    /** @hide */
192    @IntDef(
193       flag = true,
194       value = {
195               SAVE_DATA_TYPE_GENERIC,
196               SAVE_DATA_TYPE_PASSWORD,
197               SAVE_DATA_TYPE_ADDRESS,
198               SAVE_DATA_TYPE_CREDIT_CARD,
199               SAVE_DATA_TYPE_USERNAME,
200               SAVE_DATA_TYPE_EMAIL_ADDRESS})
201    @Retention(RetentionPolicy.SOURCE)
202    @interface SaveDataType{}
203
204    /**
205     * Usually {@link AutofillService#onSaveRequest(SaveRequest, SaveCallback)}
206     * is called once the {@link Activity} finishes. If this flag is set it is called once all
207     * saved views become invisible.
208     */
209    public static final int FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE = 0x1;
210
211    /** @hide */
212    @IntDef(
213            flag = true,
214            value = {FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE})
215    @Retention(RetentionPolicy.SOURCE)
216    @interface SaveInfoFlags{}
217
218    private final @SaveDataType int mType;
219    private final @NegativeButtonStyle int mNegativeButtonStyle;
220    private final IntentSender mNegativeActionListener;
221    private final AutofillId[] mRequiredIds;
222    private final AutofillId[] mOptionalIds;
223    private final CharSequence mDescription;
224    private final int mFlags;
225
226    private SaveInfo(Builder builder) {
227        mType = builder.mType;
228        mNegativeButtonStyle = builder.mNegativeButtonStyle;
229        mNegativeActionListener = builder.mNegativeActionListener;
230        mRequiredIds = builder.mRequiredIds;
231        mOptionalIds = builder.mOptionalIds;
232        mDescription = builder.mDescription;
233        mFlags = builder.mFlags;
234    }
235
236    /** @hide */
237    public @NegativeButtonStyle int getNegativeActionStyle() {
238        return mNegativeButtonStyle;
239    }
240
241    /** @hide */
242    public @Nullable IntentSender getNegativeActionListener() {
243        return mNegativeActionListener;
244    }
245
246    /** @hide */
247    public AutofillId[] getRequiredIds() {
248        return mRequiredIds;
249    }
250
251    /** @hide */
252    public @Nullable AutofillId[] getOptionalIds() {
253        return mOptionalIds;
254    }
255
256    /** @hide */
257    public @SaveDataType int getType() {
258        return mType;
259    }
260
261    /** @hide */
262    public @SaveInfoFlags int getFlags() {
263        return mFlags;
264    }
265
266    /** @hide */
267    public CharSequence getDescription() {
268        return mDescription;
269    }
270
271    /**
272     * A builder for {@link SaveInfo} objects.
273     */
274    public static final class Builder {
275
276        private final @SaveDataType int mType;
277        private @NegativeButtonStyle int mNegativeButtonStyle = NEGATIVE_BUTTON_STYLE_CANCEL;
278        private IntentSender mNegativeActionListener;
279        private final AutofillId[] mRequiredIds;
280        private AutofillId[] mOptionalIds;
281        private CharSequence mDescription;
282        private boolean mDestroyed;
283        private int mFlags;
284
285        /**
286         * Creates a new builder.
287         *
288         * @param type the type of information the associated {@link FillResponse} represents, can
289         * be any combination of {@link SaveInfo#SAVE_DATA_TYPE_GENERIC},
290         * {@link SaveInfo#SAVE_DATA_TYPE_PASSWORD},
291         * {@link SaveInfo#SAVE_DATA_TYPE_ADDRESS}, {@link SaveInfo#SAVE_DATA_TYPE_CREDIT_CARD},
292         * {@link SaveInfo#SAVE_DATA_TYPE_USERNAME}, or
293         * {@link SaveInfo#SAVE_DATA_TYPE_EMAIL_ADDRESS}.
294         * @param requiredIds ids of all required views that will trigger a save request.
295         *
296         * <p>See {@link SaveInfo} for more info.
297         *
298         * @throws IllegalArgumentException if {@code requiredIds} is {@code null} or empty, or if
299         * it contains any {@code null} entry.
300         */
301        public Builder(@SaveDataType int type, @NonNull AutofillId[] requiredIds) {
302            // TODO: add CTS unit tests (not integration) to assert the null cases
303            mType = type;
304            mRequiredIds = assertValid(requiredIds);
305        }
306
307        private AutofillId[] assertValid(AutofillId[] ids) {
308            Preconditions.checkArgument(ids != null && ids.length > 0,
309                    "must have at least one id: " + Arrays.toString(ids));
310            for (int i = 0; i < ids.length; i++) {
311                final AutofillId id = ids[i];
312                Preconditions.checkArgument(id != null,
313                        "cannot have null id: " + Arrays.toString(ids));
314            }
315            return ids;
316        }
317
318        /**
319         * Sets flags changing the save behavior.
320         *
321         * @param flags {@link #FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE} or {@code 0}.
322         * @return This builder.
323         */
324        public @NonNull Builder setFlags(@SaveInfoFlags int flags) {
325            throwIfDestroyed();
326
327            mFlags = Preconditions.checkFlagsArgument(flags, FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE);
328            return this;
329        }
330
331        /**
332         * Sets the ids of additional, optional views the service would be interested to save.
333         *
334         * <p>See {@link SaveInfo} for more info.
335         *
336         * @param ids The ids of the optional views.
337         * @return This builder.
338         *
339         * @throws IllegalArgumentException if {@code ids} is {@code null} or empty, or if
340         * it contains any {@code null} entry.
341         */
342        public @NonNull Builder setOptionalIds(@NonNull AutofillId[] ids) {
343            // TODO: add CTS unit tests (not integration) to assert the null cases
344            throwIfDestroyed();
345            mOptionalIds = assertValid(ids);
346            return this;
347        }
348
349        /**
350         * Sets an optional description to be shown in the UI when the user is asked to save.
351         *
352         * <p>Typically, it describes how the data will be stored by the service, so it can help
353         * users to decide whether they can trust the service to save their data.
354         *
355         * @param description a succint description.
356         * @return This Builder.
357         */
358        public @NonNull Builder setDescription(@Nullable CharSequence description) {
359            throwIfDestroyed();
360            mDescription = description;
361            return this;
362        }
363
364        /**
365         * Sets the style and listener for the negative save action.
366         *
367         * <p>This allows a fill-provider to customize the style and be
368         * notified when the user selects the negative action in the save
369         * UI. Note that selecting the negative action regardless of its style
370         * and listener being customized would dismiss the save UI and if a
371         * custom listener intent is provided then this intent will be
372         * started. The default style is {@link #NEGATIVE_BUTTON_STYLE_CANCEL}</p>
373         *
374         * @param style The action style.
375         * @param listener The action listener.
376         * @return This builder.
377         *
378         * @see #NEGATIVE_BUTTON_STYLE_CANCEL
379         * @see #NEGATIVE_BUTTON_STYLE_REJECT
380         *
381         * @throws IllegalArgumentException If the style is invalid
382         */
383        public @NonNull Builder setNegativeAction(@NegativeButtonStyle int style,
384                @Nullable IntentSender listener) {
385            throwIfDestroyed();
386            if (style != NEGATIVE_BUTTON_STYLE_CANCEL
387                    && style != NEGATIVE_BUTTON_STYLE_REJECT) {
388                throw new IllegalArgumentException("Invalid style: " + style);
389            }
390            mNegativeButtonStyle = style;
391            mNegativeActionListener = listener;
392            return this;
393        }
394
395        /**
396         * Builds a new {@link SaveInfo} instance.
397         */
398        public SaveInfo build() {
399            throwIfDestroyed();
400            mDestroyed = true;
401            return new SaveInfo(this);
402        }
403
404        private void throwIfDestroyed() {
405            if (mDestroyed) {
406                throw new IllegalStateException("Already called #build()");
407            }
408        }
409
410    }
411
412    /////////////////////////////////////
413    // Object "contract" methods. //
414    /////////////////////////////////////
415    @Override
416    public String toString() {
417        if (!sDebug) return super.toString();
418
419        return new StringBuilder("SaveInfo: [type=")
420                .append(DebugUtils.flagsToString(SaveInfo.class, "SAVE_DATA_TYPE_", mType))
421                .append(", requiredIds=").append(Arrays.toString(mRequiredIds))
422                .append(", optionalIds=").append(Arrays.toString(mOptionalIds))
423                .append(", description=").append(mDescription)
424                .append(DebugUtils.flagsToString(SaveInfo.class, "NEGATIVE_BUTTON_STYLE_",
425                        mNegativeButtonStyle))
426                .append(", mFlags=").append(mFlags)
427                .append("]").toString();
428    }
429
430    /////////////////////////////////////
431    // Parcelable "contract" methods. //
432    /////////////////////////////////////
433
434    @Override
435    public int describeContents() {
436        return 0;
437    }
438
439    @Override
440    public void writeToParcel(Parcel parcel, int flags) {
441        parcel.writeInt(mType);
442        parcel.writeParcelableArray(mRequiredIds, flags);
443        parcel.writeInt(mNegativeButtonStyle);
444        parcel.writeParcelable(mNegativeActionListener, flags);
445        parcel.writeParcelableArray(mOptionalIds, flags);
446        parcel.writeCharSequence(mDescription);
447        parcel.writeInt(mFlags);
448    }
449
450    public static final Parcelable.Creator<SaveInfo> CREATOR = new Parcelable.Creator<SaveInfo>() {
451        @Override
452        public SaveInfo createFromParcel(Parcel parcel) {
453            // Always go through the builder to ensure the data ingested by
454            // the system obeys the contract of the builder to avoid attacks
455            // using specially crafted parcels.
456            final Builder builder = new Builder(parcel.readInt(),
457                    parcel.readParcelableArray(null, AutofillId.class));
458            builder.setNegativeAction(parcel.readInt(), parcel.readParcelable(null));
459            final AutofillId[] optionalIds = parcel.readParcelableArray(null, AutofillId.class);
460            if (optionalIds != null) {
461                builder.setOptionalIds(optionalIds);
462            }
463            builder.setDescription(parcel.readCharSequence());
464            builder.setFlags(parcel.readInt());
465            return builder.build();
466        }
467
468        @Override
469        public SaveInfo[] newArray(int size) {
470            return new SaveInfo[size];
471        }
472    };
473}
474