SaveInfo.java revision 013efe173e56612a910ebd8576480ce4ef005e3c
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.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(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 = 0x0;
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 = 0x01;
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 = 0x02;
127
128    /**
129     * Type used when the {@link FillResponse} represents a credit card.
130     */
131    public static final int SAVE_DATA_TYPE_CREDIT_CARD = 0x04;
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 = 0x08;
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 = 0x10;
142
143    /** @hide */
144    @IntDef(
145       flag = true,
146       value = {
147               SAVE_DATA_TYPE_GENERIC,
148               SAVE_DATA_TYPE_PASSWORD,
149               SAVE_DATA_TYPE_ADDRESS,
150               SAVE_DATA_TYPE_CREDIT_CARD,
151               SAVE_DATA_TYPE_EMAIL_ADDRESS})
152    @Retention(RetentionPolicy.SOURCE)
153    @interface SaveDataType{}
154
155    private final @SaveDataType int mType;
156    private final CharSequence mNegativeActionTitle;
157    private final IntentSender mNegativeActionListener;
158    private final AutofillId[] mRequiredIds;
159    private final AutofillId[] mOptionalIds;
160    private final CharSequence mDescription;
161
162    private SaveInfo(Builder builder) {
163        mType = builder.mType;
164        mNegativeActionTitle = builder.mNegativeActionTitle;
165        mNegativeActionListener = builder.mNegativeActionListener;
166        mRequiredIds = builder.mRequiredIds;
167        mOptionalIds = builder.mOptionalIds;
168        mDescription = builder.mDescription;
169    }
170
171    /** @hide */
172    public @Nullable CharSequence getNegativeActionTitle() {
173        return mNegativeActionTitle;
174    }
175
176    /** @hide */
177    public @Nullable IntentSender getNegativeActionListener() {
178        return mNegativeActionListener;
179    }
180
181    /** @hide */
182    public AutofillId[] getRequiredIds() {
183        return mRequiredIds;
184    }
185
186    /** @hide */
187    public @Nullable AutofillId[] getOptionalIds() {
188        return mOptionalIds;
189    }
190
191    /** @hide */
192    public @SaveDataType int getType() {
193        return mType;
194    }
195
196    /** @hide */
197    public CharSequence getDescription() {
198        return mDescription;
199    }
200
201    /**
202     * A builder for {@link SaveInfo} objects.
203     */
204    public static final class Builder {
205
206        private final @SaveDataType int mType;
207        private CharSequence mNegativeActionTitle;
208        private IntentSender mNegativeActionListener;
209        // TODO(b/33197203): make mRequiredIds final once addSavableIds() is gone
210        private AutofillId[] mRequiredIds;
211        private AutofillId[] mOptionalIds;
212        private CharSequence mDescription;
213        private boolean mDestroyed;
214
215        /**
216         * Creates a new builder.
217         *
218         * @param type the type of information the associated {@link FillResponse} represents, can
219         * be any combination of {@link SaveInfo#SAVE_DATA_TYPE_GENERIC},
220         * {@link SaveInfo#SAVE_DATA_TYPE_PASSWORD},
221         * {@link SaveInfo#SAVE_DATA_TYPE_ADDRESS}, {@link SaveInfo#SAVE_DATA_TYPE_CREDIT_CARD},
222         * {@link SaveInfo#SAVE_DATA_TYPE_USERNAME}, or
223         * {@link SaveInfo#SAVE_DATA_TYPE_EMAIL_ADDRESS}.
224         * @param requiredIds ids of all required views that will trigger a save request.
225         *
226         * <p>See {@link SaveInfo} for more info.
227         *
228         * @throws IllegalArgumentException if {@code requiredIds} is {@code null} or empty.
229         */
230        public Builder(@SaveDataType int type, @NonNull AutofillId[] requiredIds) {
231            if (false) {// TODO(b/33197203): re-move when clients use it
232            Preconditions.checkArgument(requiredIds != null && requiredIds.length > 0,
233                    "must have at least one required id: " + Arrays.toString(requiredIds));
234            }
235            mType = type;
236            mRequiredIds = requiredIds;
237        }
238
239        /**
240         * @hide
241         * @deprecated
242         * // TODO(b/33197203): make sure is removed when clients migrated
243         */
244        @Deprecated
245        public Builder(@SaveDataType int type) {
246            this(type, null);
247        }
248
249        /**
250         * @hide
251         * @deprecated
252         * // TODO(b/33197203): make sure is removed when clients migrated
253         */
254        @Deprecated
255        public @NonNull Builder addSavableIds(@Nullable AutofillId... ids) {
256            throwIfDestroyed();
257            mRequiredIds = ids;
258            return this;
259        }
260
261        /**
262         * Sets the ids of additional, optional views the service would be interested to save.
263         *
264         * <p>See {@link SaveInfo} for more info.
265         *
266         * @param ids The ids of the optional views.
267         * @return This builder.
268         */
269        public @NonNull Builder setOptionalIds(@Nullable AutofillId[] ids) {
270            throwIfDestroyed();
271            if (ids != null && ids.length != 0) {
272                mOptionalIds = ids;
273            }
274            return this;
275        }
276
277        /**
278         * Sets an optional description to be shown in the UI when the user is asked to save.
279         *
280         * <p>Typically, it describes how the data will be stored by the service, so it can help
281         * users to decide whether they can trust the service to save their data.
282         *
283         * @param description a succint description.
284         * @return This Builder.
285         */
286        public @NonNull Builder setDescription(@Nullable CharSequence description) {
287            throwIfDestroyed();
288            mDescription = description;
289            return this;
290        }
291
292        /**
293         * Sets the title and listener for the negative save action.
294         *
295         * <p>This allows a fill-provider to customize the text and be
296         * notified when the user selects the negative action in the save
297         * UI. Note that selecting the negative action regardless of its text
298         * and listener being customized would dismiss the save UI and if a
299         * custom listener intent is provided then this intent will be
300         * started.</p>
301         *
302         * <p>This customization could be useful for providing additional
303         * semantics to the negative action. For example, a fill-provider
304         * can use this mechanism to add a "Disable" function or a "More info"
305         * function, etc. Note that the save action is exclusively controlled
306         * by the platform to ensure user consent is collected to release
307         * data from the filled app to the fill-provider.</p>
308         *
309         * @param title The action title.
310         * @param listener The action listener.
311         * @return This builder.
312         *
313         * @throws IllegalArgumentException If the title and the listener
314         *     are not both either null or non-null.
315         */
316        public @NonNull Builder setNegativeAction(@Nullable CharSequence title,
317                @Nullable IntentSender listener) {
318            throwIfDestroyed();
319            if (title == null ^ listener == null) {
320                throw new IllegalArgumentException("title and listener"
321                        + " must be both non-null or null");
322            }
323            mNegativeActionTitle = title;
324            mNegativeActionListener = listener;
325            return this;
326        }
327
328        /**
329         * Builds a new {@link SaveInfo} instance.
330         */
331        public SaveInfo build() {
332            throwIfDestroyed();
333            mDestroyed = true;
334            return new SaveInfo(this);
335        }
336
337        private void throwIfDestroyed() {
338            if (mDestroyed) {
339                throw new IllegalStateException("Already called #build()");
340            }
341        }
342
343    }
344
345    /////////////////////////////////////
346    // Object "contract" methods. //
347    /////////////////////////////////////
348    @Override
349    public String toString() {
350        if (!DEBUG) return super.toString();
351
352        return new StringBuilder("SaveInfo: [type=")
353                .append(DebugUtils.flagsToString(SaveInfo.class, "SAVE_DATA_TYPE_", mType))
354                .append(", requiredIds=").append(Arrays.toString(mRequiredIds))
355                .append(", optionalIds=").append(Arrays.toString(mOptionalIds))
356                .append(", description=").append(mDescription)
357                .append("]").toString();
358    }
359
360    /////////////////////////////////////
361    // Parcelable "contract" methods. //
362    /////////////////////////////////////
363
364    @Override
365    public int describeContents() {
366        return 0;
367    }
368
369    @Override
370    public void writeToParcel(Parcel parcel, int flags) {
371        parcel.writeInt(mType);
372        parcel.writeParcelableArray(mRequiredIds, flags);
373        parcel.writeCharSequence(mNegativeActionTitle);
374        parcel.writeParcelable(mNegativeActionListener, flags);
375        parcel.writeParcelableArray(mOptionalIds, flags);
376        parcel.writeCharSequence(mDescription);
377    }
378
379    public static final Parcelable.Creator<SaveInfo> CREATOR = new Parcelable.Creator<SaveInfo>() {
380        @Override
381        public SaveInfo createFromParcel(Parcel parcel) {
382            // Always go through the builder to ensure the data ingested by
383            // the system obeys the contract of the builder to avoid attacks
384            // using specially crafted parcels.
385            final Builder builder = new Builder(parcel.readInt(),
386                    parcel.readParcelableArray(null, AutofillId.class));
387            builder.setNegativeAction(parcel.readCharSequence(), parcel.readParcelable(null));
388            builder.setOptionalIds(parcel.readParcelableArray(null, AutofillId.class));
389            builder.setDescription(parcel.readCharSequence());
390            return builder.build();
391        }
392
393        @Override
394        public SaveInfo[] newArray(int size) {
395            return new SaveInfo[size];
396        }
397    };
398}
399