blob: 44616ac7525e8caf01d04c1c99d156d335bf0355 [file] [log] [blame]
// Copyright 2025 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/ui/signin/dice_migration_service.h"
#include "base/check_is_test.h"
#include "base/metrics/histogram_functions.h"
#include "base/rand_util.h"
#include "chrome/browser/enterprise/util/managed_browser_utils.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/profiles/profile_avatar_icon_util.h"
#include "chrome/browser/profiles/profile_window.h"
#include "chrome/browser/signin/identity_manager_factory.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_finder.h"
#include "chrome/browser/ui/browser_window/public/browser_window_features.h"
#include "chrome/browser/ui/browser_window/public/browser_window_interface.h"
#include "chrome/browser/ui/toasts/api/toast_id.h"
#include "chrome/browser/ui/toasts/toast_controller.h"
#include "chrome/browser/ui/views/frame/browser_view.h"
#include "chrome/browser/ui/views/frame/toolbar_button_provider.h"
#include "chrome/browser/ui/views/profiles/avatar_toolbar_button.h"
#include "chrome/browser/ui/web_applications/app_browser_controller.h"
#include "chrome/grit/branded_strings.h"
#include "chrome/grit/generated_resources.h"
#include "chrome/grit/theme_resources.h"
#include "components/pref_registry/pref_registry_syncable.h"
#include "components/prefs/pref_service.h"
#include "components/signin/public/base/signin_pref_names.h"
#include "components/signin/public/base/signin_switches.h"
#include "components/signin/public/identity_manager/account_managed_status_finder.h"
#include "components/signin/public/identity_manager/account_managed_status_finder_outcome.h"
#include "components/signin/public/identity_manager/identity_manager.h"
#include "components/signin/public/identity_manager/identity_utils.h"
#include "components/signin/public/identity_manager/primary_account_mutator.h"
#include "components/strings/grit/components_strings.h"
#include "components/user_education/common/user_education_features.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/models/dialog_model.h"
#include "ui/views/bubble/bubble_dialog_model_host.h"
#include "ui/views/widget/widget.h"
namespace {
constexpr char kHelpCenterUrl[] =
"https://support.google.com/chrome/answer/"
"185277?p=manage_autofill_settings";
constexpr base::TimeDelta kForcedSigninToastDelay = base::Seconds(5);
constexpr char kDialogCloseReasonHistogram[] =
"Signin.DiceMigrationDialog.CloseReason";
constexpr char kDialogTimerStartedHistogram[] =
"Signin.DiceMigrationDialog.TimerStarted";
constexpr char kDialogPreviouslyShownCountHistogram[] =
"Signin.DiceMigrationDialog.PreviouslyShownCount";
constexpr char kDialogDaysSinceLastShownHistogram[] =
"Signin.DiceMigrationDialog.DaysSinceLastShown";
constexpr char kDialogShownHistogram[] = "Signin.DiceMigrationDialog.Shown";
constexpr char kAccountManagedStatusHistogram[] =
"Signin.DiceMigrationDialog.AccountManagedStatus";
constexpr char kUserMigratedHistogram[] = "Signin.DiceMigrationDialog.Migrated";
constexpr char kToastTriggeredHistogram[] =
"Signin.DiceMigrationDialog.ToastTriggered";
constexpr char kDialogNotShownReasonHistogram[] =
"Signin.DiceMigrationDialog.NotShownReason";
constexpr char kRestoredFromBackupHistogram[] =
"Signin.DiceMigration.RestoredFromBackup";
constexpr char kForceMigratedHistogram[] = "Signin.DiceMigration.ForceMigrated";
constexpr char kForcedMigrationAccountManagedHistogram[] =
"Signin.ForcedDiceMigration.HasAcceptedAccountManagement";
constexpr char kForcedSigninBrowserInstanceAvailableAfterTimerHistogram[] =
"Signin.ForcedDiceMigration.BrowserInstanceAvailableAfterTimer";
void LogDialogCloseReason(DiceMigrationService::DialogCloseReason reason) {
base::UmaHistogramEnumeration(kDialogCloseReasonHistogram, reason);
}
void LogDialogNotShownReason(
DiceMigrationService::DialogNotShownReason reason) {
base::UmaHistogramEnumeration(kDialogNotShownReasonHistogram, reason);
}
void OnHelpCenterLinkClicked(Browser* browser) {
browser->OpenGURL(GURL(kHelpCenterUrl),
WindowOpenDisposition::NEW_FOREGROUND_TAB);
}
bool IsUserEligibleForDiceMigration(Profile* profile) {
signin::IdentityManager* identity_manager =
IdentityManagerFactory::GetForProfile(profile);
if (!identity_manager->HasPrimaryAccount(signin::ConsentLevel::kSignin) ||
identity_manager->HasPrimaryAccount(signin::ConsentLevel::kSync)) {
// The user is not signed in or has sync enabled.
return false;
}
if (!signin::IsImplicitBrowserSigninOrExplicitDisabled(identity_manager,
profile->GetPrefs())) {
// The user is not implicitly signed in.
return false;
}
return true;
}
void SetBannerImage(ui::DialogModel::Builder& builder,
signin::IdentityManager* identity_manager) {
CHECK(identity_manager);
CHECK(identity_manager->HasPrimaryAccount(signin::ConsentLevel::kSignin));
AccountInfo account_info = identity_manager->FindExtendedAccountInfo(
identity_manager->GetPrimaryAccountInfo(signin::ConsentLevel::kSignin));
gfx::Image avatar_image =
account_info.account_image.IsEmpty()
// TODO(crbug.com/399838468): This is the old placeholder avatar icon.
// Consider using `ProfileAttributesEntry::GetAvatarIcon()` instead.
? ui::ResourceBundle::GetSharedInstance().GetImageNamed(
profiles::GetPlaceholderAvatarIconResourceID())
: account_info.account_image;
// The position and size must match the implied one in the image,
// so these numbers are exclusively for ..._AVATAR50_X135_Y54.
static constexpr gfx::Point kAvatarPosition{135, 54};
static constexpr size_t kAvatarSize{50};
builder.SetBannerImage(profiles::EmbedAvatarOntoImage(
IDR_MIGRATE_ADDRESS_AVATAR50_X135_Y54,
avatar_image, kAvatarPosition, kAvatarSize),
profiles::EmbedAvatarOntoImage(
IDR_MIGRATE_ADDRESS_AVATAR50_X135_Y54_DARK,
avatar_image, kAvatarPosition, kAvatarSize));
}
bool MaybeMigrateUser(Profile* profile) {
if (!IsUserEligibleForDiceMigration(profile)) {
return false;
}
PrefService* prefs = profile->GetPrefs();
CHECK(prefs);
// Backup the prefs.
prefs->SetDict(
kDiceMigrationBackup,
base::Value::Dict()
.SetByDottedPath(prefs::kExplicitBrowserSignin,
prefs->GetBoolean(prefs::kExplicitBrowserSignin))
.SetByDottedPath(
prefs::kPrefsThemesSearchEnginesAccountStorageEnabled,
prefs->GetBoolean(
prefs::kPrefsThemesSearchEnginesAccountStorageEnabled)));
// TODO(crbug.com/399838468): Consider calling
// `PrimaryAccountManager::SetExplicitBrowserSigninPrefs` upon explicit signin
// pref change.
prefs->SetBoolean(prefs::kPrefsThemesSearchEnginesAccountStorageEnabled,
true);
prefs->SetBoolean(prefs::kExplicitBrowserSignin, true);
// Mark the migration pref as successful.
prefs->SetBoolean(kDiceMigrationMigrated, true);
// Reset the restoration pref.
prefs->SetBoolean(kDiceMigrationRestoredFromBackup, false);
return true;
}
bool MaybeShowToast(Browser* browser) {
ToastController* const toast_controller =
browser->browser_window_features()->toast_controller();
if (!toast_controller) {
return false;
}
toast_controller->MaybeShowToast(ToastParams(ToastId::kDiceUserMigrated));
return true;
}
} // namespace
const char kDiceMigrationDialogShownCount[] =
"signin.dice_migration.dialog_shown_count";
const char kDiceMigrationDialogLastShownTime[] =
"signin.dice_migration.dialog_last_shown_time";
const char kDiceMigrationMigrated[] = "signin.dice_migration.migrated";
const char kDiceMigrationBackup[] = "signin.dice_migration.backup";
const char kDiceMigrationRestoredFromBackup[] =
"signin.dice_migration.restored_from_backup";
// static
const int DiceMigrationService::kMaxDialogShownCount = 3;
DEFINE_CLASS_ELEMENT_IDENTIFIER_VALUE(DiceMigrationService,
kAcceptButtonElementId);
DEFINE_CLASS_ELEMENT_IDENTIFIER_VALUE(DiceMigrationService,
kCancelButtonElementId);
class DiceMigrationService::AvatarButtonObserver
: public AvatarToolbarButton::Observer {
public:
AvatarButtonObserver(AvatarToolbarButton* avatar_button,
DiceMigrationService* dice_migration_service)
: dice_migration_service_(dice_migration_service) {
CHECK(avatar_button);
CHECK(dice_migration_service_);
CHECK(dice_migration_service_->dialog_widget_);
avatar_button_observation_.Observe(avatar_button);
const std::u16string& avatar_button_text = base::ASCIIToUTF16(
dice_migration_service_->primary_account_info_.email);
if (!avatar_button_text.empty()) {
clear_avatar_button_effects_callback_ =
avatar_button->SetExplicitButtonState(
avatar_button_text, /*accessibility_label=*/std::nullopt,
/*explicit_action=*/std::nullopt);
}
}
private:
// `AvatarToolbarButton::Observer`:
void OnButtonPressed() override {
CHECK(dice_migration_service_->dialog_widget_);
avatar_button_observation_.Reset();
dice_migration_service_->StopTimerOrCloseDialog(
DialogCloseReason::kAvatarButtonClicked);
}
base::ScopedObservation<AvatarToolbarButton, AvatarToolbarButton::Observer>
avatar_button_observation_{this};
raw_ptr<DiceMigrationService> dice_migration_service_;
// Callback to reset the expanded avatar button.
base::ScopedClosureRunner clear_avatar_button_effects_callback_;
};
DiceMigrationService::DiceMigrationService(
Profile* profile,
scoped_refptr<base::SingleThreadTaskRunner> task_runner_for_testing)
: profile_(profile) {
CHECK(profile_);
if (base::FeatureList::IsEnabled(switches::kForcedDiceMigration)) {
// Force migration all implicitly signed-in users.
const bool migrated = ForceMigrateUserIfEligible();
base::UmaHistogramBoolean(kForceMigratedHistogram, migrated);
// By now, the user should have been force migrated and is no longer in the
// DICe state.
CHECK(!IsUserEligibleForDiceMigration(profile_));
return;
}
CHECK(base::FeatureList::IsEnabled(switches::kOfferMigrationToDiceUsers));
const std::optional<DialogNotShownReason> not_shown_reason =
ShouldStartDialogTriggerTimer();
base::UmaHistogramBoolean(kDialogTimerStartedHistogram,
!not_shown_reason.has_value());
if (not_shown_reason.has_value()) {
LogDialogNotShownReason(not_shown_reason.value());
return;
}
// If the flag is enabled, the user should have already been migrated and
// the above return statement should have returned.
CHECK(!base::FeatureList::IsEnabled(switches::kForcedDiceMigration));
signin::IdentityManager* identity_manager =
IdentityManagerFactory::GetForProfile(profile_);
CHECK(identity_manager);
primary_account_info_ =
identity_manager->GetPrimaryAccountInfo(signin::ConsentLevel::kSignin);
identity_manager_observation_.Observe(identity_manager);
if (task_runner_for_testing) {
CHECK_IS_TEST();
dialog_trigger_timer_.SetTaskRunner(std::move(task_runner_for_testing));
}
dialog_trigger_timer_.Start(
FROM_HERE,
base::RandTimeDelta(switches::kOfferMigrationToDiceUsersMinDelay.Get(),
switches::kOfferMigrationToDiceUsersMaxDelay.Get()),
base::BindOnce(
&DiceMigrationService::OnTimerFinishOrAccountManagedStatusKnown,
base::Unretained(this)));
account_managed_status_finder_ =
std::make_unique<signin::AccountManagedStatusFinder>(
identity_manager, primary_account_info_,
base::BindOnce(
&DiceMigrationService::OnTimerFinishOrAccountManagedStatusKnown,
base::Unretained(this)));
}
DiceMigrationService::~DiceMigrationService() {
// Most likely a no-op since the dialog gets closed before this during browser
// shutdown.
StopTimerOrCloseDialog(DialogCloseReason::kServiceDestroyed);
}
// static
void DiceMigrationService::RegisterProfilePrefs(
user_prefs::PrefRegistrySyncable* registry) {
registry->RegisterIntegerPref(kDiceMigrationDialogShownCount, 0);
registry->RegisterTimePref(kDiceMigrationDialogLastShownTime, base::Time());
registry->RegisterBooleanPref(kDiceMigrationMigrated, false);
registry->RegisterDictionaryPref(kDiceMigrationBackup);
registry->RegisterBooleanPref(kDiceMigrationRestoredFromBackup, false);
}
// static
void DiceMigrationService::RevertDiceMigration(PrefService* prefs) {
CHECK(prefs);
if (!prefs->GetBoolean(kDiceMigrationMigrated)) {
return;
}
const bool restored_from_backup = [prefs]() -> bool {
const base::Value* backup = prefs->GetUserPrefValue(kDiceMigrationBackup);
if (!backup || !backup->is_dict()) {
return false;
}
const std::optional<bool> prefs_account_storage_enabled =
backup->GetDict().FindBoolByDottedPath(
prefs::kPrefsThemesSearchEnginesAccountStorageEnabled);
const std::optional<bool> explicit_browser_signin =
backup->GetDict().FindBoolByDottedPath(prefs::kExplicitBrowserSignin);
if (!explicit_browser_signin.has_value() ||
!prefs_account_storage_enabled.has_value()) {
return false;
}
prefs->SetBoolean(prefs::kPrefsThemesSearchEnginesAccountStorageEnabled,
*prefs_account_storage_enabled);
prefs->SetBoolean(prefs::kExplicitBrowserSignin, *explicit_browser_signin);
return true;
}();
prefs->SetBoolean(kDiceMigrationRestoredFromBackup, restored_from_backup);
prefs->SetBoolean(kDiceMigrationMigrated, !restored_from_backup);
base::UmaHistogramBoolean(kRestoredFromBackupHistogram, restored_from_backup);
// Clear the backup. Also clear the dialog shown count/time to ensure the
// dialog can be shown again once the flag is enabled again.
if (restored_from_backup) {
prefs->ClearPref(kDiceMigrationBackup);
prefs->ClearPref(kDiceMigrationDialogShownCount);
}
}
std::optional<DiceMigrationService::DialogNotShownReason>
DiceMigrationService::ShouldStartDialogTriggerTimer() {
if (!IsUserEligibleForDiceMigration(profile_)) {
return DiceMigrationService::DialogNotShownReason::kNotEligible;
}
const int dialog_shown_count = GetDialogShownCount();
base::UmaHistogramExactLinear(kDialogPreviouslyShownCountHistogram,
dialog_shown_count, kMaxDialogShownCount + 1);
// Show the dialog at most `kMaxDialogShownCount` times.
if (dialog_shown_count >= kMaxDialogShownCount) {
return DiceMigrationService::DialogNotShownReason::kMaxShownCountReached;
}
if (const base::Time last_shown_time = GetDialogLastShownTime();
!last_shown_time.is_null()) {
const base::TimeDelta duration_since_last_shown =
base::Time::Now() - last_shown_time;
base::UmaHistogramCounts100(kDialogDaysSinceLastShownHistogram,
duration_since_last_shown.InDays());
// Show the dialog at least one week after the last time it was shown.
if (duration_since_last_shown <
switches::kOfferMigrationToDiceUsersMinTimeBetweenDialogs.Get()) {
return DiceMigrationService::DialogNotShownReason::
kMinTimeBetweenDialogsNotPassed;
}
}
return std::nullopt;
}
std::optional<DiceMigrationService::DialogNotShownReason>
DiceMigrationService::ShowDiceMigrationOfferDialogIfUserEligible() {
CHECK(!dialog_trigger_timer_.IsRunning());
CHECK(!dialog_widget_);
CHECK_LT(GetDialogShownCount(), kMaxDialogShownCount);
CHECK_LT(GetDialogLastShownTime(),
base::Time::Now() -
switches::kOfferMigrationToDiceUsersMinTimeBetweenDialogs.Get());
if (!IsUserEligibleForDiceMigration(profile_)) {
return DiceMigrationService::DialogNotShownReason::kNotEligible;
}
Browser* browser = chrome::FindBrowserWithProfile(profile_);
if (!browser || !browser->window()) {
return DiceMigrationService::DialogNotShownReason::
kBrowserInstanceUnavailable;
}
AvatarToolbarButton* avatar_button =
BrowserView::GetBrowserViewForBrowser(browser)
->toolbar_button_provider()
->GetAvatarToolbarButton();
// Skip showing the dialog if the avatar button is not available.
// Some browsers, such as web apps, don't have an avatar toolbar button to
// anchor the bubble. Even if a web app has an avatar toolbar button, avoid
// showing the dialog to keep the behavior consistent.
if (!avatar_button || web_app::AppBrowserController::IsWebApp(browser)) {
return DiceMigrationService::DialogNotShownReason::kAvatarButtonUnavailable;
}
ui::DialogModelLabel::TextReplacement learn_more_link =
ui::DialogModelLabel::CreateLink(
IDS_LEARN_MORE,
base::BindRepeating(&OnHelpCenterLinkClicked, browser));
auto description_text = ui::DialogModelLabel::CreateWithReplacement(
IDS_DICE_MIGRATION_DIALOG_DESCRIPTION, learn_more_link);
auto builder =
ui::DialogModel::Builder(std::make_unique<ui::DialogModelDelegate>());
SetBannerImage(builder, IdentityManagerFactory::GetForProfile(profile_));
builder.SetTitle(l10n_util::GetStringUTF16(IDS_DICE_MIGRATION_DIALOG_TITLE));
builder.AddParagraph(description_text);
builder.AddOkButton(base::DoNothing(),
ui::DialogModel::Button::Params()
.SetId(kAcceptButtonElementId)
.SetLabel(l10n_util::GetStringUTF16(
IDS_DICE_MIGRATION_DIALOG_OK_BUTTON)));
// The "final" variant does not include a close button, but rather the close-x
// button.
if (GetDialogShownCount() < kMaxDialogShownCount - 1) {
// Non-"final" variant.
builder.OverrideShowCloseButton(false);
builder.AddCancelButton(
base::DoNothing(),
ui::DialogModel::Button::Params()
.SetId(kCancelButtonElementId)
.SetLabel(l10n_util::GetStringUTF16(IDS_PROMO_SNOOZE_BUTTON)));
}
builder.DisableCloseOnDeactivate();
builder.SetIsAlertDialog();
auto bubble = std::make_unique<views::BubbleDialogModelHost>(
builder.Build(), avatar_button, views::BubbleBorder::TOP_RIGHT);
dialog_widget_ = views::BubbleDialogDelegate::CreateBubble(std::move(bubble));
dialog_widget_observation_.Observe(dialog_widget_);
browser_ = browser->AsWeakPtr();
browser_close_subscription_ =
browser->RegisterBrowserDidClose(base::BindRepeating(
&DiceMigrationService::BrowserDidClose, base::Unretained(this)));
dialog_widget_->Show();
// Update the dialog shown count and time. Note that the user may not interact
// with the dialog at all, for example, if they close the browser. Or, the
// dialog may be shown on a browser that is minimized. These cases still count
// as showing the dialog. This is better than the alternate of updating the
// shown count and time only when the user interacts with the dialog, which
// might cause the dialog to be show on every browser startup if the user
// never interacts with it.
UpdateDialogShownCountAndTime();
// Close the dialog when the avatar pill is clicked. This is also responsible
// for expanding the avatar pill when the dialog is showing.
avatar_button_observer_ =
std::make_unique<AvatarButtonObserver>(avatar_button, this);
return std::nullopt;
}
views::Widget* DiceMigrationService::GetDialogWidgetForTesting() {
return dialog_widget_.get();
}
base::OneShotTimer& DiceMigrationService::GetDialogTriggerTimerForTesting() {
return dialog_trigger_timer_;
}
void DiceMigrationService::OnWidgetDestroying(views::Widget* widget) {
CHECK_EQ(dialog_widget_, widget);
avatar_button_observer_.reset();
dialog_widget_observation_.Reset();
browser_close_subscription_.reset();
dialog_widget_ = nullptr;
switch (widget->closed_reason()) {
// Losing focus should not close the dialog.
case views::Widget::ClosedReason::kLostFocus:
NOTREACHED();
case views::Widget::ClosedReason::kUnspecified:
LogDialogCloseReason(
dialog_close_reason_.value_or(DialogCloseReason::kUnspecified));
break;
case views::Widget::ClosedReason::kAcceptButtonClicked: {
LogDialogCloseReason(DialogCloseReason::kAccepted);
const bool migrated = MaybeMigrateUser(profile_);
base::UmaHistogramBoolean(kUserMigratedHistogram, migrated);
if (migrated) {
const bool toast_triggered = browser_ && MaybeShowToast(browser_.get());
base::UmaHistogramBoolean(kToastTriggeredHistogram, toast_triggered);
}
} break;
case views::Widget::ClosedReason::kCancelButtonClicked:
// Cancel button is only available in the non-"final" variant.
CHECK_LT(GetDialogShownCount(), kMaxDialogShownCount);
LogDialogCloseReason(DialogCloseReason::kCancelled);
break;
case views::Widget::ClosedReason::kCloseButtonClicked:
// Close button is only available in the "final" variant.
CHECK_EQ(GetDialogShownCount(), kMaxDialogShownCount);
LogDialogCloseReason(DialogCloseReason::kClosed);
break;
case views::Widget::ClosedReason::kEscKeyPressed:
LogDialogCloseReason(DialogCloseReason::kEscKeyPressed);
break;
}
browser_.reset();
}
void DiceMigrationService::OnPrimaryAccountChanged(
const signin::PrimaryAccountChangeEvent& event) {
switch (event.GetEventTypeFor(signin::ConsentLevel::kSignin)) {
case signin::PrimaryAccountChangeEvent::Type::kSet:
CHECK_EQ(primary_account_info_, event.GetPreviousState().primary_account);
StopTimerOrCloseDialog(DialogCloseReason::kPrimaryAccountChanged);
return;
case signin::PrimaryAccountChangeEvent::Type::kCleared:
CHECK_EQ(primary_account_info_, event.GetPreviousState().primary_account);
StopTimerOrCloseDialog(DialogCloseReason::kPrimaryAccountCleared);
return;
case signin::PrimaryAccountChangeEvent::Type::kNone:
CHECK_EQ(primary_account_info_, event.GetCurrentState().primary_account);
break;
}
// If the user turns sync on, stop the timer or close the dialog.
if (event.GetEventTypeFor(signin::ConsentLevel::kSync) ==
signin::PrimaryAccountChangeEvent::Type::kSet) {
StopTimerOrCloseDialog(DialogCloseReason::kSyncTurnedOn);
}
}
void DiceMigrationService::OnTimerFinishOrAccountManagedStatusKnown() {
if (dialog_trigger_timer_.IsRunning()) {
return;
}
signin::AccountManagedStatusFinderOutcome
account_managed_status_finder_outcome =
account_managed_status_finder_->GetOutcome();
base::UmaHistogramEnumeration(kAccountManagedStatusHistogram,
account_managed_status_finder_outcome);
switch (account_managed_status_finder_outcome) {
case signin::AccountManagedStatusFinderOutcome::kPending:
return;
case signin::AccountManagedStatusFinderOutcome::kError:
case signin::AccountManagedStatusFinderOutcome::kTimeout:
LogDialogNotShownReason(DiceMigrationService::DialogNotShownReason::
kErrorFetchingAccountManagedStatus);
return;
// Consumer accounts.
case signin::AccountManagedStatusFinderOutcome::kConsumerGmail:
case signin::AccountManagedStatusFinderOutcome::kConsumerWellKnown:
case signin::AccountManagedStatusFinderOutcome::kConsumerNotWellKnown: {
const std::optional<DialogNotShownReason> not_shown_reason =
ShowDiceMigrationOfferDialogIfUserEligible();
base::UmaHistogramBoolean(kDialogShownHistogram,
!not_shown_reason.has_value());
if (not_shown_reason.has_value()) {
LogDialogNotShownReason(not_shown_reason.value());
}
} break;
// Managed accounts are not shown the migration dialog.
case signin::AccountManagedStatusFinderOutcome::kEnterpriseGoogleDotCom:
case signin::AccountManagedStatusFinderOutcome::kEnterprise:
LogDialogNotShownReason(
DiceMigrationService::DialogNotShownReason::kManagedAccount);
return;
}
}
void DiceMigrationService::StopTimerOrCloseDialog(
DiceMigrationService::DialogCloseReason reason) {
CHECK(!dialog_trigger_timer_.IsRunning() || !dialog_widget_);
identity_manager_observation_.Reset();
if (dialog_widget_) {
dialog_close_reason_ = reason;
dialog_widget_->CloseWithReason(views::Widget::ClosedReason::kUnspecified);
} else if (dialog_trigger_timer_.IsRunning()) {
dialog_trigger_timer_.Stop();
switch (reason) {
case DialogCloseReason::kPrimaryAccountChanged:
LogDialogNotShownReason(
DiceMigrationService::DialogNotShownReason::kPrimaryAccountChanged);
break;
case DialogCloseReason::kPrimaryAccountCleared:
LogDialogNotShownReason(
DiceMigrationService::DialogNotShownReason::kPrimaryAccountCleared);
break;
case DialogCloseReason::kSyncTurnedOn:
LogDialogNotShownReason(
DiceMigrationService::DialogNotShownReason::kSyncTurnedOn);
break;
case DialogCloseReason::kServiceDestroyed:
LogDialogNotShownReason(
DiceMigrationService::DialogNotShownReason::kServiceDestroyed);
break;
default:
NOTREACHED();
}
}
}
int DiceMigrationService::GetDialogShownCount() const {
PrefService* prefs = profile_->GetPrefs();
CHECK(prefs);
return prefs->GetInteger(kDiceMigrationDialogShownCount);
}
base::Time DiceMigrationService::GetDialogLastShownTime() const {
PrefService* prefs = profile_->GetPrefs();
CHECK(prefs);
return prefs->GetTime(kDiceMigrationDialogLastShownTime);
}
void DiceMigrationService::UpdateDialogShownCountAndTime() {
PrefService* prefs = profile_->GetPrefs();
CHECK(prefs);
prefs->SetInteger(kDiceMigrationDialogShownCount, GetDialogShownCount() + 1);
prefs->SetTime(kDiceMigrationDialogLastShownTime, base::Time::Now());
}
void DiceMigrationService::BrowserDidClose(BrowserWindowInterface* browser) {
if (dialog_widget_) {
dialog_widget_->CloseNow();
}
}
bool DiceMigrationService::ForceMigrateUserIfEligible() {
if (!IsUserEligibleForDiceMigration(profile_)) {
return false;
}
signin::IdentityManager* identity_manager =
IdentityManagerFactory::GetForProfile(profile_);
CHECK(identity_manager);
const bool has_accepted_account_management =
enterprise_util::UserAcceptedAccountManagement(profile_);
base::UmaHistogramBoolean(kForcedMigrationAccountManagedHistogram,
has_accepted_account_management);
if (!has_accepted_account_management) {
// This is either a consumer account or an enterprise account that has not
// accepted the account management.
// Remove the primary account, but keep the tokens. This will make the user
// signed in only to the web.
CHECK(identity_manager->GetPrimaryAccountMutator()
->RemovePrimaryAccountButKeepTokens(
signin_metrics::ProfileSignout::kForcedDiceMigration));
return true;
}
// The user is an enterprise account that has accepted the account management.
// Such users cannot be signed out. Migrate these users to explicitly
// signed-in state.
CHECK(MaybeMigrateUser(profile_));
// Trigger the timer to show the toast.
auto show_toast = [](Profile* profile) {
Browser* browser = chrome::FindBrowserWithProfile(profile);
base::UmaHistogramBoolean(
kForcedSigninBrowserInstanceAvailableAfterTimerHistogram, browser);
if (browser) {
CHECK(MaybeShowToast(browser));
} else {
// The profile is under creation and hence no browser instance is tied to
// it yet. Wait for the browser to be created before trying to show the
// toast.
// This object deletes itself when done.
new profiles::BrowserAddedForProfileObserver(
profile, base::BindOnce(base::IgnoreResult(&MaybeShowToast)));
}
};
// TODO(crbug.com/437083916): Rename the timer to reflect that it is also used
// for showing this toast.
CHECK(!dialog_trigger_timer_.IsRunning());
dialog_trigger_timer_.Start(FROM_HERE, kForcedSigninToastDelay,
base::BindOnce(std::move(show_toast), profile_));
return true;
}