| /* |
| * 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); |
| } |
| } |
| } |
| } |