Dataset.java revision d633f072552815301a559520a1f93eb7e79ba319
1/* 2 * Copyright (C) 2016 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.NonNull; 22import android.annotation.Nullable; 23import android.content.IntentSender; 24import android.os.Parcel; 25import android.os.Parcelable; 26import android.view.autofill.AutoFillId; 27import android.view.autofill.AutoFillValue; 28 29import com.android.internal.util.Preconditions; 30 31import java.util.ArrayList; 32 33/** 34 * A set of data that can be used to auto-fill an {@link android.app.Activity}. 35 * 36 * <p>It contains: 37 * 38 * <ol> 39 * <li>A name used to identify the dataset in the UI. 40 * <li>A list of id/value pairs for the fields that can be auto-filled. 41 * <li>A list of savable ids in addition to the ones with a provided value. 42 * </ol> 43 * 44 * @see android.service.autofill.FillResponse for examples. 45 */ 46public final class Dataset implements Parcelable { 47 48 private final CharSequence mName; 49 private final ArrayList<AutoFillId> mFieldIds; 50 private final ArrayList<AutoFillValue> mFieldValues; 51 private final IntentSender mAuthentication; 52 53 private Dataset(Builder builder) { 54 mName = builder.mName; 55 mFieldIds = builder.mFieldIds; 56 mFieldValues = builder.mFieldValues; 57 mAuthentication = builder.mAuthentication; 58 } 59 60 /** @hide */ 61 public @NonNull CharSequence getName() { 62 return mName; 63 } 64 65 /** @hide */ 66 public @Nullable ArrayList<AutoFillId> getFieldIds() { 67 return mFieldIds; 68 } 69 70 /** @hide */ 71 public @Nullable ArrayList<AutoFillValue> getFieldValues() { 72 return mFieldValues; 73 } 74 75 /** @hide */ 76 public @Nullable IntentSender getAuthentication() { 77 return mAuthentication; 78 } 79 80 /** @hide */ 81 public boolean isEmpty() { 82 return mFieldIds == null || mFieldIds.isEmpty(); 83 } 84 85 @Override 86 public String toString() { 87 if (!DEBUG) return super.toString(); 88 89 final StringBuilder builder = new StringBuilder("Dataset [name=").append(mName) 90 .append(", fieldIds=").append(mFieldIds) 91 .append(", fieldValues=").append(mFieldValues) 92 .append(", hasAuthentication=").append(mAuthentication != null); 93 return builder.append(']').toString(); 94 } 95 96 /** 97 * A builder for {@link Dataset} objects. You must to provide at least 98 * one value for a field or set an authentication intent. 99 */ 100 public static final class Builder { 101 private CharSequence mName; 102 private ArrayList<AutoFillId> mFieldIds; 103 private ArrayList<AutoFillValue> mFieldValues; 104 private IntentSender mAuthentication; 105 private boolean mDestroyed; 106 107 /** 108 * Creates a new builder. 109 * 110 * @param name Name used to identify the dataset in the UI. Typically it's the same value as 111 * the first field in the dataset (like username or email address) or a user-provided name 112 * (like "My Work Address"). 113 */ 114 public Builder(@NonNull CharSequence name) { 115 mName = Preconditions.checkStringNotEmpty(name, "name cannot be empty or null"); 116 } 117 118 /** 119 * Requires a dataset authentication before auto-filling the activity with this dataset. 120 * 121 * <p>This method is called when you need to provide an authentication 122 * UI for the data set. For example, when a data set contains credit card information 123 * (such as number, expiration date, and verification code), you can display UI 124 * asking for the verification code to before filing in the data). Even if the 125 * data set is completely populated the system will launch the specified authentication 126 * intent and will need your approval to fill it in. Since the data set is "locked" 127 * until the user authenticates it, typically this data set name is masked 128 * (for example, "VISA....1234"). Typically you would want to store the data set 129 * labels non-encrypted and the actual sensitive data encrypted and not in memory. 130 * This allows showing the labels in the UI while involving the user if one of 131 * the items with these labels is chosen. Note that if you use sensitive data as 132 * a label, for example an email address, then it should also be encrypted.</p> 133 * 134 * <p>When a user triggers auto-fill, the system launches the provided intent 135 * whose extras will have the {@link 136 * android.view.autofill.AutoFillManager#EXTRA_ASSIST_STRUCTURE screen content}. Once 137 * you complete your authentication flow you should set the activity result to {@link 138 * android.app.Activity#RESULT_OK} and provide the fully populated {@link Dataset 139 * dataset} by setting it to the {@link 140 * android.view.autofill.AutoFillManager#EXTRA_AUTHENTICATION_RESULT} extra. For example, 141 * if you provided an credit card information without the CVV for the data set in the 142 * {@link FillResponse response} then the returned data set should contain the 143 * CVV entry.</p> 144 * 145 * <p></><strong>Note:</strong> Do not make the provided pending intent 146 * immutable by using {@link android.app.PendingIntent#FLAG_IMMUTABLE} as the 147 * platform needs to fill in the authentication arguments.</p> 148 * 149 * @param authentication Intent to an activity with your authentication flow. 150 * 151 * @see android.app.PendingIntent 152 */ 153 public @NonNull Builder setAuthentication(@Nullable IntentSender authentication) { 154 throwIfDestroyed(); 155 mAuthentication = authentication; 156 return this; 157 } 158 159 /** 160 * Sets the value of a field. 161 * 162 * @param id id returned by {@link 163 * android.app.assist.AssistStructure.ViewNode#getAutoFillId()}. 164 * @param value value to be auto filled. 165 */ 166 public @NonNull Builder setValue(@NonNull AutoFillId id, @NonNull AutoFillValue value) { 167 throwIfDestroyed(); 168 Preconditions.checkNotNull(id, "id cannot be null"); 169 Preconditions.checkNotNull(value, "value cannot be null"); 170 if (mFieldIds != null) { 171 final int existingIdx = mFieldIds.indexOf(id); 172 if (existingIdx >= 0) { 173 mFieldValues.set(existingIdx, value); 174 return this; 175 } 176 } else { 177 mFieldIds = new ArrayList<>(); 178 mFieldValues = new ArrayList<>(); 179 } 180 mFieldIds.add(id); 181 mFieldValues.add(value); 182 return this; 183 } 184 185 /** 186 * Creates a new {@link Dataset} instance. You should not interact 187 * with this builder once this method is called. 188 */ 189 public @NonNull Dataset build() { 190 throwIfDestroyed(); 191 mDestroyed = true; 192 if (mFieldIds == null && mAuthentication == null) { 193 throw new IllegalArgumentException( 194 "at least one value or an authentication must be set"); 195 } 196 return new Dataset(this); 197 } 198 199 private void throwIfDestroyed() { 200 if (mDestroyed) { 201 throw new IllegalStateException("Already called #build()"); 202 } 203 } 204 } 205 206 ///////////////////////////////////// 207 // Parcelable "contract" methods. // 208 ///////////////////////////////////// 209 210 @Override 211 public int describeContents() { 212 return 0; 213 } 214 215 @Override 216 public void writeToParcel(Parcel parcel, int flags) { 217 parcel.writeCharSequence(mName); 218 parcel.writeTypedArrayList(mFieldIds, 0); 219 parcel.writeTypedArrayList(mFieldValues, 0); 220 parcel.writeParcelable(mAuthentication, flags); 221 } 222 223 public static final Creator<Dataset> CREATOR = new Creator<Dataset>() { 224 @Override 225 public Dataset createFromParcel(Parcel parcel) { 226 // Always go through the builder to ensure the data ingested by 227 // the system obeys the contract of the builder to avoid attacks 228 // using specially crafted parcels. 229 final Builder builder = new Builder(parcel.readCharSequence()); 230 final ArrayList<AutoFillId> ids = parcel.readTypedArrayList(null); 231 final ArrayList<AutoFillValue> values = parcel.readTypedArrayList(null); 232 final int idCount = (ids != null) ? ids.size() : 0; 233 final int valueCount = (values != null) ? values.size() : 0; 234 for (int i = 0; i < idCount; i++) { 235 AutoFillId id = ids.get(i); 236 AutoFillValue value = (valueCount > i) ? values.get(i) : null; 237 builder.setValue(id, value); 238 } 239 builder.setAuthentication(parcel.readParcelable(null)); 240 return builder.build(); 241 } 242 243 @Override 244 public Dataset[] newArray(int size) { 245 return new Dataset[size]; 246 } 247 }; 248} 249