SaveInfo.java revision dc6cccb905d77b1fd954c883e4369e7b401cb457
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.DEBUG; 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_EMAIL_ADDRESS}) 178 @Retention(RetentionPolicy.SOURCE) 179 @interface SaveDataType{} 180 181 /** 182 * Usually {@link AutofillService#onSaveRequest(SaveRequest, SaveCallback)} 183 * is called once the activity finishes. If this flag is set it is called once all saved views 184 * become invisible. 185 */ 186 public static final int FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE = 0x1; 187 188 /** @hide */ 189 @IntDef( 190 flag = true, 191 value = {FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE}) 192 @Retention(RetentionPolicy.SOURCE) 193 @interface SaveInfoFlags{} 194 195 private final @SaveDataType int mType; 196 private final @NegativeButtonStyle int mNegativeButtonStyle; 197 private final IntentSender mNegativeActionListener; 198 private final AutofillId[] mRequiredIds; 199 private final AutofillId[] mOptionalIds; 200 private final CharSequence mDescription; 201 private final int mFlags; 202 203 private SaveInfo(Builder builder) { 204 mType = builder.mType; 205 mNegativeButtonStyle = builder.mNegativeButtonStyle; 206 mNegativeActionListener = builder.mNegativeActionListener; 207 mRequiredIds = builder.mRequiredIds; 208 mOptionalIds = builder.mOptionalIds; 209 mDescription = builder.mDescription; 210 mFlags = builder.mFlags; 211 } 212 213 /** @hide */ 214 public @NegativeButtonStyle int getNegativeActionStyle() { 215 return mNegativeButtonStyle; 216 } 217 218 /** @hide */ 219 public @Nullable IntentSender getNegativeActionListener() { 220 return mNegativeActionListener; 221 } 222 223 /** @hide */ 224 public AutofillId[] getRequiredIds() { 225 return mRequiredIds; 226 } 227 228 /** @hide */ 229 public @Nullable AutofillId[] getOptionalIds() { 230 return mOptionalIds; 231 } 232 233 /** @hide */ 234 public @SaveDataType int getType() { 235 return mType; 236 } 237 238 /** @hide */ 239 public @SaveInfoFlags int getFlags() { 240 return mFlags; 241 } 242 243 /** @hide */ 244 public CharSequence getDescription() { 245 return mDescription; 246 } 247 248 /** 249 * A builder for {@link SaveInfo} objects. 250 */ 251 public static final class Builder { 252 253 private final @SaveDataType int mType; 254 private @NegativeButtonStyle int mNegativeButtonStyle = NEGATIVE_BUTTON_STYLE_CANCEL; 255 private IntentSender mNegativeActionListener; 256 private final AutofillId[] mRequiredIds; 257 private AutofillId[] mOptionalIds; 258 private CharSequence mDescription; 259 private boolean mDestroyed; 260 private int mFlags; 261 262 /** 263 * Creates a new builder. 264 * 265 * @param type the type of information the associated {@link FillResponse} represents, can 266 * be any combination of {@link SaveInfo#SAVE_DATA_TYPE_GENERIC}, 267 * {@link SaveInfo#SAVE_DATA_TYPE_PASSWORD}, 268 * {@link SaveInfo#SAVE_DATA_TYPE_ADDRESS}, {@link SaveInfo#SAVE_DATA_TYPE_CREDIT_CARD}, 269 * {@link SaveInfo#SAVE_DATA_TYPE_USERNAME}, or 270 * {@link SaveInfo#SAVE_DATA_TYPE_EMAIL_ADDRESS}. 271 * @param requiredIds ids of all required views that will trigger a save request. 272 * 273 * <p>See {@link SaveInfo} for more info. 274 * 275 * @throws IllegalArgumentException if {@code requiredIds} is {@code null} or empty. 276 */ 277 public Builder(@SaveDataType int type, @NonNull AutofillId[] requiredIds) { 278 Preconditions.checkArgument(requiredIds != null && requiredIds.length > 0, 279 "must have at least one required id: " + Arrays.toString(requiredIds)); 280 mType = type; 281 mRequiredIds = requiredIds; 282 } 283 284 /** 285 * Set flags changing the save behavior. 286 * 287 * @param flags {@link #FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE} or 0. 288 * @return This builder. 289 */ 290 public @NonNull Builder setFlags(@SaveInfoFlags int flags) { 291 throwIfDestroyed(); 292 293 mFlags = Preconditions.checkFlagsArgument(flags, FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE); 294 return this; 295 } 296 297 /** 298 * Sets the ids of additional, optional views the service would be interested to save. 299 * 300 * <p>See {@link SaveInfo} for more info. 301 * 302 * @param ids The ids of the optional views. 303 * @return This builder. 304 */ 305 public @NonNull Builder setOptionalIds(@Nullable AutofillId[] ids) { 306 throwIfDestroyed(); 307 if (ids != null && ids.length != 0) { 308 mOptionalIds = ids; 309 } 310 return this; 311 } 312 313 /** 314 * Sets an optional description to be shown in the UI when the user is asked to save. 315 * 316 * <p>Typically, it describes how the data will be stored by the service, so it can help 317 * users to decide whether they can trust the service to save their data. 318 * 319 * @param description a succint description. 320 * @return This Builder. 321 */ 322 public @NonNull Builder setDescription(@Nullable CharSequence description) { 323 throwIfDestroyed(); 324 mDescription = description; 325 return this; 326 } 327 328 /** @hide */ 329 // TODO (b/37563972): Remove when callers migrate 330 public @NonNull Builder setNegativeAction(@Nullable CharSequence title, 331 @Nullable IntentSender listener) { 332 throwIfDestroyed(); 333 setNegativeAction(NEGATIVE_BUTTON_STYLE_CANCEL, listener); 334 return this; 335 } 336 337 /** 338 * Sets the style and listener for the negative save action. 339 * 340 * <p>This allows a fill-provider to customize the style and be 341 * notified when the user selects the negative action in the save 342 * UI. Note that selecting the negative action regardless of its style 343 * and listener being customized would dismiss the save UI and if a 344 * custom listener intent is provided then this intent will be 345 * started. The default style is {@link #NEGATIVE_BUTTON_STYLE_CANCEL}</p> 346 * 347 * @param style The action style. 348 * @param listener The action listener. 349 * @return This builder. 350 * 351 * @see #NEGATIVE_BUTTON_STYLE_CANCEL 352 * @see #NEGATIVE_BUTTON_STYLE_REJECT 353 * 354 * @throws IllegalArgumentException If the style is invalid 355 */ 356 public @NonNull Builder setNegativeAction(@NegativeButtonStyle int style, 357 @Nullable IntentSender listener) { 358 throwIfDestroyed(); 359 if (style != NEGATIVE_BUTTON_STYLE_CANCEL 360 && style != NEGATIVE_BUTTON_STYLE_REJECT) { 361 throw new IllegalArgumentException("Invalid style: " + style); 362 } 363 mNegativeButtonStyle = style; 364 mNegativeActionListener = listener; 365 return this; 366 } 367 368 /** 369 * Builds a new {@link SaveInfo} instance. 370 */ 371 public SaveInfo build() { 372 throwIfDestroyed(); 373 mDestroyed = true; 374 return new SaveInfo(this); 375 } 376 377 private void throwIfDestroyed() { 378 if (mDestroyed) { 379 throw new IllegalStateException("Already called #build()"); 380 } 381 } 382 383 } 384 385 ///////////////////////////////////// 386 // Object "contract" methods. // 387 ///////////////////////////////////// 388 @Override 389 public String toString() { 390 if (!DEBUG) return super.toString(); 391 392 return new StringBuilder("SaveInfo: [type=") 393 .append(DebugUtils.flagsToString(SaveInfo.class, "SAVE_DATA_TYPE_", mType)) 394 .append(", requiredIds=").append(Arrays.toString(mRequiredIds)) 395 .append(", optionalIds=").append(Arrays.toString(mOptionalIds)) 396 .append(", description=").append(mDescription) 397 .append(DebugUtils.flagsToString(SaveInfo.class, "NEGATIVE_BUTTON_STYLE_", 398 mNegativeButtonStyle)) 399 .append(", mFlags=").append(mFlags) 400 .append("]").toString(); 401 } 402 403 ///////////////////////////////////// 404 // Parcelable "contract" methods. // 405 ///////////////////////////////////// 406 407 @Override 408 public int describeContents() { 409 return 0; 410 } 411 412 @Override 413 public void writeToParcel(Parcel parcel, int flags) { 414 parcel.writeInt(mType); 415 parcel.writeParcelableArray(mRequiredIds, flags); 416 parcel.writeInt(mNegativeButtonStyle); 417 parcel.writeParcelable(mNegativeActionListener, flags); 418 parcel.writeParcelableArray(mOptionalIds, flags); 419 parcel.writeCharSequence(mDescription); 420 parcel.writeInt(mFlags); 421 } 422 423 public static final Parcelable.Creator<SaveInfo> CREATOR = new Parcelable.Creator<SaveInfo>() { 424 @Override 425 public SaveInfo createFromParcel(Parcel parcel) { 426 // Always go through the builder to ensure the data ingested by 427 // the system obeys the contract of the builder to avoid attacks 428 // using specially crafted parcels. 429 final Builder builder = new Builder(parcel.readInt(), 430 parcel.readParcelableArray(null, AutofillId.class)); 431 builder.setNegativeAction(parcel.readInt(), parcel.readParcelable(null)); 432 builder.setOptionalIds(parcel.readParcelableArray(null, AutofillId.class)); 433 builder.setDescription(parcel.readCharSequence()); 434 builder.setFlags(parcel.readInt()); 435 return builder.build(); 436 } 437 438 @Override 439 public SaveInfo[] newArray(int size) { 440 return new SaveInfo[size]; 441 } 442 }; 443} 444