blob: 7c48318574389a2ea38cc3bef23582e74984536b [file] [log] [blame]
// Copyright 2019 The Chromium Authors. All rights reserved.
// 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_service.h"
#include <algorithm>
#include <map>
#include <memory>
#include <unordered_set>
#include "base/bind.h"
#include "base/feature_list.h"
#include "base/guid.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/post_task.h"
#include "base/time/time.h"
#include "chrome/browser/sharing/click_to_call/feature.h"
#include "chrome/browser/sharing/features.h"
#include "chrome/browser/sharing/shared_clipboard/feature_flags.h"
#include "chrome/browser/sharing/sharing_constants.h"
#include "chrome/browser/sharing/sharing_device_registration.h"
#include "chrome/browser/sharing/sharing_device_registration_result.h"
#include "chrome/browser/sharing/sharing_fcm_handler.h"
#include "chrome/browser/sharing/sharing_fcm_sender.h"
#include "chrome/browser/sharing/sharing_message_handler.h"
#include "chrome/browser/sharing/sharing_metrics.h"
#include "chrome/browser/sharing/sharing_sync_preference.h"
#include "chrome/browser/sharing/vapid_key_manager.h"
#include "components/gcm_driver/crypto/gcm_encryption_provider.h"
#include "components/gcm_driver/gcm_driver.h"
#include "components/sync/driver/sync_service.h"
#include "components/sync_device_info/device_info.h"
#include "components/sync_device_info/local_device_info_provider.h"
#include "content/public/browser/browser_task_traits.h"
SharingService::SharingService(
std::unique_ptr<SharingSyncPreference> sync_prefs,
std::unique_ptr<VapidKeyManager> vapid_key_manager,
std::unique_ptr<SharingDeviceRegistration> sharing_device_registration,
std::unique_ptr<SharingFCMSender> fcm_sender,
std::unique_ptr<SharingFCMHandler> fcm_handler,
gcm::GCMDriver* gcm_driver,
syncer::DeviceInfoTracker* device_info_tracker,
syncer::LocalDeviceInfoProvider* local_device_info_provider,
syncer::SyncService* sync_service,
NotificationDisplayService* notification_display_service)
: sync_prefs_(std::move(sync_prefs)),
vapid_key_manager_(std::move(vapid_key_manager)),
sharing_device_registration_(std::move(sharing_device_registration)),
fcm_sender_(std::move(fcm_sender)),
fcm_handler_(std::move(fcm_handler)),
device_info_tracker_(device_info_tracker),
local_device_info_provider_(local_device_info_provider),
sync_service_(sync_service),
backoff_entry_(&kRetryBackoffPolicy),
state_(State::DISABLED),
is_observing_device_info_tracker_(false) {
// Remove old encryption info with empty authrozed_entity to avoid DCHECK.
// See http://crbug/987591
if (gcm_driver) {
gcm::GCMEncryptionProvider* encryption_provider =
gcm_driver->GetEncryptionProviderInternal();
if (encryption_provider) {
encryption_provider->RemoveEncryptionInfo(
kSharingFCMAppID, /*authorized_entity=*/std::string(),
base::DoNothing());
}
}
// Initialize sharing handlers.
fcm_handler_->AddSharingHandler(
chrome_browser_sharing::SharingMessage::kAckMessage,
&ack_message_handler_);
ack_message_handler_.AddObserver(this);
fcm_handler_->AddSharingHandler(
chrome_browser_sharing::SharingMessage::kPingMessage,
&ping_message_handler_);
#if defined(OS_ANDROID)
if (base::FeatureList::IsEnabled(kClickToCallReceiver)) {
fcm_handler_->AddSharingHandler(
chrome_browser_sharing::SharingMessage::kClickToCallMessage,
sharing_service_proxy_android_.click_to_call_message_handler());
}
shared_clipboard_message_handler_ =
std::make_unique<SharedClipboardMessageHandlerAndroid>(this);
#else
shared_clipboard_message_handler_ =
std::make_unique<SharedClipboardMessageHandlerDesktop>(
this, notification_display_service);
#endif // defined(OS_ANDROID)
if (base::FeatureList::IsEnabled(kSharedClipboardReceiver)) {
fcm_handler_->AddSharingHandler(
chrome_browser_sharing::SharingMessage::kSharedClipboardMessage,
shared_clipboard_message_handler_.get());
}
// 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 (IsSyncDisabled()) {
// state_ is kept as State::DISABLED as SharingService has never registered,
// and only doing clean up via UnregisterDevice().
UnregisterDevice();
}
}
SharingService::~SharingService() {
ack_message_handler_.RemoveObserver(this);
if (sync_service_ && sync_service_->HasObserver(this))
sync_service_->RemoveObserver(this);
}
std::unique_ptr<syncer::DeviceInfo> SharingService::GetDeviceByGuid(
const std::string& guid) const {
if (!IsSyncEnabled())
return nullptr;
return device_info_tracker_->GetDeviceInfo(guid);
}
std::vector<std::unique_ptr<syncer::DeviceInfo>>
SharingService::GetDeviceCandidates(
sync_pb::SharingSpecificFields::EnabledFeatures required_feature) const {
std::vector<std::unique_ptr<syncer::DeviceInfo>> device_candidates;
std::vector<std::unique_ptr<syncer::DeviceInfo>> all_devices =
device_info_tracker_->GetAllDeviceInfo();
const syncer::DeviceInfo* local_device_info =
local_device_info_provider_->GetLocalDeviceInfo();
if (IsSyncDisabled() || all_devices.empty() || !local_device_info)
return device_candidates;
const base::Time min_updated_time = base::Time::Now() - kDeviceExpiration;
// Sort the DeviceInfo vector so the most recently modified devices are first.
std::sort(all_devices.begin(), all_devices.end(),
[](const auto& device1, const auto& device2) {
return device1->last_updated_timestamp() >
device2->last_updated_timestamp();
});
std::unordered_set<std::string> device_names;
for (auto& device : all_devices) {
// If the current device is considered expired for our purposes, stop here
// since the next devices in the vector are at least as expired than this
// one.
if (device->last_updated_timestamp() < min_updated_time)
break;
if (local_device_info->client_name() == device->client_name())
continue;
base::Optional<syncer::DeviceInfo::SharingInfo> sharing_info =
sync_prefs_->GetSharingInfo(device.get());
if (!sharing_info ||
!sharing_info->enabled_features.count(required_feature)) {
continue;
}
// Only insert the first occurrence of each device name.
auto inserted = device_names.insert(device->client_name());
if (inserted.second)
device_candidates.push_back(std::move(device));
}
// TODO(knollr): Remove devices from |sync_prefs_| that are in
// |synced_devices| but not in |all_devices|?
return device_candidates;
}
void SharingService::AddDeviceCandidatesInitializedObserver(
base::OnceClosure callback) {
if (IsSyncDisabled()) {
std::move(callback).Run();
return;
}
bool is_device_info_tracker_ready = device_info_tracker_->IsSyncing();
bool is_local_device_info_ready =
local_device_info_provider_->GetLocalDeviceInfo();
if (is_device_info_tracker_ready && is_local_device_info_ready) {
std::move(callback).Run();
return;
}
device_candidates_initialized_callbacks_.emplace_back(std::move(callback));
if (!is_device_info_tracker_ready && !is_observing_device_info_tracker_) {
device_info_tracker_->AddObserver(this);
is_observing_device_info_tracker_ = true;
}
if (!is_local_device_info_ready && !local_device_info_ready_subscription_) {
local_device_info_ready_subscription_ =
local_device_info_provider_->RegisterOnInitializedCallback(
base::BindRepeating(&SharingService::OnDeviceInfoChange,
weak_ptr_factory_.GetWeakPtr()));
}
}
void SharingService::SendMessageToDevice(
const std::string& device_guid,
base::TimeDelta time_to_live,
chrome_browser_sharing::SharingMessage message,
SendMessageCallback callback) {
std::string message_guid = base::GenerateGUID();
send_message_callbacks_.emplace(message_guid, std::move(callback));
base::PostDelayedTask(
FROM_HERE, {base::TaskPriority::USER_VISIBLE, content::BrowserThread::UI},
base::BindOnce(&SharingService::InvokeSendMessageCallback,
weak_ptr_factory_.GetWeakPtr(), message_guid,
SharingSendMessageResult::kAckTimeout),
kSendMessageTimeout);
base::Optional<syncer::DeviceInfo::SharingInfo> sharing_info =
sync_prefs_->GetSharingInfo(device_guid);
if (!sharing_info) {
InvokeSendMessageCallback(message_guid,
SharingSendMessageResult::kDeviceNotFound);
return;
}
fcm_sender_->SendMessageToDevice(
std::move(*sharing_info), time_to_live, std::move(message),
base::BindOnce(&SharingService::OnMessageSent,
weak_ptr_factory_.GetWeakPtr(), base::TimeTicks::Now(),
message_guid));
}
void SharingService::SetDeviceInfoTrackerForTesting(
syncer::DeviceInfoTracker* tracker) {
device_info_tracker_ = tracker;
}
void SharingService::OnMessageSent(base::TimeTicks start_time,
const std::string& message_guid,
SharingSendMessageResult result,
base::Optional<std::string> message_id) {
if (result != SharingSendMessageResult::kSuccessful) {
InvokeSendMessageCallback(message_guid, result);
return;
}
send_message_times_.emplace(*message_id, start_time);
message_guids_.emplace(*message_id, message_guid);
}
void SharingService::OnAckReceived(const std::string& message_id) {
auto times_iter = send_message_times_.find(message_id);
if (times_iter != send_message_times_.end()) {
LogSharingMessageAckTime(base::TimeTicks::Now() - times_iter->second);
send_message_times_.erase(times_iter);
}
auto iter = message_guids_.find(message_id);
if (iter == message_guids_.end())
return;
std::string message_guid = std::move(iter->second);
message_guids_.erase(iter);
InvokeSendMessageCallback(message_guid,
SharingSendMessageResult::kSuccessful);
}
void SharingService::InvokeSendMessageCallback(
const std::string& message_guid,
SharingSendMessageResult result) {
auto iter = send_message_callbacks_.find(message_guid);
if (iter == send_message_callbacks_.end())
return;
SendMessageCallback callback = std::move(iter->second);
send_message_callbacks_.erase(iter);
std::move(callback).Run(result);
LogSendSharingMessageResult(result);
}
void SharingService::OnDeviceInfoChange() {
if (!device_info_tracker_->IsSyncing() ||
!local_device_info_provider_->GetLocalDeviceInfo()) {
return;
}
device_info_tracker_->RemoveObserver(this);
is_observing_device_info_tracker_ = false;
local_device_info_ready_subscription_.reset();
for (base::OnceClosure& callback : device_candidates_initialized_callbacks_) {
std::move(callback).Run();
}
device_candidates_initialized_callbacks_.clear();
}
void SharingService::RegisterHandler(
chrome_browser_sharing::SharingMessage::PayloadCase payload_type,
SharingMessageHandler* handler) {}
SharingService::State SharingService::GetState() const {
return state_;
}
void SharingService::OnSyncShutdown(syncer::SyncService* sync) {
if (sync_service_ && sync_service_->HasObserver(this))
sync_service_->RemoveObserver(this);
sync_service_ = nullptr;
}
void SharingService::OnStateChanged(syncer::SyncService* sync) {
if (IsSyncEnabled()) {
if (base::FeatureList::IsEnabled(kSharingDeviceRegistration)) {
if (state_ == State::DISABLED) {
state_ = State::REGISTERING;
RegisterDevice();
}
} else {
// Unregister the device once and stop listening for sync state changes.
// If feature is turned back on, Chrome needs to be restarted.
if (sync_service_ && sync_service_->HasObserver(this))
sync_service_->RemoveObserver(this);
UnregisterDevice();
}
} else if (IsSyncDisabled() && state_ == State::ACTIVE) {
state_ = State::UNREGISTERING;
fcm_handler_->StopListening();
sync_prefs_->ClearVapidKeyChangeObserver();
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_feautres,
SharingDeviceRegistration::RegistrationCallback callback) {
sharing_device_registration_->SetEnabledFeaturesForTesting(
std::move(enabled_feautres));
sharing_device_registration_->RegisterDevice(std::move(callback));
}
void SharingService::UnregisterDevice() {
sharing_device_registration_->UnregisterDevice(base::BindOnce(
&SharingService::OnDeviceUnregistered, weak_ptr_factory_.GetWeakPtr()));
}
void SharingService::OnDeviceRegistered(
SharingDeviceRegistrationResult result) {
LogSharingRegistrationResult(result);
switch (result) {
case SharingDeviceRegistrationResult::kSuccess:
backoff_entry_.InformOfRequest(true);
if (state_ == State::REGISTERING) {
if (IsSyncEnabled()) {
state_ = State::ACTIVE;
fcm_handler_->StartListening();
// Listen for further VAPID key changes for re-registration.
// state_ is kept as State::ACTIVE during re-registration.
sync_prefs_->SetVapidKeyChangeObserver(base::BindRepeating(
&SharingService::RegisterDevice, weak_ptr_factory_.GetWeakPtr()));
} else if (IsSyncDisabled()) {
// In case sync is disabled during registration, unregister it.
state_ = State::UNREGISTERING;
UnregisterDevice();
}
}
// For registration as result of VAPID key change, state_ will be
// State::ACTIVE, and we don't need to start listeners.
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";
base::PostDelayedTask(
FROM_HERE,
{base::TaskPriority::BEST_EFFORT, content::BrowserThread::UI},
base::BindOnce(&SharingService::RegisterDevice,
weak_ptr_factory_.GetWeakPtr()),
backoff_entry_.GetTimeUntilRelease());
break;
case SharingDeviceRegistrationResult::kEncryptionError:
case SharingDeviceRegistrationResult::kFcmFatalError:
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) {
LogSharingUnegistrationResult(result);
if (IsSyncEnabled() &&
base::FeatureList::IsEnabled(kSharingDeviceRegistration)) {
// 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:
LOG(ERROR) << "Device un-registration failed with fatal error";
break;
case SharingDeviceRegistrationResult::kDeviceNotRegistered:
// Device has not been registered, no-op.
break;
}
}
bool SharingService::IsSyncEnabled() const {
return sync_service_ &&
sync_service_->GetTransportState() ==
syncer::SyncService::TransportState::ACTIVE &&
sync_service_->GetActiveDataTypes().Has(syncer::PREFERENCES);
}
SharingSyncPreference* SharingService::GetSyncPreferences() const {
return sync_prefs_.get();
}
bool SharingService::IsSyncDisabled() const {
// TODO(alexchau): Better way to make
// ClickToCallBrowserTest.ContextMenu_DevicesAvailable_SyncTurnedOff pass
// without unnecessarily checking SyncService::GetDisableReasons.
return sync_service_ &&
(sync_service_->GetTransportState() ==
syncer::SyncService::TransportState::DISABLED ||
(sync_service_->GetTransportState() ==
syncer::SyncService::TransportState::ACTIVE &&
!sync_service_->GetActiveDataTypes().Has(syncer::PREFERENCES)) ||
sync_service_->GetDisableReasons());
}