Commit b643d8d6 authored by Eleonora Rocchi's avatar Eleonora Rocchi Committed by Commit Bot

[PwdCheckAndroid] Add text to the status header in Check passwords.

This CL adds the logic to fill the text describing the status of the
last/current check. It also adds tests to verify the correct messages
are displayed.

IDS_PASSWORD_CHECK_STATUS_MESSAGE_ERROR_OFFLINE.png.sha1 image:
https://storage.cloud.google.com/chromium-translation-screenshots/fcb59be745a5ffbb21fcd8f78f1608f6817910d7

IDS_PASSWORD_CHECK_STATUS_MESSAGE_RUNNING.png.sha1 image:
https://storage.cloud.google.com/chromium-translation-screenshots/7ed062035e44451197ebccfc7a52e9297e669c40

IDS_PASSWORD_CHECK_STATUS_MESSAGE_SUCCESS_NO_LEAKS.png.sha1 image:
https://storage.cloud.google.com/chromium-translation-screenshots/693aefb5231f2fa632d467c4febe56e5f4cd28b4

IDS_PASSWORD_CHECK_STATUS_MESSAGE_SUCCESS_WITH_LEAKS.png.sha1 image:
https://storage.cloud.google.com/chromium-translation-screenshots/f49f6f620eb105d574ea8b989701a56f6ec02b8b

