blob: acf969bdccbc7dc6d950c78b8a1fb04e250d5fdb [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/glic/glic_profile_manager.h"
#include "base/command_line.h"
#include "base/functional/bind.h"
#include "base/task/sequenced_task_runner.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/glic/fre/glic_fre_controller.h"
#include "chrome/browser/glic/public/glic_enabling.h"
#include "chrome/browser/glic/public/glic_keyed_service_factory.h"
#include "chrome/browser/global_features.h"
#include "chrome/browser/lifetime/termination_notification.h"
#include "chrome/browser/profiles/nuke_profile_directory_utils.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_finder.h"
#include "chrome/browser/ui/browser_window/public/browser_window_interface_iterator.h"
#include "chrome/browser/ui/profiles/profile_picker.h"
#include "chrome/browser/ui/scoped_tabbed_browser_displayer.h"
#include "chrome/common/chrome_features.h"
#include "chrome/common/chrome_switches.h"
#include "content/public/browser/network_service_instance.h"
namespace {
std::optional<Profile*> g_forced_profile_for_launch_;
std::optional<base::MemoryPressureMonitor::MemoryPressureLevel>
g_forced_memory_pressure_level_;
std::optional<network::mojom::ConnectionType> g_forced_connection_type_;
} // namespace
namespace glic {
namespace {
void AutoOpenGlicPanel() {
Profile* profile = GlicProfileManager::GetInstance()->GetProfileForLaunch();
if (!profile) {
return;
}
// TODO(379166075): Remove after updating GetProfileForLaunch.
if (!GlicEnabling::IsEnabledForProfile(profile)) {
return;
}
Browser* browser = nullptr;
mojom::InvocationSource pretend_source = mojom::InvocationSource::kOsButton;
if (base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
::switches::kGlicOpenOnStartup) == "attached") {
// Attachment is best effort; FindLastActiveWithProfile() may return null
// here.
browser = chrome::FindLastActiveWithProfile(profile);
pretend_source = mojom::InvocationSource::kTopChromeButton;
}
GlicKeyedServiceFactory::GetGlicKeyedService(profile)->ToggleUI(
browser, /*prevent_close=*/true, pretend_source);
}
} // namespace
GlicProfileManager* GlicProfileManager::GetInstance() {
return g_browser_process->GetFeatures()->glic_profile_manager();
}
GlicProfileManager::GlicProfileManager() {
ProfileManager* profile_manager = g_browser_process->profile_manager();
if (profile_manager) {
profile_manager->AddObserver(this);
}
}
GlicProfileManager::~GlicProfileManager() = default;
Profile* GlicProfileManager::GetProfileForLaunch() const {
if (g_forced_profile_for_launch_) {
return *g_forced_profile_for_launch_;
}
// If the glic window is currently showing detached use that profile.
if (last_active_glic_ && last_active_glic_->IsWindowDetached()) {
return last_active_glic_->profile();
}
// Look for a profile to based on most recently used browser windows
for (BrowserWindowInterface* browser :
GetBrowserWindowInterfacesOrderedByActivation()) {
if (GlicEnabling::IsEnabledAndConsentForProfile(browser->GetProfile())) {
return browser->GetProfile();
}
}
// TODO(https://crbug.com/379166075) Remove loaded profile look up once the
// pinned profile is implemented.
// Look at the list of loaded profiles to use for glic
if (g_browser_process->profile_manager()) {
for (Profile* profile :
g_browser_process->profile_manager()->GetLoadedProfiles()) {
if (GlicEnabling::IsEnabledAndConsentForProfile(profile)) {
return profile;
}
}
}
// TODO(https://crbug.com/379166075): Implement profile choice logic.
return nullptr;
}
void GlicProfileManager::SetActiveGlic(GlicKeyedService* glic) {
if (last_active_glic_ && last_active_glic_.get() != glic &&
last_active_glic_->IsWindowShowing()) {
last_active_glic_->ClosePanel();
}
Profile* last_active_glic_profile = nullptr;
if (glic) {
last_active_glic_ = glic->GetWeakPtr();
last_active_glic_profile = last_active_glic_->profile();
} else {
last_active_glic_.reset();
}
observers_.Notify(&Observer::OnLastActiveGlicProfileChanged,
last_active_glic_profile);
}
void GlicProfileManager::OnServiceShutdown(GlicKeyedService* glic) {
if (last_active_glic_ && last_active_glic_.get() == glic) {
SetActiveGlic(nullptr);
}
}
void GlicProfileManager::Shutdown() {
g_browser_process->profile_manager()->RemoveObserver(this);
}
void GlicProfileManager::OnLoadingClientForService(GlicKeyedService* glic) {
if (base::FeatureList::IsEnabled(features::kGlicWarmMultiple)) {
return;
}
if (last_loaded_glic_ && last_loaded_glic_.get() != glic) {
last_loaded_glic_->CloseUI();
}
if (glic) {
last_loaded_glic_ = glic->GetWeakPtr();
} else {
last_loaded_glic_.reset();
}
}
void GlicProfileManager::OnUnloadingClientForService(GlicKeyedService* glic) {
if (last_loaded_glic_ && last_loaded_glic_.get() == glic) {
last_loaded_glic_.reset();
}
}
void GlicProfileManager::ShouldPreloadForProfile(
Profile* profile,
ShouldPreloadCallback callback) {
if (!profile || IsProfileDirectoryMarkedForDeletion(profile->GetPath())) {
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(std::move(callback),
GlicPrewarmingChecksResult::kProfileGone));
return;
}
if (!base::FeatureList::IsEnabled(features::kGlicWarming)) {
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE,
base::BindOnce(std::move(callback),
GlicPrewarmingChecksResult::kWarmingDisabled));
return;
}
GlicPrewarmingChecksResult result;
switch (GlicEnabling::GetProfileReadyState(profile)) {
case mojom::ProfileReadyState::kReady:
CanPreloadForProfile(profile, std::move(callback));
return;
case mojom::ProfileReadyState::kUnknownError:
result = GlicPrewarmingChecksResult::kProfileNotReadyUnknown;
break;
case mojom::ProfileReadyState::kSignInRequired:
result = GlicPrewarmingChecksResult::kProfileRequiresSignIn;
break;
case mojom::ProfileReadyState::kIneligible:
result = GlicPrewarmingChecksResult::kProfileNotEligible;
break;
case mojom::ProfileReadyState::kDisabledByAdmin:
result = GlicPrewarmingChecksResult::kProfileDisallowedByAdmin;
break;
}
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(std::move(callback), result));
}
void GlicProfileManager::ShouldPreloadFreForProfile(
Profile* profile,
base::OnceCallback<void(bool)> callback) {
if (!base::FeatureList::IsEnabled(features::kGlicFreWarming) ||
// We only want to preload the FRE if it has not been completed.
GlicEnabling::IsEnabledAndConsentForProfile(profile)) {
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(std::move(callback), false));
return;
}
CanPreloadForProfile(
profile, base::BindOnce(
[](base::OnceCallback<void(bool)> callback,
GlicPrewarmingChecksResult reason) {
std::move(callback).Run(
reason == GlicPrewarmingChecksResult::kSuccess);
},
std::move(callback)));
}
GlicKeyedService* GlicProfileManager::GetLastActiveGlic() const {
return last_active_glic_.get();
}
void GlicProfileManager::MaybeAutoOpenGlicPanel() {
if (did_auto_open_ || !base::CommandLine::ForCurrentProcess()->HasSwitch(
::switches::kGlicOpenOnStartup)) {
return;
}
// TODO(391948342): Figure out why the FRE modal doesn't show when triggered
// too early, and wait for that condition rather than delaying.
base::SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask(
FROM_HERE, base::BindOnce(&AutoOpenGlicPanel), base::Seconds(30));
did_auto_open_ = true;
}
void GlicProfileManager::ShowProfilePicker() {
base::OnceCallback<void(Profile*)> callback = base::BindOnce(
&GlicProfileManager::DidSelectProfile, weak_ptr_factory_.GetWeakPtr());
// If the panel is not closed it will be on top of the profile picker.
if (last_active_glic_) {
last_active_glic_->ClosePanel();
}
ProfilePicker::Show(
ProfilePicker::Params::ForGlicManager(std::move(callback)));
}
void GlicProfileManager::DidSelectProfile(Profile* profile) {
if (!GlicEnabling::IsEnabledForProfile(profile)) {
return;
}
GlicKeyedService* service =
GlicKeyedServiceFactory::GetGlicKeyedService(profile);
if (!GlicEnabling::HasConsentedForProfile(profile)) {
// Open a browser and show the FRE in a new tab.
chrome::ScopedTabbedBrowserDisplayer displayer(profile);
service->OpenFreDialogInNewTab(displayer.browser(),
mojom::InvocationSource::kProfilePicker);
} else {
// Toggle glic but prevent close if it is already open for the selected
// profile.
service->ToggleUI(nullptr, /*prevent_close=*/true,
mojom::InvocationSource::kProfilePicker);
}
}
void GlicProfileManager::AddObserver(Observer* observer) {
observers_.AddObserver(observer);
}
void GlicProfileManager::RemoveObserver(Observer* observer) {
observers_.RemoveObserver(observer);
}
bool GlicProfileManager::IsShowing() const {
if (!last_active_glic_) {
return false;
}
return last_active_glic_->IsWindowOrFreShowing();
}
void GlicProfileManager::OnProfileMarkedForPermanentDeletion(Profile* profile) {
GlicKeyedService* glic_keyed_service =
glic::GlicKeyedServiceFactory::GetGlicKeyedService(profile);
if (!glic_keyed_service) {
return;
}
glic_keyed_service->Shutdown();
}
// static
void GlicProfileManager::ForceProfileForLaunchForTesting(
std::optional<Profile*> profile) {
g_forced_profile_for_launch_ = profile;
}
// static
void GlicProfileManager::ForceMemoryPressureForTesting(
std::optional<base::MemoryPressureMonitor::MemoryPressureLevel> level) {
g_forced_memory_pressure_level_ = level;
}
// static
void GlicProfileManager::ForceConnectionTypeForTesting(
std::optional<network::mojom::ConnectionType> connection_type) {
g_forced_connection_type_ = connection_type;
}
bool GlicProfileManager::IsUnderMemoryPressure() const {
// TODO(crbug.com/390719004): Look at discarding when pressure increases.
base::MemoryPressureMonitor::MemoryPressureLevel memory_pressure = base::
MemoryPressureMonitor::MemoryPressureLevel::MEMORY_PRESSURE_LEVEL_NONE;
if (g_forced_memory_pressure_level_) {
memory_pressure = *g_forced_memory_pressure_level_;
} else if (const auto* memory_monitor = base::MemoryPressureMonitor::Get()) {
memory_pressure = memory_monitor->GetCurrentPressureLevel();
}
return memory_pressure >= base::MemoryPressureMonitor::MemoryPressureLevel::
MEMORY_PRESSURE_LEVEL_MODERATE;
}
void GlicProfileManager::CanPreloadForProfile(Profile* profile,
ShouldPreloadCallback callback) {
auto produce_result = [&callback](GlicPrewarmingChecksResult result,
base::Location from_here =
base::Location::Current()) {
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
from_here, base::BindOnce(std::move(callback), result));
};
if (!profile || profile->ShutdownStarted()) {
return produce_result(GlicPrewarmingChecksResult::kProfileGone);
}
auto enablement = GlicEnabling::EnablementForProfile(profile);
if (!enablement.IsProfileEligible()) {
return produce_result(GlicPrewarmingChecksResult::kProfileNotEligible);
}
if (enablement.DisallowedByAdmin()) {
return produce_result(
GlicPrewarmingChecksResult::kProfileDisallowedByAdmin);
}
if (!enablement.IsEnabled()) {
return produce_result(GlicPrewarmingChecksResult::kProfileNotEnabledOther);
}
if (last_loaded_glic_ && last_loaded_glic_->profile() == profile) {
return produce_result(GlicPrewarmingChecksResult::kProfileIsLastLoaded);
}
if (last_active_glic_ && last_active_glic_->profile() == profile) {
return produce_result(GlicPrewarmingChecksResult::kProfileIsLastActive);
}
if (!base::FeatureList::IsEnabled(features::kGlicWarmMultiple) &&
IsShowing()) {
return produce_result(GlicPrewarmingChecksResult::kBlockedByShownGlic);
}
if (IsUnderMemoryPressure()) {
return produce_result(GlicPrewarmingChecksResult::kUnderMemoryPressure);
}
auto on_got_connection_type = [](ShouldPreloadCallback callback,
network::mojom::ConnectionType type) {
std::move(callback).Run(
network::NetworkConnectionTracker::IsConnectionCellular(type)
? GlicPrewarmingChecksResult::kCellularConnection
: GlicPrewarmingChecksResult::kSuccess);
};
auto callbacks = base::SplitOnceCallback(std::move(callback));
// Attempt to synchronously query the connection type.
network::mojom::ConnectionType connection_type;
bool synchronously_got_connection_type = false;
if (g_forced_connection_type_) {
synchronously_got_connection_type = true;
connection_type = *g_forced_connection_type_;
} else {
synchronously_got_connection_type =
content::GetNetworkConnectionTracker()->GetConnectionType(
&connection_type,
base::BindOnce(on_got_connection_type, std::move(callbacks.first)));
}
if (synchronously_got_connection_type) {
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE,
base::BindOnce(on_got_connection_type, std::move(callbacks.second),
connection_type));
}
}
base::WeakPtr<GlicProfileManager> GlicProfileManager::GetWeakPtr() {
return weak_ptr_factory_.GetWeakPtr();
}
} // namespace glic