910e62b5创建于 1月15日历史提交
// 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 "components/browser_sync/active_devices_provider_impl.h"

#include <algorithm>
#include <map>
#include <memory>
#include <utility>
#include <vector>

#include "base/command_line.h"
#include "base/time/time.h"
#include "base/trace_event/trace_event.h"
#include "components/browser_sync/browser_sync_switches.h"
#include "components/sync/base/data_type.h"
#include "components/sync/engine/active_devices_invalidation_info.h"

namespace browser_sync {

// Max size of FCM registration tokens list used for sync invalidation
// optimization. If the number of active devices having FCM registration tokens
// is higher, then the resulting list will be empty meaning unknown FCM
// registration tokens.
constexpr size_t kSyncFCMRegistrationTokensListMaxSize = 5;

// An additional threshold to consider devices as active. It extends device's
// pulse interval to mitigate possible latency after DeviceInfo commit.
constexpr base::TimeDelta kSyncActiveDeviceMargin = base::Days(7);

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_);

  if (base::CommandLine::ForCurrentProcess()->HasSwitch(
          switches::kDisableSyncInvalidationOptimizations)) {
    return syncer::ActiveDevicesInvalidationInfo::CreateUninitialized();
  }

  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::DataTypeSet all_interested_data_types;

  syncer::DataTypeSet 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::DataTypeSet>
      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();
      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() >
      kSyncFCMRegistrationTokensListMaxSize) {
    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*> device_infos =
      device_info_tracker_->GetAllDeviceInfo();

  std::erase_if(device_infos, [this](const syncer::DeviceInfo* device) {
    const base::Time expected_expiration_time =
        device->last_updated_timestamp() + device->pulse_interval() +
        kSyncActiveDeviceMargin;
    // If the device's expiration time hasn't been reached, then it is
    // considered active device. Devices without chrome version are always
    // considered active. Note that all devices still have 56 days expiration
    // time (see DeviceInfoSyncBridge) and stale devices won't stay around
    // indefinitely .
    return !device->chrome_version().empty() &&
           expected_expiration_time <= clock_->Now();
  });

  std::ranges::sort(device_infos, [](const syncer::DeviceInfo* left_device,
                                     const syncer::DeviceInfo* right_device) {
    return left_device->last_updated_timestamp() <
           right_device->last_updated_timestamp();
  });

  return device_infos;
}

}  // namespace browser_sync