FillResponse.java revision 2ef19c1d73f89ca4718b5a8f0c2e7221621e844f
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.service.autofill.FillRequest.INVALID_REQUEST_ID; 20import static android.view.autofill.Helper.sDebug; 21 22import android.annotation.NonNull; 23import android.annotation.Nullable; 24import android.app.Activity; 25import android.content.IntentSender; 26import android.os.Bundle; 27import android.os.Parcel; 28import android.os.Parcelable; 29import android.view.autofill.AutofillId; 30import android.view.autofill.AutofillManager; 31import android.widget.RemoteViews; 32 33import java.util.ArrayList; 34import java.util.Arrays; 35 36/** 37 * Response for a {@link 38 * AutofillService#onFillRequest(FillRequest, android.os.CancellationSignal, FillCallback)}. 39 * 40 * <p>See the main {@link AutofillService} documentation for more details and examples. 41 */ 42public final class FillResponse implements Parcelable { 43 44 private final @Nullable ArrayList<Dataset> mDatasets; 45 private final @Nullable SaveInfo mSaveInfo; 46 private final @Nullable Bundle mClientState; 47 private final @Nullable RemoteViews mPresentation; 48 private final @Nullable IntentSender mAuthentication; 49 private final @Nullable AutofillId[] mAuthenticationIds; 50 private final @Nullable AutofillId[] mIgnoredIds; 51 private int mRequestId; 52 53 private FillResponse(@NonNull Builder builder) { 54 mDatasets = builder.mDatasets; 55 mSaveInfo = builder.mSaveInfo; 56 mClientState = builder.mCLientState; 57 mPresentation = builder.mPresentation; 58 mAuthentication = builder.mAuthentication; 59 mAuthenticationIds = builder.mAuthenticationIds; 60 mIgnoredIds = builder.mIgnoredIds; 61 mRequestId = INVALID_REQUEST_ID; 62 } 63 64 /** @hide */ 65 public @Nullable Bundle getClientState() { 66 return mClientState; 67 } 68 69 /** @hide */ 70 public @Nullable ArrayList<Dataset> getDatasets() { 71 return mDatasets; 72 } 73 74 /** @hide */ 75 public @Nullable SaveInfo getSaveInfo() { 76 return mSaveInfo; 77 } 78 79 /** @hide */ 80 public @Nullable RemoteViews getPresentation() { 81 return mPresentation; 82 } 83 84 /** @hide */ 85 public @Nullable IntentSender getAuthentication() { 86 return mAuthentication; 87 } 88 89 /** @hide */ 90 public @Nullable AutofillId[] getAuthenticationIds() { 91 return mAuthenticationIds; 92 } 93 94 /** @hide */ 95 public @Nullable AutofillId[] getIgnoredIds() { 96 return mIgnoredIds; 97 } 98 99 /** 100 * Associates a {@link FillResponse} to a request. 101 * 102 * <p>Set inside of the {@link FillCallback} code, not the {@link AutofillService}. 103 * 104 * @param requestId The id of the request to associate the response to. 105 * 106 * @hide 107 */ 108 public void setRequestId(int requestId) { 109 mRequestId = requestId; 110 } 111 112 /** @hide */ 113 public int getRequestId() { 114 return mRequestId; 115 } 116 117 /** 118 * Builder for {@link FillResponse} objects. You must to provide at least 119 * one dataset or set an authentication intent with a presentation view. 120 */ 121 public static final class Builder { 122 private ArrayList<Dataset> mDatasets; 123 private SaveInfo mSaveInfo; 124 private Bundle mCLientState; 125 private RemoteViews mPresentation; 126 private IntentSender mAuthentication; 127 private AutofillId[] mAuthenticationIds; 128 private AutofillId[] mIgnoredIds; 129 private boolean mDestroyed; 130 131 /** 132 * Requires a fill response authentication before autofilling the screen with 133 * any data set in this response. 134 * 135 * <p>This is typically useful when a user interaction is required to unlock their 136 * data vault if you encrypt the data set labels and data set data. It is recommended 137 * to encrypt only the sensitive data and not the data set labels which would allow 138 * auth on the data set level leading to a better user experience. Note that if you 139 * use sensitive data as a label, for example an email address, then it should also 140 * be encrypted. The provided {@link android.app.PendingIntent intent} must be an 141 * {@link Activity} which implements your authentication flow. Also if you provide an auth 142 * intent you also need to specify the presentation view to be shown in the fill UI 143 * for the user to trigger your authentication flow. 144 * 145 * <p>When a user triggers autofill, the system launches the provided intent 146 * whose extras will have the {@link AutofillManager#EXTRA_ASSIST_STRUCTURE screen 147 * content} and your {@link android.view.autofill.AutofillManager#EXTRA_CLIENT_STATE 148 * client state}. Once you complete your authentication flow you should set the 149 * {@link Activity} result to {@link android.app.Activity#RESULT_OK} and provide the fully 150 * populated {@link FillResponse response} by setting it to the {@link 151 * AutofillManager#EXTRA_AUTHENTICATION_RESULT} extra. 152 * For example, if you provided an empty {@link FillResponse resppnse} because the 153 * user's data was locked and marked that the response needs an authentication then 154 * in the response returned if authentication succeeds you need to provide all 155 * available data sets some of which may need to be further authenticated, for 156 * example a credit card whose CVV needs to be entered. 157 * 158 * <p>If you provide an authentication intent you must also provide a presentation 159 * which is used to visualize visualize the response for triggering the authentication 160 * flow. 161 * 162 * <p></><strong>Note:</strong> Do not make the provided pending intent 163 * immutable by using {@link android.app.PendingIntent#FLAG_IMMUTABLE} as the 164 * platform needs to fill in the authentication arguments. 165 * 166 * @param authentication Intent to an activity with your authentication flow. 167 * @param presentation The presentation to visualize the response. 168 * @param ids id of Views that when focused will display the authentication UI affordance. 169 * 170 * @return This builder. 171 * @throw {@link IllegalArgumentException} if {@code ids} is {@code null} or empty, or if 172 * neither {@code authentication} nor {@code presentation} is non-{@code null}. 173 * 174 * @see android.app.PendingIntent#getIntentSender() 175 */ 176 public @NonNull Builder setAuthentication(@NonNull AutofillId[] ids, 177 @Nullable IntentSender authentication, @Nullable RemoteViews presentation) { 178 throwIfDestroyed(); 179 if (ids == null || ids.length == 0) { 180 throw new IllegalArgumentException("ids cannot be null or empry"); 181 } 182 if (authentication == null ^ presentation == null) { 183 throw new IllegalArgumentException("authentication and presentation" 184 + " must be both non-null or null"); 185 } 186 mAuthentication = authentication; 187 mPresentation = presentation; 188 mAuthenticationIds = ids; 189 return this; 190 } 191 192 /** 193 * Specifies views that should not trigger new 194 * {@link AutofillService#onFillRequest(FillRequest, android.os.CancellationSignal, 195 * FillCallback)} requests. 196 * 197 * <p>This is typically used when the service cannot autofill the view; for example, a 198 * text field representing the result of a Captcha challenge. 199 */ 200 public Builder setIgnoredIds(AutofillId...ids) { 201 mIgnoredIds = ids; 202 return this; 203 } 204 205 /** 206 * Adds a new {@link Dataset} to this response. 207 * 208 * @return This builder. 209 */ 210 public @NonNull Builder addDataset(@Nullable Dataset dataset) { 211 throwIfDestroyed(); 212 if (dataset == null) { 213 return this; 214 } 215 if (mDatasets == null) { 216 mDatasets = new ArrayList<>(); 217 } 218 if (!mDatasets.add(dataset)) { 219 return this; 220 } 221 return this; 222 } 223 224 /** 225 * Sets the {@link SaveInfo} associated with this response. 226 * 227 * @return This builder. 228 */ 229 public @NonNull Builder setSaveInfo(@NonNull SaveInfo saveInfo) { 230 throwIfDestroyed(); 231 mSaveInfo = saveInfo; 232 return this; 233 } 234 235 /** 236 * Sets a {@link Bundle state} that will be passed to subsequent APIs that 237 * manipulate this response. For example, they are passed to subsequent 238 * calls to {@link AutofillService#onFillRequest(FillRequest, android.os.CancellationSignal, 239 * FillCallback)} and {@link AutofillService#onSaveRequest(SaveRequest, SaveCallback)}. 240 * You can use this to store intermediate state that is persistent across multiple 241 * fill requests and the subsequent save request. 242 * 243 * <p>If this method is called on multiple {@link FillResponse} objects for the same 244 * screen, just the latest bundle is passed back to the service. 245 * 246 * <p>Once a {@link AutofillService#onSaveRequest(SaveRequest, SaveCallback) 247 * save request} is made the client state is cleared. 248 * 249 * @param clientState The custom client state. 250 * @return This builder. 251 */ 252 public Builder setClientState(@Nullable Bundle clientState) { 253 throwIfDestroyed(); 254 mCLientState = clientState; 255 return this; 256 } 257 258 /** 259 * Builds a new {@link FillResponse} instance. 260 * 261 * <p>You must provide at least one dataset or some savable ids or an authentication with a 262 * presentation view. 263 * 264 * @return A built response. 265 */ 266 public FillResponse build() { 267 throwIfDestroyed(); 268 269 if (mAuthentication == null && mDatasets == null && mSaveInfo == null) { 270 throw new IllegalArgumentException("need to provide at least one DataSet or a " 271 + "SaveInfo or an authentication with a presentation"); 272 } 273 mDestroyed = true; 274 return new FillResponse(this); 275 } 276 277 private void throwIfDestroyed() { 278 if (mDestroyed) { 279 throw new IllegalStateException("Already called #build()"); 280 } 281 } 282 } 283 284 ///////////////////////////////////// 285 // Object "contract" methods. // 286 ///////////////////////////////////// 287 @Override 288 public String toString() { 289 if (!sDebug) return super.toString(); 290 291 // TODO: create a dump() method instead 292 return new StringBuilder( 293 "FillResponse : [mRequestId=" + mRequestId) 294 .append(", datasets=").append(mDatasets) 295 .append(", saveInfo=").append(mSaveInfo) 296 .append(", clientState=").append(mClientState != null) 297 .append(", hasPresentation=").append(mPresentation != null) 298 .append(", hasAuthentication=").append(mAuthentication != null) 299 .append(", authenticationIds=").append(Arrays.toString(mAuthenticationIds)) 300 .append(", ignoredIds=").append(Arrays.toString(mIgnoredIds)) 301 .append("]") 302 .toString(); 303 } 304 305 ///////////////////////////////////// 306 // Parcelable "contract" methods. // 307 ///////////////////////////////////// 308 309 @Override 310 public int describeContents() { 311 return 0; 312 } 313 314 @Override 315 public void writeToParcel(Parcel parcel, int flags) { 316 parcel.writeTypedArrayList(mDatasets, flags); 317 parcel.writeParcelable(mSaveInfo, flags); 318 parcel.writeParcelable(mClientState, flags); 319 parcel.writeParcelableArray(mAuthenticationIds, flags); 320 parcel.writeParcelable(mAuthentication, flags); 321 parcel.writeParcelable(mPresentation, flags); 322 parcel.writeParcelableArray(mIgnoredIds, flags); 323 parcel.writeInt(mRequestId); 324 } 325 326 public static final Parcelable.Creator<FillResponse> CREATOR = 327 new Parcelable.Creator<FillResponse>() { 328 @Override 329 public FillResponse createFromParcel(Parcel parcel) { 330 // Always go through the builder to ensure the data ingested by 331 // the system obeys the contract of the builder to avoid attacks 332 // using specially crafted parcels. 333 final Builder builder = new Builder(); 334 final ArrayList<Dataset> datasets = parcel.readTypedArrayList(null); 335 final int datasetCount = (datasets != null) ? datasets.size() : 0; 336 for (int i = 0; i < datasetCount; i++) { 337 builder.addDataset(datasets.get(i)); 338 } 339 builder.setSaveInfo(parcel.readParcelable(null)); 340 builder.setClientState(parcel.readParcelable(null)); 341 342 // Sets authentication state. 343 final AutofillId[] authenticationIds = parcel.readParcelableArray(null, 344 AutofillId.class); 345 final IntentSender authentication = parcel.readParcelable(null); 346 final RemoteViews presentation = parcel.readParcelable(null); 347 if (authenticationIds != null) { 348 builder.setAuthentication(authenticationIds, authentication, presentation); 349 } 350 351 builder.setIgnoredIds(parcel.readParcelableArray(null, AutofillId.class)); 352 final FillResponse response = builder.build(); 353 354 response.setRequestId(parcel.readInt()); 355 356 return response; 357 } 358 359 @Override 360 public FillResponse[] newArray(int size) { 361 return new FillResponse[size]; 362 } 363 }; 364} 365