blob: b456dd7490eb689fa24b623f704bfd91596bedd0 [file] [log] [blame]
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.autofillservice.cts;
import static com.google.common.truth.Truth.assertWithMessage;
import static android.autofillservice.cts.Helper.dumpStructure;
import static android.autofillservice.cts.Helper.findNodeByResourceId;
import static android.autofillservice.cts.Helper.getAutofillIds;
import android.app.assist.AssistStructure;
import android.app.assist.AssistStructure.ViewNode;
import android.autofillservice.cts.CannedFillResponse.Builder;
import android.content.IntentSender;
import android.os.Bundle;
import android.service.autofill.Dataset;
import android.service.autofill.FillCallback;
import android.service.autofill.FillResponse;
import android.service.autofill.SaveInfo;
import android.view.autofill.AutofillId;
import android.view.autofill.AutofillValue;
import android.widget.RemoteViews;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Helper class used to produce a {@link FillResponse} based on expected fields that should be
* present in the {@link AssistStructure}.
*
* <p>Typical usage:
*
* <pre class="prettyprint">
* InstrumentedAutoFillService.setFillResponse(new CannedFillResponse.Builder()
* .addDataset(new CannedDataset.Builder("dataset_name")
* .setField("resource_id1", AutofillValue.forText("value1"))
* .setField("resource_id2", AutofillValue.forText("value2"))
* .build())
* .build());
* </pre class="prettyprint">
*/
final class CannedFillResponse {
private final List<CannedDataset> mDatasets;
private final int mSaveType;
private final String[] mRequiredSavableIds;
private final String[] mOptionalSavableIds;
private final String mSaveDescription;
private final Bundle mExtras;
private final RemoteViews mPresentation;
private final IntentSender mAuthentication;
private final String[] mAuthenticationIds;
private final String[] mIgnoredIds;
private final CharSequence mNegativeActionLabel;
private final IntentSender mNegativeActionListener;
private final int mFlags;
private CannedFillResponse(Builder builder) {
mDatasets = builder.mDatasets;
mRequiredSavableIds = builder.mRequiredSavableIds;
mOptionalSavableIds = builder.mOptionalSavableIds;
mSaveDescription = builder.mSaveDescription;
mSaveType = builder.mSaveType;
mExtras = builder.mExtras;
mPresentation = builder.mPresentation;
mAuthentication = builder.mAuthentication;
mAuthenticationIds = builder.mAuthenticationIds;
mIgnoredIds = builder.mIgnoredIds;
mNegativeActionLabel = builder.mNegativeActionLabel;
mNegativeActionListener = builder.mNegativeActionListener;
mFlags = builder.mFlags;
}
/**
* Constant used to pass a {@code null} response to the
* {@link FillCallback#onSuccess(FillResponse)} method.
*/
static final CannedFillResponse NO_RESPONSE = new Builder().build();
/**
* Creates a new response, replacing the dataset field ids by the real ids from the assist
* structure.
*/
FillResponse asFillResponse(AssistStructure structure) {
final FillResponse.Builder builder = new FillResponse.Builder();
if (mDatasets != null) {
for (CannedDataset cannedDataset : mDatasets) {
final Dataset dataset = cannedDataset.asDataset(structure);
assertWithMessage("Cannot create datase").that(dataset).isNotNull();
builder.addDataset(dataset);
}
}
if (mRequiredSavableIds != null) {
final SaveInfo.Builder saveInfo;
if (mRequiredSavableIds == null) {
saveInfo = new SaveInfo.Builder(mSaveType, null);
} else {
saveInfo = new SaveInfo.Builder(mSaveType,
getAutofillIds(structure, mRequiredSavableIds));
}
saveInfo.setFlags(mFlags);
if (mOptionalSavableIds != null) {
saveInfo.setOptionalIds(getAutofillIds(structure, mOptionalSavableIds));
}
if (mSaveDescription != null) {
saveInfo.setDescription(mSaveDescription);
}
if (mNegativeActionLabel != null) {
saveInfo.setNegativeAction(mNegativeActionLabel, mNegativeActionListener);
}
builder.setSaveInfo(saveInfo.build());
}
if (mIgnoredIds != null) {
builder.setIgnoredIds(getAutofillIds(structure, mIgnoredIds));
}
return builder
.setClientState(mExtras)
.setAuthentication(getAutofillIds(structure, mAuthenticationIds), mAuthentication,
mPresentation)
.build();
}
@Override
public String toString() {
return "CannedFillResponse: [datasets=" + mDatasets
+ ", requiredSavableIds=" + Arrays.toString(mRequiredSavableIds)
+ ", optionalSavableIds=" + Arrays.toString(mOptionalSavableIds)
+ ", mFlags=" + mFlags
+ ", saveDescription=" + mSaveDescription
+ ", hasPresentation=" + (mPresentation != null)
+ ", hasAuthentication=" + (mAuthentication != null)
+ ", authenticationIds=" + Arrays.toString(mAuthenticationIds)
+ ", ignoredIds=" + Arrays.toString(mIgnoredIds)
+ "]";
}
static class Builder {
private final List<CannedDataset> mDatasets = new ArrayList<>();
private String[] mRequiredSavableIds;
private String[] mOptionalSavableIds;
private String mSaveDescription;
public int mSaveType = -1;
private Bundle mExtras;
private RemoteViews mPresentation;
private IntentSender mAuthentication;
private String[] mAuthenticationIds;
private String[] mIgnoredIds;
private CharSequence mNegativeActionLabel;
private IntentSender mNegativeActionListener;
private int mFlags;
public Builder addDataset(CannedDataset dataset) {
mDatasets.add(dataset);
return this;
}
/**
* Sets the required savable ids based on they {@code resourceId}.
*/
public Builder setRequiredSavableIds(int type, String... ids) {
mSaveType = type;
mRequiredSavableIds = ids;
return this;
}
public Builder setFlags(int flags) {
mFlags = flags;
return this;
}
/**
* Sets the optional savable ids based on they {@code resourceId}.
*/
public Builder setOptionalSavableIds(String... ids) {
mOptionalSavableIds = ids;
return this;
}
/**
* Sets the description passed to the {@link SaveInfo}.
*/
public Builder setSaveDescription(String description) {
mSaveDescription = description;
return this;
}
/**
* Sets the extra passed to {@link
* android.service.autofill.FillResponse.Builder#setClientState(Bundle)}.
*/
public Builder setExtras(Bundle data) {
mExtras = data;
return this;
}
/**
* Sets the view to present the response in the UI.
*/
public Builder setPresentation(RemoteViews presentation) {
mPresentation = presentation;
return this;
}
/**
* Sets the authentication intent.
*/
public Builder setAuthentication(IntentSender authentication) {
mAuthentication = authentication;
return this;
}
/**
* Sets the authentication ids.
*/
public Builder setAuthenticationIds(String... ids) {
mAuthenticationIds = ids;
return this;
}
/**
* Sets the ignored fields based on resource ids.
*/
public Builder setIgnoreFields(String...ids) {
mIgnoredIds = ids;
return this;
}
/**
* Sets the negative action spec.
*/
public Builder setNegativeAction(CharSequence label,
IntentSender listener) {
mNegativeActionLabel = label;
mNegativeActionListener = listener;
return this;
}
public CannedFillResponse build() {
return new CannedFillResponse(this);
}
}
/**
* Helper class used to produce a {@link Dataset} based on expected fields that should be
* present in the {@link AssistStructure}.
*
* <p>Typical usage:
*
* <pre class="prettyprint">
* InstrumentedAutoFillService.setFillResponse(new CannedFillResponse.Builder()
* .addDataset(new CannedDataset.Builder("dataset_name")
* .setField("resource_id1", AutofillValue.forText("value1"))
* .setField("resource_id2", AutofillValue.forText("value2"))
* .build())
* .build());
* </pre class="prettyprint">
*/
static class CannedDataset {
private final Map<String, AutofillValue> mFieldValues;
private final Map<String, RemoteViews> mFieldPresentations;
private final RemoteViews mPresentation;
private final IntentSender mAuthentication;
private final String mId;
private CannedDataset(Builder builder) {
mFieldValues = builder.mFieldValues;
mFieldPresentations = builder.mFieldPresentations;
mPresentation = builder.mPresentation;
mAuthentication = builder.mAuthentication;
mId = builder.mId;
}
/**
* Creates a new dataset, replacing the field ids by the real ids from the assist structure.
*/
Dataset asDataset(AssistStructure structure) {
final Dataset.Builder builder = (mPresentation == null)
? new Dataset.Builder()
: new Dataset.Builder(mPresentation);
if (mFieldValues != null) {
for (Map.Entry<String, AutofillValue> entry : mFieldValues.entrySet()) {
final String resourceId = entry.getKey();
final ViewNode node = findNodeByResourceId(structure, resourceId);
if (node == null) {
dumpStructure("asDataset()", structure);
throw new AssertionError("No node with resource id " + resourceId);
}
final AutofillId id = node.getAutofillId();
final AutofillValue value = entry.getValue();
final RemoteViews presentation = mFieldPresentations.get(resourceId);
if (presentation != null) {
builder.setValue(id, value, presentation);
} else {
builder.setValue(id, value);
}
}
}
builder.setId(mId).setAuthentication(mAuthentication);
return builder.build();
}
@Override
public String toString() {
return "CannedDataset " + mId + " : [hasPresentation=" + (mPresentation != null)
+ ", fieldPresentations=" + (mFieldPresentations)
+ ", hasAuthentication=" + (mAuthentication != null)
+ ", fieldValuess=" + mFieldValues + "]";
}
static class Builder {
private final Map<String, AutofillValue> mFieldValues = new HashMap<>();
private final Map<String, RemoteViews> mFieldPresentations = new HashMap<>();
private RemoteViews mPresentation;
private IntentSender mAuthentication;
private String mId;
public Builder() {
}
public Builder(RemoteViews presentation) {
mPresentation = presentation;
}
/**
* Sets the canned value of a text field based on its {@code resourceId}.
*/
public Builder setField(String resourceId, String text) {
return setField(resourceId, AutofillValue.forText(text));
}
/**
* Sets the canned value of a list field based on its {@code resourceId}.
*/
public Builder setField(String resourceId, int index) {
return setField(resourceId, AutofillValue.forList(index));
}
/**
* Sets the canned value of a toggle field based on its {@code resourceId}.
*/
public Builder setField(String resourceId, boolean toggled) {
return setField(resourceId, AutofillValue.forToggle(toggled));
}
/**
* Sets the canned value of a date field based on its {@code resourceId}.
*/
public Builder setField(String resourceId, long date) {
return setField(resourceId, AutofillValue.forDate(date));
}
/**
* Sets the canned value of a date field based on its {@code resourceId}.
*/
public Builder setField(String resourceId, AutofillValue value) {
mFieldValues.put(resourceId, value);
return this;
}
/**
* Sets the canned value of a field based on its {@code resourceId}.
*/
public Builder setField(String resourceId, String text, RemoteViews presentation) {
setField(resourceId, text);
mFieldPresentations.put(resourceId, presentation);
return this;
}
/**
* Sets the view to present the response in the UI.
*/
public Builder setPresentation(RemoteViews presentation) {
mPresentation = presentation;
return this;
}
/**
* Sets the authentication intent.
*/
public Builder setAuthentication(IntentSender authentication) {
mAuthentication = authentication;
return this;
}
/**
* Sets the name.
*/
public Builder setId(String id) {
mId = id;
return this;
}
public CannedDataset build() {
return new CannedDataset(this);
}
}
}
}