| // 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.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 |