blob: c0218d95a3625b8fc561975ce0a7c1c99fe72fdb [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 "components/sharing_message/sharing_service.h"
#include "base/feature_list.h"
#include "base/functional/bind.h"
#include "base/logging.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "components/favicon/core/favicon_service.h"
#include "components/favicon_base/favicon_types.h"
#include "components/send_tab_to_self/features.h"
#include "components/send_tab_to_self/send_tab_to_self_entry.h"
#include "components/send_tab_to_self/send_tab_to_self_model.h"
#include "components/sharing_message/features.h"
#include "components/sharing_message/sharing_constants.h"
#include "components/sharing_message/sharing_device_registration_result.h"
#include "components/sharing_message/sharing_device_source.h"
#include "components/sharing_message/sharing_fcm_handler.h"
#include "components/sharing_message/sharing_handler_registry.h"
#include "components/sharing_message/sharing_message_handler.h"
#include "components/sharing_message/sharing_metrics.h"
#include "components/sharing_message/sharing_sync_preference.h"
#include "components/sharing_message/sharing_target_device_info.h"
#include "components/sharing_message/sharing_utils.h"
#include "components/strings/grit/components_strings.h"
#include "components/sync/protocol/unencrypted_sharing_message.pb.h"
#include "components/sync/service/sync_service.h"
#include "ui/base/l10n/l10n_util.h"
#include "url/gurl.h"
namespace {
constexpr int kMinimumFaviconSize = 32;
} // namespace
SharingService::SharingService(
std::unique_ptr<SharingSyncPreference> sync_prefs,
std::unique_ptr<SharingDeviceRegistration> sharing_device_registration,
std::unique_ptr<SharingMessageSender> message_sender,
std::unique_ptr<SharingDeviceSource> device_source,
std::unique_ptr<SharingHandlerRegistry> handler_registry,
std::unique_ptr<SharingFCMHandler> fcm_handler,
syncer::SyncService* sync_service,
favicon::FaviconService* favicon_service,
send_tab_to_self::SendTabToSelfModel* send_tab_model,
scoped_refptr<base::SingleThreadTaskRunner> task_runner)
: sync_prefs_(std::move(sync_prefs)),
sharing_device_registration_(std::move(sharing_device_registration)),
message_sender_(std::move(message_sender)),
device_source_(std::move(device_source)),
handler_registry_(std::move(handler_registry)),
fcm_handler_(std::move(fcm_handler)),
sync_service_(sync_service),
favicon_service_(favicon_service),
task_runner_(std::move(task_runner)),
backoff_entry_(&kRetryBackoffPolicy),
state_(State::DISABLED) {
// If device has already registered before, start listening to FCM right away
// to avoid missing messages.
if (sync_prefs_ && sync_prefs_->GetFCMRegistration()) {
fcm_handler_->StartListening();
}
if (sync_service_ && !sync_service_->HasObserver(this)) {
sync_service_->AddObserver(this);
}
// Only unregister if sync is disabled (not initializing).
if (IsSyncDisabledForSharing(sync_service_)) {
// state_ is kept as State::DISABLED as SharingService has never registered,
// and only doing clean up via UnregisterDevice().
UnregisterDevice();
}
// `send_tab_model_` can be null in tests.
if (send_tab_model &&
base::FeatureList::IsEnabled(
send_tab_to_self::kSendTabToSelfIOSPushNotifications)) {
send_tab_to_self_scoped_observation_.Observe(send_tab_model);
}
}
SharingService::~SharingService() {
// `sync_service_` should be reset in `Shutdown` method.
DCHECK(!sync_service_);
}
std::optional<SharingTargetDeviceInfo> SharingService::GetDeviceByGuid(
const std::string& guid) const {
return device_source_->GetDeviceByGuid(guid);
}
SharingService::SharingDeviceList SharingService::GetDeviceCandidates(
sync_pb::SharingSpecificFields::EnabledFeatures required_feature) const {
return device_source_->GetDeviceCandidates(required_feature);
}
base::OnceClosure SharingService::SendMessageToDevice(
const SharingTargetDeviceInfo& device,
base::TimeDelta response_timeout,
components_sharing_message::SharingMessage message,
SharingMessageSender::ResponseCallback callback) {
return message_sender_->SendMessageToDevice(
device, response_timeout, std::move(message),
SharingMessageSender::DelegateType::kFCM, std::move(callback));
}
base::OnceClosure SharingService::SendUnencryptedMessageToDevice(
const SharingTargetDeviceInfo& device,
sync_pb::UnencryptedSharingMessage message,
SharingMessageSender::ResponseCallback callback) {
return message_sender_->SendUnencryptedMessageToDevice(
device, std::move(message), SharingMessageSender::DelegateType::kIOSPush,
std::move(callback));
}
void SharingService::RegisterSharingHandler(
std::unique_ptr<SharingMessageHandler> handler,
components_sharing_message::SharingMessage::PayloadCase payload_case) {
handler_registry_->RegisterSharingHandler(std::move(handler), payload_case);
}
void SharingService::UnregisterSharingHandler(
components_sharing_message::SharingMessage::PayloadCase payload_case) {
handler_registry_->UnregisterSharingHandler(payload_case);
}
void SharingService::SetNotificationActionHandler(
const std::string& notification_id,
NotificationActionCallback callback) {
if (callback) {
notification_action_handlers_[notification_id] = callback;
} else {
notification_action_handlers_.erase(notification_id);
}
}
SharingService::NotificationActionCallback
SharingService::GetNotificationActionHandler(
const std::string& notification_id) const {
auto iter = notification_action_handlers_.find(notification_id);
return iter == notification_action_handlers_.end()
? NotificationActionCallback()
: iter->second;
}
SharingDeviceSource* SharingService::GetDeviceSource() const {
return device_source_.get();
}
SharingService::State SharingService::GetStateForTesting() const {
return state_;
}
SharingSyncPreference* SharingService::GetSyncPreferencesForTesting() const {
return sync_prefs_.get();
}
SharingFCMHandler* SharingService::GetFCMHandlerForTesting() const {
return fcm_handler_.get();
}
SharingMessageSender* SharingService::GetMessageSenderForTesting() const {
return message_sender_.get();
}
SharingMessageHandler* SharingService::GetSharingHandlerForTesting(
components_sharing_message::SharingMessage::PayloadCase payload_case)
const {
return handler_registry_->GetSharingHandler(payload_case);
}
void SharingService::EntryAddedLocally(
const send_tab_to_self::SendTabToSelfEntry* entry) {
if (!base::FeatureList::IsEnabled(
send_tab_to_self::kSendTabToSelfIOSPushNotifications)) {
return;
}
std::optional<SharingTargetDeviceInfo> target_device_info =
GetDeviceByGuid(entry->GetTargetDeviceSyncCacheGuid());
if (!target_device_info.has_value()) {
return;
}
if (target_device_info.value().platform() != SharingDevicePlatform::kIOS) {
return;
}
if (send_tab_to_self::IsSendTabIOSPushNotificationsEnabledWithURLImage()) {
auto large_icon_types = std::vector<favicon_base::IconTypeSet>(
{{favicon_base::IconType::kWebManifestIcon},
{favicon_base::IconType::kFavicon},
{favicon_base::IconType::kTouchIcon},
{favicon_base::IconType::kTouchPrecomposedIcon}});
// Retrieve favicon to issue notification.
favicon_service_->GetLargestRawFaviconForPageURL(
entry->GetURL(), large_icon_types, kMinimumFaviconSize,
base::BindOnce(&SharingService::SendNotificationForSendTabToSelfPush,
weak_ptr_factory_.GetWeakPtr(),
send_tab_to_self::SendTabToSelfEntry(*entry)),
&task_tracker_);
} else {
SendNotificationForSendTabToSelfPush(
send_tab_to_self::SendTabToSelfEntry(*entry),
favicon_base::FaviconRawBitmapResult{});
}
}
void SharingService::ResetConnectionToSyncService() {
if (sync_service_ && sync_service_->HasObserver(this)) {
sync_service_->RemoveObserver(this);
}
sync_service_ = nullptr;
}
void SharingService::Shutdown() {
// Avoid dangling `raw_ptr`s by explicitly resetting/destroying early fields
// and objects that maintain `raw_ptr`s to things owned by other services.
ResetConnectionToSyncService();
sharing_device_registration_.reset();
}
void SharingService::OnSyncShutdown(syncer::SyncService* sync) {
ResetConnectionToSyncService();
}
void SharingService::OnStateChanged(syncer::SyncService* sync) {
if (IsSyncEnabledForSharing(sync_service_) && state_ == State::DISABLED) {
state_ = State::REGISTERING;
RegisterDevice();
} else if (IsSyncDisabledForSharing(sync_service_) &&
state_ == State::ACTIVE) {
state_ = State::UNREGISTERING;
fcm_handler_->StopListening();
UnregisterDevice();
}
}
void SharingService::RegisterDevice() {
sharing_device_registration_->RegisterDevice(base::BindOnce(
&SharingService::OnDeviceRegistered, weak_ptr_factory_.GetWeakPtr()));
}
void SharingService::RegisterDeviceInTesting(
std::set<sync_pb::SharingSpecificFields_EnabledFeatures> enabled_features,
SharingDeviceRegistration::RegistrationCallback callback) {
sharing_device_registration_->SetEnabledFeaturesForTesting(
std::move(enabled_features));
sharing_device_registration_->RegisterDevice(std::move(callback));
}
void SharingService::UnregisterDevice() {
sharing_device_registration_->UnregisterDevice(base::BindOnce(
&SharingService::OnDeviceUnregistered, weak_ptr_factory_.GetWeakPtr()));
message_sender_->ClearPendingMessages();
}
void SharingService::OnDeviceRegistered(
SharingDeviceRegistrationResult result) {
switch (result) {
case SharingDeviceRegistrationResult::kSuccess:
backoff_entry_.InformOfRequest(true);
if (state_ == State::REGISTERING) {
if (IsSyncEnabledForSharing(sync_service_)) {
state_ = State::ACTIVE;
fcm_handler_->StartListening();
} else if (IsSyncDisabledForSharing(sync_service_)) {
// In case sync is disabled during registration, unregister it.
state_ = State::UNREGISTERING;
UnregisterDevice();
}
}
break;
case SharingDeviceRegistrationResult::kFcmTransientError:
case SharingDeviceRegistrationResult::kSyncServiceError:
backoff_entry_.InformOfRequest(false);
// Transient error - try again after a delay.
LOG(ERROR) << "Device registration failed with transient error";
task_runner_->PostDelayedTask(
FROM_HERE,
base::BindOnce(&SharingService::RegisterDevice,
weak_ptr_factory_.GetWeakPtr()),
backoff_entry_.GetTimeUntilRelease());
break;
case SharingDeviceRegistrationResult::kEncryptionError:
case SharingDeviceRegistrationResult::kFcmFatalError:
case SharingDeviceRegistrationResult::kInternalError:
backoff_entry_.InformOfRequest(false);
// No need to bother retrying in the case of one of fatal errors.
LOG(ERROR) << "Device registration failed with fatal error";
break;
case SharingDeviceRegistrationResult::kDeviceNotRegistered:
// Register device cannot return kDeviceNotRegistered.
NOTREACHED();
}
}
void SharingService::OnDeviceUnregistered(
SharingDeviceRegistrationResult result) {
if (IsSyncEnabledForSharing(sync_service_)) {
// In case sync is enabled during un-registration, register it.
state_ = State::REGISTERING;
RegisterDevice();
} else {
state_ = State::DISABLED;
}
switch (result) {
case SharingDeviceRegistrationResult::kSuccess:
// Successfully unregistered, no-op
break;
case SharingDeviceRegistrationResult::kFcmTransientError:
case SharingDeviceRegistrationResult::kSyncServiceError:
LOG(ERROR) << "Device un-registration failed with transient error";
break;
case SharingDeviceRegistrationResult::kEncryptionError:
case SharingDeviceRegistrationResult::kFcmFatalError:
case SharingDeviceRegistrationResult::kInternalError:
LOG(ERROR) << "Device un-registration failed with fatal error";
break;
case SharingDeviceRegistrationResult::kDeviceNotRegistered:
// Device has not been registered, no-op.
break;
}
}
void SharingService::SendNotificationForSendTabToSelfPush(
const send_tab_to_self::SendTabToSelfEntry& entry,
const favicon_base::FaviconRawBitmapResult& result) {
std::optional<SharingTargetDeviceInfo> target_device_info =
GetDeviceByGuid(entry.GetTargetDeviceSyncCacheGuid());
sync_pb::UnencryptedSharingMessage sharing_message;
sync_pb::SendTabToSelfPush* push_notification_entry =
sharing_message.mutable_send_tab_message();
std::string title = l10n_util::GetStringFUTF8(
IDS_SEND_TAB_PUSH_NOTIFICATION_TITLE_USER_GIVEN_DEVICE_NAME,
base::UTF8ToUTF16(entry.GetDeviceName()));
std::string body = l10n_util::GetStringFUTF8(
IDS_SEND_TAB_PUSH_NOTIFICATION_BODY, base::UTF8ToUTF16(entry.GetTitle()),
base::UTF8ToUTF16(entry.GetURL().host()));
push_notification_entry->set_title(title);
push_notification_entry->set_text(body);
push_notification_entry->set_destination_url(entry.GetURL().spec());
push_notification_entry->set_placeholder_title(l10n_util::GetStringUTF8(
IDS_SEND_TAB_PUSH_NOTIFICATION_PLACEHOLDER_TITLE));
push_notification_entry->set_placeholder_body(l10n_util::GetStringUTF8(
IDS_SEND_TAB_PUSH_NOTIFICATION_PLACEHOLDER_BODY));
push_notification_entry->set_entry_unique_guid(entry.GetGUID());
auto* icon = push_notification_entry->add_icon();
icon->set_url(result.icon_url.spec());
SendUnencryptedMessageToDevice(target_device_info.value(),
std::move(sharing_message),
/*callback=*/base::DoNothing());
}