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