Skip to content

Commit

Permalink
Disable UI on reload
Browse files Browse the repository at this point in the history
This disables the UI when a  reload is happening, such that
users make any other changes in the meantime.

Bug: b:230844184
Change-Id: I62f363c9f644a30e6007a0062b857d2d3156449c
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3641622
Commit-Queue: Sandro Maggi <sandromaggi@google.com>
Reviewed-by: Luca Hunkeler <hluca@google.com>
Cr-Commit-Position: refs/heads/main@{#1002042}
  • Loading branch information
sandromaggi authored and Chromium LUCI CQ committed May 11, 2022
1 parent efbfce6 commit 2262853
Show file tree
Hide file tree
Showing 24 changed files with 313 additions and 24 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
import static androidx.test.espresso.assertion.ViewAssertions.matches;
import static androidx.test.espresso.matcher.ViewMatchers.isDescendantOfA;
import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
import static androidx.test.espresso.matcher.ViewMatchers.isEnabled;
import static androidx.test.espresso.matcher.ViewMatchers.withClassName;
import static androidx.test.espresso.matcher.ViewMatchers.withContentDescription;
import static androidx.test.espresso.matcher.ViewMatchers.withEffectiveVisibility;
import static androidx.test.espresso.matcher.ViewMatchers.withId;
Expand All @@ -35,6 +37,7 @@

import android.support.test.InstrumentationRegistry;
import android.view.View;
import android.widget.RadioButton;
import android.widget.TextView;

import androidx.test.espresso.matcher.ViewMatchers.Visibility;
Expand Down Expand Up @@ -64,8 +67,10 @@
import org.chromium.components.autofill_assistant.AssistantStaticDependencies;
import org.chromium.components.autofill_assistant.R;
import org.chromium.components.autofill_assistant.generic_ui.AssistantValue;
import org.chromium.components.autofill_assistant.user_data.AssistantChoiceList;
import org.chromium.components.autofill_assistant.user_data.AssistantCollectUserDataCoordinator;
import org.chromium.components.autofill_assistant.user_data.AssistantCollectUserDataModel;
import org.chromium.components.autofill_assistant.user_data.AssistantCollectUserDataModel.LoginChoiceModel;
import org.chromium.components.autofill_assistant.user_data.AssistantContactField;
import org.chromium.components.autofill_assistant.user_data.AssistantLoginChoice;
import org.chromium.components.autofill_assistant.user_data.AssistantTermsAndConditionsState;
Expand Down Expand Up @@ -606,14 +611,17 @@ public void testNonEmptyPaymentRequest() throws Exception {
model.set(AssistantCollectUserDataModel.VISIBLE, true);
model.set(AssistantCollectUserDataModel.REQUEST_LOGIN_CHOICE, true);
model.set(AssistantCollectUserDataModel.SELECTED_LOGIN,
new AssistantCollectUserDataModel.LoginChoiceModel(new AssistantLoginChoice(
new LoginChoiceModel(new AssistantLoginChoice(
"id", "Guest", "Description of guest checkout", "", 0, null, "")));
});

// Non-empty sections should not display the 'add' button in their title.
onView(allOf(withId(R.id.section_title_add_button),
isDescendantOfA(is(viewHolder.mContactSection))))
.check(matches(not(isDisplayed())));
onView(allOf(withId(R.id.section_title_add_button),
isDescendantOfA(is(viewHolder.mPhoneNumberSection))))
.check(matches(not(isDisplayed())));
onView(allOf(withId(R.id.section_title_add_button),
isDescendantOfA(is(viewHolder.mPaymentSection))))
.check(matches(not(isDisplayed())));
Expand All @@ -628,6 +636,9 @@ public void testNonEmptyPaymentRequest() throws Exception {
onView(allOf(withTagValue(is(VERTICAL_EXPANDER_CHEVRON)),
isDescendantOfA(is(viewHolder.mContactSection))))
.check(matches(withEffectiveVisibility(Visibility.VISIBLE)));
onView(allOf(withTagValue(is(VERTICAL_EXPANDER_CHEVRON)),
isDescendantOfA(is(viewHolder.mPhoneNumberSection))))
.check(matches(withEffectiveVisibility(Visibility.VISIBLE)));
onView(allOf(withTagValue(is(VERTICAL_EXPANDER_CHEVRON)),
isDescendantOfA(is(viewHolder.mPaymentSection))))
.check(matches(withEffectiveVisibility(Visibility.VISIBLE)));
Expand All @@ -653,6 +664,9 @@ public void testNonEmptyPaymentRequest() throws Exception {
testContact("maggie@simpson.com", "Maggie Simpson\nmaggie@simpson.com",
viewHolder.mContactSection.getCollapsedView(), viewHolder.mContactList.getItem(0),
/* isComplete = */ true);
testContact("555 123-4567", "555 123-4567",
viewHolder.mPhoneNumberSection.getCollapsedView(),
viewHolder.mPhoneNumberList.getItem(0), /* isComplete= */ true);
testPaymentMethod("1111", "Jon Doe", "12/2050",
viewHolder.mPaymentSection.getCollapsedView(),
viewHolder.mPaymentMethodList.getItem(0));
Expand All @@ -668,6 +682,7 @@ public void testNonEmptyPaymentRequest() throws Exception {
// does not trigger a notification.
assertThat(delegate.mPaymentInstrument, is(nullValue()));
assertThat(delegate.mContact, is(nullValue()));
assertThat(delegate.mPhoneNumber, is(nullValue()));
assertThat(delegate.mShippingAddress, is(nullValue()));
assertThat(delegate.mTermsStatus, is(AssistantTermsAndConditionsState.NOT_SELECTED));
assertThat(delegate.mLoginChoice, is(nullValue()));
Expand All @@ -691,11 +706,145 @@ public void testNonEmptyPaymentRequest() throws Exception {
// Check delegate status. Setting items again will not send a notification to the delegate.
assertThat(delegate.mPaymentInstrument, is(nullValue()));
assertThat(delegate.mContact, is(nullValue()));
assertThat(delegate.mPhoneNumber, is(nullValue()));
assertThat(delegate.mShippingAddress, is(nullValue()));
assertThat(delegate.mTermsStatus, is(AssistantTermsAndConditionsState.NOT_SELECTED));
assertThat(delegate.mLoginChoice, is(nullValue()));
}

/**
* Test that disabling the CollectUserData UI properly disables all the required elements.
*/
@Test
@MediumTest
public void testDisabledState() throws Exception {
PersonalDataManager.AutofillProfile profileJohn = new PersonalDataManager.AutofillProfile(
/* guid= */ "john", "https://www.example.com", /* honorificPrefix= */ "",
"John Doe",
/* companyName= */ "", "123 Main", "California", "Los Angeles",
/* dependentLocality= */ "", "90210", /* sortingCode= */ "", "US", "555 123-4567",
"johndoe@google.com", /* languageCode= */ "");
PersonalDataManager.CreditCard creditCardJohn =
new PersonalDataManager.CreditCard(/* guid= */ "john", "https://example.com",
/* isLocal= */ true, /* isCached= */ true, "John Doe", "4111111111111111",
"1111", "12", "2050", "visa", R.drawable.visa_card,
/* billingAddressId= */ "john", /* serverId= */ "");

AssistantCollectUserDataModel model = createCollectUserDataModel();
AssistantCollectUserDataCoordinator coordinator = createCollectUserDataCoordinator(model);
AutofillAssistantCollectUserDataTestHelper.MockDelegate delegate =
new AutofillAssistantCollectUserDataTestHelper.MockDelegate();
AutofillAssistantCollectUserDataTestHelper
.ViewHolder viewHolder = TestThreadUtils.runOnUiThreadBlocking(
() -> new AutofillAssistantCollectUserDataTestHelper.ViewHolder(coordinator));

// Disable the UI.
TestThreadUtils.runOnUiThreadBlocking(
() -> model.set(AssistantCollectUserDataModel.ENABLE_UI_INTERACTIONS, false));

// Request all PR sections but leave them empty.
TestThreadUtils.runOnUiThreadBlocking(() -> {
model.set(AssistantCollectUserDataModel.WEB_CONTENTS, mTestRule.getWebContents());
model.set(AssistantCollectUserDataModel.DELEGATE, delegate);
model.set(AssistantCollectUserDataModel.REQUEST_NAME, true);
model.set(AssistantCollectUserDataModel.CONTACT_SUMMARY_DESCRIPTION_OPTIONS,
mDefaultContactSummaryOptions);
model.set(AssistantCollectUserDataModel.CONTACT_FULL_DESCRIPTION_OPTIONS,
mDefaultContactFullOptions);
model.set(AssistantCollectUserDataModel.REQUEST_PHONE_NUMBER_SEPARATELY, true);
model.set(AssistantCollectUserDataModel.REQUEST_PAYMENT, true);
model.set(AssistantCollectUserDataModel.REQUEST_SHIPPING_ADDRESS, true);
model.set(AssistantCollectUserDataModel.REQUEST_LOGIN_CHOICE, true);
});

// Empty sections should have their title add button disabled.
onView(allOf(withId(R.id.section_title_add_button),
isDescendantOfA(is(viewHolder.mContactSection))))
.check(matches(not(isEnabled())));
onView(allOf(withId(R.id.section_title_add_button),
isDescendantOfA(is(viewHolder.mPhoneNumberSection))))
.check(matches(not(isEnabled())));
onView(allOf(withId(R.id.section_title_add_button),
isDescendantOfA(is(viewHolder.mPaymentSection))))
.check(matches(not(isEnabled())));
onView(allOf(withId(R.id.section_title_add_button),
isDescendantOfA(is(viewHolder.mShippingSection))))
.check(matches(not(isEnabled())));
onView(is(viewHolder.mLoginsSection)).check(matches(not(isDisplayed())));

// Fill all PR sections.
TestThreadUtils.runOnUiThreadBlocking(() -> {
List<ContactModel> contacts = new ArrayList<>();
contacts.add(new ContactModel(createDummyContact(profileJohn)));
model.set(AssistantCollectUserDataModel.AVAILABLE_CONTACTS, contacts);
model.set(AssistantCollectUserDataModel.SELECTED_CONTACT_DETAILS, contacts.get(0));
model.set(AssistantCollectUserDataModel.AVAILABLE_PHONE_NUMBERS, contacts);
model.set(AssistantCollectUserDataModel.SELECTED_PHONE_NUMBER, contacts.get(0));
List<AddressModel> addresses = new ArrayList<>();
addresses.add(new AddressModel(createDummyAddress(profileJohn),
/* fullDescription= */ "John", /* summaryDescription= */ "John"));
model.set(AssistantCollectUserDataModel.AVAILABLE_SHIPPING_ADDRESSES, addresses);
model.set(AssistantCollectUserDataModel.SELECTED_SHIPPING_ADDRESS, addresses.get(0));
List<PaymentInstrumentModel> instruments = new ArrayList<>();
instruments.add(new PaymentInstrumentModel(
AssistantCollectUserDataModel.createAssistantPaymentInstrument(
createDummyCreditCard(creditCardJohn),
createDummyAddress(profileJohn))));
model.set(AssistantCollectUserDataModel.AVAILABLE_PAYMENT_INSTRUMENTS, instruments);
model.set(
AssistantCollectUserDataModel.SELECTED_PAYMENT_INSTRUMENT, instruments.get(0));
model.set(AssistantCollectUserDataModel.VISIBLE, true);
List<AssistantLoginChoice> logins = new ArrayList<>();
logins.add(new AssistantLoginChoice(/* identifier= */ "john", /* label= */ "John Doe",
/* sublabel= */ "", /* sublabelAccessibilityHint= */ "", /* priority= */ 0,
/* infoPopup= */ null, /* editButtonContentDescription= */ ""));
model.set(AssistantCollectUserDataModel.AVAILABLE_LOGINS, logins);
model.set(AssistantCollectUserDataModel.SELECTED_LOGIN,
new LoginChoiceModel(logins.get(0)));
});

// Radio buttons, edit icons and the add button should be disabled.
onView(allOf(withClassName(is(RadioButton.class.getName())),
isDescendantOfA(is(viewHolder.mContactSection))))
.check(matches(not(isEnabled())));
onView(allOf(withTagValue(is(AssistantChoiceList.EDIT_BUTTON_TAG)),
isDescendantOfA(is(viewHolder.mContactSection))))
.check(matches(not(isEnabled())));
onView(allOf(withTagValue(is(AssistantChoiceList.ADD_BUTTON_TAG)),
isDescendantOfA(is(viewHolder.mContactSection))))
.check(matches(not(isEnabled())));
onView(allOf(withClassName(is(RadioButton.class.getName())),
isDescendantOfA(is(viewHolder.mPhoneNumberSection))))
.check(matches(not(isEnabled())));
onView(allOf(withTagValue(is(AssistantChoiceList.EDIT_BUTTON_TAG)),
isDescendantOfA(is(viewHolder.mPhoneNumberSection))))
.check(matches(not(isEnabled())));
onView(allOf(withTagValue(is(AssistantChoiceList.ADD_BUTTON_TAG)),
isDescendantOfA(is(viewHolder.mPhoneNumberSection))))
.check(matches(not(isEnabled())));
onView(allOf(withClassName(is(RadioButton.class.getName())),
isDescendantOfA(is(viewHolder.mShippingSection))))
.check(matches(not(isEnabled())));
onView(allOf(withTagValue(is(AssistantChoiceList.EDIT_BUTTON_TAG)),
isDescendantOfA(is(viewHolder.mShippingSection))))
.check(matches(not(isEnabled())));
onView(allOf(withTagValue(is(AssistantChoiceList.ADD_BUTTON_TAG)),
isDescendantOfA(is(viewHolder.mShippingSection))))
.check(matches(not(isEnabled())));
onView(allOf(withClassName(is(RadioButton.class.getName())),
isDescendantOfA(is(viewHolder.mPaymentSection))))
.check(matches(not(isEnabled())));
onView(allOf(withTagValue(is(AssistantChoiceList.EDIT_BUTTON_TAG)),
isDescendantOfA(is(viewHolder.mPaymentSection))))
.check(matches(not(isEnabled())));
onView(allOf(withTagValue(is(AssistantChoiceList.ADD_BUTTON_TAG)),
isDescendantOfA(is(viewHolder.mPaymentSection))))
.check(matches(not(isEnabled())));
onView(allOf(withClassName(is(RadioButton.class.getName())),
isDescendantOfA(is(viewHolder.mLoginsSection))))
.check(matches(not(isEnabled())));
}

/** Tests custom summary options for the contact details section. */
@Test
@MediumTest
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,11 @@
android:layout_height="wrap_content"
android:orientation="horizontal">
<org.chromium.ui.widget.ChromeImageView
android:id="@+id/section_title_add_button_icon"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_gravity="center_vertical"
android:src="@drawable/plus"
app:tint="@macro/default_icon_color_accent1"/>
android:src="@drawable/plus"/>
<Space android:layout_width="8dp" android:layout_height="0dp"/>
<TextView android:id="@+id/section_title_add_button_label"
android:layout_width="wrap_content"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

import androidx.annotation.DrawableRes;
import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
import androidx.gridlayout.widget.GridLayout;

import org.chromium.base.ApiCompatibilityUtils;
Expand Down Expand Up @@ -49,6 +50,9 @@
* - The text for the `add' and `edit' buttons can be customized.
*/
public class AssistantChoiceList extends GridLayout {
public static final String ADD_BUTTON_TAG = "ADD_BUTTON";
public static final String EDIT_BUTTON_TAG = "EDIT_BUTTON";

/**
* Represents a single choice with a radio button, customizable content and an edit button.
*/
Expand Down Expand Up @@ -340,6 +344,11 @@ public void setOnAddButtonClickedListener(Runnable listener) {
mAddButtonListener = listener;
}

/**
* Allows to change the visibility of the 'add' button.
*
* @param visible The flag.
*/
public void setAddButtonVisible(boolean visible) {
if (mAddButton != null) {
mAddButton.setVisibility(visible ? View.VISIBLE : View.GONE);
Expand All @@ -349,6 +358,29 @@ public void setAddButtonVisible(boolean visible) {
}
}

/**
* Allows to enable / disable the UI. This changes the state of the radio and edit buttons on
* all elements.
*
* @param enabled The flag.
*/
public void setUiEnabled(boolean enabled) {
for (Item item : mItems) {
item.mCompoundButton.setEnabled(enabled);
if (item.mEditButton != null) {
item.mEditButton.setEnabled(enabled);
item.mEditButton.findViewWithTag(EDIT_BUTTON_TAG).setEnabled(enabled);
}
}
if (mAddButton != null) {
mAddButton.setEnabled(enabled);
mAddButton.findViewWithTag(ADD_BUTTON_TAG).setEnabled(enabled);
}
if (mAddButtonLabel != null) {
mAddButtonLabel.setEnabled(enabled);
}
}

/**
* Adds a view to the underlying gridlayout.
*
Expand All @@ -369,6 +401,9 @@ private void addViewInternal(View view, int index, ViewGroup.LayoutParams lp) {
private View createAddButtonIcon() {
ChromeImageView addButtonIcon = new ChromeImageView(getContext());
addButtonIcon.setImageResource(R.drawable.ic_autofill_assistant_add_circle_24dp);
ApiCompatibilityUtils.setImageTintList(addButtonIcon,
ContextCompat.getColorStateList(getContext(), R.color.blue_when_enabled_list));
addButtonIcon.setTag(ADD_BUTTON_TAG);
LinearLayout container = new LinearLayout(getContext());
container.setGravity(Gravity.CENTER);
container.setPadding(0, 0, mColumnSpacing, 0);
Expand Down Expand Up @@ -432,6 +467,7 @@ private View createEditButton(
getContext(), editButtonDrawable, R.color.default_icon_color_tint_list));
editButton.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
editButton.setLayoutParams(new ViewGroup.LayoutParams(editButtonSize, editButtonSize));
editButton.setTag(EDIT_BUTTON_TAG);

LinearLayout editButtonLayout = createMinimumTouchSizeContainer();
editButtonLayout.setTag(AssistantTagsForTesting.CHOICE_LIST_EDIT_ICON);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ public void bind(
handled = updateSectionTitles(model, propertyKey, view) || handled;
handled = updateSectionContents(model, propertyKey, view) || handled;
handled = updateSectionSelectedItem(model, propertyKey, view) || handled;
handled = updateUiState(model, propertyKey, view) || handled;
// Update section visibility/padding *after* updating editors and content.
handled = updateVisibilityAndPaddings(model, propertyKey, view) || handled;

Expand Down Expand Up @@ -450,6 +451,20 @@ private boolean updateSectionSelectedItem(
return false;
}

private boolean updateUiState(
AssistantCollectUserDataModel model, PropertyKey propertyKey, ViewHolder view) {
if (propertyKey == AssistantCollectUserDataModel.ENABLE_UI_INTERACTIONS) {
boolean enabled = model.get(AssistantCollectUserDataModel.ENABLE_UI_INTERACTIONS);
view.mContactDetailsSection.setEnabled(enabled);
view.mPhoneNumberSection.setEnabled(enabled);
view.mShippingAddressSection.setEnabled(enabled);
view.mPaymentMethodSection.setEnabled(enabled);
view.mLoginSection.setEnabled(enabled);
return true;
}
return false;
}

private boolean updateVisibilityAndPaddings(
AssistantCollectUserDataModel model, PropertyKey propertyKey, ViewHolder view) {
updateSectionVisibility(model, view);
Expand Down

0 comments on commit 2262853

Please sign in to comment.