SaveInfo.java revision 640f30a7763b0a4b80c767acb84c740aac04768b
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.ArraySet; 29import android.view.autofill.AutoFillId; 30import android.view.autofill.AutofillId; 31 32import java.lang.annotation.Retention; 33import java.lang.annotation.RetentionPolicy; 34import java.util.ArrayList; 35 36/** 37 * Information used to indicate that a service is interested on saving the user-inputed data for 38 * future use. 39 * 40 * <p>A {@link SaveInfo} is always associated with a {@link FillResponse}. 41 * 42 * <p>A {@link SaveInfo} must define the type it represents, and contain at least one 43 * {@code savableId}. A {@code savableId} is the {@link AutofillId} of a view the service is 44 * interested to save in a {@code onSaveRequest()}; the ids of all {@link Dataset} present in the 45 * {@link FillResponse} associated with this {@link SaveInfo} are already marked as savable, 46 * but additional ids can be added through {@link Builder#addSavableIds(AutofillId...)}. 47 * 48 * <p>See {@link AutofillService#onSaveRequest(android.app.assist.AssistStructure, Bundle, 49 * SaveCallback)} and {@link FillResponse} for more info. 50 */ 51public final class SaveInfo implements Parcelable { 52 53 /** 54 * Type used on when the service can save the contents of an activity, but cannot describe what 55 * the content is for. 56 */ 57 public static final int SAVE_DATA_TYPE_GENERIC = 0; 58 59 /** 60 * Type used when the {@link FillResponse} represents user credentials that have a password. 61 */ 62 public static final int SAVE_DATA_TYPE_PASSWORD = 1; 63 64 65 /** 66 * Type used on when the {@link FillResponse} represents a physical address (such as street, 67 * city, state, etc). 68 */ 69 public static final int SAVE_DATA_TYPE_ADDRESS = 2; 70 71 /** 72 * Type used when the {@link FillResponse} represents a credit card. 73 */ 74 public static final int SAVE_DATA_TYPE_CREDIT_CARD = 3; 75 76 private final @SaveDataType int mType; 77 private CharSequence mNegativeActionTitle; 78 private IntentSender mNegativeActionListener; 79 private ArraySet<AutofillId> mSavableIds; 80 private final CharSequence mDescription; 81 82 /** @hide */ 83 @IntDef({ 84 SAVE_DATA_TYPE_GENERIC, 85 SAVE_DATA_TYPE_PASSWORD, 86 SAVE_DATA_TYPE_ADDRESS, 87 SAVE_DATA_TYPE_CREDIT_CARD 88 }) 89 @Retention(RetentionPolicy.SOURCE) 90 public @interface SaveDataType { 91 } 92 93 private SaveInfo(Builder builder) { 94 mType = builder.mType; 95 mNegativeActionTitle = builder.mNegativeActionTitle; 96 mNegativeActionListener = builder.mNegativeActionListener; 97 mSavableIds = builder.mSavableIds; 98 mDescription = builder.mDescription; 99 } 100 101 /** @hide */ 102 public @Nullable CharSequence getNegativeActionTitle() { 103 return mNegativeActionTitle; 104 } 105 106 /** @hide */ 107 public @Nullable IntentSender getNegativeActionListener() { 108 return mNegativeActionListener; 109 } 110 111 /** @hide */ 112 public @Nullable ArraySet<AutofillId> getSavableIds() { 113 return mSavableIds; 114 } 115 116 /** @hide */ 117 public int getType() { 118 return mType; 119 } 120 121 /** @hide */ 122 public CharSequence getDescription() { 123 return mDescription; 124 } 125 126 /** @hide */ 127 public void addSavableIds(@Nullable ArrayList<Dataset> datasets) { 128 if (datasets != null) { 129 for (Dataset dataset : datasets) { 130 final ArrayList<AutofillId> ids = dataset.getFieldIds(); 131 if (ids != null) { 132 final int fieldCount = ids.size(); 133 for (int i = 0; i < fieldCount; i++) { 134 final AutofillId id = ids.get(i); 135 if (mSavableIds == null) { 136 mSavableIds = new ArraySet<>(); 137 } 138 mSavableIds.add(id); 139 } 140 } 141 } 142 } 143 } 144 145 /** 146 * A builder for {@link SaveInfo} objects. 147 */ 148 public static final class Builder { 149 150 private final @SaveDataType int mType; 151 private CharSequence mNegativeActionTitle; 152 private IntentSender mNegativeActionListener; 153 private ArraySet<AutofillId> mSavableIds; 154 private CharSequence mDescription; 155 private boolean mDestroyed; 156 157 /** 158 * Creates a new builder. 159 * 160 * @param type the type of information the associated {@link FillResponse} represents. Must 161 * be {@link SaveInfo#SAVE_DATA_TYPE_GENERIC}, {@link SaveInfo#SAVE_DATA_TYPE_PASSWORD}, 162 * {@link SaveInfo#SAVE_DATA_TYPE_ADDRESS}, or {@link SaveInfo#SAVE_DATA_TYPE_CREDIT_CARD}; 163 * otherwise it will assume {@link SaveInfo#SAVE_DATA_TYPE_GENERIC}. 164 */ 165 public Builder(@SaveDataType int type) { 166 switch (type) { 167 case SAVE_DATA_TYPE_PASSWORD: 168 case SAVE_DATA_TYPE_ADDRESS: 169 case SAVE_DATA_TYPE_CREDIT_CARD: 170 mType = type; 171 break; 172 default: 173 mType = SAVE_DATA_TYPE_GENERIC; 174 } 175 } 176 177 /** 178 * Adds ids of additional views the service would be interested to save, but were not 179 * indirectly set through {@link FillResponse.Builder#addDataset(Dataset)}. 180 * 181 * @param ids The savable ids. 182 * @return This builder. 183 * 184 * @see FillResponse 185 */ 186 public @NonNull Builder addSavableIds(@Nullable AutofillId... ids) { 187 throwIfDestroyed(); 188 189 if (ids == null) { 190 return this; 191 } 192 for (AutofillId id : ids) { 193 if (mSavableIds == null) { 194 mSavableIds = new ArraySet<>(); 195 } 196 mSavableIds.add(id); 197 } 198 return this; 199 } 200 201 /** 202 * Sets an optional description to be shown in the UI when the user is asked to save. 203 * 204 * <p>Typically, it describes how the data will be stored by the service, so it can help 205 * users to decide whether they can trust the service to save their data. 206 * 207 * @param description a succint description. 208 * @return This Builder. 209 */ 210 public @NonNull Builder setDescription(@Nullable CharSequence description) { 211 mDescription = description; 212 return this; 213 } 214 215 /** 216 * Sets the title and listener for the negative save action. 217 * 218 * <p>This allows a fill-provider to customize the text and be 219 * notified when the user selects the negative action in the save 220 * UI. Note that selecting the negative action regardless of its text 221 * and listener being customized would dismiss the save UI and if a 222 * custom listener intent is provided then this intent will be 223 * started.</p> 224 * 225 * <p>This customization could be useful for providing additional 226 * semantics to the negative action. For example, a fill-provider 227 * can use this mechanism to add a "Disable" function or a "More info" 228 * function, etc. Note that the save action is exclusively controlled 229 * by the platform to ensure user consent is collected to release 230 * data from the filled app to the fill-provider.</p> 231 * 232 * @param title The action title. 233 * @param listener The action listener. 234 * @return This builder. 235 * 236 * @throws IllegalArgumentException If the title and the listener 237 * are not both either null or non-null. 238 */ 239 public @NonNull Builder setNegativeAction(@Nullable CharSequence title, 240 @Nullable IntentSender listener) { 241 throwIfDestroyed(); 242 if (title == null ^ listener == null) { 243 throw new IllegalArgumentException("title and listener" 244 + " must be both non-null or null"); 245 } 246 mNegativeActionTitle = title; 247 mNegativeActionListener = listener; 248 return this; 249 } 250 251 /** 252 * Builds a new {@link SaveInfo} instance. 253 */ 254 public SaveInfo build() { 255 throwIfDestroyed(); 256 mDestroyed = true; 257 return new SaveInfo(this); 258 } 259 260 private void throwIfDestroyed() { 261 if (mDestroyed) { 262 throw new IllegalStateException("Already called #build()"); 263 } 264 } 265 266 } 267 268 ///////////////////////////////////// 269 // Object "contract" methods. // 270 ///////////////////////////////////// 271 @Override 272 public String toString() { 273 if (!DEBUG) return super.toString(); 274 275 return new StringBuilder("SaveInfo: [type=").append(mType) 276 .append(", savableIds=").append(mSavableIds) 277 .append("]").toString(); 278 } 279 280 ///////////////////////////////////// 281 // Parcelable "contract" methods. // 282 ///////////////////////////////////// 283 284 @Override 285 public int describeContents() { 286 return 0; 287 } 288 289 @Override 290 public void writeToParcel(Parcel parcel, int flags) { 291 parcel.writeInt(mType); 292 parcel.writeCharSequence(mNegativeActionTitle); 293 parcel.writeParcelable(mNegativeActionListener, flags); 294 parcel.writeTypedArraySet(mSavableIds, flags); 295 parcel.writeCharSequence(mDescription); 296 } 297 298 public static final Parcelable.Creator<SaveInfo> CREATOR = new Parcelable.Creator<SaveInfo>() { 299 @Override 300 public SaveInfo createFromParcel(Parcel parcel) { 301 // Always go through the builder to ensure the data ingested by 302 // the system obeys the contract of the builder to avoid attacks 303 // using specially crafted parcels. 304 final Builder builder = new Builder(parcel.readInt()); 305 builder.setNegativeAction(parcel.readCharSequence(), parcel.readParcelable(null)); 306 final ArraySet<AutofillId> savableIds = parcel.readTypedArraySet(null); 307 final int savableIdsCount = (savableIds != null) ? savableIds.size() : 0; 308 for (int i = 0; i < savableIdsCount; i++) { 309 builder.addSavableIds(savableIds.valueAt(i)); 310 } 311 builder.setDescription(parcel.readCharSequence()); 312 return builder.build(); 313 } 314 315 @Override 316 public SaveInfo[] newArray(int size) { 317 return new SaveInfo[size]; 318 } 319 }; 320} 321