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