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