// Copyright 2016 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/ash/arc/arc_support_host.h"

#include <string>
#include <utility>
#include <vector>

#include "ash/constants/ash_features.h"
#include "ash/constants/url_constants.h"
#include "ash/strings/grit/ash_strings.h"
#include "ash/webui/settings/public/constants/routes.mojom.h"
#include "base/check_deref.h"
#include "base/functional/bind.h"
#include "base/i18n/timezone.h"
#include "base/json/json_reader.h"
#include "base/json/json_writer.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/strings/string_view_util.h"
#include "base/values.h"
#include "chrome/browser/ash/app_list/arc/arc_app_utils.h"
#include "chrome/browser/consent_auditor/consent_auditor_factory.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/extensions/app_launch_params.h"
#include "chrome/browser/ui/extensions/application_launch.h"
#include "chrome/browser/ui/webui/ash/diagnostics_dialog/diagnostics_dialog.h"
#include "chrome/browser/ui/webui/theme_source.h"
#include "chrome/grit/generated_resources.h"
#include "chromeos/ash/components/browser_context_helper/annotated_account_id.h"
#include "chromeos/ash/components/browser_context_helper/browser_context_helper.h"
#include "chromeos/ash/experiences/arc/app/arc_app_constants.h"
#include "chromeos/ash/experiences/settings_ui/settings_app_manager.h"
#include "components/application_locale_storage/application_locale_storage.h"
#include "components/consent_auditor/consent_auditor.h"
#include "components/user_manager/known_user.h"
#include "components/user_manager/user.h"
#include "components/user_manager/user_manager.h"
#include "components/user_manager/user_type.h"
#include "content/public/browser/url_data_source.h"
#include "crypto/obsolete/sha1.h"
#include "extensions/browser/app_window/app_window.h"
#include "extensions/browser/app_window/app_window_registry.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/browser/extension_util.h"
#include "extensions/common/extension.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/webui/web_ui_util.h"
#include "ui/chromeos/devicetype_utils.h"
#include "ui/display/display.h"
#include "ui/display/screen.h"
#include "ui/gfx/native_ui_types.h"

using sync_pb::UserConsentTypes;

namespace arc {
std::string GetSha1HashForArcPlayTermsOfService(std::string_view tos_content) {
  return std::string(
      base::as_string_view(crypto::obsolete::Sha1::Hash(tos_content)));
}
}  // namespace arc

