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