SaveInfo.java revision 0962262f42e3636270689100e1bb90b8f3cf6d77
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.Bundle;
26import android.os.Parcel;
27import android.os.Parcelable;
28import android.view.autofill.AutoFillId;
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(android.app.assist.AssistStructure, Bundle, 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 of user data that would be saved (like passoword or credit card info).
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 *  Typically, the {@link SaveInfo} contains the same {@code id}s as the {@link Dataset}:
55 *
56 * <pre class="prettyprint">
57 *  new FillResponse.Builder()
58 *      .add(new Dataset.Builder(createPresentation())
59 *          .setValue(id1, AutofillValue.forText("homer"))
60 *          .setValue(id2, AutofillValue.forText("D'OH!"))
61 *          .build())
62 *      .setSaveInfo(new SaveInfo.Builder(SaveInfo.SAVE_INFO_TYPE_PASSWORD, new int[] {id1, id2})
63 *                  .build())
64 *      .build();
65 * </pre>
66 *
67 * There might be cases where the {@link AutofillService} knows how to fill the
68 * {@link android.app.Activity}, but the user has no data for it. In that case, the
69 * {@link FillResponse} should contain just the {@link SaveInfo}, but no {@link Dataset}s:
70 *
71 * <pre class="prettyprint">
72 *  new FillResponse.Builder()
73 *      .setSaveInfo(new SaveInfo.Builder(SaveInfo.SAVE_INFO_TYPE_PASSWORD, new int[] {id1, id2})
74 *                  .build())
75 *      .build();
76 * </pre>
77 *
78 * <p>There might be cases where the user data in the {@link AutofillService} is enough
79 * to populate some fields but not all, and the service would still be interested on saving the
80 * other fields. In this scenario, the service could set the
81 * {@link SaveInfo.Builder#setOptionalIds(AutofillId[])} as well:
82 *
83 * <pre class="prettyprint">
84 *   new FillResponse.Builder()
85 *       .add(new Dataset.Builder(createPresentation())
86 *          .setValue(id1, AutofillValue.forText("742 Evergreen Terrace"))  // street
87 *          .setValue(id2, AutofillValue.forText("Springfield"))            // city
88 *          .build())
89 *       .setSaveInfo(new SaveInfo.Builder(SaveInfo.SAVE_INFO_TYPE_ADDRESS, new int[] {id1, id2})
90 *                   .setOptionalIds(new int[] {id3, id4}) // state and zipcode
91 *                   .build())
92 *       .build();
93 * </pre>
94 *
95 * The
96 * {@link AutofillService#onSaveRequest(android.app.assist.AssistStructure, Bundle, SaveCallback)}
97 * is triggered after a call to {@link AutofillManager#commit()}, but only when all conditions
98 * below are met:
99 *
100 * <ol>
101 *   <li>The {@link SaveInfo} associated with the {@link FillResponse} is not {@code null}.
102 *   <li>The {@link AutofillValue} of all required views (as set by the {@code requiredIds} passed
103 *       to {@link SaveInfo.Builder} constructor are not empty.
104 *   <li>The {@link AutofillValue} of at least one view (be it required or optional) has changed
105 *       (i.e., it's not the same value passed in a {@link Dataset}).
106 *   <li>The user explicitly tapped the affordance asking to save data for autofill.
107 * </ol>
108 */
109public final class SaveInfo implements Parcelable {
110
111    /**
112     * Type used on when the service can save the contents of an activity, but cannot describe what
113     * the content is for.
114     */
115    public static final int SAVE_DATA_TYPE_GENERIC = 0;
116
117    /**
118     * Type used when the {@link FillResponse} represents user credentials that have a password.
119     */
120    public static final int SAVE_DATA_TYPE_PASSWORD = 1;
121
122    /**
123     * Type used on when the {@link FillResponse} represents a physical address (such as street,
124     * city, state, etc).
125     */
126    public static final int SAVE_DATA_TYPE_ADDRESS = 2;
127
128    /**
129     * Type used when the {@link FillResponse} represents a credit card.
130     */
131    public static final int SAVE_DATA_TYPE_CREDIT_CARD = 3;
132
133    /**
134     * Type used when the {@link FillResponse} represents just an username, without a password.
135     */
136    public static final int SAVE_DATA_TYPE_USERNAME = 4;
137
138    /**
139     * Type used when the {@link FillResponse} represents just an email address, without a password.
140     */
141    public static final int SAVE_DATA_TYPE_EMAIL_ADDRESS = 5;
142
143    private final @SaveDataType int mType;
144    private final CharSequence mNegativeActionTitle;
145    private final IntentSender mNegativeActionListener;
146    private final AutofillId[] mRequiredIds;
147    private final AutofillId[] mOptionalIds;
148    private final CharSequence mDescription;
149
150    /** @hide */
151    @IntDef({
152        SAVE_DATA_TYPE_GENERIC,
153        SAVE_DATA_TYPE_PASSWORD,
154        SAVE_DATA_TYPE_ADDRESS,
155        SAVE_DATA_TYPE_CREDIT_CARD
156    })
157    @Retention(RetentionPolicy.SOURCE)
158    public @interface SaveDataType {
159    }
160
161    private SaveInfo(Builder builder) {
162        mType = builder.mType;
163        mNegativeActionTitle = builder.mNegativeActionTitle;
164        mNegativeActionListener = builder.mNegativeActionListener;
165        mRequiredIds = builder.mRequiredIds;
166        mOptionalIds = builder.mOptionalIds;
167        mDescription = builder.mDescription;
168    }
169
170    /** @hide */
171    public @Nullable CharSequence getNegativeActionTitle() {
172        return mNegativeActionTitle;
173    }
174
175    /** @hide */
176    public @Nullable IntentSender getNegativeActionListener() {
177        return mNegativeActionListener;
178    }
179
180    /** @hide */
181    public AutofillId[] getRequiredIds() {
182        return mRequiredIds;
183    }
184
185    /** @hide */
186    public @Nullable AutofillId[] getOptionalIds() {
187        return mOptionalIds;
188    }
189
190    /** @hide */
191    public int getType() {
192        return mType;
193    }
194
195    /** @hide */
196    public CharSequence getDescription() {
197        return mDescription;
198    }
199
200    /**
201     * A builder for {@link SaveInfo} objects.
202     */
203    public static final class Builder {
204
205        private final @SaveDataType int mType;
206        private CharSequence mNegativeActionTitle;
207        private IntentSender mNegativeActionListener;
208        // TODO(b/33197203): make mRequiredIds final once addSavableIds() is gone
209        private AutofillId[] mRequiredIds;
210        private AutofillId[] mOptionalIds;
211        private CharSequence mDescription;
212        private boolean mDestroyed;
213
214        /**
215         * Creates a new builder.
216         *
217         * @param type the type of information the associated {@link FillResponse} represents. Must
218         * be {@link SaveInfo#SAVE_DATA_TYPE_GENERIC}, {@link SaveInfo#SAVE_DATA_TYPE_PASSWORD},
219         * {@link SaveInfo#SAVE_DATA_TYPE_ADDRESS}, or {@link SaveInfo#SAVE_DATA_TYPE_CREDIT_CARD};
220         * otherwise it will assume {@link SaveInfo#SAVE_DATA_TYPE_GENERIC}.
221         * @param requiredIds ids of all required views that will trigger a save request.
222         *
223         * <p>See {@link SaveInfo} for more info.
224         *
225         * @throws IllegalArgumentException if {@code requiredIds} is {@code null} or empty.
226         */
227        public Builder(@SaveDataType int type, @NonNull AutofillId[] requiredIds) {
228            if (false) {// TODO(b/33197203): re-move when clients use it
229            Preconditions.checkArgument(requiredIds != null && requiredIds.length > 0,
230                    "must have at least one required id: " + Arrays.toString(requiredIds));
231            }
232            switch (type) {
233                case SAVE_DATA_TYPE_PASSWORD:
234                case SAVE_DATA_TYPE_ADDRESS:
235                case SAVE_DATA_TYPE_CREDIT_CARD:
236                case SAVE_DATA_TYPE_USERNAME:
237                case SAVE_DATA_TYPE_EMAIL_ADDRESS:
238                    mType = type;
239                    break;
240                default:
241                    mType = SAVE_DATA_TYPE_GENERIC;
242            }
243            mRequiredIds = requiredIds;
244        }
245
246        /**
247         * @hide
248         * @deprecated
249         * // TODO(b/33197203): make sure is removed when clients migrated
250         */
251        @Deprecated
252        public Builder(@SaveDataType int type) {
253            this(type, null);
254        }
255
256        /**
257         * @hide
258         * @deprecated
259         * // TODO(b/33197203): make sure is removed when clients migrated
260         */
261        @Deprecated
262        public @NonNull Builder addSavableIds(@Nullable AutofillId... ids) {
263            throwIfDestroyed();
264            mRequiredIds = ids;
265            return this;
266        }
267
268        /**
269         * Sets the ids of additional, optional views the service would be interested to save.
270         *
271         * <p>See {@link SaveInfo} for more info.
272         *
273         * @param ids The ids of the optional views.
274         * @return This builder.
275         */
276        public @NonNull Builder setOptionalIds(@Nullable AutofillId[] ids) {
277            throwIfDestroyed();
278            if (ids != null && ids.length != 0) {
279                mOptionalIds = ids;
280            }
281            return this;
282        }
283
284
285        /**
286         * @hide
287         */
288        // TODO(b/33197203): temporary fix to runtime crash
289        public @NonNull Builder addSavableIds(@Nullable AutoFillId... ids) {
290            throwIfDestroyed();
291
292            if (ids == null || ids.length == 0) {
293                return this;
294            }
295            if (mRequiredIds == null) {
296                mRequiredIds = new AutofillId[ids.length];
297            }
298            for (int i = 0; i < ids.length; i++) {
299                mRequiredIds[i] = ids[i].getDaRealId();
300            }
301            return this;
302        }
303
304        /**
305         * Sets an optional description to be shown in the UI when the user is asked to save.
306         *
307         * <p>Typically, it describes how the data will be stored by the service, so it can help
308         * users to decide whether they can trust the service to save their data.
309         *
310         * @param description a succint description.
311         * @return This Builder.
312         */
313        public @NonNull Builder setDescription(@Nullable CharSequence description) {
314            throwIfDestroyed();
315            mDescription = description;
316            return this;
317        }
318
319        /**
320         * Sets the title and listener for the negative save action.
321         *
322         * <p>This allows a fill-provider to customize the text and be
323         * notified when the user selects the negative action in the save
324         * UI. Note that selecting the negative action regardless of its text
325         * and listener being customized would dismiss the save UI and if a
326         * custom listener intent is provided then this intent will be
327         * started.</p>
328         *
329         * <p>This customization could be useful for providing additional
330         * semantics to the negative action. For example, a fill-provider
331         * can use this mechanism to add a "Disable" function or a "More info"
332         * function, etc. Note that the save action is exclusively controlled
333         * by the platform to ensure user consent is collected to release
334         * data from the filled app to the fill-provider.</p>
335         *
336         * @param title The action title.
337         * @param listener The action listener.
338         * @return This builder.
339         *
340         * @throws IllegalArgumentException If the title and the listener
341         *     are not both either null or non-null.
342         */
343        public @NonNull Builder setNegativeAction(@Nullable CharSequence title,
344                @Nullable IntentSender listener) {
345            throwIfDestroyed();
346            if (title == null ^ listener == null) {
347                throw new IllegalArgumentException("title and listener"
348                        + " must be both non-null or null");
349            }
350            mNegativeActionTitle = title;
351            mNegativeActionListener = listener;
352            return this;
353        }
354
355        /**
356         * Builds a new {@link SaveInfo} instance.
357         */
358        public SaveInfo build() {
359            throwIfDestroyed();
360            mDestroyed = true;
361            return new SaveInfo(this);
362        }
363
364        private void throwIfDestroyed() {
365            if (mDestroyed) {
366                throw new IllegalStateException("Already called #build()");
367            }
368        }
369
370    }
371
372    /////////////////////////////////////
373    // Object "contract" methods. //
374    /////////////////////////////////////
375    @Override
376    public String toString() {
377        if (!DEBUG) return super.toString();
378
379        return new StringBuilder("SaveInfo: [type=").append(mType)
380                .append(", requiredIds=").append(Arrays.toString(mRequiredIds))
381                .append(", optionalIds=").append(Arrays.toString(mOptionalIds))
382                .append(", description=").append(mDescription)
383                .append("]").toString();
384    }
385
386    /////////////////////////////////////
387    // Parcelable "contract" methods. //
388    /////////////////////////////////////
389
390    @Override
391    public int describeContents() {
392        return 0;
393    }
394
395    @Override
396    public void writeToParcel(Parcel parcel, int flags) {
397        parcel.writeInt(mType);
398        parcel.writeParcelableArray(mRequiredIds, flags);
399        parcel.writeCharSequence(mNegativeActionTitle);
400        parcel.writeParcelable(mNegativeActionListener, flags);
401        parcel.writeParcelableArray(mOptionalIds, flags);
402        parcel.writeCharSequence(mDescription);
403    }
404
405    public static final Parcelable.Creator<SaveInfo> CREATOR = new Parcelable.Creator<SaveInfo>() {
406        @Override
407        public SaveInfo createFromParcel(Parcel parcel) {
408            // Always go through the builder to ensure the data ingested by
409            // the system obeys the contract of the builder to avoid attacks
410            // using specially crafted parcels.
411            final Builder builder = new Builder(parcel.readInt(),
412                    parcel.readParcelableArray(null, AutofillId.class));
413            builder.setNegativeAction(parcel.readCharSequence(), parcel.readParcelable(null));
414            builder.setOptionalIds(parcel.readParcelableArray(null, AutofillId.class));
415            builder.setDescription(parcel.readCharSequence());
416            return builder.build();
417        }
418
419        @Override
420        public SaveInfo[] newArray(int size) {
421            return new SaveInfo[size];
422        }
423    };
424}
425