namespace {
constexpr char kAction[] = "action";
constexpr char kArcManaged[] = "arcManaged";
constexpr char kData[] = "data";
constexpr char kDeviceId[] = "deviceId";
constexpr char kActionInitialize[] = "initialize";
constexpr char kActionSetMetricsMode[] = "setMetricsMode";
constexpr char kActionBackupAndRestoreMode[] = "setBackupAndRestoreMode";
constexpr char kActionLocationServiceMode[] = "setLocationServiceMode";
constexpr char kActionSetWindowBounds[] = "setWindowBounds";
constexpr char kActionCloseWindow[] = "closeWindow";

// Action to show a page. The message should have "page" field, which is one of
// IDs for section div elements.
constexpr char kActionShowPage[] = "showPage";
constexpr char kPage[] = "page";

// Action to show the error page. The message should have "errorMessage",
// which is a localized error text, and "shouldShowSendFeedback" boolean value.
constexpr char kActionShowErrorPage[] = "showErrorPage";
constexpr char kErrorMessage[] = "errorMessage";
constexpr char kShouldShowSendFeedback[] = "shouldShowSendFeedback";
constexpr char kShouldShowNetworkTests[] = "shouldShowNetworkTests";

// The preference update should have those two fields.
constexpr char kEnabled[] = "enabled";
constexpr char kManaged[] = "managed";

// The JSON data sent from the extension should have at least "event" field.
// Each event data is defined below.
// The key of the event type.
constexpr char kEvent[] = "event";

// "onWindowClosed" is fired when the extension window is closed.
// No data will be provided.
constexpr char kEventOnWindowClosed[] = "onWindowClosed";

// "onAgreed" is fired when a user clicks "Agree" button.
// The message should have the following fields:
// - tosContent
// - tosShown
// - isMetricsEnabled
// - isBackupRestoreEnabled
// - isBackupRestoreManaged
// - isLocationServiceEnabled
// - isLocationServiceManaged
constexpr char kEventOnAgreed[] = "onAgreed";
constexpr char kTosContent[] = "tosContent";
constexpr char kTosShown[] = "tosShown";
constexpr char kIsMetricsEnabled[] = "isMetricsEnabled";
constexpr char kIsBackupRestoreEnabled[] = "isBackupRestoreEnabled";
constexpr char kIsBackupRestoreManaged[] = "isBackupRestoreManaged";
constexpr char kIsLocationServiceEnabled[] = "isLocationServiceEnabled";
constexpr char kIsLocationServiceManaged[] = "isLocationServiceManaged";

// "onCanceled" is fired when user clicks "Cancel" button.
// The message should have the same fields as "onAgreed" above.
constexpr char kEventOnCanceled[] = "onCanceled";

// "onRetryClicked" is fired when a user clicks "RETRY" button on the error
// page.
constexpr char kEventOnRetryClicked[] = "onRetryClicked";

// "onSendFeedbackClicked" is fired when a user clicks "Send Feedback" button.
constexpr char kEventOnSendFeedbackClicked[] = "onSendFeedbackClicked";

// "onRunNetworkTestsClicked" is fired when a user clicks "Check Network"
// button.
constexpr char kEventOnRunNetworkTestsClicked[] = "onRunNetworkTestsClicked";

// "onTosLoadResult" is fired when terms of service page is loaded or fails to
// load.
constexpr char kEventOnTosLoadResult[] = "onTosLoadResult";

// "onTosLoadResult" should have the following fields:
// - success
constexpr char kSuccess[] = "success";

// "onErrorPageShown" is fired when the error page is shown.
constexpr char kEventOnErrorPageShown[] = "onErrorPageShown";

// "OnErrorPageShown" should have the following fields:
// - networkTestsShown
constexpr char kNetworkTestsShown[] = "networkTestsShown";

// "onOpenPrivacySettingsPageClicked" is fired when a user clicks privacy
// settings link.
constexpr char kEventOnOpenPrivacySettingsPageClicked[] =
    "onOpenPrivacySettingsPageClicked";

// "requestWindowBound" is fired when new opt-in window is created.
constexpr char kEventRequestWindowBounds[] = "requestWindowBounds";

// x,y,width,height to define current work area.
constexpr char kDisplayWorkareaX[] = "displayWorkareaX";
constexpr char kDisplayWorkareaY[] = "displayWorkareaY";
constexpr char kDisplayWorkareaWidth[] = "displayWorkareaWidth";
constexpr char kDisplayWorkareaHeight[] = "displayWorkareaHeight";

void RequestOpenApp(Profile* profile) {
  // Launch the extension directly. Because the PlayStore app and the extension
  // share the ID historically, so AppService blocklists the extension to avoid
  // the conflict. Here, it bypasses the AppService to launch the extension.
  // Note that, if PlayStore app is enabled, but ARC is not yet enabled,
  // the event is forwarded to here, so it launches the extension to let
  // the user enter into the enabling flow, including concenting the ToS.
  const extensions::Extension* extension =
      extensions::ExtensionRegistry::Get(profile)->GetInstalledExtension(
          arc::kPlayStoreAppId);
  CHECK(extension);
  CHECK(extensions::util::IsAppLaunchable(arc::kPlayStoreAppId, profile));
  ::OpenApplication(profile,
                    CreateAppLaunchParamsUserContainer(
                        profile, extension, WindowOpenDisposition::NEW_WINDOW,
                        apps::LaunchSource::kFromChromeInternal));
}

std::ostream& operator<<(std::ostream& os, ArcSupportHost::UIPage ui_page) {
  switch (ui_page) {
    case ArcSupportHost::UIPage::NO_PAGE:
      return os << "NO_PAGE";
    case ArcSupportHost::UIPage::TERMS:
      return os << "TERMS";
    case ArcSupportHost::UIPage::ARC_LOADING:
      return os << "ARC_LOADING";
    case ArcSupportHost::UIPage::ERROR:
      return os << "ERROR";
  }

  NOTREACHED();
}

std::ostream& operator<<(std::ostream& os, ArcSupportHost::Error error) {
#define MAP_ERROR(name)             \
  case ArcSupportHost::Error::name: \
    return os << #name
  switch (error) {
    MAP_ERROR(SIGN_IN_NETWORK_ERROR);
    MAP_ERROR(SIGN_IN_GMS_SIGNIN_ERROR);
    MAP_ERROR(SIGN_IN_BAD_AUTHENTICATION_ERROR);
    MAP_ERROR(SIGN_IN_GMS_CHECKIN_ERROR);
    MAP_ERROR(SIGN_IN_CLOUD_PROVISION_FLOW_ACCOUNT_MISSING_ERROR);
    MAP_ERROR(SIGN_IN_CLOUD_PROVISION_FLOW_DOMAIN_JOIN_FAIL_ERROR);
    MAP_ERROR(SIGN_IN_CLOUD_PROVISION_FLOW_ENROLLMENT_TOKEN_INVALID);
    MAP_ERROR(SIGN_IN_CLOUD_PROVISION_FLOW_INTERRUPTED_ERROR);
    MAP_ERROR(SIGN_IN_CLOUD_PROVISION_FLOW_NETWORK_ERROR);
    MAP_ERROR(SIGN_IN_CLOUD_PROVISION_FLOW_PERMANENT_ERROR);
    MAP_ERROR(SIGN_IN_CLOUD_PROVISION_FLOW_TRANSIENT_ERROR);
    MAP_ERROR(SIGN_IN_UNKNOWN_ERROR);
    MAP_ERROR(SERVER_COMMUNICATION_ERROR);
    MAP_ERROR(ANDROID_MANAGEMENT_REQUIRED_ERROR);
    MAP_ERROR(NETWORK_UNAVAILABLE_ERROR);
    MAP_ERROR(LOW_DISK_SPACE_ERROR);
  }
#undef MAP_ERROR

  NOTREACHED();
}

}  // namespace

ArcSupportHost::ErrorInfo::ErrorInfo(Error error)
    : error(error), arg(std::nullopt) {}
