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