blob: 0eb775100399ae1a3530955afea53478ceb71096 [file] [log] [blame]
// 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/browser/sharing/sharing_ui_controller.h"
#include <utility>
#include "base/strings/utf_string_conversions.h"
#include "base/time/time.h"
#include "chrome/browser/sharing/sharing_service_factory.h"
#include "chrome/browser/ui/browser_finder.h"
#include "chrome/browser/ui/browser_window.h"
#include "chrome/browser/ui/singleton_tabs.h"
#include "chrome/common/url_constants.h"
#include "chrome/grit/branded_strings.h"
#include "components/sharing_message/features.h"
#include "components/sharing_message/sharing_constants.h"
#include "components/sharing_message/sharing_dialog.h"
#include "components/sharing_message/sharing_dialog_data.h"
#include "components/sharing_message/sharing_target_device_info.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/gfx/vector_icon_types.h"
#include "ui/strings/grit/ui_strings.h"
namespace {
BrowserWindow* GetWindowFromWebContents(content::WebContents* web_contents) {
Browser* browser = chrome::FindBrowserWithTab(web_contents);
return browser ? browser->window() : nullptr;
}
content::WebContents* GetCurrentWebContents(
content::WebContents* web_contents) {
Browser* browser = chrome::FindBrowserWithTab(web_contents);
return browser ? browser->tab_strip_model()->GetActiveWebContents() : nullptr;
}
SharingDialogType GetSharingDialogType(bool has_devices, bool has_apps) {
if (has_devices)
return SharingDialogType::kDialogWithDevicesMaybeApps;
if (has_apps)
return SharingDialogType::kDialogWithoutDevicesWithApp;
return SharingDialogType::kEducationalDialog;
}
} // namespace
SharingUiController::SharingUiController(content::WebContents* web_contents)
: web_contents_(web_contents),
sharing_service_(SharingServiceFactory::GetForBrowserContext(
web_contents->GetBrowserContext())) {}
SharingUiController::~SharingUiController() = default;
std::u16string SharingUiController::GetTitle(SharingDialogType dialog_type) {
// We only handle error messages generically.
DCHECK_EQ(SharingDialogType::kErrorDialog, dialog_type);
switch (send_result()) {
case SharingSendMessageResult::kDeviceNotFound:
case SharingSendMessageResult::kNetworkError:
case SharingSendMessageResult::kAckTimeout:
case SharingSendMessageResult::kCommitTimeout:
return l10n_util::GetStringFUTF16(
IDS_BROWSER_SHARING_ERROR_DIALOG_TITLE_GENERIC_ERROR,
GetContentType());
case SharingSendMessageResult::kSuccessful:
case SharingSendMessageResult::kCancelled:
NOTREACHED();
case SharingSendMessageResult::kPayloadTooLarge:
case SharingSendMessageResult::kInternalError:
case SharingSendMessageResult::kEncryptionError:
return l10n_util::GetStringFUTF16(
IDS_BROWSER_SHARING_ERROR_DIALOG_TITLE_INTERNAL_ERROR,
GetContentType());
}
}
std::u16string SharingUiController::GetErrorDialogText() const {
switch (send_result()) {
case SharingSendMessageResult::kDeviceNotFound:
return l10n_util::GetStringFUTF16(
IDS_BROWSER_SHARING_ERROR_DIALOG_TEXT_DEVICE_NOT_FOUND,
GetTargetDeviceName());
case SharingSendMessageResult::kCommitTimeout:
case SharingSendMessageResult::kNetworkError:
return l10n_util::GetStringUTF16(
IDS_BROWSER_SHARING_ERROR_DIALOG_TEXT_NETWORK_ERROR);
case SharingSendMessageResult::kAckTimeout:
return l10n_util::GetStringFUTF16(
IDS_BROWSER_SHARING_ERROR_DIALOG_TEXT_DEVICE_ACK_TIMEOUT,
GetTargetDeviceName());
case SharingSendMessageResult::kSuccessful:
case SharingSendMessageResult::kCancelled:
return std::u16string();
case SharingSendMessageResult::kPayloadTooLarge:
case SharingSendMessageResult::kInternalError:
case SharingSendMessageResult::kEncryptionError:
return l10n_util::GetStringUTF16(
IDS_BROWSER_SHARING_ERROR_DIALOG_TEXT_INTERNAL_ERROR);
}
}
void SharingUiController::OnDialogClosed(SharingDialog* dialog) {
// Ignore already replaced dialogs.
if (dialog != dialog_)
return;
dialog_ = nullptr;
UpdateIcon();
}
void SharingUiController::OnDialogShown(bool has_devices, bool has_apps) {
if (on_dialog_shown_closure_for_testing_)
std::move(on_dialog_shown_closure_for_testing_).Run();
}
void SharingUiController::ClearLastDialog() {
last_dialog_id_++;
is_loading_ = false;
send_result_ = SharingSendMessageResult::kSuccessful;
CloseDialog();
}
void SharingUiController::UpdateAndShowDialog(
const std::optional<url::Origin>& initiating_origin) {
ClearLastDialog();
DoUpdateApps(base::BindOnce(&SharingUiController::OnAppsReceived,
weak_ptr_factory_.GetWeakPtr(), last_dialog_id_,
initiating_origin));
}
std::vector<SharingTargetDeviceInfo> SharingUiController::GetDevices() const {
return sharing_service_->GetDeviceCandidates(GetRequiredFeature());
}
bool SharingUiController::HasSendFailed() const {
return send_result_ != SharingSendMessageResult::kSuccessful &&
send_result_ != SharingSendMessageResult::kCancelled;
}
void SharingUiController::MaybeShowErrorDialog() {
if (HasSendFailed() && web_contents_ == GetCurrentWebContents(web_contents_))
ShowNewDialog(CreateDialogData(SharingDialogType::kErrorDialog));
}
SharingDialogData SharingUiController::CreateDialogData(
SharingDialogType dialog_type) {
SharingDialogData data;
data.type = dialog_type;
data.prefix = GetFeatureMetricsPrefix();
data.title = GetTitle(data.type);
data.error_text = GetErrorDialogText();
auto weak_ptr = weak_ptr_factory_.GetWeakPtr();
data.device_callback =
base::BindOnce(&SharingUiController::OnDeviceChosen, weak_ptr);
data.app_callback =
base::BindOnce(&SharingUiController::OnAppChosen, weak_ptr);
data.close_callback =
base::BindOnce(&SharingUiController::OnDialogClosed, weak_ptr);
return data;
}
bool SharingUiController::ShouldShowLoadingIcon() const {
return true;
}
bool SharingUiController::HasAccessibleUi() const {
return true;
}
base::OnceClosure SharingUiController::SendMessageToDevice(
const SharingTargetDeviceInfo& device,
std::optional<base::TimeDelta> response_timeout,
components_sharing_message::SharingMessage sharing_message,
std::optional<SharingMessageSender::ResponseCallback> custom_callback) {
send_result_ = SharingSendMessageResult::kSuccessful;
target_device_name_ = device.client_name();
if (ShouldShowLoadingIcon()) {
last_dialog_id_++;
is_loading_ = true;
UpdateIcon();
}
SharingMessageSender::ResponseCallback response_callback = base::BindOnce(
&SharingUiController::OnResponse, weak_ptr_factory_.GetWeakPtr(),
last_dialog_id_, std::move(custom_callback));
return sharing_service_->SendMessageToDevice(
device, response_timeout.value_or(kSharingMessageTTL),
std::move(sharing_message), std::move(response_callback));
}
void SharingUiController::UpdateIcon() {
BrowserWindow* window = GetWindowFromWebContents(web_contents_);
if (!window)
return;
window->UpdatePageActionIcon(GetIconType());
}
void SharingUiController::CloseDialog() {
if (!dialog_)
return;
dialog_->Hide();
// SharingDialog::Hide may close the dialog asynchronously, and therefore not
// call OnDialogClosed immediately. If that is the case, call OnDialogClosed
// now to notify subclasses and clear |dialog_|.
if (dialog_)
OnDialogClosed(dialog_);
DCHECK(!dialog_);
}
void SharingUiController::ShowNewDialog(SharingDialogData dialog_data) {
CloseDialog();
BrowserWindow* window = GetWindowFromWebContents(web_contents_);
if (!window)
return;
bool has_devices = !dialog_data.devices.empty();
bool has_apps = !dialog_data.apps.empty();
dialog_ = window->ShowSharingDialog(web_contents(), std::move(dialog_data));
UpdateIcon();
OnDialogShown(has_devices, has_apps);
}
std::u16string SharingUiController::GetTargetDeviceName() const {
return base::UTF8ToUTF16(target_device_name_);
}
void SharingUiController::OnResponse(
int dialog_id,
std::optional<SharingMessageSender::ResponseCallback> custom_callback,
SharingSendMessageResult result,
std::unique_ptr<components_sharing_message::ResponseMessage> response) {
if (custom_callback)
std::move(custom_callback.value()).Run(result, std::move(response));
if (dialog_id != last_dialog_id_)
return;
send_result_ = result;
if (ShouldShowLoadingIcon()) {
is_loading_ = false;
UpdateIcon();
}
}
void SharingUiController::OnAppsReceived(
int dialog_id,
const std::optional<url::Origin>& initiating_origin,
std::vector<SharingApp> apps) {
if (dialog_id != last_dialog_id_)
return;
auto devices = GetDevices();
SharingDialogData dialog_data =
CreateDialogData(GetSharingDialogType(!devices.empty(), !apps.empty()));
dialog_data.devices = std::move(devices);
dialog_data.apps = std::move(apps);
dialog_data.initiating_origin = initiating_origin;
ShowNewDialog(std::move(dialog_data));
}