blob: 7a84f3657b4288eb6f5080fdba3d7ee70d336c7a [file] [log] [blame]
// Copyright 2020 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/nearby_sharing/contacts/nearby_share_contact_manager_impl.h"
#include <algorithm>
#include "base/memory/ptr_util.h"
#include "base/stl_util.h"
#include "base/time/time.h"
#include "chrome/browser/nearby_sharing/common/nearby_share_prefs.h"
#include "chrome/browser/nearby_sharing/contacts/nearby_share_contact_downloader.h"
#include "chrome/browser/nearby_sharing/contacts/nearby_share_contact_downloader_impl.h"
#include "chrome/browser/nearby_sharing/local_device_data/nearby_share_local_device_data_manager.h"
#include "chrome/browser/nearby_sharing/logging/logging.h"
#include "chrome/browser/nearby_sharing/proto/device_rpc.pb.h"
#include "chrome/browser/nearby_sharing/proto/rpc_resources.pb.h"
#include "chrome/browser/nearby_sharing/scheduling/nearby_share_scheduler.h"
#include "chrome/browser/nearby_sharing/scheduling/nearby_share_scheduler_factory.h"
#include "components/prefs/pref_service.h"
namespace {
constexpr base::TimeDelta kContactDownloadPeriod =
base::TimeDelta::FromHours(1);
constexpr base::TimeDelta kContactDownloadRpcTimeout =
base::TimeDelta::FromSeconds(60);
// Removes contact IDs from the allowlist if they are not in |contacts|.
std::set<std::string> RemoveNonexistentContactsFromAllowlist(
const std::set<std::string>& allowed_contact_ids,
const std::vector<nearbyshare::proto::ContactRecord>& contacts) {
std::set<std::string> new_allowed_contact_ids;
for (const nearbyshare::proto::ContactRecord& contact : contacts) {
if (base::Contains(allowed_contact_ids, contact.id()))
new_allowed_contact_ids.insert(contact.id());
}
return new_allowed_contact_ids;
}
// Converts a list of ContactRecord protos, along with the allowlist, into a
// list of Contact protos.
std::vector<nearbyshare::proto::Contact> ContactRecordsToContacts(
const std::set<std::string>& allowed_contact_ids,
const std::vector<nearbyshare::proto::ContactRecord>& contact_records) {
std::vector<nearbyshare::proto::Contact> contacts;
for (const auto& contact_record : contact_records) {
bool is_selected = base::Contains(allowed_contact_ids, contact_record.id());
for (const auto& identifier : contact_record.identifiers()) {
nearbyshare::proto::Contact contact;
contact.mutable_identifier()->CopyFrom(identifier);
contact.set_is_selected(is_selected);
contacts.push_back(contact);
}
}
return contacts;
}
} // namespace
// static
NearbyShareContactManagerImpl::Factory*
NearbyShareContactManagerImpl::Factory::test_factory_ = nullptr;
// static
std::unique_ptr<NearbyShareContactManager>
NearbyShareContactManagerImpl::Factory::Create(
PrefService* pref_service,
NearbyShareClientFactory* http_client_factory,
NearbyShareLocalDeviceDataManager* local_device_data_manager) {
if (test_factory_) {
return test_factory_->CreateInstance(pref_service, http_client_factory,
local_device_data_manager);
}
return base::WrapUnique(new NearbyShareContactManagerImpl(
pref_service, http_client_factory, local_device_data_manager));
}
// static
void NearbyShareContactManagerImpl::Factory::SetFactoryForTesting(
Factory* test_factory) {
test_factory_ = test_factory;
}
NearbyShareContactManagerImpl::Factory::~Factory() = default;
NearbyShareContactManagerImpl::NearbyShareContactManagerImpl(
PrefService* pref_service,
NearbyShareClientFactory* http_client_factory,
NearbyShareLocalDeviceDataManager* local_device_data_manager)
: pref_service_(pref_service),
http_client_factory_(http_client_factory),
local_device_data_manager_(local_device_data_manager),
contact_download_scheduler_(
NearbyShareSchedulerFactory::CreatePeriodicScheduler(
kContactDownloadPeriod,
/*retry_failures=*/true,
/*require_connectivity=*/true,
prefs::kNearbySharingSchedulerContactDownloadPrefName,
pref_service_,
base::BindRepeating(
&NearbyShareContactManagerImpl::OnContactsDownloadRequested,
base::Unretained(this)))),
contact_upload_scheduler_(
NearbyShareSchedulerFactory::CreateOnDemandScheduler(
/*retry_failures=*/true,
/*require_connectivity=*/true,
prefs::kNearbySharingSchedulerContactUploadPrefName,
pref_service_,
base::BindRepeating(
&NearbyShareContactManagerImpl::OnContactsUploadRequested,
base::Unretained(this)))) {}
NearbyShareContactManagerImpl::~NearbyShareContactManagerImpl() = default;
void NearbyShareContactManagerImpl::DownloadContacts(
bool only_download_if_changed) {
// A request for a full download always takes priority.
if (!only_download_if_changed)
only_download_if_changed_ = false;
contact_download_scheduler_->MakeImmediateRequest();
}
void NearbyShareContactManagerImpl::SetAllowedContacts(
const std::set<std::string>& allowed_contact_ids) {
// If the allowlist changed, re-upload contacts to Nearby server.
if (SetAllowlist(allowed_contact_ids))
contact_upload_scheduler_->MakeImmediateRequest();
}
void NearbyShareContactManagerImpl::OnStart() {
contact_download_scheduler_->Start();
contact_upload_scheduler_->Start();
}
void NearbyShareContactManagerImpl::OnStop() {
contact_download_scheduler_->Stop();
contact_upload_scheduler_->Stop();
}
std::set<std::string> NearbyShareContactManagerImpl::GetAllowedContacts()
const {
std::set<std::string> allowlist;
for (const base::Value& id :
pref_service_->Get(prefs::kNearbySharingAllowedContactsPrefName)
->GetList()) {
allowlist.insert(id.GetString());
}
return allowlist;
}
void NearbyShareContactManagerImpl::OnContactsDownloadRequested() {
NS_LOG(VERBOSE) << __func__ << ": Nearby Share contacts download requested.";
DCHECK(!contact_downloader_);
contact_downloader_ = NearbyShareContactDownloaderImpl::Factory::Create(
only_download_if_changed_, local_device_data_manager_->GetId(),
kContactDownloadRpcTimeout, http_client_factory_,
base::BindOnce(&NearbyShareContactManagerImpl::OnContactsDownloadSuccess,
base::Unretained(this)),
base::BindOnce(&NearbyShareContactManagerImpl::OnContactsDownloadFailure,
base::Unretained(this)));
contact_downloader_->Run();
}
void NearbyShareContactManagerImpl::OnContactsDownloadSuccess(
bool did_contacts_change_since_last_upload,
base::Optional<std::vector<nearbyshare::proto::ContactRecord>> contacts) {
contact_downloader_.reset();
NS_LOG(VERBOSE) << __func__ << ": Nearby Share contacts download succeeded."
<< "\n Did contacts change since last upload? "
<< (did_contacts_change_since_last_upload ? "Yes." : "No.")
<< "\n Were contacts returned? "
<< (contacts.has_value() ? "Yes." : "No.")
<< "\n Number of contacts returned: "
<< (contacts.has_value() ? contacts->size() : 0u);
if (contacts) {
// A complete list of contacts was returned. Do not download list again
// until contacts change or until explicitly requested.
only_download_if_changed_ = true;
// Remove contacts from the allowlist that are no longer in the contact
// list.
bool did_allowlist_change =
SetAllowlist(RemoveNonexistentContactsFromAllowlist(
GetAllowedContacts(), *contacts));
// Notify observers that the contact list was downloaded.
NotifyContactsDownloaded(GetAllowedContacts(), *contacts);
// Request a contacts upload if needed, or process an existing upload
// request now that we have the access to the full contacts list.
switch (upload_state_) {
case UploadState::kIdle:
if (did_contacts_change_since_last_upload || did_allowlist_change) {
contact_upload_scheduler_->MakeImmediateRequest();
}
break;
case UploadState::kWaitingForDownload:
StartContactsUpload(did_contacts_change_since_last_upload, *contacts);
break;
case UploadState::kInProgress:
// The current upload has a stale allowlist; request another upload.
if (did_allowlist_change) {
contact_upload_scheduler_->MakeImmediateRequest();
}
// NOTE: We have no way of knowing if the contact list has changed since
// we started our current upload--something that could only happen in a
// very narrow window of time; we only know if the list has changed
// since the last successful upload. We do not handle this edge case,
// instead relying on a subsequent (periodic) download to detect that
// the list needs to be re-uploaded.
break;
}
}
contact_download_scheduler_->HandleResult(/*success=*/true);
}
void NearbyShareContactManagerImpl::OnContactsDownloadFailure() {
contact_download_scheduler_->HandleResult(/*success=*/false);
}
void NearbyShareContactManagerImpl::OnContactsUploadRequested() {
DCHECK_EQ(UploadState::kIdle, upload_state_);
NS_LOG(VERBOSE) << __func__
<< ": Nearby Share contact upload requested. Waiting to "
<< "download full contact list.";
// Because the user's contact list is not persisted locally, we have to
// retrieve the full contact list ContactRecord protos from the server
// before uploading the list of Contact protos to the server.
upload_state_ = UploadState::kWaitingForDownload;
DownloadContacts(/*only_download_if_changed=*/false);
}
void NearbyShareContactManagerImpl::StartContactsUpload(
bool did_contacts_change_since_last_upload,
const std::vector<nearbyshare::proto::ContactRecord>& contacts) {
NS_LOG(VERBOSE) << __func__
<< ": Starting contacts upload to Nearby Share server.";
upload_state_ = UploadState::kInProgress;
local_device_data_manager_->UploadContacts(
ContactRecordsToContacts(GetAllowedContacts(), contacts),
base::BindOnce(&NearbyShareContactManagerImpl::OnContactsUploadFinished,
weak_ptr_factory_.GetWeakPtr(),
did_contacts_change_since_last_upload));
}
void NearbyShareContactManagerImpl::OnContactsUploadFinished(
bool did_contacts_change_since_last_upload,
bool success) {
NS_LOG(VERBOSE) << __func__ << ": Upload of contacts to Nearby Share server "
<< (success ? "succeeded." : "failed.")
<< " Did contacts change since last upload? "
<< (did_contacts_change_since_last_upload ? "Yes." : "No.");
if (success) {
NotifyContactsUploaded(did_contacts_change_since_last_upload);
}
upload_state_ = UploadState::kIdle;
contact_upload_scheduler_->HandleResult(success);
}
bool NearbyShareContactManagerImpl::SetAllowlist(
const std::set<std::string>& new_allowlist) {
std::set<std::string> old_allowlist = GetAllowedContacts();
bool were_contacts_added =
!std::includes(old_allowlist.begin(), old_allowlist.end(),
new_allowlist.begin(), new_allowlist.end());
bool were_contacts_removed =
!std::includes(new_allowlist.begin(), new_allowlist.end(),
old_allowlist.begin(), old_allowlist.end());
if (!were_contacts_added && !were_contacts_removed)
return false;
base::Value allowlist_value(base::Value::Type::LIST);
for (const std::string& id : new_allowlist) {
allowlist_value.Append(id);
}
pref_service_->Set(prefs::kNearbySharingAllowedContactsPrefName,
std::move(allowlist_value));
NotifyAllowlistChanged(were_contacts_added, were_contacts_removed);
return true;
}