blob: 15b7213fd3aff8b6e0ef631bf24cb12b67633aef [file] [log] [blame]
// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <map>
#include <memory>
#include <utility>
#include <vector>
#include "base/feature_list.h"
#include "base/metrics/field_trial_params.h"
#include "base/ranges/algorithm.h"
#include "base/trace_event/trace_event.h"
#include "components/browser_sync/active_devices_provider_impl.h"
#include "components/browser_sync/browser_sync_switches.h"
#include "components/sync/base/model_type.h"
namespace browser_sync {
ActiveDevicesProviderImpl::ActiveDevicesProviderImpl(
syncer::DeviceInfoTracker* device_info_tracker,
base::Clock* clock)
: device_info_tracker_(device_info_tracker), clock_(clock) {
DCHECK(device_info_tracker_);
device_info_tracker_observation_.Observe(device_info_tracker_);
}
ActiveDevicesProviderImpl::~ActiveDevicesProviderImpl() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(callback_.is_null());
}
syncer::ActiveDevicesInvalidationInfo
ActiveDevicesProviderImpl::CalculateInvalidationInfo(
const std::string& local_cache_guid) const {
TRACE_EVENT0("ui", "ActiveDevicesProviderImpl::CalculateInvalidationInfo");
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
const std::vector<const syncer::DeviceInfo*> active_devices =
GetActiveDevicesSortedByUpdateTime();
if (active_devices.empty()) {
// This may happen if the engine is not initialized yet. In other cases,
// |active_devices| must contain at least the local device.
return syncer::ActiveDevicesInvalidationInfo::CreateUninitialized();
}
std::vector<std::string> all_fcm_registration_tokens;
// List of interested data types for all other clients.
syncer::ModelTypeSet all_interested_data_types;
syncer::ModelTypeSet old_invalidations_interested_data_types;
// FCM registration tokens with corresponding interested data types for all
// the clients with enabled sync standalone invalidations.
std::map<std::string, syncer::ModelTypeSet>
fcm_token_and_interested_data_types;
for (const syncer::DeviceInfo* device : active_devices) {
if (!local_cache_guid.empty() && device->guid() == local_cache_guid) {
continue;
}
all_interested_data_types.PutAll(device->interested_data_types());
if (!device->fcm_registration_token().empty()) {
// If there is a duplicate FCM registration token, use the latest one. To
// achieve this, rely on sorted |active_devices| by update time. Two
// DeviceInfo entities can have the same FCM registration token if the
// sync engine was reset without signout.
fcm_token_and_interested_data_types[device->fcm_registration_token()] =
device->interested_data_types();
if (base::FeatureList::IsEnabled(
switches::kSyncUseFCMRegistrationTokensList)) {
all_fcm_registration_tokens.push_back(device->fcm_registration_token());
}
} else if (!device->interested_data_types().empty()) {
// An empty FCM registration token may be set for old clients, and for
// modern clients supporting sync standalone invalidatoins if there was an
// error during FCM registration. This does not matter in this case since
// the error case should be rare, and in the worst case the
// |single_client_old_invalidations| flag will not be provided (and this
// is just an optimization flag).
old_invalidations_interested_data_types.PutAll(
device->interested_data_types());
} else {
// For old clients which do not support interested data types assume that
// they are subscribed to all data types.
old_invalidations_interested_data_types.PutAll(syncer::ProtocolTypes());
}
}
// Do not send tokens if the list of active devices is huge. This is similar
// to the case when the client doesn't know about other devices, so return an
// empty list. Otherwise the client would return only a part of all active
// clients and other clients might miss an invalidation.
if (all_fcm_registration_tokens.size() >
static_cast<size_t>(
switches::kSyncFCMRegistrationTokensListMaxSize.Get())) {
all_fcm_registration_tokens.clear();
}
TRACE_EVENT0("ui",
"ActiveDevicesProviderImpl::CalculateInvalidationInfo() => "
"ActiveDevicesInvalidationInfo::Create");
return syncer::ActiveDevicesInvalidationInfo::Create(
std::move(all_fcm_registration_tokens), all_interested_data_types,
std::move(fcm_token_and_interested_data_types),
old_invalidations_interested_data_types);
}
void ActiveDevicesProviderImpl::SetActiveDevicesChangedCallback(
ActiveDevicesChangedCallback callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// The |callback_| must not be replaced with another non-null |callback|.
DCHECK(callback_.is_null() || callback.is_null());
callback_ = std::move(callback);
}
void ActiveDevicesProviderImpl::OnDeviceInfoChange() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (callback_) {
callback_.Run();
}
}
std::vector<const syncer::DeviceInfo*>
ActiveDevicesProviderImpl::GetActiveDevicesSortedByUpdateTime() const {
std::vector<const syncer::DeviceInfo*> all_devices =
device_info_tracker_->GetAllDeviceInfo();
if (base::FeatureList::IsEnabled(
switches::kSyncFilterOutInactiveDevicesForSingleClient)) {
std::erase_if(all_devices, [this](const syncer::DeviceInfo* device) {
const base::Time expected_expiration_time =
device->last_updated_timestamp() + device->pulse_interval() +
switches::kSyncActiveDeviceMargin.Get();
// If the device's expiration time hasn't been reached, then
// it is considered active device.
return expected_expiration_time <= clock_->Now();
});
}
base::ranges::sort(all_devices, [](const syncer::DeviceInfo* left_device,
const syncer::DeviceInfo* right_device) {
return left_device->last_updated_timestamp() <
right_device->last_updated_timestamp();
});
return all_devices;
}
} // namespace browser_sync