chromium/chromeos/ash/components/network/network_state_handler.cc

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

#include "chromeos/ash/components/network/network_state_handler.h"

#include <stddef.h>

#include <limits>
#include <memory>
#include <utility>

#include "ash/constants/ash_features.h"
#include "base/command_line.h"
#include "base/containers/contains.h"
#include "base/format_macros.h"
#include "base/functional/bind.h"
#include "base/location.h"
#include "base/memory/ptr_util.h"
#include "base/metrics/histogram_functions.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/uuid.h"
#include "base/values.h"
#include "chromeos/ash/components/network/device_state.h"
#include "chromeos/ash/components/network/fake_network_state_handler.h"
#include "chromeos/ash/components/network/network_connection_handler.h"
#include "chromeos/ash/components/network/network_event_log.h"
#include "chromeos/ash/components/network/network_handler_callbacks.h"
#include "chromeos/ash/components/network/network_state_handler_observer.h"
#include "chromeos/ash/components/network/tether_constants.h"
#include "third_party/cros_system_api/dbus/service_constants.h"

namespace ash {

namespace {

// Constants used for logging.
constexpr char kReasonStateChange[] = "State Change";
constexpr char kReasonChange[] = "New Network";
constexpr char kReasonUpdate[] = "Update";
constexpr char kReasonUpdateIPConfig[] = "UpdateIPConfig";
constexpr char kReasonUpdateDeviceIPConfig[] = "UpdateDeviceIPConfig";
constexpr char kReasonTether[] = "Tether Change";
constexpr char kReasonPortal[] = "Portal State Change";

bool ConnectionStateChanged(const NetworkState* network,
                            const std::string& prev_connection_state) {
  std::string connection_state = network->connection_state();
  bool prev_idle = prev_connection_state.empty() ||
                   prev_connection_state == shill::kStateIdle;
  bool cur_idle = connection_state == shill::kStateIdle;
  if (prev_idle || cur_idle) {
    return prev_idle != cur_idle;
  }
  return connection_state != prev_connection_state;
}

std::string GetManagedStateLogType(const ManagedState* state) {
  switch (state->managed_type()) {
    case ManagedState::MANAGED_TYPE_NETWORK:
      return "Network";
    case ManagedState::MANAGED_TYPE_DEVICE:
      return "Device";
  }
  NOTREACHED_IN_MIGRATION();
  return "";
}

std::string GetLogName(const ManagedState* state) {
  if (!state) {
    return "None";
  }
  const NetworkState* network = state->AsNetworkState();
  if (network) {
    return NetworkId(network);
  }
  return state->path();
}

bool ShouldIncludeNetworkInList(const NetworkState* network_state,
                                bool configured_only,
                                bool visible_only) {
  if (configured_only && !network_state->IsInProfile()) {
    return false;
  }

  if (visible_only && !network_state->visible()) {
    return false;
  }

  if (network_state->type() == shill::kTypeWifi &&
      !network_state->tether_guid().empty()) {
    // Wi-Fi networks which are actually underlying Wi-Fi hotspots for a
    // Tether network should not be included since they should only be shown
    // to the user as Tether networks.
    return false;
  }

  return true;
}

// ManagedState entries may not have |type| set when the network is initially
// added to a list (i.e. before the initial properties are received). Use this
// wrapper anyplace where |managed| might be uninitialized.
bool TypeMatches(const ManagedState* managed, const NetworkTypePattern& type) {
  return !managed->type().empty() && managed->Matches(type);
}

}  // namespace

// Class for tracking properties that affect whether a NetworkState is active.
class NetworkStateHandler::ActiveNetworkState {
 public:
  explicit ActiveNetworkState(const NetworkState* network)
      : guid_(network->guid()),
        connection_state_(network->connection_state()),
        activation_state_(network->activation_state()),
        connect_requested_(network->connect_requested()),
        signal_strength_(network->signal_strength()),
        network_technology_(network->network_technology()),
        portal_state_(network->GetPortalState()) {}

  bool MatchesNetworkState(const NetworkState* network) {
    return guid_ == network->guid() &&
           connection_state_ == network->connection_state() &&
           activation_state_ == network->activation_state() &&
           connect_requested_ == network->connect_requested() &&
           (abs(signal_strength_ - network->signal_strength()) <
            NetworkState::kSignalStrengthChangeThreshold) &&
           network_technology_ == network->network_technology() &&
           portal_state_ == network->GetPortalState();
  }

 private:
  // Unique network identifier.
  const std::string guid_;
  // Active networks have a connected or connecting |connection_state_|, see
  // NetworkState::Is{Connected|Connecting}State.
  const std::string connection_state_;
  // Activating Cellular networks are frequently treated like connecting
  // networks in the UI, so we also track changes to Cellular activation state.
  const std::string activation_state_;
  // The connect_requested state affects 'connecting' in the UI.
  const bool connect_requested_;
  // We care about signal strength changes to active networks.
  const int signal_strength_;
  // Network technology is indicated in network icons in the UI, so we need to
  // track changes to this value.
  const std::string network_technology_;
  // Portal state changes affects the network connection state. We want to make
  // sure the network state gets updated each time the portal state changes.
  const NetworkState::PortalState portal_state_;
};

const char NetworkStateHandler::kDefaultCheckPortalList[] =
    "ethernet,wifi,cellular";

NetworkStateHandler::NetworkStateHandler() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
}

NetworkStateHandler::~NetworkStateHandler() {
  // Normally Shutdown() will get called in ~NetworkHandler, however unit
  // tests do not use that class so this needs to call Shutdown when we
  // destroy the class.
  if (!did_shutdown_) {
    Shutdown();
  }
}

void NetworkStateHandler::Shutdown() {
  if (did_shutdown_) {
    return;  // May get called twice in tests.
  }
  did_shutdown_ = true;
  for (Observer& observer : observers_) {
    observer.OnShuttingDown();
  }
}

void NetworkStateHandler::InitShillPropertyHandler() {
  shill_property_handler_ =
      std::make_unique<internal::ShillPropertyHandler>(this);
  shill_property_handler_->Init();
}

void NetworkStateHandler::UpdateBlockedCellularNetworks(bool only_managed) {
  if (allow_only_policy_cellular_networks_to_connect_ == only_managed) {
    return;
  }
  allow_only_policy_cellular_networks_to_connect_ = only_managed;

  UpdateBlockedNetworksInternal(NetworkTypePattern::Cellular());
}

void NetworkStateHandler::UpdateBlockedWifiNetworks(
    bool only_managed,
    bool available_only,
    const std::vector<std::string>& blocked_hex_ssids) {
  if (allow_only_policy_wifi_networks_to_connect_ == only_managed &&
      allow_only_policy_wifi_networks_to_connect_if_available_ ==
          available_only &&
      blocked_hex_ssids_ == blocked_hex_ssids) {
    return;
  }
  allow_only_policy_wifi_networks_to_connect_ = only_managed;
  allow_only_policy_wifi_networks_to_connect_if_available_ = available_only;
  blocked_hex_ssids_ = blocked_hex_ssids;

  UpdateBlockedNetworksInternal(NetworkTypePattern::WiFi());
}

const NetworkState* NetworkStateHandler::GetAvailableManagedWifiNetwork()
    const {
  DeviceState* device =
      GetModifiableDeviceStateByType(NetworkTypePattern::WiFi());
  if (!device || !device->update_received()) {
    NET_LOG(ERROR) << "GetAvailableManagedWifiNetwork() called with no WiFi "
                   << "device available.";
    return nullptr;
  }

  const std::string& available_managed_network_path =
      device->available_managed_network_path();
  if (available_managed_network_path.empty()) {
    return nullptr;
  }
  return GetNetworkState(available_managed_network_path);
}

bool NetworkStateHandler::IsProfileNetworksLoaded() {
  return is_profile_networks_loaded_;
}

bool NetworkStateHandler::OnlyManagedWifiNetworksAllowed() const {
  return allow_only_policy_wifi_networks_to_connect_ ||
         (allow_only_policy_wifi_networks_to_connect_if_available_ &&
          GetAvailableManagedWifiNetwork());
}

void NetworkStateHandler::SyncStubCellularNetworks() {
  bool network_list_changed = AddOrRemoveStubCellularNetworks();
  if (!network_list_changed) {
    return;
  }
  SortNetworkList();
  NotifyNetworkListChanged();
}

void NetworkStateHandler::RequestTrafficCounters(
    const std::string& service_path,
    chromeos::DBusMethodCallback<base::Value> callback) {
  const NetworkState* network = GetNetworkState(service_path);

  // Return early if a network is not backed by shill, this can happen if the
  // network is a Tether network or is a non shill Cellular network.
  // see b/266972302.
  if (!network || network->IsNonProfileType()) {
    std::move(callback).Run(std::nullopt);
    return;
  }

  shill_property_handler_->RequestTrafficCounters(service_path,
                                                  std::move(callback));
}

void NetworkStateHandler::ResetTrafficCounters(
    const std::string& service_path) {
  const NetworkState* network = GetNetworkState(service_path);

  // Return early if a network is not backed by shill, this can happen if the
  // network is a Tether network or is a non shill Cellular network.
  // see b/266972302.
  if (!network || network->IsNonProfileType()) {
    return;
  }

  shill_property_handler_->ResetTrafficCounters(service_path);
}

// static
std::unique_ptr<NetworkStateHandler> NetworkStateHandler::InitializeForTest() {
  auto handler = base::WrapUnique(new NetworkStateHandler());
  handler->InitShillPropertyHandler();
  return handler;
}

void NetworkStateHandler::AddObserver(Observer* observer,
                                      const base::Location& from_here) {
  observers_.AddObserver(observer);
  device_event_log::AddEntry(
      from_here.file_name(), from_here.line_number(),
      device_event_log::LOG_TYPE_NETWORK, device_event_log::LOG_LEVEL_DEBUG,
      base::StringPrintf("NetworkStateHandler::AddObserver: 0x%p", observer));
}

void NetworkStateHandler::AddObserver(Observer* observer) {
  observers_.AddObserver(observer);
}

void NetworkStateHandler::RemoveObserver(Observer* observer,
                                         const base::Location& from_here) {
  observers_.RemoveObserver(observer);
  device_event_log::AddEntry(
      from_here.file_name(), from_here.line_number(),
      device_event_log::LOG_TYPE_NETWORK, device_event_log::LOG_LEVEL_DEBUG,
      base::StringPrintf("NetworkStateHandler::RemoveObserver: 0x%p",
                         observer));
}

void NetworkStateHandler::RemoveObserver(Observer* observer) {
  observers_.RemoveObserver(observer);
}

