Commit d87d1cda authored by Friedrich Horschig's avatar Friedrich Horschig Committed by Commit Bot

[TouchToFill][Android] Add 'Manage Passwords' footer

This CL adds a button that allows users to navigate to the password
preferences after pulling up the touch to fill sheet fully.

Screenshots in the linked bug.

Bug: 1013257
Change-Id: Ib6616f93498b4061a0e3b2b97b9f45cfc0364138
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1859982
Commit-Queue: Friedrich [CET] <fhorschig@chromium.org>
Reviewed-by: default avatarJan Wilken Dörrie <jdoerrie@chromium.org>
Reviewed-by: default avatarVasilii Sukhanov <vasilii@chromium.org>
Cr-Commit-Position: refs/heads/master@{#706910}
parent f0897e67
......@@ -612,8 +612,8 @@ ChromePasswordManagerClient::GetOrCreatePasswordAccessory() {
TouchToFillController*
ChromePasswordManagerClient::GetOrCreateTouchToFillController() {
if (!touch_to_fill_controller_) {
touch_to_fill_controller_ = std::make_unique<TouchToFillController>(
web_contents(), GetFaviconService());
touch_to_fill_controller_ =
std::make_unique<TouchToFillController>(this, GetFaviconService());
}
return touch_to_fill_controller_.get();
......
......@@ -7,6 +7,7 @@
#include <utility>
#include "base/logging.h"
#include "chrome/browser/password_manager/chrome_password_manager_client.h"
#include "chrome/browser/touch_to_fill/touch_to_fill_view.h"
#include "components/favicon/core/favicon_service.h"
#include "components/password_manager/core/browser/android_affiliation/affiliation_utils.h"
......@@ -14,7 +15,6 @@
#include "components/password_manager/core/browser/password_manager_driver.h"
#include "components/password_manager/core/browser/password_manager_metrics_util.h"
#include "components/url_formatter/elide_url.h"
#include "content/public/browser/web_contents.h"
#include "services/network/public/cpp/is_potentially_trustworthy.h"
using password_manager::CredentialPair;
......@@ -33,9 +33,9 @@ void OnImageFetched(base::OnceCallback<void(const gfx::Image&)> callback,
} // namespace
TouchToFillController::TouchToFillController(
content::WebContents* web_contents,
ChromePasswordManagerClient* password_client,
favicon::FaviconService* favicon_service)
: web_contents_(web_contents), favicon_service_(favicon_service) {}
: password_client_(password_client), favicon_service_(favicon_service) {}
TouchToFillController::~TouchToFillController() = default;
......@@ -67,6 +67,11 @@ void TouchToFillController::OnCredentialSelected(
->FillSuggestion(credential.username, credential.password);
}
void TouchToFillController::OnManagePasswordsSelected() {
password_client_->NavigateToManagePasswordsPage(
password_manager::ManagePasswordsReferrer::kTouchToFill);
}
void TouchToFillController::OnDismiss() {
if (!driver_)
return;
......@@ -75,7 +80,7 @@ void TouchToFillController::OnDismiss() {
}
gfx::NativeView TouchToFillController::GetNativeView() {
return web_contents_->GetNativeView();
return password_client_->web_contents()->GetNativeView();
}
void TouchToFillController::FetchFavicon(
......
......@@ -17,10 +17,6 @@
#include "components/favicon_base/favicon_types.h"
#include "ui/gfx/native_widget_types.h"
namespace content {
class WebContents;
} // namespace content
namespace favicon {
class FaviconService;
} // namespace favicon
......@@ -30,9 +26,11 @@ class PasswordManagerDriver;
struct CredentialPair;
} // namespace password_manager
class ChromePasswordManagerClient;
class TouchToFillController {
public:
TouchToFillController(content::WebContents* web_contents,
TouchToFillController(ChromePasswordManagerClient* web_contents,
favicon::FaviconService* favicon_service);
TouchToFillController(const TouchToFillController&) = delete;
TouchToFillController& operator=(const TouchToFillController&) = delete;
......@@ -47,6 +45,10 @@ class TouchToFillController {
// repeatedly.
void OnCredentialSelected(const password_manager::CredentialPair& credential);
// Informs the controller that the user has tapped the "Manage Passwords"
// button. This will open the password preferences.
void OnManagePasswordsSelected();
// Informs the controller that the user has dismissed the sheet. Invokes
// TouchToFillDismissed() on |driver_|. No-op if invoked repeatedly.
void OnDismiss();
......@@ -67,10 +69,8 @@ class TouchToFillController {
#endif
private:
// Weak pointer to the current WebContents. This is safe because the lifetime
// of this class is tied to ChromePasswordManagerClient, which implements
// WebContentsUserData.
content::WebContents* web_contents_ = nullptr;
// Weak pointer to the ChromePasswordManagerClient this class is tied to.
ChromePasswordManagerClient* password_client_ = nullptr;
// Driver passed to the latest invocation of Show(). Gets cleared when
// OnCredentialSelected() or OnDismissed() gets called.
......
......@@ -17,18 +17,24 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginEnd="@dimen/touch_to_fill_sheet_margin"
android:layout_marginStart="@dimen/touch_to_fill_sheet_margin"
android:importantForAccessibility="no"
app:srcCompat="@drawable/touch_to_fill_header_image" />
<org.chromium.ui.widget.TextViewWithLeading
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="@dimen/touch_to_fill_sheet_margin"
android:layout_marginStart="@dimen/touch_to_fill_sheet_margin"
android:layout_gravity="center_horizontal"
android:text="@string/touch_to_fill_sheet_title"
android:textAppearance="@style/TextAppearance.BlackHeadline" />
<org.chromium.ui.widget.TextViewWithLeading
android:id="@+id/touch_to_fill_sheet_subtitle"
android:layout_marginEnd="@dimen/touch_to_fill_sheet_margin"
android:layout_marginStart="@dimen/touch_to_fill_sheet_margin"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
......
......@@ -10,15 +10,15 @@
android:layout_height="match_parent"
android:layout_width="match_parent"
android:minHeight="340dp"
android:orientation="vertical"
android:paddingStart="16dp"
android:paddingEnd="16dp">
android:orientation="vertical">
<ImageView
android:id="@+id/drag_handlebar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginEnd="@dimen/touch_to_fill_sheet_margin"
android:layout_marginStart="@dimen/touch_to_fill_sheet_margin"
android:layout_marginTop="6dp"
android:layout_marginBottom="6dp"
android:importantForAccessibility="no"
......@@ -27,11 +27,31 @@
<android.support.v7.widget.RecyclerView
android:id="@+id/sheet_item_list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:minHeight="200dp"
android:layout_marginTop="24dp"
android:layout_height="0dp"
android:layout_weight="1"
android:layout_marginTop="16dp"
android:layout_marginEnd="@dimen/touch_to_fill_sheet_margin"
android:layout_marginStart="@dimen/touch_to_fill_sheet_margin"
android:clipToPadding="false"
android:paddingBottom="16dp"
android:divider="@null"
tools:listitem="@layout/touch_to_fill_credential_item"/>
<View style="@style/HorizontalDivider"
android:layout_height="@dimen/divider_height"
android:layout_marginBottom="8dp"
android:layout_width="match_parent"/>
<TextView
android:id="@+id/touch_to_fill_sheet_manage_passwords"
android:text="@string/manage_passwords"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="24dp"
android:paddingStart="@dimen/touch_to_fill_sheet_margin"
android:paddingEnd="@dimen/touch_to_fill_sheet_margin"
android:minHeight="48dp"
android:gravity="center_vertical|start"
android:textAppearance="@style/TextAppearance.BlackTitle1"
android:background="?android:attr/selectableItemBackground"/>
</LinearLayout>
......@@ -5,4 +5,5 @@
<resources>
<dimen name="touch_to_fill_favicon_size">24dp</dimen>
<dimen name="touch_to_fill_sheet_margin">16dp</dimen>
</resources>
......@@ -67,6 +67,12 @@ class TouchToFillBridge implements TouchToFillComponent.Delegate {
if (mNativeView != 0) TouchToFillBridgeJni.get().onDismiss(mNativeView);
}
@Override
public void onManagePasswordsSelected() {
assert mNativeView != 0 : "The native side is already dismissed";
TouchToFillBridgeJni.get().onManagePasswordsSelected(mNativeView);
}
@Override
public void onCredentialSelected(Credential credential) {
assert mNativeView != 0 : "The native side is already dismissed";
......@@ -82,6 +88,7 @@ class TouchToFillBridge implements TouchToFillComponent.Delegate {
@NativeMethods
interface Natives {
void onCredentialSelected(long nativeTouchToFillViewImpl, Credential credential);
void onManagePasswordsSelected(long nativeTouchToFillViewImpl);
void onDismiss(long nativeTouchToFillViewImpl);
void fetchFavicon(long nativeTouchToFillViewImpl, String origin, int desiredSizeInPx,
Callback<Bitmap> callback);
......
......@@ -10,6 +10,7 @@ import static org.chromium.chrome.browser.touch_to_fill.TouchToFillProperties.Cr
import static org.chromium.chrome.browser.touch_to_fill.TouchToFillProperties.CredentialProperties.ON_CLICK_LISTENER;
import static org.chromium.chrome.browser.touch_to_fill.TouchToFillProperties.HeaderProperties.FORMATTED_URL;
import static org.chromium.chrome.browser.touch_to_fill.TouchToFillProperties.HeaderProperties.ORIGIN_SECURE;
import static org.chromium.chrome.browser.touch_to_fill.TouchToFillProperties.ON_CLICK_MANAGE;
import static org.chromium.chrome.browser.touch_to_fill.TouchToFillProperties.SHEET_ITEMS;
import static org.chromium.chrome.browser.touch_to_fill.TouchToFillProperties.VISIBLE;
......@@ -53,6 +54,7 @@ class TouchToFillMediator {
void showCredentials(
String formattedUrl, boolean isOriginSecure, List<Credential> credentials) {
assert credentials != null;
mModel.set(ON_CLICK_MANAGE, this::onManagePasswordSelected);
mModel.set(VISIBLE, true);
ListModel<ListItem> sheetItems = mModel.get(SHEET_ITEMS);
......@@ -99,4 +101,9 @@ class TouchToFillMediator {
UMA_TOUCH_TO_FILL_DISMISSAL_REASON, reason, StateChangeReason.MAX_VALUE + 1);
mDelegate.onDismissed();
}
private void onManagePasswordSelected() {
mModel.set(VISIBLE, false);
mDelegate.onManagePasswordsSelected();
}
}
......@@ -29,9 +29,10 @@ class TouchToFillProperties {
new PropertyModel.ReadableObjectPropertyKey<>("sheet_items");
static final PropertyModel.ReadableObjectPropertyKey<Callback<Integer>> DISMISS_HANDLER =
new PropertyModel.ReadableObjectPropertyKey<>("dismiss_handler");
static final PropertyModel.WritableObjectPropertyKey<Runnable> ON_CLICK_MANAGE =
new PropertyModel.WritableObjectPropertyKey<>("on_click_manage");
static PropertyModel createDefaultModel(Callback<Integer> handler) {
return new PropertyModel.Builder(VISIBLE, SHEET_ITEMS, DISMISS_HANDLER)
return new PropertyModel.Builder(VISIBLE, SHEET_ITEMS, DISMISS_HANDLER, ON_CLICK_MANAGE)
.with(VISIBLE, false)
.with(SHEET_ITEMS, new ListModel<>())
.with(DISMISS_HANDLER, handler)
......
......@@ -81,6 +81,11 @@ class TouchToFillView implements BottomSheet.BottomSheetContent {
mSheetItemListView.setAdapter(adapter);
}
void setOnManagePasswordClick(Runnable runnable) {
mContentView.findViewById(R.id.touch_to_fill_sheet_manage_passwords)
.setOnClickListener((v) -> runnable.run());
}
Context getContext() {
return mContext;
}
......
......@@ -11,6 +11,7 @@ import static org.chromium.chrome.browser.touch_to_fill.TouchToFillProperties.Cr
import static org.chromium.chrome.browser.touch_to_fill.TouchToFillProperties.DISMISS_HANDLER;
import static org.chromium.chrome.browser.touch_to_fill.TouchToFillProperties.HeaderProperties.FORMATTED_URL;
import static org.chromium.chrome.browser.touch_to_fill.TouchToFillProperties.HeaderProperties.ORIGIN_SECURE;
import static org.chromium.chrome.browser.touch_to_fill.TouchToFillProperties.ON_CLICK_MANAGE;
import static org.chromium.chrome.browser.touch_to_fill.TouchToFillProperties.SHEET_ITEMS;
import static org.chromium.chrome.browser.touch_to_fill.TouchToFillProperties.VISIBLE;
import static org.chromium.chrome.browser.util.UrlUtilities.stripScheme;
......@@ -46,6 +47,8 @@ class TouchToFillViewBinder {
view.setDismissHandler(model.get(DISMISS_HANDLER));
} else if (propertyKey == VISIBLE) {
view.setVisible(model.get(VISIBLE));
} else if (propertyKey == ON_CLICK_MANAGE) {
view.setOnManagePasswordClick(model.get(ON_CLICK_MANAGE));
} else if (propertyKey == SHEET_ITEMS) {
view.setSheetItemListAdapter(
new RecyclerViewAdapter<>(new SimpleRecyclerViewMcp<>(model.get(SHEET_ITEMS),
......
......@@ -125,6 +125,9 @@
<message name="IDS_TOUCH_TO_FILL_SHEET_CLOSED" desc="Accessibility string read when the Touch To Fill bottom sheet showing a list of the user's credentials is closed.">
List of credentials to be filled on touch is closed.
</message>
<message name="IDS_MANAGE_PASSWORDS" desc="Title of the button at the end of a touch to fill sheet that will open the password preferences when tapped.">
Manage Passwords
</message>
</messages>
</release>
</grit>
......@@ -36,6 +36,11 @@ public interface TouchToFillComponent {
*/
void onDismissed();
/**
* Called when the user selects the "Manage Passwords" option.
*/
void onManagePasswordsSelected();
/**
* Called to fetch a favicon for one origin to display it in the UI.
*/
......
......@@ -17,6 +17,7 @@ import static org.chromium.chrome.browser.touch_to_fill.TouchToFillProperties.Cr
import static org.chromium.chrome.browser.touch_to_fill.TouchToFillProperties.CredentialProperties.ON_CLICK_LISTENER;
import static org.chromium.chrome.browser.touch_to_fill.TouchToFillProperties.HeaderProperties.FORMATTED_URL;
import static org.chromium.chrome.browser.touch_to_fill.TouchToFillProperties.HeaderProperties.ORIGIN_SECURE;
import static org.chromium.chrome.browser.touch_to_fill.TouchToFillProperties.ON_CLICK_MANAGE;
import static org.chromium.chrome.browser.touch_to_fill.TouchToFillProperties.SHEET_ITEMS;
import static org.chromium.chrome.browser.touch_to_fill.TouchToFillProperties.VISIBLE;
import static org.chromium.content_public.browser.test.util.CriteriaHelper.pollUiThread;
......@@ -54,6 +55,7 @@ import org.chromium.ui.modelutil.MVCListAdapter;
import org.chromium.ui.modelutil.PropertyModel;
import java.util.Collections;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* View tests for the Touch To Fill component ensure that model changes are reflected in the sheet.
......@@ -189,6 +191,27 @@ public class TouchToFillViewTest {
waitForEvent(mCredentialCallback).onResult(eq(ANA));
}
@Test
@MediumTest
public void testManagePasswordsIsClickable() {
final AtomicBoolean manageButtonClicked = new AtomicBoolean(false);
TestThreadUtils.runOnUiThreadBlocking(() -> {
mModel.set(ON_CLICK_MANAGE, () -> manageButtonClicked.set(true));
mModel.set(VISIBLE, true);
});
pollUiThread(() -> getBottomSheetState() == SheetState.PEEK);
TestThreadUtils.runOnUiThreadBlocking(
() -> { getActivity().getBottomSheet().setSheetState(SheetState.FULL, false); });
pollUiThread(() -> getBottomSheetState() == SheetState.FULL);
TextView manageButton = mTouchToFillView.getContentView().findViewById(
R.id.touch_to_fill_sheet_manage_passwords);
TouchCommon.singleClickView(manageButton);
pollUiThread(manageButtonClicked::get);
}
@Test
@MediumTest
public void testDismissesWhenHidden() {
......
......@@ -5,6 +5,7 @@
package org.chromium.chrome.browser.touch_to_fill;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue;
import static org.hamcrest.Matchers.nullValue;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThat;
......@@ -21,6 +22,7 @@ import static org.chromium.chrome.browser.touch_to_fill.TouchToFillProperties.Cr
import static org.chromium.chrome.browser.touch_to_fill.TouchToFillProperties.DISMISS_HANDLER;
import static org.chromium.chrome.browser.touch_to_fill.TouchToFillProperties.HeaderProperties.FORMATTED_URL;
import static org.chromium.chrome.browser.touch_to_fill.TouchToFillProperties.HeaderProperties.ORIGIN_SECURE;
import static org.chromium.chrome.browser.touch_to_fill.TouchToFillProperties.ON_CLICK_MANAGE;
import static org.chromium.chrome.browser.touch_to_fill.TouchToFillProperties.SHEET_ITEMS;
import static org.chromium.chrome.browser.touch_to_fill.TouchToFillProperties.VISIBLE;
......@@ -238,6 +240,15 @@ public class TouchToFillControllerTest {
is(1));
}
@Test
public void testHidesWhenSelectingManagePasswords() {
mMediator.showCredentials(TEST_URL, true, Arrays.asList(ANA, CARL, BOB));
assertThat(mModel.get(ON_CLICK_MANAGE), is(notNullValue()));
mModel.get(ON_CLICK_MANAGE).run();
verify(mMockDelegate).onManagePasswordsSelected();
assertThat(mModel.get(VISIBLE), is(false));
}
/**
* Helper to verify formatted URLs. The real implementation calls {@link UrlFormatter}. It's not
* useful to actually reimplement the formatter, so just modify the string in a trivial way.
......
......@@ -118,6 +118,10 @@ void TouchToFillViewImpl::OnCredentialSelected(
OnCredentialSelected(ConvertJavaCredential(env, credential));
}
void TouchToFillViewImpl::OnManagePasswordsSelected(JNIEnv* env) {
controller_->OnManagePasswordsSelected();
}
void TouchToFillViewImpl::OnDismiss(JNIEnv* env) {
OnDismiss();
}
......@@ -39,6 +39,7 @@ class TouchToFillViewImpl : public TouchToFillView {
void OnCredentialSelected(
JNIEnv* env,
const base::android::JavaParamRef<jobject>& credential);
void OnManagePasswordsSelected(JNIEnv* env);
void OnDismiss(JNIEnv* env);
private:
......
......@@ -212,6 +212,7 @@ GURL GetGooglePasswordManagerURL(ManagePasswordsReferrer referrer) {
case ManagePasswordsReferrer::kProfileChooser:
return "profile_chooser";
case ManagePasswordsReferrer::kPasswordsAccessorySheet:
case ManagePasswordsReferrer::kTouchToFill:
NOTREACHED();
}
......
......@@ -32,9 +32,12 @@ enum class ManagePasswordsReferrer {
kProfileChooser = 5,
// Corresponds to the passwords accessory sheet on Android, triggered by
// tapping on the key icon above in the keyboard accessory bar.
// Only used on Android
// Only used on Android.
kPasswordsAccessorySheet = 6,
kMaxValue = kPasswordsAccessorySheet,
// Corresponds to the touch to fill bottom sheet that replaces the dropdown.
// Only used on Android.
kTouchToFill = 7,
kMaxValue = kTouchToFill,
};
} // namespace password_manager
......
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