blob: 3ee337681ccffabce6f28fb743b42bcff8654e7c [file] [log] [blame]
// 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 "base/functional/bind.h"
#include "base/hash/sha1.h"
#include "base/i18n/timezone.h"
#include "base/json/json_reader.h"
#include "base/json/json_writer.h"
#include "base/strings/string_util.h"
#include "base/values.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/browser_app_launcher.h"
#include "chrome/browser/ash/app_list/arc/arc_app_utils.h"
#include "chrome/browser/ash/profiles/profile_helper.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/consent_auditor/consent_auditor_factory.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/signin/identity_manager_factory.h"
#include "chrome/browser/ui/ash/multi_user/multi_user_util.h"
#include "chrome/browser/ui/chrome_pages.h"
#include "chrome/browser/ui/webui/ash/diagnostics_dialog.h"
#include "chrome/common/webui_url_constants.h"
#include "chrome/grit/generated_resources.h"
#include "components/consent_auditor/consent_auditor.h"
#include "components/signin/public/base/consent_level.h"
#include "components/signin/public/identity_manager/identity_manager.h"
#include "components/user_manager/known_user.h"
#include "components/user_manager/user_manager.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 "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_widget_types.h"
using sync_pb::UserConsentTypes;
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) {
apps::AppServiceProxyFactory::GetForProfile(profile)
->BrowserAppLauncher()
->LaunchPlayStoreWithExtensions();
}
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";
}
// Some compiler reports an error even if all values of an enum-class are
// covered individually in a switch statement.
NOTREACHED();
return os;
}
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
// Some compiler reports an error even if all values of an enum-class are
// covered individually in a switch statement.
NOTREACHED();
return os;
}
} // 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(Profile* profile)
: profile_(profile),
request_open_app_callback_(base::BindRepeating(&RequestOpenApp)) {
DCHECK(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::Value::Dict 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::Value::Dict 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();
return;
}
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::Value::Dict 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::Value::Dict 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::Value::Dict 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_CROS_OPT_IN_LOCATION_SETTING_CHILD
: IDS_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::GetStringUTF16(
is_child ? IDS_CROS_OPT_IN_LEARN_MORE_LOCATION_SERVICES_CHILD
: IDS_CROS_OPT_IN_LEARN_MORE_LOCATION_SERVICES));
} 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",
ash::ProfileHelper::IsOwnerProfile(profile_));
const std::string& country_code = base::CountryCodeForCurrentTimezone();
loadtime_data.Set("countryCode", country_code);
const std::string& app_locale = g_browser_process->GetApplicationLocale();
webui::SetLoadTimeDataDefaults(app_locale, &loadtime_data);
loadtime_data.Set("locale", app_locale);
base::Value::Dict message;
message.Set(kAction, kActionInitialize);
message.Set(kData, std::move(loadtime_data));
user_manager::KnownUser known_user(g_browser_process->local_state());
const std::string device_id = known_user.GetDeviceId(
multi_user_util::GetAccountIdFromProfile(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::Value::Dict 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::Value::Dict& message) {
const std::string* event = message.FindString(kEvent);
if (!event) {
NOTREACHED();
return;
}
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();
return;
}
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;
}
auto* identity_manager = IdentityManagerFactory::GetForProfile(profile_);
// This class doesn't care about browser sync consent.
DCHECK(identity_manager->HasPrimaryAccount(signin::ConsentLevel::kSignin));
CoreAccountId account_id =
identity_manager->GetPrimaryAccountId(signin::ConsentLevel::kSignin);
bool is_child = user_manager::UserManager::Get()->IsLoggedInAsChildUser();
// 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(
base::SHA1HashString(*tos_content));
}
ConsentAuditorFactory::GetForProfile(profile_)->RecordArcPlayConsent(
account_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(account_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()) {
// TODO(b/327350824): Stop sending ARC controls to consent auditor.
if (!ash::features::IsCrosPrivacyHubLocationEnabled()) {
UserConsentTypes::ArcGoogleLocationServiceConsent
location_service_consent;
location_service_consent.set_confirmation_grd_id(
IDS_ARC_OPT_IN_DIALOG_BUTTON_AGREE);
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(account_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) {
chrome::ShowSettingsSubPageForProfile(profile_, chrome::kPrivacySubPage);
} else if (*event == kEventRequestWindowBounds) {
SetWindowBound(display::Screen::GetScreen()->GetDisplayForNewWindows());
} else {
LOG(ERROR) << "Unknown message: " << *event;
NOTREACHED();
}
}