Commit 39f47e3e authored by Andrey Zaytsev's avatar Andrey Zaytsev Committed by Commit Bot

Safety check on Android: implemented logic for the initial states

The 10 minute timeout logic follows: https://docs.google.com/presentation/d/13qExIGI7BDaSUGBAXR0B02VzKcIqVt5Y_vo0lC7nC04/edit?ts=5ef0c30f#slide=id.g8a1bab3ac0_6_125

Bug: 1070620
Change-Id: I876c0b2981321d278747ad3eaaca09890a80cb5a
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2317228
Commit-Queue: Andrey Zaytsev <andzaytsev@google.com>
Reviewed-by: default avatarNatalie Chouinard <chouinard@chromium.org>
Cr-Commit-Position: refs/heads/master@{#791771}
parent e9349a54
......@@ -63,6 +63,14 @@ public class SafetyCheckCoordinator implements DefaultLifecycleObserver {
}
}
});
// Show the initial state every time the fragment is resumed (navigation from a different
// screen, app in the background, etc).
mSettingsFragment.getLifecycle().addObserver(new DefaultLifecycleObserver() {
@Override
public void onResume(LifecycleOwner lifecycleOwner) {
mMediator.setInitialState();
}
});
}
@VisibleForTesting
......
......@@ -8,6 +8,7 @@ import android.content.Intent;
import android.net.Uri;
import android.os.Handler;
import android.os.SystemClock;
import android.text.format.DateUtils;
import android.view.View;
import androidx.annotation.VisibleForTesting;
......@@ -32,6 +33,8 @@ import java.lang.ref.WeakReference;
class SafetyCheckMediator implements SafetyCheckCommonObserver {
/** The minimal amount of time to show the checking state. */
private static final int CHECKING_MIN_DURATION_MS = 1000;
/** Time after which the null-states will be shown: 10 minutes. */
private static final long RESET_TO_NULL_AFTER_MS = 10 * DateUtils.MINUTE_IN_MILLIS;
/** Bridge to the C++ side for the Safe Browsing and passwords checks. */
private SafetyCheckBridge mSafetyCheckBridge;
......@@ -76,6 +79,8 @@ class SafetyCheckMediator implements SafetyCheckCommonObserver {
this(model, client, settingsLauncher, null, new Handler());
// Have to initialize this after the constructor call, since a "this" instance is needed.
mSafetyCheckBridge = new SafetyCheckBridge(SafetyCheckMediator.this);
// Determine and set the initial state.
setInitialState();
}
@VisibleForTesting
......@@ -128,6 +133,44 @@ class SafetyCheckMediator implements SafetyCheckCommonObserver {
mModel.set(SafetyCheckProperties.LAST_RUN_TIMESTAMP,
mPreferenceManager.readLong(
ChromePreferenceKeys.SETTINGS_SAFETY_CHECK_LAST_RUN_TIMESTAMP, 0));
if (mSafetyCheckBridge != null) {
// Determine and set the initial state.
setInitialState();
}
}
/**
* Determines the initial state to show, triggering any fast checks if necessary based on the
* last run timestamp.
*/
public void setInitialState() {
long currentTime = System.currentTimeMillis();
long lastRun = mPreferenceManager.readLong(
ChromePreferenceKeys.SETTINGS_SAFETY_CHECK_LAST_RUN_TIMESTAMP, 0);
// Always show the passwords unsafe state.
if (mSafetyCheckBridge.getNumberOfPasswordLeaksFromLastCheck() != 0) {
mModel.set(SafetyCheckProperties.PASSWORDS_STATE, PasswordsState.COMPROMISED_EXIST);
}
if (currentTime - lastRun < RESET_TO_NULL_AFTER_MS) {
// Show the passwords safe state
if (!mSafetyCheckBridge.savedPasswordsExist()) {
mModel.set(SafetyCheckProperties.PASSWORDS_STATE, PasswordsState.NO_PASSWORDS);
} else if (mSafetyCheckBridge.getNumberOfPasswordLeaksFromLastCheck() == 0) {
mModel.set(SafetyCheckProperties.PASSWORDS_STATE, PasswordsState.SAFE);
}
// Rerun the updates and Safe Browsing checks.
mModel.set(SafetyCheckProperties.SAFE_BROWSING_STATE, SafeBrowsingState.CHECKING);
mModel.set(SafetyCheckProperties.UPDATES_STATE, UpdatesState.CHECKING);
mSafetyCheckBridge.checkSafeBrowsing();
mUpdatesClient.checkForUpdates(new WeakReference(mUpdatesCheckCallback));
} else {
// The unsafe state was already set above, so only set to unchecked if it is safe.
if (mSafetyCheckBridge.getNumberOfPasswordLeaksFromLastCheck() == 0) {
mModel.set(SafetyCheckProperties.PASSWORDS_STATE, PasswordsState.UNCHECKED);
}
mModel.set(SafetyCheckProperties.SAFE_BROWSING_STATE, SafeBrowsingState.UNCHECKED);
mModel.set(SafetyCheckProperties.UPDATES_STATE, UpdatesState.UNCHECKED);
}
}
/** Triggers all safety check child checks. */
......
......@@ -21,6 +21,8 @@ import org.mockito.MockitoAnnotations;
import org.chromium.base.Callback;
import org.chromium.base.test.BaseRobolectricTestRunner;
import org.chromium.chrome.browser.password_check.BulkLeakCheckServiceState;
import org.chromium.chrome.browser.preferences.ChromePreferenceKeys;
import org.chromium.chrome.browser.preferences.SharedPreferencesManager;
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.UpdatesState;
......@@ -176,4 +178,172 @@ public class SafetyCheckMediatorTest {
assertEquals(PasswordsState.COMPROMISED_EXIST,
mModel.get(SafetyCheckProperties.PASSWORDS_STATE));
}
@Test
public void testNullStateLessThan10MinsPasswordsSafeState() {
// Ran just now.
SharedPreferencesManager preferenceManager = SharedPreferencesManager.getInstance();
preferenceManager.writeLong(ChromePreferenceKeys.SETTINGS_SAFETY_CHECK_LAST_RUN_TIMESTAMP,
System.currentTimeMillis());
// Safe Browsing: on.
doAnswer(invocation -> {
mMediator.onSafeBrowsingCheckResult(SafeBrowsingStatus.ENABLED_STANDARD);
return null;
})
.when(mBridge)
.checkSafeBrowsing();
// Passwords: safe state.
when(mBridge.savedPasswordsExist()).thenReturn(true);
when(mBridge.getNumberOfPasswordLeaksFromLastCheck()).thenReturn(0);
// Updates: outdated.
doAnswer(invocation -> {
Callback<Integer> callback =
((WeakReference<Callback<Integer>>) invocation.getArguments()[0]).get();
callback.onResult(UpdatesState.OUTDATED);
return null;
})
.when(mUpdatesDelegate)
.checkForUpdates(any(WeakReference.class));
mMediator.setInitialState();
// Verify the states.
assertEquals(SafeBrowsingState.ENABLED_STANDARD,
mModel.get(SafetyCheckProperties.SAFE_BROWSING_STATE));
assertEquals(PasswordsState.SAFE, mModel.get(SafetyCheckProperties.PASSWORDS_STATE));
assertEquals(UpdatesState.OUTDATED, mModel.get(SafetyCheckProperties.UPDATES_STATE));
}
@Test
public void testNullStateLessThan10MinsNoSavedPasswords() {
// Ran just now.
SharedPreferencesManager preferenceManager = SharedPreferencesManager.getInstance();
preferenceManager.writeLong(ChromePreferenceKeys.SETTINGS_SAFETY_CHECK_LAST_RUN_TIMESTAMP,
System.currentTimeMillis());
// Safe Browsing: disabled by admin.
doAnswer(invocation -> {
mMediator.onSafeBrowsingCheckResult(SafeBrowsingStatus.DISABLED_BY_ADMIN);
return null;
})
.when(mBridge)
.checkSafeBrowsing();
// Passwords: no passwords.
when(mBridge.savedPasswordsExist()).thenReturn(false);
when(mBridge.getNumberOfPasswordLeaksFromLastCheck()).thenReturn(0);
// Updates: offline.
doAnswer(invocation -> {
Callback<Integer> callback =
((WeakReference<Callback<Integer>>) invocation.getArguments()[0]).get();
callback.onResult(UpdatesState.OFFLINE);
return null;
})
.when(mUpdatesDelegate)
.checkForUpdates(any(WeakReference.class));
mMediator.setInitialState();
// Verify the states.
assertEquals(SafeBrowsingState.DISABLED_BY_ADMIN,
mModel.get(SafetyCheckProperties.SAFE_BROWSING_STATE));
assertEquals(
PasswordsState.NO_PASSWORDS, mModel.get(SafetyCheckProperties.PASSWORDS_STATE));
assertEquals(UpdatesState.OFFLINE, mModel.get(SafetyCheckProperties.UPDATES_STATE));
}
@Test
public void testNullStateLessThan10MinsPasswordsUnsafeState() {
// Ran just now.
SharedPreferencesManager preferenceManager = SharedPreferencesManager.getInstance();
preferenceManager.writeLong(ChromePreferenceKeys.SETTINGS_SAFETY_CHECK_LAST_RUN_TIMESTAMP,
System.currentTimeMillis());
// Safe Browsing: off.
doAnswer(invocation -> {
mMediator.onSafeBrowsingCheckResult(SafeBrowsingStatus.DISABLED);
return null;
})
.when(mBridge)
.checkSafeBrowsing();
// Passwords: compromised state.
when(mBridge.savedPasswordsExist()).thenReturn(true);
when(mBridge.getNumberOfPasswordLeaksFromLastCheck()).thenReturn(18);
// Updates: updated.
doAnswer(invocation -> {
Callback<Integer> callback =
((WeakReference<Callback<Integer>>) invocation.getArguments()[0]).get();
callback.onResult(UpdatesState.UPDATED);
return null;
})
.when(mUpdatesDelegate)
.checkForUpdates(any(WeakReference.class));
mMediator.setInitialState();
// Verify the states.
assertEquals(
SafeBrowsingState.DISABLED, mModel.get(SafetyCheckProperties.SAFE_BROWSING_STATE));
assertEquals(PasswordsState.COMPROMISED_EXIST,
mModel.get(SafetyCheckProperties.PASSWORDS_STATE));
assertEquals(UpdatesState.UPDATED, mModel.get(SafetyCheckProperties.UPDATES_STATE));
}
@Test
public void testNullStateMoreThan10MinsPasswordsSafeState() {
// Ran 20 mins ago.
SharedPreferencesManager preferenceManager = SharedPreferencesManager.getInstance();
preferenceManager.writeLong(ChromePreferenceKeys.SETTINGS_SAFETY_CHECK_LAST_RUN_TIMESTAMP,
System.currentTimeMillis() - (20 * 60 * 1000));
// Safe Browsing: on.
doAnswer(invocation -> {
mMediator.onSafeBrowsingCheckResult(SafeBrowsingStatus.ENABLED_STANDARD);
return null;
})
.when(mBridge)
.checkSafeBrowsing();
// Passwords: safe state.
when(mBridge.savedPasswordsExist()).thenReturn(true);
when(mBridge.getNumberOfPasswordLeaksFromLastCheck()).thenReturn(0);
// Updates: outdated.
doAnswer(invocation -> {
Callback<Integer> callback =
((WeakReference<Callback<Integer>>) invocation.getArguments()[0]).get();
callback.onResult(UpdatesState.OUTDATED);
return null;
})
.when(mUpdatesDelegate)
.checkForUpdates(any(WeakReference.class));
mMediator.setInitialState();
// Verify the states.
assertEquals(
SafeBrowsingState.UNCHECKED, mModel.get(SafetyCheckProperties.SAFE_BROWSING_STATE));
assertEquals(PasswordsState.UNCHECKED, mModel.get(SafetyCheckProperties.PASSWORDS_STATE));
assertEquals(UpdatesState.UNCHECKED, mModel.get(SafetyCheckProperties.UPDATES_STATE));
}
@Test
public void testNullStateMoreThan10MinsPasswordsUnsafeState() {
// Ran 20 mins ago.
SharedPreferencesManager preferenceManager = SharedPreferencesManager.getInstance();
preferenceManager.writeLong(ChromePreferenceKeys.SETTINGS_SAFETY_CHECK_LAST_RUN_TIMESTAMP,
System.currentTimeMillis() - (20 * 60 * 1000));
// Safe Browsing: off.
doAnswer(invocation -> {
mMediator.onSafeBrowsingCheckResult(SafeBrowsingStatus.DISABLED);
return null;
})
.when(mBridge)
.checkSafeBrowsing();
// Passwords: compromised state.
when(mBridge.savedPasswordsExist()).thenReturn(true);
when(mBridge.getNumberOfPasswordLeaksFromLastCheck()).thenReturn(18);
// Updates: updated.
doAnswer(invocation -> {
Callback<Integer> callback =
((WeakReference<Callback<Integer>>) invocation.getArguments()[0]).get();
callback.onResult(UpdatesState.UPDATED);
return null;
})
.when(mUpdatesDelegate)
.checkForUpdates(any(WeakReference.class));
mMediator.setInitialState();
// Verify the states.
assertEquals(
SafeBrowsingState.UNCHECKED, mModel.get(SafetyCheckProperties.SAFE_BROWSING_STATE));
assertEquals(PasswordsState.COMPROMISED_EXIST,
mModel.get(SafetyCheckProperties.PASSWORDS_STATE));
assertEquals(UpdatesState.UNCHECKED, mModel.get(SafetyCheckProperties.UPDATES_STATE));
}
}
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