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