NetworkStateHandler::TechnologyState NetworkStateHandler::GetTechnologyState(
    const NetworkTypePattern& type) const {
  std::string technology = GetTechnologyForType(type);

  if (technology == kTypeTether) {
    return tether_technology_state_;
  }

  // If a technology is not in Shill's 'AvailableTechnologies' list, it is
  // always unavailable.
  if (!shill_property_handler_->IsTechnologyAvailable(technology)) {
    return TECHNOLOGY_UNAVAILABLE;
  }

  // Prohibited should take precedence over other states.
  if (shill_property_handler_->IsTechnologyProhibited(technology)) {
    return TECHNOLOGY_PROHIBITED;
  }

  // Disabling is a pseudostate used by the UI and takes precedence over
  // enabled.
  if (shill_property_handler_->IsTechnologyDisabling(technology)) {
    DCHECK(shill_property_handler_->IsTechnologyEnabled(technology));
    return TECHNOLOGY_DISABLING;
  }

  // Enabled and Uninitialized should be mutually exclusive. 'Enabling', which
  // is a pseudo state used by the UI, takes precedence over 'Uninitialized',
  // but not 'Enabled'.
  if (shill_property_handler_->IsTechnologyEnabled(technology)) {
    return TECHNOLOGY_ENABLED;
  }
  if (shill_property_handler_->IsTechnologyEnabling(technology)) {
    return TECHNOLOGY_ENABLING;
  }
  if (shill_property_handler_->IsTechnologyUninitialized(technology)) {
    return TECHNOLOGY_UNINITIALIZED;
  }

  // Default state is 'Available', which is equivalent to 'Initialized but not
  // enabled'.
  return TECHNOLOGY_AVAILABLE;
}

void NetworkStateHandler::SetTechnologiesEnabled(
    const NetworkTypePattern& type,
    bool enabled,
    network_handler::ErrorCallback error_callback) {
  std::vector<std::string> technologies = GetTechnologiesForType(type);
  for (const std::string& technology : technologies) {
    PerformSetTechnologyEnabled(technology, enabled, base::DoNothing(),
                                std::move(error_callback));
  }

  // Signal Device/Technology state changed.
  NotifyDeviceListChanged();
}

void NetworkStateHandler::SetTechnologyEnabled(
    const NetworkTypePattern& type,
    bool enabled,
    base::OnceClosure success_callback,
    network_handler::ErrorCallback error_callback) {
  std::string technology = GetTechnologyForType(type);
  PerformSetTechnologyEnabled(technology, enabled, std::move(success_callback),
                              std::move(error_callback));

  // Signal Device/Technology state changed.
  NotifyDeviceListChanged();
}

void NetworkStateHandler::PerformSetTechnologyEnabled(
    const std::string& technology,
    bool enabled,
    base::OnceClosure success_callback,
    network_handler::ErrorCallback error_callback) {
  if (technology == kTypeTether) {
    if (tether_technology_state_ != TECHNOLOGY_ENABLED &&
        tether_technology_state_ != TECHNOLOGY_AVAILABLE) {
      NET_LOG(ERROR) << "SetTechnologyEnabled() called for the Tether "
                     << "DeviceState, but the current state was: "
                     << tether_technology_state_;
      network_handler::RunErrorCallback(
          std::move(error_callback),
          NetworkConnectionHandler::kErrorEnabledOrDisabledWhenNotAvailable);
      return;
    }

    // Tether does not exist in Shill, so set |tether_technology_state_| and
    // skip the below interactions with |shill_property_handler_|.
    tether_technology_state_ =
        enabled ? TECHNOLOGY_ENABLED : TECHNOLOGY_AVAILABLE;
    return;
  }

  if (!shill_property_handler_->IsTechnologyAvailable(technology)) {
    return;
  }
  NET_LOG(USER) << "SetTechnologyEnabled " << technology << ":" << enabled;
  shill_property_handler_->SetTechnologyEnabled(technology, enabled,
                                                std::move(error_callback),
                                                std::move(success_callback));
}

void NetworkStateHandler::SetTetherTechnologyState(
    TechnologyState technology_state) {
  if (tether_technology_state_ == technology_state) {
    return;
  }

  tether_technology_state_ = technology_state;
  EnsureTetherDeviceState();

  // Signal Device/Technology state changed.
  NotifyDeviceListChanged();
}

void NetworkStateHandler::SetTetherScanState(bool is_scanning) {
  DeviceState* tether_device_state =
      GetModifiableDeviceState(kTetherDevicePath);
  if (!tether_device_state) {
    NET_LOG(ERROR) << "SetTetherScanState() called when Tether TechnologyState "
                   << "is UNAVAILABLE; cannot set scanning state.";
    return;
  }

  bool was_scanning = tether_device_state->scanning();
  tether_device_state->set_scanning(is_scanning);

  if (was_scanning && !is_scanning) {
    // If a scan was in progress but has completed, notify observers.
    NotifyScanCompleted(tether_device_state);
  } else if (!was_scanning && is_scanning) {
    // If a scan was started, notify observers.
    NotifyScanStarted(tether_device_state);
  }
}

void NetworkStateHandler::SetProhibitedTechnologies(
    const std::vector<std::string>& prohibited_technologies) {
  // Make a copy of |prohibited_technologies| since the list may be edited
  // within this function.
  std::vector<std::string> prohibited_technologies_copy =
      prohibited_technologies;

  auto it = prohibited_technologies_copy.begin();
  while (it != prohibited_technologies_copy.end()) {
    if (*it == kTypeTether) {
      // If Tether networks are prohibited, set |tether_technology_state_| and
      // remove |kTypeTether| from the list before passing it to
      // |shill_property_handler_| below. Shill does not have a concept of
      // Tether networks, so it cannot prohibit that technology type.
      tether_technology_state_ = TECHNOLOGY_PROHIBITED;
      it = prohibited_technologies_copy.erase(it);
    } else {
      ++it;
    }
  }

  shill_property_handler_->SetProhibitedTechnologies(
      prohibited_technologies_copy);
  // Signal Device/Technology state changed.
  NotifyDeviceListChanged();
}

const DeviceState* NetworkStateHandler::GetDeviceState(
    const std::string& device_path) const {
  const DeviceState* device = GetModifiableDeviceState(device_path);
  if (device && !device->update_received()) {
    NET_LOG(DEBUG) << "Device exists but update not received: " << device_path;
    return nullptr;
  }
  return device;
}

const DeviceState* NetworkStateHandler::GetDeviceStateByType(
    const NetworkTypePattern& type) const {
  const DeviceState* device = GetModifiableDeviceStateByType(type);
  if (device && !device->update_received()) {
    return nullptr;
  }
  return device;
}

bool NetworkStateHandler::GetScanningByType(
    const NetworkTypePattern& type) const {
  for (auto iter = device_list_.begin(); iter != device_list_.end(); ++iter) {
    const DeviceState* device = (*iter)->AsDeviceState();
    DCHECK(device);
    if (!device->update_received()) {
      continue;
    }
    if (device->Matches(type) && device->scanning()) {
      return true;
    }
  }
  return false;
}

const NetworkState* NetworkStateHandler::GetNetworkState(
    const std::string& service_path) const {
  const NetworkState* network = GetModifiableNetworkState(service_path);
  if (network && !network->update_received()) {
    return nullptr;
  }
  return network;
}

const NetworkState* NetworkStateHandler::DefaultNetwork() const {
  if (default_network_path_.empty()) {
    return nullptr;
  }
  return GetNetworkState(default_network_path_);
}

const NetworkState* NetworkStateHandler::ConnectedNetworkByType(
    const NetworkTypePattern& type) {
  NetworkStateList active_networks;
  GetActiveNetworkListByType(type, &active_networks);
  for (auto* network : active_networks) {
    if (network->IsConnectedState()) {
      return network;
    }
  }
  return nullptr;
}

const NetworkState* NetworkStateHandler::ConnectingNetworkByType(
    const NetworkTypePattern& type) {
  NetworkStateList active_networks;
  GetActiveNetworkListByType(type, &active_networks);
  for (auto* network : active_networks) {
    if (network->IsConnectingState()) {
      return network;
    }
  }
  return nullptr;
}

const NetworkState* NetworkStateHandler::ActiveNetworkByType(
    const NetworkTypePattern& type) {
  NetworkStateList active_networks;
  GetActiveNetworkListByType(type, &active_networks);
  if (active_networks.size() > 0) {
    return active_networks.front();
  }
  return nullptr;
}

const NetworkState* NetworkStateHandler::FirstNetworkByType(
    const NetworkTypePattern& type) {
  // Sort to ensure visible networks are listed first.
  if (!network_list_sorted_) {
    SortNetworkList();
  }

  const NetworkState* first_network = nullptr;
  for (auto iter = network_list_.begin(); iter != network_list_.end(); ++iter) {
    const NetworkState* network = (*iter)->AsNetworkState();
    DCHECK(network);
    if (!network->update_received()) {
      continue;
    }
    if (!network->visible()) {
      break;
    }
    if (network->Matches(type)) {
      first_network = network;
      break;
    }
  }

  // Active Ethernet networks are the highest priority.
  if (first_network && first_network->type() == shill::kTypeEthernet) {
    return first_network;
  }

  const NetworkState* first_tether_network =
      type.MatchesPattern(NetworkTypePattern::Tether()) &&
              !tether_network_list_.empty()
          ? tether_network_list_[0]->AsNetworkState()
          : nullptr;

  // Active Tether networks are next.
  if (first_tether_network && first_tether_network->IsConnectingOrConnected()) {
    return first_tether_network;
  }

  // Other active networks are next.
  if (first_network && first_network->IsConnectingOrConnected()) {
    return first_network;
  }

  // Non-active Tether networks are next.
  if (first_tether_network) {
    return first_tether_network;
  }

  // Other networks are last.
  return first_network;
}

void NetworkStateHandler::SetNetworkConnectRequested(
    const std::string& service_path,
    bool connect_requested) {
  NetworkState* network = GetModifiableNetworkState(service_path);
  if (!network) {
    return;
  }
  network->connect_requested_ = connect_requested;
  network->shill_connect_error_.clear();
  network_list_sorted_ = false;
  OnNetworkConnectionStateChanged(network);
}

void NetworkStateHandler::SetShillConnectError(
    const std::string& service_path,
    const std::string& shill_connect_error) {
  NetworkState* network = GetModifiableNetworkState(service_path);
  if (!network) {
    return;
  }
  network->shill_connect_error_ = shill_connect_error;
}

void NetworkStateHandler::SetNetworkChromePortalState(
    const std::string& service_path,
    NetworkState::PortalState portal_state) {
  CHECK(!features::IsRemoveDetectPortalFromChromeEnabled());

  NetworkState* network = GetModifiableNetworkState(service_path);
  if (!network) {
    return;
  }
  NET_LOG(USER) << "Setting Chrome PortalState for "
                << NetworkPathId(service_path) << " = " << portal_state;
  auto prev_portal_state = network->GetPortalState();
  network->SetChromePortalState(portal_state);
  if (prev_portal_state == network->GetPortalState() ||
      service_path != default_network_path_) {
    return;
  }
  NotifyDefaultNetworkChanged(kReasonPortal);
}

