SaveInfo.java revision dc6cccb905d77b1fd954c883e4369e7b401cb457
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.DEBUG;
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_EMAIL_ADDRESS})
178    @Retention(RetentionPolicy.SOURCE)
179    @interface SaveDataType{}
180
181    /**
182     * Usually {@link AutofillService#onSaveRequest(SaveRequest, SaveCallback)}
183     * is called once the activity finishes. If this flag is set it is called once all saved views
184     * become invisible.
185     */
186    public static final int FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE = 0x1;
187
188    /** @hide */
189    @IntDef(
190            flag = true,
191            value = {FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE})
192    @Retention(RetentionPolicy.SOURCE)
193    @interface SaveInfoFlags{}
194
195    private final @SaveDataType int mType;
196    private final @NegativeButtonStyle int mNegativeButtonStyle;
197    private final IntentSender mNegativeActionListener;
198    private final AutofillId[] mRequiredIds;
199    private final AutofillId[] mOptionalIds;
200    private final CharSequence mDescription;
201    private final int mFlags;
202
203    private SaveInfo(Builder builder) {
204        mType = builder.mType;
205        mNegativeButtonStyle = builder.mNegativeButtonStyle;
206        mNegativeActionListener = builder.mNegativeActionListener;
207        mRequiredIds = builder.mRequiredIds;
208        mOptionalIds = builder.mOptionalIds;
209        mDescription = builder.mDescription;
210        mFlags = builder.mFlags;
211    }
212
213    /** @hide */
214    public @NegativeButtonStyle int getNegativeActionStyle() {
215        return mNegativeButtonStyle;
216    }
217
218    /** @hide */
219    public @Nullable IntentSender getNegativeActionListener() {
220        return mNegativeActionListener;
221    }
222
223    /** @hide */
224    public AutofillId[] getRequiredIds() {
225        return mRequiredIds;
226    }
227
228    /** @hide */
229    public @Nullable AutofillId[] getOptionalIds() {
230        return mOptionalIds;
231    }
232
233    /** @hide */
234    public @SaveDataType int getType() {
235        return mType;
236    }
237
238    /** @hide */
239    public @SaveInfoFlags int getFlags() {
240        return mFlags;
241    }
242
243    /** @hide */
244    public CharSequence getDescription() {
245        return mDescription;
246    }
247
248    /**
249     * A builder for {@link SaveInfo} objects.
250     */
251    public static final class Builder {
252
253        private final @SaveDataType int mType;
254        private @NegativeButtonStyle int mNegativeButtonStyle = NEGATIVE_BUTTON_STYLE_CANCEL;
255        private IntentSender mNegativeActionListener;
256        private final AutofillId[] mRequiredIds;
257        private AutofillId[] mOptionalIds;
258        private CharSequence mDescription;
259        private boolean mDestroyed;
260        private int mFlags;
261
262        /**
263         * Creates a new builder.
264         *
265         * @param type the type of information the associated {@link FillResponse} represents, can
266         * be any combination of {@link SaveInfo#SAVE_DATA_TYPE_GENERIC},
267         * {@link SaveInfo#SAVE_DATA_TYPE_PASSWORD},
268         * {@link SaveInfo#SAVE_DATA_TYPE_ADDRESS}, {@link SaveInfo#SAVE_DATA_TYPE_CREDIT_CARD},
269         * {@link SaveInfo#SAVE_DATA_TYPE_USERNAME}, or
270         * {@link SaveInfo#SAVE_DATA_TYPE_EMAIL_ADDRESS}.
271         * @param requiredIds ids of all required views that will trigger a save request.
272         *
273         * <p>See {@link SaveInfo} for more info.
274         *
275         * @throws IllegalArgumentException if {@code requiredIds} is {@code null} or empty.
276         */
277        public Builder(@SaveDataType int type, @NonNull AutofillId[] requiredIds) {
278            Preconditions.checkArgument(requiredIds != null && requiredIds.length > 0,
279                    "must have at least one required id: " + Arrays.toString(requiredIds));
280            mType = type;
281            mRequiredIds = requiredIds;
282        }
283
284        /**
285         * Set flags changing the save behavior.
286         *
287         * @param flags {@link #FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE} or 0.
288         * @return This builder.
289         */
290        public @NonNull Builder setFlags(@SaveInfoFlags int flags) {
291            throwIfDestroyed();
292
293            mFlags = Preconditions.checkFlagsArgument(flags, FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE);
294            return this;
295        }
296
297        /**
298         * Sets the ids of additional, optional views the service would be interested to save.
299         *
300         * <p>See {@link SaveInfo} for more info.
301         *
302         * @param ids The ids of the optional views.
303         * @return This builder.
304         */
305        public @NonNull Builder setOptionalIds(@Nullable AutofillId[] ids) {
306            throwIfDestroyed();
307            if (ids != null && ids.length != 0) {
308                mOptionalIds = ids;
309            }
310            return this;
311        }
312
313        /**
314         * Sets an optional description to be shown in the UI when the user is asked to save.
315         *
316         * <p>Typically, it describes how the data will be stored by the service, so it can help
317         * users to decide whether they can trust the service to save their data.
318         *
319         * @param description a succint description.
320         * @return This Builder.
321         */
322        public @NonNull Builder setDescription(@Nullable CharSequence description) {
323            throwIfDestroyed();
324            mDescription = description;
325            return this;
326        }
327
328        /** @hide */
329        // TODO (b/37563972): Remove when callers migrate
330        public @NonNull Builder setNegativeAction(@Nullable CharSequence title,
331                @Nullable IntentSender listener) {
332            throwIfDestroyed();
333            setNegativeAction(NEGATIVE_BUTTON_STYLE_CANCEL, listener);
334            return this;
335        }
336
337        /**
338         * Sets the style and listener for the negative save action.
339         *
340         * <p>This allows a fill-provider to customize the style and be
341         * notified when the user selects the negative action in the save
342         * UI. Note that selecting the negative action regardless of its style
343         * and listener being customized would dismiss the save UI and if a
344         * custom listener intent is provided then this intent will be
345         * started. The default style is {@link #NEGATIVE_BUTTON_STYLE_CANCEL}</p>
346         *
347         * @param style The action style.
348         * @param listener The action listener.
349         * @return This builder.
350         *
351         * @see #NEGATIVE_BUTTON_STYLE_CANCEL
352         * @see #NEGATIVE_BUTTON_STYLE_REJECT
353         *
354         * @throws IllegalArgumentException If the style is invalid
355         */
356        public @NonNull Builder setNegativeAction(@NegativeButtonStyle int style,
357                @Nullable IntentSender listener) {
358            throwIfDestroyed();
359            if (style != NEGATIVE_BUTTON_STYLE_CANCEL
360                    && style != NEGATIVE_BUTTON_STYLE_REJECT) {
361                throw new IllegalArgumentException("Invalid style: " + style);
362            }
363            mNegativeButtonStyle = style;
364            mNegativeActionListener = listener;
365            return this;
366        }
367
368        /**
369         * Builds a new {@link SaveInfo} instance.
370         */
371        public SaveInfo build() {
372            throwIfDestroyed();
373            mDestroyed = true;
374            return new SaveInfo(this);
375        }
376
377        private void throwIfDestroyed() {
378            if (mDestroyed) {
379                throw new IllegalStateException("Already called #build()");
380            }
381        }
382
383    }
384
385    /////////////////////////////////////
386    // Object "contract" methods. //
387    /////////////////////////////////////
388    @Override
389    public String toString() {
390        if (!DEBUG) return super.toString();
391
392        return new StringBuilder("SaveInfo: [type=")
393                .append(DebugUtils.flagsToString(SaveInfo.class, "SAVE_DATA_TYPE_", mType))
394                .append(", requiredIds=").append(Arrays.toString(mRequiredIds))
395                .append(", optionalIds=").append(Arrays.toString(mOptionalIds))
396                .append(", description=").append(mDescription)
397                .append(DebugUtils.flagsToString(SaveInfo.class, "NEGATIVE_BUTTON_STYLE_",
398                        mNegativeButtonStyle))
399                .append(", mFlags=").append(mFlags)
400                .append("]").toString();
401    }
402
403    /////////////////////////////////////
404    // Parcelable "contract" methods. //
405    /////////////////////////////////////
406
407    @Override
408    public int describeContents() {
409        return 0;
410    }
411
412    @Override
413    public void writeToParcel(Parcel parcel, int flags) {
414        parcel.writeInt(mType);
415        parcel.writeParcelableArray(mRequiredIds, flags);
416        parcel.writeInt(mNegativeButtonStyle);
417        parcel.writeParcelable(mNegativeActionListener, flags);
418        parcel.writeParcelableArray(mOptionalIds, flags);
419        parcel.writeCharSequence(mDescription);
420        parcel.writeInt(mFlags);
421    }
422
423    public static final Parcelable.Creator<SaveInfo> CREATOR = new Parcelable.Creator<SaveInfo>() {
424        @Override
425        public SaveInfo createFromParcel(Parcel parcel) {
426            // Always go through the builder to ensure the data ingested by
427            // the system obeys the contract of the builder to avoid attacks
428            // using specially crafted parcels.
429            final Builder builder = new Builder(parcel.readInt(),
430                    parcel.readParcelableArray(null, AutofillId.class));
431            builder.setNegativeAction(parcel.readInt(), parcel.readParcelable(null));
432            builder.setOptionalIds(parcel.readParcelableArray(null, AutofillId.class));
433            builder.setDescription(parcel.readCharSequence());
434            builder.setFlags(parcel.readInt());
435            return builder.build();
436        }
437
438        @Override
439        public SaveInfo[] newArray(int size) {
440            return new SaveInfo[size];
441        }
442    };
443}
444