FillResponse.java revision 782043caf81055aa1c331e9cc15b24a10e1bf17a
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 */ 16package android.service.autofill; 17 18import android.annotation.NonNull; 19import android.annotation.Nullable; 20import android.content.IntentSender; 21import android.os.Bundle; 22import android.os.Parcel; 23import android.os.Parcelable; 24import android.util.ArraySet; 25import android.view.autofill.AutoFillId; 26import android.view.autofill.AutoFillManager; 27 28/** 29 * Response for a {@link 30 * AutoFillService#onFillRequest(android.app.assist.AssistStructure, 31 * Bundle, android.os.CancellationSignal, FillCallback)} and 32 * authentication requests. 33 * 34 * <p>The response typically contains one or more {@link Dataset}s, each representing a set of 35 * fields that can be auto-filled together, and the Android system displays a dataset picker UI 36 * affordance that the user must use before the {@link android.app.Activity} is filled with 37 * the dataset. 38 * 39 * <p>For example, for a login page with username/password where the user only has one account in 40 * the response could be: 41 * 42 * <pre class="prettyprint"> 43 * new FillResponse.Builder() 44 * .add(new Dataset.Builder("homer") 45 * .setTextFieldValue(id1, "homer") 46 * .setTextFieldValue(id2, "D'OH!") 47 * .build()) 48 * .build(); 49 * </pre> 50 * 51 * <p>If the user had 2 accounts, each with its own user-provided names, the response could be: 52 * 53 * <pre class="prettyprint"> 54 * new FillResponse.Builder() 55 * .add(new Dataset.Builder("Homer's Account") 56 * .setTextFieldValue(id1, "homer") 57 * .setTextFieldValue(id2, "D'OH!") 58 * .build()) 59 * .add(new Dataset.Builder("Bart's Account") 60 * .setTextFieldValue(id1, "elbarto") 61 * .setTextFieldValue(id2, "cowabonga") 62 * .build()) 63 * .build(); 64 * </pre> 65 * 66 * <p>If the user does not have any data associated with this {@link android.app.Activity} but 67 * the service wants to offer the user the option to save the data that was entered, then the 68 * service could populate the response with {@code savableIds} instead of {@link Dataset}s: 69 * 70 * <pre class="prettyprint"> 71 * new FillResponse.Builder() 72 * .addSavableFields(id1, id2) 73 * .build(); 74 * </pre> 75 * 76 * <p>Similarly, there might be cases where the user data on the service is enough to populate some 77 * fields but not all, and the service would still be interested on saving the other fields. In this 78 * scenario, the service could populate the response with both {@link Dataset}s and {@code 79 * savableIds}: 80 * 81 * <pre class="prettyprint"> 82 * new FillResponse.Builder() 83 * .add(new Dataset.Builder("Homer") 84 * .setTextFieldValue(id1, "Homer") // first name 85 * .setTextFieldValue(id2, "Simpson") // last name 86 * .setTextFieldValue(id3, "742 Evergreen Terrace") // street 87 * .setTextFieldValue(id4, "Springfield") // city 88 * .build()) 89 * .addSavableFields(id5, id6) // state and zipcode 90 * .build(); 91 * 92 * </pre> 93 * 94 * <p>Notice that the ids that are part of a dataset (ids 1 to 4, in this example) are automatically 95 * added to the {@code savableIds} list. 96 * 97 * <p>If the service has multiple {@link Dataset}s for different sections of the activity, 98 * for example, a user section for which there are two datasets followed by an address 99 * section for which there are two datasets for each user user, then it should "partition" 100 * the activity in sections and populate the response with just a subset of the data that would 101 * fulfill the first section (the name in our example); then once the user fills the first 102 * section and taps a field from the next section (the address in our example), the Android 103 * system would issue another request for that section, and so on. Note that if the user 104 * chooses to populate the first section with a service provided dataset, the subsequent request 105 * would contain the populated values so you don't try to provide suggestions for the first 106 * section but ony for the second one based on the context of what was already filled. For 107 * example, the first response could be: 108 * 109 * <pre class="prettyprint"> 110 * new FillResponse.Builder() 111 * .add(new Dataset.Builder("Homer") 112 * .setTextFieldValue(id1, "Homer") 113 * .setTextFieldValue(id2, "Simpson") 114 * .build()) 115 * .add(new Dataset.Builder("Bart") 116 * .setTextFieldValue(id1, "Bart") 117 * .setTextFieldValue(id2, "Simpson") 118 * .build()) 119 * .build(); 120 * </pre> 121 * 122 * <p>Then after the user picks the {@code Homer} dataset and taps the {@code Street} field to 123 * trigger another auto-fill request, the second response could be: 124 * 125 * <pre class="prettyprint"> 126 * new FillResponse.Builder() 127 * .add(new Dataset.Builder("Home") 128 * .setTextFieldValue(id3, "742 Evergreen Terrace") 129 * .setTextFieldValue(id4, "Springfield") 130 * .build()) 131 * .add(new Dataset.Builder("Work") 132 * .setTextFieldValue(id3, "Springfield Power Plant") 133 * .setTextFieldValue(id4, "Springfield") 134 * .build()) 135 * .build(); 136 * </pre> 137 * 138 * <p>The service could require user authentication at the {@link FillResponse} or the 139 * {@link Dataset} level, prior to auto-filling an activity - see {@link FillResponse.Builder 140 * #setAuthentication(IntentSender)} and {@link Dataset.Builder#setAuthentication(IntentSender)}. 141 * It is recommended that you encrypt only the sensitive data but leave the labels unencrypted 142 * which would allow you to provide the dataset names to the user and if they choose one 143 * them challenge the user to onAuthenticate. For example, if the user has a home and a work 144 * address the Home and Work labels could be stored unencrypted as they don't have any sensitive 145 * data while the address data is in an encrypted storage. If the user chooses Home, then the 146 * platform will start your authentication flow. If you encrypt all data and require auth 147 * at the response level the user will have to interact with the fill UI to trigger a request 148 * for the datasets as they don't see Home and Work options which will trigger your auth 149 * flow and after successfully authenticating the user will be presented with the Home and 150 * Work options where they can pick one. Hence, you have flexibility how to implement your 151 * auth while storing labels non-encrypted and data encrypted provides a better user 152 * experience.</p> 153 */ 154public final class FillResponse implements Parcelable { 155 private static final boolean DEBUG = false; 156 157 private final ArraySet<Dataset> mDatasets; 158 private final ArraySet<AutoFillId> mSavableIds; 159 private final Bundle mExtras; 160 private final IntentSender mAuthentication; 161 162 private FillResponse(@NonNull Builder builder) { 163 mDatasets = builder.mDatasets; 164 mSavableIds = builder.mSavableIds; 165 mExtras = builder.mExtras; 166 mAuthentication = builder.mAuthentication; 167 } 168 169 /** @hide */ 170 public @Nullable Bundle getExtras() { 171 return mExtras; 172 } 173 174 /** @hide */ 175 public @Nullable ArraySet<Dataset> getDatasets() { 176 return mDatasets; 177 } 178 179 /** @hide */ 180 public @Nullable ArraySet<AutoFillId> getSavableIds() { 181 return mSavableIds; 182 } 183 184 /** @hide */ 185 public @Nullable IntentSender getAuthentication() { 186 return mAuthentication; 187 } 188 189 /** 190 * Builder for {@link FillResponse} objects. You must to provide at least 191 * one dataset or set an authentication intent. 192 */ 193 public static final class Builder { 194 private ArraySet<Dataset> mDatasets; 195 private ArraySet<AutoFillId> mSavableIds; 196 private Bundle mExtras; 197 private IntentSender mAuthentication; 198 private boolean mDestroyed; 199 200 /** 201 * Creates a new {@link FillResponse} builder. 202 */ 203 public Builder() { 204 205 } 206 207 /** 208 * Requires a fill response authentication before auto-filling the activity with 209 * any data set in this response. 210 * 211 * <p>This is typically useful when a user interaction is required to unlock their 212 * data vault if you encrypt the data set labels and data set data. It is recommended 213 * to encrypt only the sensitive data and not the data set labels which would allow 214 * auth on the data set level leading to a better user experience. Note that if you 215 * use sensitive data as a label, for example an email address, then it should also 216 * be encrypted. The provided {@link android.app.PendingIntent intent} must be an 217 * activity which implements your authentication flow.</p> 218 * 219 * <p>When a user triggers auto-fill, the system launches the provided intent 220 * whose extras will have the {@link 221 * AutoFillManager#EXTRA_ASSIST_STRUCTURE screen 222 * content}. Once you complete your authentication flow you should set the activity 223 * result to {@link android.app.Activity#RESULT_OK} and provide the fully populated {@link 224 * FillResponse response} by setting it to the {@link 225 * AutoFillManager#EXTRA_AUTHENTICATION_RESULT} extra. 226 * For example, if you provided an empty {@link FillResponse resppnse} because the 227 * user's data was locked and marked that the response needs an authentication then 228 * in the response returned if authentication succeeds you need to provide all 229 * available data sets some of which may need to be further authenticated, for 230 * example a credit card whose CVV needs to be entered.</p> 231 * 232 * <p></><strong>Note:</strong> Do not make the provided pending intent 233 * immutable by using {@link android.app.PendingIntent#FLAG_IMMUTABLE} as the 234 * platform needs to fill in the authentication arguments.</p> 235 * 236 * @param authentication Intent to an activity with your authentication flow. 237 * 238 * @see android.app.PendingIntent#getIntentSender() 239 */ 240 public @NonNull Builder setAuthentication(@Nullable IntentSender authentication) { 241 throwIfDestroyed(); 242 mAuthentication = authentication; 243 return this; 244 } 245 246 /** 247 * Adds a new {@link Dataset} to this response. Adding a dataset with the 248 * same id updates the existing one. 249 * 250 * @throws IllegalArgumentException if a dataset with same {@code name} already exists. 251 */ 252 public@NonNull Builder addDataset(@Nullable Dataset dataset) { 253 throwIfDestroyed(); 254 if (dataset == null) { 255 return this; 256 } 257 if (mDatasets == null) { 258 mDatasets = new ArraySet<>(); 259 } 260 final int datasetCount = mDatasets.size(); 261 for (int i = 0; i < datasetCount; i++) { 262 if (mDatasets.valueAt(i).getName().equals(dataset.getName())) { 263 throw new IllegalArgumentException("Duplicate dataset name: " 264 + dataset.getName()); 265 } 266 } 267 if (!mDatasets.add(dataset)) { 268 return this; 269 } 270 final int fieldCount = dataset.getFieldIds().size(); 271 for (int i = 0; i < fieldCount; i++) { 272 final AutoFillId id = dataset.getFieldIds().get(i); 273 if (mSavableIds == null) { 274 mSavableIds = new ArraySet<>(); 275 } 276 mSavableIds.add(id); 277 } 278 return this; 279 } 280 281 /** 282 * Adds ids of additional fields that the service would be interested to save (through 283 * {@link AutoFillService#onSaveRequest( 284 * android.app.assist.AssistStructure, Bundle, SaveCallback)}) 285 * but were not indirectly set through {@link #addDataset(Dataset)}. 286 * 287 * <p>See {@link FillResponse} for examples. 288 */ 289 public @NonNull Builder addSavableFields(@Nullable AutoFillId... ids) { 290 throwIfDestroyed(); 291 if (ids == null) { 292 return this; 293 } 294 for (AutoFillId id : ids) { 295 if (mSavableIds == null) { 296 mSavableIds = new ArraySet<>(); 297 } 298 mSavableIds.add(id); 299 } 300 return this; 301 } 302 303 /** 304 * Sets a {@link Bundle} that will be passed to subsequent APIs that 305 * manipulate this response. For example, they are passed to subsequent 306 * calls to {@link AutoFillService#onFillRequest( 307 * android.app.assist.AssistStructure, Bundle, android.os.CancellationSignal, 308 * FillCallback)} and {@link 309 * AutoFillService#onSaveRequest( 310 * android.app.assist.AssistStructure, Bundle, 311 * SaveCallback)}. 312 */ 313 public Builder setExtras(Bundle extras) { 314 throwIfDestroyed(); 315 mExtras = extras; 316 return this; 317 } 318 319 /** 320 * Builds a new {@link FillResponse} instance. 321 */ 322 public FillResponse build() { 323 throwIfDestroyed(); 324 mDestroyed = true; 325 return new FillResponse(this); 326 } 327 328 private void throwIfDestroyed() { 329 if (mDestroyed) { 330 throw new IllegalStateException("Already called #build()"); 331 } 332 } 333 } 334 335 ///////////////////////////////////// 336 // Object "contract" methods. // 337 ///////////////////////////////////// 338 @Override 339 public String toString() { 340 if (!DEBUG) return super.toString(); 341 final StringBuilder builder = new StringBuilder( 342 "FillResponse: [datasets=").append(mDatasets) 343 .append(", savableIds=").append(mSavableIds) 344 .append(", hasExtras=").append(mExtras != null) 345 .append(", hasAuthentication=").append(mAuthentication != null); 346 return builder.append(']').toString(); 347 } 348 349 ///////////////////////////////////// 350 // Parcelable "contract" methods. // 351 ///////////////////////////////////// 352 353 @Override 354 public int describeContents() { 355 return 0; 356 } 357 358 @Override 359 public void writeToParcel(Parcel parcel, int flags) { 360 parcel.writeTypedArraySet(mDatasets, 0); 361 parcel.writeTypedArraySet(mSavableIds, 0); 362 parcel.writeParcelable(mExtras, 0); 363 parcel.writeParcelable(mAuthentication, 0); 364 } 365 366 public static final Parcelable.Creator<FillResponse> CREATOR = 367 new Parcelable.Creator<FillResponse>() { 368 @Override 369 public FillResponse createFromParcel(Parcel parcel) { 370 // Always go through the builder to ensure the data ingested by 371 // the system obeys the contract of the builder to avoid attacks 372 // using specially crafted parcels. 373 final Builder builder = new Builder(); 374 final ArraySet<Dataset> datasets = parcel.readTypedArraySet(null); 375 final int datasetCount = (datasets != null) ? datasets.size() : 0; 376 for (int i = 0; i < datasetCount; i++) { 377 builder.addDataset(datasets.valueAt(i)); 378 } 379 final ArraySet<AutoFillId> fillIds = parcel.readTypedArraySet(null); 380 final int fillIdCount = (fillIds != null) ? fillIds.size() : 0; 381 for (int i = 0; i < fillIdCount; i++) { 382 builder.addSavableFields(fillIds.valueAt(i)); 383 } 384 builder.setExtras(parcel.readParcelable(null)); 385 builder.setAuthentication(parcel.readParcelable(null)); 386 return builder.build(); 387 } 388 389 @Override 390 public FillResponse[] newArray(int size) { 391 return new FillResponse[size]; 392 } 393 }; 394} 395