std::string NetworkStateHandler::FormattedHardwareAddressForType(
    const NetworkTypePattern& type) {
  const NetworkState* network = ConnectedNetworkByType(type);
  if (network && network->type() == kTypeTether) {
    // If this is a Tether network, get the MAC address corresponding to that
    // network instead.
    network = GetNetworkStateFromGuid(network->tether_guid());
  }
  const DeviceState* device = network ? GetDeviceState(network->device_path())
                                      : GetDeviceStateByType(type);
  if (!device || device->mac_address().empty()) {
    return std::string();
  }
  return network_util::FormattedMacAddress(device->mac_address());
}

void NetworkStateHandler::GetVisibleNetworkListByType(
    const NetworkTypePattern& type,
    NetworkStateList* list) {
  GetNetworkListByType(type, false /* configured_only */,
                       true /* visible_only */, 0 /* no limit */, list);
}

void NetworkStateHandler::GetVisibleNetworkList(NetworkStateList* list) {
  GetVisibleNetworkListByType(NetworkTypePattern::Default(), list);
}

void NetworkStateHandler::GetNetworkListByType(const NetworkTypePattern& type,
                                               bool configured_only,
                                               bool visible_only,
                                               size_t limit,
                                               NetworkStateList* list) {
  GetNetworkListByTypeImpl(type, configured_only, visible_only,
                           false /* active_only */, limit, list);
}

void NetworkStateHandler::GetActiveNetworkListByType(
    const NetworkTypePattern& type,
    NetworkStateList* list) {
  GetNetworkListByTypeImpl(type, false /* configured_only */,
                           false /* visible_only */, true /* active_only */,
                           0 /* no limit */, list);
}

void NetworkStateHandler::GetNetworkListByTypeImpl(
    const NetworkTypePattern& type,
    bool configured_only,
    bool visible_only,
    bool active_only,
    size_t limit,
    NetworkStateList* list) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  DCHECK(list);
  list->clear();

  // If |limit| is 0, there is no limit. Simplify the calculations below by
  // setting it to the maximum size_t value.
  if (limit == 0) {
    limit = std::numeric_limits<size_t>::max();
  }

  if (!network_list_sorted_) {
    SortNetworkList();
  }

  // First, add active Tether networks.
  if (type.MatchesPattern(NetworkTypePattern::Tether())) {
    AppendTetherNetworksToList(true /* get_active */, limit, list);
  }

  // Second, add active non-Tether networks.
  for (const auto& managed : network_list_) {
    const NetworkState* network = managed.get()->AsNetworkState();
    DCHECK(network);
    if (!network->update_received() || !network->Matches(type)) {
      continue;
    }
    if (!network->IsActive()) {
      break;  // Active networks are listed first.
    }
    if (!ShouldIncludeNetworkInList(network, configured_only, visible_only)) {
      continue;
    }

    if (network->type() == shill::kTypeEthernet) {
      // Ethernet networks should always be in front.
      list->insert(list->begin(), network);
    } else {
      list->push_back(network);
    }
    if (list->size() >= limit) {
      return;
    }
  }

  if (active_only) {
    return;
  }

  // Third, add inactive Tether networks.
  if (type.MatchesPattern(NetworkTypePattern::Tether())) {
    AppendTetherNetworksToList(false /* get_active */, limit, list);
  }
  if (list->size() >= limit) {
    return;
  }

  // Fourth, add inactive non-Tether networks.
  for (const auto& managed : network_list_) {
    const NetworkState* network = managed.get()->AsNetworkState();
    DCHECK(network);
    if (!network->update_received() || !network->Matches(type)) {
      continue;
    }
    if (network->IsActive()) {
      continue;
    }
    if (!ShouldIncludeNetworkInList(network, configured_only, visible_only)) {
      continue;
    }
    list->push_back(network);
    if (list->size() >= limit) {
      return;
    }
  }
}

void NetworkStateHandler::AppendTetherNetworksToList(bool get_active,
                                                     size_t limit,
                                                     NetworkStateList* list) {
  DCHECK(list);
  DCHECK_NE(0U, limit);
  if (!IsTechnologyEnabled(NetworkTypePattern::Tether())) {
    return;
  }

  for (auto iter = tether_network_list_.begin();
       iter != tether_network_list_.end() && list->size() < limit; ++iter) {
    const NetworkState* network = (*iter)->AsNetworkState();
    DCHECK(network);
    if (network->IsConnectingOrConnected() != get_active) {
      continue;
    }
    if (!ShouldIncludeNetworkInList(network, false /* configured_only */,
                                    false /* visible_only */)) {
      continue;
    }
    list->push_back(network);
  }
}

const NetworkState* NetworkStateHandler::GetNetworkStateFromServicePath(
    const std::string& service_path,
    bool configured_only) const {
  ManagedState* managed =
      GetModifiableManagedState(&network_list_, service_path);
  if (!managed) {
    managed = GetModifiableManagedState(&tether_network_list_, service_path);
    if (!managed) {
      return nullptr;
    }
  }
  const NetworkState* network = managed->AsNetworkState();
  DCHECK(network);
  if (!network->update_received() ||
      (configured_only && !network->IsInProfile())) {
    return nullptr;
  }
  return network;
}

const NetworkState* NetworkStateHandler::GetNetworkStateFromGuid(
    const std::string& guid) const {
  DCHECK(!guid.empty());
  return GetModifiableNetworkStateFromGuid(guid);
}

void NetworkStateHandler::AddTetherNetworkState(const std::string& guid,
                                                const std::string& name,
                                                const std::string& carrier,
                                                int battery_percentage,
                                                int signal_strength,
                                                bool has_connected_to_host) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  DCHECK(!guid.empty());
  DCHECK(battery_percentage >= 0 && battery_percentage <= 100);
  DCHECK(signal_strength >= 0 && signal_strength <= 100);

  if (tether_technology_state_ != TECHNOLOGY_ENABLED) {
    NET_LOG(ERROR) << "AddTetherNetworkState() called when Tether networks "
                   << "are not enabled. Cannot add NetworkState.";
    return;
  }

  // If the network already exists, do nothing.
  if (GetNetworkStateFromGuid(guid)) {
    NET_LOG(ERROR) << "AddTetherNetworkState: " << name
                   << " called with existing guid:" << guid;
    return;
  }

  // Use the GUID as the network's service path.
  std::unique_ptr<NetworkState> tether_network_state =
      std::make_unique<NetworkState>(guid /* path */);

  tether_network_state->set_name(name);
  tether_network_state->set_type(kTypeTether);
  tether_network_state->SetGuid(guid);
  tether_network_state->set_visible(true);
  tether_network_state->set_update_received();
  tether_network_state->set_update_requested(false);
  tether_network_state->set_connectable(true);
  tether_network_state->set_tether_carrier(carrier);
  tether_network_state->set_battery_percentage(battery_percentage);
  tether_network_state->set_tether_has_connected_to_host(has_connected_to_host);
  tether_network_state->set_signal_strength(signal_strength);

  tether_network_list_.push_back(std::move(tether_network_state));
  network_list_sorted_ = false;

  NotifyNetworkListChanged();
}

bool NetworkStateHandler::UpdateTetherNetworkProperties(
    const std::string& guid,
    const std::string& carrier,
    int battery_percentage,
    int signal_strength) {
  if (tether_technology_state_ != TECHNOLOGY_ENABLED) {
    NET_LOG(ERROR) << "UpdateTetherNetworkProperties() called when Tether "
                   << "networks are not enabled. Cannot update.";
    return false;
  }

  NetworkState* tether_network_state = GetModifiableNetworkStateFromGuid(guid);
  if (!tether_network_state) {
    NET_LOG(ERROR) << "UpdateTetherNetworkProperties(): No NetworkState for "
                   << "Tether network with GUID \"" << guid << "\".";
    return false;
  }

  tether_network_state->set_tether_carrier(carrier);
  tether_network_state->set_battery_percentage(battery_percentage);
  tether_network_state->set_signal_strength(signal_strength);
  network_list_sorted_ = false;

  NotifyNetworkPropertiesUpdated(tether_network_state);
  if (tether_network_state->IsConnectingOrConnected()) {
    NotifyIfActiveNetworksChanged();
  }
  return true;
}

bool NetworkStateHandler::SetTetherNetworkHasConnectedToHost(
    const std::string& guid) {
  NetworkState* tether_network_state = GetModifiableNetworkStateFromGuid(guid);
  if (!tether_network_state) {
    NET_LOG(ERROR) << "SetTetherNetworkHasConnectedToHost(): No NetworkState "
                   << "for Tether network with GUID \"" << guid << "\".";
    return false;
  }

  if (tether_network_state->tether_has_connected_to_host()) {
    return false;
  }

  tether_network_state->set_tether_has_connected_to_host(true);
  network_list_sorted_ = false;

  NotifyNetworkPropertiesUpdated(tether_network_state);
  return true;
}

bool NetworkStateHandler::RemoveTetherNetworkState(const std::string& guid) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  CHECK(!notifying_network_observers_);
  for (auto iter = tether_network_list_.begin();
       iter != tether_network_list_.end(); ++iter) {
    if (iter->get()->AsNetworkState()->guid() == guid) {
      const NetworkState* tether_network = iter->get()->AsNetworkState();
      bool was_active = tether_network->IsConnectingOrConnected();
      NetworkState* wifi_network =
          GetModifiableNetworkStateFromGuid(tether_network->tether_guid());
      if (wifi_network) {
        wifi_network->set_tether_guid(std::string());
      }
      tether_network_list_.erase(iter);

      if (was_active) {
        NotifyIfActiveNetworksChanged();
      }
      NotifyNetworkListChanged();
      return true;
    }
  }
  return false;
}

bool NetworkStateHandler::DisassociateTetherNetworkStateFromWifiNetwork(
    const std::string& tether_network_guid) {
  NetworkState* tether_network_state =
      GetModifiableNetworkStateFromGuid(tether_network_guid);

  if (!tether_network_state) {
    NET_LOG(ERROR) << "DisassociateTetherNetworkStateWithWifiNetwork(): Tether "
                   << "network with ID " << tether_network_guid
                   << " not registered; could not remove association.";
    return false;
  }

  std::string wifi_network_guid = tether_network_state->tether_guid();
  NetworkState* wifi_network_state =
      GetModifiableNetworkStateFromGuid(wifi_network_guid);

  if (!wifi_network_state) {
    NET_LOG(ERROR) << "DisassociateTetherNetworkStateWithWifiNetwork(): Wi-Fi "
                   << "network with ID " << wifi_network_guid
                   << " not registered; could not remove association.";
    return false;
  }

  if (wifi_network_state->tether_guid().empty() &&
      tether_network_state->tether_guid().empty()) {
    return true;
  }

  wifi_network_state->set_tether_guid(std::string());
  tether_network_state->set_tether_guid(std::string());
  network_list_sorted_ = false;

  NotifyNetworkPropertiesUpdated(wifi_network_state);
  NotifyNetworkPropertiesUpdated(tether_network_state);

  return true;
}

