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 package android.autofillservice.cts; 17 18 import static com.google.common.truth.Truth.assertWithMessage; 19 20 import static android.autofillservice.cts.Helper.getAutofillIds; 21 import android.app.assist.AssistStructure; 22 import android.app.assist.AssistStructure.ViewNode; 23 import android.content.IntentSender; 24 import android.os.Bundle; 25 import android.service.autofill.Dataset; 26 import android.service.autofill.FillCallback; 27 import android.service.autofill.FillResponse; 28 import android.service.autofill.SaveInfo; 29 import android.view.autofill.AutofillId; 30 import android.view.autofill.AutofillValue; 31 import android.widget.RemoteViews; 32 33 import java.util.ArrayList; 34 import java.util.Arrays; 35 import java.util.HashMap; 36 import java.util.List; 37 import java.util.Map; 38 import java.util.function.Function; 39 40 /** 41 * Helper class used to produce a {@link FillResponse} based on expected fields that should be 42 * present in the {@link AssistStructure}. 43 * 44 * <p>Typical usage: 45 * 46 * <pre class="prettyprint"> 47 * InstrumentedAutoFillService.setFillResponse(new CannedFillResponse.Builder() 48 * .addDataset(new CannedDataset.Builder("dataset_name") 49 * .setField("resource_id1", AutofillValue.forText("value1")) 50 * .setField("resource_id2", AutofillValue.forText("value2")) 51 * .build()) 52 * .build()); 53 * </pre class="prettyprint"> 54 */ 55 final class CannedFillResponse { 56 57 private final ResponseType mResponseType; 58 private final List<CannedDataset> mDatasets; 59 private final String mFailureMessage; 60 private final int mSaveType; 61 private final String[] mRequiredSavableIds; 62 private final String[] mOptionalSavableIds; 63 private final String mSaveDescription; 64 private final Bundle mExtras; 65 private final RemoteViews mPresentation; 66 private final IntentSender mAuthentication; 67 private final String[] mAuthenticationIds; 68 private final String[] mIgnoredIds; 69 private final int mNegativeActionStyle; 70 private final IntentSender mNegativeActionListener; 71 private final int mFlags; 72 CannedFillResponse(Builder builder)73 private CannedFillResponse(Builder builder) { 74 mResponseType = builder.mResponseType; 75 mDatasets = builder.mDatasets; 76 mFailureMessage = builder.mFailureMessage; 77 mRequiredSavableIds = builder.mRequiredSavableIds; 78 mOptionalSavableIds = builder.mOptionalSavableIds; 79 mSaveDescription = builder.mSaveDescription; 80 mSaveType = builder.mSaveType; 81 mExtras = builder.mExtras; 82 mPresentation = builder.mPresentation; 83 mAuthentication = builder.mAuthentication; 84 mAuthenticationIds = builder.mAuthenticationIds; 85 mIgnoredIds = builder.mIgnoredIds; 86 mNegativeActionStyle = builder.mNegativeActionStyle; 87 mNegativeActionListener = builder.mNegativeActionListener; 88 mFlags = builder.mFlags; 89 } 90 91 /** 92 * Constant used to pass a {@code null} response to the 93 * {@link FillCallback#onSuccess(FillResponse)} method. 94 */ 95 static final CannedFillResponse NO_RESPONSE = 96 new Builder(ResponseType.NULL).build(); 97 98 /** 99 * Constant used to emulate a timeout by not calling any method on {@link FillCallback}. 100 */ 101 static final CannedFillResponse DO_NOT_REPLY_RESPONSE = 102 new Builder(ResponseType.TIMEOUT).build(); 103 104 getFailureMessage()105 String getFailureMessage() { 106 return mFailureMessage; 107 } 108 getResponseType()109 ResponseType getResponseType() { 110 return mResponseType; 111 } 112 113 /** 114 * Creates a new response, replacing the dataset field ids by the real ids from the assist 115 * structure. 116 */ asFillResponse(Function<String, ViewNode> nodeResolver)117 FillResponse asFillResponse(Function<String, ViewNode> nodeResolver) { 118 final FillResponse.Builder builder = new FillResponse.Builder(); 119 if (mDatasets != null) { 120 for (CannedDataset cannedDataset : mDatasets) { 121 final Dataset dataset = cannedDataset.asDataset(nodeResolver); 122 assertWithMessage("Cannot create datase").that(dataset).isNotNull(); 123 builder.addDataset(dataset); 124 } 125 } 126 if (mRequiredSavableIds != null) { 127 final SaveInfo.Builder saveInfo = new SaveInfo.Builder(mSaveType, 128 getAutofillIds(nodeResolver, mRequiredSavableIds)); 129 130 saveInfo.setFlags(mFlags); 131 132 if (mOptionalSavableIds != null) { 133 saveInfo.setOptionalIds(getAutofillIds(nodeResolver, mOptionalSavableIds)); 134 } 135 if (mSaveDescription != null) { 136 saveInfo.setDescription(mSaveDescription); 137 } 138 saveInfo.setNegativeAction(mNegativeActionStyle, mNegativeActionListener); 139 builder.setSaveInfo(saveInfo.build()); 140 } 141 if (mIgnoredIds != null) { 142 builder.setIgnoredIds(getAutofillIds(nodeResolver, mIgnoredIds)); 143 } 144 if (mAuthenticationIds != null) { 145 builder.setAuthentication(getAutofillIds(nodeResolver, mAuthenticationIds), 146 mAuthentication, mPresentation); 147 } 148 return builder 149 .setClientState(mExtras) 150 .build(); 151 } 152 153 @Override toString()154 public String toString() { 155 return "CannedFillResponse: [type=" + mResponseType 156 + ",datasets=" + mDatasets 157 + ", requiredSavableIds=" + Arrays.toString(mRequiredSavableIds) 158 + ", optionalSavableIds=" + Arrays.toString(mOptionalSavableIds) 159 + ", flags=" + mFlags 160 + ", failureMessage=" + mFailureMessage 161 + ", saveDescription=" + mSaveDescription 162 + ", hasPresentation=" + (mPresentation != null) 163 + ", hasAuthentication=" + (mAuthentication != null) 164 + ", authenticationIds=" + Arrays.toString(mAuthenticationIds) 165 + ", ignoredIds=" + Arrays.toString(mIgnoredIds) 166 + "]"; 167 } 168 169 enum ResponseType { 170 NORMAL, 171 NULL, 172 TIMEOUT 173 } 174 175 static class Builder { 176 private final List<CannedDataset> mDatasets = new ArrayList<>(); 177 private final ResponseType mResponseType; 178 private String mFailureMessage; 179 private String[] mRequiredSavableIds; 180 private String[] mOptionalSavableIds; 181 private String mSaveDescription; 182 public int mSaveType = -1; 183 private Bundle mExtras; 184 private RemoteViews mPresentation; 185 private IntentSender mAuthentication; 186 private String[] mAuthenticationIds; 187 private String[] mIgnoredIds; 188 private int mNegativeActionStyle; 189 private IntentSender mNegativeActionListener; 190 private int mFlags; 191 Builder(ResponseType type)192 public Builder(ResponseType type) { 193 mResponseType = type; 194 } 195 Builder()196 public Builder() { 197 this(ResponseType.NORMAL); 198 } 199 addDataset(CannedDataset dataset)200 public Builder addDataset(CannedDataset dataset) { 201 assertWithMessage("already set failure").that(mFailureMessage).isNull(); 202 mDatasets.add(dataset); 203 return this; 204 } 205 206 /** 207 * Sets the required savable ids based on they {@code resourceId}. 208 */ setRequiredSavableIds(int type, String... ids)209 public Builder setRequiredSavableIds(int type, String... ids) { 210 mSaveType = type; 211 mRequiredSavableIds = ids; 212 return this; 213 } 214 setFlags(int flags)215 public Builder setFlags(int flags) { 216 mFlags = flags; 217 return this; 218 } 219 220 /** 221 * Sets the optional savable ids based on they {@code resourceId}. 222 */ setOptionalSavableIds(String... ids)223 public Builder setOptionalSavableIds(String... ids) { 224 mOptionalSavableIds = ids; 225 return this; 226 } 227 228 /** 229 * Sets the description passed to the {@link SaveInfo}. 230 */ setSaveDescription(String description)231 public Builder setSaveDescription(String description) { 232 mSaveDescription = description; 233 return this; 234 } 235 236 /** 237 * Sets the extra passed to {@link 238 * android.service.autofill.FillResponse.Builder#setClientState(Bundle)}. 239 */ setExtras(Bundle data)240 public Builder setExtras(Bundle data) { 241 mExtras = data; 242 return this; 243 } 244 245 /** 246 * Sets the view to present the response in the UI. 247 */ setPresentation(RemoteViews presentation)248 public Builder setPresentation(RemoteViews presentation) { 249 mPresentation = presentation; 250 return this; 251 } 252 253 /** 254 * Sets the authentication intent. 255 */ setAuthentication(IntentSender authentication, String... ids)256 public Builder setAuthentication(IntentSender authentication, String... ids) { 257 mAuthenticationIds = ids; 258 mAuthentication = authentication; 259 return this; 260 } 261 262 /** 263 * Sets the ignored fields based on resource ids. 264 */ setIgnoreFields(String...ids)265 public Builder setIgnoreFields(String...ids) { 266 mIgnoredIds = ids; 267 return this; 268 } 269 270 /** 271 * Sets the negative action spec. 272 */ setNegativeAction(int style, IntentSender listener)273 public Builder setNegativeAction(int style, IntentSender listener) { 274 mNegativeActionStyle = style; 275 mNegativeActionListener = listener; 276 return this; 277 } 278 build()279 public CannedFillResponse build() { 280 return new CannedFillResponse(this); 281 } 282 283 /** 284 * Sets the response to call {@link FillCallback#onFailure(CharSequence)}. 285 */ returnFailure(String message)286 public Builder returnFailure(String message) { 287 assertWithMessage("already added datasets").that(mDatasets).isEmpty(); 288 mFailureMessage = message; 289 return this; 290 } 291 } 292 293 /** 294 * Helper class used to produce a {@link Dataset} based on expected fields that should be 295 * present in the {@link AssistStructure}. 296 * 297 * <p>Typical usage: 298 * 299 * <pre class="prettyprint"> 300 * InstrumentedAutoFillService.setFillResponse(new CannedFillResponse.Builder() 301 * .addDataset(new CannedDataset.Builder("dataset_name") 302 * .setField("resource_id1", AutofillValue.forText("value1")) 303 * .setField("resource_id2", AutofillValue.forText("value2")) 304 * .build()) 305 * .build()); 306 * </pre class="prettyprint"> 307 */ 308 static class CannedDataset { 309 private final Map<String, AutofillValue> mFieldValues; 310 private final Map<String, RemoteViews> mFieldPresentations; 311 private final RemoteViews mPresentation; 312 private final IntentSender mAuthentication; 313 private final String mId; 314 CannedDataset(Builder builder)315 private CannedDataset(Builder builder) { 316 mFieldValues = builder.mFieldValues; 317 mFieldPresentations = builder.mFieldPresentations; 318 mPresentation = builder.mPresentation; 319 mAuthentication = builder.mAuthentication; 320 mId = builder.mId; 321 } 322 323 /** 324 * Creates a new dataset, replacing the field ids by the real ids from the assist structure. 325 */ asDataset(Function<String, ViewNode> nodeResolver)326 Dataset asDataset(Function<String, ViewNode> nodeResolver) { 327 final Dataset.Builder builder = (mPresentation == null) 328 ? new Dataset.Builder() 329 : new Dataset.Builder(mPresentation); 330 331 if (mFieldValues != null) { 332 for (Map.Entry<String, AutofillValue> entry : mFieldValues.entrySet()) { 333 final String resourceId = entry.getKey(); 334 final ViewNode node = nodeResolver.apply(resourceId); 335 if (node == null) { 336 throw new AssertionError("No node with resource id " + resourceId); 337 } 338 final AutofillId id = node.getAutofillId(); 339 final AutofillValue value = entry.getValue(); 340 final RemoteViews presentation = mFieldPresentations.get(resourceId); 341 if (presentation != null) { 342 builder.setValue(id, value, presentation); 343 } else { 344 builder.setValue(id, value); 345 } 346 } 347 } 348 builder.setId(mId).setAuthentication(mAuthentication); 349 return builder.build(); 350 } 351 352 @Override toString()353 public String toString() { 354 return "CannedDataset " + mId + " : [hasPresentation=" + (mPresentation != null) 355 + ", fieldPresentations=" + (mFieldPresentations) 356 + ", hasAuthentication=" + (mAuthentication != null) 357 + ", fieldValuess=" + mFieldValues + "]"; 358 } 359 360 static class Builder { 361 private final Map<String, AutofillValue> mFieldValues = new HashMap<>(); 362 private final Map<String, RemoteViews> mFieldPresentations = new HashMap<>(); 363 private RemoteViews mPresentation; 364 private IntentSender mAuthentication; 365 private String mId; 366 Builder()367 public Builder() { 368 369 } 370 Builder(RemoteViews presentation)371 public Builder(RemoteViews presentation) { 372 mPresentation = presentation; 373 } 374 375 /** 376 * Sets the canned value of a text field based on its {@code resourceId}. 377 */ setField(String resourceId, String text)378 public Builder setField(String resourceId, String text) { 379 return setField(resourceId, AutofillValue.forText(text)); 380 } 381 382 /** 383 * Sets the canned value of a list field based on its {@code resourceId}. 384 */ setField(String resourceId, int index)385 public Builder setField(String resourceId, int index) { 386 return setField(resourceId, AutofillValue.forList(index)); 387 } 388 389 /** 390 * Sets the canned value of a toggle field based on its {@code resourceId}. 391 */ setField(String resourceId, boolean toggled)392 public Builder setField(String resourceId, boolean toggled) { 393 return setField(resourceId, AutofillValue.forToggle(toggled)); 394 } 395 396 /** 397 * Sets the canned value of a date field based on its {@code resourceId}. 398 */ setField(String resourceId, long date)399 public Builder setField(String resourceId, long date) { 400 return setField(resourceId, AutofillValue.forDate(date)); 401 } 402 403 /** 404 * Sets the canned value of a date field based on its {@code resourceId}. 405 */ setField(String resourceId, AutofillValue value)406 public Builder setField(String resourceId, AutofillValue value) { 407 mFieldValues.put(resourceId, value); 408 return this; 409 } 410 411 /** 412 * Sets the canned value of a field based on its {@code resourceId}. 413 */ setField(String resourceId, String text, RemoteViews presentation)414 public Builder setField(String resourceId, String text, RemoteViews presentation) { 415 setField(resourceId, text); 416 mFieldPresentations.put(resourceId, presentation); 417 return this; 418 } 419 420 /** 421 * Sets the view to present the response in the UI. 422 */ setPresentation(RemoteViews presentation)423 public Builder setPresentation(RemoteViews presentation) { 424 mPresentation = presentation; 425 return this; 426 } 427 428 /** 429 * Sets the authentication intent. 430 */ setAuthentication(IntentSender authentication)431 public Builder setAuthentication(IntentSender authentication) { 432 mAuthentication = authentication; 433 return this; 434 } 435 436 /** 437 * Sets the name. 438 */ setId(String id)439 public Builder setId(String id) { 440 mId = id; 441 return this; 442 } 443 build()444 public CannedDataset build() { 445 return new CannedDataset(this); 446 } 447 } 448 } 449 } 450