// Copyright 2022 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/lifetime/application_lifetime_chromeos.h"
#include "ash/constants/ash_pref_names.h"
#include "base/metrics/histogram_functions.h"
#include "base/trace_event/trace_event.h"
#include "base/values.h"
#include "chrome/browser/ash/boot_times_recorder/boot_times_recorder.h"
#include "chrome/browser/ash/profiles/profile_helper.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/lifetime/application_lifetime.h"
#include "chrome/browser/lifetime/application_lifetime_chromeos.h"
#include "chrome/browser/lifetime/termination_notification.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/common/pref_names.h"
#include "chromeos/ash/components/dbus/dbus_thread_manager.h"
#include "chromeos/ash/components/dbus/session_manager/session_manager_client.h"
#include "chromeos/ash/components/dbus/update_engine/update_engine_client.h"
#include "chromeos/ash/components/login/session/session_termination_manager.h"
#include "chromeos/ash/components/settings/cros_settings.h"
#include "chromeos/dbus/power/power_manager_client.h"
#include "chromeos/dbus/power/power_policy_controller.h"
#include "components/language/core/browser/pref_names.h"
#include "components/language/core/common/locale_util.h"
#include "components/prefs/pref_service.h"
#include "components/user_manager/user_manager.h"
#include "content/public/browser/browser_thread.h"
namespace chrome {
namespace {
ash::UpdateEngineClient* GetUpdateEngineClient() {
ash::UpdateEngineClient* update_engine_client =
ash::UpdateEngineClient::Get();
DCHECK(update_engine_client);
return update_engine_client;
}
chromeos::PowerManagerClient* GetPowerManagerClient() {
chromeos::PowerManagerClient* power_manager_client =
chromeos::PowerManagerClient::Get();
DCHECK(power_manager_client);
return power_manager_client;
}
// Whether Chrome should send stop request to a session manager.
bool g_send_stop_request_to_session_manager = false;
void ReportSessionUMAMetrics() {
// GetProfileByUser() will crash in tests if profile_manager() from
// g_browser_process is not initialized.
if (!user_manager::UserManager::IsInitialized() ||
!g_browser_process->profile_manager()) {
return;
}
const user_manager::User* primary_user =
user_manager::UserManager::Get()->GetPrimaryUser();
if (!primary_user) {
return;
}
Profile* profile = ash::ProfileHelper::Get()->GetProfileByUser(primary_user);
// Could be nullptr in tests.
if (!profile) {
return;
}
PrefService* prefs = profile->GetPrefs();
if (!prefs) {
return;
}
base::Time session_start_time =
prefs->GetTime(ash::prefs::kAshLoginSessionStartedTime);
if (!session_start_time.is_null()) {
base::TimeDelta duration = base::Time::Now() - session_start_time;
// Use CustomCounts histogram instead of CustomTimes because the latter
// allows 24 days maximum (data size limit) but we need 1 month.
if (prefs->GetBoolean(ash::prefs::kAshLoginSessionStartedIsFirstSession)) {
// Report 1 minute ... 30 days in minutes.
base::UmaHistogramCustomCounts("Ash.Login.TotalFirstSessionDuration",
duration.InMinutes(), 1,
base::Days(30) / base::Minutes(1), 100);
} else {
// Report 1 minute ... 30 days in minutes.
base::UmaHistogramCustomCounts("Ash.Login.TotalSessionDuration",
duration.InMinutes(), 1,
base::Days(30) / base::Minutes(1), 100);
}
}
prefs->ClearPref(ash::prefs::kAshLoginSessionStartedTime);
prefs->ClearPref(ash::prefs::kAshLoginSessionStartedIsFirstSession);
}
} // namespace
void AttemptUserExit() {
VLOG(1) << "AttemptUserExit";
ash::BootTimesRecorder::Get()->AddLogoutTimeMarker("LogoutStarted", false);
ReportSessionUMAMetrics();
PrefService* state = g_browser_process->local_state();
if (state) {
ash::BootTimesRecorder::Get()->OnLogoutStarted(state);
if (SetLocaleForNextStart(state)) {
TRACE_EVENT0("shutdown", "CommitPendingWrite");
state->CommitPendingWrite();
}
}
SetSendStopRequestToSessionManager();
// On ChromeOS, always terminate the browser, regardless of the result of
// AreAllBrowsersCloseable(). See crbug.com/123107.
browser_shutdown::NotifyAppTerminating();
StopSession();
}
void AttemptRelaunch() {
GetPowerManagerClient()->RequestRestart(power_manager::REQUEST_RESTART_OTHER,
"Chrome relaunch");
}
void AttemptExit() {
AttemptUserExit();
}
void RelaunchIgnoreUnloadHandlers() {
AttemptRelaunch();
}
void RelaunchForUpdate() {
DCHECK(UpdatePending());
GetUpdateEngineClient()->RebootAfterUpdate();
}
bool UpdatePending() {
if (!ash::DBusThreadManager::IsInitialized())
return false;
return GetUpdateEngineClient()->GetLastStatus().current_operation() ==
update_engine::UPDATED_NEED_REBOOT;
}
bool SetLocaleForNextStart(PrefService* local_state) {
// If a policy mandates the login screen locale, use it.
ash::CrosSettings* cros_settings = ash::CrosSettings::Get();
const base::Value::List* login_screen_locales = nullptr;
if (cros_settings->GetList(ash::kDeviceLoginScreenLocales,
&login_screen_locales) &&
!login_screen_locales->empty() &&
login_screen_locales->front().is_string()) {
std::string login_screen_locale = login_screen_locales->front().GetString();
local_state->SetString(language::prefs::kApplicationLocale,
login_screen_locale);
return true;
}
// Login screen should show up in owner's locale.
std::string owner_locale = local_state->GetString(prefs::kOwnerLocale);
std::string pref_locale =
local_state->GetString(language::prefs::kApplicationLocale);
language::ConvertToActualUILocale(&pref_locale);
if (!owner_locale.empty() && pref_locale != owner_locale &&
!local_state->IsManagedPreference(language::prefs::kApplicationLocale)) {
local_state->SetString(language::prefs::kApplicationLocale, owner_locale);
return true;
}
return false;
}
bool IsSendingStopRequestToSessionManager() {
return g_send_stop_request_to_session_manager;
}
void SetSendStopRequestToSessionManager(bool should_send_request) {
g_send_stop_request_to_session_manager = should_send_request;
}
void StopSession() {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
// Only call this function once.
static bool notified = false;
if (notified)
return;
notified = true;
if (chromeos::PowerPolicyController::IsInitialized())
chromeos::PowerPolicyController::Get()->NotifyChromeIsExiting();
if (chrome::UpdatePending()) {
chrome::RelaunchForUpdate();
return;
}
// Signal session manager to stop the session if Chrome has initiated an
// attempt to do so.
if (chrome::IsSendingStopRequestToSessionManager() &&
ash::SessionTerminationManager::Get()) {
ash::SessionTerminationManager::Get()->StopSession(
login_manager::SessionStopReason::REQUEST_FROM_SESSION_MANAGER);
}
}
void LogMarkAsCleanShutdown() {
// Enable VLOG level 1.
#undef ENABLED_VLOG_LEVEL
#define ENABLED_VLOG_LEVEL 1
VLOG(1) << "Set the current session exit type as clean.";
#undef ENABLED_VLOG_LEVEL
#define ENABLED_VLOG_LEVEL -1
}
} // namespace chrome