blob: 592b62e9d1ec895ae8e0f60ceaedf4b7042ef896 [file] [log] [blame]
// Copyright 2024 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/startup/default_browser_prompt_manager.h"
#include <memory>
#include "base/containers/contains.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/metrics/histogram_functions.h"
#include "base/time/time.h"
#include "base/timer/timer.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_list.h"
#include "chrome/browser/ui/startup/default_browser_infobar_delegate.h"
#include "chrome/browser/ui/ui_features.h"
#include "chrome/common/pref_names.h"
#include "components/infobars/core/confirm_infobar_delegate.h"
#include "components/infobars/core/infobar.h"
#include "components/prefs/pref_service.h"
#include "content/public/browser/web_contents.h"
// static
DefaultBrowserPromptManager* DefaultBrowserPromptManager::GetInstance() {
return base::Singleton<DefaultBrowserPromptManager>::get();
}
// static
void DefaultBrowserPromptManager::ResetPromptPrefs(Profile* profile) {
profile->GetPrefs()->ClearPref(prefs::kDefaultBrowserLastDeclined);
PrefService* local_state = g_browser_process->local_state();
local_state->ClearPref(prefs::kDefaultBrowserLastDeclinedTime);
local_state->ClearPref(prefs::kDefaultBrowserDeclinedCount);
local_state->ClearPref(prefs::kDefaultBrowserFirstShownTime);
}
// static
void DefaultBrowserPromptManager::UpdatePrefsForDismissedPrompt(
Profile* profile) {
base::Time now = base::Time::Now();
profile->GetPrefs()->SetInt64(prefs::kDefaultBrowserLastDeclined,
now.ToInternalValue());
PrefService* local_state = g_browser_process->local_state();
local_state->SetTime(prefs::kDefaultBrowserLastDeclinedTime, now);
local_state->SetInteger(
prefs::kDefaultBrowserDeclinedCount,
local_state->GetInteger(prefs::kDefaultBrowserDeclinedCount) + 1);
local_state->ClearPref(prefs::kDefaultBrowserFirstShownTime);
}
// static
void DefaultBrowserPromptManager::MaybeResetAppMenuPromptPrefs(
Profile* profile) {
if (!base::FeatureList::IsEnabled(features::kDefaultBrowserPromptRefresh) ||
!features::kShowDefaultBrowserAppMenuChip.Get()) {
g_browser_process->local_state()->ClearPref(
prefs::kDefaultBrowserFirstShownTime);
return;
}
if (!ShouldShowAppMenuPrompt()) {
// Found that app menu should no longer be shown. Triggers an implicit
// dismissal so that the subsequent call to ShouldShowPrompts() will return
// false.
UpdatePrefsForDismissedPrompt(profile);
}
}
void DefaultBrowserPromptManager::AddObserver(Observer* observer) {
observers_.AddObserver(observer);
}
void DefaultBrowserPromptManager::RemoveObserver(Observer* observer) {
observers_.RemoveObserver(observer);
}
void DefaultBrowserPromptManager::MaybeShowPrompt() {
CHECK(base::FeatureList::IsEnabled(features::kDefaultBrowserPromptRefresh));
if (features::kShowDefaultBrowserAppMenuItem.Get()) {
SetAppMenuItemVisibility(true);
}
if (!ShouldShowPrompts()) {
return;
}
if (features::kShowDefaultBrowserAppMenuChip.Get()) {
SetShowAppMenuPromptVisibility(true);
}
if (features::kShowDefaultBrowserInfoBar.Get()) {
browser_tab_strip_tracker_ =
std::make_unique<BrowserTabStripTracker>(this, this);
browser_tab_strip_tracker_->Init();
}
}
void DefaultBrowserPromptManager::CloseAllPrompts() {
CloseAllInfoBars();
SetShowAppMenuPromptVisibility(false);
SetAppMenuItemVisibility(false);
}
bool DefaultBrowserPromptManager::ShouldTrackBrowser(Browser* browser) {
return browser->is_type_normal() &&
!browser->profile()->IsIncognitoProfile() &&
!browser->profile()->IsGuestSession();
}
void DefaultBrowserPromptManager::OnTabStripModelChanged(
TabStripModel* tab_strip_model,
const TabStripModelChange& change,
const TabStripSelectionChange& selection) {
if (change.type() == TabStripModelChange::kInserted) {
for (const auto& contents : change.GetInsert()->contents) {
if (!base::Contains(infobars_, contents.contents)) {
CreateInfoBarForWebContents(contents.contents,
tab_strip_model->profile());
}
}
}
}
void DefaultBrowserPromptManager::OnInfoBarRemoved(infobars::InfoBar* infobar,
bool animate) {
auto infobars_entry = base::ranges::find(
infobars_, infobar, &decltype(infobars_)::value_type::second);
if (infobars_entry == infobars_.end()) {
return;
}
infobar->owner()->RemoveObserver(this);
infobars_.erase(infobars_entry);
static_cast<ConfirmInfoBarDelegate*>(infobar->delegate())
->RemoveObserver(this);
if (user_initiated_info_bar_close_pending_) {
CloseAllPrompts();
user_initiated_info_bar_close_pending_ = false;
}
}
void DefaultBrowserPromptManager::OnAccept() {
base::UmaHistogramCounts100("DefaultBrowser.InfoBar.TimesShownBeforeAccept",
g_browser_process->local_state()->GetInteger(
prefs::kDefaultBrowserDeclinedCount) +
1);
user_initiated_info_bar_close_pending_ = true;
}
void DefaultBrowserPromptManager::OnDismiss() {
user_initiated_info_bar_close_pending_ = true;
}
DefaultBrowserPromptManager::DefaultBrowserPromptManager() = default;
DefaultBrowserPromptManager::~DefaultBrowserPromptManager() = default;
// static
bool DefaultBrowserPromptManager::ShouldShowPrompts() {
PrefService* local_state = g_browser_process->local_state();
const int declined_count =
local_state->GetInteger(prefs::kDefaultBrowserDeclinedCount);
const base::Time last_declined_time =
local_state->GetTime(prefs::kDefaultBrowserLastDeclinedTime);
const int max_prompt_count = features::kMaxPromptCount.Get();
// A negative value for the max prompt count indicates that the prompt
// should be shown indefinitely. Otherwise, don't show the prompt if
// declined count equals or exceeds the max prompt count. A max prompt count
// of zero should mean that the prompt is never shown.
if (max_prompt_count >= 0 && declined_count >= max_prompt_count) {
return false;
}
// Show if the user has never declined the prompt.
if (declined_count == 0) {
return true;
}
// Show if it has been long enough since the last declined time
base::TimeDelta reprompt_duration =
features::kRepromptDuration.Get() *
std::pow(features::kRepromptDurationMultiplier.Get(), declined_count - 1);
return (base::Time::Now() - last_declined_time) > reprompt_duration;
}
// static
bool DefaultBrowserPromptManager::ShouldShowAppMenuPrompt() {
PrefService* local_state = g_browser_process->local_state();
const PrefService::Preference* first_shown_time_pref =
local_state->FindPreference(prefs::kDefaultBrowserFirstShownTime);
base::Time first_shown_time =
local_state->GetTime(prefs::kDefaultBrowserFirstShownTime);
return first_shown_time_pref->IsDefaultValue() ||
(base::Time::Now() - first_shown_time) <
features::kDefaultBrowserAppMenuDuration.Get();
}
void DefaultBrowserPromptManager::CreateInfoBarForWebContents(
content::WebContents* web_contents,
Profile* profile) {
// Ensure that an infobar hasn't already been created.
CHECK(!infobars_.contains(web_contents));
infobars::InfoBar* infobar = chrome::DefaultBrowserInfoBarDelegate::Create(
infobars::ContentInfoBarManager::FromWebContents(web_contents), profile);
infobars_[web_contents] = infobar;
static_cast<ConfirmInfoBarDelegate*>(infobar->delegate())->AddObserver(this);
auto* infobar_manager =
infobars::ContentInfoBarManager::FromWebContents(web_contents);
infobar_manager->AddObserver(this);
}
void DefaultBrowserPromptManager::CloseAllInfoBars() {
browser_tab_strip_tracker_.reset();
for (const auto& infobars_entry : infobars_) {
infobars_entry.second->owner()->RemoveObserver(this);
infobars_entry.second->RemoveSelf();
}
infobars_.clear();
}
void DefaultBrowserPromptManager::SetShowAppMenuPromptVisibility(bool show) {
if (show == show_app_menu_prompt_) {
return;
}
if (show) {
PrefService* local_state = g_browser_process->local_state();
base::TimeDelta app_menu_remaining_duration;
if (local_state->FindPreference(prefs::kDefaultBrowserFirstShownTime)
->IsDefaultValue()) {
local_state->SetTime(prefs::kDefaultBrowserFirstShownTime,
base::Time::Now());
app_menu_remaining_duration =
features::kDefaultBrowserAppMenuDuration.Get();
} else {
base::Time first_shown_time =
local_state->GetTime(prefs::kDefaultBrowserFirstShownTime);
// There is a chance the remaining duration is negative due to time
// passing since `ShouldShowAppMenuPrompt()` was last checked, so clamp to
// >= 0.
app_menu_remaining_duration =
std::max(features::kDefaultBrowserAppMenuDuration.Get() -
(base::Time::Now() - first_shown_time),
base::Microseconds(0));
}
app_menu_prompt_dismiss_timer_.Start(
FROM_HERE, app_menu_remaining_duration, base::BindOnce([]() {
UpdatePrefsForDismissedPrompt(
BrowserList::GetInstance()->GetLastActive()->profile());
DefaultBrowserPromptManager::GetInstance()->CloseAllPrompts();
}));
} else {
app_menu_prompt_dismiss_timer_.Stop();
}
show_app_menu_prompt_ = show;
for (auto& obs : observers_) {
obs.OnShowAppMenuPromptChanged();
}
}
void DefaultBrowserPromptManager::SetAppMenuItemVisibility(bool show) {
show_app_menu_item_ = show;
}