chromium/ash/quick_pair/repository/fast_pair/footprints_fetcher_impl.cc

// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "ash/quick_pair/repository/fast_pair/footprints_fetcher_impl.h"

#include "ash/quick_pair/common/fast_pair/fast_pair_http_result.h"
#include "ash/quick_pair/common/fast_pair/fast_pair_metrics.h"
#include "ash/quick_pair/proto/fastpair.pb.h"
#include "ash/quick_pair/proto/fastpair_data.pb.h"
#include "ash/quick_pair/repository/oauth_http_fetcher.h"
#include "base/base64.h"
#include "base/json/json_reader.h"
#include "base/strings/stringprintf.h"
#include "components/cross_device/logging/logging.h"
#include "google_apis/gaia/gaia_constants.h"
#include "google_apis/google_api_keys.h"
#include "net/traffic_annotation/network_traffic_annotation.h"
#include "url/gurl.h"

namespace ash {
namespace quick_pair {

namespace {

const char kUserDevicesUrl[] =
    "https://nearbydevices-pa.googleapis.com/v1/user/devices"
    "?key=%s&alt=proto";

const char kUserDeleteDeviceUrl[] =
    "https://nearbydevices-pa.googleapis.com/v1/user/device/%s"
    "?key=%s&alt=proto";

const net::PartialNetworkTrafficAnnotationTag kTrafficAnnotation =
    net::DefinePartialNetworkTrafficAnnotation("fast_pair_footprints_request",
                                               "oauth2_api_call_flow",
                                               R"(
      semantics {
          sender: "Fast Pair repository access"
        description:
            "Retrieves and updates details about bluetooth devices which have "
            "been paired with a user's account."
        trigger:
          "This request is sent at the start of a session."
        data: "List of paired devices with metadata."
        destination: GOOGLE_OWNED_SERVICE
      }
      policy {
          cookies_allowed: NO
          setting:
            "You can enable or disable this feature by toggling on/off the "
            "Fast Pair toggle in chrome://os-settings under 'Bluetooth'. The "
            "feature is enabled by default. Fast Pair does not fetch data from "
            "the repository if the user is not signed in."
          chrome_policy {
            FastPairEnabled {
                FastPairEnabled: true
            }
          }
        })");

std::unique_ptr<HttpFetcher> CreateHttpFetcher() {
  return std::make_unique<OAuthHttpFetcher>(
      kTrafficAnnotation, GaiaConstants::kCloudPlatformProjectsOAuth2Scope);
}

GURL GetUserDevicesUrl() {
  return GURL(
      base::StringPrintf(kUserDevicesUrl, google_apis::GetAPIKey().c_str()));
}

GURL GetUserDeleteDeviceUrl(const std::string& hex_account_key) {
  return GURL(base::StringPrintf(kUserDeleteDeviceUrl, hex_account_key.c_str(),
                                 google_apis::GetAPIKey().c_str()));
}

}  // namespace

FootprintsFetcherImpl::FootprintsFetcherImpl() = default;
FootprintsFetcherImpl::~FootprintsFetcherImpl() = default;

void FootprintsFetcherImpl::GetUserDevices(UserReadDevicesCallback callback) {
  auto http_fetcher = CreateHttpFetcher();
  auto* raw_http_fetcher = http_fetcher.get();
  raw_http_fetcher->ExecuteGetRequest(
      GetUserDevicesUrl(),
      base::BindOnce(&FootprintsFetcherImpl::OnGetComplete,
                     weak_ptr_factory_.GetWeakPtr(), std::move(callback),
                     std::move(http_fetcher)));
}

void FootprintsFetcherImpl::OnGetComplete(
    UserReadDevicesCallback callback,
    std::unique_ptr<HttpFetcher> http_fetcher,
    std::unique_ptr<std::string> response_body,
    std::unique_ptr<FastPairHttpResult> http_result) {
  CD_LOG(VERBOSE, Feature::FP)
      << __func__ << ": HTTP result: "
      << (http_result ? http_result->ToString() : "[null]");

  if (http_result)
    RecordFootprintsFetcherGetResult(*http_result);

  if (!response_body) {
    CD_LOG(WARNING, Feature::FP) << __func__ << ": No response.";
    std::move(callback).Run(std::nullopt);
    return;
  }

  nearby::fastpair::UserReadDevicesResponse devices;
  if (!devices.ParseFromString(*response_body)) {
    CD_LOG(WARNING, Feature::FP) << __func__ << ": Failed to parse.";
    std::move(callback).Run(std::nullopt);
    return;
  }

  CD_LOG(VERBOSE, Feature::FP)
      << __func__
      << ": Successfully retrived footprints data.  Paired devices:";
  for (const auto& info : devices.fast_pair_info()) {
    if (info.has_device()) {
      nearby::fastpair::StoredDiscoveryItem device;
      if (device.ParseFromString(info.device().discovery_item_bytes())) {
        CD_LOG(VERBOSE, Feature::FP) << device.title();
      }
    }
  }

  std::move(callback).Run(devices);
}

void FootprintsFetcherImpl::AddUserFastPairInfo(
    nearby::fastpair::FastPairInfo info,
    AddDeviceCallback callback) {
  auto http_fetcher = CreateHttpFetcher();
  auto* raw_http_fetcher = http_fetcher.get();
  raw_http_fetcher->ExecutePostRequest(
      GetUserDevicesUrl(), info.SerializeAsString(),
      base::BindOnce(&FootprintsFetcherImpl::OnPostComplete,
                     weak_ptr_factory_.GetWeakPtr(), std::move(callback),
                     std::move(http_fetcher)));
}

void FootprintsFetcherImpl::OnPostComplete(
    AddDeviceCallback callback,
    std::unique_ptr<HttpFetcher> http_fetcher,
    std::unique_ptr<std::string> response_body,
    std::unique_ptr<FastPairHttpResult> http_result) {
  CD_LOG(VERBOSE, Feature::FP)
      << __func__ << ": HTTP result: "
      << (http_result ? http_result->ToString() : "[null]");

  if (http_result)
    RecordFootprintsFetcherPostResult(*http_result);

  if (!response_body) {
    CD_LOG(WARNING, Feature::FP) << __func__ << ": No response.";
    std::move(callback).Run(/*success=*/false);
    return;
  }

  CD_LOG(VERBOSE, Feature::FP)
      << __func__ << ": Successfully saved new footprints data.";
  std::move(callback).Run(/*success=*/true);
}

void FootprintsFetcherImpl::DeleteUserDevice(const std::string& hex_account_key,
                                             DeleteDeviceCallback callback) {
  CD_LOG(VERBOSE, Feature::FP)
      << __func__ << " Deleting user device for acc key " << hex_account_key;
  auto http_fetcher = CreateHttpFetcher();
  auto* raw_http_fetcher = http_fetcher.get();
  raw_http_fetcher->ExecuteDeleteRequest(
      GetUserDeleteDeviceUrl(hex_account_key),
      base::BindOnce(&FootprintsFetcherImpl::OnDeleteComplete,
                     weak_ptr_factory_.GetWeakPtr(), std::move(callback),
                     std::move(http_fetcher)));
}

void FootprintsFetcherImpl::OnDeleteComplete(
    DeleteDeviceCallback callback,
    std::unique_ptr<HttpFetcher> http_fetcher,
    std::unique_ptr<std::string> response_body,
    std::unique_ptr<FastPairHttpResult> http_result) {
  CD_LOG(VERBOSE, Feature::FP)
      << __func__ << ": HTTP result: "
      << (http_result ? http_result->ToString() : "[null]");

  if (http_result)
    RecordFootprintsFetcherDeleteResult(*http_result);

  if (!response_body) {
    CD_LOG(WARNING, Feature::FP) << __func__ << ": No response.";
    std::move(callback).Run(/*success=*/false);
    return;
  }

  CD_LOG(VERBOSE, Feature::FP)
      << __func__ << ": Successfully deleted footprints data.";
  std::move(callback).Run(/*success=*/true);
}

}  // namespace quick_pair
}  // namespace ash