OSDN Git Service

Use concrete CREATOR instance for parceling lists
[android-x86/frameworks-base.git] / core / java / android / service / autofill / Dataset.java
1 /*
2  * Copyright (C) 2016 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
17 package android.service.autofill;
18
19 import static android.view.autofill.Helper.sDebug;
20
21 import android.annotation.NonNull;
22 import android.annotation.Nullable;
23 import android.content.IntentSender;
24 import android.os.Parcel;
25 import android.os.Parcelable;
26 import android.view.autofill.AutofillId;
27 import android.view.autofill.AutofillValue;
28 import android.widget.RemoteViews;
29
30 import com.android.internal.util.Preconditions;
31
32 import java.util.ArrayList;
33
34 /**
35  * A dataset object represents a group of key/value pairs used to autofill parts of a screen.
36  *
37  * <p>In its simplest form, a dataset contains one or more key / value pairs (comprised of
38  * {@link AutofillId} and {@link AutofillValue} respectively); and one or more
39  * {@link RemoteViews presentation} for these pairs (a pair could have its own
40  * {@link RemoteViews presentation}, or use the default {@link RemoteViews presentation} associated
41  * with the whole dataset). When an autofill service returns datasets in a {@link FillResponse}
42  * and the screen input is focused in a view that is present in at least one of these datasets,
43  * the Android System displays a UI affordance containing the {@link RemoteViews presentation} of
44  * all datasets pairs that have that view's {@link AutofillId}. Then, when the user selects a
45  * dataset from the affordance, all views in that dataset are autofilled.
46  *
47  * <p>In a more sophisticated form, the dataset value can be protected until the user authenticates
48  * the dataset - see {@link Dataset.Builder#setAuthentication(IntentSender)}.
49  *
50  * @see android.service.autofill.AutofillService for more information and examples about the
51  * role of datasets in the autofill workflow.
52  */
53 public final class Dataset implements Parcelable {
54
55     private final ArrayList<AutofillId> mFieldIds;
56     private final ArrayList<AutofillValue> mFieldValues;
57     private final ArrayList<RemoteViews> mFieldPresentations;
58     private final RemoteViews mPresentation;
59     private final IntentSender mAuthentication;
60     @Nullable String mId;
61
62     private Dataset(Builder builder) {
63         mFieldIds = builder.mFieldIds;
64         mFieldValues = builder.mFieldValues;
65         mFieldPresentations = builder.mFieldPresentations;
66         mPresentation = builder.mPresentation;
67         mAuthentication = builder.mAuthentication;
68         mId = builder.mId;
69     }
70
71     /** @hide */
72     public @Nullable ArrayList<AutofillId> getFieldIds() {
73         return mFieldIds;
74     }
75
76     /** @hide */
77     public @Nullable ArrayList<AutofillValue> getFieldValues() {
78         return mFieldValues;
79     }
80
81     /** @hide */
82     public RemoteViews getFieldPresentation(int index) {
83         final RemoteViews customPresentation = mFieldPresentations.get(index);
84         return customPresentation != null ? customPresentation : mPresentation;
85     }
86
87     /** @hide */
88     public @Nullable IntentSender getAuthentication() {
89         return mAuthentication;
90     }
91
92     /** @hide */
93     public boolean isEmpty() {
94         return mFieldIds == null || mFieldIds.isEmpty();
95     }
96
97     @Override
98     public String toString() {
99         if (!sDebug) return super.toString();
100
101         return new StringBuilder("Dataset " + mId + " [")
102                 .append("fieldIds=").append(mFieldIds)
103                 .append(", fieldValues=").append(mFieldValues)
104                 .append(", fieldPresentations=")
105                 .append(mFieldPresentations == null ? 0 : mFieldPresentations.size())
106                 .append(", hasPresentation=").append(mPresentation != null)
107                 .append(", hasAuthentication=").append(mAuthentication != null)
108                 .append(']').toString();
109     }
110
111     /**
112      * Gets the id of this dataset.
113      *
114      * @return The id of this dataset or {@code null} if not set
115      *
116      * @hide
117      */
118     public String getId() {
119         return mId;
120     }
121
122     /**
123      * A builder for {@link Dataset} objects. You must provide at least
124      * one value for a field or set an authentication intent.
125      */
126     public static final class Builder {
127         private ArrayList<AutofillId> mFieldIds;
128         private ArrayList<AutofillValue> mFieldValues;
129         private ArrayList<RemoteViews> mFieldPresentations;
130         private RemoteViews mPresentation;
131         private IntentSender mAuthentication;
132         private boolean mDestroyed;
133         @Nullable private String mId;
134
135         /**
136          * Creates a new builder.
137          *
138          * @param presentation The presentation used to visualize this dataset.
139          */
140         public Builder(@NonNull RemoteViews presentation) {
141             Preconditions.checkNotNull(presentation, "presentation must be non-null");
142             mPresentation = presentation;
143         }
144
145         /**
146          * Creates a new builder for a dataset where each field will be visualized independently.
147          *
148          * <p>When using this constructor, fields must be set through
149          * {@link #setValue(AutofillId, AutofillValue, RemoteViews)}.
150          */
151         public Builder() {
152         }
153
154         /**
155          * Requires a dataset authentication before autofilling the activity with this dataset.
156          *
157          * <p>This method is called when you need to provide an authentication
158          * UI for the data set. For example, when a data set contains credit card information
159          * (such as number, expiration date, and verification code), you can display UI
160          * asking for the verification code before filing in the data. Even if the
161          * data set is completely populated the system will launch the specified authentication
162          * intent and will need your approval to fill it in. Since the data set is "locked"
163          * until the user authenticates it, typically this data set name is masked
164          * (for example, "VISA....1234"). Typically you would want to store the data set
165          * labels non-encrypted and the actual sensitive data encrypted and not in memory.
166          * This allows showing the labels in the UI while involving the user if one of
167          * the items with these labels is chosen. Note that if you use sensitive data as
168          * a label, for example an email address, then it should also be encrypted.</p>
169          *
170          * <p>When a user triggers autofill, the system launches the provided intent
171          * whose extras will have the {@link
172          * android.view.autofill.AutofillManager#EXTRA_ASSIST_STRUCTURE screen content},
173          * and your {@link android.view.autofill.AutofillManager#EXTRA_CLIENT_STATE client
174          * state}. Once you complete your authentication flow you should set the activity
175          * result to {@link android.app.Activity#RESULT_OK} and provide the fully populated
176          * {@link Dataset dataset} or a fully-populated {@link FillResponse response} by
177          * setting it to the {@link
178          * android.view.autofill.AutofillManager#EXTRA_AUTHENTICATION_RESULT} extra. If you
179          * provide a dataset in the result, it will replace the authenticated dataset and
180          * will be immediately filled in. If you provide a response, it will replace the
181          * current response and the UI will be refreshed. For example, if you provided
182          * credit card information without the CVV for the data set in the {@link FillResponse
183          * response} then the returned data set should contain the CVV entry.
184          *
185          * <p><b>NOTE:</b> Do not make the provided pending intent
186          * immutable by using {@link android.app.PendingIntent#FLAG_IMMUTABLE} as the
187          * platform needs to fill in the authentication arguments.
188          *
189          * @param authentication Intent to an activity with your authentication flow.
190          * @return This builder.
191          *
192          * @see android.app.PendingIntent
193          */
194         public @NonNull Builder setAuthentication(@Nullable IntentSender authentication) {
195             throwIfDestroyed();
196             mAuthentication = authentication;
197             return this;
198         }
199
200         /**
201          * Sets the id for the dataset so its usage history can be retrieved later.
202          *
203          * <p>The id of the last selected dataset can be read from
204          * {@link AutofillService#getFillEventHistory()}. If the id is not set it will not be clear
205          * if a dataset was selected as {@link AutofillService#getFillEventHistory()} uses
206          * {@code null} to indicate that no dataset was selected.
207          *
208          * @param id id for this dataset or {@code null} to unset.
209
210          * @return This builder.
211          */
212         public @NonNull Builder setId(@Nullable String id) {
213             throwIfDestroyed();
214
215             mId = id;
216             return this;
217         }
218
219         /**
220          * Sets the value of a field.
221          *
222          * @param id id returned by {@link
223          *         android.app.assist.AssistStructure.ViewNode#getAutofillId()}.
224          * @param value value to be autofilled. Pass {@code null} if you do not have the value
225          *        but the target view is a logical part of the dataset. For example, if
226          *        the dataset needs an authentication and you have no access to the value.
227          * @return This builder.
228          * @throws IllegalStateException if the builder was constructed without a
229          * {@link RemoteViews presentation}.
230          */
231         public @NonNull Builder setValue(@NonNull AutofillId id, @Nullable AutofillValue value) {
232             throwIfDestroyed();
233             if (mPresentation == null) {
234                 throw new IllegalStateException("Dataset presentation not set on constructor");
235             }
236             setValueAndPresentation(id, value, null);
237             return this;
238         }
239
240         /**
241          * Sets the value of a field, using a custom {@link RemoteViews presentation} to
242          * visualize it.
243          *
244          * @param id id returned by {@link
245          *         android.app.assist.AssistStructure.ViewNode#getAutofillId()}.
246          * @param value value to be auto filled. Pass {@code null} if you do not have the value
247          *        but the target view is a logical part of the dataset. For example, if
248          *        the dataset needs an authentication and you have no access to the value.
249          *        Filtering matches any user typed string to {@code null} values.
250          * @param presentation The presentation used to visualize this field.
251          * @return This builder.
252          */
253         public @NonNull Builder setValue(@NonNull AutofillId id, @Nullable AutofillValue value,
254                 @NonNull RemoteViews presentation) {
255             throwIfDestroyed();
256             Preconditions.checkNotNull(presentation, "presentation cannot be null");
257             setValueAndPresentation(id, value, presentation);
258             return this;
259         }
260
261         private void setValueAndPresentation(AutofillId id, AutofillValue value,
262                 RemoteViews presentation) {
263             Preconditions.checkNotNull(id, "id cannot be null");
264             if (mFieldIds != null) {
265                 final int existingIdx = mFieldIds.indexOf(id);
266                 if (existingIdx >= 0) {
267                     mFieldValues.set(existingIdx, value);
268                     mFieldPresentations.set(existingIdx, presentation);
269                     return;
270                 }
271             } else {
272                 mFieldIds = new ArrayList<>();
273                 mFieldValues = new ArrayList<>();
274                 mFieldPresentations = new ArrayList<>();
275             }
276             mFieldIds.add(id);
277             mFieldValues.add(value);
278             mFieldPresentations.add(presentation);
279         }
280
281         /**
282          * Creates a new {@link Dataset} instance.
283          *
284          * <p>You should not interact with this builder once this method is called.
285          *
286          * <p>It is required that you specify at least one field before calling this method. It's
287          * also mandatory to provide a presentation view to visualize the data set in the UI.
288          *
289          * @return The built dataset.
290          */
291         public @NonNull Dataset build() {
292             throwIfDestroyed();
293             mDestroyed = true;
294             if (mFieldIds == null) {
295                 throw new IllegalArgumentException("at least one value must be set");
296             }
297             return new Dataset(this);
298         }
299
300         private void throwIfDestroyed() {
301             if (mDestroyed) {
302                 throw new IllegalStateException("Already called #build()");
303             }
304         }
305     }
306
307     /////////////////////////////////////
308     //  Parcelable "contract" methods. //
309     /////////////////////////////////////
310
311     @Override
312     public int describeContents() {
313         return 0;
314     }
315
316     @Override
317     public void writeToParcel(Parcel parcel, int flags) {
318         parcel.writeParcelable(mPresentation, flags);
319         parcel.writeTypedList(mFieldIds, flags);
320         parcel.writeTypedList(mFieldValues, flags);
321         parcel.writeParcelableList(mFieldPresentations, flags);
322         parcel.writeParcelable(mAuthentication, flags);
323         parcel.writeString(mId);
324     }
325
326     public static final Creator<Dataset> CREATOR = new Creator<Dataset>() {
327         @Override
328         public Dataset createFromParcel(Parcel parcel) {
329             // Always go through the builder to ensure the data ingested by
330             // the system obeys the contract of the builder to avoid attacks
331             // using specially crafted parcels.
332             final RemoteViews presentation = parcel.readParcelable(null);
333             final Builder builder = (presentation == null)
334                     ? new Builder()
335                     : new Builder(presentation);
336             final ArrayList<AutofillId> ids = parcel.createTypedArrayList(AutofillId.CREATOR);
337             final ArrayList<AutofillValue> values =
338                     parcel.createTypedArrayList(AutofillValue.CREATOR);
339             final ArrayList<RemoteViews> presentations = new ArrayList<>();
340             parcel.readParcelableList(presentations, null);
341             final int idCount = (ids != null) ? ids.size() : 0;
342             final int valueCount = (values != null) ? values.size() : 0;
343             for (int i = 0; i < idCount; i++) {
344                 final AutofillId id = ids.get(i);
345                 final AutofillValue value = (valueCount > i) ? values.get(i) : null;
346                 final RemoteViews fieldPresentation = presentations.isEmpty() ? null
347                         : presentations.get(i);
348                 builder.setValueAndPresentation(id, value, fieldPresentation);
349             }
350             builder.setAuthentication(parcel.readParcelable(null));
351             builder.setId(parcel.readString());
352             return builder.build();
353         }
354
355         @Override
356         public Dataset[] newArray(int size) {
357             return new Dataset[size];
358         }
359     };
360 }