bool NetworkStateHandler::AssociateTetherNetworkStateWithWifiNetwork(
    const std::string& tether_network_guid,
    const std::string& wifi_network_guid) {
  if (tether_technology_state_ != TECHNOLOGY_ENABLED) {
    NET_LOG(ERROR) << "AssociateTetherNetworkStateWithWifiNetwork() called "
                   << "when Tether networks are not enabled. Cannot "
                   << "associate.";
    return false;
  }

  NetworkState* tether_network_state =
      GetModifiableNetworkStateFromGuid(tether_network_guid);
  if (!tether_network_state) {
    NET_LOG(ERROR) << "Tether network does not exist: " << tether_network_guid;
    return false;
  }
  if (!NetworkTypePattern::Tether().MatchesType(tether_network_state->type())) {
    NET_LOG(ERROR) << "Network is not a Tether network: "
                   << tether_network_guid;
    return false;
  }

  NetworkState* wifi_network_state =
      GetModifiableNetworkStateFromGuid(wifi_network_guid);
  if (!wifi_network_state) {
    NET_LOG(ERROR) << "Wi-Fi Network does not exist: " << wifi_network_guid;
    return false;
  }
  if (!NetworkTypePattern::WiFi().MatchesType(wifi_network_state->type())) {
    NET_LOG(ERROR) << "Network is not a W-Fi network: "
                   << NetworkId(wifi_network_state);
    return false;
  }

  if (wifi_network_state->tether_guid() == tether_network_guid &&
      tether_network_state->tether_guid() == wifi_network_guid) {
    return true;
  }

  tether_network_state->set_tether_guid(wifi_network_guid);
  wifi_network_state->set_tether_guid(tether_network_guid);
  network_list_sorted_ = false;

  NotifyNetworkPropertiesUpdated(wifi_network_state);
  NotifyNetworkPropertiesUpdated(tether_network_state);

  return true;
}

void NetworkStateHandler::SetTetherNetworkStateDisconnected(
    const std::string& guid) {
  SetTetherNetworkStateConnectionState(guid, shill::kStateIdle);
}

void NetworkStateHandler::SetTetherNetworkStateConnecting(
    const std::string& guid) {
  // The default network should only be set if there currently is no default
  // network. Otherwise, the default network should not change unless the
  // connection completes successfully and the newly-connected network is
  // prioritized higher than the existing default network. Note that, in
  // general, a connected Ethernet network is still considered the default
  // network even if a Tether or Wi-Fi network becomes connected.
  if (default_network_path_.empty()) {
    NET_LOG(EVENT) << "Connecting to Tether network when there is currently no "
                   << "default network; setting as new default network. GUID: "
                   << guid;
    SetDefaultNetworkValues(guid, /*metered=*/true);
  }

  SetTetherNetworkStateConnectionState(guid, shill::kStateConfiguration);
}

void NetworkStateHandler::SetTetherNetworkStateConnected(
    const std::string& guid) {
  // Being connected implies that AssociateTetherNetworkStateWithWifiNetwork()
  // was already called, so ensure that the association is still intact.
  // TODO(b/278966899): Promote this to a CHECK.
  DCHECK(GetNetworkStateFromGuid(GetNetworkStateFromGuid(guid)->tether_guid())
             ->tether_guid() == guid);

  // At this point, there should be a default network set.
  // TODO(b/279047073): We can hit this due to a race between
  // `SetTetherNetworkStateConnected` and `DefaultNetworkServiceChange`.
  DCHECK(!default_network_path_.empty());

  SetTetherNetworkStateConnectionState(guid, shill::kStateOnline);
}

void NetworkStateHandler::SetTetherNetworkStateConnectionState(
    const std::string& guid,
    const std::string& connection_state) {
  NetworkState* tether_network_state = GetModifiableNetworkStateFromGuid(guid);
  if (!tether_network_state) {
    NET_LOG(ERROR) << "SetTetherNetworkStateConnectionState: Tether network "
                   << "not found: " << guid;
    return;
  }

  DCHECK(
      NetworkTypePattern::Tether().MatchesType(tether_network_state->type()));

  std::string prev_connection_state = tether_network_state->connection_state();
  tether_network_state->SetConnectionState(connection_state);
  network_list_sorted_ = false;

  if (ConnectionStateChanged(tether_network_state, prev_connection_state)) {
    NET_LOG(EVENT) << "Changing connection state for Tether network with GUID "
                   << guid << ". Old state: " << prev_connection_state << ", "
                   << "New state: " << connection_state;
    if (!tether_network_state->IsConnectingOrConnected() &&
        tether_network_state->path() == default_network_path_) {
      SetDefaultNetworkValues(/*path=*/std::string(), /*metered=*/false);
      NotifyDefaultNetworkChanged(kReasonTether);
    }
    OnNetworkConnectionStateChanged(tether_network_state);
    NotifyNetworkPropertiesUpdated(tether_network_state);
  }
}

void NetworkStateHandler::EnsureTetherDeviceState() {
  bool should_be_present =
      tether_technology_state_ != TechnologyState::TECHNOLOGY_UNAVAILABLE;

  for (auto it = device_list_.begin(); it < device_list_.end(); ++it) {
    std::string path = (*it)->path();
    if (path == kTetherDevicePath) {
      // If the Tether DeviceState is in the list and it should not be, remove
      // it and return. If it is in the list and it should be, the list is
      // already valid, so return without removing it.
      if (!should_be_present) {
        device_list_.erase(it);
      }
      return;
    }
  }

  if (!should_be_present) {
    // If the Tether DeviceState was not in the list and it should not be, the
    // list is already valid, so return.
    return;
  }

  // The Tether DeviceState is not present in the list, but it should be. Since
  // Tether networks are not recognized by Shill, they will never receive an
  // update, so set properties on the state here.
  std::unique_ptr<ManagedState> tether_device_state = ManagedState::Create(
      ManagedState::ManagedType::MANAGED_TYPE_DEVICE, kTetherDevicePath);
  tether_device_state->set_update_received();
  tether_device_state->set_update_requested(false);
  tether_device_state->set_name(kTetherDeviceName);
  tether_device_state->set_type(kTypeTether);

  device_list_.push_back(std::move(tether_device_state));
}

bool NetworkStateHandler::UpdateBlockedByPolicy(NetworkState* network) const {
  bool is_wifi_type = TypeMatches(network, NetworkTypePattern::WiFi());
  bool is_cellular_type = TypeMatches(network, NetworkTypePattern::Cellular());
  if (!is_wifi_type && !is_cellular_type) {
    return false;
  }

  bool prev_blocked_by_policy = network->blocked_by_policy();
  bool blocked_by_policy = false;
  if (is_wifi_type) {
    blocked_by_policy =
        !network->IsManagedByPolicy() &&
        (OnlyManagedWifiNetworksAllowed() ||
         base::Contains(blocked_hex_ssids_, network->GetHexSsid()));
  } else {
    blocked_by_policy = !network->IsManagedByPolicy() &&
                        allow_only_policy_cellular_networks_to_connect_;
  }
  network->set_blocked_by_policy(blocked_by_policy);
  return prev_blocked_by_policy != blocked_by_policy;
}

void NetworkStateHandler::UpdateManagedWifiNetworkAvailable() {
  DeviceState* device =
      GetModifiableDeviceStateByType(NetworkTypePattern::WiFi());
  if (!device || !device->update_received()) {
    return;  // May be null in tests.
  }

  const std::string prev_available_managed_network_path =
      device->available_managed_network_path();
  std::string available_managed_network_path;

  NetworkStateHandler::NetworkStateList networks;
  GetNetworkListByType(NetworkTypePattern::WiFi(), true, true, 0, &networks);
  for (const NetworkState* network : networks) {
    if (network->IsManagedByPolicy()) {
      available_managed_network_path = network->path();
      break;
    }
  }

  if (prev_available_managed_network_path != available_managed_network_path) {
    device->set_available_managed_network_path(available_managed_network_path);
    UpdateBlockedNetworksInternal(NetworkTypePattern::WiFi());
    NotifyDevicePropertiesUpdated(device);
  }
}

void NetworkStateHandler::UpdateBlockedNetworksInternal(
    const NetworkTypePattern& network_type) {
  for (auto iter = network_list_.begin(); iter != network_list_.end(); ++iter) {
    NetworkState* network = (*iter)->AsNetworkState();
    if (!TypeMatches(network, network_type)) {
      continue;
    }
    if (UpdateBlockedByPolicy(network)) {
      NotifyNetworkPropertiesUpdated(network);
    }
  }
}

void NetworkStateHandler::GetDeviceList(DeviceStateList* list) const {
  GetDeviceListByType(NetworkTypePattern::Default(), list);
}

void NetworkStateHandler::GetDeviceListByType(const NetworkTypePattern& type,
                                              DeviceStateList* list) const {
  DCHECK(list);
  list->clear();

  for (auto iter = device_list_.begin(); iter != device_list_.end(); ++iter) {
    const DeviceState* device = (*iter)->AsDeviceState();
    DCHECK(device);
    if (device->update_received() && device->Matches(type)) {
      list->push_back(device);
    }
  }
}

void NetworkStateHandler::RequestScan(const NetworkTypePattern& type) {
  NET_LOG(USER) << "RequestScan: " << type.ToDebugString();
  if (type.MatchesPattern(NetworkTypePattern::WiFi())) {
    if (IsTechnologyEnabled(NetworkTypePattern::WiFi())) {
      shill_property_handler_->RequestScanByType(shill::kTypeWifi);
    } else if (type.Equals(NetworkTypePattern::WiFi())) {
      return;  // Skip notify if disabled and wifi only requested.
    }
  }

  if (type.Equals(NetworkTypePattern::Cellular())) {
    // Only request a Cellular scan if Cellular is requested explicitly.
    if (IsTechnologyEnabled(NetworkTypePattern::Cellular())) {
      shill_property_handler_->RequestScanByType(shill::kTypeCellular);
    } else {
      return;  // Skip notify if disabled and cellular only requested.
    }
  }

  // Note: for Tether we initiate the scan in the observer.
  NotifyScanRequested(type);
}

void NetworkStateHandler::RequestUpdateForNetwork(
    const std::string& service_path) {
  NetworkState* network = GetModifiableNetworkState(service_path);
  if (network) {
    // Do not request properties for networks which are not backed by Shill.
    if (network->IsNonProfileType()) {
      return;
    }
    // Do not request properties if a condition has already triggered a request.
    if (network->update_requested()) {
      return;
    }
    network->set_update_requested(true);
    NET_LOG(EVENT) << "RequestUpdate for: " << NetworkId(network);
  } else {
    NET_LOG(EVENT) << "RequestUpdate for: " << NetworkPathId(service_path);
  }
  shill_property_handler_->RequestProperties(ManagedState::MANAGED_TYPE_NETWORK,
                                             service_path);
}

