chromium/chrome/browser/ui/webui/ash/app_install/app_install_page_handler.cc

// Copyright 2023 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/ash/app_install/app_install_page_handler.h"

#include <utility>

#include "base/functional/callback_helpers.h"
#include "base/functional/overloaded.h"
#include "base/metrics/user_metrics.h"
#include "chrome/browser/apps/app_service/app_service_proxy.h"
#include "chrome/browser/apps/app_service/app_service_proxy_factory.h"
#include "chrome/browser/apps/app_service/metrics/app_discovery_metrics.h"
#include "chrome/browser/apps/app_service/package_id_util.h"
#include "chrome/browser/ui/webui/ash/app_install/app_install.mojom.h"
#include "chrome/browser/web_applications/web_app_constants.h"
#include "components/metrics/structured/structured_events.h"
#include "components/metrics/structured/structured_metrics_client.h"
#include "components/services/app_service/public/cpp/app_launch_util.h"
#include "components/services/app_service/public/cpp/app_types.h"

namespace ash::app_install {

namespace {

namespace cros_events = metrics::structured::events::v2::cr_os_events;

int64_t ToLong(web_app::WebAppInstallStatus web_app_install_status) {
  return static_cast<int64_t>(web_app_install_status);
}

bool g_auto_accept_for_testing = false;

}  // namespace

// static
bool AppInstallPageHandler::GetAutoAcceptForTesting() {
  return g_auto_accept_for_testing;
}

// static
void AppInstallPageHandler::SetAutoAcceptForTesting(bool auto_accept) {
  g_auto_accept_for_testing = auto_accept;
}

AppInstallPageHandler::AppInstallPageHandler(
    Profile* profile,
    std::optional<AppInstallDialogArgs> dialog_args,
    CloseDialogCallback close_dialog_callback,
    mojo::PendingReceiver<mojom::PageHandler> pending_page_handler)
    : profile_{profile},
      dialog_args_{std::move(dialog_args)},
      close_dialog_callback_{std::move(close_dialog_callback)},
      page_handler_receiver_{this, std::move(pending_page_handler)},
      app_info_actions_receiver_{this},
      connection_error_actions_receiver_{this} {
  base::RecordAction(
      base::UserMetricsAction("ChromeOS.AppInstallDialog.Shown"));

  if (GetAutoAcceptForTesting()) {
    InstallApp(base::DoNothing());
    CloseDialog();
  }
}

AppInstallPageHandler::~AppInstallPageHandler() = default;

void AppInstallPageHandler::GetDialogArgs(GetDialogArgsCallback callback) {
  if (!dialog_args_.has_value()) {
    pending_dialog_args_callbacks_.push_back(std::move(callback));
    return;
  }

  std::move(callback).Run(ConvertDialogArgsToMojom(dialog_args_.value()));
}

void AppInstallPageHandler::CloseDialog() {
  if (!dialog_args_.has_value()) {
    return;
  }

  // Ensure that `close_dialog_callback_` always runs. `close_dialog_callback_`
  // could be null if the close button is clicked multiple times . In this case,
  // the ScopedClosureRunner will not run anything.
  base::ScopedClosureRunner close_callback_runner(
      std::move(close_dialog_callback_));

  if (auto* app_info_args = absl::get_if<AppInfoArgs>(&dialog_args_.value())) {
    if (!app_info_args->dialog_accepted_callback) {
      return;
    }

    base::RecordAction(
        base::UserMetricsAction("ChromeOS.AppInstallDialog.Cancelled"));

    if (std::optional<std::string> metrics_id =
            apps::AppDiscoveryMetrics::GetAppStringToRecordForPackage(
                app_info_args->package_id)) {
      metrics::structured::StructuredMetricsClient::Record(
          cros_events::AppDiscovery_Browser_AppInstallDialogResult()
              .SetWebAppInstallStatus(
                  ToLong(web_app::WebAppInstallStatus::kCancelled))
              .SetAppId(*metrics_id));
    }

    std::move(app_info_args->dialog_accepted_callback).Run(false);
  }
}

void AppInstallPageHandler::InstallApp(InstallAppCallback callback) {
  if (!dialog_args_.has_value()) {
    return;
  }

  AppInfoArgs& app_info_args = absl::get<AppInfoArgs>(dialog_args_.value());

  base::RecordAction(
      base::UserMetricsAction("ChromeOS.AppInstallDialog.Installed"));

  if (std::optional<std::string> metrics_id =
          apps::AppDiscoveryMetrics::GetAppStringToRecordForPackage(
              app_info_args.package_id)) {
    metrics::structured::StructuredMetricsClient::Record(
        cros_events::AppDiscovery_Browser_AppInstallDialogResult()
            .SetWebAppInstallStatus(
                ToLong(web_app::WebAppInstallStatus::kAccepted))
            .SetAppId(*metrics_id));
  }

  install_app_callback_ = std::move(callback);
  std::move(app_info_args.dialog_accepted_callback).Run(true);
}

void AppInstallPageHandler::SetDialogArgs(AppInstallDialogArgs dialog_args) {
  CHECK(!dialog_args_.has_value());
  dialog_args_ = std::move(dialog_args);
  for (GetDialogArgsCallback& callback : pending_dialog_args_callbacks_) {
    std::move(callback).Run(ConvertDialogArgsToMojom(dialog_args_.value()));
  }
  pending_dialog_args_callbacks_.clear();
}

void AppInstallPageHandler::OnInstallComplete(
    bool success,
    std::optional<base::OnceCallback<void(bool accepted)>> retry_callback) {
  if (!dialog_args_.has_value()) {
    return;
  }

  if (!success) {
    CHECK(retry_callback.has_value());
    absl::get<AppInfoArgs>(dialog_args_.value()).dialog_accepted_callback =
        std::move(retry_callback.value());
  }
  if (install_app_callback_) {
    std::move(install_app_callback_).Run(success);
  }
}

void AppInstallPageHandler::LaunchApp() {
  if (!dialog_args_.has_value()) {
    return;
  }

  std::optional<std::string> app_id = apps_util::GetAppWithPackageId(
      &*profile_, absl::get<AppInfoArgs>(dialog_args_.value()).package_id);
  if (!app_id.has_value()) {
    mojo::ReportBadMessage("Unable to launch app without an app_id.");
    return;
  }
  base::RecordAction(
      base::UserMetricsAction("ChromeOS.AppInstallDialog.AppLaunched"));
  apps::AppServiceProxyFactory::GetForProfile(profile_)->Launch(
      app_id.value(), ui::EF_NONE, apps::LaunchSource::kFromInstaller);
}

void AppInstallPageHandler::TryAgain() {
  if (!dialog_args_.has_value()) {
    return;
  }

  base::OnceClosure& callback =
      absl::get<ConnectionErrorArgs>(dialog_args_.value()).try_again_callback;
  if (callback) {
    std::move(callback).Run();
  }
}

mojom::DialogArgsPtr AppInstallPageHandler::ConvertDialogArgsToMojom(
    const AppInstallDialogArgs& dialog_args) {
  return absl::visit(
      base::Overloaded(
          [&](const AppInfoArgs& app_info_args) {
            return mojom::DialogArgs::NewAppInfoArgs(mojom::AppInfoArgs::New(
                app_info_args.data.Clone(),
                app_info_actions_receiver_.BindNewPipeAndPassRemote()));
          },
          [&](const NoAppErrorArgs& no_app_error_args) {
            return mojom::DialogArgs::NewNoAppErrorArgs(
                mojom::NoAppErrorArgs::New());
          },
          [&](const ConnectionErrorArgs& connection_error_args) {
            return mojom::DialogArgs::NewConnectionErrorActions(
                connection_error_actions_receiver_.BindNewPipeAndPassRemote());
          }),
      dialog_args);
}

}  // namespace ash::app_install