SaveInfo.java revision 73fedacfebcccd54f2c36ff7c8eb9929e8ce8455
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.
277         */
278        public Builder(@SaveDataType int type, @NonNull AutofillId[] requiredIds) {
279            Preconditions.checkArgument(requiredIds != null && requiredIds.length > 0,
280                    "must have at least one required id: " + Arrays.toString(requiredIds));
281            mType = type;
282            mRequiredIds = requiredIds;
283        }
284
285        /**
286         * Set flags changing the save behavior.
287         *
288         * @param flags {@link #FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE} or 0.
289         * @return This builder.
290         */
291        public @NonNull Builder setFlags(@SaveInfoFlags int flags) {
292            throwIfDestroyed();
293
294            mFlags = Preconditions.checkFlagsArgument(flags, FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE);
295            return this;
296        }
297
298        /**
299         * Sets the ids of additional, optional views the service would be interested to save.
300         *
301         * <p>See {@link SaveInfo} for more info.
302         *
303         * @param ids The ids of the optional views.
304         * @return This builder.
305         */
306        public @NonNull Builder setOptionalIds(@Nullable AutofillId[] ids) {
307            throwIfDestroyed();
308            if (ids != null && ids.length != 0) {
309                mOptionalIds = ids;
310            }
311            return this;
312        }
313
314        /**
315         * Sets an optional description to be shown in the UI when the user is asked to save.
316         *
317         * <p>Typically, it describes how the data will be stored by the service, so it can help
318         * users to decide whether they can trust the service to save their data.
319         *
320         * @param description a succint description.
321         * @return This Builder.
322         */
323        public @NonNull Builder setDescription(@Nullable CharSequence description) {
324            throwIfDestroyed();
325            mDescription = description;
326            return this;
327        }
328
329        /**
330         * Sets the style and listener for the negative save action.
331         *
332         * <p>This allows a fill-provider to customize the style and be
333         * notified when the user selects the negative action in the save
334         * UI. Note that selecting the negative action regardless of its style
335         * and listener being customized would dismiss the save UI and if a
336         * custom listener intent is provided then this intent will be
337         * started. The default style is {@link #NEGATIVE_BUTTON_STYLE_CANCEL}</p>
338         *
339         * @param style The action style.
340         * @param listener The action listener.
341         * @return This builder.
342         *
343         * @see #NEGATIVE_BUTTON_STYLE_CANCEL
344         * @see #NEGATIVE_BUTTON_STYLE_REJECT
345         *
346         * @throws IllegalArgumentException If the style is invalid
347         */
348        public @NonNull Builder setNegativeAction(@NegativeButtonStyle int style,
349                @Nullable IntentSender listener) {
350            throwIfDestroyed();
351            if (style != NEGATIVE_BUTTON_STYLE_CANCEL
352                    && style != NEGATIVE_BUTTON_STYLE_REJECT) {
353                throw new IllegalArgumentException("Invalid style: " + style);
354            }
355            mNegativeButtonStyle = style;
356            mNegativeActionListener = listener;
357            return this;
358        }
359
360        /**
361         * Builds a new {@link SaveInfo} instance.
362         */
363        public SaveInfo build() {
364            throwIfDestroyed();
365            mDestroyed = true;
366            return new SaveInfo(this);
367        }
368
369        private void throwIfDestroyed() {
370            if (mDestroyed) {
371                throw new IllegalStateException("Already called #build()");
372            }
373        }
374
375    }
376
377    /////////////////////////////////////
378    // Object "contract" methods. //
379    /////////////////////////////////////
380    @Override
381    public String toString() {
382        if (!sDebug) return super.toString();
383
384        return new StringBuilder("SaveInfo: [type=")
385                .append(DebugUtils.flagsToString(SaveInfo.class, "SAVE_DATA_TYPE_", mType))
386                .append(", requiredIds=").append(Arrays.toString(mRequiredIds))
387                .append(", optionalIds=").append(Arrays.toString(mOptionalIds))
388                .append(", description=").append(mDescription)
389                .append(DebugUtils.flagsToString(SaveInfo.class, "NEGATIVE_BUTTON_STYLE_",
390                        mNegativeButtonStyle))
391                .append(", mFlags=").append(mFlags)
392                .append("]").toString();
393    }
394
395    /////////////////////////////////////
396    // Parcelable "contract" methods. //
397    /////////////////////////////////////
398
399    @Override
400    public int describeContents() {
401        return 0;
402    }
403
404    @Override
405    public void writeToParcel(Parcel parcel, int flags) {
406        parcel.writeInt(mType);
407        parcel.writeParcelableArray(mRequiredIds, flags);
408        parcel.writeInt(mNegativeButtonStyle);
409        parcel.writeParcelable(mNegativeActionListener, flags);
410        parcel.writeParcelableArray(mOptionalIds, flags);
411        parcel.writeCharSequence(mDescription);
412        parcel.writeInt(mFlags);
413    }
414
415    public static final Parcelable.Creator<SaveInfo> CREATOR = new Parcelable.Creator<SaveInfo>() {
416        @Override
417        public SaveInfo createFromParcel(Parcel parcel) {
418            // Always go through the builder to ensure the data ingested by
419            // the system obeys the contract of the builder to avoid attacks
420            // using specially crafted parcels.
421            final Builder builder = new Builder(parcel.readInt(),
422                    parcel.readParcelableArray(null, AutofillId.class));
423            builder.setNegativeAction(parcel.readInt(), parcel.readParcelable(null));
424            builder.setOptionalIds(parcel.readParcelableArray(null, AutofillId.class));
425            builder.setDescription(parcel.readCharSequence());
426            builder.setFlags(parcel.readInt());
427            return builder.build();
428        }
429
430        @Override
431        public SaveInfo[] newArray(int size) {
432            return new SaveInfo[size];
433        }
434    };
435}
436