chromium/services/device/geolocation/wifi_data_provider_lacros.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 "services/device/geolocation/wifi_data_provider_lacros.h"

#include "base/functional/bind.h"
#include "base/logging.h"
#include "base/strings/utf_string_conversions.h"
#include "base/time/time.h"
#include "base/timer/timer.h"
#include "chromeos/lacros/lacros_service.h"
#include "services/device/geolocation/wifi_data_provider_handle.h"
#include "services/device/geolocation/wifi_polling_policy.h"
#include "services/device/public/mojom/geolocation_internals.mojom.h"

namespace device {

namespace {

// The time periods between successive polls of the wifi data.
constexpr int kDefaultPollingIntervalMilliseconds = 10 * 1000;
constexpr int kNoChangePollingIntervalMilliseconds = 2 * 60 * 1000;
constexpr int kTwoNoChangePollingIntervalMilliseconds = 10 * 60 * 1000;
constexpr int kNoWifiPollingIntervalMilliseconds = 20 * 1000;

std::unique_ptr<WifiPollingPolicy> CreatePollingPolicy() {
  return std::make_unique<GenericWifiPollingPolicy<
      kDefaultPollingIntervalMilliseconds, kNoChangePollingIntervalMilliseconds,
      kTwoNoChangePollingIntervalMilliseconds,
      kNoWifiPollingIntervalMilliseconds>>();
}

void PopulateWifiData(
    const std::vector<crosapi::mojom::AccessPointDataPtr>& access_points,
    WifiData& wifi_data) {
  for (const auto& access_point : access_points) {
    mojom::AccessPointData ap_data;
    ap_data.mac_address = base::UTF16ToUTF8(access_point->mac_address);
    ap_data.radio_signal_strength = access_point->radio_signal_strength;
    ap_data.channel = access_point->channel;
    ap_data.signal_to_noise = access_point->signal_to_noise;
    wifi_data.access_point_data.insert(ap_data);
  }
}

// crosapi::GeolocationService is not available if either the LacrosService is
// not available or if the current version of ash is not new enough to support
// the GeolocationService.
bool IsGeolocationServiceAvailable() {
  if (!chromeos::LacrosService::Get())
    return false;
  const int crosapiVersion =
      chromeos::LacrosService::Get()
          ->GetInterfaceVersion<crosapi::mojom::Crosapi>();
  const int minRequiredVersion = static_cast<int>(
      crosapi::mojom::Crosapi::kBindGeolocationServiceMinVersion);
  return crosapiVersion >= minRequiredVersion;
}

}  // namespace

WifiDataProviderLacros::WifiDataProviderLacros() = default;

WifiDataProviderLacros::~WifiDataProviderLacros() = default;

void WifiDataProviderLacros::StartDataProvider() {
  DCHECK(CalledOnClientThread());
  DCHECK(!started_);

  // Do not start the provider if the GeolocationService is not available.
  if (!IsGeolocationServiceAvailable()) {
    is_first_scan_complete_ = true;
    return;
  }

  if (!WifiPollingPolicy::IsInitialized())
    WifiPollingPolicy::Initialize(CreatePollingPolicy());
  DCHECK(WifiPollingPolicy::IsInitialized());

  started_ = true;
  int delay_interval_ms = WifiPollingPolicy::Get()->InitialInterval();
  ScheduleNextScan(delay_interval_ms);
  first_scan_delayed_ = (delay_interval_ms > 0);
}

void WifiDataProviderLacros::StopDataProvider() {
  DCHECK(CalledOnClientThread());
  if (started_) {
    wifi_scan_timer_.Stop();
    started_ = false;
  }
}

bool WifiDataProviderLacros::DelayedByPolicy() {
  DCHECK(CalledOnClientThread());
  return is_first_scan_complete_ ? true : first_scan_delayed_;
}

bool WifiDataProviderLacros::GetData(WifiData* data) {
  DCHECK(CalledOnClientThread());
  DCHECK(data);
  *data = wifi_data_;
  return is_first_scan_complete_;
}

// There is currently no reason to force a rescan on ChromeOS so this has not
// been implemented.
void WifiDataProviderLacros::ForceRescan() {}

void WifiDataProviderLacros::DidWifiScanTaskForTesting(
    bool service_initialized,
    bool data_available,
    base::TimeDelta time_since_last_updated,
    std::vector<crosapi::mojom::AccessPointDataPtr> access_points) {
  DidWifiScanTask(service_initialized, data_available, time_since_last_updated,
                  std::move(access_points));
}

void WifiDataProviderLacros::ScheduleNextScan(int interval_ms) {
  // Do not schedule a scan if the GeolocationService is not available or if not
  // `started_`. This could occur if DoWifiScanTask() is called back after the
  // provider has been stopped.
  if (!IsGeolocationServiceAvailable() || !started_)
    return;

  base::TimeDelta interval = base::Milliseconds(interval_ms);
  if (!wifi_scan_timer_.IsRunning() ||
      interval < wifi_scan_timer_.GetCurrentDelay()) {
    wifi_scan_timer_.Start(
        FROM_HERE, interval,
        base::BindRepeating(&WifiDataProviderLacros::DoWifiScanTask,
                            base::Unretained(this)));
  }
}

void WifiDataProviderLacros::DoWifiScanTask() {
  DCHECK(started_);
  DCHECK(IsGeolocationServiceAvailable());

  if (!geolocation_service_.is_bound()) {
    chromeos::LacrosService::Get()->BindGeolocationService(
        geolocation_service_.BindNewPipeAndPassReceiver());
  }

  geolocation_service_->GetWifiAccessPoints(base::BindOnce(
      &WifiDataProviderLacros::DidWifiScanTask, weak_factory_.GetWeakPtr()));
}

void WifiDataProviderLacros::DidWifiScanTask(
    bool service_initialized,
    bool data_available,
    base::TimeDelta time_since_last_updated,
    std::vector<crosapi::mojom::AccessPointDataPtr> access_points) {
  if (!service_initialized) {
    LOG(ERROR) << "DoWifiScanTask() called with uninitialized NetworkHandler";
    return;
  }

  // If the age is significantly longer than our long polling time, assume the
  // data is stale and trigger a faster update.
  const bool is_data_stale =
      time_since_last_updated >
      base::Milliseconds(kTwoNoChangePollingIntervalMilliseconds * 2);
  if (!data_available || is_data_stale) {
    ScheduleNextScan(WifiPollingPolicy::Get()->NoWifiInterval());
    return;
  }

  WifiData new_data;
  PopulateWifiData(access_points, new_data);
  const bool update_available = wifi_data_.DiffersSignificantly(new_data);
  wifi_data_ = new_data;
  WifiPollingPolicy::Get()->UpdatePollingInterval(update_available);
  ScheduleNextScan(WifiPollingPolicy::Get()->PollingInterval());

  if (update_available || !is_first_scan_complete_) {
    is_first_scan_complete_ = true;
    RunCallbacks();
  }
}

// static
WifiDataProvider* WifiDataProviderHandle::DefaultFactoryFunction() {
  return new WifiDataProviderLacros();
}

}  // namespace device