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