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