ArcSupportHost::ErrorInfo::ErrorInfo(Error error, const std::optional<int>& arg)
    : error(error), arg(arg) {}
ArcSupportHost::ErrorInfo::ErrorInfo(const ErrorInfo&) = default;
ArcSupportHost::ErrorInfo& ArcSupportHost::ErrorInfo::operator=(
    const ArcSupportHost::ErrorInfo&) = default;

ArcSupportHost::ArcSupportHost(
    PrefService* local_state,
    const ApplicationLocaleStorage* application_locale_storage,
    Profile* profile)
    : local_state_(CHECK_DEREF(local_state)),
      application_locale_storage_(CHECK_DEREF(application_locale_storage)),
      profile_(profile),
      request_open_app_callback_(base::BindRepeating(&RequestOpenApp)) {
  DCHECK(profile_);
  content::URLDataSource::Add(profile_,
                              std::make_unique<ThemeSource>(profile_));
}

ArcSupportHost::~ArcSupportHost() {
  // Delegates should have been reset to nullptr at this point.
  DCHECK(!tos_delegate_);
  DCHECK(!error_delegate_);

  if (message_host_)
    DisconnectMessageHost();
}

void ArcSupportHost::SetTermsOfServiceDelegate(
    TermsOfServiceDelegate* delegate) {
  tos_delegate_ = delegate;
}

void ArcSupportHost::SetErrorDelegate(ErrorDelegate* delegate) {
  error_delegate_ = delegate;
}

gfx::NativeWindow ArcSupportHost::GetNativeWindow() const {
  extensions::AppWindowRegistry* registry =
      extensions::AppWindowRegistry::Get(profile_);
  if (!registry) {
    return gfx::NativeWindow();
  }

  extensions::AppWindow* window =
      registry->GetCurrentAppWindowForApp(arc::kPlayStoreAppId);
  return window ? window->GetNativeWindow() : gfx::NativeWindow();
}

bool ArcSupportHost::GetShouldShowRunNetworkTests() {
  return should_show_run_network_tests_;
}

void ArcSupportHost::SetArcManaged(bool is_arc_managed) {
  DCHECK(!message_host_ || (is_arc_managed_ == is_arc_managed));
  is_arc_managed_ = is_arc_managed;
}

void ArcSupportHost::Close() {
  ui_page_ = UIPage::NO_PAGE;
  if (!message_host_) {
    VLOG(2) << "ArcSupportHost::Close() is called "
            << "but message_host_ is not available.";
    return;
  }

  base::DictValue message;
  message.Set(kAction, kActionCloseWindow);
  message_host_->SendMessage(message);

  // Disconnect immediately, so that onWindowClosed event will not be
  // delivered to here.
  DisconnectMessageHost();
}

void ArcSupportHost::ShowTermsOfService() {
  ShowPage(UIPage::TERMS);
}

void ArcSupportHost::ShowArcLoading() {
  ShowPage(UIPage::ARC_LOADING);
}

void ArcSupportHost::ShowPage(UIPage ui_page) {
  ui_page_ = ui_page;
  if (!message_host_) {
    if (app_start_pending_) {
      VLOG(2) << "ArcSupportHost::ShowPage(" << ui_page << ") is called "
              << "before connection to ARC support Chrome app has finished "
              << "establishing.";
      return;
    }
    RequestAppStart();
    return;
  }

  base::DictValue message;
  message.Set(kAction, kActionShowPage);
  switch (ui_page) {
    case UIPage::TERMS:
      message.Set(kPage, "terms");
      break;
    case UIPage::ARC_LOADING:
      message.Set(kPage, "arc-loading");
      break;
    default:
      NOTREACHED();
  }
  message_host_->SendMessage(message);
}