void NetworkStateHandler::RequestUpdateForDevice(
    const std::string& device_path) {
  DeviceState* device = GetModifiableDeviceState(device_path);
  if (!device) {
    return;
  }

  device->set_update_requested(true);
  NET_LOG(EVENT) << "Request update for device path: " << device_path;
  shill_property_handler_->RequestProperties(ManagedState::MANAGED_TYPE_DEVICE,
                                             device_path);
  device_paths_with_stale_properties_.erase(device_path);
}

void NetworkStateHandler::SendUpdateNotificationForNetwork(
    const std::string& service_path) {
  const NetworkState* network = GetNetworkState(service_path);
  if (!network) {
    return;
  }
  NotifyNetworkPropertiesUpdated(network);
}

void NetworkStateHandler::ClearLastErrorForNetwork(
    const std::string& service_path) {
  NetworkState* network = GetModifiableNetworkState(service_path);
  if (network) {
    network->ClearError();
  }
}

void NetworkStateHandler::SetWakeOnLanEnabled(bool enabled) {
  NET_LOG(EVENT) << "SetWakeOnLanEnabled: " << enabled;
  shill_property_handler_->SetWakeOnLanEnabled(enabled);
}

void NetworkStateHandler::SetHostname(const std::string& hostname) {
  NET_LOG(EVENT) << "SetHostname";
  shill_property_handler_->SetHostname(hostname);
}

void NetworkStateHandler::SetNetworkThrottlingStatus(
    bool enabled,
    uint32_t upload_rate_kbits,
    uint32_t download_rate_kbits) {
  if (enabled) {
    NET_LOG(EVENT) << "SetNetworkThrottlingStatus: Enabled: "
                   << upload_rate_kbits << ", " << download_rate_kbits;
  } else {
    NET_LOG(EVENT) << "SetNetworkThrottlingStatus: Disabled.";
  }
  shill_property_handler_->SetNetworkThrottlingStatus(
      enabled, upload_rate_kbits, download_rate_kbits);
}

void NetworkStateHandler::SetFastTransitionStatus(bool enabled) {
  NET_LOG(USER) << "SetFastTransitionStatus: " << enabled;
  shill_property_handler_->SetFastTransitionStatus(enabled);
}

void NetworkStateHandler::RequestPortalDetection() {
  const NetworkState* default_network = DefaultNetwork();
  if (!default_network) {
    NET_LOG(DEBUG) << "RequestPortalDetection skipped, no default network.";
    return;
  }
  if (default_network->IsOnline()) {
    NET_LOG(DEBUG) << "RequestPortalDetection skipped for online network: "
                   << NetworkId(default_network);
    return;
  }
  NET_LOG(USER) << "RequestPortalDetection for " << NetworkId(default_network);
  shill_property_handler_->RequestPortalDetection(default_network_path_);
}

const NetworkState* NetworkStateHandler::GetEAPForEthernet(
    const std::string& service_path,
    bool connected_only) {
  const NetworkState* network = GetNetworkState(service_path);
  if (!network) {
    NET_LOG(ERROR) << "GetEAPForEthernet: Unknown service: "
                   << NetworkPathId(service_path);
    return nullptr;
  }
  if (network->type() != shill::kTypeEthernet) {
    NET_LOG(ERROR) << "GetEAPForEthernet: Not Ethernet: " << NetworkId(network);
    return nullptr;
  }
  if (connected_only) {
    if (!network->IsConnectedState()) {
      NET_LOG(DEBUG) << "GetEAPForEthernet: Not connected.";
      return nullptr;
    }

    // The same EAP service is shared for all ethernet services/devices.
    // However EAP is used/enabled per device and only if the connection was
    // successfully established.
    const DeviceState* device = GetDeviceState(network->device_path());
    if (!device) {
      NET_LOG(ERROR) << "GetEAPForEthernet: Unknown device "
                     << network->device_path()
                     << " for connected ethernet service: "
                     << NetworkId(network);
      return nullptr;
    }
    if (!device->eap_authentication_completed()) {
      NET_LOG(DEBUG) << "GetEAPForEthernet: EAP Authenticaiton not completed.";
      return nullptr;
    }
  }

  NetworkStateList list;
  GetNetworkListByType(NetworkTypePattern::Primitive(shill::kTypeEthernetEap),
                       true /* configured_only */, false /* visible_only */,
                       1 /* limit */, &list);
  if (list.empty()) {
    if (connected_only) {
      NET_LOG(ERROR)
          << "GetEAPForEthernet: Connected using EAP but no EAP service found: "
          << NetworkId(network);
    }
    return nullptr;
  }
  return list.front();
}

void NetworkStateHandler::SetErrorForTest(const std::string& service_path,
                                          const std::string& error) {
  NetworkState* network_state = GetModifiableNetworkState(service_path);
  if (!network_state) {
    NET_LOG(ERROR) << "No matching NetworkState for: "
                   << NetworkPathId(service_path);
    return;
  }
  network_state->last_error_ = error;
}

void NetworkStateHandler::SetDeviceStateUpdatedForTest(
    const std::string& device_path) {
  DeviceState* device = GetModifiableDeviceState(device_path);
  DCHECK(device);
  device->set_update_received();
}

//------------------------------------------------------------------------------
// ShillPropertyHandler::Delegate overrides

void NetworkStateHandler::UpdateManagedList(ManagedState::ManagedType type,
                                            const base::Value::List& entries) {
  CHECK(!notifying_network_observers_);

  ManagedStateList* managed_list = GetManagedList(type);
  NET_LOG(DEBUG) << "UpdateManagedList: " << ManagedState::TypeToString(type)
                 << ": " << entries.size();
  // Create a map of existing entries. Assumes all entries in |managed_list|
  // are unique.
  std::map<std::string, std::unique_ptr<ManagedState>> managed_map;
  for (auto& item : *managed_list) {
    std::string path = item->path();
    DCHECK(!base::Contains(managed_map, path));
    managed_map[path] = std::move(item);
  }
  // Clear the list (objects are temporarily owned by managed_map).
  managed_list->clear();
  // Updates managed_list and request updates for new entries.
  std::set<std::string> list_entries;
  for (const auto& iter : entries) {
    const std::string* path = iter.GetIfString();
    if (!path) {
      continue;
    }
    if (!path || (*path).empty() || *path == shill::kFlimflamServicePath) {
      NET_LOG(ERROR) << "Bad path in type " << type << " Path: " << *path;
      continue;
    }
    auto found = managed_map.find(*path);
    if (found == managed_map.end()) {
      if (list_entries.count(*path) != 0) {
        NET_LOG(ERROR) << "Duplicate entry in list for " << *path;
        continue;
      }
      managed_list->push_back(ManagedState::Create(type, *path));
    } else {
      managed_list->push_back(std::move(found->second));
      managed_map.erase(found);
    }
    list_entries.insert(*path);
  }

  if (type == ManagedState::ManagedType::MANAGED_TYPE_DEVICE) {
    // Also move the Tether DeviceState if it exists. This will not happen as
    // part of the loop above since |entries| will never contain the Tether
    // path.
    auto iter = managed_map.find(kTetherDevicePath);
    if (iter != managed_map.end()) {
      managed_list->push_back(std::move(iter->second));
      managed_map.erase(iter);
    }
  }

  UpdateManagedWifiNetworkAvailable();
  UpdateBlockedCellularNetworks();
  if (type != ManagedState::ManagedType::MANAGED_TYPE_NETWORK) {
    return;
  }

  // Non-Shill services are added in Chrome and is not present in |entries|.
  // Add these services back to managed_list.
  for (auto iter = managed_map.begin(); iter != managed_map.end();) {
    NetworkState* network = iter->second->AsNetworkState();
    if (!network->IsNonShillCellularNetwork()) {
      iter++;
      continue;
    }
    managed_list->push_back(std::move(iter->second));
    iter = managed_map.erase(iter);
  }

  // Network list is explicitly sorted in ManagedListChanged() which is notified
  // after this method. But this ensures that any intervening calls to
  // GetNetworkList* methods will use the sorted list.
  network_list_sorted_ = false;

  // Remove associations Tether NetworkStates had with now removed Wi-Fi
  // NetworkStates.
  for (auto& iter : managed_map) {
    ManagedState* managed = iter.second.get();
    if (!TypeMatches(managed, NetworkTypePattern::WiFi())) {
      continue;
    }
    NetworkState* tether_network = GetModifiableNetworkStateFromGuid(
        managed->AsNetworkState()->tether_guid());
    if (tether_network) {
      tether_network->set_tether_guid(std::string());
    }
  }
}

void NetworkStateHandler::UpdateBlockedCellularNetworks() {
  DeviceState* device =
      GetModifiableDeviceStateByType(NetworkTypePattern::Cellular());
  if (!device || !device->update_received()) {
    return;  // May be null in tests.
  }

  UpdateBlockedNetworksInternal(NetworkTypePattern::Cellular());
}

void NetworkStateHandler::ProfileListChanged(
    const base::Value::List& profile_list) {
  NET_LOG(EVENT) << "ProfileListChanged. Re-Requesting Network Properties";
  ProcessIsUserLoggedIn(profile_list);
  for (ManagedStateList::iterator iter = network_list_.begin();
       iter != network_list_.end(); ++iter) {
    const NetworkState* network = (*iter)->AsNetworkState();
    DCHECK(network);

    // Do not request properties for networks which are not backed by Shill.
    if (network->IsNonProfileType()) {
      continue;
    }

    shill_property_handler_->RequestProperties(
        ManagedState::MANAGED_TYPE_NETWORK, network->path());
  }
}

void NetworkStateHandler::UpdateManagedStateProperties(
    ManagedState::ManagedType type,
    const std::string& path,
    const base::Value::Dict& properties) {
  ManagedStateList* managed_list = GetManagedList(type);
  ManagedState* managed = GetModifiableManagedState(managed_list, path);
  if (!managed) {
    // The network has been removed from the list of networks.
    NET_LOG(DEBUG) << "UpdateManagedStateProperties: Not found: " << path;
    return;
  }
  managed->set_update_received();

  NET_LOG(EVENT) << GetManagedStateLogType(managed)
                 << " Properties Received: " << GetLogName(managed);

  if (type == ManagedState::MANAGED_TYPE_NETWORK) {
    UpdateNetworkStateProperties(managed->AsNetworkState(), properties);
    managed->set_update_requested(false);
    return;
  }

  // Device
  for (const auto iter : properties) {
    managed->PropertyChanged(iter.first, iter.second);
  }
  managed->InitialPropertiesReceived(properties);
  managed->set_update_requested(false);

  if (device_paths_with_stale_properties_.find(path) !=
      device_paths_with_stale_properties_.end()) {
    RequestUpdateForDevice(path);
  }
}

