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