Commit 03594e1f authored by Clemens Arbesser's avatar Clemens Arbesser Committed by Commit Bot

[Autofill Assistant] Improve UI for logins. Add missing a11y strings.

This CL improves the user interface for the selection of login options. It adds an optional sublabel and an optional |learn_more| popup that displays additional information to the user when tapped.

This CL also adds several missing a11y strings for the edit buttons of user form sections.

Example video and screenshots in the bug linked below.

Bug: b/143694298
Change-Id: Ibcbc939b03e9d078d5979672ea9ca4677abd55b3
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1893209
Commit-Queue: Clemens Arbesser <arbesser@google.com>
Reviewed-by: default avatarMathias Carlen <mcarlen@chromium.org>
Cr-Commit-Position: refs/heads/master@{#713804}
parent de47eedd
......@@ -133,6 +133,7 @@ android_library("java") {
"java/src/org/chromium/chrome/browser/autofill_assistant/user_data/AssistantLoginSection.java",
"java/src/org/chromium/chrome/browser/autofill_assistant/user_data/AssistantCollectUserDataSection.java",
"java/src/org/chromium/chrome/browser/autofill_assistant/user_data/AssistantCollectUserDataNativeDelegate.java",
"java/src/org/chromium/chrome/browser/autofill_assistant/user_data/AssistantInfoPopup.java",
"java/src/org/chromium/chrome/browser/autofill_assistant/user_data/AssistantPaymentMethodSection.java",
"java/src/org/chromium/chrome/browser/autofill_assistant/user_data/AssistantCollectUserDataModel.java",
"java/src/org/chromium/chrome/browser/autofill_assistant/user_data/AssistantDateChoiceOptions.java",
......
......@@ -4,14 +4,19 @@
found in the LICENSE file. -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/login_full"
android:id="@+id/login_summary"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:gravity="center_vertical">
<TextView
android:id="@+id/username"
android:id="@+id/label"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="@style/TextAppearance.BlackBody"/>
<TextView
android:id="@+id/sublabel"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="@style/TextAppearance.BlackBody" />
</LinearLayout>
\ No newline at end of file
......@@ -6,6 +6,7 @@ package org.chromium.chrome.browser.autofill_assistant.user_data;
import android.content.Context;
import android.content.res.TypedArray;
import android.support.annotation.Nullable;
import android.support.v7.widget.GridLayout;
import android.util.AttributeSet;
import android.view.Gravity;
......@@ -19,7 +20,7 @@ import android.widget.RadioButton;
import android.widget.Space;
import android.widget.TextView;
import androidx.annotation.Nullable;
import androidx.annotation.DrawableRes;
import org.chromium.base.ApiCompatibilityUtils;
import org.chromium.base.Callback;
......@@ -167,6 +168,13 @@ public class AssistantChoiceList extends GridLayout {
addItem(view, hasEditButton, null, null);
}
public void addItem(View view, boolean hasEditButton,
@Nullable Callback<Boolean> itemSelectedListener,
@Nullable Runnable itemEditedListener) {
addItem(view, hasEditButton, itemSelectedListener, itemEditedListener,
R.drawable.ic_edit_24dp, "");
}
/**
* Adds an item to the list. Additional widgets to select and edit the item are created as
* necessary.
......@@ -176,10 +184,12 @@ public class AssistantChoiceList extends GridLayout {
* @param itemSelectedListener Optional listener which is notified when the item is selected or
* deselected.
* @param itemEditedListener Optional listener which is notified when the item is edited.
* @param editButtonDrawable The drawable to use for the optional edit button.
* @param editButtonContentDescription The content description for the optional edit button.
*/
public void addItem(View view, boolean hasEditButton,
@Nullable Callback<Boolean> itemSelectedListener,
@Nullable Runnable itemEditedListener) {
@Nullable Callback<Boolean> itemSelectedListener, @Nullable Runnable itemEditedListener,
@DrawableRes int editButtonDrawable, String editButtonContentDescription) {
CompoundButton radioButton =
mAllowMultipleChoices ? new CheckBox(getContext()) : new RadioButton(getContext());
radioButton.setPadding(0, 0, mColumnSpacing, 0);
......@@ -195,7 +205,7 @@ public class AssistantChoiceList extends GridLayout {
View editButton = null;
LinearLayout spacer = null;
if (hasEditButton) {
editButton = createEditButton();
editButton = createEditButton(editButtonDrawable, editButtonContentDescription);
editButton.setOnClickListener(unusedView -> {
if (itemEditedListener != null) {
itemEditedListener.run();
......@@ -388,17 +398,19 @@ public class AssistantChoiceList extends GridLayout {
return lp;
}
private View createEditButton() {
private View createEditButton(
@DrawableRes int editButtonDrawable, String editButtonContentDescription) {
int editButtonSize = getContext().getResources().getDimensionPixelSize(
R.dimen.autofill_assistant_choicelist_edit_button_size);
ChromeImageView editButton = new ChromeImageView(getContext());
editButton.setImageResource(R.drawable.ic_edit_24dp);
editButton.setImageResource(editButtonDrawable);
editButton.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
editButton.setLayoutParams(new ViewGroup.LayoutParams(editButtonSize, editButtonSize));
LinearLayout editButtonLayout = createMinimumTouchSizeContainer();
editButtonLayout.setGravity(Gravity.CENTER);
editButtonLayout.addView(editButton);
editButtonLayout.setContentDescription(editButtonContentDescription);
return editButtonLayout;
}
......
......@@ -4,6 +4,8 @@
package org.chromium.chrome.browser.autofill_assistant.user_data;
import android.support.annotation.Nullable;
import org.chromium.base.annotations.CalledByNative;
import org.chromium.base.annotations.JNINamespace;
import org.chromium.chrome.browser.autofill.PersonalDataManager;
......@@ -248,6 +250,12 @@ public class AssistantCollectUserDataModel extends PropertyModel {
set(DELEGATE, delegate);
}
/** Creates a simple info popup with a title and some text. */
@CalledByNative
private static AssistantInfoPopup createInfoPopup(String title, String text) {
return new AssistantInfoPopup(title, text);
}
/** Creates an empty list of login options. */
@CalledByNative
private static List<AssistantLoginChoice> createLoginChoiceList() {
......@@ -256,9 +264,11 @@ public class AssistantCollectUserDataModel extends PropertyModel {
/** Appends a login choice to {@code loginChoices}. */
@CalledByNative
private void addLoginChoice(List<AssistantLoginChoice> loginChoices, String identifier,
String label, int priority) {
loginChoices.add(new AssistantLoginChoice(identifier, label, priority));
private static void addLoginChoice(List<AssistantLoginChoice> loginChoices, String identifier,
String label, String sublabel, String sublabelAccessibilityHint, int priority,
@Nullable AssistantInfoPopup infoPopup) {
loginChoices.add(new AssistantLoginChoice(
identifier, label, sublabel, sublabelAccessibilityHint, priority, infoPopup));
}
/** Sets the list of available login choices. */
......
......@@ -12,6 +12,7 @@ import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.annotation.DrawableRes;
import androidx.annotation.Nullable;
import org.chromium.base.ApiCompatibilityUtils;
......@@ -39,7 +40,6 @@ public abstract class AssistantCollectUserDataSection<T extends EditableOption>
private final int mFullViewResId;
private final int mTitleToContentPadding;
private final List<Item> mItems;
private final boolean mCanEditItems;
protected final Context mContext;
protected T mSelectedOption;
......@@ -69,16 +69,14 @@ public abstract class AssistantCollectUserDataSection<T extends EditableOption>
* button should be created.
* @param listAddButton The string to display in the add button at the bottom of the list. Can
* be null if no add button should be created.
* @param canEditItems Whether items can be edited (i.e., show an edit button) or not.
*/
public AssistantCollectUserDataSection(Context context, ViewGroup parent, int summaryViewResId,
int fullViewResId, int titleToContentPadding, @Nullable String titleAddButton,
@Nullable String listAddButton, boolean canEditItems) {
@Nullable String listAddButton) {
mContext = context;
mFullViewResId = fullViewResId;
mItems = new ArrayList<>();
mTitleToContentPadding = titleToContentPadding;
mCanEditItems = canEditItems;
LayoutInflater inflater = LayoutInflater.from(context);
mSectionExpander = new AssistantVerticalExpander(context, null);
......@@ -291,7 +289,7 @@ public abstract class AssistantCollectUserDataSection<T extends EditableOption>
}
/**
* Creates a new |Item| from |option|.
* Creates a new item from {@code option}.
*/
private Item createItem(T option) {
View fullView = LayoutInflater.from(mContext).inflate(mFullViewResId, null);
......@@ -301,48 +299,69 @@ public abstract class AssistantCollectUserDataSection<T extends EditableOption>
}
/**
* Adds |item| to the UI.
* Adds {@code item} to the UI.
*/
private void addItem(Item item) {
mItems.add(item);
mItemsView.addItem(item.mFullView, /*hasEditButton=*/mCanEditItems, selected -> {
if (mIgnoreItemSelectedNotifications || !selected) {
return;
}
mIgnoreItemSelectedNotifications = true;
selectItem(item.mFullView, item.mOption);
mIgnoreItemSelectedNotifications = false;
if (item.mOption.isComplete()) {
// Workaround for Android bug: a layout transition may cause the newly checked
// radiobutton to not render properly.
mSectionExpander.post(() -> mSectionExpander.setExpanded(false));
} else {
createOrEditItem(item.mOption);
}
}, () -> createOrEditItem(item.mOption));
boolean canEditOption = canEditOption(item.mOption);
@DrawableRes
int editButtonDrawable = R.drawable.ic_edit_24dp;
String editButtonContentDescription = "";
if (canEditOption) {
editButtonDrawable = getEditButtonDrawable(item.mOption);
editButtonContentDescription = getEditButtonContentDescription(item.mOption);
}
mItemsView.addItem(item.mFullView, /*hasEditButton=*/canEditOption,
selected
-> {
if (mIgnoreItemSelectedNotifications || !selected) {
return;
}
mIgnoreItemSelectedNotifications = true;
selectItem(item.mFullView, item.mOption);
mIgnoreItemSelectedNotifications = false;
if (item.mOption.isComplete()) {
// Workaround for Android bug: a layout transition may cause the newly
// checked radiobutton to not render properly.
mSectionExpander.post(() -> mSectionExpander.setExpanded(false));
} else {
createOrEditItem(item.mOption);
}
},
()
-> createOrEditItem(item.mOption),
/*editButtonDrawable=*/editButtonDrawable,
/*editButtonContentDescription=*/editButtonContentDescription);
updateVisibility();
}
/**
* Asks the subclass to edit an item or create a new one (if |oldItem| is null). Subclasses
* should call |addOrUpdateItem| when they are done.
* Asks the subclass to edit an item or create a new one (if {@code oldItem} is null).
* Subclasses should call {@code addOrUpdateItem} when they are done.
* @param oldItem The item to be edited (null if a new item should be created).
*/
protected abstract void createOrEditItem(@Nullable T oldItem);
/**
* Asks the subclass to update the contents of |fullView|, which was previously created by
* |createFullView|.
* Asks the subclass to update the contents of {@code fullView}, which was previously created by
* {@code createFullView}.
*/
protected abstract void updateFullView(View fullView, T option);
/**
* Asks the subclass to update the contents of the summary view.
*/
/** Asks the subclass to update the contents of the summary view. */
protected abstract void updateSummaryView(View summaryView, T option);
/** Asks the subclass whether {@code option} should be editable or not. */
protected abstract boolean canEditOption(T option);
/** Asks the subclass which drawable to use for {@code option}. */
protected abstract @DrawableRes int getEditButtonDrawable(T option);
/** Asks the subclass for the content description of {@code option}. */
protected abstract String getEditButtonContentDescription(T option);
/**
* For convenience. Hides |view| if it is empty.
* For convenience. Hides {@code view} if it is empty.
*/
void hideIfEmpty(TextView view) {
view.setVisibility(view.length() == 0 ? View.GONE : View.VISIBLE);
......
......@@ -10,6 +10,7 @@ import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.DrawableRes;
import androidx.annotation.Nullable;
import org.chromium.chrome.autofill_assistant.R;
......@@ -35,7 +36,7 @@ public class AssistantContactDetailsSection
context.getResources().getDimensionPixelSize(
R.dimen.autofill_assistant_payment_request_title_padding),
context.getString(R.string.payments_add_contact),
context.getString(R.string.payments_add_contact), /*canEditItems=*/true);
context.getString(R.string.payments_add_contact));
setTitle(context.getString(R.string.payments_contact_details_label));
}
......@@ -104,6 +105,21 @@ public class AssistantContactDetailsSection
contactIncompleteView.setVisibility(contact.isComplete() ? View.GONE : View.VISIBLE);
}
@Override
protected boolean canEditOption(AutofillContact contact) {
return true;
}
@Override
protected @DrawableRes int getEditButtonDrawable(AutofillContact contact) {
return R.drawable.ic_edit_24dp;
}
@Override
protected String getEditButtonContentDescription(AutofillContact contact) {
return mContext.getString(R.string.payments_edit_contact_details_label);
}
/**
* The Chrome profiles have changed externally. This will rebuild the UI with the new/changed
* set of profiles, while keeping the selected item if possible.
......
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.chrome.browser.autofill_assistant.user_data;
/**
* Represents a simple info popup.
*/
public class AssistantInfoPopup {
private final String mTitle;
private final String mText;
public AssistantInfoPopup(String title, String text) {
mTitle = title;
mText = text;
}
public String getTitle() {
return mTitle;
}
public String getText() {
return mText;
}
}
......@@ -4,6 +4,8 @@
package org.chromium.chrome.browser.autofill_assistant.user_data;
import android.support.annotation.Nullable;
import org.chromium.chrome.browser.widget.prefeditor.EditableOption;
/**
......@@ -13,18 +15,36 @@ import org.chromium.chrome.browser.widget.prefeditor.EditableOption;
*/
public class AssistantLoginChoice extends EditableOption {
private final int mPriority;
private final String mSublabelAccessibilityHint;
private final @Nullable AssistantInfoPopup mInfoPopup;
/**
* @param identifier The unique identifier of this login choice.
* @param label The label to display to the user.
* @param sublabel Optional sublabel to display below the label.
* @param sublabelAccessibilityHint The a11y hint for {@code sublabel}.
* @param priority The priority of this login choice (lower value == higher priority). Can be -1
* to indicate default/auto.
* @param infoPopup Optional popup that provides further information for this login choice.
*/
public AssistantLoginChoice(String identifier, String label, int priority) {
super(identifier, label, null, null);
public AssistantLoginChoice(String identifier, String label, String sublabel,
String sublabelAccessibilityHint, int priority,
@Nullable AssistantInfoPopup infoPopup) {
super(identifier, label, sublabel, null);
mPriority = priority;
mSublabelAccessibilityHint = sublabelAccessibilityHint;
mInfoPopup = infoPopup;
}
public int getPriority() {
return mPriority;
}
public @Nullable AssistantInfoPopup getInfoPopup() {
return mInfoPopup;
}
public String getSublabelAccessibilityHint() {
return mSublabelAccessibilityHint;
}
}
......@@ -4,13 +4,20 @@
package org.chromium.chrome.browser.autofill_assistant.user_data;
import static android.view.View.IMPORTANT_FOR_ACCESSIBILITY_AUTO;
import static android.view.View.IMPORTANT_FOR_ACCESSIBILITY_NO;
import android.content.Context;
import android.support.annotation.Nullable;
import android.text.TextUtils;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.Nullable;
import androidx.annotation.DrawableRes;
import org.chromium.chrome.autofill_assistant.R;
import org.chromium.ui.UiUtils;
import java.util.List;
......@@ -19,18 +26,23 @@ import java.util.List;
*/
public class AssistantLoginSection extends AssistantCollectUserDataSection<AssistantLoginChoice> {
AssistantLoginSection(Context context, ViewGroup parent) {
super(context, parent,
org.chromium.chrome.autofill_assistant.R.layout.autofill_assistant_login,
org.chromium.chrome.autofill_assistant.R.layout.autofill_assistant_login,
super(context, parent, R.layout.autofill_assistant_login, R.layout.autofill_assistant_login,
context.getResources().getDimensionPixelSize(
org.chromium.chrome.autofill_assistant.R.dimen
.autofill_assistant_payment_request_title_padding),
/*titleAddButton=*/null, /*listAddButton=*/null, /*canEditItems=*/false);
/*titleAddButton=*/null, /*listAddButton=*/null);
}
@Override
protected void createOrEditItem(@Nullable AssistantLoginChoice oldItem) {
// Nothing to do, this section currently does not support adding or creating items.
assert oldItem != null;
assert oldItem.getInfoPopup() != null;
new UiUtils.CompatibleAlertDialogBuilder(mContext, R.style.Theme_Chromium_AlertDialog)
.setTitle(oldItem.getInfoPopup().getTitle())
.setMessage(oldItem.getInfoPopup().getText())
.setPositiveButton(R.string.close, (dialog, which) -> {})
.show();
}
@Override
......@@ -40,9 +52,35 @@ public class AssistantLoginSection extends AssistantCollectUserDataSection<Assis
@Override
protected void updateSummaryView(View summaryView, AssistantLoginChoice option) {
TextView usernameView =
summaryView.findViewById(org.chromium.chrome.autofill_assistant.R.id.username);
usernameView.setText(option.getLabel());
TextView labelView = summaryView.findViewById(R.id.label);
labelView.setText(option.getLabel());
TextView sublabelView = summaryView.findViewById(R.id.sublabel);
if (TextUtils.isEmpty(option.getSublabel())) {
sublabelView.setVisibility(View.GONE);
} else {
sublabelView.setText(option.getSublabel());
sublabelView.setContentDescription(option.getSublabelAccessibilityHint());
sublabelView.setImportantForAccessibility(
TextUtils.isEmpty(option.getSublabelAccessibilityHint())
? IMPORTANT_FOR_ACCESSIBILITY_NO
: IMPORTANT_FOR_ACCESSIBILITY_AUTO);
}
}
@Override
protected boolean canEditOption(AssistantLoginChoice choice) {
return choice.getInfoPopup() != null;
}
@Override
protected @DrawableRes int getEditButtonDrawable(AssistantLoginChoice choice) {
return R.drawable.btn_info;
}
@Override
protected String getEditButtonContentDescription(AssistantLoginChoice choice) {
// TODO(b/143862732): Send this a11y string from the backend.
return mContext.getString(R.string.learn_more);
}
/**
......
......@@ -12,6 +12,7 @@ import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.DrawableRes;
import androidx.annotation.Nullable;
import org.chromium.chrome.autofill_assistant.R;
......@@ -38,7 +39,7 @@ public class AssistantPaymentMethodSection
context.getResources().getDimensionPixelSize(
R.dimen.autofill_assistant_payment_request_payment_method_title_padding),
context.getString(R.string.payments_add_card),
context.getString(R.string.payments_add_card), /*canEditItems=*/true);
context.getString(R.string.payments_add_card));
setTitle(context.getString(R.string.payments_method_of_payment_label));
}
......@@ -120,6 +121,21 @@ public class AssistantPaymentMethodSection
hideIfEmpty(methodIncompleteView);
}
@Override
protected boolean canEditOption(AutofillPaymentInstrument method) {
return true;
}
@Override
protected @DrawableRes int getEditButtonDrawable(AutofillPaymentInstrument method) {
return R.drawable.ic_edit_24dp;
}
@Override
protected String getEditButtonContentDescription(AutofillPaymentInstrument method) {
return mContext.getString(R.string.autofill_edit_credit_card);
}
void onProfilesChanged(List<PersonalDataManager.AutofillProfile> profiles) {
// TODO(crbug.com/806868): replace suggested billing addresses (remove if necessary).
for (PersonalDataManager.AutofillProfile profile : profiles) {
......
......@@ -10,6 +10,7 @@ import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.DrawableRes;
import androidx.annotation.Nullable;
import org.chromium.chrome.autofill_assistant.R;
......@@ -34,7 +35,7 @@ public class AssistantShippingAddressSection
context.getResources().getDimensionPixelSize(
R.dimen.autofill_assistant_payment_request_title_padding),
context.getString(R.string.payments_add_address),
context.getString(R.string.payments_add_address), /*canEditItems=*/true);
context.getString(R.string.payments_add_address));
setTitle(context.getString(R.string.payments_shipping_address_label));
}
......@@ -93,6 +94,21 @@ public class AssistantShippingAddressSection
methodIncompleteView.setVisibility(address.isComplete() ? View.GONE : View.VISIBLE);
}
@Override
protected boolean canEditOption(AutofillAddress address) {
return true;
}
@Override
protected @DrawableRes int getEditButtonDrawable(AutofillAddress address) {
return R.drawable.ic_edit_24dp;
}
@Override
protected String getEditButtonContentDescription(AutofillAddress address) {
return mContext.getString(R.string.payments_edit_address);
}
void onProfilesChanged(List<PersonalDataManager.AutofillProfile> profiles) {
if (mIgnoreProfileChangeNotifications) {
return;
......
......@@ -54,6 +54,7 @@ import org.chromium.chrome.browser.autofill_assistant.user_data.AssistantCollect
import org.chromium.chrome.browser.autofill_assistant.user_data.AssistantCollectUserDataModel;
import org.chromium.chrome.browser.autofill_assistant.user_data.AssistantDateChoiceOptions;
import org.chromium.chrome.browser.autofill_assistant.user_data.AssistantDateTime;
import org.chromium.chrome.browser.autofill_assistant.user_data.AssistantInfoPopup;
import org.chromium.chrome.browser.autofill_assistant.user_data.AssistantLoginChoice;
import org.chromium.chrome.browser.autofill_assistant.user_data.AssistantTermsAndConditionsState;
import org.chromium.chrome.browser.autofill_assistant.user_data.additional_sections.AssistantAdditionalSectionFactory;
......@@ -503,7 +504,8 @@ public class AutofillAssistantCollectUserDataUiTest {
model.set(AssistantCollectUserDataModel.VISIBLE, true);
model.set(AssistantCollectUserDataModel.REQUEST_LOGIN_CHOICE, true);
model.set(AssistantCollectUserDataModel.AVAILABLE_LOGINS,
Collections.singletonList(new AssistantLoginChoice("id", "Guest", 0)));
Collections.singletonList(new AssistantLoginChoice(
"id", "Guest", "Description of guest checkout", "", 0, null)));
});
/* Non-empty sections should not display the 'add' button in their title. */
......@@ -554,8 +556,8 @@ public class AutofillAssistantCollectUserDataUiTest {
"Acme Inc., 123 Main, 90210 Los Angeles, California, Uzbekistan",
viewHolder.mShippingSection.getCollapsedView(),
viewHolder.mShippingAddressList.getItem(0));
testLoginDetails("Guest", viewHolder.mLoginsSection.getCollapsedView(),
viewHolder.mLoginList.getItem(0));
testLoginDetails("Guest", "Description of guest checkout",
viewHolder.mLoginsSection.getCollapsedView(), viewHolder.mLoginList.getItem(0));
/* Check delegate status. */
assertThat(delegate.mPaymentMethod.getCard().getNumber(), is("4111111111111111"));
......@@ -1101,6 +1103,34 @@ public class AutofillAssistantCollectUserDataUiTest {
assertThat(delegate.mAdditionalValues.get("loyalty"), is("L-394834"));
}
@Test
@MediumTest
public void testLoginSectionInfoPopup() throws Exception {
AssistantCollectUserDataModel model = new AssistantCollectUserDataModel();
createCollectUserDataCoordinator(model);
AutofillAssistantCollectUserDataTestHelper.MockDelegate delegate =
new AutofillAssistantCollectUserDataTestHelper.MockDelegate();
AssistantInfoPopup infoPopup =
new AssistantInfoPopup("Guest checkout", "Text explanation.");
TestThreadUtils.runOnUiThreadBlocking(() -> {
model.set(AssistantCollectUserDataModel.DELEGATE, delegate);
model.set(AssistantCollectUserDataModel.VISIBLE, true);
model.set(AssistantCollectUserDataModel.LOGIN_SECTION_TITLE, "Login options");
model.set(AssistantCollectUserDataModel.REQUEST_LOGIN_CHOICE, true);
model.set(AssistantCollectUserDataModel.AVAILABLE_LOGINS,
Collections.singletonList(new AssistantLoginChoice(
"id", "Guest checkout", "", "", 0, infoPopup)));
});
onView(withText("Login options")).perform(click());
onView(withContentDescription(mTestRule.getActivity().getString(R.string.learn_more)))
.perform(click());
onView(withText("Guest checkout")).check(matches(isDisplayed()));
onView(withText("Text explanation.")).check(matches(isDisplayed()));
onView(withText(mTestRule.getActivity().getString(R.string.close))).perform(click());
}
private View getPaymentSummaryErrorView(ViewHolder viewHolder) {
return viewHolder.mPaymentSection.findViewById(R.id.payment_method_summary)
.findViewById(R.id.incomplete_error);
......@@ -1157,10 +1187,13 @@ public class AutofillAssistantCollectUserDataUiTest {
.check(matches(not(isDisplayed())));
}
private void testLoginDetails(String expectedLabel, View summaryView, View fullView) {
onView(allOf(withId(R.id.username), isDescendantOfA(is(summaryView))))
private void testLoginDetails(
String expectedLabel, String expectedSublabel, View summaryView, View fullView) {
onView(allOf(withId(R.id.label), isDescendantOfA(is(summaryView))))
.check(matches(withText(expectedLabel)));
onView(allOf(withId(R.id.username), isDescendantOfA(is(fullView))))
onView(allOf(withId(R.id.label), isDescendantOfA(is(fullView))))
.check(matches(withText(expectedLabel)));
onView(allOf(withId(R.id.sublabel), isDescendantOfA(is(fullView))))
.check(matches(withText(expectedSublabel)));
}
}
......@@ -116,6 +116,33 @@ base::Optional<int> CreateJavaColor(JNIEnv* env,
env, base::android::ConvertUTF8ToJavaString(env, color_string));
}
// Creates the Java equivalent to |login_choices|.
base::android::ScopedJavaLocalRef<jobject> CreateJavaLoginChoiceList(
JNIEnv* env,
const std::vector<LoginChoice>& login_choices) {
auto jlist = Java_AssistantCollectUserDataModel_createLoginChoiceList(env);
for (const auto& login_choice : login_choices) {
base::android::ScopedJavaLocalRef<jobject> jinfo_popup = nullptr;
if (login_choice.info_popup.has_value()) {
jinfo_popup = Java_AssistantCollectUserDataModel_createInfoPopup(
env,
base::android::ConvertUTF8ToJavaString(
env, login_choice.info_popup->title()),
base::android::ConvertUTF8ToJavaString(
env, login_choice.info_popup->text()));
}
Java_AssistantCollectUserDataModel_addLoginChoice(
env, jlist,
base::android::ConvertUTF8ToJavaString(env, login_choice.identifier),
base::android::ConvertUTF8ToJavaString(env, login_choice.label),
base::android::ConvertUTF8ToJavaString(env, login_choice.sublabel),
base::android::ConvertUTF8ToJavaString(
env, login_choice.sublabel_accessibility_hint),
login_choice.preselect_priority, jinfo_popup);
}
return jlist;
}
// Creates the java equivalent to the text inputs specified in |section|.
base::android::ScopedJavaLocalRef<jobject> CreateJavaTextInputsForSection(
JNIEnv* env,
......@@ -888,14 +915,8 @@ void UiControllerAndroid::OnCollectUserDataOptionsChanged(
base::android::ToJavaArrayOfStrings(
env, collect_user_data_options->supported_basic_card_networks));
if (collect_user_data_options->request_login_choice) {
auto jlist = Java_AssistantCollectUserDataModel_createLoginChoiceList(env);
for (const auto& login_choice : collect_user_data_options->login_choices) {
Java_AssistantCollectUserDataModel_addLoginChoice(
env, jmodel, jlist,
base::android::ConvertUTF8ToJavaString(env, login_choice.identifier),
base::android::ConvertUTF8ToJavaString(env, login_choice.label),
login_choice.preselect_priority);
}
auto jlist = CreateJavaLoginChoiceList(
env, collect_user_data_options->login_choices);
Java_AssistantCollectUserDataModel_setLoginChoices(env, jmodel, jlist);
}
Java_AssistantCollectUserDataModel_setRequestDateRange(
......
......@@ -309,15 +309,19 @@ void CollectUserDataAction::OnGetLogins(
std::unique_ptr<CollectUserDataOptions> collect_user_data_options,
std::vector<WebsiteLoginFetcher::Login> logins) {
for (const auto& login : logins) {
LoginChoice choice = {
base::NumberToString(collect_user_data_options->login_choices.size()),
login.username, login_option.preselection_priority()};
collect_user_data_options->login_choices.emplace_back(std::move(choice));
auto identifier =
base::NumberToString(collect_user_data_options->login_choices.size());
collect_user_data_options->login_choices.emplace_back(
identifier, login.username, login_option.sublabel(),
login_option.sublabel_accessibility_hint(),
login_option.preselection_priority(),
login_option.has_info_popup()
? base::make_optional(login_option.info_popup())
: base::nullopt);
login_details_map_.emplace(
choice.identifier,
std::make_unique<LoginDetails>(
login_option.choose_automatically_if_no_other_options(),
login_option.payload(), login));
identifier, std::make_unique<LoginDetails>(
login_option.choose_automatically_if_no_other_options(),
login_option.payload(), login));
}
ShowToUser(std::move(collect_user_data_options));
}
......@@ -577,9 +581,14 @@ CollectUserDataAction::CreateOptionsFromProto() {
base::NumberToString(
collect_user_data_options->login_choices.size()),
login_option.custom().label(),
login_option.sublabel(),
login_option.sublabel_accessibility_hint(),
login_option.has_preselection_priority()
? login_option.preselection_priority()
: -1};
: -1,
login_option.has_info_popup()
? base::make_optional(login_option.info_popup())
: base::nullopt};
collect_user_data_options->login_choices.emplace_back(
std::move(choice));
login_details_map_.emplace(
......
......@@ -1206,6 +1206,14 @@ message ContactDetailsProto {
optional bool request_payer_phone = 4;
}
// A generic read-only popup message.
message InfoPopupProto {
// The title of the popup window.
optional string title = 1;
// The text of the popup window.
optional string text = 2;
}
message LoginDetailsProto {
// A custom login option which will be handled by the backend, e.g.,
// 'Guest checkout' or 'Log in with Google'.
......@@ -1218,6 +1226,13 @@ message LoginDetailsProto {
message LoginOptionPasswordManagerProto {}
message LoginOptionProto {
// If set, an info icon will be shown that displays a popup when tapped.
optional InfoPopupProto info_popup = 6;
// The optional sublabel to display beneath the label.
optional string sublabel = 7;
optional string sublabel_accessibility_hint = 8;
// If the option was chosen, this payload will be returned to the server.
optional bytes payload = 1;
......
......@@ -10,10 +10,19 @@
namespace autofill_assistant {
LoginChoice::LoginChoice(const std::string& id,
const std::string& text,
int priority)
: identifier(id), label(text), preselect_priority(priority) {}
LoginChoice::LoginChoice(const std::string& _identifier,
const std::string& _label,
const std::string& _sublabel,
const std::string& _sublabel_accessibility_hint,
int _preselect_priority,
const base::Optional<InfoPopupProto>& _info_popup)
: identifier(_identifier),
label(_label),
sublabel(_sublabel),
sublabel_accessibility_hint(_sublabel_accessibility_hint),
preselect_priority(_preselect_priority),
info_popup(_info_popup) {}
LoginChoice::LoginChoice(const LoginChoice& another) = default;
LoginChoice::~LoginChoice() = default;
UserData::UserData() = default;
......
......@@ -11,6 +11,7 @@
#include <vector>
#include "base/callback.h"
#include "base/optional.h"
#include "components/autofill_assistant/browser/service.pb.h"
#include "components/autofill_assistant/browser/user_action.h"
......@@ -38,15 +39,27 @@ enum TextInputType { INPUT_TEXT = 0, INPUT_ALPHANUMERIC = 1 };
// Represents a concrete login choice in the UI, e.g., 'Guest checkout' or
// a particular Chrome PWM login account.
struct LoginChoice {
LoginChoice(const std::string& id, const std::string& text, int priority);
LoginChoice(const std::string& id,
const std::string& label,
const std::string& sublabel,
const std::string& sublabel_accessibility_hint,
int priority,
const base::Optional<InfoPopupProto>& info_popup);
LoginChoice(const LoginChoice& another);
~LoginChoice();
// Uniquely identifies this login choice.
std::string identifier;
// The label to display to the user.
std::string label;
// The sublabel to display to the user.
std::string sublabel;
// The a11y hint for |sublabel|.
std::string sublabel_accessibility_hint;
// The priority to pre-select this choice (-1 == not set/automatic).
int preselect_priority = -1;
// The popup to show to provide more information about this login choice.
base::Optional<InfoPopupProto> info_popup;
};
// Struct for holding the user data.
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment