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