blob: bb195f3bcaa656f45cd8edf40b018224bf8a26d3 [file] [log] [blame]
/*
* Copyright (C) 2019 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.augmented;
import static android.autofillservice.cts.augmented.AugmentedHelper.getContentDescriptionForUi;
import android.autofillservice.cts.R;
import android.content.Context;
import android.service.autofill.augmented.FillCallback;
import android.service.autofill.augmented.FillController;
import android.service.autofill.augmented.FillRequest;
import android.service.autofill.augmented.FillResponse;
import android.service.autofill.augmented.FillWindow;
import android.service.autofill.augmented.PresentationParams;
import android.service.autofill.augmented.PresentationParams.Area;
import android.util.ArrayMap;
import android.util.Log;
import android.util.Pair;
import android.view.LayoutInflater;
import android.view.autofill.AutofillId;
import android.view.autofill.AutofillValue;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.google.common.base.Preconditions;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* Helper class used to produce a {@link FillResponse}.
*/
public final class CannedAugmentedFillResponse {
private static final String TAG = CannedAugmentedFillResponse.class.getSimpleName();
private final AugmentedResponseType mResponseType;
private final Map<AutofillId, Dataset> mDatasets;
private long mDelay;
private final Dataset mOnlyDataset;
private CannedAugmentedFillResponse(@NonNull Builder builder) {
mResponseType = builder.mResponseType;
mDatasets = builder.mDatasets;
mDelay = builder.mDelay;
mOnlyDataset = builder.mOnlyDataset;
}
/**
* Constant used to pass a {@code null} response to the
* {@link FillCallback#onSuccess(FillResponse)} method.
*/
public static final CannedAugmentedFillResponse NO_AUGMENTED_RESPONSE =
new Builder(AugmentedResponseType.NULL).build();
/**
* Constant used to emulate a timeout by not calling any method on {@link FillCallback}.
*/
public static final CannedAugmentedFillResponse DO_NOT_REPLY_AUGMENTED_RESPONSE =
new Builder(AugmentedResponseType.TIMEOUT).build();
public AugmentedResponseType getResponseType() {
return mResponseType;
}
public long getDelay() {
return mDelay;
}
/**
* Creates the "real" response.
*/
public FillResponse asFillResponse(@NonNull Context context, @NonNull FillRequest request,
@NonNull FillController controller) {
final AutofillId focusedId = request.getFocusedId();
final Dataset dataset;
if (mOnlyDataset != null) {
dataset = mOnlyDataset;
} else {
dataset = mDatasets.get(focusedId);
}
if (dataset == null) {
Log.d(TAG, "no dataset for field " + focusedId);
return null;
}
Log.d(TAG, "asFillResponse: id=" + focusedId + ", dataset=" + dataset);
final PresentationParams presentationParams = request.getPresentationParams();
if (presentationParams == null) {
Log.w(TAG, "No PresentationParams");
return null;
}
final Area strip = presentationParams.getSuggestionArea();
if (strip == null) {
Log.w(TAG, "No suggestion strip");
return null;
}
final LayoutInflater inflater = LayoutInflater.from(context);
final TextView rootView = (TextView) inflater.inflate(R.layout.augmented_autofill_ui, null);
Log.d(TAG, "Setting autofill UI text to:" + dataset.mPresentation);
rootView.setText(dataset.mPresentation);
rootView.setContentDescription(getContentDescriptionForUi(focusedId));
final FillWindow fillWindow = new FillWindow();
rootView.setOnClickListener((v) -> {
Log.d(TAG, "Destroying window first");
fillWindow.destroy();
final List<Pair<AutofillId, AutofillValue>> values;
final AutofillValue onlyValue = dataset.getOnlyFieldValue();
if (onlyValue != null) {
Log.i(TAG, "Autofilling only value for " + focusedId + " as " + onlyValue);
values = new ArrayList<>(1);
values.add(new Pair<AutofillId, AutofillValue>(focusedId, onlyValue));
} else {
values = dataset.getValues();
Log.i(TAG, "Autofilling: " + AugmentedHelper.toString(values));
}
controller.autofill(values);
});
boolean ok = fillWindow.update(strip, rootView, 0);
if (!ok) {
Log.w(TAG, "FillWindow.update() failed for " + strip + " and " + rootView);
return null;
}
return new FillResponse.Builder().setFillWindow(fillWindow).build();
}
@Override
public String toString() {
return "CannedAugmentedFillResponse: [type=" + mResponseType
+ ", onlyDataset=" + mOnlyDataset
+ ", datasets=" + mDatasets
+ "]";
}
public enum AugmentedResponseType {
NORMAL,
NULL,
TIMEOUT,
}
public static final class Builder {
private final Map<AutofillId, Dataset> mDatasets = new ArrayMap<>();
private final AugmentedResponseType mResponseType;
private long mDelay;
private Dataset mOnlyDataset;
public Builder(@NonNull AugmentedResponseType type) {
mResponseType = type;
}
public Builder() {
this(AugmentedResponseType.NORMAL);
}
/**
* Sets the {@link Dataset} that will be filled when the given {@code ids} is focused and
* the UI is tapped.
*/
@NonNull
public Builder setDataset(@NonNull Dataset dataset, @NonNull AutofillId... ids) {
if (mOnlyDataset != null) {
throw new IllegalStateException("already called setOnlyDataset()");
}
for (AutofillId id : ids) {
mDatasets.put(id, dataset);
}
return this;
}
/**
* Sets the delay for onFillRequest().
*/
public Builder setDelay(long delay) {
mDelay = delay;
return this;
}
/**
* Sets the only dataset that will be returned.
*
* <p>Used when the test case doesn't know the autofill id of the focused field.
* @param dataset
*/
@NonNull
public Builder setOnlyDataset(@NonNull Dataset dataset) {
if (!mDatasets.isEmpty()) {
throw new IllegalStateException("already called setDataset()");
}
mOnlyDataset = dataset;
return this;
}
@NonNull
public CannedAugmentedFillResponse build() {
return new CannedAugmentedFillResponse(this);
}
} // CannedAugmentedFillResponse.Builder
/**
* Helper class used to define which fields will be autofilled when the user taps the Augmented
* Autofill UI.
*/
public static class Dataset {
private final Map<AutofillId, AutofillValue> mFieldValuesById;
private final String mPresentation;
private final AutofillValue mOnlyFieldValue;
private Dataset(@NonNull Builder builder) {
mFieldValuesById = builder.mFieldValuesById;
mPresentation = builder.mPresentation;
mOnlyFieldValue = builder.mOnlyFieldValue;
}
@NonNull
public List<Pair<AutofillId, AutofillValue>> getValues() {
return mFieldValuesById.entrySet().stream()
.map((entry) -> (new Pair<>(entry.getKey(), entry.getValue())))
.collect(Collectors.toList());
}
@Nullable
public AutofillValue getOnlyFieldValue() {
return mOnlyFieldValue;
}
@Override
public String toString() {
return "Dataset: [presentation=" + mPresentation
+ ", onlyField=" + mOnlyFieldValue
+ ", fields=" + mFieldValuesById
+ "]";
}
public static class Builder {
private final Map<AutofillId, AutofillValue> mFieldValuesById = new ArrayMap<>();
private final String mPresentation;
private AutofillValue mOnlyFieldValue;
public Builder(@NonNull String presentation) {
mPresentation = Preconditions.checkNotNull(presentation);
}
/**
* Sets the value that will be autofilled on the field with {@code id}.
*/
public Builder setField(@NonNull AutofillId id, @NonNull String text) {
if (mOnlyFieldValue != null) {
throw new IllegalStateException("already called setOnlyField()");
}
mFieldValuesById.put(id, AutofillValue.forText(text));
return this;
}
/**
* Sets this dataset to return the given {@code text} for the focused field.
*
* <p>Used when the test case doesn't know the autofill id of the focused field.
*/
public Builder setOnlyField(@NonNull String text) {
if (!mFieldValuesById.isEmpty()) {
throw new IllegalStateException("already called setField()");
}
mOnlyFieldValue = AutofillValue.forText(text);
return this;
}
public Dataset build() {
return new Dataset(this);
}
} // Dataset.Builder
} // Dataset
} // CannedAugmentedFillResponse