Commit 58ebeccb authored by Ioana Pandele's avatar Ioana Pandele Committed by Commit Bot

Add toggle to the set of elements that can be displayed in an accessory sheet

This change adds a custom toggle to the accessory sheet to be displayed by the
passwords accessory sheet when password saving is disabled for the current website.

A custom toggle is needed because the view needs to display a title as well as
a subtitle that indicates whether the toggle is on or off and the toggle needs to
be vertically centered in the view.

The SwitchView is set to be unclickable so that the parent view handles the click
and the tap animates properly. The background is set to null so that there is no
ripple effect on the thumb of toggle. This is consistent to the appearance of
ChromeSwitchPreference.

Bug: 1044930
Change-Id: I6ba5fdf8b70c117729fc0163f0ca62408a6e59d8
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2095119Reviewed-by: default avatarJan Wilken Dörrie <jdoerrie@chromium.org>
Reviewed-by: default avatarFriedrich [CET] <fhorschig@chromium.org>
Commit-Queue: Ioana Pandele <ioanap@chromium.org>
Cr-Commit-Position: refs/heads/master@{#751057}
parent 905e17db
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright 2020 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. -->
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/option_toggle"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingStart="@dimen/keyboard_accessory_suggestion_padding"
android:paddingEnd="@dimen/keyboard_accessory_suggestion_padding"
android:fillViewport="true"
android:minHeight="@dimen/keyboard_accessory_suggestion_height"
android:background="?attr/selectableItemBackground">
<LinearLayout
android:orientation="vertical"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_gravity="center_vertical">
<TextView
android:id="@+id/option_toggle_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="@style/TextAppearance.TextLarge.Primary" />
<TextView
android:id="@+id/option_toggle_subtitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="@style/TextAppearance.TextLarge.Secondary" />
</LinearLayout>
<android.widget.Switch
android:id="@+id/option_toggle_switch"
android:layout_width="48dp"
android:layout_height="wrap_content"
android:layout_weight="0"
android:layout_gravity="center_vertical"
android:showText="false"
android:clickable="false"/>
</LinearLayout>
...@@ -15,6 +15,7 @@ import org.chromium.chrome.browser.ChromeActivity; ...@@ -15,6 +15,7 @@ import org.chromium.chrome.browser.ChromeActivity;
import org.chromium.chrome.browser.keyboard_accessory.data.KeyboardAccessoryData.AccessorySheetData; import org.chromium.chrome.browser.keyboard_accessory.data.KeyboardAccessoryData.AccessorySheetData;
import org.chromium.chrome.browser.keyboard_accessory.data.KeyboardAccessoryData.Action; import org.chromium.chrome.browser.keyboard_accessory.data.KeyboardAccessoryData.Action;
import org.chromium.chrome.browser.keyboard_accessory.data.KeyboardAccessoryData.FooterCommand; import org.chromium.chrome.browser.keyboard_accessory.data.KeyboardAccessoryData.FooterCommand;
import org.chromium.chrome.browser.keyboard_accessory.data.KeyboardAccessoryData.OptionToggle;
import org.chromium.chrome.browser.keyboard_accessory.data.KeyboardAccessoryData.UserInfo; import org.chromium.chrome.browser.keyboard_accessory.data.KeyboardAccessoryData.UserInfo;
import org.chromium.chrome.browser.keyboard_accessory.data.PropertyProvider; import org.chromium.chrome.browser.keyboard_accessory.data.PropertyProvider;
import org.chromium.chrome.browser.keyboard_accessory.data.UserInfoField; import org.chromium.chrome.browser.keyboard_accessory.data.UserInfoField;
...@@ -123,7 +124,10 @@ class ManualFillingComponentBridge { ...@@ -123,7 +124,10 @@ class ManualFillingComponentBridge {
@CalledByNative @CalledByNative
private void addOptionToggleToAccessorySheetData(Object objAccessorySheetData, private void addOptionToggleToAccessorySheetData(Object objAccessorySheetData,
String displayText, boolean enabled, @AccessoryAction int accessoryAction) { String displayText, boolean enabled, @AccessoryAction int accessoryAction) {
// TODO(crbug.com/1044930): Implement this. // TODO(crbug.com/1044930): Update the callback with a call to native which communicates the
// new state of the toggle.
((AccessorySheetData) objAccessorySheetData)
.setOptionToggle(new OptionToggle(displayText, enabled, e -> {}));
} }
@CalledByNative @CalledByNative
...@@ -166,10 +170,10 @@ class ManualFillingComponentBridge { ...@@ -166,10 +170,10 @@ class ManualFillingComponentBridge {
} }
@VisibleForTesting @VisibleForTesting
public static void cachePasswordSheetData( public static void cachePasswordSheetData(WebContents webContents, String[] userNames,
WebContents webContents, String[] userNames, String[] passwords) { String[] passwords, boolean originBlacklisted) {
ManualFillingComponentBridgeJni.get().cachePasswordSheetDataForTesting( ManualFillingComponentBridgeJni.get().cachePasswordSheetDataForTesting(
webContents, userNames, passwords); webContents, userNames, passwords, originBlacklisted);
} }
@VisibleForTesting @VisibleForTesting
...@@ -195,8 +199,8 @@ class ManualFillingComponentBridge { ...@@ -195,8 +199,8 @@ class ManualFillingComponentBridge {
ManualFillingComponentBridge caller, int tabType, UserInfoField userInfoField); ManualFillingComponentBridge caller, int tabType, UserInfoField userInfoField);
void onOptionSelected(long nativeManualFillingViewAndroid, void onOptionSelected(long nativeManualFillingViewAndroid,
ManualFillingComponentBridge caller, int accessoryAction); ManualFillingComponentBridge caller, int accessoryAction);
void cachePasswordSheetDataForTesting( void cachePasswordSheetDataForTesting(WebContents webContents, String[] userNames,
WebContents webContents, String[] userNames, String[] passwords); String[] passwords, boolean originBlacklisted);
void notifyFocusedFieldTypeForTesting(WebContents webContents, int focusedFieldType); void notifyFocusedFieldTypeForTesting(WebContents webContents, int focusedFieldType);
void signalAutoGenerationStatusForTesting(WebContents webContents, boolean available); void signalAutoGenerationStatusForTesting(WebContents webContents, boolean available);
void disableServerPredictionsForTesting(); void disableServerPredictionsForTesting();
......
...@@ -12,6 +12,7 @@ import org.chromium.chrome.browser.keyboard_accessory.AccessoryTabType; ...@@ -12,6 +12,7 @@ import org.chromium.chrome.browser.keyboard_accessory.AccessoryTabType;
import org.chromium.chrome.browser.keyboard_accessory.ManualFillingMetricsRecorder; import org.chromium.chrome.browser.keyboard_accessory.ManualFillingMetricsRecorder;
import org.chromium.chrome.browser.keyboard_accessory.data.KeyboardAccessoryData.AccessorySheetData; import org.chromium.chrome.browser.keyboard_accessory.data.KeyboardAccessoryData.AccessorySheetData;
import org.chromium.chrome.browser.keyboard_accessory.data.KeyboardAccessoryData.FooterCommand; import org.chromium.chrome.browser.keyboard_accessory.data.KeyboardAccessoryData.FooterCommand;
import org.chromium.chrome.browser.keyboard_accessory.data.KeyboardAccessoryData.OptionToggle;
import org.chromium.chrome.browser.keyboard_accessory.data.KeyboardAccessoryData.UserInfo; import org.chromium.chrome.browser.keyboard_accessory.data.KeyboardAccessoryData.UserInfo;
import org.chromium.chrome.browser.keyboard_accessory.data.Provider; import org.chromium.chrome.browser.keyboard_accessory.data.Provider;
import org.chromium.chrome.browser.keyboard_accessory.sheet_tabs.AccessorySheetTabModel.AccessorySheetDataPiece; import org.chromium.chrome.browser.keyboard_accessory.sheet_tabs.AccessorySheetTabModel.AccessorySheetDataPiece;
...@@ -64,6 +65,9 @@ class AccessorySheetTabMediator implements Provider.Observer<AccessorySheetData> ...@@ -64,6 +65,9 @@ class AccessorySheetTabMediator implements Provider.Observer<AccessorySheetData>
if (shouldShowTitle(accessorySheetData.getUserInfoList())) { if (shouldShowTitle(accessorySheetData.getUserInfoList())) {
items.add(new AccessorySheetDataPiece(accessorySheetData.getTitle(), Type.TITLE)); items.add(new AccessorySheetDataPiece(accessorySheetData.getTitle(), Type.TITLE));
} }
if (accessorySheetData.getOptionToggle() != null) {
items.add(createDataPieceForToggle(accessorySheetData.getOptionToggle()));
}
if (!accessorySheetData.getWarning().isEmpty()) { if (!accessorySheetData.getWarning().isEmpty()) {
items.add(new AccessorySheetDataPiece(accessorySheetData.getWarning(), Type.WARNING)); items.add(new AccessorySheetDataPiece(accessorySheetData.getWarning(), Type.WARNING));
} }
...@@ -77,6 +81,28 @@ class AccessorySheetTabMediator implements Provider.Observer<AccessorySheetData> ...@@ -77,6 +81,28 @@ class AccessorySheetTabMediator implements Provider.Observer<AccessorySheetData>
return items.toArray(new AccessorySheetDataPiece[0]); return items.toArray(new AccessorySheetDataPiece[0]);
} }
private AccessorySheetDataPiece createDataPieceForToggle(OptionToggle toggle) {
OptionToggle toggleWithAddedCallback =
new OptionToggle(toggle.getDisplayText(), toggle.isEnabled(), enabled -> {
updateOptionToggleEnabled();
toggle.getCallback().onResult(enabled);
});
return new AccessorySheetDataPiece(toggleWithAddedCallback, Type.OPTION_TOGGLE);
}
private void updateOptionToggleEnabled() {
for (int i = 0; i < mModel.size(); ++i) {
AccessorySheetDataPiece data = mModel.get(i);
if (AccessorySheetDataPiece.getType(data) == Type.OPTION_TOGGLE) {
OptionToggle toggle = (OptionToggle) data.getDataPiece();
OptionToggle updatedToggle = new OptionToggle(
toggle.getDisplayText(), !toggle.isEnabled(), toggle.getCallback());
mModel.update(i, new AccessorySheetDataPiece(updatedToggle, Type.OPTION_TOGGLE));
break;
}
}
}
private boolean shouldShowTitle(List<UserInfo> userInfoList) { private boolean shouldShowTitle(List<UserInfo> userInfoList) {
return !ChromeFeatureList.isEnabled(ChromeFeatureList.AUTOFILL_KEYBOARD_ACCESSORY) return !ChromeFeatureList.isEnabled(ChromeFeatureList.AUTOFILL_KEYBOARD_ACCESSORY)
|| userInfoList.isEmpty(); || userInfoList.isEmpty();
......
...@@ -25,7 +25,7 @@ class AccessorySheetTabModel extends ListModel<AccessorySheetTabModel.AccessoryS ...@@ -25,7 +25,7 @@ class AccessorySheetTabModel extends ListModel<AccessorySheetTabModel.AccessoryS
*/ */
static class AccessorySheetDataPiece { static class AccessorySheetDataPiece {
@IntDef({Type.TITLE, Type.PASSWORD_INFO, Type.ADDRESS_INFO, Type.CREDIT_CARD_INFO, @IntDef({Type.TITLE, Type.PASSWORD_INFO, Type.ADDRESS_INFO, Type.CREDIT_CARD_INFO,
Type.TOUCH_TO_FILL_INFO, Type.FOOTER_COMMAND, Type.WARNING}) Type.TOUCH_TO_FILL_INFO, Type.FOOTER_COMMAND, Type.WARNING, Type.OPTION_TOGGLE})
@Retention(RetentionPolicy.SOURCE) @Retention(RetentionPolicy.SOURCE)
@interface Type { @interface Type {
/** /**
...@@ -56,6 +56,12 @@ class AccessorySheetTabModel extends ListModel<AccessorySheetTabModel.AccessoryS ...@@ -56,6 +56,12 @@ class AccessorySheetTabModel extends ListModel<AccessorySheetTabModel.AccessoryS
* An optional warning to be displayed at the beginning of a sheet. * An optional warning to be displayed at the beginning of a sheet.
*/ */
int WARNING = 7; int WARNING = 7;
/**
* An optional toggle to be displayed at the beginning of a sheet. Used for example
* to allow the user to enable password saving for a website for which saving was
* previously disabled.
*/
int OPTION_TOGGLE = 8;
} }
private Object mDataPiece; private Object mDataPiece;
......
...@@ -8,6 +8,7 @@ import android.view.LayoutInflater; ...@@ -8,6 +8,7 @@ import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.LinearLayout; import android.widget.LinearLayout;
import android.widget.Switch;
import android.widget.TextView; import android.widget.TextView;
import androidx.annotation.LayoutRes; import androidx.annotation.LayoutRes;
...@@ -53,6 +54,8 @@ class AccessorySheetTabViewBinder { ...@@ -53,6 +54,8 @@ class AccessorySheetTabViewBinder {
return new TitleViewHolder(parent); return new TitleViewHolder(parent);
case AccessorySheetDataPiece.Type.FOOTER_COMMAND: case AccessorySheetDataPiece.Type.FOOTER_COMMAND:
return new FooterCommandViewHolder(parent); return new FooterCommandViewHolder(parent);
case AccessorySheetDataPiece.Type.OPTION_TOGGLE:
return new OptionToggleViewHolder(parent);
} }
assert false : "Unhandled type of data piece: " + viewType; assert false : "Unhandled type of data piece: " + viewType;
return null; return null;
...@@ -95,6 +98,33 @@ class AccessorySheetTabViewBinder { ...@@ -95,6 +98,33 @@ class AccessorySheetTabViewBinder {
} }
} }
/**
* Holds a title, subtitle which shows the state of the toggle and a toggle.
*/
static class OptionToggleViewHolder
extends ElementViewHolder<KeyboardAccessoryData.OptionToggle, LinearLayout> {
OptionToggleViewHolder(ViewGroup parent) {
super(parent, R.layout.keyboard_accessory_sheet_tab_option_toggle);
}
@Override
protected void bind(KeyboardAccessoryData.OptionToggle optionToggle, LinearLayout view) {
view.setClickable(true);
view.setOnClickListener(
v -> optionToggle.getCallback().onResult(!optionToggle.isEnabled()));
TextView titleText = view.findViewById(R.id.option_toggle_title);
titleText.setText(optionToggle.getDisplayText());
TextView subtitleText = view.findViewById(R.id.option_toggle_subtitle);
subtitleText.setText(optionToggle.isEnabled() ? R.string.text_on : R.string.text_off);
Switch switchView = view.findViewById(R.id.option_toggle_switch);
switchView.setChecked(optionToggle.isEnabled());
switchView.setBackground(null);
}
}
static void initializeView( static void initializeView(
RecyclerView view, @Nullable RecyclerView.OnScrollListener scrollListener) { RecyclerView view, @Nullable RecyclerView.OnScrollListener scrollListener) {
view.setLayoutManager( view.setLayoutManager(
......
...@@ -35,6 +35,7 @@ class PasswordAccessorySheetModernViewBinder { ...@@ -35,6 +35,7 @@ class PasswordAccessorySheetModernViewBinder {
return new AccessorySheetTabViewBinder.TitleViewHolder( return new AccessorySheetTabViewBinder.TitleViewHolder(
parent, R.layout.keyboard_accessory_sheet_tab_title); parent, R.layout.keyboard_accessory_sheet_tab_title);
case AccessorySheetDataPiece.Type.FOOTER_COMMAND: case AccessorySheetDataPiece.Type.FOOTER_COMMAND:
case AccessorySheetDataPiece.Type.OPTION_TOGGLE:
return AccessorySheetTabViewBinder.create(parent, viewType); return AccessorySheetTabViewBinder.create(parent, viewType);
} }
assert false : "Unhandled type of data piece: " + viewType; assert false : "Unhandled type of data piece: " + viewType;
......
...@@ -123,7 +123,7 @@ public class ManualFillingTestHelper { ...@@ -123,7 +123,7 @@ public class ManualFillingTestHelper {
AccessoryTabType.PASSWORDS, mSheetSuggestionsProvider); AccessoryTabType.PASSWORDS, mSheetSuggestionsProvider);
}); });
if (waitForNode) DOMUtils.waitForNonZeroNodeBounds(mWebContentsRef.get(), PASSWORD_NODE_ID); if (waitForNode) DOMUtils.waitForNonZeroNodeBounds(mWebContentsRef.get(), PASSWORD_NODE_ID);
cacheCredentials(new String[0], new String[0]); // This caches the empty state. cacheCredentials(new String[0], new String[0], false); // This caches the empty state.
} }
public void clear() { public void clear() {
...@@ -316,7 +316,7 @@ public class ManualFillingTestHelper { ...@@ -316,7 +316,7 @@ public class ManualFillingTestHelper {
*/ */
public void cacheTestCredentials() { public void cacheTestCredentials() {
cacheCredentials(new String[] {"mpark@gmail.com", "mayapark@googlemail.com"}, cacheCredentials(new String[] {"mpark@gmail.com", "mayapark@googlemail.com"},
new String[] {"TestPassword", "SomeReallyLongPassword"}); new String[] {"TestPassword", "SomeReallyLongPassword"}, false);
} }
/** /**
...@@ -325,7 +325,7 @@ public class ManualFillingTestHelper { ...@@ -325,7 +325,7 @@ public class ManualFillingTestHelper {
* @param password A {@link String} to be used as display text for a password chip. * @param password A {@link String} to be used as display text for a password chip.
*/ */
public void cacheCredentials(String username, String password) { public void cacheCredentials(String username, String password) {
cacheCredentials(new String[] {username}, new String[] {password}); cacheCredentials(new String[] {username}, new String[] {password}, false);
} }
/** /**
...@@ -333,11 +333,14 @@ public class ManualFillingTestHelper { ...@@ -333,11 +333,14 @@ public class ManualFillingTestHelper {
* controller. The controller will only refresh this cache on page load. * controller. The controller will only refresh this cache on page load.
* @param usernames {@link String}s to be used as display text for username chips. * @param usernames {@link String}s to be used as display text for username chips.
* @param passwords {@link String}s to be used as display text for password chips. * @param passwords {@link String}s to be used as display text for password chips.
* @param originBlacklisted boolean indicating whether password saving is disabled for the
* origin.
*/ */
public void cacheCredentials(String[] usernames, String[] passwords) { public void cacheCredentials(
String[] usernames, String[] passwords, boolean originBlacklisted) {
TestThreadUtils.runOnUiThreadBlocking(() -> { TestThreadUtils.runOnUiThreadBlocking(() -> {
ManualFillingComponentBridge.cachePasswordSheetData( ManualFillingComponentBridge.cachePasswordSheetData(
mActivityTestRule.getWebContents(), usernames, passwords); mActivityTestRule.getWebContents(), usernames, passwords, originBlacklisted);
}); });
} }
......
...@@ -7,7 +7,9 @@ package org.chromium.chrome.browser.keyboard_accessory.sheet_tabs; ...@@ -7,7 +7,9 @@ package org.chromium.chrome.browser.keyboard_accessory.sheet_tabs;
import static android.support.test.espresso.Espresso.onView; import static android.support.test.espresso.Espresso.onView;
import static android.support.test.espresso.action.ViewActions.click; import static android.support.test.espresso.action.ViewActions.click;
import static android.support.test.espresso.assertion.ViewAssertions.matches; import static android.support.test.espresso.assertion.ViewAssertions.matches;
import static android.support.test.espresso.matcher.ViewMatchers.isChecked;
import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed; import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
import static android.support.test.espresso.matcher.ViewMatchers.isNotChecked;
import static android.support.test.espresso.matcher.ViewMatchers.withId; import static android.support.test.espresso.matcher.ViewMatchers.withId;
import static android.support.test.espresso.matcher.ViewMatchers.withText; import static android.support.test.espresso.matcher.ViewMatchers.withText;
...@@ -30,11 +32,13 @@ import org.junit.runner.RunWith; ...@@ -30,11 +32,13 @@ import org.junit.runner.RunWith;
import org.chromium.base.test.util.CommandLineFlags; import org.chromium.base.test.util.CommandLineFlags;
import org.chromium.base.test.util.DisableIf; import org.chromium.base.test.util.DisableIf;
import org.chromium.base.test.util.RetryOnFailure; import org.chromium.base.test.util.RetryOnFailure;
import org.chromium.chrome.browser.flags.ChromeFeatureList;
import org.chromium.chrome.browser.flags.ChromeSwitches; import org.chromium.chrome.browser.flags.ChromeSwitches;
import org.chromium.chrome.browser.keyboard_accessory.ManualFillingTestHelper; import org.chromium.chrome.browser.keyboard_accessory.ManualFillingTestHelper;
import org.chromium.chrome.browser.keyboard_accessory.R; import org.chromium.chrome.browser.keyboard_accessory.R;
import org.chromium.chrome.test.ChromeJUnit4ClassRunner; import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
import org.chromium.chrome.test.ChromeTabbedActivityTestRule; import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
import org.chromium.chrome.test.util.browser.Features.EnableFeatures;
import org.chromium.content_public.browser.test.util.CriteriaHelper; import org.chromium.content_public.browser.test.util.CriteriaHelper;
import java.util.concurrent.TimeoutException; import java.util.concurrent.TimeoutException;
...@@ -138,4 +142,25 @@ public class PasswordAccessoryIntegrationTest { ...@@ -138,4 +142,25 @@ public class PasswordAccessoryIntegrationTest {
whenDisplayed(withId(R.id.passwords_sheet)); whenDisplayed(withId(R.id.passwords_sheet));
onView(withText(containsString("No saved passwords"))).check(matches(isDisplayed())); onView(withText(containsString("No saved passwords"))).check(matches(isDisplayed()));
} }
@Test
@SmallTest
@EnableFeatures(ChromeFeatureList.RECOVER_FROM_NEVER_SAVE_ANDROID)
public void testEnablesUnblacklistingToggle() throws TimeoutException {
mHelper.loadTestPage(false);
mHelper.cacheCredentials(new String[0], new String[0], true);
// Focus the field to bring up the accessory.
mHelper.focusPasswordField();
mHelper.waitForKeyboardAccessoryToBeShown();
whenDisplayed(allOf(isDisplayed(), isKeyboardAccessoryTabLayout()))
.perform(selectTabAtPosition(0));
whenDisplayed(withId(R.id.option_toggle_switch)).check(matches(isNotChecked()));
onView(withId(R.id.option_toggle_subtitle)).check(matches(withText(R.string.text_off)));
whenDisplayed(withId(R.id.option_toggle_switch)).perform(click());
onView(withId(R.id.option_toggle_switch)).check(matches(isChecked()));
onView(withId(R.id.option_toggle_subtitle)).check(matches(withText(R.string.text_on)));
}
} }
...@@ -4,17 +4,22 @@ ...@@ -4,17 +4,22 @@
package org.chromium.chrome.browser.keyboard_accessory.sheet_tabs; package org.chromium.chrome.browser.keyboard_accessory.sheet_tabs;
import static android.support.test.espresso.matcher.ViewMatchers.withText;
import static org.hamcrest.Matchers.greaterThan; import static org.hamcrest.Matchers.greaterThan;
import static org.hamcrest.Matchers.instanceOf; import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not; import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.nullValue; import static org.hamcrest.Matchers.nullValue;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThat; import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import android.support.test.filters.MediumTest; import android.support.test.filters.MediumTest;
import android.text.method.PasswordTransformationMethod; import android.text.method.PasswordTransformationMethod;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.Switch;
import android.widget.TextView; import android.widget.TextView;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
...@@ -30,6 +35,7 @@ import org.chromium.chrome.browser.flags.ChromeSwitches; ...@@ -30,6 +35,7 @@ import org.chromium.chrome.browser.flags.ChromeSwitches;
import org.chromium.chrome.browser.keyboard_accessory.AccessoryTabType; import org.chromium.chrome.browser.keyboard_accessory.AccessoryTabType;
import org.chromium.chrome.browser.keyboard_accessory.R; import org.chromium.chrome.browser.keyboard_accessory.R;
import org.chromium.chrome.browser.keyboard_accessory.data.KeyboardAccessoryData; import org.chromium.chrome.browser.keyboard_accessory.data.KeyboardAccessoryData;
import org.chromium.chrome.browser.keyboard_accessory.data.KeyboardAccessoryData.OptionToggle;
import org.chromium.chrome.browser.keyboard_accessory.data.KeyboardAccessoryData.UserInfo; import org.chromium.chrome.browser.keyboard_accessory.data.KeyboardAccessoryData.UserInfo;
import org.chromium.chrome.browser.keyboard_accessory.data.UserInfoField; import org.chromium.chrome.browser.keyboard_accessory.data.UserInfoField;
import org.chromium.chrome.browser.keyboard_accessory.sheet_component.AccessorySheetCoordinator; import org.chromium.chrome.browser.keyboard_accessory.sheet_component.AccessorySheetCoordinator;
...@@ -166,6 +172,69 @@ public class PasswordAccessorySheetModernViewTest { ...@@ -166,6 +172,69 @@ public class PasswordAccessorySheetModernViewTest {
assertThat(getUserInfoAt(1).getTitle().getText(), is("other.origin.eg")); assertThat(getUserInfoAt(1).getTitle().getText(), is("other.origin.eg"));
} }
@Test
@MediumTest
public void testOptionToggleRenderedIfNotEmpty() throws ExecutionException {
assertThat(mView.get().getChildCount(), is(0));
TestThreadUtils.runOnUiThreadBlocking(() -> {
OptionToggle toggle =
new OptionToggle("Save passwords for this site", false, result -> {});
mModel.add(new AccessorySheetDataPiece(
toggle, AccessorySheetDataPiece.Type.OPTION_TOGGLE));
});
CriteriaHelper.pollUiThread(Criteria.equals(1, () -> mView.get().getChildCount()));
View title = mView.get().findViewById(R.id.option_toggle_title);
assertThat(title, is(not(nullValue())));
assertThat(title, instanceOf(TextView.class));
assertThat(((TextView) title).getText(), is("Save passwords for this site"));
View subtitle = mView.get().findViewById(R.id.option_toggle_subtitle);
assertThat(subtitle, is(not(nullValue())));
assertThat(subtitle, instanceOf(TextView.class));
assertThat(subtitle, withText(R.string.text_off));
View switchView = mView.get().findViewById(R.id.option_toggle_switch);
assertThat(switchView, is(not(nullValue())));
assertThat(switchView, instanceOf(Switch.class));
assertFalse(((Switch) switchView).isChecked());
}
@Test
@MediumTest
public void testClickingDisabledToggleInvokesCallbackToEnable() throws ExecutionException {
AtomicReference<Boolean> toggleEnabled = new AtomicReference<>();
TestThreadUtils.runOnUiThreadBlocking(() -> {
OptionToggle toggle =
new OptionToggle("Save passwords for this site", false, toggleEnabled::set);
mModel.add(new AccessorySheetDataPiece(
toggle, AccessorySheetDataPiece.Type.OPTION_TOGGLE));
});
CriteriaHelper.pollUiThread(Criteria.equals(1, () -> mView.get().getChildCount()));
TestThreadUtils.runOnUiThreadBlocking(
mView.get().findViewById(R.id.option_toggle)::performClick);
assertTrue(toggleEnabled.get());
}
@Test
@MediumTest
public void testClickingEnabledToggleInvokesCallbackToDisable() throws ExecutionException {
AtomicReference<Boolean> toggleEnabled = new AtomicReference<>();
TestThreadUtils.runOnUiThreadBlocking(() -> {
OptionToggle toggle =
new OptionToggle("Save passwords for this site", true, toggleEnabled::set);
mModel.add(new AccessorySheetDataPiece(
toggle, AccessorySheetDataPiece.Type.OPTION_TOGGLE));
});
CriteriaHelper.pollUiThread(Criteria.equals(1, () -> mView.get().getChildCount()));
TestThreadUtils.runOnUiThreadBlocking(
mView.get().findViewById(R.id.option_toggle)::performClick);
assertFalse(toggleEnabled.get());
}
private PasswordAccessoryInfoView getUserInfoAt(int index) { private PasswordAccessoryInfoView getUserInfoAt(int index) {
assertThat(mView.get().getChildCount(), is(greaterThan(index))); assertThat(mView.get().getChildCount(), is(greaterThan(index)));
assertThat(mView.get().getChildAt(index), instanceOf(PasswordAccessoryInfoView.class)); assertThat(mView.get().getChildAt(index), instanceOf(PasswordAccessoryInfoView.class));
......
...@@ -8,6 +8,7 @@ import static org.hamcrest.CoreMatchers.is; ...@@ -8,6 +8,7 @@ import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.equalTo;
import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThat; import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
...@@ -47,12 +48,14 @@ import org.chromium.chrome.browser.keyboard_accessory.AccessoryTabType; ...@@ -47,12 +48,14 @@ import org.chromium.chrome.browser.keyboard_accessory.AccessoryTabType;
import org.chromium.chrome.browser.keyboard_accessory.data.KeyboardAccessoryData; import org.chromium.chrome.browser.keyboard_accessory.data.KeyboardAccessoryData;
import org.chromium.chrome.browser.keyboard_accessory.data.KeyboardAccessoryData.AccessorySheetData; import org.chromium.chrome.browser.keyboard_accessory.data.KeyboardAccessoryData.AccessorySheetData;
import org.chromium.chrome.browser.keyboard_accessory.data.KeyboardAccessoryData.FooterCommand; import org.chromium.chrome.browser.keyboard_accessory.data.KeyboardAccessoryData.FooterCommand;
import org.chromium.chrome.browser.keyboard_accessory.data.KeyboardAccessoryData.OptionToggle;
import org.chromium.chrome.browser.keyboard_accessory.data.KeyboardAccessoryData.UserInfo; import org.chromium.chrome.browser.keyboard_accessory.data.KeyboardAccessoryData.UserInfo;
import org.chromium.chrome.browser.keyboard_accessory.data.PropertyProvider; import org.chromium.chrome.browser.keyboard_accessory.data.PropertyProvider;
import org.chromium.chrome.browser.keyboard_accessory.data.UserInfoField; import org.chromium.chrome.browser.keyboard_accessory.data.UserInfoField;
import org.chromium.ui.modelutil.ListObservable; import org.chromium.ui.modelutil.ListObservable;
import java.util.HashMap; import java.util.HashMap;
import java.util.concurrent.atomic.AtomicReference;
/** /**
* Controller tests for the password accessory sheet. * Controller tests for the password accessory sheet.
...@@ -187,6 +190,32 @@ public class PasswordAccessorySheetControllerTest { ...@@ -187,6 +190,32 @@ public class PasswordAccessorySheetControllerTest {
assertThat(getType(mSheetDataPieces.get(1)), is(FOOTER_COMMAND)); assertThat(getType(mSheetDataPieces.get(1)), is(FOOTER_COMMAND));
} }
@Test
public void testOptionToggleCompoundCallback() {
final PropertyProvider<AccessorySheetData> testProvider = new PropertyProvider<>();
final AccessorySheetData testData =
new AccessorySheetData(AccessoryTabType.PASSWORDS, "Passwords", "");
AtomicReference<Boolean> toggleEnabled = new AtomicReference<>();
testData.setOptionToggle(
new OptionToggle("Save passwords for this site", false, toggleEnabled::set));
mCoordinator.registerDataProvider(testProvider);
testProvider.notifyObservers(testData);
// Invoke callback on the option toggle that was stored in the model. This is not the same
// as the OptionToggle passed above, because the mediator repackages it to include an
// additional method call in the callback.
OptionToggle repackagedToggle = (OptionToggle) mSheetDataPieces.get(1).getDataPiece();
// Pretend to enable the toggle like a click would do.
repackagedToggle.getCallback().onResult(true);
// Check that the original callback was called and that the model was updated with an
// enabled toggle
assertTrue(toggleEnabled.get());
assertTrue(((OptionToggle) mSheetDataPieces.get(1).getDataPiece()).isEnabled());
}
@Test @Test
public void testRecordsActionImpressionsWhenShown() { public void testRecordsActionImpressionsWhenShown() {
assertThat(getActionImpressions(AccessoryAction.MANAGE_PASSWORDS), is(0)); assertThat(getActionImpressions(AccessoryAction.MANAGE_PASSWORDS), is(0));
......
...@@ -169,6 +169,35 @@ public class KeyboardAccessoryData { ...@@ -169,6 +169,35 @@ public class KeyboardAccessoryData {
} }
} }
/**
* Represents a toggle displayed above suggestions in the accessory sheet, through which the
* user can set an option. Displayed for example when password saving is disabled for the
* current site, to allow the user to easily re-enable saving if desired.
*/
public static final class OptionToggle {
private final String mDisplayText;
private final boolean mEnabled;
private final Callback<Boolean> mCallback;
public OptionToggle(String displayText, boolean enabled, Callback<Boolean> callback) {
mDisplayText = displayText;
mEnabled = enabled;
mCallback = callback;
}
public String getDisplayText() {
return mDisplayText;
}
public boolean isEnabled() {
return mEnabled;
}
public Callback<Boolean> getCallback() {
return mCallback;
}
}
/** /**
* Represents a Profile, or a Credit Card, or the credentials for a website * Represents a Profile, or a Credit Card, or the credentials for a website
* (username + password), to be shown on the manual fallback UI. * (username + password), to be shown on the manual fallback UI.
...@@ -239,8 +268,7 @@ public class KeyboardAccessoryData { ...@@ -239,8 +268,7 @@ public class KeyboardAccessoryData {
} }
/** /**
* Returns the translated text to be shown on the UI for this footer command. This text is * Invokes the stored callback. To be called when the user taps on the footer command.
* used for accessibility.
*/ */
public void execute() { public void execute() {
mCallback.onResult(this); mCallback.onResult(this);
...@@ -255,6 +283,7 @@ public class KeyboardAccessoryData { ...@@ -255,6 +283,7 @@ public class KeyboardAccessoryData {
private final String mTitle; private final String mTitle;
private final String mWarning; private final String mWarning;
private final @AccessoryTabType int mSheetType; private final @AccessoryTabType int mSheetType;
private OptionToggle mToggle;
private final List<UserInfo> mUserInfoList = new ArrayList<>(); private final List<UserInfo> mUserInfoList = new ArrayList<>();
private final List<FooterCommand> mFooterCommands = new ArrayList<>(); private final List<FooterCommand> mFooterCommands = new ArrayList<>();
...@@ -267,12 +296,22 @@ public class KeyboardAccessoryData { ...@@ -267,12 +296,22 @@ public class KeyboardAccessoryData {
mSheetType = sheetType; mSheetType = sheetType;
mTitle = title; mTitle = title;
mWarning = warning; mWarning = warning;
mToggle = null;
} }
public @AccessoryTabType int getSheetType() { public @AccessoryTabType int getSheetType() {
return mSheetType; return mSheetType;
} }
public void setOptionToggle(OptionToggle toggle) {
mToggle = toggle;
}
@Nullable
public OptionToggle getOptionToggle() {
return mToggle;
}
/** /**
* Returns the title of the accessory sheet. This text is also used for accessibility. * Returns the title of the accessory sheet. This text is also used for accessibility.
*/ */
......
...@@ -243,6 +243,7 @@ const base::Feature* kFeaturesExposedToJava[] = { ...@@ -243,6 +243,7 @@ const base::Feature* kFeaturesExposedToJava[] = {
&password_manager::features::kGooglePasswordManager, &password_manager::features::kGooglePasswordManager,
&password_manager::features::kPasswordEditingAndroid, &password_manager::features::kPasswordEditingAndroid,
&password_manager::features::kPasswordManagerOnboardingAndroid, &password_manager::features::kPasswordManagerOnboardingAndroid,
&password_manager::features::kRecoverFromNeverSaveAndroid,
&safe_browsing::kCaptureSafetyNetId, &safe_browsing::kCaptureSafetyNetId,
&security_state::features::kMarkHttpAsFeature, &security_state::features::kMarkHttpAsFeature,
&signin::kMobileIdentityConsistency, &signin::kMobileIdentityConsistency,
......
...@@ -359,6 +359,7 @@ public abstract class ChromeFeatureList { ...@@ -359,6 +359,7 @@ public abstract class ChromeFeatureList {
public static final String QUIET_NOTIFICATION_PROMPTS = "QuietNotificationPrompts"; public static final String QUIET_NOTIFICATION_PROMPTS = "QuietNotificationPrompts";
public static final String REACHED_CODE_PROFILER = "ReachedCodeProfiler"; public static final String REACHED_CODE_PROFILER = "ReachedCodeProfiler";
public static final String READER_MODE_IN_CCT = "ReaderModeInCCT"; public static final String READER_MODE_IN_CCT = "ReaderModeInCCT";
public static final String RECOVER_FROM_NEVER_SAVE_ANDROID = "RecoverFromNeverSaveAndroid";
public static final String REMOVE_NAVIGATION_HISTORY = "RemoveNavigationHistory"; public static final String REMOVE_NAVIGATION_HISTORY = "RemoveNavigationHistory";
public static final String REORDER_BOOKMARKS = "ReorderBookmarks"; public static final String REORDER_BOOKMARKS = "ReorderBookmarks";
public static final String REVAMPED_CONTEXT_MENU = "RevampedContextMenu"; public static final String REVAMPED_CONTEXT_MENU = "RevampedContextMenu";
......
...@@ -180,7 +180,8 @@ void JNI_ManualFillingComponentBridge_CachePasswordSheetDataForTesting( ...@@ -180,7 +180,8 @@ void JNI_ManualFillingComponentBridge_CachePasswordSheetDataForTesting(
JNIEnv* env, JNIEnv* env,
const base::android::JavaParamRef<jobject>& j_web_contents, const base::android::JavaParamRef<jobject>& j_web_contents,
const base::android::JavaParamRef<jobjectArray>& j_usernames, const base::android::JavaParamRef<jobjectArray>& j_usernames,
const base::android::JavaParamRef<jobjectArray>& j_passwords) { const base::android::JavaParamRef<jobjectArray>& j_passwords,
jboolean j_blacklisted) {
content::WebContents* web_contents = content::WebContents* web_contents =
content::WebContents::FromJavaWebContents(j_web_contents); content::WebContents::FromJavaWebContents(j_web_contents);
...@@ -203,7 +204,7 @@ void JNI_ManualFillingComponentBridge_CachePasswordSheetDataForTesting( ...@@ -203,7 +204,7 @@ void JNI_ManualFillingComponentBridge_CachePasswordSheetDataForTesting(
->GetCredentialCacheForTesting() ->GetCredentialCacheForTesting()
->SaveCredentialsAndBlacklistedForOrigin( ->SaveCredentialsAndBlacklistedForOrigin(
credentials, credentials,
password_manager::CredentialCache::IsOriginBlacklisted(false), password_manager::CredentialCache::IsOriginBlacklisted(j_blacklisted),
origin); origin);
} }
......
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