Commit ac8291e1 authored by James Cook's avatar James Cook Committed by Commit Bot

SplitSettingsSync: Show sync consent dialog on first browser open

Show the existing browser sync consent dialog when the first browser
window opens. Long-term we will add a first-run "welcome" WebUI,
which will instantiate the helper. For now this unblocks development
on other browser sync settings UI.

Basically, the parts that observe the browser list are speculative,
everything else we will probably keep.

Screenshot: http://screen/CLjXCGd5SQn

Bug: 1013466
Test: added to unit_tests
Change-Id: I8510527b692daf42e3a87e40f4fc5ca36c7f8993
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1930946Reviewed-by: default avatarKyle Horimoto <khorimoto@chromium.org>
Commit-Queue: James Cook <jamescook@chromium.org>
Cr-Commit-Position: refs/heads/master@{#726982}
parent 4e9fbb31
......@@ -224,6 +224,7 @@ source_set("chromeos") {
"//components/tracing:startup_tracing",
"//components/translate/core/browser",
"//components/ukm/content",
"//components/unified_consent",
"//components/user_manager",
"//components/vector_icons",
......@@ -2183,6 +2184,8 @@ source_set("chromeos") {
"sync/os_sync_model_type_controller.h",
"sync/os_syncable_service_model_type_controller.cc",
"sync/os_syncable_service_model_type_controller.h",
"sync/turn_sync_on_helper.cc",
"sync/turn_sync_on_helper.h",
"system/automatic_reboot_manager.cc",
"system/automatic_reboot_manager.h",
"system/automatic_reboot_manager_observer.h",
......@@ -2987,6 +2990,7 @@ source_set("unit_tests") {
"smb_client/smb_url_unittest.cc",
"smb_client/temp_file_manager_unittest.cc",
"startup_settings_cache_unittest.cc",
"sync/turn_sync_on_helper_unittest.cc",
"system/automatic_reboot_manager_unittest.cc",
"system/device_disabling_manager_unittest.cc",
"system/procfs_util_unittest.cc",
......
......@@ -82,6 +82,7 @@
#include "chrome/browser/chromeos/policy/tpm_auto_update_mode_policy_handler.h"
#include "chrome/browser/chromeos/profiles/profile_helper.h"
#include "chrome/browser/chromeos/settings/cros_settings.h"
#include "chrome/browser/chromeos/sync/turn_sync_on_helper.h"
#include "chrome/browser/chromeos/tether/tether_service.h"
#include "chrome/browser/chromeos/tpm_firmware_update_notification.h"
#include "chrome/browser/chromeos/u2f_notification.h"
......@@ -100,6 +101,7 @@
#include "chrome/browser/signin/identity_manager_factory.h"
#include "chrome/browser/supervised_user/child_accounts/child_account_service.h"
#include "chrome/browser/supervised_user/child_accounts/child_account_service_factory.h"
#include "chrome/browser/sync/profile_sync_service_factory.h"
#include "chrome/browser/ui/app_list/app_list_client_impl.h"
#include "chrome/browser/ui/browser_navigator.h"
#include "chrome/browser/ui/browser_navigator_params.h"
......@@ -1768,6 +1770,11 @@ void UserSessionManager::InitializeBrowser(Profile* profile) {
// If needed, create browser observer to display first run OOBE Goodies page.
first_run::GoodiesDisplayer::Init();
if (chromeos::features::IsSplitSettingsSyncEnabled() &&
ProfileSyncServiceFactory::IsSyncAllowed(profile)) {
turn_sync_on_helper_ = std::make_unique<TurnSyncOnHelper>(profile);
}
// Schedule a flush if profile is not ephemeral.
if (!ProfileHelper::IsEphemeralUserProfile(profile))
ProfileHelper::Get()->FlushProfile(profile);
......@@ -2435,6 +2442,7 @@ bool UserSessionManager::TokenHandlesEnabled() {
}
void UserSessionManager::Shutdown() {
turn_sync_on_helper_.reset();
token_handle_fetcher_.reset();
token_handle_util_.reset();
first_run::GoodiesDisplayer::Delete();
......
......@@ -46,6 +46,7 @@ class PrefRegistrySimple;
class PrefService;
class Profile;
class TokenHandleFetcher;
class TurnSyncOnHelper;
namespace base {
class CommandLine;
......@@ -660,6 +661,8 @@ class UserSessionManager
std::unique_ptr<ReleaseNotesNotification> release_notes_notification_;
std::unique_ptr<TurnSyncOnHelper> turn_sync_on_helper_;
base::WeakPtrFactory<UserSessionManager> weak_factory_{this};
DISALLOW_COPY_AND_ASSIGN(UserSessionManager);
......
......@@ -32,6 +32,7 @@
#include "chrome/browser/chromeos/net/wake_on_wifi_manager.h"
#include "chrome/browser/chromeos/profiles/profile_helper.h"
#include "chrome/browser/chromeos/settings/cros_settings.h"
#include "chrome/browser/chromeos/sync/turn_sync_on_helper.h"
#include "chrome/browser/chromeos/system/input_device_settings.h"
#include "chrome/browser/chromeos/system/timezone_resolver_manager.h"
#include "chrome/browser/chromeos/system/timezone_util.h"
......@@ -160,6 +161,10 @@ void Preferences::RegisterPrefs(PrefRegistrySimple* registry) {
// static
void Preferences::RegisterProfilePrefs(
user_prefs::PrefRegistrySyncable* registry) {
// Some classes register their own prefs.
TurnSyncOnHelper::RegisterProfilePrefs(registry);
input_method::InputMethodSyncer::RegisterProfilePrefs(registry);
std::string hardware_keyboard_id;
// TODO(yusukes): Remove the runtime hack.
if (IsRunningAsSystemCompositor()) {
......@@ -334,8 +339,6 @@ void Preferences::RegisterProfilePrefs(
registry->RegisterBooleanPref(prefs::kTouchVirtualKeyboardEnabled, false);
input_method::InputMethodSyncer::RegisterProfilePrefs(registry);
std::string current_timezone_id;
if (chromeos::CrosSettings::IsInitialized()) {
// In unit tests CrosSettings is not always initialized.
......
// Copyright 2019 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.
#include "chrome/browser/chromeos/sync/turn_sync_on_helper.h"
#include <utility>
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/signin/identity_manager_factory.h"
#include "chrome/browser/sync/profile_sync_service_factory.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_finder.h"
#include "chrome/browser/ui/browser_list.h"
#include "chrome/browser/ui/browser_tabstrip.h"
#include "chrome/browser/ui/browser_window.h"
#include "chrome/browser/ui/chrome_pages.h"
#include "chrome/browser/ui/webui/signin/login_ui_service_factory.h"
#include "chrome/browser/unified_consent/unified_consent_service_factory.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/webui_url_constants.h"
#include "chromeos/constants/chromeos_features.h"
#include "components/prefs/pref_registry_simple.h"
#include "components/signin/public/identity_manager/identity_manager.h"
#include "components/sync/driver/sync_service.h"
#include "components/sync/driver/sync_user_settings.h"
#include "components/unified_consent/unified_consent_service.h"
#include "components/user_manager/user.h"
#include "components/user_manager/user_manager.h"
namespace {
// Whether the user has seen the sync first-run dialog. They might or might not
// have consented to sync. Other platforms don't need this because they can
// infer the no-consent state from IdentityManager::GetPrimaryAccount()
// returning an empty account, but on Chrome OS the primary user isn't allowed
// to sign out.
const char kSyncFirstRunCompleted[] = "sync.first_run_completed";
// Ensures a tabbed browser is visible and returns it.
Browser* EnsureBrowser(Browser* browser, Profile* profile) {
if (!browser) {
// The user has closed the browser that we used previously. Grab the most
// recently active browser or else create a new one.
browser = chrome::FindLastActiveWithProfile(profile);
if (!browser) {
// The browser deletes itself.
browser = Browser::Create(
Browser::CreateParams(profile, /*user_gesture=*/true));
chrome::AddTabAt(browser, GURL(), /*index=*/-1, /*foreground=*/true);
}
}
browser->window()->Show();
return browser;
}
// Production delegate with real UI.
class DelegateImpl : public TurnSyncOnHelper::Delegate {
public:
DelegateImpl() = default;
~DelegateImpl() override = default;
void ShowSyncConfirmation(Profile* profile, Browser* browser) override {
browser = EnsureBrowser(browser, profile);
browser->signin_view_controller()->ShowModalSyncConfirmationDialog(browser);
}
void ShowSyncSettings(Profile* profile, Browser* browser) override {
browser = EnsureBrowser(browser, profile);
chrome::ShowSettingsSubPage(browser, chrome::kSyncSetupSubPage);
}
};
} // namespace
TurnSyncOnHelper::TurnSyncOnHelper(Profile* profile)
: TurnSyncOnHelper(profile, std::make_unique<DelegateImpl>()) {}
TurnSyncOnHelper::TurnSyncOnHelper(Profile* profile,
std::unique_ptr<Delegate> delegate)
: profile_(profile), delegate_(std::move(delegate)) {
DCHECK(profile_);
DCHECK(chromeos::features::IsSplitSettingsSyncEnabled());
BrowserList::AddObserver(this);
}
TurnSyncOnHelper::~TurnSyncOnHelper() {
BrowserList::RemoveObserver(this);
}
// static
void TurnSyncOnHelper::RegisterProfilePrefs(PrefRegistrySimple* registry) {
registry->RegisterBooleanPref(kSyncFirstRunCompleted, false);
}
void TurnSyncOnHelper::OnBrowserSetLastActive(Browser* browser) {
// Tabbed browser window (not an app).
if (!browser->is_type_normal())
return;
// Not guest or incognito window.
if (browser->profile()->IsOffTheRecord())
return;
// Not skipping first-run.
base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
if (!command_line->HasSwitch(switches::kForceFirstRun) &&
command_line->HasSwitch(switches::kNoFirstRun)) {
return;
}
// Not previously completed.
if (profile_->GetPrefs()->GetBoolean(kSyncFirstRunCompleted))
return;
browser_ = browser;
BrowserList::RemoveObserver(this);
StartFlow();
}
void TurnSyncOnHelper::StartFlow() {
syncer::SyncService* sync_service = GetSyncService();
// Abort if sync not allowed.
if (!sync_service)
return;
// Record that the user has started the flow ("requested setup").
sync_service->GetUserSettings()->SetSyncRequested(true);
// Defer if sync engine is still starting up.
if (SyncStartupTracker::GetSyncServiceState(sync_service) ==
SyncStartupTracker::SYNC_STARTUP_PENDING) {
sync_startup_tracker_ =
std::make_unique<SyncStartupTracker>(sync_service, this);
return;
}
ShowSyncConfirmationUI();
}
void TurnSyncOnHelper::SyncStartupCompleted() {
DCHECK(sync_startup_tracker_);
sync_startup_tracker_.reset();
ShowSyncConfirmationUI();
}
void TurnSyncOnHelper::SyncStartupFailed() {
DCHECK(sync_startup_tracker_);
sync_startup_tracker_.reset();
ShowSyncConfirmationUI();
}
void TurnSyncOnHelper::ShowSyncConfirmationUI() {
// Register as an observer so OnSyncConfirmationUIClosed() will be called.
scoped_login_ui_service_observer_.Add(
LoginUIServiceFactory::GetForProfile(profile_));
delegate_->ShowSyncConfirmation(profile_, browser_);
}
void TurnSyncOnHelper::OnSyncConfirmationUIClosed(
LoginUIService::SyncConfirmationUIClosedResult result) {
FinishSyncSetup(result);
// This method can be called if the browser is closed from the shelf menu
// with the consent dialog open. Make sure we don't keep a deleted pointer.
browser_ = nullptr;
}
void TurnSyncOnHelper::FinishSyncSetup(
LoginUIService::SyncConfirmationUIClosedResult result) {
unified_consent::UnifiedConsentService* consent_service =
UnifiedConsentServiceFactory::GetForProfile(profile_);
switch (result) {
case LoginUIService::CONFIGURE_SYNC_FIRST:
if (consent_service)
consent_service->SetUrlKeyedAnonymizedDataCollectionEnabled(true);
delegate_->ShowSyncSettings(profile_, browser_);
break;
case LoginUIService::SYNC_WITH_DEFAULT_SETTINGS: {
syncer::SyncService* sync_service = GetSyncService();
if (sync_service) {
sync_service->GetUserSettings()->SetFirstSetupComplete(
syncer::SyncFirstSetupCompleteSource::BASIC_FLOW);
}
if (consent_service)
consent_service->SetUrlKeyedAnonymizedDataCollectionEnabled(true);
break;
}
case LoginUIService::ABORT_SIGNIN:
// Chrome OS users stay signed in even if sync setup is cancelled.
break;
}
profile_->GetPrefs()->SetBoolean(kSyncFirstRunCompleted, true);
}
syncer::SyncService* TurnSyncOnHelper::GetSyncService() {
return ProfileSyncServiceFactory::IsSyncAllowed(profile_)
? ProfileSyncServiceFactory::GetForProfile(profile_)
: nullptr;
}
// Copyright 2019 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.
#ifndef CHROME_BROWSER_CHROMEOS_SYNC_TURN_SYNC_ON_HELPER_H_
#define CHROME_BROWSER_CHROMEOS_SYNC_TURN_SYNC_ON_HELPER_H_
#include <memory>
#include "base/scoped_observer.h"
#include "chrome/browser/sync/sync_startup_tracker.h"
#include "chrome/browser/ui/browser_list_observer.h"
#include "chrome/browser/ui/webui/signin/login_ui_service.h"
class Browser;
class PrefRegistrySimple;
class Profile;
namespace syncer {
class SyncService;
} // namespace syncer
// Shows the browser sync consent dialog and turns on sync if the user consents.
// Similar to DiceTurnSyncOnHelper, but Chrome OS doesn't use DICE and doesn't
// allow the user to sign out.
// TODO(crbug.com/1036440): For development purposes we show the dialog
// immediately when the first browser window opens. Long-term the browser will
// open a page similar to chrome://welcome on first run. Once this browser
// first-run flow is implemented the BrowserListObserver can be removed.
class TurnSyncOnHelper : public SyncStartupTracker::Observer,
public LoginUIService::Observer,
public BrowserListObserver {
public:
// Delegate to stub out the UI for testing.
class Delegate {
public:
virtual ~Delegate() = default;
virtual void ShowSyncConfirmation(Profile* profile, Browser* browser) = 0;
virtual void ShowSyncSettings(Profile* profile, Browser* browser) = 0;
};
// Uses the production delegate with real UI.
explicit TurnSyncOnHelper(Profile* profile);
TurnSyncOnHelper(Profile* profile, std::unique_ptr<Delegate> delegate);
~TurnSyncOnHelper() override;
TurnSyncOnHelper(const TurnSyncOnHelper&) = delete;
TurnSyncOnHelper& operator=(const TurnSyncOnHelper&) = delete;
static void RegisterProfilePrefs(PrefRegistrySimple* registry);
// BrowserListObserver:
void OnBrowserSetLastActive(Browser* browser) override;
// SyncStartupTracker::Observer:
void SyncStartupCompleted() override;
void SyncStartupFailed() override;
// LoginUIService::Observer:
void OnSyncConfirmationUIClosed(
LoginUIService::SyncConfirmationUIClosedResult result) override;
private:
// Starts the setup flow.
void StartFlow();
// Displays the Sync confirmation UI.
// Note: If sync fails to start (e.g. sync is disabled by admin), the sync
// confirmation dialog will be updated accordingly.
void ShowSyncConfirmationUI();
// Handles the user input from the sync confirmation UI.
void FinishSyncSetup(LoginUIService::SyncConfirmationUIClosedResult result);
// Returns the SyncService, or nullptr if sync is not allowed.
syncer::SyncService* GetSyncService();
Profile* const profile_;
std::unique_ptr<Delegate> delegate_;
Browser* browser_;
std::unique_ptr<SyncStartupTracker> sync_startup_tracker_;
ScopedObserver<LoginUIService, LoginUIService::Observer>
scoped_login_ui_service_observer_{this};
};
#endif // CHROME_BROWSER_CHROMEOS_SYNC_TURN_SYNC_ON_HELPER_H_
// Copyright 2019 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.
#include "chrome/browser/chromeos/sync/turn_sync_on_helper.h"
#include "base/test/scoped_feature_list.h"
#include "chrome/browser/sync/profile_sync_service_factory.h"
#include "chrome/test/base/browser_with_test_window_test.h"
#include "chromeos/constants/chromeos_features.h"
#include "components/sync/driver/sync_service.h"
#include "components/sync/driver/sync_user_settings.h"
#include "components/sync/driver/test_sync_service.h"
#include "components/unified_consent/url_keyed_data_collection_consent_helper.h"
using unified_consent::UrlKeyedDataCollectionConsentHelper;
namespace {
const char kSyncFirstRunCompleted[] = "sync.first_run_completed";
class TestDelegate : public TurnSyncOnHelper::Delegate {
public:
TestDelegate() = default;
~TestDelegate() override = default;
void ShowSyncConfirmation(Profile* profile, Browser* browser) override {
show_sync_confirmation_count_++;
}
void ShowSyncSettings(Profile* profile, Browser* browser) override {
show_sync_settings_count_++;
}
int show_sync_confirmation_count_ = 0;
int show_sync_settings_count_ = 0;
};
std::unique_ptr<KeyedService> BuildTestSyncService(
content::BrowserContext* context) {
return std::make_unique<syncer::TestSyncService>();
}
class TurnSyncOnHelperTest : public BrowserWithTestWindowTest {
public:
TurnSyncOnHelperTest() {
feature_list_.InitAndEnableFeature(chromeos::features::kSplitSettingsSync);
}
~TurnSyncOnHelperTest() override = default;
void SetUp() override {
BrowserWithTestWindowTest::SetUp();
sync_service_ = static_cast<syncer::TestSyncService*>(
ProfileSyncServiceFactory::GetInstance()->SetTestingFactoryAndUse(
profile(), base::BindRepeating(&BuildTestSyncService)));
// Reset the sync service to the pre-setup state.
sync_service_->SetFirstSetupComplete(false);
sync_service_->GetUserSettings()->SetSyncRequested(false);
}
base::test::ScopedFeatureList feature_list_;
syncer::TestSyncService* sync_service_ = nullptr;
};
TEST_F(TurnSyncOnHelperTest, UserAcceptsDefaults) {
auto test_delegate = std::make_unique<TestDelegate>();
TestDelegate* delegate = test_delegate.get();
TurnSyncOnHelper helper(profile(), std::move(test_delegate));
// Simulate the first browser window becoming active.
helper.OnBrowserSetLastActive(browser());
// Sync confirmation dialog is shown
EXPECT_TRUE(sync_service_->GetUserSettings()->IsSyncRequested());
EXPECT_EQ(1, delegate->show_sync_confirmation_count_);
// Simulate the user clicking "Yes, I'm in".
helper.OnSyncConfirmationUIClosed(LoginUIService::SYNC_WITH_DEFAULT_SETTINGS);
// Setup is complete and we didn't show settings.
EXPECT_TRUE(sync_service_->GetUserSettings()->IsFirstSetupComplete());
EXPECT_EQ(0, delegate->show_sync_settings_count_);
}
TEST_F(TurnSyncOnHelperTest, UserClicksSettings) {
auto test_delegate = std::make_unique<TestDelegate>();
TestDelegate* delegate = test_delegate.get();
TurnSyncOnHelper helper(profile(), std::move(test_delegate));
// Simulate the first browser window becoming active.
helper.OnBrowserSetLastActive(browser());
// Simulate the user clicking "Settings".
helper.OnSyncConfirmationUIClosed(LoginUIService::CONFIGURE_SYNC_FIRST);
// Setup is not complete and we opened settings.
EXPECT_FALSE(sync_service_->GetUserSettings()->IsFirstSetupComplete());
EXPECT_EQ(1, delegate->show_sync_settings_count_);
}
TEST_F(TurnSyncOnHelperTest, UserClicksCancel) {
auto test_delegate = std::make_unique<TestDelegate>();
TestDelegate* delegate = test_delegate.get();
TurnSyncOnHelper helper(profile(), std::move(test_delegate));
// Simulate the first browser window becoming active.
helper.OnBrowserSetLastActive(browser());
// Simulate the user clicking "Cancel".
helper.OnSyncConfirmationUIClosed(LoginUIService::ABORT_SIGNIN);
// Setup is not complete and we didn't show settings.
EXPECT_FALSE(sync_service_->GetUserSettings()->IsFirstSetupComplete());
EXPECT_EQ(0, delegate->show_sync_settings_count_);
}
TEST_F(TurnSyncOnHelperTest, UserPreviouslySetUpSync) {
// Simulate a user who previously completed the first-run flow.
profile()->GetPrefs()->SetBoolean(kSyncFirstRunCompleted, true);
auto test_delegate = std::make_unique<TestDelegate>();
TestDelegate* delegate = test_delegate.get();
TurnSyncOnHelper helper(profile(), std::move(test_delegate));
// Simulate the first browser window becoming active.
helper.OnBrowserSetLastActive(browser());
// Sync confirmation dialog isn't shown.
EXPECT_EQ(0, delegate->show_sync_confirmation_count_);
}
TEST_F(TurnSyncOnHelperTest, UrlKeyedMetricsConsent) {
// User is not consented by default.
std::unique_ptr<UrlKeyedDataCollectionConsentHelper> consent_helper =
UrlKeyedDataCollectionConsentHelper::
NewAnonymizedDataCollectionConsentHelper(
profile()->GetPrefs(),
ProfileSyncServiceFactory::GetForProfile(profile()));
ASSERT_FALSE(consent_helper->IsEnabled());
// Simulate user consenting to sync.
TurnSyncOnHelper helper(profile(), std::make_unique<TestDelegate>());
helper.OnBrowserSetLastActive(browser());
helper.OnSyncConfirmationUIClosed(LoginUIService::SYNC_WITH_DEFAULT_SETTINGS);
// URL keyed metrics are enabled.
EXPECT_TRUE(consent_helper->IsEnabled());
}
} // namespace
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