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