void NetworkStateHandler::UpdateNetworkStateProperties(
    NetworkState* network,
    const base::Value::Dict& properties) {
  DCHECK(network);
  bool network_property_updated = false;
  std::string prev_connection_state = network->connection_state();
  bool metered = false;
  bool had_icccid_before_update = !network->iccid().empty();
  for (const auto iter : properties) {
    if (network->PropertyChanged(iter.first, iter.second)) {
      network_property_updated = true;
    }
    if (iter.first == shill::kMeteredProperty) {
      metered = iter.second.is_bool() && iter.second.GetBool();
    }
  }
  if (network->path() == default_network_path_) {
    default_network_is_metered_ = metered && network->IsConnectedState();
  }

  if (network->Matches(NetworkTypePattern::WiFi() |
                       NetworkTypePattern::Cellular())) {
    network_property_updated |= UpdateBlockedByPolicy(network);
  }
  network_property_updated |= network->InitialPropertiesReceived(properties);

  UpdateGuid(network);

  network_list_sorted_ = false;

  if (network->Matches(NetworkTypePattern::Cellular())) {
    HandleCellularNetworkUpdateReceived(network, had_icccid_before_update);
  }

  // Notify observers of NetworkState changes.
  if (network_property_updated || network->update_requested()) {
    // Signal connection state changed after all properties have been updated.
    if (ConnectionStateChanged(network, prev_connection_state)) {
      // Also notifies that the default network changed if this is the default.
      OnNetworkConnectionStateChanged(network);
    } else if (network->path() == default_network_path_ &&
               network->IsActive()) {
      // Always notify that the default network changed for a complete update.
      NET_LOG(DEBUG) << "UpdateNetworkStateProperties for default: "
                     << NetworkId(network);
      NotifyDefaultNetworkChanged(kReasonUpdate);
    }
    NotifyNetworkPropertiesUpdated(network);
  }
}

void NetworkStateHandler::UpdateNetworkServiceProperty(
    const std::string& service_path,
    const std::string& key,
    const base::Value& value) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  SCOPED_NET_LOG_IF_SLOW();
  bool changed = false;
  NetworkState* network = GetModifiableNetworkState(service_path);
  if (!network || !network->update_received()) {
    // Shill may send a service property update before processing Chrome's
    // initial GetProperties request. If this occurs, the initial request will
    // include the changed property value so we can ignore this update.
    return;
  }
  std::string prev_connection_state = network->connection_state();
  std::string prev_profile_path = network->profile_path();
  bool had_icccid_before_update = !network->iccid().empty();
  changed |= network->PropertyChanged(key, value);
  changed |= UpdateBlockedByPolicy(network);
  if (!changed) {
    return;
  }

  // If added or removed from a Profile, request a full update so that a
  // NetworkState gets created.
  bool request_update = prev_profile_path != network->profile_path();
  bool sort_networks = false;
  bool notify_default = network->path() == default_network_path_;
  bool notify_connection_state = false;
  bool notify_active = false;

  if (key == shill::kStateProperty || key == shill::kVisibleProperty) {
    network_list_sorted_ = false;
    if (ConnectionStateChanged(network, prev_connection_state)) {
      notify_connection_state = true;
      notify_active = true;
      if (notify_default) {
        notify_default = VerifyDefaultNetworkConnectionStateChange(network);
      }
      // If the default network connection state changed, sort networks now
      // and ensure that a default cellular network exists.
      if (notify_default) {
        sort_networks = true;
      }

      // If the connection state changes, other properties such as IPConfig
      // may have changed, so request a full update.
      request_update = true;
    }
  } else if (key == shill::kActivationStateProperty) {
    // Activation state may affect "connecting" state in the UI.
    notify_connection_state = true;
    network_list_sorted_ = false;
  }

  if (network->Matches(NetworkTypePattern::Cellular())) {
    HandleCellularNetworkUpdateReceived(network, had_icccid_before_update);
  }

  if (request_update) {
    RequestUpdateForNetwork(service_path);
    notify_default = false;  // Notify will occur when properties are received.
  }

  const std::string* value_str = value.GetIfString();
  if (key == shill::kSignalStrengthProperty || key == shill::kWifiBSsid ||
      key == shill::kWifiFrequency ||
      key == shill::kWifiFrequencyListProperty ||
      key == shill::kNetworkTechnologyProperty ||
      (key == shill::kDeviceProperty && value_str && *value_str == "/")) {
    // Uninteresting update. This includes 'Device' property changes to "/"
    // (occurs just before a service is removed).
    // For non active networks do not log or send any notifications.
    if (!network->IsActive()) {
      return;
    }
    // Otherwise do not trigger 'default network changed'.
    notify_default = false;
    // Notify signal strength and network technology changes for active
    // networks.
    if (key == shill::kSignalStrengthProperty ||
        key == shill::kNetworkTechnologyProperty) {
      notify_active = true;
    }
  }

  LogPropertyUpdated(network, key, value);
  if (notify_connection_state) {
    NotifyNetworkConnectionStateChanged(network);
  }
  if (notify_default) {
    std::stringstream logstream;
    logstream << std::string(kReasonUpdate) << ":" << key << "=" << value;
    NotifyDefaultNetworkChanged(logstream.str());
  }
  if (notify_active) {
    NotifyIfActiveNetworksChanged();
  }
  NotifyNetworkPropertiesUpdated(network);
  if (sort_networks) {
    bool network_list_changed = AddOrRemoveStubCellularNetworks();
    SortNetworkList();
    if (network_list_changed) {
      NotifyNetworkListChanged();
    }
  }
}

void NetworkStateHandler::UpdateDeviceProperty(const std::string& device_path,
                                               const std::string& key,
                                               const base::Value& value) {
  SCOPED_NET_LOG_IF_SLOW();
  DeviceState* device = GetModifiableDeviceState(device_path);
  if (!device) {
    return;
  }

  if (!device->update_received()) {
    device_paths_with_stale_properties_.insert(device_path);
    return;
  }

  const bool was_scanning = device->scanning();
  if (!device->PropertyChanged(key, value)) {
    return;
  }

  LogPropertyUpdated(device, key, value);
  NotifyDevicePropertiesUpdated(device);

  if (key == shill::kScanningProperty && was_scanning != device->scanning()) {
    if (device->scanning()) {
      NotifyScanStarted(device);
    } else {
      NotifyScanCompleted(device);
    }

    if (device->type() == shill::kTypeWifi && !device->scanning()) {
      UpdateManagedWifiNetworkAvailable();
    }
    if (device->type() == shill::kTypeCellular && !device->scanning()) {
      UpdateBlockedCellularNetworks();
    }
  }
  if (key == shill::kEapAuthenticationCompletedProperty) {
    // Notify a change for each Ethernet service using this device.
    NetworkStateList ethernet_services;
    GetNetworkListByType(NetworkTypePattern::Ethernet(),
                         false /* configured_only */, false /* visible_only */,
                         0 /* no limit */, &ethernet_services);
    for (NetworkStateList::const_iterator it = ethernet_services.begin();
         it != ethernet_services.end(); ++it) {
      const NetworkState* ethernet_service = *it;
      if (ethernet_service->update_received() ||
          ethernet_service->device_path() != device->path()) {
        continue;
      }
      RequestUpdateForNetwork(ethernet_service->path());
    }
  }
  if (key == shill::kSIMSlotInfoProperty) {
    // Change in SIM Slot info can result in changes to stub cellular services.
    SyncStubCellularNetworks();
  }
}

void NetworkStateHandler::UpdateIPConfigProperties(
    ManagedState::ManagedType type,
    const std::string& path,
    const std::string& ip_config_path,
    base::Value::Dict properties) {
  if (type == ManagedState::MANAGED_TYPE_NETWORK) {
    NetworkState* network = GetModifiableNetworkState(path);
    if (!network) {
      return;
    }
    network->IPConfigPropertiesChanged(properties);
    NotifyNetworkPropertiesUpdated(network);
    if (network->path() == default_network_path_) {
      NotifyDefaultNetworkChanged(kReasonUpdateIPConfig);
    }
    if (network->IsActive()) {
      NotifyIfActiveNetworksChanged();
    }
  } else if (type == ManagedState::MANAGED_TYPE_DEVICE) {
    DeviceState* device = GetModifiableDeviceState(path);
    if (!device) {
      return;
    }
    device->IPConfigPropertiesChanged(ip_config_path, std::move(properties));
    NotifyDevicePropertiesUpdated(device);
    if (!default_network_path_.empty()) {
      const NetworkState* default_network =
          GetNetworkState(default_network_path_);
      if (default_network && default_network->device_path() == path) {
        NotifyNetworkPropertiesUpdated(default_network);
        NotifyDefaultNetworkChanged(kReasonUpdateDeviceIPConfig);
      }
    }
  }
}

void NetworkStateHandler::CheckPortalListChanged(
    const std::string& check_portal_list) {
  check_portal_list_ = check_portal_list;
}

void NetworkStateHandler::HostnameChanged(const std::string& hostname) {
  NET_LOG(EVENT) << "HostnameChanged";
  hostname_ = hostname;
  for (Observer& observer : observers_) {
    observer.HostnameChanged(hostname);
  }
}

void NetworkStateHandler::TechnologyListChanged() {
  // Eventually we would like to replace Technology state with Device state.
  // For now, treat technology state changes as device list changes.
  NotifyDeviceListChanged();

  // Stub cellular networks can be affected by cellular technology state.
  SyncStubCellularNetworks();
}

void NetworkStateHandler::ManagedStateListChanged(
    ManagedState::ManagedType type) {
  SCOPED_NET_LOG_IF_SLOW();
  switch (type) {
    case ManagedState::MANAGED_TYPE_NETWORK:
      AddOrRemoveStubCellularNetworks();
      SortNetworkList();
      NotifyIfActiveNetworksChanged();
      NotifyNetworkListChanged();
      UpdateBlockedCellularNetworks();
      UpdateManagedWifiNetworkAvailable();
      // ManagedStateListChanged only gets executed if all pending updates have
      // completed. Profile networks are loaded if a user is logged in and all
      // pending updates are complete.
      is_profile_networks_loaded_ = is_user_logged_in_;
      return;
    case ManagedState::MANAGED_TYPE_DEVICE:
      std::string devices;
      for (auto iter = device_list_.begin(); iter != device_list_.end();
           ++iter) {
        if (iter != device_list_.begin()) {
          devices += ", ";
        }
        devices += (*iter)->name();
      }
      NET_LOG(EVENT) << "DeviceList: " << devices;
      NotifyDeviceListChanged();
      // A change to the device list may affect the default Cellular network.
      SyncStubCellularNetworks();
      return;
  }
  NOTREACHED_IN_MIGRATION();
}

