chromium/chrome/browser/nearby_sharing/contacts/nearby_share_contact_downloader_impl.cc

// 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 "chrome/browser/nearby_sharing/contacts/nearby_share_contact_downloader_impl.h"

#include <utility>
#include <vector>

#include "base/memory/ptr_util.h"
#include "base/metrics/histogram_functions.h"
#include "chrome/browser/nearby_sharing/client/nearby_share_client.h"
#include "chrome/browser/nearby_sharing/common/nearby_share_features.h"
#include "components/cross_device/logging/logging.h"

namespace {

void RecordListContactPeopleResultMetrics(
    ash::nearby::NearbyHttpResult result) {
  base::UmaHistogramEnumeration("Nearby.Share.Contacts.HttpResult", result);
}

void RecordContactDownloadResultMetrics(bool success,
                                        size_t current_page_number,
                                        base::TimeTicks start_timestamp_) {
  base::UmaHistogramBoolean("Nearby.Share.Contacts.DownloadResult", success);
  if (success) {
    base::UmaHistogramCounts100(
        "Nearby.Share.Contacts.DownloadPageCount.Success", current_page_number);
    base::UmaHistogramTimes("Nearby.Share.Contacts.TimeToDownload.Success",
                            base::TimeTicks::Now() - start_timestamp_);
  } else {
    base::UmaHistogramCounts100(
        "Nearby.Share.Contacts.DownloadPageCount.Failure", current_page_number);
    base::UmaHistogramTimes("Nearby.Share.Contacts.TimeToDownload.Failure",
                            base::TimeTicks::Now() - start_timestamp_);
  }
}

void RecordContactDistributionMetrics(
    const std::vector<nearby::sharing::proto::ContactRecord>&
        unfiltered_contacts) {
  size_t num_reachable = 0;
  size_t num_unknown_type = 0;
  size_t num_google_type = 0;
  size_t num_device_type = 0;
  for (const auto& contact : unfiltered_contacts) {
    if (contact.is_reachable())
      ++num_reachable;

    switch (contact.type()) {
      case nearby::sharing::proto::ContactRecord::UNKNOWN:
        ++num_unknown_type;
        break;
      case nearby::sharing::proto::ContactRecord::GOOGLE_CONTACT:
        ++num_google_type;
        break;
      case nearby::sharing::proto::ContactRecord::DEVICE_CONTACT:
        ++num_device_type;
        break;
      case nearby::sharing::proto::
          ContactRecord_Type_ContactRecord_Type_INT_MIN_SENTINEL_DO_NOT_USE_:
      case nearby::sharing::proto::
          ContactRecord_Type_ContactRecord_Type_INT_MAX_SENTINEL_DO_NOT_USE_:
        NOTREACHED_IN_MIGRATION();
    }
  }
  base::UmaHistogramCounts10000("Nearby.Share.Contacts.NumContacts.Unfiltered",
                                unfiltered_contacts.size());
  base::UmaHistogramCounts10000("Nearby.Share.Contacts.NumContacts.Reachable",
                                num_reachable);
  base::UmaHistogramCounts10000("Nearby.Share.Contacts.NumContacts.Unreachable",
                                unfiltered_contacts.size() - num_reachable);
  base::UmaHistogramCounts10000(
      "Nearby.Share.Contacts.NumContacts.UnknownContactType", num_unknown_type);
  base::UmaHistogramCounts10000(
      "Nearby.Share.Contacts.NumContacts.GoogleContactType", num_google_type);
  base::UmaHistogramCounts10000(
      "Nearby.Share.Contacts.NumContacts.DeviceContactType", num_device_type);
  if (!unfiltered_contacts.empty()) {
    base::UmaHistogramPercentage(
        "Nearby.Share.Contacts.PercentReachable",
        std::lround(100.0f * num_reachable / unfiltered_contacts.size()));
    base::UmaHistogramPercentage(
        "Nearby.Share.Contacts.PercentDeviceContactType",
        std::lround(100.0f * num_device_type / unfiltered_contacts.size()));
  }
}

}  // namespace

// static
NearbyShareContactDownloaderImpl::Factory*
    NearbyShareContactDownloaderImpl::Factory::test_factory_ = nullptr;

// static
std::unique_ptr<NearbyShareContactDownloader>
NearbyShareContactDownloaderImpl::Factory::Create(
    const std::string& device_id,
    base::TimeDelta timeout,
    NearbyShareClientFactory* client_factory,
    SuccessCallback success_callback,
    FailureCallback failure_callback) {
  if (test_factory_) {
    return test_factory_->CreateInstance(device_id, timeout, client_factory,
                                         std::move(success_callback),
                                         std::move(failure_callback));
  }

  return base::WrapUnique(new NearbyShareContactDownloaderImpl(
      device_id, timeout, client_factory, std::move(success_callback),
      std::move(failure_callback)));
}

// static
void NearbyShareContactDownloaderImpl::Factory::SetFactoryForTesting(
    Factory* test_factory) {
  test_factory_ = test_factory;
}

NearbyShareContactDownloaderImpl::Factory::~Factory() = default;

