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