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