Commit 844cd90c authored by Eleonora Rocchi's avatar Eleonora Rocchi Committed by Commit Bot

[PwdCheckAndroid] Add dialog to view a compromised credential

This CL adds a dialog that shows a compromised credential. The dialog
is shown when the ¨View password¨ entry is clicked on the More menu of
a compromised credential. It also adds a test to verify the close
button of the dialog works correctly.

Screenshot in the linked bug.

Bug: 1117502, 1092444
Change-Id: Iff5784b3e78a3f58072c7e7944f85f6d93b6820a
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2363750Reviewed-by: default avatarIoana Pandele <ioanap@chromium.org>
Reviewed-by: default avatarFriedrich [CET] <fhorschig@chromium.org>
Commit-Queue: Eleonora Rocchi <erocchi@google.com>
Cr-Commit-Position: refs/heads/master@{#800107}
parent 5f2f2631
......@@ -83,10 +83,12 @@ android_library("internal_java") {
"java/src/org/chromium/chrome/browser/password_check/PasswordCheckBridge.java",
"java/src/org/chromium/chrome/browser/password_check/PasswordCheckCoordinator.java",
"java/src/org/chromium/chrome/browser/password_check/PasswordCheckDeletionDialogFragment.java",
"java/src/org/chromium/chrome/browser/password_check/PasswordCheckDialogFragment.java",
"java/src/org/chromium/chrome/browser/password_check/PasswordCheckImpl.java",
"java/src/org/chromium/chrome/browser/password_check/PasswordCheckMediator.java",
"java/src/org/chromium/chrome/browser/password_check/PasswordCheckProperties.java",
"java/src/org/chromium/chrome/browser/password_check/PasswordCheckViewBinder.java",
"java/src/org/chromium/chrome/browser/password_check/PasswordCheckViewDialogFragment.java",
"java/src/org/chromium/chrome/browser/password_check/PasswordCheckViewHolder.java",
"java/src/org/chromium/chrome/browser/password_check/helper/PasswordCheckChangePasswordHelper.java",
"java/src/org/chromium/chrome/browser/password_check/helper/PasswordCheckReauthenticationHelper.java",
......@@ -110,6 +112,7 @@ android_resources("java_resources") {
"java/res/layout/password_check_compromised_credential_item.xml",
"java/res/layout/password_check_compromised_credential_with_script_item.xml",
"java/res/layout/password_check_header_item.xml",
"java/res/layout/password_check_view_credential_dialog.xml",
"java/res/values/dimens.xml",
]
create_srcjar = false
......
<?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"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:orientation="vertical">
<TextView
android:id="@+id/view_dialog_compromised_password"
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:layout_gravity="center"
android:layout_margin="@dimen/view_dialog_compromised_password_margin"
android:textAppearance="@style/TextAppearance.TextLarge.Secondary"/>
</LinearLayout>
\ No newline at end of file
......@@ -29,4 +29,6 @@
<dimen name="compromised_credential_row_padding_start">16dp</dimen>
<dimen name="compromised_credential_row_padding_top">12dp</dimen>
<dimen name="compromised_credential_row_reason_margin_top">2dp</dimen>
<dimen name="view_dialog_compromised_password_margin">24dp</dimen>
</resources>
......@@ -5,33 +5,21 @@
package org.chromium.chrome.browser.password_check;
import android.app.Dialog;
import android.content.DialogInterface;
import android.os.Bundle;
import androidx.appcompat.app.AlertDialog;
import androidx.fragment.app.DialogFragment;
import org.chromium.chrome.browser.password_check.internal.R;
/**
* Shows the dialog that confirms the user really wants to delete a credential.
*/
public class PasswordCheckDeletionDialogFragment extends DialogFragment {
/**
* This interface combines handling the clicks on the buttons and the general dismissal of the
* dialog.
*/
interface Handler extends DialogInterface.OnClickListener {
/** Handle the dismissal of the dialog.*/
void onDismiss();
}
public class PasswordCheckDeletionDialogFragment extends PasswordCheckDialogFragment {
// This handler is used to answer the user actions on the dialog.
private final Handler mHandler;
private final String mOrigin;
PasswordCheckDeletionDialogFragment(Handler handler, String origin) {
mHandler = handler;
super(handler);
mOrigin = origin;
}
......@@ -51,19 +39,4 @@ public class PasswordCheckDeletionDialogFragment extends DialogFragment {
getString(R.string.password_check_delete_credential_dialog_body, mOrigin))
.create();
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (savedInstanceState != null) {
dismiss();
return;
}
}
@Override
public void onDismiss(DialogInterface dialog) {
super.onDismiss(dialog);
if (mHandler != null) mHandler.onDismiss();
}
}
// 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.
package org.chromium.chrome.browser.password_check;
import android.content.DialogInterface;
import android.os.Bundle;
import androidx.fragment.app.DialogFragment;
/**
* Class to create a dialog in the Password check view.
*/
public class PasswordCheckDialogFragment extends DialogFragment {
/**
* This interface combines handling the clicks on the buttons and the general dismissal of the
* dialog.
*/
interface Handler extends DialogInterface.OnClickListener {
/** Handle the dismissal of the dialog.*/
void onDismiss();
}
// This handler is used to answer the user actions on the dialog.
protected final Handler mHandler;
PasswordCheckDialogFragment(Handler handler) {
mHandler = handler;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (savedInstanceState != null) {
dismiss();
return;
}
}
@Override
public void onDismiss(DialogInterface dialog) {
super.onDismiss(dialog);
if (mHandler != null) mHandler.onDismiss();
}
}
......@@ -17,6 +17,8 @@ import static org.chromium.chrome.browser.password_check.PasswordCheckProperties
import static org.chromium.chrome.browser.password_check.PasswordCheckProperties.HeaderProperties.RESTART_BUTTON_ACTION;
import static org.chromium.chrome.browser.password_check.PasswordCheckProperties.HeaderProperties.UNKNOWN_PROGRESS;
import static org.chromium.chrome.browser.password_check.PasswordCheckProperties.ITEMS;
import static org.chromium.chrome.browser.password_check.PasswordCheckProperties.VIEW_CREDENTIAL;
import static org.chromium.chrome.browser.password_check.PasswordCheckProperties.VIEW_DIALOG_HANDLER;
import android.content.DialogInterface;
import android.util.Pair;
......@@ -186,12 +188,23 @@ class PasswordCheckMediator
return;
}
mReauthenticationHelper.reauthenticate(ReauthReason.VIEW_PASSWORD,
reauthSucceeded
-> {
// TODO(crbug.com/1117502) Add implementation to view the compromised
// credential
mReauthenticationHelper.reauthenticate(ReauthReason.VIEW_PASSWORD, reauthSucceeded -> {
if (reauthSucceeded) {
mModel.set(VIEW_CREDENTIAL, credential);
mModel.set(VIEW_DIALOG_HANDLER, new PasswordCheckViewDialogFragment.Handler() {
@Override
public void onClick(DialogInterface dialog, int which) {
mModel.set(VIEW_CREDENTIAL, null);
mModel.set(VIEW_DIALOG_HANDLER, null);
}
@Override
public void onDismiss() {
mModel.set(VIEW_DIALOG_HANDLER, null);
}
});
}
});
}
@Override
......
......@@ -27,8 +27,14 @@ class PasswordCheckProperties {
new PropertyModel.WritableObjectPropertyKey<>("deletion_confirmation_handler");
static final PropertyModel.WritableObjectPropertyKey<String> DELETION_ORIGIN =
new PropertyModel.WritableObjectPropertyKey<>("deletion_origin");
static final PropertyKey[] ALL_KEYS = {ITEMS, DELETION_CONFIRMATION_HANDLER, DELETION_ORIGIN};
static final PropertyModel.WritableObjectPropertyKey<CompromisedCredential> VIEW_CREDENTIAL =
new PropertyModel.WritableObjectPropertyKey<>("view_credential");
static final PropertyModel.WritableObjectPropertyKey<PasswordCheckViewDialogFragment.Handler>
VIEW_DIALOG_HANDLER =
new PropertyModel.WritableObjectPropertyKey<>("view_dialog_handler");
static final PropertyKey[] ALL_KEYS = {ITEMS, DELETION_CONFIRMATION_HANDLER, DELETION_ORIGIN,
VIEW_CREDENTIAL, VIEW_DIALOG_HANDLER};
static PropertyModel createDefaultModel() {
return new PropertyModel.Builder(ALL_KEYS).with(ITEMS, new ListModel<>()).build();
......
......@@ -17,6 +17,8 @@ import static org.chromium.chrome.browser.password_check.PasswordCheckProperties
import static org.chromium.chrome.browser.password_check.PasswordCheckProperties.HeaderProperties.RESTART_BUTTON_ACTION;
import static org.chromium.chrome.browser.password_check.PasswordCheckProperties.HeaderProperties.UNKNOWN_PROGRESS;
import static org.chromium.chrome.browser.password_check.PasswordCheckProperties.ITEMS;
import static org.chromium.chrome.browser.password_check.PasswordCheckProperties.VIEW_CREDENTIAL;
import static org.chromium.chrome.browser.password_check.PasswordCheckProperties.VIEW_DIALOG_HANDLER;
import android.content.Context;
import android.content.res.Resources;
......@@ -78,6 +80,12 @@ class PasswordCheckViewBinder {
model.get(DELETION_CONFIRMATION_HANDLER), model.get(DELETION_ORIGIN)));
} else if (propertyKey == DELETION_ORIGIN) {
// Binding not necessary (only used indirectly).
} else if (propertyKey == VIEW_CREDENTIAL) {
// Binding not necessary (only used indirectly).
} else if (propertyKey == VIEW_DIALOG_HANDLER) {
if (model.get(VIEW_DIALOG_HANDLER) == null) return; // Initial or onDismiss.
view.showDialogFragment(new PasswordCheckViewDialogFragment(
model.get(VIEW_DIALOG_HANDLER), model.get(VIEW_CREDENTIAL)));
} else {
assert false : "Unhandled update to property:" + propertyKey;
}
......
// 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.
package org.chromium.chrome.browser.password_check;
import android.app.Dialog;
import android.os.Bundle;
import android.text.InputType;
import android.view.View;
import android.widget.TextView;
import androidx.appcompat.app.AlertDialog;
import org.chromium.chrome.browser.password_check.internal.R;
/**
* Shows the dialog that allows the user to see the compromised credential.
*/
public class PasswordCheckViewDialogFragment extends PasswordCheckDialogFragment {
private CompromisedCredential mCredential;
PasswordCheckViewDialogFragment(Handler handler, CompromisedCredential credential) {
super(handler);
mCredential = credential;
}
/**
* Opens the dialog with the compromised credential
*/
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
View dialogContent = getActivity().getLayoutInflater().inflate(
R.layout.password_check_view_credential_dialog, null);
TextView passwordView = dialogContent.findViewById(R.id.view_dialog_compromised_password);
passwordView.setText(mCredential.getPassword());
passwordView.setInputType(InputType.TYPE_CLASS_TEXT
| InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD
| InputType.TYPE_TEXT_FLAG_MULTI_LINE);
AlertDialog viewDialog = new AlertDialog.Builder(getActivity())
.setTitle(mCredential.getDisplayOrigin())
.setNegativeButton(R.string.close, mHandler)
.setView(dialogContent)
.create();
return viewDialog;
}
}
......@@ -32,6 +32,8 @@ import static org.chromium.chrome.browser.password_check.PasswordCheckProperties
import static org.chromium.chrome.browser.password_check.PasswordCheckProperties.HeaderProperties.RESTART_BUTTON_ACTION;
import static org.chromium.chrome.browser.password_check.PasswordCheckProperties.HeaderProperties.UNKNOWN_PROGRESS;
import static org.chromium.chrome.browser.password_check.PasswordCheckProperties.ITEMS;
import static org.chromium.chrome.browser.password_check.PasswordCheckProperties.VIEW_CREDENTIAL;
import static org.chromium.chrome.browser.password_check.PasswordCheckProperties.VIEW_DIALOG_HANDLER;
import static org.chromium.chrome.browser.password_check.PasswordCheckUIStatus.ERROR_NO_PASSWORDS;
import static org.chromium.chrome.browser.password_check.PasswordCheckUIStatus.ERROR_OFFLINE;
import static org.chromium.chrome.browser.password_check.PasswordCheckUIStatus.ERROR_QUOTA_LIMIT;
......@@ -401,6 +403,20 @@ public class PasswordCheckViewTest {
assertThat(getHeaderSubtitle().getVisibility(), is(View.GONE));
}
@Test
@SmallTest
public void testGetTimestampStrings() {
Resources res = mPasswordCheckView.getContext().getResources();
assertThat(PasswordCheckViewBinder.getTimestamp(res, 10 * S_TO_MS), is("Just now"));
assertThat(PasswordCheckViewBinder.getTimestamp(res, MIN_TO_MS), is("1 minute ago"));
assertThat(PasswordCheckViewBinder.getTimestamp(res, 17 * MIN_TO_MS), is("17 minutes ago"));
assertThat(PasswordCheckViewBinder.getTimestamp(res, H_TO_MS), is("1 hour ago"));
assertThat(PasswordCheckViewBinder.getTimestamp(res, 13 * H_TO_MS), is("13 hours ago"));
assertThat(PasswordCheckViewBinder.getTimestamp(res, DAY_TO_MS), is("1 day ago"));
assertThat(PasswordCheckViewBinder.getTimestamp(res, 2 * DAY_TO_MS), is("2 days ago"));
assertThat(PasswordCheckViewBinder.getTimestamp(res, 315 * DAY_TO_MS), is("315 days ago"));
}
@Test
@MediumTest
public void testCredentialDisplaysNameOriginAndReason() {
......@@ -561,20 +577,6 @@ public class PasswordCheckViewTest {
verify(mMockHandler).onView(eq(ANA));
}
@Test
@SmallTest
public void testGetTimestampStrings() {
Resources res = mPasswordCheckView.getContext().getResources();
assertThat(PasswordCheckViewBinder.getTimestamp(res, 10 * S_TO_MS), is("Just now"));
assertThat(PasswordCheckViewBinder.getTimestamp(res, MIN_TO_MS), is("1 minute ago"));
assertThat(PasswordCheckViewBinder.getTimestamp(res, 17 * MIN_TO_MS), is("17 minutes ago"));
assertThat(PasswordCheckViewBinder.getTimestamp(res, H_TO_MS), is("1 hour ago"));
assertThat(PasswordCheckViewBinder.getTimestamp(res, 13 * H_TO_MS), is("13 hours ago"));
assertThat(PasswordCheckViewBinder.getTimestamp(res, DAY_TO_MS), is("1 day ago"));
assertThat(PasswordCheckViewBinder.getTimestamp(res, 2 * DAY_TO_MS), is("2 days ago"));
assertThat(PasswordCheckViewBinder.getTimestamp(res, 315 * DAY_TO_MS), is("315 days ago"));
}
@Test
@MediumTest
public void testConfirmingDeletionDialogTriggersHandler() {
......@@ -599,6 +601,30 @@ public class PasswordCheckViewTest {
assertThat(recordedConfirmation.get(), is(1));
}
@Test
@MediumTest
public void testCloseViewDialogTriggersHandler() {
final AtomicInteger recordedClosure = new AtomicInteger(0);
PasswordCheckDeletionDialogFragment.Handler fakeHandler =
new PasswordCheckDeletionDialogFragment.Handler() {
@Override
public void onDismiss() {}
@Override
public void onClick(DialogInterface dialogInterface, int i) {
recordedClosure.incrementAndGet();
}
};
mModel.set(VIEW_CREDENTIAL, ANA);
runOnUiThreadBlocking(() -> mModel.set(VIEW_DIALOG_HANDLER, fakeHandler));
onView(withText(R.string.close))
.inRoot(withDecorView(
not(is(mPasswordCheckView.getActivity().getWindow().getDecorView()))))
.perform(click());
assertThat(recordedClosure.get(), is(1));
}
private MVCListAdapter.ListItem buildHeader(@PasswordCheckUIStatus int status,
Integer compromisedCredentialsCount, Long checkTimestamp) {
return buildHeader(status, compromisedCredentialsCount, checkTimestamp, null);
......
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