void ArcSupportHost::ShowError(ErrorInfo error_info,
                               bool should_show_send_feedback,
                               bool should_show_run_network_tests) {
  ui_page_ = UIPage::ERROR;
  error_info_.emplace(error_info);
  should_show_send_feedback_ = should_show_send_feedback;
  should_show_run_network_tests_ = should_show_run_network_tests;
  if (!message_host_) {
    if (app_start_pending_) {
      VLOG(2) << "ArcSupportHost::ShowError(" << error_info.error << ", "
              << error_info.arg.value_or(-1) << ", "
              << should_show_send_feedback << ", "
              << should_show_run_network_tests
              << ") is called before connection "
              << "to ARC support Chrome app is established.";
      return;
    }
    RequestAppStart();
    return;
  }

  base::DictValue message_args;
  message_args.Set(kAction, kActionShowErrorPage);
  int message_id;
#define MAP_ERROR(name, id) \
  case Error::name:         \
    message_id = id;        \
    break
  switch (error_info.error) {
    MAP_ERROR(SIGN_IN_NETWORK_ERROR, IDS_ARC_SIGN_IN_NETWORK_ERROR);
    MAP_ERROR(SIGN_IN_GMS_SIGNIN_ERROR, IDS_ARC_SIGN_IN_GMS_SIGNIN_ERROR);
    MAP_ERROR(SIGN_IN_BAD_AUTHENTICATION_ERROR,
              IDS_ARC_SIGN_IN_BAD_AUTHENTICATION_ERROR);
    MAP_ERROR(SIGN_IN_GMS_CHECKIN_ERROR, IDS_ARC_SIGN_IN_GMS_CHECKIN_ERROR);
    MAP_ERROR(SIGN_IN_CLOUD_PROVISION_FLOW_ACCOUNT_MISSING_ERROR,
              IDS_ARC_SIGN_IN_CLOUD_PROVISION_FLOW_ACCOUNT_MISSING_ERROR);
    MAP_ERROR(SIGN_IN_CLOUD_PROVISION_FLOW_DOMAIN_JOIN_FAIL_ERROR,
              IDS_ARC_SIGN_IN_CLOUD_PROVISION_FLOW_DOMAIN_JOIN_FAIL_ERROR);
    MAP_ERROR(SIGN_IN_CLOUD_PROVISION_FLOW_ENROLLMENT_TOKEN_INVALID,
              IDS_ARC_SIGN_IN_CLOUD_PROVISION_FLOW_ENROLLMENT_TOKEN_INVALID);
    MAP_ERROR(SIGN_IN_CLOUD_PROVISION_FLOW_INTERRUPTED_ERROR,
              IDS_ARC_SIGN_IN_CLOUD_PROVISION_FLOW_INTERRUPTED_ERROR);
    MAP_ERROR(SIGN_IN_CLOUD_PROVISION_FLOW_NETWORK_ERROR,
              IDS_ARC_SIGN_IN_CLOUD_PROVISION_FLOW_NETWORK_ERROR);
    MAP_ERROR(SIGN_IN_CLOUD_PROVISION_FLOW_PERMANENT_ERROR,
              IDS_ARC_SIGN_IN_CLOUD_PROVISION_FLOW_PERMANENT_ERROR);
    MAP_ERROR(SIGN_IN_CLOUD_PROVISION_FLOW_TRANSIENT_ERROR,
              IDS_ARC_SIGN_IN_CLOUD_PROVISION_FLOW_TRANSIENT_ERROR);
    MAP_ERROR(SIGN_IN_UNKNOWN_ERROR, IDS_ARC_SIGN_IN_UNKNOWN_ERROR);
    MAP_ERROR(SERVER_COMMUNICATION_ERROR, IDS_ARC_SERVER_COMMUNICATION_ERROR);
    MAP_ERROR(ANDROID_MANAGEMENT_REQUIRED_ERROR,
              IDS_ARC_ANDROID_MANAGEMENT_REQUIRED_ERROR);
    MAP_ERROR(NETWORK_UNAVAILABLE_ERROR, IDS_ARC_NETWORK_UNAVAILABLE_ERROR);
    MAP_ERROR(LOW_DISK_SPACE_ERROR, IDS_ARC_LOW_DISK_SPACE_ERROR);
  }
#undef MAP_ERROR

  std::u16string message;
  switch (error_info.error) {
    case Error::SIGN_IN_CLOUD_PROVISION_FLOW_ACCOUNT_MISSING_ERROR:
    case Error::SIGN_IN_CLOUD_PROVISION_FLOW_DOMAIN_JOIN_FAIL_ERROR:
    case Error::SIGN_IN_CLOUD_PROVISION_FLOW_ENROLLMENT_TOKEN_INVALID:
    case Error::SIGN_IN_CLOUD_PROVISION_FLOW_INTERRUPTED_ERROR:
    case Error::SIGN_IN_CLOUD_PROVISION_FLOW_NETWORK_ERROR:
    case Error::SIGN_IN_CLOUD_PROVISION_FLOW_PERMANENT_ERROR:
    case Error::SIGN_IN_CLOUD_PROVISION_FLOW_TRANSIENT_ERROR:
    case Error::SIGN_IN_GMS_SIGNIN_ERROR:
    case Error::SIGN_IN_GMS_CHECKIN_ERROR:
    case Error::SIGN_IN_UNKNOWN_ERROR:
      DCHECK(error_info.arg);
      message = l10n_util::GetStringFUTF16(
          message_id, base::NumberToString16(error_info.arg.value()));
      break;
    default:
      message = l10n_util::GetStringUTF16(message_id);
      break;
  }

  message_args.Set(kErrorMessage, message);
  message_args.Set(kShouldShowSendFeedback, should_show_send_feedback);
  message_args.Set(kShouldShowNetworkTests, should_show_run_network_tests);
  message_host_->SendMessage(message_args);
}

void ArcSupportHost::SetMetricsPreferenceCheckbox(bool is_enabled,
                                                  bool is_managed) {
  metrics_checkbox_ = PreferenceCheckboxData(is_enabled, is_managed);
  SendPreferenceCheckboxUpdate(kActionSetMetricsMode, metrics_checkbox_);
}

void ArcSupportHost::SetBackupAndRestorePreferenceCheckbox(bool is_enabled,
                                                           bool is_managed) {
  backup_and_restore_checkbox_ = PreferenceCheckboxData(is_enabled, is_managed);
  SendPreferenceCheckboxUpdate(kActionBackupAndRestoreMode,
                               backup_and_restore_checkbox_);
}

void ArcSupportHost::SetLocationServicesPreferenceCheckbox(bool is_enabled,
                                                           bool is_managed) {
  location_services_checkbox_ = PreferenceCheckboxData(is_enabled, is_managed);
  SendPreferenceCheckboxUpdate(kActionLocationServiceMode,
                               location_services_checkbox_);
}

