Commit b12c9b56 authored by Xinghui Lu's avatar Xinghui Lu Committed by Commit Bot

Add Safe Browsing no protection confirmation dialog.

If a user checks no protection, a dialog will pop up to double check if
the user wants to disable Safe Browsing.

screenshot: http://screen/jLDJGvZN4cy

Bug: 1097310
Change-Id: Ib8008e4502dec8762785a84a7048123b5d224319
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2327320Reviewed-by: default avatarNatalie Chouinard <chouinard@chromium.org>
Reviewed-by: default avatarTheresa  <twellington@chromium.org>
Reviewed-by: default avatarVarun Khaneja <vakh@chromium.org>
Reviewed-by: default avatarPavel Yatsuk <pavely@chromium.org>
Commit-Queue: Xinghui Lu <xinghuilu@chromium.org>
Cr-Commit-Position: refs/heads/master@{#795228}
parent a02c341b
...@@ -25,6 +25,7 @@ android_library("java") { ...@@ -25,6 +25,7 @@ android_library("java") {
sources = [ sources = [
"java/src/org/chromium/chrome/browser/safe_browsing/SafeBrowsingBridge.java", "java/src/org/chromium/chrome/browser/safe_browsing/SafeBrowsingBridge.java",
"java/src/org/chromium/chrome/browser/safe_browsing/settings/EnhancedProtectionSettingsFragment.java", "java/src/org/chromium/chrome/browser/safe_browsing/settings/EnhancedProtectionSettingsFragment.java",
"java/src/org/chromium/chrome/browser/safe_browsing/settings/NoProtectionConfirmationDialog.java",
"java/src/org/chromium/chrome/browser/safe_browsing/settings/RadioButtonGroupSafeBrowsingPreference.java", "java/src/org/chromium/chrome/browser/safe_browsing/settings/RadioButtonGroupSafeBrowsingPreference.java",
"java/src/org/chromium/chrome/browser/safe_browsing/settings/SecuritySettingsFragment.java", "java/src/org/chromium/chrome/browser/safe_browsing/settings/SecuritySettingsFragment.java",
"java/src/org/chromium/chrome/browser/safe_browsing/settings/StandardProtectionSettingsFragment.java", "java/src/org/chromium/chrome/browser/safe_browsing/settings/StandardProtectionSettingsFragment.java",
...@@ -37,6 +38,7 @@ android_library("java") { ...@@ -37,6 +38,7 @@ android_library("java") {
"//chrome/browser/preferences:java", "//chrome/browser/preferences:java",
"//chrome/browser/profiles/android:java", "//chrome/browser/profiles/android:java",
"//chrome/browser/settings:java", "//chrome/browser/settings:java",
"//components/browser_ui/modaldialog/android:java",
"//components/browser_ui/settings/android:java", "//components/browser_ui/settings/android:java",
"//components/browser_ui/widget/android:java", "//components/browser_ui/widget/android:java",
"//components/prefs/android:java", "//components/prefs/android:java",
...@@ -74,6 +76,7 @@ android_library("javatests") { ...@@ -74,6 +76,7 @@ android_library("javatests") {
"//components/user_prefs/android:java", "//components/user_prefs/android:java",
"//content/public/test/android:content_java_test_support", "//content/public/test/android:content_java_test_support",
"//third_party/android_deps:androidx_test_runner_java", "//third_party/android_deps:androidx_test_runner_java",
"//third_party/android_deps:espresso_java",
"//third_party/android_support_test_runner:runner_java", "//third_party/android_support_test_runner:runner_java",
"//third_party/junit", "//third_party/junit",
"//third_party/mockito:mockito_java", "//third_party/mockito:mockito_java",
......
include_rules = [
"+components/browser_ui/modaldialog/android",
]
// 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.safe_browsing.settings;
import android.content.Context;
import android.content.res.Resources;
import org.chromium.base.Callback;
import org.chromium.components.browser_ui.modaldialog.AppModalPresenter;
import org.chromium.ui.modaldialog.DialogDismissalCause;
import org.chromium.ui.modaldialog.ModalDialogManager;
import org.chromium.ui.modaldialog.ModalDialogManager.ModalDialogType;
import org.chromium.ui.modaldialog.ModalDialogProperties;
import org.chromium.ui.modaldialog.ModalDialogProperties.Controller;
import org.chromium.ui.modelutil.PropertyModel;
/**
* Dialog to confirm if the user is sure to disable Safe Browsing.
*/
public class NoProtectionConfirmationDialog {
private Context mContext;
private ModalDialogManager mManager;
private PropertyModel mModel;
private Callback<Boolean> mDidConfirmCallback;
public static NoProtectionConfirmationDialog create(
Context context, Callback<Boolean> didConfirmCallback) {
return new NoProtectionConfirmationDialog(context, didConfirmCallback);
}
private NoProtectionConfirmationDialog(Context context, Callback<Boolean> didConfirmCallback) {
mContext = context;
mDidConfirmCallback = didConfirmCallback;
}
/** Show this dialog in the context of its enclosing activity. */
public void show() {
Resources resources = mContext.getResources();
PropertyModel.Builder builder =
new PropertyModel.Builder(ModalDialogProperties.ALL_KEYS)
.with(ModalDialogProperties.CONTROLLER, makeController())
.with(ModalDialogProperties.TITLE, resources,
R.string.safe_browsing_no_protection_confirmation_dialog_title)
.with(ModalDialogProperties.MESSAGE, resources,
R.string.safe_browsing_no_protection_confirmation_dialog_message)
.with(ModalDialogProperties.POSITIVE_BUTTON_TEXT, resources,
R.string.safe_browsing_no_protection_confirmation_dialog_confirm)
.with(ModalDialogProperties.PRIMARY_BUTTON_FILLED, true)
.with(ModalDialogProperties.NEGATIVE_BUTTON_TEXT, resources,
R.string.cancel);
mModel = builder.build();
mManager = new ModalDialogManager(new AppModalPresenter(mContext), ModalDialogType.APP);
mManager.showDialog(mModel, ModalDialogType.APP);
}
private Controller makeController() {
return new ModalDialogProperties.Controller() {
@Override
public void onClick(PropertyModel model, int buttonType) {
switch (buttonType) {
case ModalDialogProperties.ButtonType.POSITIVE:
mManager.dismissDialog(
mModel, DialogDismissalCause.POSITIVE_BUTTON_CLICKED);
break;
case ModalDialogProperties.ButtonType.NEGATIVE:
mManager.dismissDialog(
mModel, DialogDismissalCause.NEGATIVE_BUTTON_CLICKED);
break;
default:
assert false : "Should not be reached.";
}
}
@Override
public void onDismiss(PropertyModel model, int dismissalCause) {
if (dismissalCause == DialogDismissalCause.POSITIVE_BUTTON_CLICKED) {
mDidConfirmCallback.onResult(true);
} else {
mDidConfirmCallback.onResult(false);
}
mManager.destroy();
}
};
}
}
...@@ -106,15 +106,7 @@ public class RadioButtonGroupSafeBrowsingPreference extends Preference ...@@ -106,15 +106,7 @@ public class RadioButtonGroupSafeBrowsingPreference extends Preference
(RadioButtonWithDescriptionLayout) mNoProtection.getRootView(); (RadioButtonWithDescriptionLayout) mNoProtection.getRootView();
groupLayout.setOnCheckedChangeListener(this); groupLayout.setOnCheckedChangeListener(this);
assert ((mSafeBrowsingState != SafeBrowsingState.ENHANCED_PROTECTION) setCheckedState(mSafeBrowsingState);
|| mIsEnhancedProtectionEnabled)
: "Safe Browsing state shouldn't be enhanced protection when the flag is disabled.";
if (mIsEnhancedProtectionEnabled) {
mEnhancedProtection.setChecked(
mSafeBrowsingState == SafeBrowsingState.ENHANCED_PROTECTION);
}
mStandardProtection.setChecked(mSafeBrowsingState == SafeBrowsingState.STANDARD_PROTECTION);
mNoProtection.setChecked(mSafeBrowsingState == SafeBrowsingState.NO_SAFE_BROWSING);
// If Safe Browsing is managed, disable the radio button group, but keep the aux buttons // If Safe Browsing is managed, disable the radio button group, but keep the aux buttons
// enabled to disclose information. // enabled to disclose information.
...@@ -160,6 +152,23 @@ public class RadioButtonGroupSafeBrowsingPreference extends Preference ...@@ -160,6 +152,23 @@ public class RadioButtonGroupSafeBrowsingPreference extends Preference
ManagedPreferencesUtils.initPreference(mManagedPrefDelegate, this); ManagedPreferencesUtils.initPreference(mManagedPrefDelegate, this);
} }
/**
* Sets the checked state of the Safe Browsing radio button group.
* @param checkedState Set the radio button of checkedState to checked, and set the radio
* buttons of other states to unchecked.
*/
public void setCheckedState(@SafeBrowsingState int checkedState) {
mSafeBrowsingState = checkedState;
assert ((checkedState != SafeBrowsingState.ENHANCED_PROTECTION)
|| mIsEnhancedProtectionEnabled)
: "Checked state shouldn't be enhanced protection when the flag is disabled.";
if (mIsEnhancedProtectionEnabled) {
mEnhancedProtection.setChecked(checkedState == SafeBrowsingState.ENHANCED_PROTECTION);
}
mStandardProtection.setChecked(checkedState == SafeBrowsingState.STANDARD_PROTECTION);
mNoProtection.setChecked(checkedState == SafeBrowsingState.NO_SAFE_BROWSING);
}
@VisibleForTesting @VisibleForTesting
public @SafeBrowsingState int getSafeBrowsingStateForTesting() { public @SafeBrowsingState int getSafeBrowsingStateForTesting() {
return mSafeBrowsingState; return mSafeBrowsingState;
......
...@@ -7,6 +7,7 @@ package org.chromium.chrome.browser.safe_browsing.settings; ...@@ -7,6 +7,7 @@ package org.chromium.chrome.browser.safe_browsing.settings;
import android.os.Bundle; import android.os.Bundle;
import androidx.annotation.VisibleForTesting; import androidx.annotation.VisibleForTesting;
import androidx.preference.Preference;
import androidx.preference.PreferenceFragmentCompat; import androidx.preference.PreferenceFragmentCompat;
import org.chromium.chrome.browser.flags.ChromeFeatureList; import org.chromium.chrome.browser.flags.ChromeFeatureList;
...@@ -24,7 +25,8 @@ import org.chromium.components.browser_ui.settings.TextMessagePreference; ...@@ -24,7 +25,8 @@ import org.chromium.components.browser_ui.settings.TextMessagePreference;
*/ */
public class SecuritySettingsFragment extends PreferenceFragmentCompat public class SecuritySettingsFragment extends PreferenceFragmentCompat
implements FragmentSettingsLauncher, implements FragmentSettingsLauncher,
RadioButtonGroupSafeBrowsingPreference.OnSafeBrowsingModeDetailsRequested { RadioButtonGroupSafeBrowsingPreference.OnSafeBrowsingModeDetailsRequested,
Preference.OnPreferenceChangeListener {
@VisibleForTesting @VisibleForTesting
static final String PREF_TEXT_MANAGED = "text_managed"; static final String PREF_TEXT_MANAGED = "text_managed";
@VisibleForTesting @VisibleForTesting
...@@ -33,6 +35,8 @@ public class SecuritySettingsFragment extends PreferenceFragmentCompat ...@@ -33,6 +35,8 @@ public class SecuritySettingsFragment extends PreferenceFragmentCompat
// An instance of SettingsLauncher that is used to launch Safe Browsing subsections. // An instance of SettingsLauncher that is used to launch Safe Browsing subsections.
private SettingsLauncher mSettingsLauncher; private SettingsLauncher mSettingsLauncher;
private RadioButtonGroupSafeBrowsingPreference mSafeBrowsingPreference;
@Override @Override
public void onCreatePreferences(Bundle bundle, String s) { public void onCreatePreferences(Bundle bundle, String s) {
SettingsUtils.addPreferencesFromResource(this, R.xml.security_preferences); SettingsUtils.addPreferencesFromResource(this, R.xml.security_preferences);
...@@ -40,24 +44,18 @@ public class SecuritySettingsFragment extends PreferenceFragmentCompat ...@@ -40,24 +44,18 @@ public class SecuritySettingsFragment extends PreferenceFragmentCompat
ManagedPreferenceDelegate managedPreferenceDelegate = createManagedPreferenceDelegate(); ManagedPreferenceDelegate managedPreferenceDelegate = createManagedPreferenceDelegate();
RadioButtonGroupSafeBrowsingPreference safeBrowsingPreference = mSafeBrowsingPreference = findPreference(PREF_SAFE_BROWSING);
findPreference(PREF_SAFE_BROWSING); mSafeBrowsingPreference.init(SafeBrowsingBridge.getSafeBrowsingState(),
safeBrowsingPreference.init(SafeBrowsingBridge.getSafeBrowsingState(),
ChromeFeatureList.isEnabled( ChromeFeatureList.isEnabled(
ChromeFeatureList.SAFE_BROWSING_ENHANCED_PROTECTION_ENABLED)); ChromeFeatureList.SAFE_BROWSING_ENHANCED_PROTECTION_ENABLED));
safeBrowsingPreference.setSafeBrowsingModeDetailsRequestedListener(this); mSafeBrowsingPreference.setSafeBrowsingModeDetailsRequestedListener(this);
safeBrowsingPreference.setManagedPreferenceDelegate(managedPreferenceDelegate); mSafeBrowsingPreference.setManagedPreferenceDelegate(managedPreferenceDelegate);
safeBrowsingPreference.setOnPreferenceChangeListener((preference, newValue) -> { mSafeBrowsingPreference.setOnPreferenceChangeListener(this);
@SafeBrowsingState
int newState = (int) newValue;
SafeBrowsingBridge.setSafeBrowsingState(newState);
return true;
});
TextMessagePreference textManaged = findPreference(PREF_TEXT_MANAGED); TextMessagePreference textManaged = findPreference(PREF_TEXT_MANAGED);
textManaged.setManagedPreferenceDelegate(managedPreferenceDelegate); textManaged.setManagedPreferenceDelegate(managedPreferenceDelegate);
textManaged.setVisible(managedPreferenceDelegate.isPreferenceClickDisabledByPolicy( textManaged.setVisible(managedPreferenceDelegate.isPreferenceClickDisabledByPolicy(
safeBrowsingPreference)); mSafeBrowsingPreference));
} }
@Override @Override
...@@ -89,4 +87,40 @@ public class SecuritySettingsFragment extends PreferenceFragmentCompat ...@@ -89,4 +87,40 @@ public class SecuritySettingsFragment extends PreferenceFragmentCompat
return false; return false;
}; };
} }
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
String key = preference.getKey();
assert PREF_SAFE_BROWSING.equals(key) : "Unexpected preference key.";
@SafeBrowsingState
int newState = (int) newValue;
@SafeBrowsingState
int currentState = SafeBrowsingBridge.getSafeBrowsingState();
// If the user selects no protection from another Safe Browsing state, show a confirmation
// dialog to double check if they want to select no protection.
if (newState == SafeBrowsingState.NO_SAFE_BROWSING
&& currentState != SafeBrowsingState.NO_SAFE_BROWSING) {
// The user hasn't confirmed to select no protection, keep the radio button / UI checked
// state at the currently selected level.
mSafeBrowsingPreference.setCheckedState(currentState);
NoProtectionConfirmationDialog
.create(getContext(),
(didConfirm) -> {
if (didConfirm) {
// The user has confirmed to select no protection, set Safe
// Browsing pref to no protection, and change the radio button /
// UI checked state to no protection.
SafeBrowsingBridge.setSafeBrowsingState(
SafeBrowsingState.NO_SAFE_BROWSING);
mSafeBrowsingPreference.setCheckedState(
SafeBrowsingState.NO_SAFE_BROWSING);
}
// No-ops if the user denies.
})
.show();
} else {
SafeBrowsingBridge.setSafeBrowsingState(newState);
}
return true;
}
} }
...@@ -4,6 +4,13 @@ ...@@ -4,6 +4,13 @@
package org.chromium.chrome.browser.safe_browsing.settings; package org.chromium.chrome.browser.safe_browsing.settings;
import static androidx.test.espresso.Espresso.onView;
import static androidx.test.espresso.action.ViewActions.click;
import static androidx.test.espresso.assertion.ViewAssertions.doesNotExist;
import static androidx.test.espresso.assertion.ViewAssertions.matches;
import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
import static androidx.test.espresso.matcher.ViewMatchers.withText;
import androidx.test.filters.SmallTest; import androidx.test.filters.SmallTest;
import org.junit.Assert; import org.junit.Assert;
...@@ -134,19 +141,103 @@ public class SecuritySettingsFragmentTest { ...@@ -134,19 +141,103 @@ public class SecuritySettingsFragmentTest {
Assert.assertEquals(ASSERT_SAFE_BROWSING_STATE_NATIVE, Assert.assertEquals(ASSERT_SAFE_BROWSING_STATE_NATIVE,
SafeBrowsingState.STANDARD_PROTECTION, SafeBrowsingState.STANDARD_PROTECTION,
SafeBrowsingBridge.getSafeBrowsingState()); SafeBrowsingBridge.getSafeBrowsingState());
});
}
// Click the No Protection button. @Test
@SmallTest
@Feature({"SafeBrowsing"})
@Features.EnableFeatures(ChromeFeatureList.SAFE_BROWSING_ENHANCED_PROTECTION_ENABLED)
public void testCheckNoProtectionRadioButtonsCancel() {
TestThreadUtils.runOnUiThreadBlocking(() -> {
ChromeBrowserInitializer.getInstance().handleSynchronousStartup();
SafeBrowsingBridge.setSafeBrowsingState(SafeBrowsingState.ENHANCED_PROTECTION);
});
launchSettingsActivity();
TestThreadUtils.runOnUiThreadBlocking(() -> {
getNoProtectionButton().onClick(null); getNoProtectionButton().onClick(null);
Assert.assertEquals(ASSERT_SAFE_BROWSING_STATE_RADIO_BUTTON_GROUP,
SafeBrowsingState.NO_SAFE_BROWSING, getSafeBrowsingState()); // Checked button hasn't changed yet, because confirmation is pending.
Assert.assertFalse( Assert.assertTrue(
ASSERT_RADIO_BUTTON_CHECKED, getEnhancedProtectionButton().isChecked());
Assert.assertFalse(ASSERT_RADIO_BUTTON_CHECKED, getNoProtectionButton().isChecked());
});
// The dialog is displayed.
onView(withText(R.string.safe_browsing_no_protection_confirmation_dialog_title))
.check(matches(isDisplayed()));
// Don't confirm.
onView(withText(R.string.cancel)).perform(click());
TestThreadUtils.runOnUiThreadBlocking(() -> {
// It should stay in enhanced protection mode.
Assert.assertTrue(
ASSERT_RADIO_BUTTON_CHECKED, getEnhancedProtectionButton().isChecked());
Assert.assertFalse(ASSERT_RADIO_BUTTON_CHECKED, getNoProtectionButton().isChecked());
Assert.assertEquals(ASSERT_SAFE_BROWSING_STATE_NATIVE,
SafeBrowsingState.ENHANCED_PROTECTION,
SafeBrowsingBridge.getSafeBrowsingState());
});
// The confirmation dialog should be gone.
onView(withText(R.string.safe_browsing_no_protection_confirmation_dialog_title))
.check(doesNotExist());
}
@Test
@SmallTest
@Feature({"SafeBrowsing"})
@Features.EnableFeatures(ChromeFeatureList.SAFE_BROWSING_ENHANCED_PROTECTION_ENABLED)
public void testCheckNoProtectionRadioButtonsConfirm() {
TestThreadUtils.runOnUiThreadBlocking(() -> {
ChromeBrowserInitializer.getInstance().handleSynchronousStartup();
SafeBrowsingBridge.setSafeBrowsingState(SafeBrowsingState.ENHANCED_PROTECTION);
});
launchSettingsActivity();
TestThreadUtils.runOnUiThreadBlocking(() -> {
getNoProtectionButton().onClick(null);
// Checked button is not changed yet, because confirmation is pending.
Assert.assertTrue(
ASSERT_RADIO_BUTTON_CHECKED, getEnhancedProtectionButton().isChecked()); ASSERT_RADIO_BUTTON_CHECKED, getEnhancedProtectionButton().isChecked());
Assert.assertFalse(ASSERT_RADIO_BUTTON_CHECKED, getNoProtectionButton().isChecked());
});
// The dialog is displayed.
onView(withText(R.string.safe_browsing_no_protection_confirmation_dialog_title))
.check(matches(isDisplayed()));
// Confirm.
onView(withText(R.string.safe_browsing_no_protection_confirmation_dialog_confirm))
.perform(click());
TestThreadUtils.runOnUiThreadBlocking(() -> {
Assert.assertFalse( Assert.assertFalse(
ASSERT_RADIO_BUTTON_CHECKED, getStandardProtectionButton().isChecked()); ASSERT_RADIO_BUTTON_CHECKED, getEnhancedProtectionButton().isChecked());
Assert.assertTrue(ASSERT_RADIO_BUTTON_CHECKED, getNoProtectionButton().isChecked()); Assert.assertTrue(ASSERT_RADIO_BUTTON_CHECKED, getNoProtectionButton().isChecked());
Assert.assertEquals(ASSERT_SAFE_BROWSING_STATE_NATIVE, Assert.assertEquals(ASSERT_SAFE_BROWSING_STATE_NATIVE,
SafeBrowsingState.NO_SAFE_BROWSING, SafeBrowsingBridge.getSafeBrowsingState()); SafeBrowsingState.NO_SAFE_BROWSING, SafeBrowsingBridge.getSafeBrowsingState());
}); });
// The confirmation dialog should be gone.
onView(withText(R.string.safe_browsing_no_protection_confirmation_dialog_title))
.check(doesNotExist());
}
@Test
@SmallTest
@Feature({"SafeBrowsing"})
@Features.EnableFeatures(ChromeFeatureList.SAFE_BROWSING_ENHANCED_PROTECTION_ENABLED)
public void testCheckNoProtectionConfirmationIfAlreadyInNoProtectionMode() {
TestThreadUtils.runOnUiThreadBlocking(() -> {
ChromeBrowserInitializer.getInstance().handleSynchronousStartup();
SafeBrowsingBridge.setSafeBrowsingState(SafeBrowsingState.NO_SAFE_BROWSING);
});
launchSettingsActivity();
TestThreadUtils.runOnUiThreadBlocking(() -> { getNoProtectionButton().onClick(null); });
// Since it is already in no protection mode, the dialog shouldn't be shown.
onView(withText(R.string.safe_browsing_no_protection_confirmation_dialog_title))
.check(doesNotExist());
} }
@Test @Test
......
...@@ -1015,6 +1015,17 @@ Your Google account may have other forms of browsing history like searches and a ...@@ -1015,6 +1015,17 @@ Your Google account may have other forms of browsing history like searches and a
Sends URLs of some pages you visit, limited system information, and some page content to Google, to help discover new threats and protect everyone on the web. Sends URLs of some pages you visit, limited system information, and some page content to Google, to help discover new threats and protect everyone on the web.
</message> </message>
<!-- Safe Browsing no protection confirmation dialog -->
<message name="IDS_SAFE_BROWSING_NO_PROTECTION_CONFIRMATION_DIALOG_TITLE" desc="Title for Safe Browsing no protection confirmation dialog.">
Turn off Safe Browsing?
</message>
<message name="IDS_SAFE_BROWSING_NO_PROTECTION_CONFIRMATION_DIALOG_MESSAGE" desc="Message for Safe Browsing no protection confirmation dialog.">
Safe Browsing protects you against deceptive websites. If you turn it off, be extra careful when browsing, especially before entering passwords.
</message>
<message name="IDS_SAFE_BROWSING_NO_PROTECTION_CONFIRMATION_DIALOG_CONFIRM" desc="Message for Safe Browsing no protection confirmation button.">
Turn off
</message>
<!-- Accessibility preferences --> <!-- Accessibility preferences -->
<message name="IDS_PREFS_ACCESSIBILITY" desc="Title of Accessibility settings, which allows the user to change webpage font sizes. [CHAR-LIMIT=32]"> <message name="IDS_PREFS_ACCESSIBILITY" desc="Title of Accessibility settings, which allows the user to change webpage font sizes. [CHAR-LIMIT=32]">
Accessibility Accessibility
......
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