SaveInfo.java revision 640f30a7763b0a4b80c767acb84c740aac04768b
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.ArraySet;
29import android.view.autofill.AutoFillId;
30import android.view.autofill.AutofillId;
31
32import java.lang.annotation.Retention;
33import java.lang.annotation.RetentionPolicy;
34import java.util.ArrayList;
35
36/**
37 * Information used to indicate that a service is interested on saving the user-inputed data for
38 * future use.
39 *
40 * <p>A {@link SaveInfo} is always associated with a {@link FillResponse}.
41 *
42 * <p>A {@link SaveInfo} must define the type it represents, and contain at least one
43 * {@code savableId}. A {@code savableId} is the {@link AutofillId} of a view the service is
44 * interested to save in a {@code onSaveRequest()}; the ids of all {@link Dataset} present in the
45 * {@link FillResponse} associated with this {@link SaveInfo} are already marked as savable,
46 * but additional ids can be added through {@link Builder#addSavableIds(AutofillId...)}.
47 *
48 * <p>See {@link AutofillService#onSaveRequest(android.app.assist.AssistStructure, Bundle,
49 * SaveCallback)} and {@link FillResponse} for more info.
50 */
51public final class SaveInfo implements Parcelable {
52
53    /**
54     * Type used on when the service can save the contents of an activity, but cannot describe what
55     * the content is for.
56     */
57    public static final int SAVE_DATA_TYPE_GENERIC = 0;
58
59    /**
60     * Type used when the {@link FillResponse} represents user credentials that have a password.
61     */
62    public static final int SAVE_DATA_TYPE_PASSWORD = 1;
63
64
65    /**
66     * Type used on when the {@link FillResponse} represents a physical address (such as street,
67     * city, state, etc).
68     */
69    public static final int SAVE_DATA_TYPE_ADDRESS = 2;
70
71    /**
72     * Type used when the {@link FillResponse} represents a credit card.
73     */
74    public static final int SAVE_DATA_TYPE_CREDIT_CARD = 3;
75
76    private final @SaveDataType int mType;
77    private CharSequence mNegativeActionTitle;
78    private IntentSender mNegativeActionListener;
79    private ArraySet<AutofillId> mSavableIds;
80    private final CharSequence mDescription;
81
82    /** @hide */
83    @IntDef({
84        SAVE_DATA_TYPE_GENERIC,
85        SAVE_DATA_TYPE_PASSWORD,
86        SAVE_DATA_TYPE_ADDRESS,
87        SAVE_DATA_TYPE_CREDIT_CARD
88    })
89    @Retention(RetentionPolicy.SOURCE)
90    public @interface SaveDataType {
91    }
92
93    private SaveInfo(Builder builder) {
94        mType = builder.mType;
95        mNegativeActionTitle = builder.mNegativeActionTitle;
96        mNegativeActionListener = builder.mNegativeActionListener;
97        mSavableIds = builder.mSavableIds;
98        mDescription = builder.mDescription;
99    }
100
101    /** @hide */
102    public @Nullable CharSequence getNegativeActionTitle() {
103        return mNegativeActionTitle;
104    }
105
106    /** @hide */
107    public @Nullable IntentSender getNegativeActionListener() {
108        return mNegativeActionListener;
109    }
110
111    /** @hide */
112    public @Nullable ArraySet<AutofillId> getSavableIds() {
113        return mSavableIds;
114    }
115
116    /** @hide */
117    public int getType() {
118        return mType;
119    }
120
121    /** @hide */
122    public CharSequence getDescription() {
123        return mDescription;
124    }
125
126    /** @hide */
127    public void addSavableIds(@Nullable ArrayList<Dataset> datasets) {
128        if (datasets != null) {
129            for (Dataset dataset : datasets) {
130                final ArrayList<AutofillId> ids = dataset.getFieldIds();
131                if (ids != null) {
132                    final int fieldCount = ids.size();
133                    for (int i = 0; i < fieldCount; i++) {
134                        final AutofillId id = ids.get(i);
135                        if (mSavableIds == null) {
136                            mSavableIds = new ArraySet<>();
137                        }
138                        mSavableIds.add(id);
139                    }
140                }
141            }
142        }
143    }
144
145    /**
146     * A builder for {@link SaveInfo} objects.
147     */
148    public static final class Builder {
149
150        private final @SaveDataType int mType;
151        private CharSequence mNegativeActionTitle;
152        private IntentSender mNegativeActionListener;
153        private ArraySet<AutofillId> mSavableIds;
154        private CharSequence mDescription;
155        private boolean mDestroyed;
156
157        /**
158         * Creates a new builder.
159         *
160         * @param type the type of information the associated {@link FillResponse} represents. Must
161         * be {@link SaveInfo#SAVE_DATA_TYPE_GENERIC}, {@link SaveInfo#SAVE_DATA_TYPE_PASSWORD},
162         * {@link SaveInfo#SAVE_DATA_TYPE_ADDRESS}, or {@link SaveInfo#SAVE_DATA_TYPE_CREDIT_CARD};
163         * otherwise it will assume {@link SaveInfo#SAVE_DATA_TYPE_GENERIC}.
164         */
165        public Builder(@SaveDataType int type) {
166            switch (type) {
167                case SAVE_DATA_TYPE_PASSWORD:
168                case SAVE_DATA_TYPE_ADDRESS:
169                case SAVE_DATA_TYPE_CREDIT_CARD:
170                    mType = type;
171                    break;
172                default:
173                    mType = SAVE_DATA_TYPE_GENERIC;
174            }
175        }
176
177        /**
178         * Adds ids of additional views the service would be interested to save, but were not
179         * indirectly set through {@link FillResponse.Builder#addDataset(Dataset)}.
180         *
181         * @param ids The savable ids.
182         * @return This builder.
183         *
184         * @see FillResponse
185         */
186        public @NonNull Builder addSavableIds(@Nullable AutofillId... ids) {
187            throwIfDestroyed();
188
189            if (ids == null) {
190                return this;
191            }
192            for (AutofillId id : ids) {
193                if (mSavableIds == null) {
194                    mSavableIds = new ArraySet<>();
195                }
196                mSavableIds.add(id);
197            }
198            return this;
199        }
200
201        /**
202         * Sets an optional description to be shown in the UI when the user is asked to save.
203         *
204         * <p>Typically, it describes how the data will be stored by the service, so it can help
205         * users to decide whether they can trust the service to save their data.
206         *
207         * @param description a succint description.
208         * @return This Builder.
209         */
210        public @NonNull Builder setDescription(@Nullable CharSequence description) {
211            mDescription = description;
212            return this;
213        }
214
215        /**
216         * Sets the title and listener for the negative save action.
217         *
218         * <p>This allows a fill-provider to customize the text and be
219         * notified when the user selects the negative action in the save
220         * UI. Note that selecting the negative action regardless of its text
221         * and listener being customized would dismiss the save UI and if a
222         * custom listener intent is provided then this intent will be
223         * started.</p>
224         *
225         * <p>This customization could be useful for providing additional
226         * semantics to the negative action. For example, a fill-provider
227         * can use this mechanism to add a "Disable" function or a "More info"
228         * function, etc. Note that the save action is exclusively controlled
229         * by the platform to ensure user consent is collected to release
230         * data from the filled app to the fill-provider.</p>
231         *
232         * @param title The action title.
233         * @param listener The action listener.
234         * @return This builder.
235         *
236         * @throws IllegalArgumentException If the title and the listener
237         *     are not both either null or non-null.
238         */
239        public @NonNull Builder setNegativeAction(@Nullable CharSequence title,
240                @Nullable IntentSender listener) {
241            throwIfDestroyed();
242            if (title == null ^ listener == null) {
243                throw new IllegalArgumentException("title and listener"
244                        + " must be both non-null or null");
245            }
246            mNegativeActionTitle = title;
247            mNegativeActionListener = listener;
248            return this;
249        }
250
251        /**
252         * Builds a new {@link SaveInfo} instance.
253         */
254        public SaveInfo build() {
255            throwIfDestroyed();
256            mDestroyed = true;
257            return new SaveInfo(this);
258        }
259
260        private void throwIfDestroyed() {
261            if (mDestroyed) {
262                throw new IllegalStateException("Already called #build()");
263            }
264        }
265
266    }
267
268    /////////////////////////////////////
269    // Object "contract" methods. //
270    /////////////////////////////////////
271    @Override
272    public String toString() {
273        if (!DEBUG) return super.toString();
274
275        return new StringBuilder("SaveInfo: [type=").append(mType)
276                .append(", savableIds=").append(mSavableIds)
277                .append("]").toString();
278    }
279
280    /////////////////////////////////////
281    // Parcelable "contract" methods. //
282    /////////////////////////////////////
283
284    @Override
285    public int describeContents() {
286        return 0;
287    }
288
289    @Override
290    public void writeToParcel(Parcel parcel, int flags) {
291        parcel.writeInt(mType);
292        parcel.writeCharSequence(mNegativeActionTitle);
293        parcel.writeParcelable(mNegativeActionListener, flags);
294        parcel.writeTypedArraySet(mSavableIds, flags);
295        parcel.writeCharSequence(mDescription);
296    }
297
298    public static final Parcelable.Creator<SaveInfo> CREATOR = new Parcelable.Creator<SaveInfo>() {
299        @Override
300        public SaveInfo createFromParcel(Parcel parcel) {
301            // Always go through the builder to ensure the data ingested by
302            // the system obeys the contract of the builder to avoid attacks
303            // using specially crafted parcels.
304            final Builder builder = new Builder(parcel.readInt());
305            builder.setNegativeAction(parcel.readCharSequence(), parcel.readParcelable(null));
306            final ArraySet<AutofillId> savableIds = parcel.readTypedArraySet(null);
307            final int savableIdsCount = (savableIds != null) ? savableIds.size() : 0;
308            for (int i = 0; i < savableIdsCount; i++) {
309                builder.addSavableIds(savableIds.valueAt(i));
310            }
311            builder.setDescription(parcel.readCharSequence());
312            return builder.build();
313        }
314
315        @Override
316        public SaveInfo[] newArray(int size) {
317            return new SaveInfo[size];
318        }
319    };
320}
321