Commit 9fb74e82 authored by Andrey Zaytsev's avatar Andrey Zaytsev Committed by Commit Bot

Safety check on Android: added the last run timestamp to the UI

UI demo: https://screenshot.googleplex.com/PdYajrS8FfE.gif

Bug: 1070620, 1107807
Change-Id: I2fac517d4aefd5999449988cfe7d289f76e67563
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2303714Reviewed-by: default avatarTheresa  <twellington@chromium.org>
Reviewed-by: default avatarNatalie Chouinard <chouinard@chromium.org>
Reviewed-by: default avatarMartin Šrámek <msramek@chromium.org>
Commit-Queue: Andrey Zaytsev <andzaytsev@google.com>
Auto-Submit: Andrey Zaytsev <andzaytsev@google.com>
Cr-Commit-Position: refs/heads/master@{#791461}
parent f775e465
...@@ -593,6 +593,10 @@ public final class ChromePreferenceKeys { ...@@ -593,6 +593,10 @@ public final class ChromePreferenceKeys {
"org.chromium.chrome.browser.settings.privacy." "org.chromium.chrome.browser.settings.privacy."
+ "PREF_OTHER_FORMS_OF_HISTORY_DIALOG_SHOWN"; + "PREF_OTHER_FORMS_OF_HISTORY_DIALOG_SHOWN";
/** Stores the timestamp of the last performed Safety check. */
public static final String SETTINGS_SAFETY_CHECK_LAST_RUN_TIMESTAMP =
"Chrome.SafetyCheck.LastRunTimestamp";
/** Stores the number of times the user has performed Safety check. */ /** Stores the number of times the user has performed Safety check. */
public static final String SETTINGS_SAFETY_CHECK_RUN_COUNTER = "Chrome.SafetyCheck.RunCounter"; public static final String SETTINGS_SAFETY_CHECK_RUN_COUNTER = "Chrome.SafetyCheck.RunCounter";
...@@ -800,6 +804,7 @@ public final class ChromePreferenceKeys { ...@@ -800,6 +804,7 @@ public final class ChromePreferenceKeys {
HOMEPAGE_USE_CHROME_NTP, HOMEPAGE_USE_CHROME_NTP,
PROMO_IS_DISMISSED.pattern(), PROMO_IS_DISMISSED.pattern(),
PROMO_TIMES_SEEN.pattern(), PROMO_TIMES_SEEN.pattern(),
SETTINGS_SAFETY_CHECK_LAST_RUN_TIMESTAMP,
SETTINGS_SAFETY_CHECK_RUN_COUNTER, SETTINGS_SAFETY_CHECK_RUN_COUNTER,
TWA_DISCLOSURE_SEEN_PACKAGES TWA_DISCLOSURE_SEEN_PACKAGES
); );
......
...@@ -100,7 +100,7 @@ android_library("junit") { ...@@ -100,7 +100,7 @@ android_library("junit") {
android_resources("java_resources") { android_resources("java_resources") {
sources = [ sources = [
"java/res/layout/safety_check_button.xml", "java/res/layout/safety_check_bottom_elements.xml",
"java/res/layout/safety_check_status.xml", "java/res/layout/safety_check_status.xml",
"java/res/values/dimens.xml", "java/res/values/dimens.xml",
"java/res/xml/safety_check_preferences.xml", "java/res/xml/safety_check_preferences.xml",
......
<?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:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_gravity="end" >
<!-- Text view for displaying the timestamp of the last run. -->
<TextView
android:id="@+id/safety_check_timestamp"
android:layout_marginEnd="@dimen/safety_check_button_margin"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end" />
<!-- Button for starting Safety check. -->
<org.chromium.ui.widget.ButtonCompat
android:id="@+id/safety_check_button"
style="@style/FilledButton.Flat"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:layout_margin="@dimen/safety_check_button_margin"
android:focusable="true"
android:text="@string/safety_check_button" />
</LinearLayout>
<?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. -->
<!-- Button for starting Safety check. -->
<org.chromium.ui.widget.ButtonCompat xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/safety_check_button"
style="@style/FilledButton.Flat"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:layout_margin="@dimen/safety_check_button_margin"
android:focusable="true"
android:text="@string/safety_check_button" />
...@@ -5,7 +5,6 @@ ...@@ -5,7 +5,6 @@
<FrameLayout <FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="match_parent"> android:layout_height="match_parent">
<!-- Spinning icon indicating a running check. --> <!-- Spinning icon indicating a running check. -->
......
...@@ -80,6 +80,10 @@ class SafetyCheckMediator implements SafetyCheckCommonObserver { ...@@ -80,6 +80,10 @@ class SafetyCheckMediator implements SafetyCheckCommonObserver {
// Set the listener for clicking the Check button. // Set the listener for clicking the Check button.
mModel.set(SafetyCheckProperties.SAFETY_CHECK_BUTTON_CLICK_LISTENER, mModel.set(SafetyCheckProperties.SAFETY_CHECK_BUTTON_CLICK_LISTENER,
(View.OnClickListener) (v) -> performSafetyCheck()); (View.OnClickListener) (v) -> performSafetyCheck());
// Get the timestamp of the last run.
mModel.set(SafetyCheckProperties.LAST_RUN_TIMESTAMP,
mPreferenceManager.readLong(
ChromePreferenceKeys.SETTINGS_SAFETY_CHECK_LAST_RUN_TIMESTAMP, 0));
} }
/** Triggers all safety check child checks. */ /** Triggers all safety check child checks. */
...@@ -89,6 +93,11 @@ class SafetyCheckMediator implements SafetyCheckCommonObserver { ...@@ -89,6 +93,11 @@ class SafetyCheckMediator implements SafetyCheckCommonObserver {
cancelCallbacks(); cancelCallbacks();
// Record the start time for tracking 1 second checking delay in the UI. // Record the start time for tracking 1 second checking delay in the UI.
mCheckStartTime = SystemClock.elapsedRealtime(); mCheckStartTime = SystemClock.elapsedRealtime();
// Record the absolute start time for showing when the last Safety check was performed.
long currentTime = System.currentTimeMillis();
mModel.set(SafetyCheckProperties.LAST_RUN_TIMESTAMP, currentTime);
mPreferenceManager.writeLong(
ChromePreferenceKeys.SETTINGS_SAFETY_CHECK_LAST_RUN_TIMESTAMP, currentTime);
// Increment the stored number of Safety check starts. // Increment the stored number of Safety check starts.
mPreferenceManager.incrementInt(ChromePreferenceKeys.SETTINGS_SAFETY_CHECK_RUN_COUNTER); mPreferenceManager.incrementInt(ChromePreferenceKeys.SETTINGS_SAFETY_CHECK_RUN_COUNTER);
// Set the checking state for all elements. // Set the checking state for all elements.
......
...@@ -10,6 +10,7 @@ import org.chromium.chrome.browser.password_check.BulkLeakCheckServiceState; ...@@ -10,6 +10,7 @@ import org.chromium.chrome.browser.password_check.BulkLeakCheckServiceState;
import org.chromium.ui.modelutil.PropertyKey; import org.chromium.ui.modelutil.PropertyKey;
import org.chromium.ui.modelutil.PropertyModel; import org.chromium.ui.modelutil.PropertyModel;
import org.chromium.ui.modelutil.PropertyModel.WritableIntPropertyKey; import org.chromium.ui.modelutil.PropertyModel.WritableIntPropertyKey;
import org.chromium.ui.modelutil.PropertyModel.WritableLongPropertyKey;
import org.chromium.ui.modelutil.PropertyModel.WritableObjectPropertyKey; import org.chromium.ui.modelutil.PropertyModel.WritableObjectPropertyKey;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
...@@ -25,6 +26,8 @@ class SafetyCheckProperties { ...@@ -25,6 +26,8 @@ class SafetyCheckProperties {
/** Listener for Safety check button click events. */ /** Listener for Safety check button click events. */
static final WritableObjectPropertyKey SAFETY_CHECK_BUTTON_CLICK_LISTENER = static final WritableObjectPropertyKey SAFETY_CHECK_BUTTON_CLICK_LISTENER =
new WritableObjectPropertyKey(); new WritableObjectPropertyKey();
/** Timestamp of the last run, a Long object. */
static final WritableLongPropertyKey LAST_RUN_TIMESTAMP = new WritableLongPropertyKey();
@IntDef({PasswordsState.UNCHECKED, PasswordsState.CHECKING, PasswordsState.SAFE, @IntDef({PasswordsState.UNCHECKED, PasswordsState.CHECKING, PasswordsState.SAFE,
PasswordsState.COMPROMISED_EXIST, PasswordsState.OFFLINE, PasswordsState.NO_PASSWORDS, PasswordsState.COMPROMISED_EXIST, PasswordsState.OFFLINE, PasswordsState.NO_PASSWORDS,
...@@ -112,13 +115,14 @@ class SafetyCheckProperties { ...@@ -112,13 +115,14 @@ class SafetyCheckProperties {
} }
static final PropertyKey[] ALL_KEYS = new PropertyKey[] {PASSWORDS_STATE, SAFE_BROWSING_STATE, static final PropertyKey[] ALL_KEYS = new PropertyKey[] {PASSWORDS_STATE, SAFE_BROWSING_STATE,
UPDATES_STATE, SAFETY_CHECK_BUTTON_CLICK_LISTENER}; UPDATES_STATE, SAFETY_CHECK_BUTTON_CLICK_LISTENER, LAST_RUN_TIMESTAMP};
static PropertyModel createSafetyCheckModel() { static PropertyModel createSafetyCheckModel() {
return new PropertyModel.Builder(ALL_KEYS) return new PropertyModel.Builder(ALL_KEYS)
.with(PASSWORDS_STATE, PasswordsState.UNCHECKED) .with(PASSWORDS_STATE, PasswordsState.UNCHECKED)
.with(SAFE_BROWSING_STATE, SafeBrowsingState.UNCHECKED) .with(SAFE_BROWSING_STATE, SafeBrowsingState.UNCHECKED)
.with(UPDATES_STATE, UpdatesState.UNCHECKED) .with(UPDATES_STATE, UpdatesState.UNCHECKED)
.with(LAST_RUN_TIMESTAMP, 0)
.build(); .build();
} }
} }
...@@ -13,6 +13,7 @@ import android.view.LayoutInflater; ...@@ -13,6 +13,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.TextView;
import androidx.preference.Preference; import androidx.preference.Preference;
import androidx.preference.PreferenceFragmentCompat; import androidx.preference.PreferenceFragmentCompat;
...@@ -35,6 +36,8 @@ public class SafetyCheckSettingsFragment extends PreferenceFragmentCompat { ...@@ -35,6 +36,8 @@ public class SafetyCheckSettingsFragment extends PreferenceFragmentCompat {
/** The "Check" button at the bottom that needs to be added after the View is inflated. */ /** The "Check" button at the bottom that needs to be added after the View is inflated. */
private ButtonCompat mCheckButton; private ButtonCompat mCheckButton;
private TextView mTimestampTextView;
public static CharSequence getSafetyCheckSettingsElementTitle(Context context) { public static CharSequence getSafetyCheckSettingsElementTitle(Context context) {
SharedPreferencesManager preferenceManager = SharedPreferencesManager.getInstance(); SharedPreferencesManager preferenceManager = SharedPreferencesManager.getInstance();
if (preferenceManager.readInt(ChromePreferenceKeys.SETTINGS_SAFETY_CHECK_RUN_COUNTER) if (preferenceManager.readInt(ChromePreferenceKeys.SETTINGS_SAFETY_CHECK_RUN_COUNTER)
...@@ -77,18 +80,28 @@ public class SafetyCheckSettingsFragment extends PreferenceFragmentCompat { ...@@ -77,18 +80,28 @@ public class SafetyCheckSettingsFragment extends PreferenceFragmentCompat {
LinearLayout view = LinearLayout view =
(LinearLayout) super.onCreateView(inflater, container, savedInstanceState); (LinearLayout) super.onCreateView(inflater, container, savedInstanceState);
// Add a button to the bottom of the preferences view. // Add a button to the bottom of the preferences view.
mCheckButton = (ButtonCompat) inflater.inflate(R.layout.safety_check_button, view, false); LinearLayout bottomView =
view.addView(mCheckButton); (LinearLayout) inflater.inflate(R.layout.safety_check_bottom_elements, view, false);
mCheckButton = (ButtonCompat) bottomView.findViewById(R.id.safety_check_button);
mTimestampTextView = (TextView) bottomView.findViewById(R.id.safety_check_timestamp);
view.addView(bottomView);
return view; return view;
} }
/** /**
* @return A {@link ButtonCompat} object for the Check button. * @return A {@link ButtonCompat} object for the Check button.
*/ */
public ButtonCompat getCheckButton() { ButtonCompat getCheckButton() {
return mCheckButton; return mCheckButton;
} }
/**
* @return A {@link TextView} object for the last run timestamp.
*/
TextView getTimestampTextView() {
return mTimestampTextView;
}
/** /**
* Update the status string of a given Safety check element, e.g. Passwords. * Update the status string of a given Safety check element, e.g. Passwords.
* @param key An android:key String corresponding to Safety check element. * @param key An android:key String corresponding to Safety check element.
......
...@@ -4,8 +4,11 @@ ...@@ -4,8 +4,11 @@
package org.chromium.chrome.browser.safety_check; package org.chromium.chrome.browser.safety_check;
import android.content.Context;
import android.view.View; import android.view.View;
import androidx.annotation.VisibleForTesting;
import org.chromium.chrome.browser.safety_check.SafetyCheckProperties.PasswordsState; import org.chromium.chrome.browser.safety_check.SafetyCheckProperties.PasswordsState;
import org.chromium.chrome.browser.safety_check.SafetyCheckProperties.SafeBrowsingState; import org.chromium.chrome.browser.safety_check.SafetyCheckProperties.SafeBrowsingState;
import org.chromium.chrome.browser.safety_check.SafetyCheckProperties.UpdatesState; import org.chromium.chrome.browser.safety_check.SafetyCheckProperties.UpdatesState;
...@@ -16,6 +19,9 @@ class SafetyCheckViewBinder { ...@@ -16,6 +19,9 @@ class SafetyCheckViewBinder {
private static final String PASSWORDS_KEY = "passwords"; private static final String PASSWORDS_KEY = "passwords";
private static final String SAFE_BROWSING_KEY = "safe_browsing"; private static final String SAFE_BROWSING_KEY = "safe_browsing";
private static final String UPDATES_KEY = "updates"; private static final String UPDATES_KEY = "updates";
private static final long MIN_TO_MS = 60 * 1000;
private static final long H_TO_MS = 60 * MIN_TO_MS;
private static final long DAY_TO_MS = 24 * H_TO_MS;
private static int getStringForPasswords(@PasswordsState int state) { private static int getStringForPasswords(@PasswordsState int state) {
switch (state) { switch (state) {
...@@ -142,6 +148,42 @@ class SafetyCheckViewBinder { ...@@ -142,6 +148,42 @@ class SafetyCheckViewBinder {
return 0; return 0;
} }
/**
* Generates a String representing how long ago the Safety check was performed last time.
* @param context A {@link Context} instance to extract the strings.
* @param lastRunTime A long representing the last run timestamp in milliseconds.
* @param currentTime A long representing current time in milliseconds.
* @return A string to display in the UI for the last run timestamp.
*/
@VisibleForTesting
static String getLastRunTimestampText(Context context, long lastRunTime, long currentTime) {
if (lastRunTime == 0) {
return "";
}
long timeDiff = currentTime - lastRunTime;
if (timeDiff < MIN_TO_MS) {
return context.getString(R.string.safety_check_timestamp_after);
} else if (timeDiff < H_TO_MS) {
int minutes = (int) (timeDiff / MIN_TO_MS);
return context.getResources().getQuantityString(
R.plurals.safety_check_timestamp_after_mins, minutes, minutes);
} else if (timeDiff < DAY_TO_MS) {
int hours = (int) (timeDiff / H_TO_MS);
return context.getResources().getQuantityString(
R.plurals.safety_check_timestamp_after_hours, hours, hours);
} else if (timeDiff < 2 * DAY_TO_MS) {
return context.getString(R.string.safety_check_timestamp_after_yesterday);
} else {
int days = (int) (timeDiff / DAY_TO_MS);
return context.getResources().getQuantityString(
R.plurals.safety_check_timestamp_after_days, days, days);
}
}
private static void clearTimestampText(SafetyCheckSettingsFragment fragment) {
fragment.getTimestampTextView().setText("");
}
static void bind( static void bind(
PropertyModel model, SafetyCheckSettingsFragment fragment, PropertyKey propertyKey) { PropertyModel model, SafetyCheckSettingsFragment fragment, PropertyKey propertyKey) {
if (SafetyCheckProperties.PASSWORDS_STATE == propertyKey) { if (SafetyCheckProperties.PASSWORDS_STATE == propertyKey) {
...@@ -149,10 +191,12 @@ class SafetyCheckViewBinder { ...@@ -149,10 +191,12 @@ class SafetyCheckViewBinder {
int state = model.get(SafetyCheckProperties.PASSWORDS_STATE); int state = model.get(SafetyCheckProperties.PASSWORDS_STATE);
fragment.updateElementStatus(PASSWORDS_KEY, getStringForPasswords(state)); fragment.updateElementStatus(PASSWORDS_KEY, getStringForPasswords(state));
SafetyCheckElementPreference preference = fragment.findPreference(PASSWORDS_KEY); SafetyCheckElementPreference preference = fragment.findPreference(PASSWORDS_KEY);
preference.setEnabled(true);
if (state == PasswordsState.UNCHECKED) { if (state == PasswordsState.UNCHECKED) {
preference.clearStatusIndicator(); preference.clearStatusIndicator();
preference.setEnabled(true); preference.setEnabled(true);
} else if (state == PasswordsState.CHECKING) { } else if (state == PasswordsState.CHECKING) {
clearTimestampText(fragment);
preference.showProgressBar(); preference.showProgressBar();
preference.setEnabled(false); preference.setEnabled(false);
} else { } else {
...@@ -164,10 +208,12 @@ class SafetyCheckViewBinder { ...@@ -164,10 +208,12 @@ class SafetyCheckViewBinder {
int state = model.get(SafetyCheckProperties.SAFE_BROWSING_STATE); int state = model.get(SafetyCheckProperties.SAFE_BROWSING_STATE);
fragment.updateElementStatus(SAFE_BROWSING_KEY, getStringForSafeBrowsing(state)); fragment.updateElementStatus(SAFE_BROWSING_KEY, getStringForSafeBrowsing(state));
SafetyCheckElementPreference preference = fragment.findPreference(SAFE_BROWSING_KEY); SafetyCheckElementPreference preference = fragment.findPreference(SAFE_BROWSING_KEY);
preference.setEnabled(true);
if (state == SafeBrowsingState.UNCHECKED) { if (state == SafeBrowsingState.UNCHECKED) {
preference.clearStatusIndicator(); preference.clearStatusIndicator();
preference.setEnabled(true); preference.setEnabled(true);
} else if (state == SafeBrowsingState.CHECKING) { } else if (state == SafeBrowsingState.CHECKING) {
clearTimestampText(fragment);
preference.showProgressBar(); preference.showProgressBar();
preference.setEnabled(false); preference.setEnabled(false);
} else { } else {
...@@ -179,10 +225,12 @@ class SafetyCheckViewBinder { ...@@ -179,10 +225,12 @@ class SafetyCheckViewBinder {
int state = model.get(SafetyCheckProperties.UPDATES_STATE); int state = model.get(SafetyCheckProperties.UPDATES_STATE);
fragment.updateElementStatus(UPDATES_KEY, getStringForUpdates(state)); fragment.updateElementStatus(UPDATES_KEY, getStringForUpdates(state));
SafetyCheckElementPreference preference = fragment.findPreference(UPDATES_KEY); SafetyCheckElementPreference preference = fragment.findPreference(UPDATES_KEY);
preference.setEnabled(true);
if (state == UpdatesState.UNCHECKED) { if (state == UpdatesState.UNCHECKED) {
preference.clearStatusIndicator(); preference.clearStatusIndicator();
preference.setEnabled(true); preference.setEnabled(true);
} else if (state == UpdatesState.CHECKING) { } else if (state == UpdatesState.CHECKING) {
clearTimestampText(fragment);
preference.showProgressBar(); preference.showProgressBar();
preference.setEnabled(false); preference.setEnabled(false);
} else { } else {
...@@ -192,6 +240,11 @@ class SafetyCheckViewBinder { ...@@ -192,6 +240,11 @@ class SafetyCheckViewBinder {
} else if (SafetyCheckProperties.SAFETY_CHECK_BUTTON_CLICK_LISTENER == propertyKey) { } else if (SafetyCheckProperties.SAFETY_CHECK_BUTTON_CLICK_LISTENER == propertyKey) {
fragment.getCheckButton().setOnClickListener((View.OnClickListener) model.get( fragment.getCheckButton().setOnClickListener((View.OnClickListener) model.get(
SafetyCheckProperties.SAFETY_CHECK_BUTTON_CLICK_LISTENER)); SafetyCheckProperties.SAFETY_CHECK_BUTTON_CLICK_LISTENER));
} else if (SafetyCheckProperties.LAST_RUN_TIMESTAMP == propertyKey) {
long lastRunTime = model.get(SafetyCheckProperties.LAST_RUN_TIMESTAMP);
long currentTime = System.currentTimeMillis();
fragment.getTimestampTextView().setText(
getLastRunTimestampText(fragment.getContext(), lastRunTime, currentTime));
} else { } else {
assert false : "Unhandled property detected in SafetyCheckViewBinder!"; assert false : "Unhandled property detected in SafetyCheckViewBinder!";
} }
......
...@@ -8,6 +8,7 @@ import static org.junit.Assert.assertEquals; ...@@ -8,6 +8,7 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import android.content.Context;
import android.support.test.InstrumentationRegistry; import android.support.test.InstrumentationRegistry;
import androidx.preference.Preference; import androidx.preference.Preference;
...@@ -36,6 +37,11 @@ public class SafetyCheckSettingsFragmentTest { ...@@ -36,6 +37,11 @@ public class SafetyCheckSettingsFragmentTest {
private static final String PASSWORDS = "passwords"; private static final String PASSWORDS = "passwords";
private static final String SAFE_BROWSING = "safe_browsing"; private static final String SAFE_BROWSING = "safe_browsing";
private static final String UPDATES = "updates"; private static final String UPDATES = "updates";
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;
@Rule @Rule
public SettingsActivityTestRule<SafetyCheckSettingsFragment> mSettingsActivityTestRule = public SettingsActivityTestRule<SafetyCheckSettingsFragment> mSettingsActivityTestRule =
new SettingsActivityTestRule<>(SafetyCheckSettingsFragment.class); new SettingsActivityTestRule<>(SafetyCheckSettingsFragment.class);
...@@ -77,10 +83,38 @@ public class SafetyCheckSettingsFragmentTest { ...@@ -77,10 +83,38 @@ public class SafetyCheckSettingsFragmentTest {
assertFalse(title.contains("New")); assertFalse(title.contains("New"));
} }
@Test
@SmallTest
public void testLastRunTimestampStrings() {
long t0 = 12345;
Context context = InstrumentationRegistry.getTargetContext();
// Start time not set - returns an empty string.
assertEquals("", SafetyCheckViewBinder.getLastRunTimestampText(context, 0, 123));
assertEquals("Checked just now",
SafetyCheckViewBinder.getLastRunTimestampText(context, t0, t0 + 10 * S_TO_MS));
assertEquals("Checked 1 minute ago",
SafetyCheckViewBinder.getLastRunTimestampText(context, t0, t0 + MIN_TO_MS));
assertEquals("Checked 17 minutes ago",
SafetyCheckViewBinder.getLastRunTimestampText(context, t0, t0 + 17 * MIN_TO_MS));
assertEquals("Checked 1 hour ago",
SafetyCheckViewBinder.getLastRunTimestampText(context, t0, t0 + H_TO_MS));
assertEquals("Checked 13 hours ago",
SafetyCheckViewBinder.getLastRunTimestampText(context, t0, t0 + 13 * H_TO_MS));
assertEquals("Checked yesterday",
SafetyCheckViewBinder.getLastRunTimestampText(context, t0, t0 + DAY_TO_MS));
assertEquals("Checked yesterday",
SafetyCheckViewBinder.getLastRunTimestampText(context, t0, t0 + 2 * DAY_TO_MS - 1));
assertEquals("Checked 2 days ago",
SafetyCheckViewBinder.getLastRunTimestampText(context, t0, t0 + 2 * DAY_TO_MS));
assertEquals("Checked 315 days ago",
SafetyCheckViewBinder.getLastRunTimestampText(context, t0, t0 + 315 * DAY_TO_MS));
}
private void createFragmentAndModel() { private void createFragmentAndModel() {
mSettingsActivityTestRule.startSettingsActivity(); mSettingsActivityTestRule.startSettingsActivity();
mFragment = (SafetyCheckSettingsFragment) mSettingsActivityTestRule.getFragment(); mFragment = (SafetyCheckSettingsFragment) mSettingsActivityTestRule.getFragment();
mModel = SafetyCheckCoordinator.createModelAndMcp(mFragment); TestThreadUtils.runOnUiThreadBlocking(
() -> { mModel = SafetyCheckCoordinator.createModelAndMcp(mFragment); });
} }
@Test @Test
......
...@@ -925,6 +925,27 @@ Your Google account may have other forms of browsing history like searches and a ...@@ -925,6 +925,27 @@ Your Google account may have other forms of browsing history like searches and a
<message name="IDS_SAFETY_CHECK_UPDATES_ERROR" desc="Text to display when the updates check failed for some reason."> <message name="IDS_SAFETY_CHECK_UPDATES_ERROR" desc="Text to display when the updates check failed for some reason.">
Chrome can’t check for updates Chrome can’t check for updates
</message> </message>
<message name="IDS_SAFETY_CHECK_TIMESTAMP_AFTER" desc="A message shown on the safety check page, explaining to the user that it ran a moment ago.">
Checked just now
</message>
<message name="IDS_SAFETY_CHECK_TIMESTAMP_AFTER_MINS" desc="A message shown on the safety check page, explaining to the user that it ran minutes ago.">
{NUM_MINS, plural,
=1 {Checked 1 minute ago}
other {Checked # minutes ago}}
</message>
<message name="IDS_SAFETY_CHECK_TIMESTAMP_AFTER_HOURS" desc="A message shown on the safety check page, explaining to the user that it ran hours ago.">
{NUM_HOURS, plural,
=1 {Checked 1 hour ago}
other {Checked # hours ago}}
</message>
<message name="IDS_SAFETY_CHECK_TIMESTAMP_AFTER_YESTERDAY" desc="A message shown on the safety check page, showing to the user that it ran yesterday.">
Checked yesterday
</message>
<message name="IDS_SAFETY_CHECK_TIMESTAMP_AFTER_DAYS" desc="A message shown on the safety check page, showing to the user how many days ago it ran.">
{NUM_DAYS, plural,
=1 {Checked 1 day ago}
other {Checked # days ago}}
</message>
<!-- Security preferences --> <!-- Security preferences -->
<message name="IDS_PREFS_SECURITY_TITLE" desc="Title for the Security preferences. [CHAR-LIMIT=32]"> <message name="IDS_PREFS_SECURITY_TITLE" desc="Title for the Security preferences. [CHAR-LIMIT=32]">
......
...@@ -151,6 +151,42 @@ public class PropertyModel extends PropertyObservable<PropertyKey> { ...@@ -151,6 +151,42 @@ public class PropertyModel extends PropertyObservable<PropertyKey> {
} }
} }
/** The key type for read-only long model properties. */
public static class ReadableLongPropertyKey extends NamedPropertyKey {
/**
* Constructs a new unnamed read-only long property key.
*/
public ReadableLongPropertyKey() {
this(null);
}
/**
* Constructs a new named read-only long property key, e.g. for use in debugging.
* @param name The optional name of the property.
*/
public ReadableLongPropertyKey(@Nullable String name) {
super(name);
}
}
/** The key type for mutable int model properties. */
public static final class WritableLongPropertyKey extends ReadableLongPropertyKey {
/**
* Constructs a new unnamed writable long property key.
*/
public WritableLongPropertyKey() {
this(null);
}
/**
* Constructs a new named writable long property key, e.g. for use in debugging.
* @param name The optional name of the property.
*/
public WritableLongPropertyKey(@Nullable String name) {
super(name);
}
}
/** /**
* The key type for read-only Object model properties. * The key type for read-only Object model properties.
* *
...@@ -296,6 +332,32 @@ public class PropertyModel extends PropertyObservable<PropertyKey> { ...@@ -296,6 +332,32 @@ public class PropertyModel extends PropertyObservable<PropertyKey> {
notifyPropertyChanged(key); notifyPropertyChanged(key);
} }
/**
* Get the current value from the long based key.
*/
public long get(ReadableLongPropertyKey key) {
validateKey(key);
LongContainer container = (LongContainer) mData.get(key);
return container == null ? 0 : container.value;
}
/**
* Set the value for the long based key.
*/
public void set(WritableLongPropertyKey key, long value) {
validateKey(key);
LongContainer container = (LongContainer) mData.get(key);
if (container == null) {
container = new LongContainer();
mData.put(key, container);
} else if (container.value == value) {
return;
}
container.value = value;
notifyPropertyChanged(key);
}
/** /**
* Get the current value from the boolean based key. * Get the current value from the boolean based key.
*/ */
...@@ -425,6 +487,14 @@ public class PropertyModel extends PropertyObservable<PropertyKey> { ...@@ -425,6 +487,14 @@ public class PropertyModel extends PropertyObservable<PropertyKey> {
return this; return this;
} }
public Builder with(ReadableLongPropertyKey key, long value) {
validateKey(key);
LongContainer container = new LongContainer();
container.value = value;
mData.put(key, container);
return this;
}
public Builder with(ReadableBooleanPropertyKey key, boolean value) { public Builder with(ReadableBooleanPropertyKey key, boolean value) {
validateKey(key); validateKey(key);
BooleanContainer container = new BooleanContainer(); BooleanContainer container = new BooleanContainer();
...@@ -525,6 +595,21 @@ public class PropertyModel extends PropertyObservable<PropertyKey> { ...@@ -525,6 +595,21 @@ public class PropertyModel extends PropertyObservable<PropertyKey> {
} }
} }
private static class LongContainer extends ValueContainer {
public long value;
@Override
public String toString() {
return value + " in " + super.toString();
}
@Override
public boolean equals(Object other) {
return other != null && other instanceof LongContainer
&& ((LongContainer) other).value == value;
}
}
private static class BooleanContainer extends ValueContainer { private static class BooleanContainer extends ValueContainer {
public boolean value; public boolean value;
......
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