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