910e62b5创建于 1月15日历史提交
// Copyright 2018 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/position_cache_impl.h"

#include <algorithm>

#include "base/strings/utf_string_conversions.h"
#include "components/device_event_log/device_event_log.h"
#include "services/device/geolocation/wifi_data.h"
#include "services/device/public/mojom/geolocation_internals.mojom.h"
#include "services/device/public/mojom/geoposition.mojom.h"

namespace device {

// static
const size_t PositionCacheImpl::kMaximumSize = 10;
// static
const base::TimeDelta PositionCacheImpl::kMaximumLifetime = base::Days(1);

PositionCacheImpl::CacheEntry::CacheEntry(
    const Hash& hash,
    mojom::GeopositionPtr position,
    std::unique_ptr<base::OneShotTimer> eviction_timer)
    : hash_(hash),
      position_(std::move(position)),
      eviction_timer_(std::move(eviction_timer)) {}
PositionCacheImpl::CacheEntry::~CacheEntry() = default;
PositionCacheImpl::CacheEntry::CacheEntry(CacheEntry&&) = default;
PositionCacheImpl::CacheEntry& PositionCacheImpl::CacheEntry::operator=(
    CacheEntry&&) = default;

// static
PositionCacheImpl::Hash PositionCacheImpl::MakeKey(const WifiData& wifi_data) {
  // Currently we use only WiFi data and base the key only on the MAC addresses.
  std::string key;
  const size_t kCharsPerMacAddress = 6 * 3 + 1;  // e.g. "11:22:33:44:55:66|"
  key.reserve(wifi_data.access_point_data.size() * kCharsPerMacAddress);
  const std::string separator("|");
  for (const auto& access_point_data : wifi_data.access_point_data) {
    key += separator;
    key += access_point_data.mac_address;
    key += separator;
  }
  return key;
}

PositionCacheImpl::PositionCacheImpl(const base::TickClock* clock)
    : clock_(clock) {
  net::NetworkChangeNotifier::AddNetworkChangeObserver(this);
}

PositionCacheImpl::~PositionCacheImpl() {
  net::NetworkChangeNotifier::RemoveNetworkChangeObserver(this);
}

void PositionCacheImpl::CachePosition(const WifiData& wifi_data,
                                      const mojom::Geoposition& position) {
  const Hash key = MakeKey(wifi_data);

  // If the cache is full, remove the oldest entry.
  if (data_.size() == kMaximumSize) {
    data_.erase(data_.begin());
  }

  DCHECK_LT(data_.size(), kMaximumSize);

  auto eviction_timer = std::make_unique<base::OneShotTimer>(clock_);
  // Ensure that the entry we're adding will be evicted after kMaximumLifetime.
  // base::Unretained safe because the timer is indirectly owned by |this|.
  eviction_timer->Start(FROM_HERE, kMaximumLifetime,
                        base::BindOnce(&PositionCacheImpl::EvictEntry,
                                       base::Unretained(this), key));

  data_.emplace_back(key, position.Clone(), std::move(eviction_timer));
}

const mojom::Geoposition* PositionCacheImpl::FindPosition(
    const WifiData& wifi_data) {
  const Hash key = MakeKey(wifi_data);
  auto it = std::ranges::find_if(
      data_, [key](const CacheEntry& entry) { return entry == key; });
  if (it == data_.end()) {
    ++miss_count_;
    last_miss_ = base::Time::Now();
    return nullptr;
  }
  ++hit_count_;
  last_hit_ = base::Time::Now();
  return it->position();
}

size_t PositionCacheImpl::GetPositionCacheSize() const {
  return data_.size();
}

const mojom::GeopositionResult* PositionCacheImpl::GetLastUsedNetworkPosition()
    const {
  GEOLOCATION_LOG(DEBUG) << "Get last used network position";
  return last_used_result_.get();
}

void PositionCacheImpl::SetLastUsedNetworkPosition(
    const mojom::GeopositionResult& result) {
  last_used_result_ = result.Clone();
}

void PositionCacheImpl::FillDiagnostics(
    mojom::PositionCacheDiagnostics& diagnostics) {
  diagnostics.cache_size = data_.size();
  if (last_hit_) {
    diagnostics.last_hit = *last_hit_;
  }
  if (last_miss_) {
    diagnostics.last_miss = *last_miss_;
  }
  if (hit_count_ || miss_count_) {
    diagnostics.hit_rate =
        static_cast<double>(hit_count_) / (hit_count_ + miss_count_);
  }
  if (last_used_result_) {
    diagnostics.last_network_result = last_used_result_.Clone();
  }
}

void PositionCacheImpl::OnNetworkChanged(
    net::NetworkChangeNotifier::ConnectionType) {
  GEOLOCATION_LOG(DEBUG) << "Network changed";
  // OnNetworkChanged is called " when a change occurs to the host
  // computer's hardware or software that affects the route network packets
  // take to any network server.". This means that whatever position we had
  // stored for a wired connection (empty WifiData) could have become stale.
  EvictEntry(MakeKey(WifiData()));
  last_used_result_.reset();
}

void PositionCacheImpl::EvictEntry(const Hash& hash) {
  std::erase(data_, hash);
}

}  // namespace device