[go: nahoru, domu]

blob: a0ab24927e49a6407d0a4d1b5a25a47f1a9e74f6 [file] [log] [blame]
// Copyright 2018 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 "ash/services/multidevice_setup/eligible_host_devices_provider_impl.h"
#include "ash/components/multidevice/software_feature.h"
#include "ash/components/multidevice/software_feature_state.h"
#include "ash/constants/ash_features.h"
#include "base/feature_list.h"
#include "base/memory/ptr_util.h"
#include "base/time/time.h"
namespace ash {
namespace multidevice_setup {
// static
constexpr base::TimeDelta
EligibleHostDevicesProviderImpl::kInactiveDeviceThresholdInDays;
// static
EligibleHostDevicesProviderImpl::Factory*
EligibleHostDevicesProviderImpl::Factory::test_factory_ = nullptr;
// static
std::unique_ptr<EligibleHostDevicesProvider>
EligibleHostDevicesProviderImpl::Factory::Create(
device_sync::DeviceSyncClient* device_sync_client) {
if (test_factory_)
return test_factory_->CreateInstance(device_sync_client);
return base::WrapUnique(
new EligibleHostDevicesProviderImpl(device_sync_client));
}
// static
void EligibleHostDevicesProviderImpl::Factory::SetFactoryForTesting(
Factory* factory) {
test_factory_ = factory;
}
EligibleHostDevicesProviderImpl::Factory::~Factory() = default;
EligibleHostDevicesProviderImpl::EligibleHostDevicesProviderImpl(
device_sync::DeviceSyncClient* device_sync_client)
: device_sync_client_(device_sync_client) {
device_sync_client_->AddObserver(this);
UpdateEligibleDevicesSet();
}
EligibleHostDevicesProviderImpl::~EligibleHostDevicesProviderImpl() {
device_sync_client_->RemoveObserver(this);
}
multidevice::RemoteDeviceRefList
EligibleHostDevicesProviderImpl::GetEligibleHostDevices() const {
// When enabled, this is equivalent to GetEligibleActiveHostDevices() without
// the connectivity data.
// TODO(https://crbug.com/1229876): Consolidate GetEligibleHostDevices() and
// GetEligibleActiveHostDevices().
if (base::FeatureList::IsEnabled(
features::kCryptAuthV2AlwaysUseActiveEligibleHosts)) {
multidevice::RemoteDeviceRefList eligible_active_devices;
for (const auto& device : eligible_active_devices_from_last_sync_) {
eligible_active_devices.push_back(device.remote_device);
}
return eligible_active_devices;
}
return eligible_devices_from_last_sync_;
}
multidevice::DeviceWithConnectivityStatusList
EligibleHostDevicesProviderImpl::GetEligibleActiveHostDevices() const {
return eligible_active_devices_from_last_sync_;
}
void EligibleHostDevicesProviderImpl::OnNewDevicesSynced() {
UpdateEligibleDevicesSet();
}
void EligibleHostDevicesProviderImpl::UpdateEligibleDevicesSet() {
eligible_devices_from_last_sync_.clear();
for (const auto& remote_device : device_sync_client_->GetSyncedDevices()) {
multidevice::SoftwareFeatureState host_state =
remote_device.GetSoftwareFeatureState(
multidevice::SoftwareFeature::kBetterTogetherHost);
if (host_state == multidevice::SoftwareFeatureState::kSupported ||
host_state == multidevice::SoftwareFeatureState::kEnabled) {
eligible_devices_from_last_sync_.push_back(remote_device);
}
}
// Sort from most-recently-updated to least-recently-updated. The timestamp
// used is provided by the back-end and indicates the last time at which the
// device's metadata was updated on the server. Note that this does not
// provide us with the last time that a user actually used this device, but it
// is a good estimate.
std::sort(eligible_devices_from_last_sync_.begin(),
eligible_devices_from_last_sync_.end(),
[](const auto& first_device, const auto& second_device) {
return first_device.last_update_time_millis() >
second_device.last_update_time_millis();
});
eligible_active_devices_from_last_sync_.clear();
for (const auto& remote_device : eligible_devices_from_last_sync_) {
eligible_active_devices_from_last_sync_.push_back(
multidevice::DeviceWithConnectivityStatus(
remote_device,
cryptauthv2::ConnectivityStatus::UNKNOWN_CONNECTIVITY));
}
if (base::FeatureList::IsEnabled(
features::kCryptAuthV2DeviceActivityStatus)) {
device_sync_client_->GetDevicesActivityStatus(base::BindOnce(
&EligibleHostDevicesProviderImpl::OnGetDevicesActivityStatus,
base::Unretained(this)));
}
}
void EligibleHostDevicesProviderImpl::OnGetDevicesActivityStatus(
device_sync::mojom::NetworkRequestResult network_result,
absl::optional<std::vector<device_sync::mojom::DeviceActivityStatusPtr>>
devices_activity_status_optional) {
if (network_result != device_sync::mojom::NetworkRequestResult::kSuccess ||
!devices_activity_status_optional) {
return;
}
base::flat_map<std::string, device_sync::mojom::DeviceActivityStatusPtr>
id_to_activity_status_map;
for (device_sync::mojom::DeviceActivityStatusPtr& device_activity_status_ptr :
*devices_activity_status_optional) {
id_to_activity_status_map.insert({device_activity_status_ptr->device_id,
std::move(device_activity_status_ptr)});
}
// Remove inactive devices. A device is inactive if it has a
// last_activity_time or last_update_time before some defined threshold.
base::Time now = base::Time::Now();
eligible_active_devices_from_last_sync_.erase(
std::remove_if(
eligible_active_devices_from_last_sync_.begin(),
eligible_active_devices_from_last_sync_.end(),
[&id_to_activity_status_map,
&now](const multidevice::DeviceWithConnectivityStatus& device) {
auto it = id_to_activity_status_map.find(
device.remote_device.instance_id());
if (it == id_to_activity_status_map.end()) {
return false;
}
// Note: Do not filter out devices if the last activity time was not
// set by the server, as indicated by a trivial base::Time value.
base::Time last_activity_time =
std::get<1>(*it)->last_activity_time;
if (!last_activity_time.is_null() &&
now - last_activity_time > kInactiveDeviceThresholdInDays) {
return true;
}
// Note: Do not filter out devices if the last update time was not
// set by the server, as indicated by a trivial base::Time value.
// Note: This |last_update_time| is from GetDevicesActivityStatus,
// not from the RemoteDevice; they track different events.
base::Time last_update_time = std::get<1>(*it)->last_update_time;
return !last_update_time.is_null() &&
now - last_update_time > kInactiveDeviceThresholdInDays;
}),
eligible_active_devices_from_last_sync_.end());
// Sort the list preferring online devices (if flag is enabled), then last
// activity time, then the last time the device enrolled or uploaded encrypted
// metadata to the CryptAuth server (GetDevicesActivityStatus's
// |last_update_time|), then last time a feature bit was flipped for the
// device on the CryptAuth server (RemoteDevice's |last_update_time_millis|).
std::sort(
eligible_active_devices_from_last_sync_.begin(),
eligible_active_devices_from_last_sync_.end(),
[&id_to_activity_status_map](const auto& first_device,
const auto& second_device) {
auto it1 = id_to_activity_status_map.find(
first_device.remote_device.instance_id());
auto it2 = id_to_activity_status_map.find(
second_device.remote_device.instance_id());
if (it1 == id_to_activity_status_map.end() &&
it2 == id_to_activity_status_map.end()) {
return first_device.remote_device.last_update_time_millis() >
second_device.remote_device.last_update_time_millis();
}
if (it1 == id_to_activity_status_map.end()) {
return false;
}
if (it2 == id_to_activity_status_map.end()) {
return true;
}
const device_sync::mojom::DeviceActivityStatusPtr&
first_activity_status = std::get<1>(*it1);
const device_sync::mojom::DeviceActivityStatusPtr&
second_activity_status = std::get<1>(*it2);
if (base::FeatureList::IsEnabled(
features::kCryptAuthV2DeviceActivityStatusUseConnectivity)) {
if (first_activity_status->connectivity_status ==
cryptauthv2::ConnectivityStatus::ONLINE &&
second_activity_status->connectivity_status !=
cryptauthv2::ConnectivityStatus::ONLINE) {
return true;
}
if (second_activity_status->connectivity_status ==
cryptauthv2::ConnectivityStatus::ONLINE &&
first_activity_status->connectivity_status !=
cryptauthv2::ConnectivityStatus::ONLINE) {
return false;
}
}
if (first_activity_status->last_activity_time !=
second_activity_status->last_activity_time) {
return first_activity_status->last_activity_time >
second_activity_status->last_activity_time;
}
// Note: This |last_update_time| is from GetDevicesActivityStatus, not
// from the RemoteDevice; they track different events.
if (first_activity_status->last_update_time !=
second_activity_status->last_update_time) {
return first_activity_status->last_update_time >
second_activity_status->last_update_time;
}
return first_device.remote_device.last_update_time_millis() >
second_device.remote_device.last_update_time_millis();
});
if (base::FeatureList::IsEnabled(
features::kCryptAuthV2DeviceActivityStatusUseConnectivity)) {
for (auto& host_device : eligible_active_devices_from_last_sync_) {
auto it = id_to_activity_status_map.find(
host_device.remote_device.instance_id());
if (it == id_to_activity_status_map.end()) {
continue;
}
host_device.connectivity_status = std::get<1>(*it)->connectivity_status;
}
}
// Remove devices with the same non-trivial |last_activity_time|, keeping only
// the device with the most recent |last_update_time| or
// |last_update_time_millis|. Note: |eligible_active_devices_from_last_sync_|
// is already sorted in the preferred order.
if (base::FeatureList::IsEnabled(
features::kCryptAuthV2DedupDeviceLastActivityTime)) {
base::flat_set<base::Time> set_of_same_last_activity_time;
eligible_active_devices_from_last_sync_.erase(
std::remove_if(
eligible_active_devices_from_last_sync_.begin(),
eligible_active_devices_from_last_sync_.end(),
[&id_to_activity_status_map, &set_of_same_last_activity_time](
const multidevice::DeviceWithConnectivityStatus& device) {
auto it = id_to_activity_status_map.find(
device.remote_device.instance_id());
if (it == id_to_activity_status_map.end()) {
return false;
}
base::Time last_activity_time =
std::get<1>(*it)->last_activity_time;
// Do not filter out devices if the last activity time was not set
// by the server, as indicated by a trivial base::Time value.
if (last_activity_time.is_null()) {
return false;
}
if (set_of_same_last_activity_time.contains(last_activity_time)) {
return true;
}
set_of_same_last_activity_time.insert(last_activity_time);
return false;
}),
eligible_active_devices_from_last_sync_.end());
}
}
} // namespace multidevice_setup
} // namespace ash