NearbyShareContactDownloaderImpl::NearbyShareContactDownloaderImpl(
    const std::string& device_id,
    base::TimeDelta timeout,
    NearbyShareClientFactory* client_factory,
    SuccessCallback success_callback,
    FailureCallback failure_callback)
    : NearbyShareContactDownloader(device_id,
                                   std::move(success_callback),
                                   std::move(failure_callback)),
      timeout_(timeout),
      client_factory_(client_factory) {}

NearbyShareContactDownloaderImpl::~NearbyShareContactDownloaderImpl() = default;

void NearbyShareContactDownloaderImpl::OnRun() {
  CD_LOG(VERBOSE, Feature::NS) << __func__ << ": Starting contacts download.";
  start_timestamp_ = base::TimeTicks::Now();
  CallListContactPeople(/*next_page_token=*/std::nullopt);
}

void NearbyShareContactDownloaderImpl::CallListContactPeople(
    const std::optional<std::string>& next_page_token) {
  ++current_page_number_;
  CD_LOG(VERBOSE, Feature::NS)
      << __func__ << ": Making ListContactPeople RPC call to fetch page number "
      << current_page_number_
      << " with page token: " << next_page_token.value_or("[null]");
  timer_.Start(
      FROM_HERE, timeout_,
      base::BindOnce(
          &NearbyShareContactDownloaderImpl::OnListContactPeopleTimeout,
          base::Unretained(this)));

  nearby::sharing::proto::ListContactPeopleRequest request;
  if (next_page_token)
    request.set_page_token(*next_page_token);

  client_ = client_factory_->CreateInstance();
  client_->ListContactPeople(
      request,
      base::BindOnce(
          &NearbyShareContactDownloaderImpl::OnListContactPeopleSuccess,
          base::Unretained(this)),
      base::BindOnce(
          &NearbyShareContactDownloaderImpl::OnListContactPeopleFailure,
          base::Unretained(this)));
}

void NearbyShareContactDownloaderImpl::OnListContactPeopleSuccess(
    const nearby::sharing::proto::ListContactPeopleResponse& response) {
  timer_.Stop();
  contacts_.insert(contacts_.end(), response.contact_records().begin(),
                   response.contact_records().end());
  std::optional<std::string> next_page_token =
      response.next_page_token().empty()
          ? std::nullopt
          : std::make_optional<std::string>(response.next_page_token());
  client_.reset();
  RecordListContactPeopleResultMetrics(ash::nearby::NearbyHttpResult::kSuccess);

  if (next_page_token) {
    CallListContactPeople(next_page_token);
    return;
  }

  CD_LOG(VERBOSE, Feature::NS) << __func__ << ": Download of "
                               << contacts_.size() << " contacts succeeded.";
  RecordContactDownloadResultMetrics(/*success=*/true, current_page_number_,
                                     start_timestamp_);
  RecordContactDistributionMetrics(contacts_);

  // Remove device contacts if the feature flag is disabled.
  if (!base::FeatureList::IsEnabled(features::kNearbySharingDeviceContacts)) {
    size_t initial_num_contacts = contacts_.size();
    std::erase_if(
        contacts_, [](const nearby::sharing::proto::ContactRecord& contact) {
          return contact.type() ==
                 nearby::sharing::proto::ContactRecord::DEVICE_CONTACT;
        });
    CD_LOG(VERBOSE, Feature::NS)
        << __func__ << ": Removed " << initial_num_contacts - contacts_.size()
        << " device contacts.";
  }

  // Remove unreachable contacts.
  size_t initial_num_contacts = contacts_.size();
  std::erase_if(contacts_,
                [](const nearby::sharing::proto::ContactRecord& contact) {
                  return !contact.is_reachable();
                });
  uint32_t num_unreachable_contacts_filtered_out =
      initial_num_contacts - contacts_.size();
  CD_LOG(VERBOSE, Feature::NS)
      << __func__ << ": Removed " << num_unreachable_contacts_filtered_out
      << " unreachable contacts.";

  Succeed(std::move(contacts_), num_unreachable_contacts_filtered_out);
}

void NearbyShareContactDownloaderImpl::OnListContactPeopleFailure(
    ash::nearby::NearbyHttpError error) {
  timer_.Stop();
  client_.reset();
  RecordListContactPeopleResultMetrics(
      ash::nearby::NearbyHttpErrorToResult(error));
  RecordContactDownloadResultMetrics(/*success=*/false, current_page_number_,
                                     start_timestamp_);

  CD_LOG(ERROR, Feature::NS)
      << __func__ << ": Contact download RPC call failed with error " << error
      << " fetching page number " << current_page_number_;
  Fail();
}

void NearbyShareContactDownloaderImpl::OnListContactPeopleTimeout() {
  client_.reset();
  RecordListContactPeopleResultMetrics(ash::nearby::NearbyHttpResult::kTimeout);
  RecordContactDownloadResultMetrics(/*success=*/false, current_page_number_,
                                     start_timestamp_);

  CD_LOG(ERROR, Feature::NS)
      << __func__ << ": Contact download RPC call timed out.";
  Fail();
}