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