| // Copyright 2012 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/webui/help/version_updater_chromeos.h" |
| |
| #include <cmath> |
| #include <memory> |
| #include <optional> |
| #include <string> |
| |
| #include "base/functional/bind.h" |
| #include "base/functional/callback_helpers.h" |
| #include "base/memory/ptr_util.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "chrome/browser/ash/login/startup_utils.h" |
| #include "chrome/browser/ash/login/wizard_controller.h" |
| #include "chrome/browser/ash/ownership/owner_settings_service_ash.h" |
| #include "chrome/browser/ash/ownership/owner_settings_service_ash_factory.h" |
| #include "chrome/browser/browser_process.h" |
| #include "chrome/browser/enterprise/browser_management/management_service_factory.h" |
| #include "chrome/browser/ui/webui/help/help_utils_chromeos.h" |
| #include "chrome/grit/generated_resources.h" |
| #include "chromeos/ash/components/dbus/update_engine/update_engine_client.h" |
| #include "chromeos/ash/components/network/network_handler.h" |
| #include "chromeos/ash/components/network/network_state.h" |
| #include "chromeos/ash/components/network/network_state_handler.h" |
| #include "chromeos/ash/components/network/network_type_pattern.h" |
| #include "chromeos/ash/components/settings/cros_settings.h" |
| #include "chromeos/ash/components/settings/cros_settings_names.h" |
| #include "chromeos/dbus/power/power_manager_client.h" |
| #include "chromeos/strings/grit/chromeos_strings.h" |
| #include "components/policy/core/common/management/management_service.h" |
| #include "content/public/browser/web_contents.h" |
| #include "third_party/cros_system_api/dbus/service_constants.h" |
| #include "ui/base/l10n/l10n_util.h" |
| |
| namespace { |
| |
| using ::ash::OwnerSettingsServiceAsh; |
| using ::ash::OwnerSettingsServiceAshFactory; |
| using ::ash::UpdateEngineClient; |
| |
| // Network status in the context of device update. |
| enum NetworkStatus { |
| // It's allowed to use current network for update. |
| NETWORK_STATUS_ALLOWED = 0, |
| // It's disallowed to use current network for update. |
| NETWORK_STATUS_DISALLOWED, |
| // Device is in offline state. |
| NETWORK_STATUS_OFFLINE |
| }; |
| |
| const bool kDefaultAutoUpdateDisabled = false; |
| |
| NetworkStatus GetNetworkStatus(bool interactive, |
| const ash::NetworkState* network, |
| bool metered) { |
| if (!network || !network->IsConnectedState()) // Offline state. |
| return NETWORK_STATUS_OFFLINE; |
| |
| if (metered && |
| !help_utils_chromeos::IsUpdateOverCellularAllowed(interactive)) { |
| return NETWORK_STATUS_DISALLOWED; |
| } |
| return NETWORK_STATUS_ALLOWED; |
| } |
| |
| // Returns true if auto-update is disabled by the system administrator. |
| bool IsAutoUpdateDisabled() { |
| bool update_disabled = kDefaultAutoUpdateDisabled; |
| ash::CrosSettings* settings = ash::CrosSettings::Get(); |
| if (!settings) |
| return update_disabled; |
| const base::Value* update_disabled_value = |
| settings->GetPref(ash::kUpdateDisabled); |
| if (update_disabled_value) { |
| CHECK(update_disabled_value->is_bool()); |
| update_disabled = update_disabled_value->GetBool(); |
| } |
| return update_disabled; |
| } |
| |
| std::u16string GetConnectionTypeAsUTF16(const ash::NetworkState* network, |
| bool metered) { |
| const std::string type = network->type(); |
| if (ash::NetworkTypePattern::WiFi().MatchesType(type)) { |
| if (metered) |
| return l10n_util::GetStringUTF16(IDS_NETWORK_TYPE_METERED_WIFI); |
| return l10n_util::GetStringUTF16(IDS_NETWORK_TYPE_WIFI); |
| } |
| if (ash::NetworkTypePattern::Ethernet().MatchesType(type)) |
| return l10n_util::GetStringUTF16(IDS_NETWORK_TYPE_ETHERNET); |
| if (ash::NetworkTypePattern::Mobile().MatchesType(type)) |
| return l10n_util::GetStringUTF16(IDS_NETWORK_TYPE_MOBILE_DATA); |
| if (ash::NetworkTypePattern::VPN().MatchesType(type)) |
| return l10n_util::GetStringUTF16(IDS_NETWORK_TYPE_VPN); |
| NOTREACHED_IN_MIGRATION(); |
| return std::u16string(); |
| } |
| |
| // Returns whether an update is allowed. If not, it calls the callback with |
| // the appropriate status. |interactive| indicates whether the user is actively |
| // checking for updates. |
| bool EnsureCanUpdate(bool interactive, |
| const VersionUpdater::StatusCallback& callback) { |
| if (IsAutoUpdateDisabled()) { |
| callback.Run(VersionUpdater::DISABLED_BY_ADMIN, 0, false, false, |
| std::string(), 0, |
| l10n_util::GetStringUTF16(IDS_UPGRADE_DISABLED_BY_POLICY)); |
| return false; |
| } |
| |
| ash::NetworkStateHandler* network_state_handler = |
| ash::NetworkHandler::Get()->network_state_handler(); |
| const ash::NetworkState* network = network_state_handler->DefaultNetwork(); |
| const bool metered = network_state_handler->default_network_is_metered(); |
| // Don't allow an update if we're currently offline or connected |
| // to a network for which updates are disallowed. |
| NetworkStatus status = GetNetworkStatus(interactive, network, metered); |
| if (status == NETWORK_STATUS_OFFLINE) { |
| callback.Run(VersionUpdater::FAILED_OFFLINE, 0, false, false, std::string(), |
| 0, l10n_util::GetStringUTF16(IDS_UPGRADE_OFFLINE)); |
| return false; |
| } else if (status == NETWORK_STATUS_DISALLOWED) { |
| std::u16string message = l10n_util::GetStringFUTF16( |
| IDS_UPGRADE_DISALLOWED, GetConnectionTypeAsUTF16(network, metered)); |
| callback.Run(VersionUpdater::FAILED_CONNECTION_TYPE_DISALLOWED, 0, false, |
| false, std::string(), 0, message); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| } // namespace |
| |
| std::unique_ptr<VersionUpdater> VersionUpdater::Create( |
| content::WebContents* web_contents) { |
| return base::WrapUnique(new VersionUpdaterCros(web_contents)); |
| } |
| |
| void VersionUpdaterCros::GetUpdateStatus(StatusCallback callback) { |
| callback_ = std::move(callback); |
| |
| // User is not actively checking for updates. |
| if (!EnsureCanUpdate(false /* interactive */, callback_)) |
| return; |
| |
| UpdateEngineClient* update_engine_client = UpdateEngineClient::Get(); |
| if (!update_engine_client->HasObserver(this)) |
| update_engine_client->AddObserver(this); |
| |
| this->UpdateStatusChanged(update_engine_client->GetLastStatus()); |
| } |
| |
| void VersionUpdaterCros::ApplyDeferredUpdate() { |
| UpdateEngineClient* update_engine_client = UpdateEngineClient::Get(); |
| |
| DCHECK(update_engine_client->GetLastStatus().current_operation() == |
| update_engine::Operation::UPDATED_BUT_DEFERRED); |
| |
| update_engine_client->ApplyDeferredUpdate(/*shutdown_after_update=*/false, |
| base::DoNothing()); |
| } |
| |
| void VersionUpdaterCros::CheckForUpdate(StatusCallback callback, |
| PromoteCallback) { |
| callback_ = std::move(callback); |
| |
| // User is actively checking for updates. |
| if (!EnsureCanUpdate(true /* interactive */, callback_)) |
| return; |
| |
| UpdateEngineClient* update_engine_client = UpdateEngineClient::Get(); |
| if (!update_engine_client->HasObserver(this)) |
| update_engine_client->AddObserver(this); |
| |
| if (update_engine_client->GetLastStatus().current_operation() != |
| update_engine::Operation::IDLE) { |
| check_for_update_when_idle_ = true; |
| return; |
| } |
| check_for_update_when_idle_ = false; |
| |
| // Make sure that libcros is loaded and OOBE is complete. |
| if (!ash::WizardController::default_controller() || |
| ash::StartupUtils::IsDeviceRegistered()) { |
| update_engine_client->RequestUpdateCheck(base::BindOnce( |
| &VersionUpdaterCros::OnUpdateCheck, weak_ptr_factory_.GetWeakPtr())); |
| } |
| } |
| |
| void VersionUpdaterCros::SetChannel(const std::string& channel, |
| bool is_powerwash_allowed) { |
| OwnerSettingsServiceAsh* service = |
| context_ |
| ? OwnerSettingsServiceAshFactory::GetInstance()->GetForBrowserContext( |
| context_) |
| : nullptr; |
| // For local owner set the field in the policy blob. |
| if (service) |
| service->SetString(ash::kReleaseChannel, channel); |
| UpdateEngineClient::Get()->SetChannel(channel, is_powerwash_allowed); |
| } |
| |
| void VersionUpdaterCros::SetUpdateOverCellularOneTimePermission( |
| StatusCallback callback, |
| const std::string& update_version, |
| int64_t update_size) { |
| callback_ = std::move(callback); |
| UpdateEngineClient::Get()->SetUpdateOverCellularOneTimePermission( |
| update_version, update_size, |
| base::BindOnce( |
| &VersionUpdaterCros::OnSetUpdateOverCellularOneTimePermission, |
| weak_ptr_factory_.GetWeakPtr())); |
| } |
| |
| void VersionUpdaterCros::OnSetUpdateOverCellularOneTimePermission( |
| bool success) { |
| if (success) { |
| // One time permission is set successfully, so we can proceed to update. |
| CheckForUpdate(callback_, VersionUpdater::PromoteCallback()); |
| } else { |
| // TODO(crbug.com/40612027): invoke callback to signal about page to |
| // show appropriate error message. |
| LOG(ERROR) << "Error setting update over cellular one time permission."; |
| callback_.Run(VersionUpdater::FAILED, 0, false, false, std::string(), 0, |
| std::u16string()); |
| } |
| } |
| |
| void VersionUpdaterCros::GetChannel(bool get_current_channel, |
| ChannelCallback cb) { |
| // Request the channel information. Bind to a weak_ptr bound method rather |
| // than passing |cb| directly so that |cb| does not outlive |this|. |
| UpdateEngineClient::Get()->GetChannel( |
| get_current_channel, |
| base::BindOnce(&VersionUpdaterCros::OnGetChannel, |
| weak_ptr_factory_.GetWeakPtr(), std::move(cb))); |
| } |
| |
| void VersionUpdaterCros::OnGetChannel(ChannelCallback cb, |
| const std::string& current_channel) { |
| std::move(cb).Run(current_channel); |
| } |
| |
| void VersionUpdaterCros::GetEolInfo(EolInfoCallback cb) { |
| // Request the EolInfo. Bind to a weak_ptr bound method rather than passing |
| // |cb| directly so that |cb| does not outlive |this|. |
| UpdateEngineClient::Get()->GetEolInfo( |
| base::BindOnce(&VersionUpdaterCros::OnGetEolInfo, |
| weak_ptr_factory_.GetWeakPtr(), std::move(cb))); |
| } |
| |
| void VersionUpdaterCros::OnGetEolInfo(EolInfoCallback cb, |
| UpdateEngineClient::EolInfo eol_info) { |
| std::move(cb).Run(std::move(eol_info)); |
| } |
| |
| void VersionUpdaterCros::ToggleFeature(const std::string& feature, |
| bool enable) { |
| UpdateEngineClient::Get()->ToggleFeature(feature, enable); |
| } |
| |
| void VersionUpdaterCros::IsFeatureEnabled(const std::string& feature, |
| IsFeatureEnabledCallback callback) { |
| UpdateEngineClient::Get()->IsFeatureEnabled( |
| feature, |
| base::BindOnce(&VersionUpdaterCros::OnIsFeatureEnabled, |
| weak_ptr_factory_.GetWeakPtr(), std::move(callback))); |
| } |
| |
| void VersionUpdaterCros::OnIsFeatureEnabled(IsFeatureEnabledCallback callback, |
| std::optional<bool> enabled) { |
| std::move(callback).Run(std::move(enabled)); |
| } |
| |
| bool VersionUpdaterCros::IsManagedAutoUpdateEnabled() { |
| return !IsAutoUpdateDisabled(); |
| } |
| |
| VersionUpdaterCros::VersionUpdaterCros(content::WebContents* web_contents) |
| : context_(web_contents ? web_contents->GetBrowserContext() : nullptr), |
| last_operation_(update_engine::Operation::IDLE), |
| check_for_update_when_idle_(false) {} |
| |
| VersionUpdaterCros::~VersionUpdaterCros() { |
| UpdateEngineClient::Get()->RemoveObserver(this); |
| } |
| |
| void VersionUpdaterCros::UpdateStatusChanged( |
| const update_engine::StatusResult& status) { |
| Status my_status = UPDATED; |
| int progress = 0; |
| std::string version = status.new_version(); |
| int64_t size = status.new_size(); |
| std::u16string message; |
| |
| // If the status change is for an installation, this means that DLCs are being |
| // installed and has nothing to with the OS. Ignore this status change. |
| if (status.is_install()) |
| return; |
| |
| // If the updater is currently idle, just show the last operation (unless it |
| // was previously checking for an update -- in that case, the system is |
| // up to date now). See http://crbug.com/120063 for details. |
| update_engine::Operation operation_to_show = status.current_operation(); |
| if (status.current_operation() == update_engine::Operation::IDLE && |
| last_operation_ != update_engine::Operation::CHECKING_FOR_UPDATE) { |
| operation_to_show = last_operation_; |
| } |
| |
| switch (operation_to_show) { |
| case update_engine::Operation::IDLE: |
| case update_engine::Operation::DISABLED: |
| case update_engine::Operation::ERROR: |
| case update_engine::Operation::REPORTING_ERROR_EVENT: |
| case update_engine::Operation::ATTEMPTING_ROLLBACK: |
| case update_engine::Operation::CLEANUP_PREVIOUS_UPDATE: |
| // Update engine reports errors for some conditions that shouldn't |
| // actually be displayed as errors to users so leave the status as |
| // UPDATED. However for some specific errors use the specific FAILED |
| // statuses. Last attempt error remains when update engine state is |
| // idle. |
| |
| if (status.last_attempt_error() == |
| static_cast<int32_t>( |
| update_engine::ErrorCode::kOmahaUpdateIgnoredPerPolicy)) { |
| if (policy::ManagementServiceFactory::GetForPlatform()->IsManaged()) { |
| my_status = DISABLED_BY_ADMIN; |
| } else { |
| // Handle the special case where after a consumer rollback, |
| // updating to the previously installed version just rolledback from |
| // is disallowed. |
| // TODO(b/277962165) Update the platform side to expose a more |
| // specific error code for this case. |
| my_status = UPDATE_TO_ROLLBACK_VERSION_DISALLOWED; |
| } |
| } else if (status.last_attempt_error() == |
| static_cast<int32_t>( |
| update_engine::ErrorCode::kOmahaErrorInHTTPResponse)) { |
| my_status = FAILED_HTTP; |
| } else if (status.last_attempt_error() == |
| static_cast<int32_t>( |
| update_engine::ErrorCode::kDownloadTransferError)) { |
| my_status = FAILED_DOWNLOAD; |
| } |
| break; |
| case update_engine::Operation::CHECKING_FOR_UPDATE: |
| my_status = CHECKING; |
| break; |
| case update_engine::Operation::DOWNLOADING: |
| progress = static_cast<int>(round(status.progress() * 100)); |
| [[fallthrough]]; |
| case update_engine::Operation::UPDATE_AVAILABLE: |
| my_status = UPDATING; |
| break; |
| case update_engine::Operation::NEED_PERMISSION_TO_UPDATE: |
| my_status = NEED_PERMISSION_TO_UPDATE; |
| break; |
| case update_engine::Operation::VERIFYING: |
| case update_engine::Operation::FINALIZING: |
| // Once the download is finished, keep the progress at 100; it shouldn't |
| // go down while the status is the same. |
| progress = 100; |
| my_status = UPDATING; |
| break; |
| case update_engine::Operation::UPDATED_NEED_REBOOT: |
| my_status = NEARLY_UPDATED; |
| break; |
| case update_engine::Operation::UPDATED_BUT_DEFERRED: |
| my_status = DEFERRED; |
| break; |
| default: |
| NOTREACHED_IN_MIGRATION(); |
| } |
| |
| // If the current auto update is non-interactive and will be deferred, ignore |
| // update status change and show UPDATED instead. The NEARLY_UPDATED or |
| // DEFERRED status will still be shown, because user may need to interact with |
| // UI to apply the update and reboot the device. |
| if (my_status != NEARLY_UPDATED && my_status != DEFERRED && |
| !status.is_interactive() && status.will_defer_update()) { |
| my_status = UPDATED; |
| progress = 0; |
| } |
| |
| callback_.Run(my_status, progress, status.is_enterprise_rollback(), |
| status.will_powerwash_after_reboot(), version, size, message); |
| last_operation_ = status.current_operation(); |
| |
| if (check_for_update_when_idle_ && |
| status.current_operation() == update_engine::Operation::IDLE) { |
| CheckForUpdate(callback_, VersionUpdater::PromoteCallback()); |
| } |
| } |
| |
| void VersionUpdaterCros::OnUpdateCheck( |
| UpdateEngineClient::UpdateCheckResult result) { |
| // If version updating is not implemented, this binary is the most up-to-date |
| // possible with respect to automatic updating. |
| if (result == UpdateEngineClient::UPDATE_RESULT_NOTIMPLEMENTED) |
| callback_.Run(UPDATED, 0, false, false, std::string(), 0, std::u16string()); |
| } |