blob: 232387d0c96f4a11b257926fba45d193dd17185a [file] [log] [blame]
// Copyright (c) 2010, 2011 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "src/service_manager_impl.h"
#include <glog/logging.h>
#include "src/cashew_server.h"
#include "src/service.h"
namespace cashew {
// Flimflam D-Bus indentifiers
static const char *kFlimflamManagerName = "org.chromium.flimflam";
static const char *kFlimflamManagerPath = "/";
// Flimflam property names
static const char *kDefaultTechnologyProperty = "DefaultTechnology";
static const char *kServicesProperty = "Services";
static const char *kStateProperty = "State";
// Flimflam on-the-wire State values
static const char *kFlimflamStateOffline = "offline";
static const char *kFlimflamStateConnected = "connected";
static const char *kFlimflamStateOnline = "online";
// Flimflam service types
static const char *kTypeCellular = "cellular";
// GetFlimflamProperties retry interval
static const guint kSecondsPerMinute = 60;
static const guint kGetFlimflamPropertiesIntervalSeconds =
1 * kSecondsPerMinute;
ServiceManagerImpl::ServiceManagerImpl(DBus::Connection& connection, // NOLINT
GMainLoop * const main_loop)
: DBus::ObjectProxy(connection, kFlimflamManagerPath,
kFlimflamManagerName),
connection_(connection), main_loop_(CHECK_NOTNULL(main_loop)),
cashew_server_(NULL), default_technology_(Service::kTypeUnknown),
default_cellular_service_(NULL),
connectivity_state_(kConnectivityStateUnknown),
get_properties_source_id_(0), retrying_get_properties_(false) {
// schedule a GetProperties() call to Flimflam to init our state
// we'll keep trying periodically until we succeed
// we'll subsequently update this state by monitoring PropertyChanged signals
get_properties_source_id_ =
g_idle_add(StaticGetFlimflamPropertiesCallback, this);
if (get_properties_source_id_ == 0) {
LOG(ERROR) << "ctor: g_idle_add failed";
}
property_changed_handler_.delegate(this);
}
ServiceManagerImpl::~ServiceManagerImpl() {
DCHECK(!g_main_loop_is_running(main_loop_));
ClearDefaultCellularService();
DeleteServices(&services_);
if (get_properties_source_id_ != 0 &&
!g_source_remove(get_properties_source_id_)) {
DLOG(WARNING) << "dtor: g_source_remove failed";
}
}
const Service* ServiceManagerImpl::GetService(const std::string& service_path)
const {
ServiceMap::const_iterator it = services_.find(service_path);
if (it == services_.end()) {
return NULL;
}
DCHECK(it->second != NULL);
return static_cast<const Service *>(it->second);
}
void ServiceManagerImpl::SetCashewServer(CashewServer *cashew_server) {
DLOG(INFO) << "SetCashewServer";
cashew_server_ = cashew_server;
}
Service::Type ServiceManagerImpl::GetDefaultTechnology() const {
return default_technology_;
}
ServiceManager::ConnectivityState ServiceManagerImpl::GetConnectivityState()
const {
return connectivity_state_;
}
// static
bool ServiceManagerImpl::IsOfflineConnectivityState(
ServiceManager::ConnectivityState state) {
if (state == kConnectivityStateUnknown ||
state == kConnectivityStateOffline) {
return true;
}
return false;
}
// static
bool ServiceManagerImpl::IsOnlineConnectivityState(
ServiceManager::ConnectivityState state) {
if (state == kConnectivityStateConnected ||
state == kConnectivityStateOnline) {
return true;
}
return false;
}
// Flimflam Manager D-Bus Proxy methods
void ServiceManagerImpl::PropertyChanged(const std::string& property_name,
const DBus::Variant& new_value) {
DLOG(INFO) << "PropertyChanged: property_name = " << property_name;
// Queue a tuple representing this signal for later processing from the glib
// main loop. We do this to avoid libdbus-c++ deadlocks that can occur when
// sending a dbus message from within a dbus callback like this one.
PropertyChangedSignal signal(property_name, new_value);
property_changed_handler_.EnqueueSignal(signal);
}
void ServiceManagerImpl::StateChanged(const std::string& new_state_string) {
DLOG(INFO) << "StateChanged: new_state_string = " << new_state_string;
// Queue a tuple representing this signal for later processing from the glib
// main loop. See comments in PropertyChanged above.
DBus::Variant new_value;
new_value.writer().append_string(new_state_string.c_str());
// StateChanged is a special case of PropertyChanged, so we just construct a
// PropertyChanged signal containing the new state info
PropertyChangedSignal signal(kStateProperty, new_value);
property_changed_handler_.EnqueueSignal(signal);
}
// PropertyChangedDelegate methods
void ServiceManagerImpl::OnPropertyChanged(
const PropertyChangedHandler *handler,
const std::string& property_name,
const DBus::Variant& new_value) {
DCHECK(handler == &property_changed_handler_);
DLOG(INFO) << "OnPropertyChanged: property_name = " << property_name;
if (property_name == kDefaultTechnologyProperty) {
OnDefaultTechnologyUpdate(new_value.reader().get_string());
} else if (property_name == kServicesProperty) {
// interpret new_value as a vector of service paths
ServicePathList paths;
DBus::MessageIter reader = new_value.reader();
reader >> paths;
OnServicesUpdate(paths);
} else if (property_name == kStateProperty) {
OnStateUpdate(new_value.reader().get_string());
} else {
// we don't care about this property
}
}
// Service methods
void ServiceManagerImpl::EmitDataPlansUpdate(const Service& service) {
DLOG(INFO) << "EmitDataPlansUpdate: service = " << service.GetPath();
if (cashew_server_ == NULL) {
DLOG(WARNING) << "EmitDataPlansUpdate: no Cashew server";
return;
}
cashew_server_->EmitDataPlansUpdate(service);
}
void ServiceManagerImpl::OnDataPlanInactive(const Service& service,
const DataPlan& plan) {
DLOG(INFO) << "OnDataPlanInactive: service = " << service.GetPath()
<< ", plan = " << plan.GetName();
DCHECK(!plan.IsActive());
// TODO(vlaviano): hook for future use
// we could emit a PlanExpired D-Bus signal for Chrome to consume
}
// Private methods
void ServiceManagerImpl::DeleteServices(ServiceMap *service_map) {
DCHECK(service_map != NULL);
while (!service_map->empty()) {
const std::string& path =
static_cast<std::string>(service_map->begin()->first);
DLOG(INFO) << "DeleteServices: deleting service: " << path;
Service *service = static_cast<Service *>(service_map->begin()->second);
DCHECK(service != NULL);
DCHECK(service != default_cellular_service_);
DCHECK(*service_map == services_ || GetService(service->GetPath()) == NULL);
delete service;
service_map->erase(service_map->begin());
}
}
// static
gboolean ServiceManagerImpl::StaticGetFlimflamPropertiesCallback(
gpointer data) {
ServiceManagerImpl *service_manager =
reinterpret_cast<ServiceManagerImpl*>(data);
CHECK_NOTNULL(service_manager);
DCHECK_NE(service_manager->get_properties_source_id_, 0);
if (!service_manager->GetFlimflamProperties()) {
// call failed, so try again later
DLOG(WARNING) << "StaticGetFlimflamPropertiesCallback: "
<< "GetFlimflamProperties failed, will retry in "
<< kGetFlimflamPropertiesIntervalSeconds << " secs";
// if we've arrived here from our first g_idle_add call, set up a timer
if (!service_manager->retrying_get_properties_) {
guint source_id =
g_timeout_add_seconds(kGetFlimflamPropertiesIntervalSeconds,
StaticGetFlimflamPropertiesCallback,
service_manager);
service_manager->get_properties_source_id_ = source_id;
if (source_id == 0) {
LOG(ERROR) << "StaticGetFlimflamPropertiesCallback: "
<< "g_timeout_add_seconds failed";
return FALSE; // get rid of old glib source and give up
}
service_manager->retrying_get_properties_ = true;
// get rid of our old glib source, but our new timer will call us
return FALSE;
}
// otherwise, our timer is already set up
// return TRUE to indicate that we want to be called again later
return TRUE;
}
service_manager->get_properties_source_id_ = 0;
service_manager->retrying_get_properties_ = false;
return FALSE; // we succeeded, so don't repeat
}
bool ServiceManagerImpl::GetFlimflamProperties() {
DLOG(INFO) << "GetFlimflamProperties";
PropertyMap properties;
// dbus-c++ throws exceptions
// invoke the "Existing Non-conformant Code" clause of the style guide and
// isolate the rest of the system from this
try {
// TODO(vlaviano): make this call asynchronous
properties = GetProperties();
} catch (DBus::Error& error) { // NOLINT
LOG(WARNING) << "GetFlimflamProperties: GetProperties() -> Exception: "
<< error.name() << ": " << error.message();
return false;
} catch (...) { // NOLINT
LOG(WARNING) << "GetFlimflamProperties: GetProperties() -> Exception";
return false;
}
DLOG(INFO) << "GetFlimflamProperties: Received " << properties.size()
<< " properties";
// grab the properties in which we're interested
PropertyMap::const_iterator it = properties.find(kDefaultTechnologyProperty);
if (it != properties.end()) {
const DBus::Variant& value = static_cast<DBus::Variant>(it->second);
OnDefaultTechnologyUpdate(value.reader().get_string());
} else {
DLOG(WARNING) << "GetFlimflamProperties: no DefaultTechnology property";
}
it = properties.find(kServicesProperty);
if (it != properties.end()) {
const DBus::Variant& value = static_cast<DBus::Variant>(it->second);
// interpret value as vector of service paths
ServicePathList paths;
DBus::MessageIter reader = value.reader();
reader >> paths;
OnServicesUpdate(paths);
} else {
DLOG(WARNING) << "GetFlimflamProperties: no Services property";
}
it = properties.find(kStateProperty);
if (it != properties.end()) {
const DBus::Variant& value = static_cast<DBus::Variant>(it->second);
StateChanged(value.reader().get_string());
} else {
DLOG(WARNING) << "GetFlimflamProperties: no State property";
}
return true;
}
void ServiceManagerImpl::OnStateUpdate(const std::string& new_state_string) {
DLOG(INFO) << "OnStateUpdate: new_state = " << new_state_string;
ConnectivityState old_state = connectivity_state_;
connectivity_state_ = ConnectivityStateFromString(new_state_string);
// see if we're coming online or going offline
// NOTE: we consider "unknown" to be offline
if (IsOfflineConnectivityState(old_state) &&
IsOnlineConnectivityState(connectivity_state_)) {
OnFlimflamOnline();
} else if (IsOnlineConnectivityState(old_state) &&
IsOfflineConnectivityState(connectivity_state_)) {
OnFlimflamOffline();
}
}
void ServiceManagerImpl::OnDefaultTechnologyUpdate(
const std::string& default_technology) {
DLOG(INFO) << "OnDefaultTechnologyUpdate: default technology = "
<< default_technology;
default_technology_ = Service::TypeFromString(default_technology);
if (default_technology_ == Service::kTypeUnknown) {
LOG(WARNING) << "OnDefaultTechnologyUpdate: unknown default technology: "
<< default_technology;
}
if (default_technology_ != Service::kTypeCellular &&
default_cellular_service_ != NULL) {
// if the default technology isn't cellular and yet we think that one of
// our cellular services is the default service, then we need to update
// our view of the world.
ClearDefaultCellularService();
}
}
void ServiceManagerImpl::OnServicesUpdate(const ServicePathList& paths) {
// Remember old set of services. This is the baseline from which we will
// determine what has changed.
ServiceMap old_services = services_;
services_.clear();
// Flimflam service types are embedded in their path names, so we can use
// the pattern "/cellular_" to decide which paths are cellular services.
static const std::string cellular_pattern = std::string("/") + kTypeCellular
+ "_";
// Walk paths in this update and see if they're new to us
DLOG(INFO) << "OnServicesUpdate: " << paths.size() << " service paths";
ServicePathList::const_iterator it;
for (it = paths.begin(); it != paths.end(); ++it) {
const DBus::Path& path = *it;
// We only care about cellular services
if (path.find(cellular_pattern) == std::string::npos) {
continue;
}
ServiceMap::iterator old_it = old_services.find(path);
if (old_it != old_services.end()) {
// service is old: move it from old_services to services_
Service *service = static_cast<Service *>(old_it->second);
DCHECK(service != NULL);
old_services.erase(old_it);
services_[path] = service;
} else {
// service is new: create a Service obj for it and add it to services_
OnNewService(path);
}
}
// clear default service if it failed to appear in the update and we're
// about to delete it
if (default_cellular_service_ != NULL) {
ServiceMap::const_iterator it =
old_services.find(default_cellular_service_->GetPath());
if (it != old_services.end()) {
ClearDefaultCellularService();
}
}
// services left in old_services failed to appear in the update: delete them
DeleteServices(&old_services);
// Flimflam always places the default service first in its list of services,
// so we'll take a look at the first element in the paths list and see if the
// default service has changed. Note that |paths| can be empty. We still want
// to know about this, since it means that there is no default service.
const DBus::Path *default_service_path = NULL;
if (!paths.empty()) {
default_service_path = &*paths.begin();
}
OnDefaultServiceUpdate(default_service_path);
}
void ServiceManagerImpl::OnNewService(const DBus::Path& path) {
DLOG(INFO) << "OnNewService: " << path;
DCHECK(GetService(path) == NULL);
Service *service = Service::NewService(this, connection_, path);
if (service == NULL) {
LOG(ERROR) << "OnNewService: couldn't create service for " << path;
return;
}
services_[path] = service;
}
void ServiceManagerImpl::OnDefaultServiceUpdate(const DBus::Path *path) {
DLOG(INFO) << "OnDefaultServiceUpdate: new default service = "
<< ((path == NULL) ? "None" : *path);
// check if new default service path (if any) corresponds to one of our
// cellular services
Service *new_default_cellular_service = NULL;
if (path != NULL) {
new_default_cellular_service = const_cast<Service*>(GetService(*path));
}
if (new_default_cellular_service != NULL) {
SetDefaultCellularService(new_default_cellular_service);
} else {
ClearDefaultCellularService();
}
}
void ServiceManagerImpl::ClearDefaultCellularService() {
DLOG(INFO) << "ClearDefaultCellularService";
if (default_cellular_service_ != NULL) {
DCHECK(default_cellular_service_->IsDefaultService());
default_cellular_service_->OnDefaultServiceUpdate(false);
default_cellular_service_ = NULL;
}
}
void ServiceManagerImpl::SetDefaultCellularService(Service *service) {
DLOG(INFO) << "SetDefaultCellularService: "
<< ((service == NULL) ? "None" : service->GetPath());
if (default_cellular_service_ == service) {
return;
}
if (default_cellular_service_ != NULL) {
ClearDefaultCellularService();
}
DCHECK(default_cellular_service_ == NULL);
if (service != NULL) {
DCHECK(!service->IsDefaultService());
service->OnDefaultServiceUpdate(true);
default_cellular_service_ = service;
}
}
// static
ServiceManager::ConnectivityState
ServiceManagerImpl::ConnectivityStateFromString(const std::string& state) {
if (state == kFlimflamStateOffline) {
return kConnectivityStateOffline;
}
if (state == kFlimflamStateConnected) {
return kConnectivityStateConnected;
}
if (state == kFlimflamStateOnline) {
return kConnectivityStateOnline;
}
return kConnectivityStateUnknown;
}
void ServiceManagerImpl::OnFlimflamOnline() {
DLOG(INFO) << "OnFlimflamOnline";
// notify our child services
ServiceMap::iterator it;
for (it = services_.begin(); it != services_.end(); ++it) {
Service *service = static_cast<Service*>(it->second);
DCHECK(service != NULL);
service->OnFlimflamOnline();
}
}
void ServiceManagerImpl::OnFlimflamOffline() {
DLOG(INFO) << "OnFlimflamOffline";
// notify our child services
ServiceMap::iterator it;
for (it = services_.begin(); it != services_.end(); ++it) {
Service *service = static_cast<Service*>(it->second);
DCHECK(service != NULL);
service->OnFlimflamOffline();
}
}
} // namespace cashew