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