Bug: 1109691, 1092444
Change-Id: I41144279472e00062fd12b91e8c645c3a283bf61
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2322486
Commit-Queue: Eleonora Rocchi <erocchi@google.com>
Reviewed-by: default avatarFriedrich [CET] <fhorschig@chromium.org>
Cr-Commit-Position: refs/heads/master@{#795874}
parent e64d8c93
...@@ -47,6 +47,7 @@ ...@@ -47,6 +47,7 @@
<TextView <TextView
android:id="@+id/check_status_description" android:id="@+id/check_status_description"
android:layout_marginTop="@dimen/check_status_description_margin_top"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:textAppearance="@style/TextAppearance.TextSmall.Secondary" android:textAppearance="@style/TextAppearance.TextSmall.Secondary"
......
...@@ -4,11 +4,17 @@ ...@@ -4,11 +4,17 @@
found in the LICENSE file. --> found in the LICENSE file. -->
<resources> <resources>
<dimen name="check_status_description_margin_top">2dp</dimen>
<dimen name="check_status_icon_margin_horizontal">16dp</dimen> <dimen name="check_status_icon_margin_horizontal">16dp</dimen>
<dimen name="check_status_icon_size">24dp</dimen> <dimen name="check_status_icon_size">24dp</dimen>
<dimen name="check_status_restart_button_clickable_surface_size">48dp</dimen> <dimen name="check_status_restart_button_clickable_surface_size">48dp</dimen>
<dimen name="check_status_restart_button_margin_end">4dp</dimen> <dimen name="check_status_restart_button_margin_end">4dp</dimen>
<!-- Margin for the header message, it depends on the status -->
<dimen name="check_status_message_error_margin_vertical">24dp</dimen>
<dimen name="check_status_message_idle_margin_vertical">27dp</dimen>
<dimen name="check_status_message_running_margin_vertical">36dp</dimen>
<dimen name="compromised_credential_row_button_margin_top">16dp</dimen> <dimen name="compromised_credential_row_button_margin_top">16dp</dimen>
<dimen name="compromised_credential_row_more_padding_end">8dp</dimen> <dimen name="compromised_credential_row_more_padding_end">8dp</dimen>
<dimen name="compromised_credential_row_more_padding_start">16dp</dimen> <dimen name="compromised_credential_row_more_padding_start">16dp</dimen>
......
...@@ -10,16 +10,22 @@ import static org.chromium.chrome.browser.password_check.PasswordCheckProperties ...@@ -10,16 +10,22 @@ import static org.chromium.chrome.browser.password_check.PasswordCheckProperties
import static org.chromium.chrome.browser.password_check.PasswordCheckProperties.HeaderProperties.CHECK_STATUS; import static org.chromium.chrome.browser.password_check.PasswordCheckProperties.HeaderProperties.CHECK_STATUS;
import static org.chromium.chrome.browser.password_check.PasswordCheckProperties.HeaderProperties.CHECK_TIMESTAMP; import static org.chromium.chrome.browser.password_check.PasswordCheckProperties.HeaderProperties.CHECK_TIMESTAMP;
import static org.chromium.chrome.browser.password_check.PasswordCheckProperties.HeaderProperties.COMPROMISED_CREDENTIALS_COUNT; import static org.chromium.chrome.browser.password_check.PasswordCheckProperties.HeaderProperties.COMPROMISED_CREDENTIALS_COUNT;
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.ITEMS;
import static org.chromium.components.embedder_support.util.UrlUtilities.stripScheme; import static org.chromium.components.embedder_support.util.UrlUtilities.stripScheme;
import android.content.Context; import android.content.Context;
import android.content.res.Resources;
import android.util.Pair;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.ImageButton; import android.widget.ImageButton;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView; import android.widget.TextView;
import androidx.annotation.VisibleForTesting;
import org.chromium.chrome.browser.password_check.PasswordCheckProperties.ItemType; import org.chromium.chrome.browser.password_check.PasswordCheckProperties.ItemType;
import org.chromium.chrome.browser.password_check.internal.R; import org.chromium.chrome.browser.password_check.internal.R;
import org.chromium.components.browser_ui.widget.listmenu.BasicListMenu; import org.chromium.components.browser_ui.widget.listmenu.BasicListMenu;
...@@ -156,21 +162,25 @@ class PasswordCheckViewBinder { ...@@ -156,21 +162,25 @@ class PasswordCheckViewBinder {
* @param key The {@link PropertyKey} which changed. * @param key The {@link PropertyKey} which changed.
*/ */
private static void bindHeaderView(PropertyModel model, View view, PropertyKey key) { private static void bindHeaderView(PropertyModel model, View view, PropertyKey key) {
Pair<Integer, Integer> progress = model.get(CHECK_PROGRESS);
@PasswordCheckUIStatus @PasswordCheckUIStatus
int status = model.get(CHECK_STATUS); int status = model.get(CHECK_STATUS);
Long checkTimestamp = model.get(CHECK_TIMESTAMP);
Integer compromisedCredentialsCount = model.get(COMPROMISED_CREDENTIALS_COUNT); Integer compromisedCredentialsCount = model.get(COMPROMISED_CREDENTIALS_COUNT);
if (key == CHECK_PROGRESS) { if (key == CHECK_PROGRESS) {
// TODO(crbug.com/1109691): Set text based on progress. updateStatusText(view, status, compromisedCredentialsCount, checkTimestamp, progress);
} else if (key == CHECK_STATUS) { } else if (key == CHECK_STATUS) {
// TODO(crbug.com/1109691): Set text and illustration based on status. // TODO(crbug.com/1109691): Set illustration based on status.
updateActionButton(view, status); updateActionButton(view, status);
updateStatusIcon(view, status, compromisedCredentialsCount); updateStatusIcon(view, status, compromisedCredentialsCount);
updateStatusText(view, status, compromisedCredentialsCount, checkTimestamp, progress);
} else if (key == CHECK_TIMESTAMP) {
updateStatusText(view, status, compromisedCredentialsCount, checkTimestamp, progress);
} else if (key == COMPROMISED_CREDENTIALS_COUNT) { } else if (key == COMPROMISED_CREDENTIALS_COUNT) {
// TODO(crbug.com/1109691): Set text and illustration based on compromised credential // TODO(crbug.com/1109691): Set illustration based on compromised credentials count.
// count.
updateStatusIcon(view, status, compromisedCredentialsCount); updateStatusIcon(view, status, compromisedCredentialsCount);
} else if (key == CHECK_TIMESTAMP) { updateStatusText(view, status, compromisedCredentialsCount, checkTimestamp, progress);
// TODO(crbug.com/1109691): Set text description based on timestamp.
} else { } else {
assert false : "Unhandled update to property:" + key; assert false : "Unhandled update to property:" + key;
} }
...@@ -195,6 +205,7 @@ class PasswordCheckViewBinder { ...@@ -195,6 +205,7 @@ class PasswordCheckViewBinder {
private static void updateStatusIcon( private static void updateStatusIcon(
View view, @PasswordCheckUIStatus int status, Integer compromisedCredentialsCount) { View view, @PasswordCheckUIStatus int status, Integer compromisedCredentialsCount) {
// TODO(crbug.com/1114051): Set default values for header properties.
if (status == PasswordCheckUIStatus.IDLE && compromisedCredentialsCount == null) return; if (status == PasswordCheckUIStatus.IDLE && compromisedCredentialsCount == null) return;
ImageView statusIcon = view.findViewById(R.id.check_status_icon); ImageView statusIcon = view.findViewById(R.id.check_status_icon);
statusIcon.setImageResource(getIconResource(status, compromisedCredentialsCount)); statusIcon.setImageResource(getIconResource(status, compromisedCredentialsCount));
...@@ -234,6 +245,121 @@ class PasswordCheckViewBinder { ...@@ -234,6 +245,121 @@ class PasswordCheckViewBinder {
return status == PasswordCheckUIStatus.RUNNING ? View.VISIBLE : View.GONE; return status == PasswordCheckUIStatus.RUNNING ? View.VISIBLE : View.GONE;
} }
private static void updateStatusText(View view, @PasswordCheckUIStatus int status,
Integer compromisedCredentialsCount, Long checkTimestamp,
Pair<Integer, Integer> progress) {
// TODO(crbug.com/1114051): Set default values for header properties.
if (status == PasswordCheckUIStatus.IDLE
&& (compromisedCredentialsCount == null || checkTimestamp == null)) {
return;
}
if (status == PasswordCheckUIStatus.RUNNING && progress == null) return;
TextView statusMessage = view.findViewById(R.id.check_status_message);
statusMessage.setText(
getStatusMessage(view, status, compromisedCredentialsCount, progress));
LinearLayout textLayout = view.findViewById(R.id.check_status_text_layout);
int verticalMargin = getDimensionPixelOffset(view, getStatusTextMargin(status));
textLayout.setPadding(0, verticalMargin, 0, verticalMargin);
TextView statusDescription = view.findViewById(R.id.check_status_description);
statusDescription.setText(getStatusDescription(view, checkTimestamp));
statusDescription.setVisibility(getStatusDescriptionVisibility(status));
}
private static String getStatusMessage(View view, @PasswordCheckUIStatus int status,
Integer compromisedCredentialsCount, Pair<Integer, Integer> progress) {
switch (status) {
case PasswordCheckUIStatus.IDLE:
assert compromisedCredentialsCount != null;
return compromisedCredentialsCount == 0
? getString(view, R.string.password_check_status_message_idle_no_leaks)
: view.getContext().getResources().getQuantityString(
R.plurals.password_check_status_message_idle_with_leaks,
compromisedCredentialsCount, compromisedCredentialsCount);
case PasswordCheckUIStatus.RUNNING:
assert progress != null;
if (progress.equals(UNKNOWN_PROGRESS)) {
return getString(view, R.string.password_check_status_message_initial_running);
} else {
return String.format(
getString(view, R.string.password_check_status_message_running),
progress.first, progress.second);
}
case PasswordCheckUIStatus.ERROR_OFFLINE:
return getString(view, R.string.password_check_status_message_error_offline);
case PasswordCheckUIStatus.ERROR_NO_PASSWORDS:
return getString(view, R.string.password_check_status_message_error_no_passwords);
case PasswordCheckUIStatus.ERROR_SIGNED_OUT:
return getString(view, R.string.password_check_status_message_error_signed_out);
case PasswordCheckUIStatus.ERROR_QUOTA_LIMIT:
return getString(view, R.string.password_check_status_message_error_quota_limit);
case PasswordCheckUIStatus.ERROR_QUOTA_LIMIT_ACCOUNT_CHECK:
return getString(view,
R.string.password_check_status_message_error_quota_limit_account_check);
case PasswordCheckUIStatus.ERROR_UNKNOWN:
return getString(view, R.string.password_check_status_message_error_unknown);
default:
assert false : "Unhandled check status " + status + "on message update";
}
return null;
}
private static int getStatusTextMargin(@PasswordCheckUIStatus int status) {
switch (status) {
case PasswordCheckUIStatus.IDLE:
return R.dimen.check_status_message_idle_margin_vertical;
case PasswordCheckUIStatus.RUNNING:
return R.dimen.check_status_message_running_margin_vertical;
case PasswordCheckUIStatus.ERROR_OFFLINE:
case PasswordCheckUIStatus.ERROR_NO_PASSWORDS:
case PasswordCheckUIStatus.ERROR_SIGNED_OUT:
case PasswordCheckUIStatus.ERROR_QUOTA_LIMIT:
case PasswordCheckUIStatus.ERROR_QUOTA_LIMIT_ACCOUNT_CHECK:
case PasswordCheckUIStatus.ERROR_UNKNOWN:
return R.dimen.check_status_message_error_margin_vertical;
default:
assert false : "Unhandled check status " + status + "on text margin update";
}
return 0;
}
private static String getStatusDescription(View view, Long checkTimestamp) {
if (checkTimestamp == null) return null;
Resources res = view.getContext().getResources();
return res.getString(R.string.password_check_status_description_idle,
getTimestamp(res, System.currentTimeMillis() - checkTimestamp));
}
@VisibleForTesting
protected static String getTimestamp(Resources res, long timeDeltaMs) {
if (timeDeltaMs < 0) timeDeltaMs = 0;
int daysElapsed = (int) (timeDeltaMs / (24L * 60L * 60L * 1000L));
int hoursElapsed = (int) (timeDeltaMs / (60L * 60L * 1000L));
int minutesElapsed = (int) (timeDeltaMs / (60L * 1000L));
String relativeTime;
if (daysElapsed > 0L) {
relativeTime = res.getQuantityString(
org.chromium.chrome.R.plurals.n_days_ago, daysElapsed, daysElapsed);
} else if (hoursElapsed > 0L) {
relativeTime = res.getQuantityString(
org.chromium.chrome.R.plurals.n_hours_ago, hoursElapsed, hoursElapsed);
} else if (minutesElapsed > 0L) {
relativeTime = res.getQuantityString(
org.chromium.chrome.R.plurals.n_minutes_ago, minutesElapsed, minutesElapsed);
} else {
relativeTime = res.getString(R.string.password_check_just_now);
}
return relativeTime;
}
private static int getStatusDescriptionVisibility(@PasswordCheckUIStatus int status) {
return status == PasswordCheckUIStatus.IDLE ? View.VISIBLE : View.GONE;
}
private static ListMenu createCredentialMenu(Context context, CompromisedCredential credential, private static ListMenu createCredentialMenu(Context context, CompromisedCredential credential,
PasswordCheckCoordinator.CredentialEventHandler credentialHandler) { PasswordCheckCoordinator.CredentialEventHandler credentialHandler) {
MVCListAdapter.ModelList menuItems = new MVCListAdapter.ModelList(); MVCListAdapter.ModelList menuItems = new MVCListAdapter.ModelList();
...@@ -249,4 +375,12 @@ class PasswordCheckViewBinder { ...@@ -249,4 +375,12 @@ class PasswordCheckViewBinder {
}; };
return new BasicListMenu(context, menuItems, delegate); return new BasicListMenu(context, menuItems, delegate);
} }
private static String getString(View view, int resourceId) {
return view.getContext().getResources().getString(resourceId);
}
private static int getDimensionPixelOffset(View view, int resourceId) {
return view.getContext().getResources().getDimensionPixelOffset(resourceId);
}
} }
...@@ -187,10 +187,49 @@ ...@@ -187,10 +187,49 @@
<message name="IDS_PASSWORD_CHECK_CREDENTIAL_ROW_SCRIPT_BUTTON_EXPLANATION" desc="Small description explaining that an automated password change can be done with a script."> <message name="IDS_PASSWORD_CHECK_CREDENTIAL_ROW_SCRIPT_BUTTON_EXPLANATION" desc="Small description explaining that an automated password change can be done with a script.">
Let Google Assistant help you change your password Let Google Assistant help you change your password
</message> </message>
<message name="IDS_PASSWORD_CHECK_STATUS_DESCRIPTION_IDLE" desc="Small description explaining that the password check has been completed and showing how long ago the last check has been successfully completed.">
Checked passwords · <ph name="TIME_SINCE_LAST_CHECK">%1$s<ex>Just now</ex></ph>
</message>
<message name="IDS_PASSWORD_CHECK_STATUS_MESSAGE_ERROR_NO_PASSWORDS" desc="Error message explaining that the password check failed because the user has no passwords on this device.">
No saved passwords. Chrome can check your passwords when you save them.
</message>
<message name="IDS_PASSWORD_CHECK_STATUS_MESSAGE_ERROR_OFFLINE" desc="Error message explaining that the password check failed because the user is offline.">
Chrome can't check your passwords. Try checking your internet connection.
</message>
<message name="IDS_PASSWORD_CHECK_STATUS_MESSAGE_ERROR_QUOTA_LIMIT" desc="Error message explaining that the password check failed because the user has exceeded their quota.">
Chrome couldn't check all passwords. Try again tomorrow.
</message>
<message name="IDS_PASSWORD_CHECK_STATUS_MESSAGE_ERROR_QUOTA_LIMIT_ACCOUNT_CHECK" desc="Error message explaining that the password check failed because the user has exceeded their quota and redirecting to check in the Google Account.">
Chrome couldn't check all passwords. Try again tomorrow or check passwords in your Google Account.
</message>
<message name="IDS_PASSWORD_CHECK_STATUS_MESSAGE_ERROR_SIGNED_OUT" desc="Error message explaining that the password check failed because the user is signed-out.">
Chrome can check your passwords when you sign in with your Google Account.
</message>
<message name="IDS_PASSWORD_CHECK_STATUS_MESSAGE_ERROR_UNKNOWN" desc="Error message explaining that the password check failed for unknown reasons.">
Chrome can't check your passwords. Try again.
</message>
<message name="IDS_PASSWORD_CHECK_STATUS_MESSAGE_IDLE_NO_LEAKS" desc="Message explaining that the password check has been completed and no leaked passwords have been found.">
No compromised passwords
</message>
<message name="IDS_PASSWORD_CHECK_STATUS_MESSAGE_IDLE_WITH_LEAKS" desc="Message explaining that the password check has been completed and some leaked passwords have been found.">
{COMPROMISED_PASSWORDS, plural, =1 {# compromised password} other {# compromised passwords}}
</message>
<message name="IDS_PASSWORD_CHECK_STATUS_MESSAGE_INITIAL_RUNNING" desc="Status message shown when the check for leaked passwords is running and we have not yet information regarding the progress.">
Checking passwords…
</message>
<message name="IDS_PASSWORD_CHECK_STATUS_MESSAGE_RUNNING" desc="Status message shown when the check for leaked passwords is running.">
Checking passwords (<ph name="ANALYSED_PASSWORDS">%1$s<ex>1</ex></ph> of <ph name="TOTAL_PASSWORDS">%2$s<ex>42</ex></ph>)…
</message>
<!-- Accessibility strings --> <!-- Accessibility strings -->
<message name="IDS_ACCESSIBILITY_PASSWORD_CHECK_RESTART_BUTTON" desc="Content description for the button to restart the password check."> <message name="IDS_ACCESSIBILITY_PASSWORD_CHECK_RESTART_BUTTON" desc="Content description for the button to restart the password check.">
Restart password check Restart password check
</message> </message>
<!-- Utility string to show the timestamp -->
<message name="IDS_PASSWORD_CHECK_JUST_NOW" desc="A time label for a check that happened just now. For example 'Checked passwords · Just now'">
Just now
</message>
</messages> </messages>
</release> </release>
</grit> </grit>
...@@ -21,19 +21,29 @@ import static org.mockito.Mockito.verify; ...@@ -21,19 +21,29 @@ import static org.mockito.Mockito.verify;
import static org.chromium.chrome.browser.password_check.PasswordCheckProperties.CompromisedCredentialProperties.COMPROMISED_CREDENTIAL; import static org.chromium.chrome.browser.password_check.PasswordCheckProperties.CompromisedCredentialProperties.COMPROMISED_CREDENTIAL;
import static org.chromium.chrome.browser.password_check.PasswordCheckProperties.CompromisedCredentialProperties.CREDENTIAL_HANDLER; import static org.chromium.chrome.browser.password_check.PasswordCheckProperties.CompromisedCredentialProperties.CREDENTIAL_HANDLER;
import static org.chromium.chrome.browser.password_check.PasswordCheckProperties.HeaderProperties.CHECK_PROGRESS;
import static org.chromium.chrome.browser.password_check.PasswordCheckProperties.HeaderProperties.CHECK_STATUS; import static org.chromium.chrome.browser.password_check.PasswordCheckProperties.HeaderProperties.CHECK_STATUS;
import static org.chromium.chrome.browser.password_check.PasswordCheckProperties.HeaderProperties.CHECK_TIMESTAMP;
import static org.chromium.chrome.browser.password_check.PasswordCheckProperties.HeaderProperties.COMPROMISED_CREDENTIALS_COUNT; import static org.chromium.chrome.browser.password_check.PasswordCheckProperties.HeaderProperties.COMPROMISED_CREDENTIALS_COUNT;
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.ITEMS;
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_OFFLINE;
import static org.chromium.chrome.browser.password_check.PasswordCheckUIStatus.ERROR_QUOTA_LIMIT;
import static org.chromium.chrome.browser.password_check.PasswordCheckUIStatus.ERROR_QUOTA_LIMIT_ACCOUNT_CHECK;
import static org.chromium.chrome.browser.password_check.PasswordCheckUIStatus.ERROR_SIGNED_OUT;
import static org.chromium.chrome.browser.password_check.PasswordCheckUIStatus.ERROR_UNKNOWN;
import static org.chromium.chrome.browser.password_check.PasswordCheckUIStatus.IDLE; import static org.chromium.chrome.browser.password_check.PasswordCheckUIStatus.IDLE;
import static org.chromium.chrome.browser.password_check.PasswordCheckUIStatus.RUNNING; import static org.chromium.chrome.browser.password_check.PasswordCheckUIStatus.RUNNING;
import static org.chromium.content_public.browser.test.util.CriteriaHelper.pollUiThread; import static org.chromium.content_public.browser.test.util.CriteriaHelper.pollUiThread;
import static org.chromium.content_public.browser.test.util.TestThreadUtils.runOnUiThreadBlocking; import static org.chromium.content_public.browser.test.util.TestThreadUtils.runOnUiThreadBlocking;
import android.content.res.Resources;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.graphics.Canvas; import android.graphics.Canvas;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.os.Bundle; import android.os.Bundle;
import android.util.Pair;
import android.view.View; import android.view.View;
import android.widget.ImageButton; import android.widget.ImageButton;
import android.widget.ImageView; import android.widget.ImageView;
...@@ -44,6 +54,7 @@ import androidx.annotation.IdRes; ...@@ -44,6 +54,7 @@ import androidx.annotation.IdRes;
import androidx.appcompat.content.res.AppCompatResources; import androidx.appcompat.content.res.AppCompatResources;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
import androidx.test.filters.MediumTest; import androidx.test.filters.MediumTest;
import androidx.test.filters.SmallTest;
import org.junit.Before; import org.junit.Before;
import org.junit.Rule; import org.junit.Rule;
...@@ -82,6 +93,13 @@ public class PasswordCheckViewTest { ...@@ -82,6 +93,13 @@ public class PasswordCheckViewTest {
private static final CompromisedCredential SCRIPTED = private static final CompromisedCredential SCRIPTED =
new CompromisedCredential("script.com", "Charlie", "secret", false, true); new CompromisedCredential("script.com", "Charlie", "secret", false, true);
private static final int LEAKS_COUNT = 2;
private static final long S_TO_MS = 1000;
private static final long MIN_TO_MS = 60 * S_TO_MS;
private static final long H_TO_MS = 60 * MIN_TO_MS;
private static final long DAY_TO_MS = 24 * H_TO_MS;
private PropertyModel mModel = PasswordCheckProperties.createDefaultModel(); private PropertyModel mModel = PasswordCheckProperties.createDefaultModel();
private PasswordCheckFragmentView mPasswordCheckView; private PasswordCheckFragmentView mPasswordCheckView;
...@@ -132,7 +150,9 @@ public class PasswordCheckViewTest { ...@@ -132,7 +150,9 @@ public class PasswordCheckViewTest {
@Test @Test
@MediumTest @MediumTest
public void testStatusDisplaysIconOnIdleNoLeaks() { public void testStatusDisplaysIconOnIdleNoLeaks() {
runOnUiThreadBlocking(() -> { mModel.get(ITEMS).add(buildHeader(IDLE, 0)); }); Long checkTimestamp = System.currentTimeMillis();
runOnUiThreadBlocking(
() -> { mModel.get(ITEMS).add(buildHeader(IDLE, 0, checkTimestamp)); });
waitForListViewToHaveLength(1); waitForListViewToHaveLength(1);
assertDisplaysIcon(R.drawable.ic_check_circle_filled_green_24dp); assertDisplaysIcon(R.drawable.ic_check_circle_filled_green_24dp);
} }
...@@ -140,7 +160,9 @@ public class PasswordCheckViewTest { ...@@ -140,7 +160,9 @@ public class PasswordCheckViewTest {
@Test @Test
@MediumTest @MediumTest
public void testStatusDisplaysIconOnIdleWithLeaks() { public void testStatusDisplaysIconOnIdleWithLeaks() {
runOnUiThreadBlocking(() -> { mModel.get(ITEMS).add(buildHeader(IDLE, 2)); }); Long checkTimestamp = System.currentTimeMillis();
runOnUiThreadBlocking(
() -> { mModel.get(ITEMS).add(buildHeader(IDLE, LEAKS_COUNT, checkTimestamp)); });
waitForListViewToHaveLength(1); waitForListViewToHaveLength(1);
assertDisplaysIcon(org.chromium.chrome.R.drawable.ic_warning_red_24dp); assertDisplaysIcon(org.chromium.chrome.R.drawable.ic_warning_red_24dp);
} }
...@@ -165,7 +187,9 @@ public class PasswordCheckViewTest { ...@@ -165,7 +187,9 @@ public class PasswordCheckViewTest {
@Test @Test
@MediumTest @MediumTest
public void testStatusDisplaysRestartAction() { public void testStatusDisplaysRestartAction() {
runOnUiThreadBlocking(() -> { mModel.get(ITEMS).add(buildHeader(IDLE, 0)); }); Long checkTimestamp = System.currentTimeMillis();
runOnUiThreadBlocking(
() -> { mModel.get(ITEMS).add(buildHeader(IDLE, 0, checkTimestamp)); });
waitForListViewToHaveLength(1); waitForListViewToHaveLength(1);
assertThat(getActionButton().getVisibility(), is(View.VISIBLE)); assertThat(getActionButton().getVisibility(), is(View.VISIBLE));
assertTrue(getActionButton().isClickable()); assertTrue(getActionButton().isClickable());
...@@ -180,6 +204,114 @@ public class PasswordCheckViewTest { ...@@ -180,6 +204,114 @@ public class PasswordCheckViewTest {
assertFalse(getActionButton().isClickable()); assertFalse(getActionButton().isClickable());
} }
@Test
@MediumTest
public void testStatusRunnningText() {
runOnUiThreadBlocking(
() -> { mModel.get(ITEMS).add(buildHeader(RUNNING, UNKNOWN_PROGRESS)); });
waitForListViewToHaveLength(1);
assertThat(getHeaderMessage().getText(),
is(getString(R.string.password_check_status_message_initial_running)));
assertThat(getHeaderMessage().getVisibility(), is(View.VISIBLE));
assertThat(getHeaderDescription().getVisibility(), is(View.GONE));
}
@Test
@MediumTest
public void testStatusIdleNoLeaksText() {
Long checkTimestamp = System.currentTimeMillis();
runOnUiThreadBlocking(
() -> { mModel.get(ITEMS).add(buildHeader(IDLE, 0, checkTimestamp)); });
waitForListViewToHaveLength(1);
assertThat(getHeaderMessage().getText(),
is(getString(R.string.password_check_status_message_idle_no_leaks)));
assertThat(getHeaderMessage().getVisibility(), is(View.VISIBLE));
assertThat(getHeaderDescription().getVisibility(), is(View.VISIBLE));
}
@Test
@MediumTest
public void testStatusIdleWithLeaksText() {
Long checkTimestamp = System.currentTimeMillis();
runOnUiThreadBlocking(
() -> { mModel.get(ITEMS).add(buildHeader(IDLE, LEAKS_COUNT, checkTimestamp)); });
waitForListViewToHaveLength(1);
assertThat(getHeaderMessage().getText(),
is(mPasswordCheckView.getContext().getResources().getQuantityString(
R.plurals.password_check_status_message_idle_with_leaks, LEAKS_COUNT,
LEAKS_COUNT)));
assertThat(getHeaderMessage().getVisibility(), is(View.VISIBLE));
assertThat(getHeaderDescription().getVisibility(), is(View.VISIBLE));
}
@Test
@MediumTest
public void testStatusErrorOfflineText() {
runOnUiThreadBlocking(() -> { mModel.get(ITEMS).add(buildHeader(ERROR_OFFLINE)); });
waitForListViewToHaveLength(1);
assertThat(getHeaderMessage().getText(),
is(getString(R.string.password_check_status_message_error_offline)));
assertThat(getHeaderMessage().getVisibility(), is(View.VISIBLE));
assertThat(getHeaderDescription().getVisibility(), is(View.GONE));
}
@Test
@MediumTest
public void testStatusErrorNoPasswordsText() {
runOnUiThreadBlocking(() -> { mModel.get(ITEMS).add(buildHeader(ERROR_NO_PASSWORDS)); });
waitForListViewToHaveLength(1);
assertThat(getHeaderMessage().getText(),
is(getString(R.string.password_check_status_message_error_no_passwords)));
assertThat(getHeaderMessage().getVisibility(), is(View.VISIBLE));
assertThat(getHeaderDescription().getVisibility(), is(View.GONE));
}
@Test
@MediumTest
public void testStatusErrorQuotaLimitText() {
runOnUiThreadBlocking(() -> { mModel.get(ITEMS).add(buildHeader(ERROR_QUOTA_LIMIT)); });
waitForListViewToHaveLength(1);
assertThat(getHeaderMessage().getText(),
is(getString(R.string.password_check_status_message_error_quota_limit)));
assertThat(getHeaderMessage().getVisibility(), is(View.VISIBLE));
assertThat(getHeaderDescription().getVisibility(), is(View.GONE));
}
@Test
@MediumTest
public void testStatusErrorQuotaLimitAccountCheckText() {
runOnUiThreadBlocking(
() -> { mModel.get(ITEMS).add(buildHeader(ERROR_QUOTA_LIMIT_ACCOUNT_CHECK)); });
waitForListViewToHaveLength(1);
assertThat(getHeaderMessage().getText(),
is(getString(
R.string.password_check_status_message_error_quota_limit_account_check)));
assertThat(getHeaderMessage().getVisibility(), is(View.VISIBLE));
assertThat(getHeaderDescription().getVisibility(), is(View.GONE));
}
@Test
@MediumTest
public void testStatusErrorSignedOutText() {
runOnUiThreadBlocking(() -> { mModel.get(ITEMS).add(buildHeader(ERROR_SIGNED_OUT)); });
waitForListViewToHaveLength(1);
assertThat(getHeaderMessage().getText(),
is(getString(R.string.password_check_status_message_error_signed_out)));
assertThat(getHeaderMessage().getVisibility(), is(View.VISIBLE));
assertThat(getHeaderDescription().getVisibility(), is(View.GONE));
}
@Test
@MediumTest
public void testStatusErrorUnknownText() {
runOnUiThreadBlocking(() -> { mModel.get(ITEMS).add(buildHeader(ERROR_UNKNOWN)); });
waitForListViewToHaveLength(1);
assertThat(getHeaderMessage().getText(),
is(getString(R.string.password_check_status_message_error_unknown)));
assertThat(getHeaderMessage().getVisibility(), is(View.VISIBLE));
assertThat(getHeaderDescription().getVisibility(), is(View.GONE));
}
@Test @Test
@MediumTest @MediumTest
public void testCrendentialDisplaysNameOriginAndReason() { public void testCrendentialDisplaysNameOriginAndReason() {
...@@ -237,7 +369,7 @@ public class PasswordCheckViewTest { ...@@ -237,7 +369,7 @@ public class PasswordCheckViewTest {
@MediumTest @MediumTest
public void testClickingChangePasswordTriggersHandler() { public void testClickingChangePasswordTriggersHandler() {
runOnUiThreadBlocking(() -> mModel.get(ITEMS).add(buildCredentialItem(ANA))); runOnUiThreadBlocking(() -> mModel.get(ITEMS).add(buildCredentialItem(ANA)));
pollUiThread(() -> Criteria.checkThat(getPasswordCheckViewList().getChildCount(), is(1))); waitForListViewToHaveLength(1);
TouchCommon.singleClickView(getCredentialChangeButtonAt(0)); TouchCommon.singleClickView(getCredentialChangeButtonAt(0));
...@@ -248,7 +380,7 @@ public class PasswordCheckViewTest { ...@@ -248,7 +380,7 @@ public class PasswordCheckViewTest {
@MediumTest @MediumTest
public void testClickingChangePasswordWithScriptTriggersHandler() { public void testClickingChangePasswordWithScriptTriggersHandler() {
runOnUiThreadBlocking(() -> mModel.get(ITEMS).add(buildCredentialItem(SCRIPTED))); runOnUiThreadBlocking(() -> mModel.get(ITEMS).add(buildCredentialItem(SCRIPTED)));
pollUiThread(() -> Criteria.checkThat(getPasswordCheckViewList().getChildCount(), is(1))); waitForListViewToHaveLength(1);
TouchCommon.singleClickView(getCredentialChangeButtonWithScriptAt(0)); TouchCommon.singleClickView(getCredentialChangeButtonWithScriptAt(0));
...@@ -271,15 +403,42 @@ public class PasswordCheckViewTest { ...@@ -271,15 +403,42 @@ public class PasswordCheckViewTest {
verify(mMockHandler).onRemove(eq(ANA)); verify(mMockHandler).onRemove(eq(ANA));
} }
private MVCListAdapter.ListItem buildHeader(@PasswordCheckUIStatus int status) { @Test
return buildHeader(status, null); @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"));
}
private MVCListAdapter.ListItem buildHeader(@PasswordCheckUIStatus int status,
Integer compromisedCredentialsCount, Long checkTimestamp) {
return buildHeader(status, compromisedCredentialsCount, checkTimestamp, null);
} }
private MVCListAdapter.ListItem buildHeader( private MVCListAdapter.ListItem buildHeader(
@PasswordCheckUIStatus int status, Integer compromisedCredentialsCount) { @PasswordCheckUIStatus int status, Pair<Integer, Integer> progress) {
return buildHeader(status, null, null, progress);
}
private MVCListAdapter.ListItem buildHeader(@PasswordCheckUIStatus int status) {
return buildHeader(status, null, null, null);
}
private MVCListAdapter.ListItem buildHeader(@PasswordCheckUIStatus int status,
Integer compromisedCredentialsCount, Long checkTimestamp,
Pair<Integer, Integer> progress) {
return new MVCListAdapter.ListItem(PasswordCheckProperties.ItemType.HEADER, return new MVCListAdapter.ListItem(PasswordCheckProperties.ItemType.HEADER,
new PropertyModel.Builder(HeaderProperties.ALL_KEYS) new PropertyModel.Builder(HeaderProperties.ALL_KEYS)
.with(CHECK_PROGRESS, progress)
.with(CHECK_STATUS, status) .with(CHECK_STATUS, status)
.with(CHECK_TIMESTAMP, checkTimestamp)
.with(COMPROMISED_CREDENTIALS_COUNT, compromisedCredentialsCount) .with(COMPROMISED_CREDENTIALS_COUNT, compromisedCredentialsCount)
.build()); .build());
} }
...@@ -331,6 +490,14 @@ public class PasswordCheckViewTest { ...@@ -331,6 +490,14 @@ public class PasswordCheckViewTest {
return getStatus().findViewById(R.id.check_status_progress); return getStatus().findViewById(R.id.check_status_progress);
} }
private TextView getHeaderDescription() {
return getStatus().findViewById(R.id.check_status_description);
}
private TextView getHeaderMessage() {
return getStatus().findViewById(R.id.check_status_message);
}
private ImageButton getActionButton() { private ImageButton getActionButton() {
return getStatus().findViewById(R.id.check_status_restart_button); return getStatus().findViewById(R.id.check_status_restart_button);
} }
......
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