// Copyright 2019 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/updater/app/app_install.h"
#include <ocidl.h>
#include <windows.h>
#include <olectl.h>
#include <shldisp.h>
#include <shlobj.h>
#include <winhttp.h>
#include <wrl/client.h>
#include <memory>
#include <optional>
#include <string>
#include <tuple>
#include <utility>
#include <vector>
#include "base/check.h"
#include "base/check_op.h"
#include "base/debug/alias.h"
#include "base/debug/dump_without_crashing.h"
#include "base/files/file_path.h"
#include "base/functional/callback.h"
#include "base/json/json_string_value_serializer.h"
#include "base/logging.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/ref_counted.h"
#include "base/memory/scoped_refptr.h"
#include "base/sequence_checker.h"
#include "base/strings/escape.h"
#include "base/strings/strcat.h"
#include "base/strings/stringprintf.h"
#include "base/strings/sys_string_conversions.h"
#include "base/strings/utf_string_conversions.h"
#include "base/synchronization/waitable_event.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/single_thread_task_runner.h"
#include "base/task/task_traits.h"
#include "base/task/thread_pool.h"
#include "base/threading/thread_restrictions.h"
#include "base/time/time.h"
#include "base/values.h"
#include "base/version.h"
#include "base/win/registry.h"
#include "base/win/scoped_com_initializer.h"
#include "chrome/updater/app/app_install_progress.h"
#include "chrome/updater/app/app_install_util_win.h"
#include "chrome/updater/app/app_install_win_internal.h"
#include "chrome/updater/external_constants.h"
#include "chrome/updater/registration_data.h"
#include "chrome/updater/service_proxy_factory.h"
#include "chrome/updater/update_service.h"
#include "chrome/updater/update_service_internal.h"
#include "chrome/updater/updater_branding.h"
#include "chrome/updater/updater_scope.h"
#include "chrome/updater/util/progress_sampler.h"
#include "chrome/updater/util/util.h"
#include "chrome/updater/util/win_util.h"
#include "chrome/updater/win/installer/exit_code.h"
#include "chrome/updater/win/manifest_util.h"
#include "chrome/updater/win/ui/l10n_util.h"
#include "chrome/updater/win/ui/progress_wnd.h"
#include "chrome/updater/win/ui/resources/resources.grh"
#include "chrome/updater/win/ui/resources/updater_installer_strings.h"
#include "chrome/updater/win/win_constants.h"
#include "components/update_client/protocol_parser.h"
#include "components/update_client/update_client_errors.h"
#include "url/gurl.h"
namespace updater {
namespace {
class InstallProgressSilentObserver : public AppInstallProgress {
public:
explicit InstallProgressSilentObserver(ui::OmahaWndEvents* events_sink);
~InstallProgressSilentObserver() override = default;
// Overrides for AppInstallProgress.
void OnCheckingForUpdate() override;
void OnUpdateAvailable(const std::string& app_id,
const std::u16string& app_name,
const base::Version& version) override;
void OnWaitingToDownload(const std::string& app_id,
const std::u16string& app_name) override;
void OnDownloading(const std::string& app_id,
const std::u16string& app_name,
const std::optional<base::TimeDelta> time_remaining,
int pos) override;
void OnWaitingRetryDownload(const std::string& app_id,
const std::u16string& app_name,
const base::Time& next_retry_time) override;
void OnWaitingToInstall(const std::string& app_id,
const std::u16string& app_name) override;
void OnInstalling(const std::string& app_id,
const std::u16string& app_name,
const std::optional<base::TimeDelta> time_remaining,
int pos) override;
void OnPause() override;
void OnComplete(const ObserverCompletionInfo& observer_info) override;
private:
SEQUENCE_CHECKER(sequence_checker_);
// Event sink must out-live this observer.
raw_ptr<ui::OmahaWndEvents> events_sink_ = nullptr;
};
InstallProgressSilentObserver::InstallProgressSilentObserver(
ui::OmahaWndEvents* events_sink)
: events_sink_(events_sink) {
CHECK(events_sink_);
}
void InstallProgressSilentObserver::OnCheckingForUpdate() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
}
void InstallProgressSilentObserver::OnUpdateAvailable(
const std::string& app_id,
const std::u16string& app_name,
const base::Version& version) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
}
void InstallProgressSilentObserver::OnWaitingToDownload(
const std::string& app_id,
const std::u16string& app_name) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
}
void InstallProgressSilentObserver::OnDownloading(
const std::string& app_id,
const std::u16string& app_name,
const std::optional<base::TimeDelta> time_remaining,
int pos) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
}
void InstallProgressSilentObserver::OnWaitingRetryDownload(
const std::string& app_id,
const std::u16string& app_name,
const base::Time& next_retry_time) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
}
void InstallProgressSilentObserver::OnWaitingToInstall(
const std::string& app_id,
const std::u16string& app_name) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
}
void InstallProgressSilentObserver::OnInstalling(
const std::string& app_id,
const std::u16string& app_name,
const std::optional<base::TimeDelta> time_remaining,
int pos) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
}
void InstallProgressSilentObserver::OnPause() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
}
void InstallProgressSilentObserver::OnComplete(
const ObserverCompletionInfo& observer_info) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
CHECK(events_sink_);
VLOG(1) << __func__;
if (base::CommandLine::ForCurrentProcess()->HasSwitch(
kAlwaysLaunchCmdSwitch)) {
auto scoped_com_initializer =
std::make_unique<base::win::ScopedCOMInitializer>(
base::win::ScopedCOMInitializer::kMTA);
LaunchCmdLines(observer_info);
}
events_sink_->DoExit();
}
// Implements a simple inter-thread communication protocol based on Windows
// messages exchanged between the application installer and its UI.
//
// Since the installer code and the UI code execute on different sequences, the
// installer can't invoke directly functions exposed by the UI.
class AppInstallProgressIPC : public AppInstallProgress {
public:
// Used as an inter-thread communication mechanism between the installer and
// UI threads.
static constexpr unsigned int WM_PROGRESS_WINDOW_IPC = WM_APP + 1;
AppInstallProgressIPC(AppInstallProgress* observer, DWORD observer_thread_id)
: observer_(observer), observer_thread_id_(observer_thread_id) {
CHECK(observer);
}
AppInstallProgressIPC(const AppInstallProgressIPC&) = delete;
AppInstallProgressIPC& operator=(const AppInstallProgressIPC&) = delete;
~AppInstallProgressIPC() override = default;
// Called by the window proc when a specific application message is processed
// by the progress window. This call always occurs in the context of the
// thread which owns the window.
void Invoke(WPARAM wparam, LPARAM lparam) {
CHECK_EQ(observer_thread_id_, ::GetCurrentThreadId());
CHECK_NE(lparam, 0);
CHECK_EQ(wparam, WPARAM{0});
std::unique_ptr<base::OnceClosure> callback_wrapper(
reinterpret_cast<base::OnceClosure*>(lparam));
std::move(*callback_wrapper).Run();
}
// Overrides for AppInstallProgress.
void OnCheckingForUpdate() override {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
CHECK(observer_);
PostClosure(base::BindOnce(&AppInstallProgress::OnUpdateAvailable,
base::Unretained(observer_), std::string(),
std::u16string(), base::Version()));
}
void OnUpdateAvailable(const std::string& app_id,
const std::u16string& app_name,
const base::Version& version) override {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
CHECK(observer_);
PostClosure(base::BindOnce(&AppInstallProgress::OnUpdateAvailable,
base::Unretained(observer_), app_id, app_name,
version));
}
void OnWaitingToDownload(const std::string& app_id,
const std::u16string& app_name) override {
NOTREACHED_IN_MIGRATION();
}
void OnDownloading(const std::string& app_id,
const std::u16string& app_name,
const std::optional<base::TimeDelta> time_remaining,
int pos) override {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
CHECK(observer_);
PostClosure(base::BindOnce(&AppInstallProgress::OnDownloading,
base::Unretained(observer_), app_id, app_name,
time_remaining, pos));
}
void OnWaitingRetryDownload(const std::string& app_id,
const std::u16string& app_name,
const base::Time& next_retry_time) override {
NOTREACHED_IN_MIGRATION();
}
void OnWaitingToInstall(const std::string& app_id,
const std::u16string& app_name) override {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
CHECK(observer_);
PostClosure(base::BindOnce(&AppInstallProgress::OnWaitingToInstall,
base::Unretained(observer_), app_id, app_name));
}
void OnInstalling(const std::string& app_id,
const std::u16string& app_name,
const std::optional<base::TimeDelta> time_remaining,
int pos) override {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
CHECK(observer_);
PostClosure(base::BindOnce(&AppInstallProgress::OnInstalling,
base::Unretained(observer_), app_id, app_name,
time_remaining, pos));
}
void OnPause() override { NOTREACHED_IN_MIGRATION(); }
void OnComplete(const ObserverCompletionInfo& observer_info) override {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
CHECK(observer_);
PostClosure(base::BindOnce(&AppInstallProgress::OnComplete,
base::Unretained(observer_), observer_info));
}
private:
void PostClosure(base::OnceClosure closure) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
std::unique_ptr<base::OnceClosure> closure_wrapper =
std::make_unique<base::OnceClosure>(std::move(closure));
::PostThreadMessage(observer_thread_id_, WM_PROGRESS_WINDOW_IPC, 0,
reinterpret_cast<LPARAM>(closure_wrapper.release()));
}
SEQUENCE_CHECKER(sequence_checker_);
// This member is not owned by this class.
raw_ptr<AppInstallProgress> observer_ = nullptr;
// The thread id of the thread which creates the `observer_`. The thread
// must have a message queue to enable this IPC class to post messages to it.
DWORD observer_thread_id_ = 0;
};
void SetUsageStats(UpdaterScope scope,
const std::string& app_id,
std::optional<bool> usage_stats) {
if (!usage_stats) {
return;
}
const LONG result =
base::win::RegKey(
UpdaterScopeToHKeyRoot(scope),
base::StrCat({CLIENT_STATE_KEY, base::SysUTF8ToWide(app_id)}).c_str(),
Wow6432(KEY_WRITE))
.WriteValue(L"usagestats", usage_stats.value() ? 1 : 0);
if (result != ERROR_SUCCESS) {
VLOG(1) << "Error writing usage stats for " << app_id << ":" << result;
return;
}
if (IsSystemInstall(scope)) {
base::win::RegKey(
UpdaterScopeToHKeyRoot(scope),
base::StrCat({CLIENT_STATE_MEDIUM_KEY, base::SysUTF8ToWide(app_id)})
.c_str(),
Wow6432(KEY_WRITE))
.DeleteValue(L"usagestats");
}
}
// Implements installing a single application by invoking the code in
// |UpdateService|, listening to |UpdateService| and UI events, and
// driving the UI code by calling the functions exposed by
// |AppInstallProgress|. This class receives state changes for an install
// and it notifies the UI, which is an observer of this class.
//
// The UI code can't run in a thread where the message loop is an instance of
// |base::MessageLoop|. |base::MessageLoop| does not handle all the messages
// needed by the UI, since the UI is written in terms of WTL, and it requires
// a |WTL::MessageLoop| to work, for example, accelerators, dialog messages,
// TAB key, etc are all handled by WTL. Therefore, the UI code runs on its own
// thread. This thread owns all the UI objects, which must be created and
// destroyed on this thread. The rest of the code in this class runs on
// the updater main thread.
//
// This class controls the lifetime of the UI thread. Once the UI thread is
// created, it is going to run a message loop until the main thread initiates
// its teardown by posting a WM_QUIT message to it. This makes the UI message
// loop exit, and after that, the execution flow returns to the scheduler task,
// which has been running the UI message loop. Upon its completion, the task
// posts a reply to the main thread, which makes the main thread exit its run
// loop, and then the main thread returns to the destructor of this class,
// and destructs its class members.
class AppInstallControllerImpl : public AppInstallController,
public ui::ProgressWndEvents,
public WTL::CMessageFilter {
public:
explicit AppInstallControllerImpl(bool is_silent_install);
AppInstallControllerImpl();
AppInstallControllerImpl(const AppInstallControllerImpl&) = delete;
AppInstallControllerImpl& operator=(const AppInstallControllerImpl&) = delete;
// Override for AppInstallController.
void Initialize() override;
void InstallApp(const std::string& app_id,
const std::string& app_name,
base::OnceCallback<void(int)> callback) override;
void InstallAppOffline(const std::string& app_id,
const std::string& app_name,
base::OnceCallback<void(int)> callback) override;
void Exit(int exit_code) override;
void set_update_service(
scoped_refptr<UpdateService> update_service) override {
update_service_ = update_service;
}
private:
friend class base::RefCountedThreadSafe<AppInstallControllerImpl>;
~AppInstallControllerImpl() override;
// Overrides for OmahaWndEvents. These functions are called on the UI thread.
void DoClose() override {}
void DoExit() override;
// Overrides for CompleteWndEvents. This function is called on the UI thread.
bool DoLaunchBrowser(const std::string& url) override;
// Overrides for ProgressWndEvents. These functions are called on the UI
// thread.
bool DoRestartBrowser(bool restart_all_browsers,
const std::vector<GURL>& urls) override;
bool DoReboot() override;
void DoCancel() override;
// Overrides for WTL::CMessageFilter.
BOOL PreTranslateMessage(MSG* msg) override;
// This function is called on a dedicated COM STA thread.
void LoadLogo(const std::string& app_id, HWND progress_hwnd);
// These functions are called on the UI thread.
void InitializeUI();
void RunUI();
// These functions are called on the main updater sequence.
void PreInstallApp(const std::string& app_id,
const std::string& app_name,
base::OnceCallback<void(int)> callback);
void DoInstallAppOffline(
const update_client::ProtocolParser::Results& results,
const std::string& installer_version,
const base::FilePath& installer_path,
const std::string& install_args,
const std::string& install_data);
void HandleOsNotSupported();
void InstallComplete(UpdateService::Result result);
// Returns the thread id of the thread which owns the progress window.
DWORD GetUIThreadID() const;
// Receives the state changes during handling of the Install function call.
void StateChange(const UpdateService::UpdateState& update_state);
SEQUENCE_CHECKER(sequence_checker_);
// Provides an execution environment for the updater main sequence.
scoped_refptr<base::SequencedTaskRunner> main_task_runner_;
// Provides an execution environment for the UI code. Typically, it runs
// a single task which is the UI run loop.
scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner_;
// The application ID and the associated application name. The application
// name is displayed by the UI and it must be localized.
std::string app_id_;
std::u16string app_name_;
// The out-of-process service used for making RPC calls to install the app.
scoped_refptr<UpdateService> update_service_;
// The message loop associated with the UI.
std::unique_ptr<WTL::CMessageLoop> ui_message_loop_;
std::unique_ptr<AppInstallProgress> observer_;
HWND observer_hwnd_ = nullptr;
DWORD ui_thread_id_ = 0u;
// The adapter for the inter-thread calls between the updater main thread
// and the UI thread.
std::unique_ptr<AppInstallProgressIPC> install_progress_observer_ipc_;
// Contains the result of installing the application. This is populated by the
// state change callback or the completion callback, if the former callback
// was not posted.
std::optional<ObserverCompletionInfo> observer_completion_info_;
// Called when InstallApp is done.
base::OnceCallback<void(int)> callback_;
const bool is_silent_install_ = false;
ProgressSampler download_progress_sampler_;
ProgressSampler install_progress_sampler_;
};
AppInstallControllerImpl::AppInstallControllerImpl(bool is_silent_install)
: main_task_runner_(base::SequencedTaskRunner::GetCurrentDefault()),
ui_task_runner_(base::ThreadPool::CreateSingleThreadTaskRunner(
{base::TaskPriority::USER_BLOCKING,
base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN},
base::SingleThreadTaskRunnerThreadMode::DEDICATED)),
is_silent_install_(is_silent_install),
download_progress_sampler_(base::Seconds(5), base::Seconds(1)),
install_progress_sampler_(base::Seconds(5), base::Seconds(1)) {}
AppInstallControllerImpl::~AppInstallControllerImpl() = default;
void AppInstallControllerImpl::Initialize() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
base::WaitableEvent ui_initialized_event;
ui_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(
[](scoped_refptr<AppInstallControllerImpl> self,
base::WaitableEvent& event) {
self->InitializeUI();
event.Signal();
},
base::WrapRefCounted(this), std::ref(ui_initialized_event)));
ui_initialized_event.Wait();
// The UI thread runs the observer.
install_progress_observer_ipc_ =
std::make_unique<AppInstallProgressIPC>(observer_.get(), ui_thread_id_);
// At this point, the UI has been initialized, which means the UI
// can be used from now on as an observer of the application
// install. The task below runs the UI message loop for the UI until
// it exits when a WM_QUIT message has been posted to it.
ui_task_runner_->PostTask(
FROM_HERE, base::BindOnce(&AppInstallControllerImpl::RunUI, this));
}
void AppInstallControllerImpl::InstallApp(
const std::string& app_id,
const std::string& app_name,
base::OnceCallback<void(int)> callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
PreInstallApp(app_id, app_name, std::move(callback));
RegistrationRequest request;
request.app_id = app_id_;
request.version = base::Version(kNullVersion);
std::optional<tagging::AppArgs> app_args = GetAppArgs(app_id_);
std::optional<tagging::TagArgs> tag_args = GetTagArgs().tag_args;
if (app_args) {
request.ap = app_args->ap;
}
if (tag_args) {
request.brand_code = tag_args->brand_code;
}
base::ThreadPool::PostTaskAndReply(
FROM_HERE,
base::BindOnce(&SetUsageStats, GetUpdaterScope(), app_id_,
tag_args ? tag_args->usage_stats_enable : std::nullopt),
base::BindOnce(
&UpdateService::Install, update_service_, request,
GetDecodedInstallDataFromAppArgs(app_id_),
GetInstallDataIndexFromAppArgs(app_id_),
UpdateService::Priority::kForeground,
base::BindRepeating(&AppInstallControllerImpl::StateChange, this),
base::BindOnce(&AppInstallControllerImpl::InstallComplete, this)));
}
void AppInstallControllerImpl::PreInstallApp(
const std::string& app_id,
const std::string& app_name,
base::OnceCallback<void(int)> callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
app_id_ = app_id;
app_name_ = base::UTF8ToUTF16(app_name);
callback_ = std::move(callback);
// The app logo is expected to be hosted at `{AppLogoURL}{url escaped
// app_id_}.bmp`. If `{url escaped app_id_}.bmp` exists, a logo is shown in
// the updater UI for that app install.
//
// For example, if `app_id_` is `{8A69D345-D564-463C-AFF1-A69D9E530F96}`,
// the `{url escaped app_id_}.bmp` is
// `%7b8A69D345-D564-463C-AFF1-A69D9E530F96%7d.bmp`.
//
// `AppLogoURL` is specified in external constants.
base::ThreadPool::CreateCOMSTATaskRunner({base::MayBlock()})
->PostTask(FROM_HERE, base::BindOnce(&AppInstallControllerImpl::LoadLogo,
this, app_id_, observer_hwnd_));
}
void AppInstallControllerImpl::InstallAppOffline(
const std::string& app_id,
const std::string& app_name,
base::OnceCallback<void(int)> callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
PreInstallApp(app_id, app_name, std::move(callback));
base::ThreadPool::PostTaskAndReplyWithResult(
FROM_HERE, {base::MayBlock()},
base::BindOnce(
[](const std::string& app_id) {
// Parse the offline manifest to get the install
// command and install data.
update_client::ProtocolParser::Results results;
std::string installer_version;
base::FilePath installer_path;
std::string install_args;
std::string install_data;
ReadInstallCommandFromManifest(
base::CommandLine::ForCurrentProcess()->GetSwitchValueNative(
kOfflineDirSwitch),
app_id, GetInstallDataIndexFromAppArgs(app_id), results,
installer_version, installer_path, install_args, install_data);
const std::string client_install_data =
GetDecodedInstallDataFromAppArgs(app_id);
return std::make_tuple(
results, installer_version, installer_path, install_args,
client_install_data.empty() ? install_data
: client_install_data);
},
app_id_),
base::BindOnce(
[](scoped_refptr<AppInstallControllerImpl> self,
const std::tuple<
update_client::ProtocolParser::Results /*results*/,
std::string /*installer_version*/,
base::FilePath /*installer_path*/, std::string /*arguments*/,
std::string /*install_data*/>& result) {
self->DoInstallAppOffline(std::get<0>(result), std::get<1>(result),
std::get<2>(result), std::get<3>(result),
std::get<4>(result));
},
base::WrapRefCounted(this)));
}
void AppInstallControllerImpl::DoInstallAppOffline(
const update_client::ProtocolParser::Results& results,
const std::string& installer_version,
const base::FilePath& installer_path,
const std::string& install_args,
const std::string& install_data) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!IsOsSupported(results)) {
HandleOsNotSupported();
return;
}
base::Value::Dict install_settings_dict;
install_settings_dict.Set(kInstallerVersion, installer_version);
const base::CommandLine cmd_line(*base::CommandLine::ForCurrentProcess());
install_settings_dict.Set(kEnterpriseSwitch,
cmd_line.HasSwitch(kEnterpriseSwitch));
install_settings_dict.Set(kSessionIdSwitch,
cmd_line.GetSwitchValueASCII(kSessionIdSwitch));
std::string install_settings;
if (!JSONStringValueSerializer(&install_settings)
.Serialize(install_settings_dict)) {
VLOG(1) << "Failed to serialize install settings.";
}
std::optional<tagging::TagArgs> tag_args = GetTagArgs().tag_args;
RegistrationRequest request;
request.app_id = app_id_;
request.version = base::Version(kNullVersion);
std::optional<tagging::AppArgs> app_args = GetAppArgs(app_id_);
if (app_args) {
request.ap = app_args->ap;
}
if (tag_args) {
request.brand_code = tag_args->brand_code;
}
VLOG(1) << __func__ << ": " << installer_path << ": " << install_args << ": "
<< install_data;
base::ThreadPool::PostTaskAndReply(
FROM_HERE,
base::BindOnce(&SetUsageStats, GetUpdaterScope(), app_id_,
tag_args ? tag_args->usage_stats_enable : std::nullopt),
base::BindOnce(
&UpdateService::RegisterApp, update_service_, request,
base::BindOnce(
[](scoped_refptr<AppInstallControllerImpl> self,
const base::FilePath& installer_path,
const std::string& install_args,
const std::string& install_data,
const std::string& install_settings, int result) {
if (result != kRegistrationSuccess) {
VLOG(1) << "Registration failed: " << result;
self->InstallComplete(UpdateService::Result::kServiceFailed);
return;
}
self->update_service_->RunInstaller(
self->app_id_, installer_path, install_args, install_data,
install_settings,
base::BindRepeating(&AppInstallControllerImpl::StateChange,
self),
base::BindOnce(&AppInstallControllerImpl::InstallComplete,
self));
},
base::WrapRefCounted(this), installer_path, install_args,
install_data, install_settings)));
}
void AppInstallControllerImpl::HandleOsNotSupported() {
UpdateService::UpdateState update_state;
update_state.app_id = app_id_;
update_state.state = UpdateService::UpdateState::State::kUpdateError;
update_state.error_category = UpdateService::ErrorCategory::kInstall;
observer_completion_info_ = HandleInstallResult(update_state);
observer_completion_info_->completion_text =
base::WideToUTF16(GetLocalizedString(IDS_UPDATER_OS_NOT_SUPPORTED_BASE));
InstallComplete(UpdateService::Result::kInstallFailed);
}
void AppInstallControllerImpl::InstallComplete(UpdateService::Result result) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
VLOG(1) << __func__;
// Create a best-effort `UpdateState` instance if one is not available because
// state change callbacks were never posted for this install. This happens if
// the execution path returns early, before it has reached the state machine
// of the component in the `update_client`.
if (!observer_completion_info_.has_value()) {
UpdateService::UpdateState update_state;
update_state.app_id = app_id_;
update_state.state = UpdateService::UpdateState::State::kUpdateError;
update_state.error_code = static_cast<int>(result);
update_state.error_category = [result] {
switch (result) {
case UpdateService::Result::kUpdateCheckFailed:
return UpdateService::ErrorCategory::kUpdateCheck;
case UpdateService::Result::kInstallFailed:
return UpdateService::ErrorCategory::kInstall;
default:
return UpdateService::ErrorCategory::kService;
}
}();
observer_completion_info_ = HandleInstallResult(update_state);
}
update_service_ = nullptr;
CHECK(observer_completion_info_.has_value());
install_progress_observer_ipc_->OnComplete(observer_completion_info_.value());
}
void AppInstallControllerImpl::Exit(int exit_code) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
VLOG(1) << __func__;
update_service_ = nullptr;
if (exit_code == kErrorOk) {
install_progress_observer_ipc_->OnComplete({});
}
UpdateService::UpdateState update_state;
update_state.state = UpdateService::UpdateState::State::kNotStarted;
update_state.error_code = exit_code;
install_progress_observer_ipc_->OnComplete(HandleInstallResult(update_state));
}
void AppInstallControllerImpl::StateChange(
const UpdateService::UpdateState& update_state) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
AppInstallProgressIPC* dbg_ipc = install_progress_observer_ipc_.get();
base::debug::Alias(&dbg_ipc);
CHECK(install_progress_observer_ipc_);
// TODO(crbug.com/345250525) - understand why the check fails.
UpdateService::UpdateState::State state = update_state.state;
base::debug::Alias(&state);
DEBUG_ALIAS_FOR_CSTR(dbg_app_id1, app_id_.c_str(), 64);
DEBUG_ALIAS_FOR_CSTR(dbg_app_id2, update_state.app_id.c_str(), 64);
if (app_id_ != update_state.app_id) {
base::debug::DumpWithoutCrashing();
return;
}
switch (update_state.state) {
case UpdateService::UpdateState::State::kCheckingForUpdates:
install_progress_observer_ipc_->OnCheckingForUpdate();
break;
case UpdateService::UpdateState::State::kUpdateAvailable:
install_progress_observer_ipc_->OnUpdateAvailable(
app_id_, app_name_, update_state.next_version);
break;
case UpdateService::UpdateState::State::kDownloading: {
const auto pos = GetDownloadProgress(update_state.downloaded_bytes,
update_state.total_bytes);
if (pos >= 0) {
download_progress_sampler_.AddSample(update_state.downloaded_bytes);
}
install_progress_observer_ipc_->OnDownloading(
app_id_, app_name_,
download_progress_sampler_.GetRemainingTime(update_state.total_bytes),
pos >= 0 ? pos : 0);
break;
}
case UpdateService::UpdateState::State::kInstalling: {
install_progress_observer_ipc_->OnWaitingToInstall(app_id_, app_name_);
const int pos = update_state.install_progress; // [0..100]
if (pos >= 0) {
install_progress_sampler_.AddSample(pos);
}
install_progress_observer_ipc_->OnInstalling(
app_id_, app_name_, install_progress_sampler_.GetRemainingTime(100),
pos >= 0 ? pos : 0);
break;
}
case UpdateService::UpdateState::State::kUpdated:
case UpdateService::UpdateState::State::kNoUpdate:
case UpdateService::UpdateState::State::kUpdateError:
observer_completion_info_ = HandleInstallResult(update_state);
break;
case UpdateService::UpdateState::State::kUnknown:
case UpdateService::UpdateState::State::kNotStarted:
break;
}
}
// Loads the logo in BMP format if it exists for the provided `app_id`, and sets
// the resultant image onto the app bitmap for the progress window.
void AppInstallControllerImpl::LoadLogo(const std::string& app_id,
HWND progress_hwnd) {
std::wstring url = base::SysUTF8ToWide(base::StringPrintf(
"%s%s.bmp?lang=%s",
CreateExternalConstants()->AppLogoURL().possibly_invalid_spec().c_str(),
base::EscapeUrlEncodedData(app_id, false).c_str(),
base::WideToUTF8(GetPreferredLanguage()).c_str()));
if (url.empty()) {
VLOG(1) << __func__ << "No url specified";
return;
}
Microsoft::WRL::ComPtr<IPicture> picture;
HRESULT hr =
::OleLoadPicturePath(&url[0], nullptr, 0, 0, IID_PPV_ARGS(&picture));
if (FAILED(hr)) {
VLOG(1) << __func__ << "::OleLoadPicturePath failed: " << url << ": "
<< std::hex << hr << ": " << logging::SystemErrorCodeToString(hr);
return;
}
HBITMAP bitmap = nullptr;
hr = picture->get_Handle(reinterpret_cast<UINT*>(&bitmap));
if (FAILED(hr)) {
VLOG(1) << __func__ << "picture->get_Handle failed: " << std::hex << hr
<< ": " << logging::SystemErrorCodeToString(hr);
return;
}
if (!::IsWindow(progress_hwnd)) {
VLOG(1) << __func__ << "progress_hwnd not valid anymore";
return;
}
::SendDlgItemMessage(progress_hwnd, IDC_APP_BITMAP, STM_SETIMAGE,
IMAGE_BITMAP,
reinterpret_cast<LPARAM>(::CopyImage(
bitmap, IMAGE_BITMAP, 0, 0, LR_COPYRETURNORG)));
}
// Creates the install progress observer. The observer has thread affinity. It
// must be created, process its messages, and be destroyed on the same thread.
void AppInstallControllerImpl::InitializeUI() {
CHECK(ui_task_runner_->RunsTasksInCurrentSequence());
base::ScopedDisallowBlocking no_blocking_allowed_on_ui_thread;
ui_message_loop_ = std::make_unique<WTL::CMessageLoop>();
ui_message_loop_->AddMessageFilter(this);
ui_thread_id_ = ::GetCurrentThreadId();
if (is_silent_install_) {
observer_ = std::make_unique<InstallProgressSilentObserver>(this);
} else {
auto progress_wnd =
std::make_unique<ui::ProgressWnd>(ui_message_loop_.get(), nullptr);
progress_wnd->SetEventSink(this);
progress_wnd->Initialize();
progress_wnd->Show();
observer_hwnd_ = progress_wnd->m_hWnd;
observer_.reset(progress_wnd.release());
}
}
void AppInstallControllerImpl::RunUI() {
CHECK(ui_task_runner_->RunsTasksInCurrentSequence());
CHECK_EQ(GetUIThreadID(), GetCurrentThreadId());
ui_message_loop_->Run();
ui_message_loop_->RemoveMessageFilter(this);
// This object is owned by the UI thread must be destroyed on this thread.
observer_ = nullptr;
if (!callback_) {
return;
}
main_task_runner_->PostTask(FROM_HERE,
base::BindOnce(std::move(callback_), kErrorOk));
}
void AppInstallControllerImpl::DoExit() {
CHECK_EQ(GetUIThreadID(), GetCurrentThreadId());
PostThreadMessage(GetCurrentThreadId(), WM_QUIT, 0, 0);
}
BOOL AppInstallControllerImpl::PreTranslateMessage(MSG* msg) {
if (const auto ui_thread_id = GetUIThreadID(); ui_thread_id != 0) {
CHECK_EQ(ui_thread_id, GetCurrentThreadId());
} else {
VLOG(1) << "Can't find a thread id for the message: " << msg->message;
}
if (msg->message == AppInstallProgressIPC::WM_PROGRESS_WINDOW_IPC) {
install_progress_observer_ipc_->Invoke(msg->wParam, msg->lParam);
return true;
}
return false;
}
DWORD AppInstallControllerImpl::GetUIThreadID() const {
CHECK_NE(ui_thread_id_, 0u);
return ui_thread_id_;
}
bool AppInstallControllerImpl::DoLaunchBrowser(const std::string& url) {
CHECK_EQ(GetUIThreadID(), GetCurrentThreadId());
return SUCCEEDED(RunDeElevatedNoWait(base::SysUTF8ToWide(url), {}));
}
bool AppInstallControllerImpl::DoRestartBrowser(bool restart_all_browsers,
const std::vector<GURL>& urls) {
CHECK_EQ(GetUIThreadID(), GetCurrentThreadId());
return false;
}
bool AppInstallControllerImpl::DoReboot() {
CHECK_EQ(GetUIThreadID(), GetCurrentThreadId());
return false;
}
void AppInstallControllerImpl::DoCancel() {
CHECK_EQ(GetUIThreadID(), GetCurrentThreadId());
if (!update_service_) {
return;
}
main_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&UpdateService::CancelInstalls, update_service_, app_id_));
}
std::wstring GetTextForStartupError(int error_code) {
switch (error_code) {
case kErrorWrongUser:
return GetLocalizedString(
::IsUserAnAdmin() ? IDS_WRONG_USER_DEELEVATION_REQUIRED_ERROR_BASE
: IDS_WRONG_USER_ELEVATION_REQUIRED_ERROR_BASE);
case kErrorFailedToLockSetupMutex:
return GetLocalizedString(IDS_UNABLE_TO_GET_SETUP_LOCK_BASE);
default:
return GetLocalizedStringF(IDS_GENERIC_STARTUP_ERROR_BASE,
GetTextForSystemError(error_code));
}
}
} // namespace
[[nodiscard]] ObserverCompletionInfo HandleInstallResult(
const UpdateService::UpdateState& update_state) {
CompletionCodes completion_code = CompletionCodes::COMPLETION_CODE_ERROR;
std::wstring completion_text;
switch (update_state.state) {
case UpdateService::UpdateState::State::kUpdated:
VLOG(1) << "Update success.";
completion_code = CompletionCodes::COMPLETION_CODE_SUCCESS;
completion_text =
GetLocalizedString(IDS_BUNDLE_INSTALLED_SUCCESSFULLY_BASE);
break;
case UpdateService::UpdateState::State::kNoUpdate:
VLOG(1) << "No updates.";
completion_code = CompletionCodes::COMPLETION_CODE_ERROR;
completion_text = GetLocalizedString(IDS_NO_UPDATE_RESPONSE_BASE);
break;
case UpdateService::UpdateState::State::kUpdateError:
VLOG(1) << "Updater error: " << update_state.error_code << ".";
completion_code = CompletionCodes::COMPLETION_CODE_ERROR;
completion_text = GetLocalizedString(IDS_INSTALL_UPDATER_FAILED_BASE);
break;
case UpdateService::UpdateState::State::kNotStarted:
VLOG(1) << "Updater error: " << update_state.error_code << ".";
completion_code = CompletionCodes::COMPLETION_CODE_ERROR;
completion_text = GetTextForStartupError(update_state.error_code);
break;
default:
NOTREACHED_IN_MIGRATION();
break;
}
ObserverCompletionInfo observer_info;
observer_info.completion_code = completion_code;
observer_info.completion_text = base::WideToUTF16(completion_text);
observer_info.help_url = GURL(base::StringPrintf(
"%s?product=%s&error=%d", HELP_CENTER_URL,
base::EscapeUrlEncodedData(update_state.app_id, false).c_str(),
update_state.error_code));
AppCompletionInfo app_info;
if (update_state.state == UpdateService::UpdateState::State::kNotStarted) {
app_info.app_id = kUpdaterAppId;
} else if (update_state.state !=
UpdateService::UpdateState::State::kNoUpdate) {
app_info.app_id = update_state.app_id;
app_info.error_code = update_state.error_code;
app_info.completion_message =
base::UTF8ToUTF16(update_state.installer_text);
app_info.extra_code1 = update_state.extra_code1;
app_info.post_install_launch_command_line = update_state.installer_cmd_line;
VLOG(1) << app_info.app_id << " installation completed: error category["
<< update_state.error_category << "], error_code["
<< app_info.error_code << "], extra_code1[" << app_info.extra_code1
<< "], completion_message[" << app_info.completion_message
<< "], post_install_launch_command_line["
<< app_info.post_install_launch_command_line << "]";
// If the installer provides a launch command,
// `COMPLETION_CODE_EXIT_SILENTLY_ON_LAUNCH_COMMAND` will cause the UI
// client to run the launch command and exit in the interactive install
// case.
if (app_info.error_code == 0) {
app_info.completion_code =
app_info.post_install_launch_command_line.empty()
? CompletionCodes::COMPLETION_CODE_SUCCESS
: CompletionCodes::
COMPLETION_CODE_EXIT_SILENTLY_ON_LAUNCH_COMMAND;
} else if (app_info.error_code == ERROR_SUCCESS_REBOOT_INITIATED ||
app_info.error_code == ERROR_SUCCESS_REBOOT_REQUIRED ||
app_info.error_code == ERROR_SUCCESS_RESTART_REQUIRED) {
app_info.completion_code =
CompletionCodes::COMPLETION_CODE_REBOOT_NOTICE_ONLY;
} else {
app_info.completion_code = CompletionCodes::COMPLETION_CODE_ERROR;
}
}
observer_info.apps_info.push_back(app_info);
return observer_info;
}
scoped_refptr<App> MakeAppInstall(bool is_silent_install) {
if (IsSystemInstall()) {
if (base::CommandLine::ForCurrentProcess()->HasSwitch(kOemSwitch)) {
const bool success = SetOemInstallState();
LOG_IF(ERROR, !success) << "SetOemInstallState failed";
}
std::optional<tagging::TagArgs> tag_args = GetTagArgs().tag_args;
if (tag_args && !tag_args->enrollment_token.empty()) {
const bool success =
StoreRunTimeEnrollmentToken(tag_args->enrollment_token);
LOG_IF(ERROR, !success) << "StoreRunTimeEnrollmentToken failed";
}
}
return base::MakeRefCounted<AppInstall>(base::BindRepeating(
[](bool is_silent_install) -> scoped_refptr<AppInstallController> {
return base::MakeRefCounted<AppInstallControllerImpl>(
is_silent_install);
},
is_silent_install));
}
} // namespace updater