void NetworkStateHandler::SortNetworkList() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  if (tether_sort_delegate_) {
    tether_sort_delegate_->SortTetherNetworkList(&tether_network_list_);
  }

  // Note: usually active networks will precede inactive networks, however
  // this may briefly be untrue during state transitions (e.g. a network may
  // transition to idle before the list is updated). Also separate inactive
  // Mobile and VPN networks (see below).
  ManagedStateList active, non_wifi_visible, wifi_visible, hidden, new_networks;
  for (ManagedStateList::iterator iter = network_list_.begin();
       iter != network_list_.end(); ++iter) {
    NetworkState* network = (*iter)->AsNetworkState();
    // NetworkState entries are created when they appear in the list, but the
    // details are not populated until an update is received.
    if (!network->update_received()) {
      new_networks.push_back(std::move(*iter));
      continue;
    }
    if (network->IsActive()) {
      active.push_back(std::move(*iter));
      continue;
    }
    if (!network->visible()) {
      hidden.push_back(std::move(*iter));
      continue;
    }
    if (NetworkTypePattern::WiFi().MatchesType(network->type())) {
      wifi_visible.push_back(std::move(*iter));
    } else {
      non_wifi_visible.push_back(std::move(*iter));
    }
  }

  // List active networks first (will always include Ethernet).
  network_list_ = std::move(active);

  // List non wifi visible networks next (Mobile and VPN).
  std::move(non_wifi_visible.begin(), non_wifi_visible.end(),
            std::back_inserter(network_list_));
  // List WiFi networks last.
  std::move(wifi_visible.begin(), wifi_visible.end(),
            std::back_inserter(network_list_));
  // Include hidden and new networks in the list at the end; they should not
  // be shown by the UI.
  std::move(hidden.begin(), hidden.end(), std::back_inserter(network_list_));
  std::move(new_networks.begin(), new_networks.end(),
            std::back_inserter(network_list_));
  network_list_sorted_ = true;
}

void NetworkStateHandler::DefaultNetworkServiceChanged(
    const std::string& service_path) {
  // Shill uses '/' for empty service path values; check explicitly for that.
  const char kEmptyServicePath[] = "/";
  std::string new_service_path =
      (service_path != kEmptyServicePath) ? service_path : "";
  if (new_service_path == default_network_path_) {
    return;
  }

  if (new_service_path.empty()) {
    // If Shill reports that there is no longer a default network but there is
    // still an active Tether connection corresponding to the default network,
    // return early without changing |default_network_path_|. Observers will be
    // notified of the default network change due to a subsequent call to
    // SetTetherNetworkStateDisconnected().
    const NetworkState* old_default_network = DefaultNetwork();
    if (old_default_network && old_default_network->type() == kTypeTether) {
      return;
    }
  }

  NET_LOG(EVENT) << "DefaultNetworkServiceChanged: "
                 << NetworkPathId(service_path);
  if (new_service_path.empty()) {
    // Notify that there is no default network.
    SetDefaultNetworkValues(/*path=*/std::string(), /*metered=*/false);
    NotifyDefaultNetworkChanged(kReasonChange);
    return;
  }

  const NetworkState* network = GetNetworkState(service_path);
  if (!network) {
    // If NetworkState is not available yet, do not notify observers here,
    // they will be notified when the state is received.
    NET_LOG(EVENT) << "Default NetworkState not available: "
                   << NetworkPathId(service_path);
    // Metered will be updated to the correct value when properties arrive.
    SetDefaultNetworkValues(service_path, /*metered=*/false);
    return;
  }

  if (!network->tether_guid().empty()) {
    DCHECK(network->type() == shill::kTypeWifi);

    // If the new default network from Shill's point of view is a Wi-Fi
    // network which corresponds to a hotspot for a Tether network, set the
    // default network to be the associated Tether network instead.
    network = GetNetworkStateFromGuid(network->tether_guid());
    if (default_network_path_ != network->path()) {
      NET_LOG(DEBUG) << "Tether network is default: " << NetworkId(network);
      SetDefaultNetworkValues(network->path(), /*metered=*/true);
      NotifyDefaultNetworkChanged(kReasonChange);
    }
    return;
  }

  // Request the updated default network properties which will trigger
  // NotifyDefaultNetworkChanged().
  // Metered will be updated to the correct value when properties arrive.
  SetDefaultNetworkValues(service_path, /*metered=*/false);
  RequestUpdateForNetwork(service_path);
}

//------------------------------------------------------------------------------
// Private methods

void NetworkStateHandler::UpdateGuid(NetworkState* network) {
  std::string specifier = network->GetSpecifier();
  DCHECK(!specifier.empty());
  if (!network->guid().empty()) {
    // If the network is saved in a profile, remove the entry from the map.
    // Otherwise ensure that the entry matches the specified GUID. (e.g. in
    // case a visible network with a specified guid gets configured with a
    // new guid). Exception: Ethernet expects to have a single network and a
    // consistent GUID.
    if (network->type() != shill::kTypeEthernet && network->IsInProfile()) {
      specifier_guid_map_.erase(specifier);
    } else {
      specifier_guid_map_[specifier] = network->guid();
    }
    return;
  }
  // Ensure that the NetworkState has a valid GUID.
  std::string guid;
  SpecifierGuidMap::iterator guid_iter = specifier_guid_map_.find(specifier);
  if (guid_iter != specifier_guid_map_.end()) {
    guid = guid_iter->second;
  } else {
    guid = base::Uuid::GenerateRandomV4().AsLowercaseString();
    specifier_guid_map_[specifier] = guid;
  }
  network->SetGuid(guid);
}

void NetworkStateHandler::HandleCellularNetworkUpdateReceived(
    NetworkState* network,
    bool had_icccid_before_update) {
  const DeviceState* device = GetDeviceState(network->device_path());
  if (!device) {
    return;
  }

  // One "roaming" state is shared between all cellular networks.
  network->provider_requires_roaming_ = device->provider_requires_roaming();

  const std::string& iccid = network->iccid();

  // If this network previously did not have an ICCID but just received one via
  // a property update, this may indicates that a stub cellular network has
  // transitioned to a Shill-backed network.
  if (!had_icccid_before_update && !iccid.empty() &&
      stub_cellular_networks_provider_) {
    std::string stub_service_path, stub_guid;
    bool replaced_stub =
        stub_cellular_networks_provider_->GetStubNetworkMetadata(
            iccid, device, &stub_service_path, &stub_guid);
    if (replaced_stub) {
      NotifyNetworkIdentifierTransitioned(stub_service_path, network->path(),
                                          stub_guid, network->guid());
    }
  }
}

bool NetworkStateHandler::AddOrRemoveStubCellularNetworks() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  CHECK(!notifying_network_observers_);
  if (!stub_cellular_networks_provider_) {
    return false;
  }

  const DeviceState* device_state =
      GetDeviceStateByType(NetworkTypePattern::Cellular());
  ManagedStateList new_stub_networks;
  bool network_list_changed =
      stub_cellular_networks_provider_->AddOrRemoveStubCellularNetworks(
          network_list_, new_stub_networks, device_state);
  if (!new_stub_networks.size()) {
    return network_list_changed;
  }

  // Newly created stub cellular networks will not have a GUID. Assign GUIDs for
  // these new networks and add to network_list_.
  for (std::unique_ptr<ManagedState>& managed_state : new_stub_networks) {
    NetworkState* network = managed_state->AsNetworkState();
    UpdateGuid(network);
  }
  std::move(new_stub_networks.begin(), new_stub_networks.end(),
            std::back_inserter(network_list_));
  return true;
}

void NetworkStateHandler::NotifyNetworkListChanged() {
  NET_LOG(EVENT) << "NOTIFY: NetworkListChanged. Size: "
                 << network_list_.size();
  for (Observer& observer : observers_) {
    observer.NetworkListChanged();
  }
}

void NetworkStateHandler::NotifyDeviceListChanged() {
  SCOPED_NET_LOG_IF_SLOW();
  NET_LOG(EVENT) << "NOTIFY: DeviceListChanged. Size: " << device_list_.size();
  for (Observer& observer : observers_) {
    observer.DeviceListChanged();
  }
}

DeviceState* NetworkStateHandler::GetModifiableDeviceState(
    const std::string& device_path) const {
  ManagedState* managed = GetModifiableManagedState(&device_list_, device_path);
  if (!managed) {
    return nullptr;
  }
  return managed->AsDeviceState();
}

DeviceState* NetworkStateHandler::GetModifiableDeviceStateByType(
    const NetworkTypePattern& type) const {
  for (const auto& device : device_list_) {
    if (TypeMatches(device.get(), type)) {
      return device->AsDeviceState();
    }
  }
  return nullptr;
}

NetworkState* NetworkStateHandler::GetModifiableNetworkState(
    const std::string& service_path) const {
  ManagedState* managed =
      GetModifiableManagedState(&network_list_, service_path);
  if (!managed) {
    managed = GetModifiableManagedState(&tether_network_list_, service_path);
    if (!managed) {
      return nullptr;
    }
  }
  return managed->AsNetworkState();
}

NetworkState* NetworkStateHandler::GetModifiableNetworkStateFromGuid(
    const std::string& guid) const {
  for (auto iter = tether_network_list_.begin();
       iter != tether_network_list_.end(); ++iter) {
    NetworkState* tether_network = (*iter)->AsNetworkState();
    if (tether_network->guid() == guid) {
      return tether_network;
    }
  }

  for (auto iter = network_list_.begin(); iter != network_list_.end(); ++iter) {
    NetworkState* network = (*iter)->AsNetworkState();
    if (network->guid() == guid) {
      return network;
    }
  }

  return nullptr;
}

ManagedState* NetworkStateHandler::GetModifiableManagedState(
    const ManagedStateList* managed_list,
    const std::string& path) const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  for (auto iter = managed_list->begin(); iter != managed_list->end(); ++iter) {
    ManagedState* managed = iter->get();
    if (managed->path() == path) {
      return managed;
    }
  }
  return nullptr;
}

NetworkStateHandler::ManagedStateList* NetworkStateHandler::GetManagedList(
    ManagedState::ManagedType type) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  switch (type) {
    case ManagedState::MANAGED_TYPE_NETWORK:
      return &network_list_;
    case ManagedState::MANAGED_TYPE_DEVICE:
      return &device_list_;
  }
  NOTREACHED_IN_MIGRATION();
  return nullptr;
}

void NetworkStateHandler::OnNetworkConnectionStateChanged(
    NetworkState* network) {
  DCHECK(network);
  bool default_changed = false;
  if (network->path() == default_network_path_) {
    default_changed = VerifyDefaultNetworkConnectionStateChange(network);
  }
  NotifyNetworkConnectionStateChanged(network);
  if (default_changed) {
    NotifyDefaultNetworkChanged(kReasonStateChange);
  }
}

bool NetworkStateHandler::VerifyDefaultNetworkConnectionStateChange(
    NetworkState* network) {
  DCHECK(network->path() == default_network_path_);
  if (network->IsConnectedState() ||
      NetworkState::StateIsPortalled(network->connection_state())) {
    return true;
  }
  if (network->IsConnectingState()) {
    // Wait until the network is actually connected to notify that the default
    // network changed.
    NET_LOG(DEBUG) << "Default network is connecting: " << NetworkId(network)
                   << "State: " << network->connection_state();
    return false;
  }
  NET_LOG(DEBUG) << "Default network not connected: " << NetworkId(network);
  return false;
}

