SaveInfo.java revision 73fedacfebcccd54f2c36ff7c8eb9929e8ce8455
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. 277 */ 278 public Builder(@SaveDataType int type, @NonNull AutofillId[] requiredIds) { 279 Preconditions.checkArgument(requiredIds != null && requiredIds.length > 0, 280 "must have at least one required id: " + Arrays.toString(requiredIds)); 281 mType = type; 282 mRequiredIds = requiredIds; 283 } 284 285 /** 286 * Set flags changing the save behavior. 287 * 288 * @param flags {@link #FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE} or 0. 289 * @return This builder. 290 */ 291 public @NonNull Builder setFlags(@SaveInfoFlags int flags) { 292 throwIfDestroyed(); 293 294 mFlags = Preconditions.checkFlagsArgument(flags, FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE); 295 return this; 296 } 297 298 /** 299 * Sets the ids of additional, optional views the service would be interested to save. 300 * 301 * <p>See {@link SaveInfo} for more info. 302 * 303 * @param ids The ids of the optional views. 304 * @return This builder. 305 */ 306 public @NonNull Builder setOptionalIds(@Nullable AutofillId[] ids) { 307 throwIfDestroyed(); 308 if (ids != null && ids.length != 0) { 309 mOptionalIds = ids; 310 } 311 return this; 312 } 313 314 /** 315 * Sets an optional description to be shown in the UI when the user is asked to save. 316 * 317 * <p>Typically, it describes how the data will be stored by the service, so it can help 318 * users to decide whether they can trust the service to save their data. 319 * 320 * @param description a succint description. 321 * @return This Builder. 322 */ 323 public @NonNull Builder setDescription(@Nullable CharSequence description) { 324 throwIfDestroyed(); 325 mDescription = description; 326 return this; 327 } 328 329 /** 330 * Sets the style and listener for the negative save action. 331 * 332 * <p>This allows a fill-provider to customize the style and be 333 * notified when the user selects the negative action in the save 334 * UI. Note that selecting the negative action regardless of its style 335 * and listener being customized would dismiss the save UI and if a 336 * custom listener intent is provided then this intent will be 337 * started. The default style is {@link #NEGATIVE_BUTTON_STYLE_CANCEL}</p> 338 * 339 * @param style The action style. 340 * @param listener The action listener. 341 * @return This builder. 342 * 343 * @see #NEGATIVE_BUTTON_STYLE_CANCEL 344 * @see #NEGATIVE_BUTTON_STYLE_REJECT 345 * 346 * @throws IllegalArgumentException If the style is invalid 347 */ 348 public @NonNull Builder setNegativeAction(@NegativeButtonStyle int style, 349 @Nullable IntentSender listener) { 350 throwIfDestroyed(); 351 if (style != NEGATIVE_BUTTON_STYLE_CANCEL 352 && style != NEGATIVE_BUTTON_STYLE_REJECT) { 353 throw new IllegalArgumentException("Invalid style: " + style); 354 } 355 mNegativeButtonStyle = style; 356 mNegativeActionListener = listener; 357 return this; 358 } 359 360 /** 361 * Builds a new {@link SaveInfo} instance. 362 */ 363 public SaveInfo build() { 364 throwIfDestroyed(); 365 mDestroyed = true; 366 return new SaveInfo(this); 367 } 368 369 private void throwIfDestroyed() { 370 if (mDestroyed) { 371 throw new IllegalStateException("Already called #build()"); 372 } 373 } 374 375 } 376 377 ///////////////////////////////////// 378 // Object "contract" methods. // 379 ///////////////////////////////////// 380 @Override 381 public String toString() { 382 if (!sDebug) return super.toString(); 383 384 return new StringBuilder("SaveInfo: [type=") 385 .append(DebugUtils.flagsToString(SaveInfo.class, "SAVE_DATA_TYPE_", mType)) 386 .append(", requiredIds=").append(Arrays.toString(mRequiredIds)) 387 .append(", optionalIds=").append(Arrays.toString(mOptionalIds)) 388 .append(", description=").append(mDescription) 389 .append(DebugUtils.flagsToString(SaveInfo.class, "NEGATIVE_BUTTON_STYLE_", 390 mNegativeButtonStyle)) 391 .append(", mFlags=").append(mFlags) 392 .append("]").toString(); 393 } 394 395 ///////////////////////////////////// 396 // Parcelable "contract" methods. // 397 ///////////////////////////////////// 398 399 @Override 400 public int describeContents() { 401 return 0; 402 } 403 404 @Override 405 public void writeToParcel(Parcel parcel, int flags) { 406 parcel.writeInt(mType); 407 parcel.writeParcelableArray(mRequiredIds, flags); 408 parcel.writeInt(mNegativeButtonStyle); 409 parcel.writeParcelable(mNegativeActionListener, flags); 410 parcel.writeParcelableArray(mOptionalIds, flags); 411 parcel.writeCharSequence(mDescription); 412 parcel.writeInt(mFlags); 413 } 414 415 public static final Parcelable.Creator<SaveInfo> CREATOR = new Parcelable.Creator<SaveInfo>() { 416 @Override 417 public SaveInfo createFromParcel(Parcel parcel) { 418 // Always go through the builder to ensure the data ingested by 419 // the system obeys the contract of the builder to avoid attacks 420 // using specially crafted parcels. 421 final Builder builder = new Builder(parcel.readInt(), 422 parcel.readParcelableArray(null, AutofillId.class)); 423 builder.setNegativeAction(parcel.readInt(), parcel.readParcelable(null)); 424 builder.setOptionalIds(parcel.readParcelableArray(null, AutofillId.class)); 425 builder.setDescription(parcel.readCharSequence()); 426 builder.setFlags(parcel.readInt()); 427 return builder.build(); 428 } 429 430 @Override 431 public SaveInfo[] newArray(int size) { 432 return new SaveInfo[size]; 433 } 434 }; 435} 436