| OLD | NEW |
| (Empty) | |
| 1 // Copyright (c) 2010, 2011 The Chromium OS Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. |
| 4 |
| 5 #include "src/service_manager_impl.h" |
| 6 |
| 7 #include <glog/logging.h> |
| 8 |
| 9 #include "src/cashew_server.h" |
| 10 #include "src/service.h" |
| 11 |
| 12 namespace cashew { |
| 13 |
| 14 // Flimflam D-Bus indentifiers |
| 15 static const char *kFlimflamManagerName = "org.chromium.flimflam"; |
| 16 static const char *kFlimflamManagerPath = "/"; |
| 17 |
| 18 // Flimflam property names |
| 19 static const char *kDefaultTechnologyProperty = "DefaultTechnology"; |
| 20 static const char *kServicesProperty = "Services"; |
| 21 static const char *kStateProperty = "State"; |
| 22 |
| 23 // Flimflam on-the-wire State values |
| 24 static const char *kFlimflamStateOffline = "offline"; |
| 25 static const char *kFlimflamStateConnected = "connected"; |
| 26 static const char *kFlimflamStateOnline = "online"; |
| 27 |
| 28 // Flimflam service types |
| 29 static const char *kTypeCellular = "cellular"; |
| 30 |
| 31 // GetFlimflamProperties retry interval |
| 32 static const guint kSecondsPerMinute = 60; |
| 33 static const guint kGetFlimflamPropertiesIntervalSeconds = |
| 34 1 * kSecondsPerMinute; |
| 35 |
| 36 ServiceManagerImpl::ServiceManagerImpl(DBus::Connection& connection, // NOLINT |
| 37 GMainLoop * const main_loop) |
| 38 : DBus::ObjectProxy(connection, kFlimflamManagerPath, |
| 39 kFlimflamManagerName), |
| 40 connection_(connection), main_loop_(CHECK_NOTNULL(main_loop)), |
| 41 cashew_server_(NULL), default_technology_(Service::kTypeUnknown), |
| 42 default_cellular_service_(NULL), |
| 43 connectivity_state_(kConnectivityStateUnknown), |
| 44 get_properties_source_id_(0), retrying_get_properties_(false) { |
| 45 // schedule a GetProperties() call to Flimflam to init our state |
| 46 // we'll keep trying periodically until we succeed |
| 47 // we'll subsequently update this state by monitoring PropertyChanged signals |
| 48 get_properties_source_id_ = |
| 49 g_idle_add(StaticGetFlimflamPropertiesCallback, this); |
| 50 if (get_properties_source_id_ == 0) { |
| 51 LOG(ERROR) << "ctor: g_idle_add failed"; |
| 52 } |
| 53 property_changed_handler_.delegate(this); |
| 54 } |
| 55 |
| 56 ServiceManagerImpl::~ServiceManagerImpl() { |
| 57 DCHECK(!g_main_loop_is_running(main_loop_)); |
| 58 ClearDefaultCellularService(); |
| 59 DeleteServices(&services_); |
| 60 if (get_properties_source_id_ != 0 && |
| 61 !g_source_remove(get_properties_source_id_)) { |
| 62 DLOG(WARNING) << "dtor: g_source_remove failed"; |
| 63 } |
| 64 } |
| 65 |
| 66 const Service* ServiceManagerImpl::GetService(const std::string& service_path) |
| 67 const { |
| 68 ServiceMap::const_iterator it = services_.find(service_path); |
| 69 if (it == services_.end()) { |
| 70 return NULL; |
| 71 } |
| 72 DCHECK(it->second != NULL); |
| 73 return static_cast<const Service *>(it->second); |
| 74 } |
| 75 |
| 76 void ServiceManagerImpl::SetCashewServer(CashewServer *cashew_server) { |
| 77 DLOG(INFO) << "SetCashewServer"; |
| 78 cashew_server_ = cashew_server; |
| 79 } |
| 80 |
| 81 Service::Type ServiceManagerImpl::GetDefaultTechnology() const { |
| 82 return default_technology_; |
| 83 } |
| 84 |
| 85 ServiceManager::ConnectivityState ServiceManagerImpl::GetConnectivityState() |
| 86 const { |
| 87 return connectivity_state_; |
| 88 } |
| 89 |
| 90 // static |
| 91 bool ServiceManagerImpl::IsOfflineConnectivityState( |
| 92 ServiceManager::ConnectivityState state) { |
| 93 if (state == kConnectivityStateUnknown || |
| 94 state == kConnectivityStateOffline) { |
| 95 return true; |
| 96 } |
| 97 return false; |
| 98 } |
| 99 |
| 100 // static |
| 101 bool ServiceManagerImpl::IsOnlineConnectivityState( |
| 102 ServiceManager::ConnectivityState state) { |
| 103 if (state == kConnectivityStateConnected || |
| 104 state == kConnectivityStateOnline) { |
| 105 return true; |
| 106 } |
| 107 return false; |
| 108 } |
| 109 |
| 110 // Flimflam Manager D-Bus Proxy methods |
| 111 |
| 112 void ServiceManagerImpl::PropertyChanged(const std::string& property_name, |
| 113 const DBus::Variant& new_value) { |
| 114 DLOG(INFO) << "PropertyChanged: property_name = " << property_name; |
| 115 // Queue a tuple representing this signal for later processing from the glib |
| 116 // main loop. We do this to avoid libdbus-c++ deadlocks that can occur when |
| 117 // sending a dbus message from within a dbus callback like this one. |
| 118 PropertyChangedSignal signal(property_name, new_value); |
| 119 property_changed_handler_.EnqueueSignal(signal); |
| 120 } |
| 121 |
| 122 void ServiceManagerImpl::StateChanged(const std::string& new_state_string) { |
| 123 DLOG(INFO) << "StateChanged: new_state_string = " << new_state_string; |
| 124 // Queue a tuple representing this signal for later processing from the glib |
| 125 // main loop. See comments in PropertyChanged above. |
| 126 DBus::Variant new_value; |
| 127 new_value.writer().append_string(new_state_string.c_str()); |
| 128 // StateChanged is a special case of PropertyChanged, so we just construct a |
| 129 // PropertyChanged signal containing the new state info |
| 130 PropertyChangedSignal signal(kStateProperty, new_value); |
| 131 property_changed_handler_.EnqueueSignal(signal); |
| 132 } |
| 133 |
| 134 // PropertyChangedDelegate methods |
| 135 |
| 136 void ServiceManagerImpl::OnPropertyChanged( |
| 137 const PropertyChangedHandler *handler, |
| 138 const std::string& property_name, |
| 139 const DBus::Variant& new_value) { |
| 140 DCHECK(handler == &property_changed_handler_); |
| 141 DLOG(INFO) << "OnPropertyChanged: property_name = " << property_name; |
| 142 if (property_name == kDefaultTechnologyProperty) { |
| 143 OnDefaultTechnologyUpdate(new_value.reader().get_string()); |
| 144 } else if (property_name == kServicesProperty) { |
| 145 // interpret new_value as a vector of service paths |
| 146 ServicePathList paths; |
| 147 DBus::MessageIter reader = new_value.reader(); |
| 148 reader >> paths; |
| 149 OnServicesUpdate(paths); |
| 150 } else if (property_name == kStateProperty) { |
| 151 OnStateUpdate(new_value.reader().get_string()); |
| 152 } else { |
| 153 // we don't care about this property |
| 154 } |
| 155 } |
| 156 |
| 157 // Service methods |
| 158 |
| 159 void ServiceManagerImpl::EmitDataPlansUpdate(const Service& service) { |
| 160 DLOG(INFO) << "EmitDataPlansUpdate: service = " << service.GetPath(); |
| 161 if (cashew_server_ == NULL) { |
| 162 DLOG(WARNING) << "EmitDataPlansUpdate: no Cashew server"; |
| 163 return; |
| 164 } |
| 165 cashew_server_->EmitDataPlansUpdate(service); |
| 166 } |
| 167 |
| 168 // Private methods |
| 169 |
| 170 void ServiceManagerImpl::DeleteServices(ServiceMap *service_map) { |
| 171 DCHECK(service_map != NULL); |
| 172 while (!service_map->empty()) { |
| 173 const std::string& path = |
| 174 static_cast<std::string>(service_map->begin()->first); |
| 175 DLOG(INFO) << "DeleteServices: deleting service: " << path; |
| 176 Service *service = static_cast<Service *>(service_map->begin()->second); |
| 177 DCHECK(service != NULL); |
| 178 DCHECK(service != default_cellular_service_); |
| 179 DCHECK(*service_map == services_ || GetService(service->GetPath()) == NULL); |
| 180 delete service; |
| 181 service_map->erase(service_map->begin()); |
| 182 } |
| 183 } |
| 184 |
| 185 // static |
| 186 gboolean ServiceManagerImpl::StaticGetFlimflamPropertiesCallback( |
| 187 gpointer data) { |
| 188 ServiceManagerImpl *service_manager = |
| 189 reinterpret_cast<ServiceManagerImpl*>(data); |
| 190 CHECK_NOTNULL(service_manager); |
| 191 DCHECK_NE(service_manager->get_properties_source_id_, 0); |
| 192 if (!service_manager->GetFlimflamProperties()) { |
| 193 // call failed, so try again later |
| 194 DLOG(WARNING) << "StaticGetFlimflamPropertiesCallback: " |
| 195 << "GetFlimflamProperties failed, will retry in " |
| 196 << kGetFlimflamPropertiesIntervalSeconds << " secs"; |
| 197 // if we've arrived here from our first g_idle_add call, set up a timer |
| 198 if (!service_manager->retrying_get_properties_) { |
| 199 guint source_id = |
| 200 g_timeout_add_seconds(kGetFlimflamPropertiesIntervalSeconds, |
| 201 StaticGetFlimflamPropertiesCallback, |
| 202 service_manager); |
| 203 service_manager->get_properties_source_id_ = source_id; |
| 204 if (source_id == 0) { |
| 205 LOG(ERROR) << "StaticGetFlimflamPropertiesCallback: " |
| 206 << "g_timeout_add_seconds failed"; |
| 207 return FALSE; // get rid of old glib source and give up |
| 208 } |
| 209 service_manager->retrying_get_properties_ = true; |
| 210 // get rid of our old glib source, but our new timer will call us |
| 211 return FALSE; |
| 212 } |
| 213 // otherwise, our timer is already set up |
| 214 // return TRUE to indicate that we want to be called again later |
| 215 return TRUE; |
| 216 } |
| 217 service_manager->get_properties_source_id_ = 0; |
| 218 service_manager->retrying_get_properties_ = false; |
| 219 return FALSE; // we succeeded, so don't repeat |
| 220 } |
| 221 |
| 222 bool ServiceManagerImpl::GetFlimflamProperties() { |
| 223 DLOG(INFO) << "GetFlimflamProperties"; |
| 224 PropertyMap properties; |
| 225 // dbus-c++ throws exceptions |
| 226 // invoke the "Existing Non-conformant Code" clause of the style guide and |
| 227 // isolate the rest of the system from this |
| 228 try { |
| 229 // TODO(vlaviano): make this call asynchronous |
| 230 properties = GetProperties(); |
| 231 } catch (DBus::Error& error) { // NOLINT |
| 232 LOG(WARNING) << "GetFlimflamProperties: GetProperties() -> Exception: " |
| 233 << error.name() << ": " << error.message(); |
| 234 return false; |
| 235 } catch (...) { // NOLINT |
| 236 LOG(WARNING) << "GetFlimflamProperties: GetProperties() -> Exception"; |
| 237 return false; |
| 238 } |
| 239 DLOG(INFO) << "GetFlimflamProperties: Received " << properties.size() |
| 240 << " properties"; |
| 241 |
| 242 // grab the properties in which we're interested |
| 243 PropertyMap::const_iterator it = properties.find(kDefaultTechnologyProperty); |
| 244 if (it != properties.end()) { |
| 245 const DBus::Variant& value = static_cast<DBus::Variant>(it->second); |
| 246 OnDefaultTechnologyUpdate(value.reader().get_string()); |
| 247 } else { |
| 248 DLOG(WARNING) << "GetFlimflamProperties: no DefaultTechnology property"; |
| 249 } |
| 250 it = properties.find(kServicesProperty); |
| 251 if (it != properties.end()) { |
| 252 const DBus::Variant& value = static_cast<DBus::Variant>(it->second); |
| 253 // interpret value as vector of service paths |
| 254 ServicePathList paths; |
| 255 DBus::MessageIter reader = value.reader(); |
| 256 reader >> paths; |
| 257 OnServicesUpdate(paths); |
| 258 } else { |
| 259 DLOG(WARNING) << "GetFlimflamProperties: no Services property"; |
| 260 } |
| 261 it = properties.find(kStateProperty); |
| 262 if (it != properties.end()) { |
| 263 const DBus::Variant& value = static_cast<DBus::Variant>(it->second); |
| 264 StateChanged(value.reader().get_string()); |
| 265 } else { |
| 266 DLOG(WARNING) << "GetFlimflamProperties: no State property"; |
| 267 } |
| 268 return true; |
| 269 } |
| 270 |
| 271 void ServiceManagerImpl::OnStateUpdate(const std::string& new_state_string) { |
| 272 DLOG(INFO) << "OnStateUpdate: new_state = " << new_state_string; |
| 273 ConnectivityState old_state = connectivity_state_; |
| 274 connectivity_state_ = ConnectivityStateFromString(new_state_string); |
| 275 // see if we're coming online or going offline |
| 276 // NOTE: we consider "unknown" to be offline |
| 277 if (IsOfflineConnectivityState(old_state) && |
| 278 IsOnlineConnectivityState(connectivity_state_)) { |
| 279 OnFlimflamOnline(); |
| 280 } else if (IsOnlineConnectivityState(old_state) && |
| 281 IsOfflineConnectivityState(connectivity_state_)) { |
| 282 OnFlimflamOffline(); |
| 283 } |
| 284 } |
| 285 |
| 286 void ServiceManagerImpl::OnDefaultTechnologyUpdate( |
| 287 const std::string& default_technology) { |
| 288 DLOG(INFO) << "OnDefaultTechnologyUpdate: default technology = " |
| 289 << default_technology; |
| 290 default_technology_ = Service::TypeFromString(default_technology); |
| 291 if (default_technology_ == Service::kTypeUnknown) { |
| 292 LOG(WARNING) << "OnDefaultTechnologyUpdate: unknown default technology: " |
| 293 << default_technology; |
| 294 } |
| 295 if (default_technology_ != Service::kTypeCellular && |
| 296 default_cellular_service_ != NULL) { |
| 297 // if the default technology isn't cellular and yet we think that one of |
| 298 // our cellular services is the default service, then we need to update |
| 299 // our view of the world. |
| 300 ClearDefaultCellularService(); |
| 301 } |
| 302 } |
| 303 |
| 304 void ServiceManagerImpl::OnServicesUpdate(const ServicePathList& paths) { |
| 305 // Remember old set of services. This is the baseline from which we will |
| 306 // determine what has changed. |
| 307 ServiceMap old_services = services_; |
| 308 services_.clear(); |
| 309 |
| 310 // Flimflam service types are embedded in their path names, so we can use |
| 311 // the pattern "/cellular_" to decide which paths are cellular services. |
| 312 static const std::string cellular_pattern = std::string("/") + kTypeCellular |
| 313 + "_"; |
| 314 |
| 315 // Walk paths in this update and see if they're new to us |
| 316 DLOG(INFO) << "OnServicesUpdate: " << paths.size() << " service paths"; |
| 317 ServicePathList::const_iterator it; |
| 318 for (it = paths.begin(); it != paths.end(); ++it) { |
| 319 const DBus::Path& path = *it; |
| 320 |
| 321 // We only care about cellular services |
| 322 if (path.find(cellular_pattern) == std::string::npos) { |
| 323 continue; |
| 324 } |
| 325 |
| 326 ServiceMap::iterator old_it = old_services.find(path); |
| 327 if (old_it != old_services.end()) { |
| 328 // service is old: move it from old_services to services_ |
| 329 Service *service = static_cast<Service *>(old_it->second); |
| 330 DCHECK(service != NULL); |
| 331 old_services.erase(old_it); |
| 332 services_[path] = service; |
| 333 } else { |
| 334 // service is new: create a Service obj for it and add it to services_ |
| 335 OnNewService(path); |
| 336 } |
| 337 } |
| 338 |
| 339 // clear default service if it failed to appear in the update and we're |
| 340 // about to delete it |
| 341 if (default_cellular_service_ != NULL) { |
| 342 ServiceMap::const_iterator it = |
| 343 old_services.find(default_cellular_service_->GetPath()); |
| 344 if (it != old_services.end()) { |
| 345 ClearDefaultCellularService(); |
| 346 } |
| 347 } |
| 348 |
| 349 // services left in old_services failed to appear in the update: delete them |
| 350 DeleteServices(&old_services); |
| 351 |
| 352 // Flimflam always places the default service first in its list of services, |
| 353 // so we'll take a look at the first element in the paths list and see if the |
| 354 // default service has changed. Note that |paths| can be empty. We still want |
| 355 // to know about this, since it means that there is no default service. |
| 356 const DBus::Path *default_service_path = NULL; |
| 357 if (!paths.empty()) { |
| 358 default_service_path = &*paths.begin(); |
| 359 } |
| 360 OnDefaultServiceUpdate(default_service_path); |
| 361 } |
| 362 |
| 363 void ServiceManagerImpl::OnNewService(const DBus::Path& path) { |
| 364 DLOG(INFO) << "OnNewService: " << path; |
| 365 DCHECK(GetService(path) == NULL); |
| 366 Service *service = Service::NewService(this, connection_, path); |
| 367 if (service == NULL) { |
| 368 LOG(ERROR) << "OnNewService: couldn't create service for " << path; |
| 369 return; |
| 370 } |
| 371 services_[path] = service; |
| 372 } |
| 373 |
| 374 void ServiceManagerImpl::OnDefaultServiceUpdate(const DBus::Path *path) { |
| 375 DLOG(INFO) << "OnDefaultServiceUpdate: new default service = " |
| 376 << ((path == NULL) ? "None" : *path); |
| 377 // check if new default service path (if any) corresponds to one of our |
| 378 // cellular services |
| 379 Service *new_default_cellular_service = NULL; |
| 380 if (path != NULL) { |
| 381 new_default_cellular_service = const_cast<Service*>(GetService(*path)); |
| 382 } |
| 383 if (new_default_cellular_service != NULL) { |
| 384 SetDefaultCellularService(new_default_cellular_service); |
| 385 } else { |
| 386 ClearDefaultCellularService(); |
| 387 } |
| 388 } |
| 389 |
| 390 void ServiceManagerImpl::ClearDefaultCellularService() { |
| 391 DLOG(INFO) << "ClearDefaultCellularService"; |
| 392 if (default_cellular_service_ != NULL) { |
| 393 DCHECK(default_cellular_service_->IsDefaultService()); |
| 394 default_cellular_service_->OnDefaultServiceUpdate(false); |
| 395 default_cellular_service_ = NULL; |
| 396 } |
| 397 } |
| 398 |
| 399 void ServiceManagerImpl::SetDefaultCellularService(Service *service) { |
| 400 DLOG(INFO) << "SetDefaultCellularService: " |
| 401 << ((service == NULL) ? "None" : service->GetPath()); |
| 402 if (default_cellular_service_ == service) { |
| 403 return; |
| 404 } |
| 405 if (default_cellular_service_ != NULL) { |
| 406 ClearDefaultCellularService(); |
| 407 } |
| 408 DCHECK(default_cellular_service_ == NULL); |
| 409 if (service != NULL) { |
| 410 DCHECK(!service->IsDefaultService()); |
| 411 service->OnDefaultServiceUpdate(true); |
| 412 default_cellular_service_ = service; |
| 413 } |
| 414 } |
| 415 |
| 416 // static |
| 417 ServiceManager::ConnectivityState |
| 418 ServiceManagerImpl::ConnectivityStateFromString(const std::string& state) { |
| 419 if (state == kFlimflamStateOffline) { |
| 420 return kConnectivityStateOffline; |
| 421 } |
| 422 if (state == kFlimflamStateConnected) { |
| 423 return kConnectivityStateConnected; |
| 424 } |
| 425 if (state == kFlimflamStateOnline) { |
| 426 return kConnectivityStateOnline; |
| 427 } |
| 428 return kConnectivityStateUnknown; |
| 429 } |
| 430 |
| 431 void ServiceManagerImpl::OnFlimflamOnline() { |
| 432 DLOG(INFO) << "OnFlimflamOnline"; |
| 433 // notify our child services |
| 434 ServiceMap::iterator it; |
| 435 for (it = services_.begin(); it != services_.end(); ++it) { |
| 436 Service *service = static_cast<Service*>(it->second); |
| 437 DCHECK(service != NULL); |
| 438 service->OnFlimflamOnline(); |
| 439 } |
| 440 } |
| 441 |
| 442 void ServiceManagerImpl::OnFlimflamOffline() { |
| 443 DLOG(INFO) << "OnFlimflamOffline"; |
| 444 // notify our child services |
| 445 ServiceMap::iterator it; |
| 446 for (it = services_.begin(); it != services_.end(); ++it) { |
| 447 Service *service = static_cast<Service*>(it->second); |
| 448 DCHECK(service != NULL); |
| 449 service->OnFlimflamOffline(); |
| 450 } |
| 451 } |
| 452 |
| 453 } // namespace cashew |
| OLD | NEW |