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