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