| // Copyright (c) 2014 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/chromeos/login/screens/reset_screen.h" |
| |
| #include "base/command_line.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "base/task/post_task.h" |
| #include "base/values.h" |
| #include "chrome/browser/browser_process.h" |
| #include "chrome/browser/chromeos/login/screens/base_screen_delegate.h" |
| #include "chrome/browser/chromeos/login/screens/error_screen.h" |
| #include "chrome/browser/chromeos/login/screens/network_error.h" |
| #include "chrome/browser/chromeos/login/screens/reset_view.h" |
| #include "chrome/browser/chromeos/login/ui/login_display_host.h" |
| #include "chrome/browser/chromeos/reset/metrics.h" |
| #include "chrome/browser/chromeos/tpm_firmware_update.h" |
| #include "chrome/common/pref_names.h" |
| #include "chromeos/chromeos_switches.h" |
| #include "chromeos/dbus/dbus_thread_manager.h" |
| #include "chromeos/dbus/power_manager_client.h" |
| #include "chromeos/dbus/session_manager_client.h" |
| #include "components/prefs/pref_registry_simple.h" |
| #include "components/prefs/pref_service.h" |
| #include "third_party/cros_system_api/dbus/service_constants.h" |
| |
| namespace chromeos { |
| namespace { |
| |
| constexpr const char kUserActionCancelReset[] = "cancel-reset"; |
| constexpr const char kUserActionResetRestartPressed[] = "restart-pressed"; |
| constexpr const char kUserActionResetPowerwashPressed[] = "powerwash-pressed"; |
| constexpr const char kUserActionResetLearnMorePressed[] = "learn-more-link"; |
| constexpr const char kUserActionResetRollbackToggled[] = "rollback-toggled"; |
| constexpr const char kUserActionResetShowConfirmationPressed[] = |
| "show-confirmation"; |
| constexpr const char kUserActionResetResetConfirmationDismissed[] = |
| "reset-confirm-dismissed"; |
| constexpr const char kUserActionTPMFirmwareUpdateLearnMore[] = |
| "tpm-firmware-update-learn-more-link"; |
| |
| constexpr const char kContextKeyIsRollbackAvailable[] = "rollback-available"; |
| constexpr const char kContextKeyIsRollbackChecked[] = "rollback-checked"; |
| constexpr const char kContextKeyIsTPMFirmwareUpdateAvailable[] = |
| "tpm-firmware-update-available"; |
| constexpr const char kContextKeyIsTPMFirmwareUpdateChecked[] = |
| "tpm-firmware-update-checked"; |
| constexpr const char kContextKeyIsTPMFirmwareUpdateEditable[] = |
| "tpm-firmware-update-editable"; |
| constexpr const char kContextKeyTPMFirmwareUpdateMode[] = |
| "tpm-firmware-update-mode"; |
| constexpr const char kContextKeyIsConfirmational[] = "is-confirmational-view"; |
| constexpr const char kContextKeyIsOfficialBuild[] = "is-official-build"; |
| constexpr const char kContextKeyScreenState[] = "screen-state"; |
| |
| void StartTPMFirmwareUpdate( |
| tpm_firmware_update::Mode requested_mode, |
| const std::set<tpm_firmware_update::Mode>& available_modes) { |
| if (available_modes.count(requested_mode) == 0) { |
| // This should not happen, except for edge cases such as hijacked |
| // UI, device policy changing while the dialog was up, etc. |
| LOG(ERROR) << "Firmware update no longer available?"; |
| return; |
| } |
| |
| std::string mode_string; |
| switch (requested_mode) { |
| case tpm_firmware_update::Mode::kNone: |
| // Error handled below. |
| break; |
| case tpm_firmware_update::Mode::kPowerwash: |
| mode_string = "first_boot"; |
| break; |
| case tpm_firmware_update::Mode::kPreserveDeviceState: |
| mode_string = "preserve_stateful"; |
| break; |
| case tpm_firmware_update::Mode::kCleanup: |
| mode_string = "cleanup"; |
| break; |
| } |
| |
| if (mode_string.empty()) { |
| LOG(ERROR) << "Invalid mode " << static_cast<int>(requested_mode); |
| return; |
| } |
| |
| DBusThreadManager::Get()->GetSessionManagerClient()->StartTPMFirmwareUpdate( |
| mode_string); |
| } |
| |
| } // namespace |
| |
| ResetScreen::ResetScreen(BaseScreenDelegate* base_screen_delegate, |
| ResetView* view) |
| : BaseScreen(base_screen_delegate, OobeScreen::SCREEN_OOBE_RESET), |
| view_(view), |
| weak_ptr_factory_(this) { |
| DCHECK(view_); |
| if (view_) |
| view_->Bind(this); |
| context_.SetInteger(kContextKeyScreenState, STATE_RESTART_REQUIRED); |
| context_.SetBoolean(kContextKeyIsRollbackAvailable, false); |
| context_.SetBoolean(kContextKeyIsRollbackChecked, false); |
| context_.SetBoolean(kContextKeyIsTPMFirmwareUpdateAvailable, false); |
| context_.SetBoolean(kContextKeyIsTPMFirmwareUpdateChecked, false); |
| context_.SetBoolean(kContextKeyIsTPMFirmwareUpdateEditable, true); |
| context_.SetInteger(kContextKeyTPMFirmwareUpdateMode, |
| static_cast<int>(tpm_firmware_update::Mode::kPowerwash)); |
| context_.SetBoolean(kContextKeyIsConfirmational, false); |
| context_.SetBoolean(kContextKeyIsOfficialBuild, false); |
| #if defined(OFFICIAL_BUILD) |
| context_.SetBoolean(kContextKeyIsOfficialBuild, true); |
| #endif |
| } |
| |
| ResetScreen::~ResetScreen() { |
| if (view_) |
| view_->Unbind(); |
| DBusThreadManager::Get()->GetUpdateEngineClient()->RemoveObserver(this); |
| } |
| |
| // static |
| void ResetScreen::RegisterPrefs(PrefRegistrySimple* registry) { |
| registry->RegisterBooleanPref(prefs::kFactoryResetRequested, false); |
| registry->RegisterIntegerPref( |
| prefs::kFactoryResetTPMFirmwareUpdateMode, |
| static_cast<int>(tpm_firmware_update::Mode::kNone)); |
| } |
| |
| void ResetScreen::Show() { |
| if (view_) |
| view_->Show(); |
| |
| reset::DialogViewType dialog_type = |
| reset::DIALOG_VIEW_TYPE_SIZE; // used by UMA metrics. |
| |
| ContextEditor context_editor = GetContextEditor(); |
| |
| bool restart_required = user_manager::UserManager::Get()->IsUserLoggedIn() || |
| !base::CommandLine::ForCurrentProcess()->HasSwitch( |
| switches::kFirstExecAfterBoot); |
| if (restart_required) { |
| context_editor.SetInteger(kContextKeyScreenState, STATE_RESTART_REQUIRED); |
| dialog_type = reset::DIALOG_SHORTCUT_RESTART_REQUIRED; |
| } else { |
| context_editor.SetInteger(kContextKeyScreenState, STATE_POWERWASH_PROPOSAL); |
| } |
| |
| // Set availability of Rollback feature. |
| if (base::CommandLine::ForCurrentProcess()->HasSwitch( |
| switches::kDisableRollbackOption)) { |
| context_editor.SetBoolean(kContextKeyIsRollbackAvailable, false); |
| dialog_type = reset::DIALOG_SHORTCUT_OFFERING_ROLLBACK_UNAVAILABLE; |
| } else { |
| chromeos::DBusThreadManager::Get() |
| ->GetUpdateEngineClient() |
| ->CanRollbackCheck(base::Bind(&ResetScreen::OnRollbackCheck, |
| weak_ptr_factory_.GetWeakPtr())); |
| } |
| |
| if (dialog_type < reset::DIALOG_VIEW_TYPE_SIZE) { |
| UMA_HISTOGRAM_ENUMERATION("Reset.ChromeOS.PowerwashDialogShown", |
| dialog_type, reset::DIALOG_VIEW_TYPE_SIZE); |
| } |
| |
| // Set availability of TPM firmware update. |
| PrefService* prefs = g_browser_process->local_state(); |
| bool tpm_firmware_update_requested = |
| prefs->HasPrefPath(prefs::kFactoryResetTPMFirmwareUpdateMode); |
| if (tpm_firmware_update_requested) { |
| // If an update has been requested previously, rely on the earlier update |
| // availability test to initialize the dialog. This avoids a race condition |
| // where the powerwash dialog gets shown immediately after reboot before the |
| // init job to determine update availability has completed. |
| context_editor.SetBoolean(kContextKeyIsTPMFirmwareUpdateAvailable, true); |
| context_editor.SetInteger( |
| kContextKeyTPMFirmwareUpdateMode, |
| prefs->GetInteger(prefs::kFactoryResetTPMFirmwareUpdateMode)); |
| } else { |
| // If a TPM firmware update hasn't previously been requested, check the |
| // system to see whether to offer the checkbox to update TPM firmware. Note |
| // that due to the asynchronous availability check, the decision might not |
| // be available immediately, so set a timeout of a couple seconds. |
| tpm_firmware_update::GetAvailableUpdateModes( |
| base::BindOnce(&ResetScreen::OnTPMFirmwareUpdateAvailableCheck, |
| weak_ptr_factory_.GetWeakPtr()), |
| base::TimeDelta::FromSeconds(10)); |
| } |
| |
| context_editor.SetBoolean(kContextKeyIsTPMFirmwareUpdateChecked, |
| tpm_firmware_update_requested); |
| context_editor.SetBoolean(kContextKeyIsTPMFirmwareUpdateEditable, |
| !tpm_firmware_update_requested); |
| |
| // Clear prefs so the reset screen isn't triggered again the next time the |
| // device is about to show the login screen. |
| prefs->ClearPref(prefs::kFactoryResetRequested); |
| prefs->ClearPref(prefs::kFactoryResetTPMFirmwareUpdateMode); |
| prefs->CommitPendingWrite(); |
| } |
| |
| void ResetScreen::Hide() { |
| if (view_) |
| view_->Hide(); |
| } |
| |
| void ResetScreen::OnViewDestroyed(ResetView* view) { |
| if (view_ == view) |
| view_ = nullptr; |
| } |
| |
| void ResetScreen::OnUserAction(const std::string& action_id) { |
| if (action_id == kUserActionCancelReset) |
| OnCancel(); |
| else if (action_id == kUserActionResetRestartPressed) |
| OnRestart(); |
| else if (action_id == kUserActionResetPowerwashPressed) |
| OnPowerwash(); |
| else if (action_id == kUserActionResetLearnMorePressed) |
| ShowHelpArticle(HelpAppLauncher::HELP_POWERWASH); |
| else if (action_id == kUserActionResetRollbackToggled) |
| OnToggleRollback(); |
| else if (action_id == kUserActionResetShowConfirmationPressed) |
| OnShowConfirm(); |
| else if (action_id == kUserActionResetResetConfirmationDismissed) |
| OnConfirmationDismissed(); |
| else if (action_id == kUserActionTPMFirmwareUpdateLearnMore) |
| ShowHelpArticle(HelpAppLauncher::HELP_TPM_FIRMWARE_UPDATE); |
| else |
| BaseScreen::OnUserAction(action_id); |
| } |
| |
| void ResetScreen::OnCancel() { |
| if (context_.GetInteger(kContextKeyScreenState, STATE_RESTART_REQUIRED) == |
| STATE_REVERT_PROMISE) |
| return; |
| // Hide Rollback view for the next show. |
| if (context_.GetBoolean(kContextKeyIsRollbackAvailable) && |
| context_.GetBoolean(kContextKeyIsRollbackChecked)) |
| OnToggleRollback(); |
| Finish(ScreenExitCode::RESET_CANCELED); |
| DBusThreadManager::Get()->GetUpdateEngineClient()->RemoveObserver(this); |
| } |
| |
| void ResetScreen::OnPowerwash() { |
| if (context_.GetInteger(kContextKeyScreenState, 0) != |
| STATE_POWERWASH_PROPOSAL) |
| return; |
| |
| GetContextEditor().SetBoolean(kContextKeyIsConfirmational, false); |
| CommitContextChanges(); |
| |
| if (context_.GetBoolean(kContextKeyIsRollbackChecked) && |
| !context_.GetBoolean(kContextKeyIsRollbackAvailable)) { |
| NOTREACHED() |
| << "Rollback was checked but not available. Starting powerwash."; |
| } |
| |
| if (context_.GetBoolean(kContextKeyIsRollbackAvailable) && |
| context_.GetBoolean(kContextKeyIsRollbackChecked)) { |
| GetContextEditor().SetInteger(kContextKeyScreenState, STATE_REVERT_PROMISE); |
| DBusThreadManager::Get()->GetUpdateEngineClient()->AddObserver(this); |
| VLOG(1) << "Starting Rollback"; |
| DBusThreadManager::Get()->GetUpdateEngineClient()->Rollback(); |
| } else if (context_.GetBoolean(kContextKeyIsTPMFirmwareUpdateChecked)) { |
| VLOG(1) << "Starting TPM firmware update"; |
| // Re-check availability with a couple seconds timeout. This addresses the |
| // case where the powerwash dialog gets shown immediately after reboot and |
| // the decision on whether the update is available is not known immediately. |
| tpm_firmware_update::GetAvailableUpdateModes( |
| base::BindOnce( |
| &StartTPMFirmwareUpdate, |
| static_cast<tpm_firmware_update::Mode>( |
| context_.GetInteger(kContextKeyTPMFirmwareUpdateMode))), |
| base::TimeDelta::FromSeconds(10)); |
| } else { |
| VLOG(1) << "Starting Powerwash"; |
| DBusThreadManager::Get()->GetSessionManagerClient()->StartDeviceWipe(); |
| } |
| } |
| |
| void ResetScreen::OnRestart() { |
| PrefService* prefs = g_browser_process->local_state(); |
| prefs->SetBoolean(prefs::kFactoryResetRequested, true); |
| if (context_.GetBoolean(kContextKeyIsTPMFirmwareUpdateChecked)) { |
| prefs->SetInteger(prefs::kFactoryResetTPMFirmwareUpdateMode, |
| static_cast<int>(tpm_firmware_update::Mode::kPowerwash)); |
| } else { |
| prefs->ClearPref(prefs::kFactoryResetTPMFirmwareUpdateMode); |
| } |
| prefs->CommitPendingWrite(); |
| |
| chromeos::DBusThreadManager::Get()->GetPowerManagerClient()->RequestRestart( |
| power_manager::REQUEST_RESTART_FOR_USER, "login reset screen restart"); |
| } |
| |
| void ResetScreen::OnToggleRollback() { |
| // Hide Rollback if visible. |
| if (context_.GetBoolean(kContextKeyIsRollbackAvailable) && |
| context_.GetBoolean(kContextKeyIsRollbackChecked)) { |
| VLOG(1) << "Hiding rollback view on reset screen"; |
| GetContextEditor().SetBoolean(kContextKeyIsRollbackChecked, false); |
| return; |
| } |
| |
| // Show Rollback if available. |
| VLOG(1) << "Requested rollback availability" |
| << context_.GetBoolean(kContextKeyIsRollbackAvailable); |
| if (context_.GetBoolean(kContextKeyIsRollbackAvailable) && |
| !context_.GetBoolean(kContextKeyIsRollbackChecked)) { |
| UMA_HISTOGRAM_ENUMERATION( |
| "Reset.ChromeOS.PowerwashDialogShown", |
| reset::DIALOG_SHORTCUT_OFFERING_ROLLBACK_AVAILABLE, |
| reset::DIALOG_VIEW_TYPE_SIZE); |
| GetContextEditor().SetBoolean(kContextKeyIsRollbackChecked, true); |
| } |
| } |
| |
| void ResetScreen::OnShowConfirm() { |
| reset::DialogViewType dialog_type = |
| context_.GetBoolean(kContextKeyIsRollbackChecked) |
| ? reset::DIALOG_SHORTCUT_CONFIRMING_POWERWASH_AND_ROLLBACK |
| : reset::DIALOG_SHORTCUT_CONFIRMING_POWERWASH_ONLY; |
| UMA_HISTOGRAM_ENUMERATION("Reset.ChromeOS.PowerwashDialogShown", dialog_type, |
| reset::DIALOG_VIEW_TYPE_SIZE); |
| |
| GetContextEditor().SetBoolean(kContextKeyIsConfirmational, true); |
| } |
| |
| void ResetScreen::OnConfirmationDismissed() { |
| GetContextEditor().SetBoolean(kContextKeyIsConfirmational, false); |
| } |
| |
| void ResetScreen::ShowHelpArticle(HelpAppLauncher::HelpTopic topic) { |
| #if defined(OFFICIAL_BUILD) |
| VLOG(1) << "Trying to view help article " << topic; |
| if (!help_app_.get()) { |
| help_app_ = new HelpAppLauncher( |
| LoginDisplayHost::default_host()->GetNativeWindow()); |
| } |
| help_app_->ShowHelpTopic(topic); |
| #endif |
| } |
| |
| void ResetScreen::UpdateStatusChanged( |
| const UpdateEngineClient::Status& status) { |
| VLOG(1) << "Update status change to " << status.status; |
| if (status.status == UpdateEngineClient::UPDATE_STATUS_ERROR || |
| status.status == |
| UpdateEngineClient::UPDATE_STATUS_REPORTING_ERROR_EVENT) { |
| GetContextEditor().SetInteger(kContextKeyScreenState, STATE_ERROR); |
| // Show error screen. |
| GetErrorScreen()->SetUIState(NetworkError::UI_STATE_ROLLBACK_ERROR); |
| get_base_screen_delegate()->ShowErrorScreen(); |
| } else if (status.status == |
| UpdateEngineClient::UPDATE_STATUS_UPDATED_NEED_REBOOT) { |
| DBusThreadManager::Get()->GetPowerManagerClient()->RequestRestart( |
| power_manager::REQUEST_RESTART_FOR_UPDATE, "login reset screen update"); |
| } |
| } |
| |
| // Invoked from call to CanRollbackCheck upon completion of the DBus call. |
| void ResetScreen::OnRollbackCheck(bool can_rollback) { |
| VLOG(1) << "Callback from CanRollbackCheck, result " << can_rollback; |
| reset::DialogViewType dialog_type = |
| can_rollback ? reset::DIALOG_SHORTCUT_OFFERING_ROLLBACK_AVAILABLE |
| : reset::DIALOG_SHORTCUT_OFFERING_ROLLBACK_UNAVAILABLE; |
| UMA_HISTOGRAM_ENUMERATION("Reset.ChromeOS.PowerwashDialogShown", dialog_type, |
| reset::DIALOG_VIEW_TYPE_SIZE); |
| |
| GetContextEditor().SetBoolean(kContextKeyIsRollbackAvailable, can_rollback); |
| } |
| |
| void ResetScreen::OnTPMFirmwareUpdateAvailableCheck( |
| const std::set<tpm_firmware_update::Mode>& modes) { |
| bool available = modes.count(tpm_firmware_update::Mode::kPowerwash) > 0; |
| ContextEditor context_editor = GetContextEditor(); |
| context_editor.SetBoolean(kContextKeyIsTPMFirmwareUpdateAvailable, available); |
| if (available) { |
| context_editor.SetInteger( |
| kContextKeyTPMFirmwareUpdateMode, |
| static_cast<int>(tpm_firmware_update::Mode::kPowerwash)); |
| } |
| } |
| |
| ErrorScreen* ResetScreen::GetErrorScreen() { |
| return get_base_screen_delegate()->GetErrorScreen(); |
| } |
| |
| } // namespace chromeos |