void ArcSupportHost::SendPreferenceCheckboxUpdate(
    const std::string& action_name,
    const PreferenceCheckboxData& data) {
  if (!message_host_)
    return;

  base::DictValue message;
  message.Set(kAction, action_name);
  message.Set(kEnabled, data.is_enabled);
  message.Set(kManaged, data.is_managed);
  message_host_->SendMessage(message);
}

void ArcSupportHost::SetMessageHost(arc::ArcSupportMessageHost* message_host) {
  if (message_host_ == message_host)
    return;

  app_start_pending_ = false;
  if (message_host_)
    DisconnectMessageHost();
  message_host_ = message_host;
  message_host_->SetObserver(this);
  display_observer_.emplace(this);

  if (!Initialize()) {
    Close();
    return;
  }

  // Hereafter, install the requested ui state into the ARC support Chrome app.

  // Set preference checkbox state.
  SendPreferenceCheckboxUpdate(kActionSetMetricsMode, metrics_checkbox_);
  SendPreferenceCheckboxUpdate(kActionBackupAndRestoreMode,
                               backup_and_restore_checkbox_);
  SendPreferenceCheckboxUpdate(kActionLocationServiceMode,
                               location_services_checkbox_);

  if (ui_page_ == UIPage::NO_PAGE) {
    // Close() is called before opening the window.
    Close();
  } else if (ui_page_ == UIPage::ERROR) {
    DCHECK(error_info_);
    ShowError(error_info_.value(), should_show_send_feedback_,
              should_show_run_network_tests_);
  } else {
    ShowPage(ui_page_);
  }
}

void ArcSupportHost::UnsetMessageHost(
    arc::ArcSupportMessageHost* message_host) {
  if (message_host_ != message_host)
    return;
  DisconnectMessageHost();
}

void ArcSupportHost::DisconnectMessageHost() {
  DCHECK(message_host_);
  display_observer_.reset();
  message_host_->SetObserver(nullptr);
  message_host_ = nullptr;
}

void ArcSupportHost::RequestAppStart() {
  DCHECK(!message_host_);
  DCHECK(!app_start_pending_);

  app_start_pending_ = true;
  request_open_app_callback_.Run(profile_.get());
}

void ArcSupportHost::SetRequestOpenAppCallbackForTesting(
    const RequestOpenAppCallback& callback) {
  DCHECK(!message_host_);
  DCHECK(!app_start_pending_);
  DCHECK(!callback.is_null());
  request_open_app_callback_ = callback;
}