void NetworkStateHandler::NotifyNetworkConnectionStateChanged(
    NetworkState* network) {
  DCHECK(network);
  SCOPED_NET_LOG_IF_SLOW();
  std::string desc = "NetworkConnectionStateChanged";
  if (network->path() == default_network_path_) {
    desc = "Default" + desc;
  }
  NET_LOG(EVENT) << "NOTIFY: " << desc << ": " << NetworkId(network) << ": "
                 << network->connection_state();
  notifying_network_observers_ = true;
  for (Observer& observer : observers_) {
    observer.NetworkConnectionStateChanged(network);
  }
  notifying_network_observers_ = false;
  NotifyIfActiveNetworksChanged();
}

void NetworkStateHandler::NotifyDefaultNetworkChanged(
    const std::string& log_reason) {
  SCOPED_NET_LOG_IF_SLOW();
  // If the default network is in an invalid state, |default_network_path_|
  // will be cleared; call DefaultNetworkChanged(nullptr).
  const NetworkState* default_network;
  if (default_network_path_.empty()) {
    default_network = nullptr;
  } else {
    default_network = GetModifiableNetworkState(default_network_path_);
    DCHECK(default_network) << "No default network: " << default_network_path_;
  }
  NET_LOG(EVENT) << "NOTIFY: DefaultNetworkChanged: "
                 << NetworkId(default_network) << ": " << log_reason;
  notifying_network_observers_ = true;
  for (Observer& observer : observers_) {
    observer.DefaultNetworkChanged(default_network);
  }
  notifying_network_observers_ = false;

  UpdatePortalStateAndNotify(default_network);
}

void NetworkStateHandler::UpdatePortalStateAndNotify(
    const NetworkState* default_network) {
  NetworkState::PortalState new_portal_state;
  std::string new_default_network_path;
  if (default_network &&
      (default_network->GetPortalState() != default_network_portal_state_ ||
       default_network->proxy_config() != default_network_proxy_config_)) {
    new_portal_state = default_network->GetPortalState();
    new_default_network_path = default_network->path();
    if (default_network->proxy_config()) {
      default_network_proxy_config_ = default_network->proxy_config()->Clone();
    } else {
      default_network_proxy_config_.reset();
    }
  } else if (!default_network && (default_network_portal_state_ !=
                                      NetworkState::PortalState::kUnknown ||
                                  default_network_proxy_config_.has_value())) {
    new_portal_state = NetworkState::PortalState::kUnknown;
    default_network_proxy_config_.reset();
  } else {
    // No portal state changes.
    return;
  }

  // Update metrics.
  if (new_default_network_path != default_network_path_) {
    // When the default network changes, update time histograms with a 0 result
    // to indicate a failure to transition to online.
    if (time_in_portal_) {
      SendPortalHistogramTimes(base::TimeDelta());
      time_in_portal_.reset();
    }
  } else {
    switch (new_portal_state) {
      case NetworkState::PortalState::kUnknown:
        // If we transition to an unknown state, update time histograms with a 0
        // result to indicate a failure to transition to online.
        if (time_in_portal_) {
          SendPortalHistogramTimes(base::TimeDelta());
          time_in_portal_.reset();
        }
        break;
      case NetworkState::PortalState::kOnline:
        if (time_in_portal_) {
          SendPortalHistogramTimes(time_in_portal_->Elapsed());
          time_in_portal_.reset();
        }
        break;
      case NetworkState::PortalState::kPortalSuspected:
        [[fallthrough]];
      case NetworkState::PortalState::kPortal:
        time_in_portal_ = base::ElapsedTimer();
        break;
      case NetworkState::PortalState::kNoInternet:
        // We don't track these states, reset the timer.
        time_in_portal_.reset();
        break;
    }
  }

  // Update the portal state after sending histograms.
  default_network_portal_state_ = new_portal_state;

  // Notify observers.
  NET_LOG(EVENT) << "NOTIFY: PortalStateChanged: "
                 << GetLogName(default_network) << ": "
                 << default_network_portal_state_;
  for (Observer& observer : observers_) {
    observer.PortalStateChanged(default_network, default_network_portal_state_);
  }
}

void NetworkStateHandler::SendPortalHistogramTimes(base::TimeDelta elapsed) {
  switch (default_network_portal_state_) {
    case NetworkState::PortalState::kPortal:
      base::UmaHistogramMediumTimes("Network.RedirectFoundToOnlineTime",
                                    elapsed);
      break;
    case NetworkState::PortalState::kPortalSuspected:
      base::UmaHistogramMediumTimes("Network.PortalSuspectedToOnlineTime",
                                    elapsed);
      break;
    default:
      // Previous state was not portalled, no times to report.
      break;
  }
}

bool NetworkStateHandler::ActiveNetworksChanged(
    const NetworkStateList& active_networks) {
  if (active_networks.size() != active_network_list_.size()) {
    return true;
  }
  for (size_t i = 0; i < active_network_list_.size(); ++i) {
    if (!active_network_list_[i].MatchesNetworkState(active_networks[i])) {
      return true;
    }
  }
  return false;
}

void NetworkStateHandler::NotifyIfActiveNetworksChanged() {
  SCOPED_NET_LOG_IF_SLOW();
  NetworkStateList active_networks;
  GetActiveNetworkListByType(NetworkTypePattern::Default(), &active_networks);
  if (!ActiveNetworksChanged(active_networks)) {
    return;
  }

  NET_LOG(EVENT) << "NOTIFY:ActiveNetworksChanged";

  active_network_list_.clear();
  active_network_list_.reserve(active_networks.size());
  for (const NetworkState* network : active_networks) {
    active_network_list_.emplace_back(network);
  }

  notifying_network_observers_ = true;
  for (Observer& observer : observers_) {
    observer.ActiveNetworksChanged(active_networks);
  }
  notifying_network_observers_ = false;
}

void NetworkStateHandler::NotifyNetworkPropertiesUpdated(
    const NetworkState* network) {
  // Skip property updates before NetworkState::InitialPropertiesReceived.
  if (network->type().empty()) {
    return;
  }
  SCOPED_NET_LOG_IF_SLOW();
  NET_LOG(EVENT) << "NOTIFY: NetworkPropertiesUpdated: " << NetworkId(network);
  notifying_network_observers_ = true;
  for (Observer& observer : observers_) {
    observer.NetworkPropertiesUpdated(network);
  }
  notifying_network_observers_ = false;
}

void NetworkStateHandler::NotifyDevicePropertiesUpdated(
    const DeviceState* device) {
  SCOPED_NET_LOG_IF_SLOW();
  NET_LOG(EVENT) << "NOTIFY: DevicePropertiesUpdated: " << device->path();
  for (Observer& observer : observers_) {
    observer.DevicePropertiesUpdated(device);
  }
}

void NetworkStateHandler::NotifyScanRequested(const NetworkTypePattern& type) {
  SCOPED_NET_LOG_IF_SLOW();
  NET_LOG(EVENT) << "NOTIFY: ScanRequested";
  for (Observer& observer : observers_) {
    observer.ScanRequested(type);
  }
}

void NetworkStateHandler::NotifyScanCompleted(const DeviceState* device) {
  SCOPED_NET_LOG_IF_SLOW();
  NET_LOG(EVENT) << "NOTIFY: ScanCompleted for: " << device->path();
  for (Observer& observer : observers_) {
    observer.ScanCompleted(device);
  }
}

void NetworkStateHandler::NotifyScanStarted(const DeviceState* device) {
  SCOPED_NET_LOG_IF_SLOW();
  NET_LOG(EVENT) << "NOTIFY: ScanStarted for: " << device->path();
  for (Observer& observer : observers_) {
    observer.ScanStarted(device);
  }
}

void NetworkStateHandler::NotifyNetworkIdentifierTransitioned(
    const std::string& old_service_path,
    const std::string& new_service_path,
    const std::string& old_guid,
    const std::string& new_guid) {
  SCOPED_NET_LOG_IF_SLOW();
  NET_LOG(EVENT) << "NOTIFY: NetworkIdentifierTransitioned: "
                 << "Service path: " << old_service_path << " => "
                 << new_service_path << ", GUID: " << old_guid << " => "
                 << new_guid;
  for (Observer& observer : observers_) {
    observer.NetworkIdentifierTransitioned(old_service_path, new_service_path,
                                           old_guid, new_guid);
  }
}

void NetworkStateHandler::LogPropertyUpdated(const ManagedState* state,
                                             const std::string& key,
                                             const base::Value& value) {
  std::string type_str =
      state->managed_type() == ManagedState::MANAGED_TYPE_DEVICE ? "Device"
      : state->path() == default_network_path_ ? "DefaultNetwork"
                                               : "Network";
  device_event_log::LogLevel log_level = device_event_log::LOG_LEVEL_EVENT;
  if (key == shill::kSignalStrengthProperty && !state->IsActive()) {
    log_level = device_event_log::LOG_LEVEL_DEBUG;
  }
  DEVICE_LOG(::device_event_log::LOG_TYPE_NETWORK, log_level)
      << type_str << "PropertyUpdated: " << GetLogName(state) << ", " << key
      << " = " << value;
}

std::string NetworkStateHandler::GetTechnologyForType(
    const NetworkTypePattern& type) const {
  if (type.MatchesType(shill::kTypeEthernet)) {
    return shill::kTypeEthernet;
  }

  if (type.MatchesType(shill::kTypeWifi)) {
    return shill::kTypeWifi;
  }

  if (type.MatchesType(shill::kTypeCellular)) {
    return shill::kTypeCellular;
  }

  if (type.MatchesType(kTypeTether)) {
    return kTypeTether;
  }

  NET_LOG(ERROR) << "Unexpected Type for technology: " << type.ToDebugString();
  return std::string();
}

std::vector<std::string> NetworkStateHandler::GetTechnologiesForType(
    const NetworkTypePattern& type) const {
  std::vector<std::string> technologies;
  if (type.MatchesType(shill::kTypeEthernet)) {
    technologies.emplace_back(shill::kTypeEthernet);
  }
  if (type.MatchesType(shill::kTypeWifi)) {
    technologies.emplace_back(shill::kTypeWifi);
  }
  if (type.MatchesType(shill::kTypeCellular)) {
    technologies.emplace_back(shill::kTypeCellular);
  }
  if (type.MatchesType(shill::kTypeVPN)) {
    technologies.emplace_back(shill::kTypeVPN);
  }
  if (type.MatchesType(kTypeTether)) {
    technologies.emplace_back(kTypeTether);
  }

  CHECK_GT(technologies.size(), 0ul);
  return technologies;
}

void NetworkStateHandler::SetDefaultNetworkValues(const std::string& path,
                                                  bool metered) {
  // If the default network changes, ensure that the portal state is updated.
  if (!path.empty()) {
    UpdatePortalStateAndNotify(GetNetworkState(path));
  }
  default_network_path_ = path;
  default_network_is_metered_ = metered;
}

void NetworkStateHandler::ProcessIsUserLoggedIn(
    const base::Value::List& profile_list) {
  // The profile list contains the shared profile on the login screen. Once the
  // user is logged in there is more than one profile in the profile list.
  is_user_logged_in_ = profile_list.size() > 1;
}

}  // namespace ash