chromium/chrome/browser/nearby_sharing/fast_initiation/fast_initiation_scanner.cc

// Copyright 2021 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/fast_initiation/fast_initiation_scanner.h"

#include <stdint.h>

#include <optional>
#include <vector>

#include "base/time/time.h"
#include "chrome/browser/nearby_sharing/fast_initiation/constants.h"
#include "chrome/browser/nearby_sharing/nearby_share_metrics.h"
#include "components/cross_device/logging/logging.h"
#include "device/bluetooth/bluetooth_low_energy_scan_filter.h"

namespace {

// The length of time that a device must be in range before it is reported via a
// "device found" event.
constexpr base::TimeDelta kBackgroundScanningDeviceFoundTimeout =
    base::Seconds(1);

// The length of time that a device must be out of range before this is reported
// via a "device lost" event.
constexpr base::TimeDelta kBackgroundScanningDeviceLostTimeout =
    base::Seconds(7);
}  // namespace

// static
FastInitiationScanner::Factory*
    FastInitiationScanner::Factory::factory_instance_ = nullptr;

// static
std::unique_ptr<FastInitiationScanner> FastInitiationScanner::Factory::Create(
    scoped_refptr<device::BluetoothAdapter> adapter) {
  if (factory_instance_)
    return factory_instance_->CreateInstance(adapter);

  return std::make_unique<FastInitiationScanner>(adapter);
}

// static
bool FastInitiationScanner::Factory::IsHardwareSupportAvailable(
    device::BluetoothAdapter* adapter) {
  if (factory_instance_)
    return factory_instance_->IsHardwareSupportAvailable();

  // The function only returns correct status when adapter is powered.
  return adapter->IsPowered() &&
         adapter->GetLowEnergyScanSessionHardwareOffloadingStatus() ==
             device::BluetoothAdapter::
                 LowEnergyScanSessionHardwareOffloadingStatus::kSupported;
}

// static
void FastInitiationScanner::Factory::SetFactoryForTesting(
    FastInitiationScanner::Factory* factory) {
  factory_instance_ = factory;
}

FastInitiationScanner::FastInitiationScanner(
    scoped_refptr<device::BluetoothAdapter> adapter)
    : adapter_(adapter) {
  DCHECK(adapter_ && adapter_->IsPresent() && adapter_->IsPowered());
}

FastInitiationScanner::~FastInitiationScanner() {}

void FastInitiationScanner::StartScanning(
    base::RepeatingClosure devices_detected_callback,
    base::RepeatingClosure devices_not_detected_callback,
    base::OnceClosure scanner_invalidated_callback) {
  devices_detected_callback_ = std::move(devices_detected_callback);
  devices_not_detected_callback_ = std::move(devices_not_detected_callback);
  scanner_invalidated_callback_ = std::move(scanner_invalidated_callback);

  std::vector<uint8_t> pattern_value;
  // Add the service UUID in reversed byte order.
  pattern_value.insert(pattern_value.begin(),
                       std::rbegin(kFastInitiationServiceId),
                       std::rend(kFastInitiationServiceId));
  // Add the service data in the original order.
  pattern_value.insert(pattern_value.end(), std::begin(kFastInitiationModelId),
                       std::end(kFastInitiationModelId));

  device::BluetoothLowEnergyScanFilter::Pattern pattern(
      /*start_position=*/0,
      device::BluetoothLowEnergyScanFilter::AdvertisementDataType::kServiceData,
      std::move(pattern_value));
  auto filter = device::BluetoothLowEnergyScanFilter::Create(
      device::BluetoothLowEnergyScanFilter::Range::kNear,
      kBackgroundScanningDeviceFoundTimeout,
      kBackgroundScanningDeviceLostTimeout, {pattern},
      /*rssi_sampling_period=*/std::nullopt);
  if (!filter) {
    CD_LOG(ERROR, Feature::NS)
        << __func__
        << ": Failed to start Fast Initiation scanning due to "
           "failure to create filter.";
    std::move(scanner_invalidated_callback_).Run();
    return;
  }

  background_scan_session_ = adapter_->StartLowEnergyScanSession(
      std::move(filter), /*delegate=*/weak_ptr_factory_.GetWeakPtr());
}

void FastInitiationScanner::OnSessionStarted(
    device::BluetoothLowEnergyScanSession* scan_session,
    std::optional<device::BluetoothLowEnergyScanSession::ErrorCode>
        error_code) {
  if (error_code) {
    CD_LOG(WARNING, Feature::NS)
        << __func__
        << ": Error, error_code=" << static_cast<int>(error_code.value());
    std::move(scanner_invalidated_callback_).Run();
  } else {
    CD_LOG(VERBOSE, Feature::NS) << __func__ << ": Success";
  }
  RecordNearbyShareBackgroundScanningSessionStarted(/*success=*/!error_code);
}

void FastInitiationScanner::OnDeviceFound(
    device::BluetoothLowEnergyScanSession* scan_session,
    device::BluetoothDevice* device) {
  CD_LOG(VERBOSE, Feature::NS) << __func__;
  size_t device_count_prev = detected_devices_.size();
  detected_devices_.insert(device->GetAddress());

  // Invoke the callback when we go from zero devices to more than zero.
  if (device_count_prev == 0) {
    devices_detected_callback_.Run();
    RecordNearbyShareBackgroundScanningDevicesDetected();
    devices_detected_timestamp_ = base::TimeTicks::Now();
  }
}

void FastInitiationScanner::OnDeviceLost(
    device::BluetoothLowEnergyScanSession* scan_session,
    device::BluetoothDevice* device) {
  CD_LOG(VERBOSE, Feature::NS) << __func__;
  size_t device_count_prev = detected_devices_.size();
  if (detected_devices_.erase(device->GetAddress()) == 0) {
    CD_LOG(WARNING, Feature::NS)
        << __func__ << ": Received device lost event for device not in list.";
  }

  // Invoke the callback when we go from more than zero devices to zero.
  if (device_count_prev > 0 && detected_devices_.empty()) {
    devices_not_detected_callback_.Run();
    RecordNearbyShareBackgroundScanningDevicesDetectedDuration(
        base::TimeTicks::Now() - devices_detected_timestamp_);
  }
}

void FastInitiationScanner::OnSessionInvalidated(
    device::BluetoothLowEnergyScanSession* scan_session) {
  CD_LOG(VERBOSE, Feature::NS) << __func__;
  detected_devices_.clear();
  std::move(scanner_invalidated_callback_).Run();
}