bool ArcSupportHost::Initialize() {
  DCHECK(message_host_);

  const bool is_child =
      user_manager::UserManager::Get()->IsLoggedInAsChildUser();

  base::DictValue loadtime_data;
  loadtime_data.Set("appWindow", l10n_util::GetStringUTF16(
                                     IDS_ARC_PLAYSTORE_ICON_TITLE_BETA));
  loadtime_data.Set("greetingHeader",
                    l10n_util::GetStringUTF16(IDS_ARC_OOBE_TERMS_HEADING));
  loadtime_data.Set(
      "initializingHeader",
      l10n_util::GetStringUTF16(IDS_ARC_PLAYSTORE_SETTING_UP_TITLE));
  loadtime_data.Set("greetingDescription",
                    l10n_util::GetStringUTF16(IDS_ARC_OOBE_TERMS_DESCRIPTION));
  loadtime_data.Set("buttonAgree", l10n_util::GetStringUTF16(
                                       IDS_ARC_OPT_IN_DIALOG_BUTTON_AGREE));
  loadtime_data.Set("buttonCancel", l10n_util::GetStringUTF16(
                                        IDS_ARC_OPT_IN_DIALOG_BUTTON_CANCEL));
  loadtime_data.Set("buttonNext", l10n_util::GetStringUTF16(
                                      IDS_ARC_OPT_IN_DIALOG_BUTTON_NEXT));
  loadtime_data.Set(
      "buttonSendFeedback",
      l10n_util::GetStringUTF16(IDS_ARC_OPT_IN_DIALOG_BUTTON_SEND_FEEDBACK));
  loadtime_data.Set("buttonRunNetworkTests",
                    l10n_util::GetStringUTF16(
                        IDS_ARC_OPT_IN_DIALOG_BUTTON_RUN_NETWORK_TESTS));
  loadtime_data.Set("buttonRetry", l10n_util::GetStringUTF16(
                                       IDS_ARC_OPT_IN_DIALOG_BUTTON_RETRY));
  loadtime_data.Set(
      "progressTermsLoading",
      l10n_util::GetStringUTF16(IDS_ARC_OPT_IN_DIALOG_PROGRESS_TERMS));
  loadtime_data.Set(
      "progressAndroidLoading",
      l10n_util::GetStringUTF16(IDS_ARC_OPT_IN_DIALOG_PROGRESS_ANDROID));
  loadtime_data.Set(
      "authorizationFailed",
      l10n_util::GetStringUTF16(IDS_ARC_OPT_IN_DIALOG_AUTHORIZATION_FAILED));
  loadtime_data.Set(
      "termsOfService",
      l10n_util::GetStringUTF16(IDS_ARC_OPT_IN_DIALOG_TERMS_OF_SERVICE));
  loadtime_data.Set("textMetricsEnabled",
                    l10n_util::GetStringUTF16(
                        is_child ? IDS_ARC_OPT_IN_DIALOG_METRICS_ENABLED_CHILD
                                 : IDS_ARC_OPT_IN_DIALOG_METRICS_ENABLED));
  loadtime_data.Set("textMetricsDisabled",
                    l10n_util::GetStringUTF16(
                        is_child ? IDS_ARC_OPT_IN_DIALOG_METRICS_DISABLED_CHILD
                                 : IDS_ARC_OPT_IN_DIALOG_METRICS_DISABLED));
  loadtime_data.Set(
      "textMetricsManagedEnabled",
      l10n_util::GetStringUTF16(
          is_child ? IDS_ARC_OPT_IN_DIALOG_METRICS_MANAGED_ENABLED_CHILD
                   : IDS_ARC_OPT_IN_DIALOG_METRICS_MANAGED_ENABLED));
  loadtime_data.Set(
      "textMetricsManagedDisabled",
      l10n_util::GetStringUTF16(
          is_child ? IDS_ARC_OPT_IN_DIALOG_METRICS_MANAGED_DISABLED_CHILD
                   : IDS_ARC_OPT_IN_DIALOG_METRICS_MANAGED_DISABLED));
  loadtime_data.Set(
      "textBackupRestoreLabel",
      l10n_util::GetStringUTF16(
          is_child ? IDS_ARC_OPT_IN_DIALOG_BACKUP_RESTORE_CHILD_LABEL
                   : IDS_ARC_OPT_IN_DIALOG_BACKUP_RESTORE_LABEL));
  loadtime_data.Set("textBackupRestore",
                    l10n_util::GetStringUTF16(
                        is_child ? IDS_ARC_OPT_IN_DIALOG_BACKUP_RESTORE_CHILD
                                 : IDS_ARC_OPT_IN_DIALOG_BACKUP_RESTORE));
  loadtime_data.Set("textPaiService",
                    l10n_util::GetStringUTF16(IDS_ARC_OPT_IN_PAI));
  loadtime_data.Set(
      "textGoogleServiceConfirmation",
      l10n_util::GetStringUTF16(IDS_ARC_OPT_IN_GOOGLE_SERVICE_CONFIRMATION));
  if (ash::features::IsCrosPrivacyHubLocationEnabled()) {
    loadtime_data.Set("textLocationService",
                      l10n_util::GetStringUTF16(
                          is_child ? IDS_ARC_CROS_OPT_IN_LOCATION_SETTING_CHILD
                                   : IDS_ARC_CROS_OPT_IN_LOCATION_SETTING));
  } else {
    loadtime_data.Set("textLocationService",
                      l10n_util::GetStringUTF16(
                          is_child ? IDS_ARC_OPT_IN_LOCATION_SETTING_CHILD
                                   : IDS_ARC_OPT_IN_LOCATION_SETTING));
  }
  loadtime_data.Set("serverError", l10n_util::GetStringUTF16(
                                       IDS_ARC_SERVER_COMMUNICATION_ERROR));
  loadtime_data.Set("controlledByPolicy",
                    l10n_util::GetStringUTF16(IDS_CONTROLLED_SETTING_POLICY));
  loadtime_data.Set(
      "learnMoreStatisticsTitle",
      l10n_util::GetStringUTF16(IDS_ARC_OPT_IN_LEARN_MORE_STATISTICS_TITLE));
  loadtime_data.Set("learnMoreStatistics",
                    l10n_util::GetStringUTF16(
                        is_child ? IDS_ARC_OPT_IN_LEARN_MORE_STATISTICS_CHILD
                                 : IDS_ARC_OPT_IN_LEARN_MORE_STATISTICS));
  loadtime_data.Set("learnMoreBackupAndRestoreTitle",
                    l10n_util::GetStringUTF16(
                        IDS_ARC_OPT_IN_LEARN_MORE_BACKUP_AND_RESTORE_TITLE));
  loadtime_data.Set(
      "learnMoreBackupAndRestore",
      l10n_util::GetStringUTF16(
          is_child ? IDS_ARC_OPT_IN_LEARN_MORE_BACKUP_AND_RESTORE_CHILD
                   : IDS_ARC_OPT_IN_LEARN_MORE_BACKUP_AND_RESTORE));
  loadtime_data.Set("learnMoreLocationServicesTitle",
                    l10n_util::GetStringUTF16(
                        IDS_ARC_OPT_IN_LEARN_MORE_LOCATION_SERVICES_TITLE));
  if (ash::features::IsCrosPrivacyHubLocationEnabled()) {
    loadtime_data.Set(
        "learnMoreLocationServices",
        l10n_util::GetStringFUTF16(
            is_child ? IDS_ARC_CROS_OPT_IN_LEARN_MORE_LOCATION_SERVICES_CHILD
                     : IDS_ARC_CROS_OPT_IN_LEARN_MORE_LOCATION_SERVICES,
            ash::external_urls::kPrivacyHubGeolocationAccuracyLearnMoreURL));
  } else {
    loadtime_data.Set(
        "learnMoreLocationServices",
        l10n_util::GetStringUTF16(
            is_child ? IDS_ARC_OPT_IN_LEARN_MORE_LOCATION_SERVICES_CHILD
                     : IDS_ARC_OPT_IN_LEARN_MORE_LOCATION_SERVICES));
  }
  loadtime_data.Set(
      "learnMorePaiServiceTitle",
      l10n_util::GetStringUTF16(IDS_ARC_OPT_IN_LEARN_MORE_PAI_SERVICE_TITLE));
  loadtime_data.Set(
      "learnMorePaiService",
      l10n_util::GetStringUTF16(IDS_ARC_OPT_IN_LEARN_MORE_PAI_SERVICE));
  loadtime_data.Set("overlayClose",
                    l10n_util::GetStringUTF16(IDS_ARC_OPT_IN_LEARN_MORE_CLOSE));
  loadtime_data.Set(
      "privacyPolicyLink",
      l10n_util::GetStringUTF16(IDS_ARC_OPT_IN_PRIVACY_POLICY_LINK));
  loadtime_data.Set("overlayLoading",
                    l10n_util::GetStringUTF16(IDS_ARC_POPUP_HELP_LOADING));

  loadtime_data.Set(kArcManaged, is_arc_managed_);
  loadtime_data.Set(
      "isOwnerProfile",
      user_manager::UserManager::Get()->IsOwnerUser(
          ash::BrowserContextHelper::Get()->GetUserByBrowserContext(profile_)));

  const std::string& country_code = base::CountryCodeForCurrentTimezone();
  loadtime_data.Set("countryCode", country_code);

  const std::string& app_locale = application_locale_storage_->Get();
  webui::SetLoadTimeDataDefaults(app_locale, &loadtime_data);
  loadtime_data.Set("locale", app_locale);

  base::DictValue message;
  message.Set(kAction, kActionInitialize);
  message.Set(kData, std::move(loadtime_data));

  user_manager::KnownUser known_user(&local_state_.get());
  const std::string device_id = known_user.GetDeviceId(
      CHECK_DEREF(ash::AnnotatedAccountId::Get(profile_)));
  message.Set(kDeviceId, device_id);

  message_host_->SendMessage(message);
  return true;
}

