SaveInfo.java revision 2ef19c1d73f89ca4718b5a8f0c2e7221621e844f
1/* 2 * Copyright (C) 2017 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.sDebug; 20 21import android.annotation.IntDef; 22import android.annotation.NonNull; 23import android.annotation.Nullable; 24import android.app.Activity; 25import android.content.IntentSender; 26import android.os.Parcel; 27import android.os.Parcelable; 28import android.util.DebugUtils; 29import android.view.autofill.AutofillId; 30import android.view.autofill.AutofillManager; 31import android.view.autofill.AutofillValue; 32 33import com.android.internal.util.Preconditions; 34 35import java.lang.annotation.Retention; 36import java.lang.annotation.RetentionPolicy; 37import java.util.Arrays; 38 39/** 40 * Information used to indicate that an {@link AutofillService} is interested on saving the 41 * user-inputed data for future use, through a 42 * {@link AutofillService#onSaveRequest(SaveRequest, SaveCallback)} 43 * call. 44 * 45 * <p>A {@link SaveInfo} is always associated with a {@link FillResponse}, and it contains at least 46 * two pieces of information: 47 * 48 * <ol> 49 * <li>The type(s) of user data (like password or credit card info) that would be saved. 50 * <li>The minimum set of views (represented by their {@link AutofillId}) that need to be changed 51 * to trigger a save request. 52 * </ol> 53 * 54 * <p>Typically, the {@link SaveInfo} contains the same {@code id}s as the {@link Dataset}: 55 * 56 * <pre class="prettyprint"> 57 * new FillResponse.Builder() 58 * .addDataset(new Dataset.Builder() 59 * .setValue(id1, AutofillValue.forText("homer"), createPresentation("homer")) // username 60 * .setValue(id2, AutofillValue.forText("D'OH!"), createPresentation("password for homer")) // password 61 * .build()) 62 * .setSaveInfo(new SaveInfo.Builder( 63 * SaveInfo.SAVE_DATA_TYPE_USERNAME | SaveInfo.SAVE_DATA_TYPE_PASSWORD, 64 * new AutofillId[] { id1, id2 }).build()) 65 * .build(); 66 * </pre> 67 * 68 * <p>The save type flags are used to display the appropriate strings in the save UI affordance. 69 * You can pass multiple values, but try to keep it short if possible. In the above example, just 70 * {@code SaveInfo.SAVE_DATA_TYPE_PASSWORD} would be enough. 71 * 72 * <p>There might be cases where the {@link AutofillService} knows how to fill the screen, 73 * but the user has no data for it. In that case, the {@link FillResponse} should contain just the 74 * {@link SaveInfo}, but no {@link Dataset Datasets}: 75 * 76 * <pre class="prettyprint"> 77 * new FillResponse.Builder() 78 * .setSaveInfo(new SaveInfo.Builder(SaveInfo.SAVE_DATA_TYPE_PASSWORD, 79 * new AutofillId[] { id1, id2 }).build()) 80 * .build(); 81 * </pre> 82 * 83 * <p>There might be cases where the user data in the {@link AutofillService} is enough 84 * to populate some fields but not all, and the service would still be interested on saving the 85 * other fields. In that case, the service could set the 86 * {@link SaveInfo.Builder#setOptionalIds(AutofillId[])} as well: 87 * 88 * <pre class="prettyprint"> 89 * new FillResponse.Builder() 90 * .addDataset(new Dataset.Builder() 91 * .setValue(id1, AutofillValue.forText("742 Evergreen Terrace"), 92 * createPresentation("742 Evergreen Terrace")) // street 93 * .setValue(id2, AutofillValue.forText("Springfield"), 94 * createPresentation("Springfield")) // city 95 * .build()) 96 * .setSaveInfo(new SaveInfo.Builder(SaveInfo.SAVE_DATA_TYPE_ADDRESS, 97 * new AutofillId[] { id1, id2 }) // street and city 98 * .setOptionalIds(new AutofillId[] { id3, id4 }) // state and zipcode 99 * .build()) 100 * .build(); 101 * </pre> 102 * 103 * <p>The {@link AutofillService#onSaveRequest(SaveRequest, SaveCallback)} can be triggered after 104 * any of the following events: 105 * <ul> 106 * <li>The {@link Activity} finishes. 107 * <li>The app explicitly called {@link AutofillManager#commit()}. 108 * <li>All required views became invisible (if the {@link SaveInfo} was created with the 109 * {@link #FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE} flag). 110 * </ul> 111 * 112 * <p>But it is only triggered when all conditions below are met: 113 * <ul> 114 * <li>The {@link SaveInfo} associated with the {@link FillResponse} is not {@code null}. 115 * <li>The {@link AutofillValue}s of all required views (as set by the {@code requiredIds} passed 116 * to the {@link SaveInfo.Builder} constructor are not empty. 117 * <li>The {@link AutofillValue} of at least one view (be it required or optional) has changed 118 * (i.e., it's neither the same value passed in a {@link Dataset}, nor the initial value 119 * presented in the view). 120 * <li>The user explicitly tapped the UI affordance asking to save data for autofill. 121 * </ul> 122 * 123 * <p>The service can also customize some aspects of the save UI affordance: 124 * <ul> 125 * <li>Add a subtitle by calling {@link Builder#setDescription(CharSequence)}. 126 * <li>Customize the button used to reject the save request by calling 127 * {@link Builder#setNegativeAction(int, IntentSender)}. 128 * </ul> 129 */ 130public final class SaveInfo implements Parcelable { 131 132 /** 133 * Type used when the service can save the contents of a screen, but cannot describe what 134 * the content is for. 135 */ 136 public static final int SAVE_DATA_TYPE_GENERIC = 0x0; 137 138 /** 139 * Type used when the {@link FillResponse} represents user credentials that have a password. 140 */ 141 public static final int SAVE_DATA_TYPE_PASSWORD = 0x01; 142 143 /** 144 * Type used on when the {@link FillResponse} represents a physical address (such as street, 145 * city, state, etc). 146 */ 147 public static final int SAVE_DATA_TYPE_ADDRESS = 0x02; 148 149 /** 150 * Type used when the {@link FillResponse} represents a credit card. 151 */ 152 public static final int SAVE_DATA_TYPE_CREDIT_CARD = 0x04; 153 154 /** 155 * Type used when the {@link FillResponse} represents just an username, without a password. 156 */ 157 public static final int SAVE_DATA_TYPE_USERNAME = 0x08; 158 159 /** 160 * Type used when the {@link FillResponse} represents just an email address, without a password. 161 */ 162 public static final int SAVE_DATA_TYPE_EMAIL_ADDRESS = 0x10; 163 164 /** 165 * Style for the negative button of the save UI to cancel the 166 * save operation. In this case, the user tapping the negative 167 * button signals that they would prefer to not save the filled 168 * content. 169 */ 170 public static final int NEGATIVE_BUTTON_STYLE_CANCEL = 0; 171 172 /** 173 * Style for the negative button of the save UI to reject the 174 * save operation. This could be useful if the user needs to 175 * opt-in your service and the save prompt is an advertisement 176 * of the potential value you can add to the user. In this 177 * case, the user tapping the negative button sends a strong 178 * signal that the feature may not be useful and you may 179 * consider some backoff strategy. 180 */ 181 public static final int NEGATIVE_BUTTON_STYLE_REJECT = 1; 182 183 /** @hide */ 184 @IntDef( 185 value = { 186 NEGATIVE_BUTTON_STYLE_CANCEL, 187 NEGATIVE_BUTTON_STYLE_REJECT}) 188 @Retention(RetentionPolicy.SOURCE) 189 @interface NegativeButtonStyle{} 190 191 /** @hide */ 192 @IntDef( 193 flag = true, 194 value = { 195 SAVE_DATA_TYPE_GENERIC, 196 SAVE_DATA_TYPE_PASSWORD, 197 SAVE_DATA_TYPE_ADDRESS, 198 SAVE_DATA_TYPE_CREDIT_CARD, 199 SAVE_DATA_TYPE_USERNAME, 200 SAVE_DATA_TYPE_EMAIL_ADDRESS}) 201 @Retention(RetentionPolicy.SOURCE) 202 @interface SaveDataType{} 203 204 /** 205 * Usually {@link AutofillService#onSaveRequest(SaveRequest, SaveCallback)} 206 * is called once the {@link Activity} finishes. If this flag is set it is called once all 207 * saved views become invisible. 208 */ 209 public static final int FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE = 0x1; 210 211 /** @hide */ 212 @IntDef( 213 flag = true, 214 value = {FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE}) 215 @Retention(RetentionPolicy.SOURCE) 216 @interface SaveInfoFlags{} 217 218 private final @SaveDataType int mType; 219 private final @NegativeButtonStyle int mNegativeButtonStyle; 220 private final IntentSender mNegativeActionListener; 221 private final AutofillId[] mRequiredIds; 222 private final AutofillId[] mOptionalIds; 223 private final CharSequence mDescription; 224 private final int mFlags; 225 226 private SaveInfo(Builder builder) { 227 mType = builder.mType; 228 mNegativeButtonStyle = builder.mNegativeButtonStyle; 229 mNegativeActionListener = builder.mNegativeActionListener; 230 mRequiredIds = builder.mRequiredIds; 231 mOptionalIds = builder.mOptionalIds; 232 mDescription = builder.mDescription; 233 mFlags = builder.mFlags; 234 } 235 236 /** @hide */ 237 public @NegativeButtonStyle int getNegativeActionStyle() { 238 return mNegativeButtonStyle; 239 } 240 241 /** @hide */ 242 public @Nullable IntentSender getNegativeActionListener() { 243 return mNegativeActionListener; 244 } 245 246 /** @hide */ 247 public AutofillId[] getRequiredIds() { 248 return mRequiredIds; 249 } 250 251 /** @hide */ 252 public @Nullable AutofillId[] getOptionalIds() { 253 return mOptionalIds; 254 } 255 256 /** @hide */ 257 public @SaveDataType int getType() { 258 return mType; 259 } 260 261 /** @hide */ 262 public @SaveInfoFlags int getFlags() { 263 return mFlags; 264 } 265 266 /** @hide */ 267 public CharSequence getDescription() { 268 return mDescription; 269 } 270 271 /** 272 * A builder for {@link SaveInfo} objects. 273 */ 274 public static final class Builder { 275 276 private final @SaveDataType int mType; 277 private @NegativeButtonStyle int mNegativeButtonStyle = NEGATIVE_BUTTON_STYLE_CANCEL; 278 private IntentSender mNegativeActionListener; 279 private final AutofillId[] mRequiredIds; 280 private AutofillId[] mOptionalIds; 281 private CharSequence mDescription; 282 private boolean mDestroyed; 283 private int mFlags; 284 285 /** 286 * Creates a new builder. 287 * 288 * @param type the type of information the associated {@link FillResponse} represents, can 289 * be any combination of {@link SaveInfo#SAVE_DATA_TYPE_GENERIC}, 290 * {@link SaveInfo#SAVE_DATA_TYPE_PASSWORD}, 291 * {@link SaveInfo#SAVE_DATA_TYPE_ADDRESS}, {@link SaveInfo#SAVE_DATA_TYPE_CREDIT_CARD}, 292 * {@link SaveInfo#SAVE_DATA_TYPE_USERNAME}, or 293 * {@link SaveInfo#SAVE_DATA_TYPE_EMAIL_ADDRESS}. 294 * @param requiredIds ids of all required views that will trigger a save request. 295 * 296 * <p>See {@link SaveInfo} for more info. 297 * 298 * @throws IllegalArgumentException if {@code requiredIds} is {@code null} or empty, or if 299 * it contains any {@code null} entry. 300 */ 301 public Builder(@SaveDataType int type, @NonNull AutofillId[] requiredIds) { 302 // TODO: add CTS unit tests (not integration) to assert the null cases 303 mType = type; 304 mRequiredIds = assertValid(requiredIds); 305 } 306 307 private AutofillId[] assertValid(AutofillId[] ids) { 308 Preconditions.checkArgument(ids != null && ids.length > 0, 309 "must have at least one id: " + Arrays.toString(ids)); 310 for (int i = 0; i < ids.length; i++) { 311 final AutofillId id = ids[i]; 312 Preconditions.checkArgument(id != null, 313 "cannot have null id: " + Arrays.toString(ids)); 314 } 315 return ids; 316 } 317 318 /** 319 * Sets flags changing the save behavior. 320 * 321 * @param flags {@link #FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE} or {@code 0}. 322 * @return This builder. 323 */ 324 public @NonNull Builder setFlags(@SaveInfoFlags int flags) { 325 throwIfDestroyed(); 326 327 mFlags = Preconditions.checkFlagsArgument(flags, FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE); 328 return this; 329 } 330 331 /** 332 * Sets the ids of additional, optional views the service would be interested to save. 333 * 334 * <p>See {@link SaveInfo} for more info. 335 * 336 * @param ids The ids of the optional views. 337 * @return This builder. 338 * 339 * @throws IllegalArgumentException if {@code ids} is {@code null} or empty, or if 340 * it contains any {@code null} entry. 341 */ 342 public @NonNull Builder setOptionalIds(@NonNull AutofillId[] ids) { 343 // TODO: add CTS unit tests (not integration) to assert the null cases 344 throwIfDestroyed(); 345 mOptionalIds = assertValid(ids); 346 return this; 347 } 348 349 /** 350 * Sets an optional description to be shown in the UI when the user is asked to save. 351 * 352 * <p>Typically, it describes how the data will be stored by the service, so it can help 353 * users to decide whether they can trust the service to save their data. 354 * 355 * @param description a succint description. 356 * @return This Builder. 357 */ 358 public @NonNull Builder setDescription(@Nullable CharSequence description) { 359 throwIfDestroyed(); 360 mDescription = description; 361 return this; 362 } 363 364 /** 365 * Sets the style and listener for the negative save action. 366 * 367 * <p>This allows a fill-provider to customize the style and be 368 * notified when the user selects the negative action in the save 369 * UI. Note that selecting the negative action regardless of its style 370 * and listener being customized would dismiss the save UI and if a 371 * custom listener intent is provided then this intent will be 372 * started. The default style is {@link #NEGATIVE_BUTTON_STYLE_CANCEL}</p> 373 * 374 * @param style The action style. 375 * @param listener The action listener. 376 * @return This builder. 377 * 378 * @see #NEGATIVE_BUTTON_STYLE_CANCEL 379 * @see #NEGATIVE_BUTTON_STYLE_REJECT 380 * 381 * @throws IllegalArgumentException If the style is invalid 382 */ 383 public @NonNull Builder setNegativeAction(@NegativeButtonStyle int style, 384 @Nullable IntentSender listener) { 385 throwIfDestroyed(); 386 if (style != NEGATIVE_BUTTON_STYLE_CANCEL 387 && style != NEGATIVE_BUTTON_STYLE_REJECT) { 388 throw new IllegalArgumentException("Invalid style: " + style); 389 } 390 mNegativeButtonStyle = style; 391 mNegativeActionListener = listener; 392 return this; 393 } 394 395 /** 396 * Builds a new {@link SaveInfo} instance. 397 */ 398 public SaveInfo build() { 399 throwIfDestroyed(); 400 mDestroyed = true; 401 return new SaveInfo(this); 402 } 403 404 private void throwIfDestroyed() { 405 if (mDestroyed) { 406 throw new IllegalStateException("Already called #build()"); 407 } 408 } 409 410 } 411 412 ///////////////////////////////////// 413 // Object "contract" methods. // 414 ///////////////////////////////////// 415 @Override 416 public String toString() { 417 if (!sDebug) return super.toString(); 418 419 return new StringBuilder("SaveInfo: [type=") 420 .append(DebugUtils.flagsToString(SaveInfo.class, "SAVE_DATA_TYPE_", mType)) 421 .append(", requiredIds=").append(Arrays.toString(mRequiredIds)) 422 .append(", optionalIds=").append(Arrays.toString(mOptionalIds)) 423 .append(", description=").append(mDescription) 424 .append(DebugUtils.flagsToString(SaveInfo.class, "NEGATIVE_BUTTON_STYLE_", 425 mNegativeButtonStyle)) 426 .append(", mFlags=").append(mFlags) 427 .append("]").toString(); 428 } 429 430 ///////////////////////////////////// 431 // Parcelable "contract" methods. // 432 ///////////////////////////////////// 433 434 @Override 435 public int describeContents() { 436 return 0; 437 } 438 439 @Override 440 public void writeToParcel(Parcel parcel, int flags) { 441 parcel.writeInt(mType); 442 parcel.writeParcelableArray(mRequiredIds, flags); 443 parcel.writeInt(mNegativeButtonStyle); 444 parcel.writeParcelable(mNegativeActionListener, flags); 445 parcel.writeParcelableArray(mOptionalIds, flags); 446 parcel.writeCharSequence(mDescription); 447 parcel.writeInt(mFlags); 448 } 449 450 public static final Parcelable.Creator<SaveInfo> CREATOR = new Parcelable.Creator<SaveInfo>() { 451 @Override 452 public SaveInfo createFromParcel(Parcel parcel) { 453 // Always go through the builder to ensure the data ingested by 454 // the system obeys the contract of the builder to avoid attacks 455 // using specially crafted parcels. 456 final Builder builder = new Builder(parcel.readInt(), 457 parcel.readParcelableArray(null, AutofillId.class)); 458 builder.setNegativeAction(parcel.readInt(), parcel.readParcelable(null)); 459 final AutofillId[] optionalIds = parcel.readParcelableArray(null, AutofillId.class); 460 if (optionalIds != null) { 461 builder.setOptionalIds(optionalIds); 462 } 463 builder.setDescription(parcel.readCharSequence()); 464 builder.setFlags(parcel.readInt()); 465 return builder.build(); 466 } 467 468 @Override 469 public SaveInfo[] newArray(int size) { 470 return new SaveInfo[size]; 471 } 472 }; 473} 474