void ArcSupportHost::OnDisplayMetricsChanged(const display::Display& display,
                                             uint32_t changed_metrics) {
  SetWindowBound(display);
}

void ArcSupportHost::SetWindowBound(const display::Display& display) {
  if (!message_host_)
    return;

  base::DictValue message;
  message.Set(kAction, kActionSetWindowBounds);
  message.Set(kDisplayWorkareaX, display.work_area().x());
  message.Set(kDisplayWorkareaY, display.work_area().y());
  message.Set(kDisplayWorkareaWidth, display.work_area().width());
  message.Set(kDisplayWorkareaHeight, display.work_area().height());
  message_host_->SendMessage(message);
}

void ArcSupportHost::OnMessage(const base::DictValue& message) {
  const std::string* event = message.FindString(kEvent);
  if (!event) {
    NOTREACHED();
  }

  if (*event == kEventOnWindowClosed) {
    // If ToS negotiation is ongoing, call the specific function.
    if (tos_delegate_) {
      tos_delegate_->OnTermsRejected();
    } else {
      DCHECK(error_delegate_);
      error_delegate_->OnWindowClosed();
    }
  } else if (*event == kEventOnAgreed || *event == kEventOnCanceled) {
    DCHECK(tos_delegate_);
    std::optional<bool> tos_shown = message.FindBool(kTosShown);
    std::optional<bool> is_metrics_enabled =
        message.FindBool(kIsMetricsEnabled);
    std::optional<bool> is_backup_restore_enabled =
        message.FindBool(kIsBackupRestoreEnabled);
    std::optional<bool> is_backup_restore_managed =
        message.FindBool(kIsBackupRestoreManaged);
    std::optional<bool> is_location_service_enabled =
        message.FindBool(kIsLocationServiceEnabled);
    std::optional<bool> is_location_service_managed =
        message.FindBool(kIsLocationServiceManaged);

    const std::string* tos_content = message.FindString(kTosContent);
    if (!tos_content || !tos_shown.has_value() ||
        !is_metrics_enabled.has_value() ||
        !is_backup_restore_enabled.has_value() ||
        !is_backup_restore_managed.has_value() ||
        !is_location_service_enabled.has_value() ||
        !is_location_service_managed.has_value()) {
      NOTREACHED();
    }

    bool accepted = *event == kEventOnAgreed;
    if (!accepted) {
      // Cancel is equivalent to not granting consent to the individual
      // features, so ensure we don't record consent.
      is_backup_restore_enabled = false;
      is_location_service_enabled = false;
    }

    const auto& user = CHECK_DEREF(
        ash::BrowserContextHelper::Get()->GetUserByBrowserContext(profile_));
    GaiaId gaia_id = user.GetAccountId().GetGaiaId();
    bool is_child = user.GetType() == user_manager::UserType::kChild;

    // Record acceptance of ToS if it was shown to the user, otherwise simply
    // record acceptance of an empty ToS.
    UserConsentTypes::ArcPlayTermsOfServiceConsent play_consent;
    play_consent.set_status(accepted ? UserConsentTypes::GIVEN
                                     : UserConsentTypes::NOT_GIVEN);
    play_consent.set_confirmation_grd_id(IDS_ARC_OPT_IN_DIALOG_BUTTON_AGREE);
    play_consent.set_consent_flow(
        UserConsentTypes::ArcPlayTermsOfServiceConsent::SETUP);
    if (tos_shown.value()) {
      play_consent.set_play_terms_of_service_text_length(tos_content->length());
      play_consent.set_play_terms_of_service_hash(
          arc::GetSha1HashForArcPlayTermsOfService(*tos_content));
    }
    ConsentAuditorFactory::GetForProfile(profile_)->RecordArcPlayConsent(
        gaia_id, play_consent);

    // If the user - not policy - controls Backup and Restore setting, record
    // whether consent was given.
    if (!is_backup_restore_managed.value()) {
      UserConsentTypes::ArcBackupAndRestoreConsent backup_and_restore_consent;
      backup_and_restore_consent.set_confirmation_grd_id(
          IDS_ARC_OPT_IN_DIALOG_BUTTON_AGREE);
      backup_and_restore_consent.add_description_grd_ids(
          is_child ? IDS_ARC_OPT_IN_DIALOG_BACKUP_RESTORE_CHILD
                   : IDS_ARC_OPT_IN_DIALOG_BACKUP_RESTORE);
      backup_and_restore_consent.set_status(is_backup_restore_enabled.value()
                                                ? UserConsentTypes::GIVEN
                                                : UserConsentTypes::NOT_GIVEN);

      ConsentAuditorFactory::GetForProfile(profile_)
          ->RecordArcBackupAndRestoreConsent(gaia_id,
                                             backup_and_restore_consent);
    }

    // If the user - not policy - controls Location Services setting, record
    // whether consent was given.
    if (!is_location_service_managed.value()) {
      UserConsentTypes::ArcGoogleLocationServiceConsent
          location_service_consent;
      location_service_consent.set_confirmation_grd_id(
          IDS_ARC_OPT_IN_DIALOG_BUTTON_AGREE);

      if (ash::features::IsCrosPrivacyHubLocationEnabled()) {
        location_service_consent.add_description_grd_ids(
            is_child ? IDS_ARC_CROS_OPT_IN_LOCATION_SETTING_CHILD
                     : IDS_ARC_CROS_OPT_IN_LOCATION_SETTING);
      } else {
        location_service_consent.add_description_grd_ids(
            is_child ? IDS_ARC_OPT_IN_LOCATION_SETTING_CHILD
                     : IDS_ARC_OPT_IN_LOCATION_SETTING);
      }

      location_service_consent.set_status(is_location_service_enabled.value()
                                              ? UserConsentTypes::GIVEN
                                              : UserConsentTypes::NOT_GIVEN);
      ConsentAuditorFactory::GetForProfile(profile_)
          ->RecordArcGoogleLocationServiceConsent(gaia_id,
                                                  location_service_consent);
    }

    if (accepted) {
      // tos_delegate_->OnTermsAgreed() will free tos_delegate_. But will update
      // optin UI async to loading page. It is possible that user can click the
      // accept button again before optin UI is updated. b/284000632
      if (!tos_delegate_) {
        LOG(ERROR) << "tos_delegate_ has been freed.";
        return;
      }

      tos_delegate_->OnTermsAgreed(is_metrics_enabled.value(),
                                   is_backup_restore_enabled.value(),
                                   is_location_service_enabled.value());
    }
  } else if (*event == kEventOnRetryClicked) {
    // If ToS negotiation is ongoing, call the corresponding delegate.
    // Otherwise, call the general retry function.
    if (tos_delegate_) {
      tos_delegate_->OnTermsRetryClicked();
    } else {
      DCHECK(error_delegate_);
      error_delegate_->OnRetryClicked();
    }
  } else if (*event == kEventOnSendFeedbackClicked) {
    DCHECK(error_delegate_);
    error_delegate_->OnSendFeedbackClicked();
  } else if (*event == kEventOnRunNetworkTestsClicked) {
    DCHECK(error_delegate_);
    error_delegate_->OnRunNetworkTestsClicked();
  } else if (*event == kEventOnTosLoadResult) {
    if (tos_delegate_) {
      tos_delegate_->OnTermsLoadResult(
          message.FindBool(kSuccess).value_or(false));
    }
  } else if (*event == kEventOnErrorPageShown) {
    DCHECK(error_delegate_);
    error_delegate_->OnErrorPageShown(
        message.FindBool(kNetworkTestsShown).value_or(false));
  } else if (*event == kEventOnOpenPrivacySettingsPageClicked) {
    auto* user =
        ash::BrowserContextHelper::Get()->GetUserByBrowserContext(profile_);
    ash::SettingsAppManager::Get()->Open(
        CHECK_DEREF(user),
        {.sub_page = chromeos::settings::mojom::kPrivacyHubSubpagePath});
  } else if (*event == kEventRequestWindowBounds) {
    SetWindowBound(display::Screen::Get()->GetDisplayForNewWindows());
  } else {
    NOTREACHED() << "Unknown message: " << *event;
  }
}
