shill: Add CellularCapabilityUniversal

Add the CellularCapabilityUniversal class so that shill can talk to a
ModemManager1 DBUS interface.  Ensure that a modems can be created
with either the new or the old modem manager running.

Register to receive DBus property changes from ModemManager1

BUG=chromium-os:28596, chromium-os:26650
TEST=Run unit tests, test that modem is created with Y3300

Change-Id: I8717318e944589bc85e763bd7234336559256dbc
Reviewed-on: https://gerrit.chromium.org/gerrit/19888
Commit-Ready: Jason Glasgow <jglasgow@chromium.org>
Reviewed-by: Jason Glasgow <jglasgow@chromium.org>
Tested-by: Jason Glasgow <jglasgow@chromium.org>
diff --git a/cellular_capability_universal.cc b/cellular_capability_universal.cc
new file mode 100644
index 0000000..85956b3
--- /dev/null
+++ b/cellular_capability_universal.cc
@@ -0,0 +1,806 @@
+// Copyright (c) 2012 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 "shill/cellular_capability_universal.h"
+
+#include <base/bind.h>
+#include <base/logging.h>
+#include <base/stl_util.h>
+#include <base/string_number_conversions.h>
+#include <base/stringprintf.h>
+#include <chromeos/dbus/service_constants.h>
+#include <mobile_provider.h>
+#include <mm/ModemManager-names.h>
+
+#include <string>
+#include <vector>
+
+#include "shill/adaptor_interfaces.h"
+#include "shill/cellular_service.h"
+#include "shill/error.h"
+#include "shill/property_accessor.h"
+#include "shill/proxy_factory.h"
+
+#ifdef MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN
+#error "Do not include mm-modem.h"
+#endif
+
+// The following are constants that should be found in
+// mm/ModemManager-names.h  The are reproduced here as #define because
+// that is how they will appear eventually in ModemManager-names.h
+#define MM_MODEM_SIMPLE_CONNECT_PIN "pin"
+#define MM_MODEM_SIMPLE_CONNECT_OPERATOR_ID "operator-id"
+#define MM_MODEM_SIMPLE_CONNECT_BANDS "bands"
+#define MM_MODEM_SIMPLE_CONNECT_ALLWOED_MODES "allowed-modes"
+#define MM_MODEM_SIMPLE_CONNECT_PREFERRED_MODE "preferred-mode"
+#define MM_MODEM_SIMPLE_CONNECT_APN "apn"
+#define MM_MODEM_SIMPLE_CONNECT_IP_TYPE "ip-type"
+#define MM_MODEM_SIMPLE_CONNECT_USER "user"
+#define MM_MODEM_SIMPLE_CONNECT_PASSWORD "password"
+#define MM_MODEM_SIMPLE_CONNECT_NUMBER "number"
+#define MM_MODEM_SIMPLE_CONNECT_ALLOW_ROAMING "allow-roaming"
+#define MM_MODEM_SIMPLE_CONNECT_RM_PROTOCOL "rm-protocol"
+
+using base::Bind;
+using std::string;
+using std::vector;
+
+namespace shill {
+
+// static
+unsigned int CellularCapabilityUniversal::friendly_service_name_id_ = 0;
+
+static const char kPhoneNumber[] = "*99#";
+
+static string AccessTechnologyToString(uint32 access_technologies) {
+  if (access_technologies & MM_MODEM_ACCESS_TECHNOLOGY_LTE)
+    return flimflam::kNetworkTechnologyLte;
+  if (access_technologies & (MM_MODEM_ACCESS_TECHNOLOGY_EVDO0 |
+                              MM_MODEM_ACCESS_TECHNOLOGY_EVDOA |
+                              MM_MODEM_ACCESS_TECHNOLOGY_EVDOB))
+    return flimflam::kNetworkTechnologyEvdo;
+  if (access_technologies & MM_MODEM_ACCESS_TECHNOLOGY_1XRTT)
+    return flimflam::kNetworkTechnology1Xrtt;
+  if (access_technologies & MM_MODEM_ACCESS_TECHNOLOGY_HSPA_PLUS)
+    return flimflam::kNetworkTechnologyHspaPlus;
+  if (access_technologies & (MM_MODEM_ACCESS_TECHNOLOGY_HSPA |
+                              MM_MODEM_ACCESS_TECHNOLOGY_HSUPA |
+                              MM_MODEM_ACCESS_TECHNOLOGY_HSDPA))
+    return flimflam::kNetworkTechnologyHspa;
+  if (access_technologies & MM_MODEM_ACCESS_TECHNOLOGY_UMTS)
+    return flimflam::kNetworkTechnologyUmts;
+  if (access_technologies & MM_MODEM_ACCESS_TECHNOLOGY_EDGE)
+    return flimflam::kNetworkTechnologyEdge;
+  if (access_technologies & MM_MODEM_ACCESS_TECHNOLOGY_GPRS)
+    return flimflam::kNetworkTechnologyGprs;
+  if (access_technologies & (MM_MODEM_ACCESS_TECHNOLOGY_GSM_COMPACT |
+                              MM_MODEM_ACCESS_TECHNOLOGY_GSM))
+      return flimflam::kNetworkTechnologyGsm;
+  return "";
+}
+
+CellularCapabilityUniversal::CellularCapabilityUniversal(
+    Cellular *cellular,
+    ProxyFactory *proxy_factory)
+    : CellularCapability(cellular, proxy_factory),
+      weak_ptr_factory_(this),
+      registration_state_(MM_MODEM_3GPP_REGISTRATION_STATE_UNKNOWN),
+      cdma_registration_state_(MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN),
+      access_technologies_(MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN),
+      home_provider_(NULL),
+      scanning_supported_(true),
+      scanning_(false),
+      scan_interval_(0) {
+  VLOG(2) << "Cellular capability constructed: Universal";
+  PropertyStore *store = cellular->mutable_store();
+
+  store->RegisterConstString(flimflam::kCarrierProperty, &carrier_);
+  store->RegisterConstBool(flimflam::kSupportNetworkScanProperty,
+                           &scanning_supported_);
+  store->RegisterConstString(flimflam::kEsnProperty, &esn_);
+  store->RegisterConstString(flimflam::kFirmwareRevisionProperty,
+                             &firmware_revision_);
+  store->RegisterConstString(flimflam::kHardwareRevisionProperty,
+                             &hardware_revision_);
+  store->RegisterConstString(flimflam::kImeiProperty, &imei_);
+  store->RegisterConstString(flimflam::kImsiProperty, &imsi_);
+  store->RegisterConstString(flimflam::kManufacturerProperty, &manufacturer_);
+  store->RegisterConstString(flimflam::kMdnProperty, &mdn_);
+  store->RegisterConstString(flimflam::kMeidProperty, &meid_);
+  store->RegisterConstString(flimflam::kMinProperty, &min_);
+  store->RegisterConstString(flimflam::kModelIDProperty, &model_id_);
+  store->RegisterConstString(flimflam::kSelectedNetworkProperty,
+                             &selected_network_);
+  store->RegisterConstStringmaps(flimflam::kFoundNetworksProperty,
+                                 &found_networks_);
+  store->RegisterConstBool(flimflam::kScanningProperty, &scanning_);
+  store->RegisterUint16(flimflam::kScanIntervalProperty, &scan_interval_);
+  HelpRegisterDerivedKeyValueStore(
+      flimflam::kSIMLockStatusProperty,
+      &CellularCapabilityUniversal::SimLockStatusToProperty,
+      NULL);
+  store->RegisterConstStringmaps(flimflam::kCellularApnListProperty,
+                                 &apn_list_);
+}
+
+KeyValueStore CellularCapabilityUniversal::SimLockStatusToProperty(
+    Error */*error*/) {
+  KeyValueStore status;
+  status.SetBool(flimflam::kSIMLockEnabledProperty, sim_lock_status_.enabled);
+  status.SetString(flimflam::kSIMLockTypeProperty, sim_lock_status_.lock_type);
+  status.SetUint(flimflam::kSIMLockRetriesLeftProperty,
+                 sim_lock_status_.retries_left);
+  return status;
+}
+
+void CellularCapabilityUniversal::HelpRegisterDerivedKeyValueStore(
+    const string &name,
+    KeyValueStore(CellularCapabilityUniversal::*get)(Error *error),
+    void(CellularCapabilityUniversal::*set)(
+        const KeyValueStore &value, Error *error)) {
+  cellular()->mutable_store()->RegisterDerivedKeyValueStore(
+      name,
+      KeyValueStoreAccessor(
+          new CustomAccessor<CellularCapabilityUniversal, KeyValueStore>(
+              this, get, set)));
+}
+
+void CellularCapabilityUniversal::InitProxies() {
+  modem_3gpp_proxy_.reset(
+      proxy_factory()->CreateMM1ModemModem3gppProxy(cellular()->dbus_path(),
+                                                    cellular()->dbus_owner()));
+  modem_cdma_proxy_.reset(
+      proxy_factory()->CreateMM1ModemModemCdmaProxy(cellular()->dbus_path(),
+                                                    cellular()->dbus_owner()));
+  modem_proxy_.reset(
+      proxy_factory()->CreateMM1ModemProxy(cellular()->dbus_path(),
+                                           cellular()->dbus_owner()));
+  modem_simple_proxy_.reset(
+      proxy_factory()->CreateMM1ModemSimpleProxy(cellular()->dbus_path(),
+                                                 cellular()->dbus_owner()));
+  // Do not create a SIM proxy until the device is enabled because we
+  // do not yet know the object path of the sim object.
+
+  // TODO(jglasgow): register callbacks
+}
+
+void CellularCapabilityUniversal::StartModem(Error *error,
+                                       const ResultCallback &callback) {
+  OnUnsupportedOperation(__func__, error);
+}
+
+void CellularCapabilityUniversal::StopModem(Error *error,
+                                      const ResultCallback &callback) {
+  OnUnsupportedOperation(__func__, error);
+}
+
+void CellularCapabilityUniversal::Connect(const DBusPropertiesMap &properties,
+                                          Error *error,
+                                          const ResultCallback &callback) {
+  OnUnsupportedOperation(__func__, error);
+}
+
+void CellularCapabilityUniversal::Disconnect(Error *error,
+                                             const ResultCallback &callback) {
+  OnUnsupportedOperation(__func__, error);
+}
+
+void CellularCapabilityUniversal::Activate(const std::string &carrier,
+                                           Error *error,
+                                           const ResultCallback &callback) {
+  OnUnsupportedOperation(__func__, error);
+}
+
+void CellularCapabilityUniversal::ReleaseProxies() {
+  VLOG(2) << __func__;
+  modem_3gpp_proxy_.reset();
+  modem_cdma_proxy_.reset();
+  modem_proxy_.reset();
+  modem_simple_proxy_.reset();
+  sim_proxy_.reset();
+}
+
+void CellularCapabilityUniversal::OnServiceCreated() {
+  // If IMSI is available, base the service's storage identifier on it.
+  if (!imsi_.empty()) {
+    cellular()->service()->SetStorageIdentifier(
+        string(flimflam::kTypeCellular) + "_" +
+        cellular()->address() + "_" + imsi_);
+  }
+  cellular()->service()->SetActivationState(
+      flimflam::kActivationStateActivated);
+  UpdateServingOperator();
+}
+
+void CellularCapabilityUniversal::UpdateStatus(
+    const DBusPropertiesMap &properties) {
+  if (ContainsKey(properties, kPropertyIMSI)) {
+    SetHomeProvider();
+  }
+}
+
+// Create the list of APNs to try, in the following order:
+// - last APN that resulted in a successful connection attempt on the
+//   current network (if any)
+// - the APN, if any, that was set by the user
+// - the list of APNs found in the mobile broadband provider DB for the
+//   home provider associated with the current SIM
+// - as a last resort, attempt to connect with no APN
+void CellularCapabilityUniversal::SetupApnTryList() {
+  apn_try_list_.clear();
+
+  DCHECK(cellular()->service().get());
+  const Stringmap *apn_info = cellular()->service()->GetLastGoodApn();
+  if (apn_info)
+    apn_try_list_.push_back(*apn_info);
+
+  apn_info = cellular()->service()->GetUserSpecifiedApn();
+  if (apn_info)
+    apn_try_list_.push_back(*apn_info);
+
+  apn_try_list_.insert(apn_try_list_.end(), apn_list_.begin(), apn_list_.end());
+}
+
+void CellularCapabilityUniversal::SetupConnectProperties(
+    DBusPropertiesMap *properties) {
+  SetupApnTryList();
+  FillConnectPropertyMap(properties);
+}
+
+void CellularCapabilityUniversal::FillConnectPropertyMap(
+    DBusPropertiesMap *properties) {
+
+  // TODO(jglasgow): Is this really needed anymore?
+  (*properties)[MM_MODEM_SIMPLE_CONNECT_NUMBER].writer().append_string(
+      kPhoneNumber);
+
+  (*properties)[MM_MODEM_SIMPLE_CONNECT_ALLOW_ROAMING].writer().append_bool(
+      AllowRoaming());
+
+  if (!apn_try_list_.empty()) {
+    // Leave the APN at the front of the list, so that it can be recorded
+    // if the connect attempt succeeds.
+    Stringmap apn_info = apn_try_list_.front();
+    VLOG(2) << __func__ << ": Using APN " << apn_info[flimflam::kApnProperty];
+    (*properties)[MM_MODEM_SIMPLE_CONNECT_APN].writer().append_string(
+        apn_info[flimflam::kApnProperty].c_str());
+    if (ContainsKey(apn_info, flimflam::kApnUsernameProperty))
+      (*properties)[MM_MODEM_SIMPLE_CONNECT_USER].writer().append_string(
+          apn_info[flimflam::kApnUsernameProperty].c_str());
+    if (ContainsKey(apn_info, flimflam::kApnPasswordProperty))
+      (*properties)[MM_MODEM_SIMPLE_CONNECT_PASSWORD].writer().append_string(
+          apn_info[flimflam::kApnPasswordProperty].c_str());
+  }
+}
+
+void CellularCapabilityUniversal::OnConnectReply(const ResultCallback &callback,
+                                           const Error &error) {
+  if (error.IsFailure()) {
+    cellular()->service()->ClearLastGoodApn();
+    // The APN that was just tried (and failed) is still at the
+    // front of the list, about to be removed. If the list is empty
+    // after that, try one last time without an APN. This may succeed
+    // with some modems in some cases.
+    if (error.type() == Error::kInvalidApn && !apn_try_list_.empty()) {
+      apn_try_list_.pop_front();
+      VLOG(2) << "Connect failed with invalid APN, " << apn_try_list_.size()
+              << " remaining APNs to try";
+      DBusPropertiesMap props;
+      FillConnectPropertyMap(&props);
+      Error error;
+      Connect(props, &error, callback);
+      return;
+    }
+    cellular()->OnConnectFailed(error);
+  } else {
+    if (!apn_try_list_.empty()) {
+      cellular()->service()->SetLastGoodApn(apn_try_list_.front());
+      apn_try_list_.clear();
+    }
+    cellular()->OnConnected();
+  }
+
+  if (!callback.is_null())
+    callback.Run(error);
+}
+
+bool CellularCapabilityUniversal::AllowRoaming() {
+  bool requires_roaming =
+      home_provider_ ? home_provider_->requires_roaming : false;
+  return requires_roaming || allow_roaming_property();
+}
+
+void CellularCapabilityUniversal::GetRegistrationState() {
+  VLOG(2) << __func__;
+  string operator_code;
+  string operator_name;
+
+  const MMModem3gppRegistrationState state =
+      static_cast<MMModem3gppRegistrationState>(
+          modem_3gpp_proxy_->RegistrationState());
+  if (state == MM_MODEM_3GPP_REGISTRATION_STATE_HOME ||
+      state == MM_MODEM_3GPP_REGISTRATION_STATE_ROAMING) {
+    operator_code = modem_3gpp_proxy_->OperatorCode();
+    operator_name = modem_3gpp_proxy_->OperatorName();
+  }
+  On3GPPRegistrationChanged(state, operator_code, operator_name);
+}
+
+void CellularCapabilityUniversal::GetProperties(
+    const ResultCallback &callback) {
+  VLOG(2) << __func__;
+
+  // TODO(petkov): Switch to asynchronous calls (crosbug.com/17583).
+  uint32 technologies = modem_proxy_->AccessTechnologies();
+  // TODO(jglasgow): figure out the most likely one that we are using....
+  SetAccessTechnologies(technologies);
+  VLOG(2) << "AccessTechnologies: " << technologies;
+
+  // TODO(petkov): Switch to asynchronous calls (crosbug.com/17583).
+  uint32 locks = modem_3gpp_proxy_->EnabledFacilityLocks();
+  sim_lock_status_.enabled = locks & MM_MODEM_3GPP_FACILITY_SIM;
+  VLOG(2) << "GSM EnabledFacilityLocks: " << locks;
+
+  // TODO(jglasgow): Switch to asynchronous calls (crosbug.com/17583).
+  const DBus::Struct<unsigned int, bool> quality =
+      modem_proxy_->SignalQuality();
+  OnSignalQualityChanged(quality._1);
+
+  // TODO(jglasgow): Switch to asynchronous calls (crosbug.com/17583).
+  if (imei_.empty()) {
+    imei_ = modem_3gpp_proxy_->Imei();
+  }
+  if (imsi_.empty()) {
+    imsi_ = sim_proxy_->Imsi();
+  }
+  if (spn_.empty()) {
+    spn_ = sim_proxy_->OperatorName();
+    // TODO(jglasgow): May eventually want to get SPDI, etc
+  }
+  if (mdn_.empty()) {
+    // TODO(jglasgow): use OwnNumbers()
+  }
+  GetRegistrationState();
+
+  callback.Run(Error());
+}
+
+string CellularCapabilityUniversal::CreateFriendlyServiceName() {
+  VLOG(2) << __func__;
+  if (registration_state_ == MM_MODEM_3GPP_REGISTRATION_STATE_HOME &&
+      !cellular()->home_provider().GetName().empty()) {
+    return cellular()->home_provider().GetName();
+  }
+  if (!serving_operator_.GetName().empty()) {
+    return serving_operator_.GetName();
+  }
+  if (!carrier_.empty()) {
+    return carrier_;
+  }
+  if (!serving_operator_.GetCode().empty()) {
+    return "cellular_" + serving_operator_.GetCode();
+  }
+  return base::StringPrintf("GSMNetwork%u", friendly_service_name_id_++);
+}
+
+void CellularCapabilityUniversal::SetHomeProvider() {
+  VLOG(2) << __func__ << "(IMSI: " << imsi_
+          << " SPN: " << spn_ << ")";
+  // TODO(petkov): The test for NULL provider_db should be done by
+  // mobile_provider_lookup_best_match.
+  if (imsi_.empty() || !cellular()->provider_db()) {
+    return;
+  }
+  mobile_provider *provider =
+      mobile_provider_lookup_best_match(
+          cellular()->provider_db(), spn_.c_str(), imsi_.c_str());
+  if (!provider) {
+    VLOG(2) << "GSM provider not found.";
+    return;
+  }
+  home_provider_ = provider;
+  Cellular::Operator oper;
+  if (provider->networks) {
+    oper.SetCode(provider->networks[0]);
+  }
+  if (provider->country) {
+    oper.SetCountry(provider->country);
+  }
+  if (spn_.empty()) {
+    const char *name = mobile_provider_get_name(provider);
+    if (name) {
+      oper.SetName(name);
+    }
+  } else {
+    oper.SetName(spn_);
+  }
+  cellular()->set_home_provider(oper);
+  InitAPNList();
+}
+
+void CellularCapabilityUniversal::UpdateOperatorInfo() {
+  VLOG(2) << __func__;
+  const string &network_id = serving_operator_.GetCode();
+  if (!network_id.empty()) {
+    VLOG(2) << "Looking up network id: " << network_id;
+    mobile_provider *provider =
+        mobile_provider_lookup_by_network(cellular()->provider_db(),
+                                          network_id.c_str());
+    if (provider) {
+      const char *provider_name = mobile_provider_get_name(provider);
+      if (provider_name && *provider_name) {
+        serving_operator_.SetName(provider_name);
+        if (provider->country) {
+          serving_operator_.SetCountry(provider->country);
+        }
+        VLOG(2) << "Operator name: " << serving_operator_.GetName()
+                << ", country: " << serving_operator_.GetCountry();
+      }
+    } else {
+      VLOG(2) << "GSM provider not found.";
+    }
+  }
+  UpdateServingOperator();
+}
+
+void CellularCapabilityUniversal::UpdateServingOperator() {
+  VLOG(2) << __func__;
+  if (cellular()->service().get()) {
+    cellular()->service()->SetServingOperator(serving_operator_);
+  }
+}
+
+void CellularCapabilityUniversal::InitAPNList() {
+  VLOG(2) << __func__;
+  if (!home_provider_) {
+    return;
+  }
+  apn_list_.clear();
+  for (int i = 0; i < home_provider_->num_apns; ++i) {
+    Stringmap props;
+    mobile_apn *apn = home_provider_->apns[i];
+    if (apn->value) {
+      props[flimflam::kApnProperty] = apn->value;
+    }
+    if (apn->username) {
+      props[flimflam::kApnUsernameProperty] = apn->username;
+    }
+    if (apn->password) {
+      props[flimflam::kApnPasswordProperty] = apn->password;
+    }
+    // Find the first localized and non-localized name, if any.
+    const localized_name *lname = NULL;
+    const localized_name *name = NULL;
+    for (int j = 0; j < apn->num_names; ++j) {
+      if (apn->names[j]->lang) {
+        if (!lname) {
+          lname = apn->names[j];
+        }
+      } else if (!name) {
+        name = apn->names[j];
+      }
+    }
+    if (name) {
+      props[flimflam::kApnNameProperty] = name->name;
+    }
+    if (lname) {
+      props[flimflam::kApnLocalizedNameProperty] = lname->name;
+      props[flimflam::kApnLanguageProperty] = lname->lang;
+    }
+    apn_list_.push_back(props);
+  }
+  cellular()->adaptor()->EmitStringmapsChanged(
+      flimflam::kCellularApnListProperty, apn_list_);
+}
+
+// always called from an async context
+void CellularCapabilityUniversal::Register(const ResultCallback &callback) {
+  VLOG(2) << __func__ << " \"" << selected_network_ << "\"";
+  CHECK(!callback.is_null());
+  Error error;
+  ResultCallback cb = Bind(&CellularCapabilityUniversal::OnRegisterReply,
+                                weak_ptr_factory_.GetWeakPtr(), callback);
+  modem_3gpp_proxy_->Register(selected_network_, &error, cb, kTimeoutRegister);
+  if (error.IsFailure())
+    callback.Run(error);
+}
+
+void CellularCapabilityUniversal::RegisterOnNetwork(
+    const string &network_id,
+    Error *error,
+    const ResultCallback &callback) {
+  VLOG(2) << __func__ << "(" << network_id << ")";
+  CHECK(error);
+  desired_network_ = network_id;
+  ResultCallback cb = Bind(&CellularCapabilityUniversal::OnRegisterReply,
+                                weak_ptr_factory_.GetWeakPtr(), callback);
+  modem_3gpp_proxy_->Register(network_id, error, cb, kTimeoutRegister);
+}
+
+void CellularCapabilityUniversal::OnRegisterReply(
+    const ResultCallback &callback,
+    const Error &error) {
+  VLOG(2) << __func__ << "(" << error << ")";
+
+  if (error.IsSuccess()) {
+    selected_network_ = desired_network_;
+    desired_network_.clear();
+    callback.Run(error);
+    return;
+  }
+  // If registration on the desired network failed,
+  // try to register on the home network.
+  if (!desired_network_.empty()) {
+    desired_network_.clear();
+    selected_network_.clear();
+    LOG(INFO) << "Couldn't register on selected network, trying home network";
+    Register(callback);
+    return;
+  }
+  callback.Run(error);
+}
+
+bool CellularCapabilityUniversal::IsRegistered() {
+  return (registration_state_ == MM_MODEM_3GPP_REGISTRATION_STATE_HOME ||
+          registration_state_ == MM_MODEM_3GPP_REGISTRATION_STATE_ROAMING);
+}
+
+void CellularCapabilityUniversal::RequirePIN(
+    const std::string &pin, bool require,
+    Error *error, const ResultCallback &callback) {
+  CHECK(error);
+  sim_proxy_->EnablePin(pin, require, error, callback, kTimeoutDefault);
+}
+
+void CellularCapabilityUniversal::EnterPIN(const string &pin,
+                                           Error *error,
+                                           const ResultCallback &callback) {
+  CHECK(error);
+  sim_proxy_->SendPin(pin, error, callback, kTimeoutDefault);
+}
+
+void CellularCapabilityUniversal::UnblockPIN(const string &unblock_code,
+                                             const string &pin,
+                                             Error *error,
+                                             const ResultCallback &callback) {
+  CHECK(error);
+  sim_proxy_->SendPuk(unblock_code, pin, error, callback, kTimeoutDefault);
+}
+
+void CellularCapabilityUniversal::ChangePIN(
+    const string &old_pin, const string &new_pin,
+    Error *error, const ResultCallback &callback) {
+  CHECK(error);
+  sim_proxy_->ChangePin(old_pin, new_pin, error, callback, kTimeoutDefault);
+}
+
+void CellularCapabilityUniversal::Scan(Error *error,
+                                       const ResultCallback &callback) {
+  VLOG(2) << __func__;
+  // TODO(petkov): Defer scan requests if a scan is in progress already.
+  CHECK(error);
+  DBusPropertyMapsCallback cb = Bind(&CellularCapabilityUniversal::OnScanReply,
+                                     weak_ptr_factory_.GetWeakPtr(), callback);
+  modem_3gpp_proxy_->Scan(error, cb, kTimeoutScan);
+}
+
+void CellularCapabilityUniversal::OnScanReply(const ResultCallback &callback,
+                                              const ScanResults &results,
+                                              const Error &error) {
+  VLOG(2) << __func__;
+
+  // Error handling is weak.  The current expectation is that on any
+  // error, found_networks_ should be cleared and a property change
+  // notification sent out.
+  //
+  // TODO(jglasgow): fix error handling
+  found_networks_.clear();
+  if (!error.IsFailure()) {
+    for (ScanResults::const_iterator it = results.begin();
+         it != results.end(); ++it) {
+      found_networks_.push_back(ParseScanResult(*it));
+    }
+  }
+  cellular()->adaptor()->EmitStringmapsChanged(flimflam::kFoundNetworksProperty,
+                                               found_networks_);
+  callback.Run(error);
+}
+
+Stringmap CellularCapabilityUniversal::ParseScanResult(
+    const ScanResult &result) {
+
+  static const char kStatusProperty[] = "status";
+  static const char kOperatorLongProperty[] = "operator-long";
+  static const char kOperatorShortProperty[] = "operator-short";
+  static const char kOperatorCodeProperty[] = "operator-code";
+  static const char kOperatorAccessTechnologyProperty[] = "access-technology";
+
+  /* ScanResults contain the following keys:
+
+     "status"
+     A MMModem3gppNetworkAvailability value representing network
+     availability status, given as an unsigned integer (signature "u").
+     This key will always be present.
+
+     "operator-long"
+     Long-format name of operator, given as a string value (signature
+     "s"). If the name is unknown, this field should not be present.
+
+     "operator-short"
+     Short-format name of operator, given as a string value
+     (signature "s"). If the name is unknown, this field should not
+     be present.
+
+     "operator-code"
+     Mobile code of the operator, given as a string value (signature
+     "s"). Returned in the format "MCCMNC", where MCC is the
+     three-digit ITU E.212 Mobile Country Code and MNC is the two- or
+     three-digit GSM Mobile Network Code. e.g. "31026" or "310260".
+
+     "access-technology"
+     A MMModemAccessTechnology value representing the generic access
+     technology used by this mobile network, given as an unsigned
+     integer (signature "u").
+  */
+  Stringmap parsed;
+
+  uint32 status;
+  if (DBusProperties::GetUint32(result, kStatusProperty, &status)) {
+    // numerical values are taken from 3GPP TS 27.007 Section 7.3.
+    static const char * const kStatusString[] = {
+      "unknown",    // MM_MODEM_3GPP_NETWORK_AVAILABILITY_UNKNOWN
+      "available",  // MM_MODEM_3GPP_NETWORK_AVAILABILITY_AVAILABLE
+      "current",    // MM_MODEM_3GPP_NETWORK_AVAILABILITY_CURRENT
+      "forbidden",  // MM_MODEM_3GPP_NETWORK_AVAILABILITY_FORBIDDEN
+    };
+    parsed[flimflam::kStatusProperty] = kStatusString[status];
+  }
+
+  uint32 tech;  // MMModemAccessTechnology
+  if (DBusProperties::GetUint32(result, kOperatorAccessTechnologyProperty,
+                                &tech)) {
+    parsed[flimflam::kTechnologyProperty] = AccessTechnologyToString(tech);
+  }
+
+  string operator_long, operator_short, operator_code;
+  if (DBusProperties::GetString(result, kOperatorLongProperty, &operator_long))
+    parsed[flimflam::kLongNameProperty] = operator_long;
+  if (DBusProperties::GetString(result, kOperatorShortProperty,
+                                &operator_short))
+    parsed[flimflam::kShortNameProperty] = operator_short;
+  if (DBusProperties::GetString(result, kOperatorCodeProperty, &operator_code))
+    parsed[flimflam::kNetworkIdProperty] = operator_code;
+
+  // If the long name is not available but the network ID is, look up the long
+  // name in the mobile provider database.
+  if ((!ContainsKey(parsed, flimflam::kLongNameProperty) ||
+       parsed[flimflam::kLongNameProperty].empty()) &&
+      ContainsKey(parsed, flimflam::kNetworkIdProperty)) {
+    mobile_provider *provider =
+        mobile_provider_lookup_by_network(
+            cellular()->provider_db(),
+            parsed[flimflam::kNetworkIdProperty].c_str());
+    if (provider) {
+      const char *long_name = mobile_provider_get_name(provider);
+      if (long_name && *long_name) {
+        parsed[flimflam::kLongNameProperty] = long_name;
+      }
+    }
+  }
+  return parsed;
+}
+
+void CellularCapabilityUniversal::SetAccessTechnologies(
+    uint32 access_technologies) {
+  access_technologies_ = access_technologies;
+  if (cellular()->service().get()) {
+    cellular()->service()->SetNetworkTechnology(GetNetworkTechnologyString());
+  }
+}
+
+string CellularCapabilityUniversal::GetNetworkTechnologyString() const {
+  // Order is imnportant.  Return the highest speed technology
+  // TODO(jglasgow): change shill interfaces to a capability model
+
+  return AccessTechnologyToString(access_technologies_);
+}
+
+string CellularCapabilityUniversal::GetRoamingStateString() const {
+  switch (registration_state_) {
+    case MM_MODEM_3GPP_REGISTRATION_STATE_HOME:
+      return flimflam::kRoamingStateHome;
+    case MM_MODEM_3GPP_REGISTRATION_STATE_ROAMING:
+      return flimflam::kRoamingStateRoaming;
+    default:
+      break;
+  }
+  return flimflam::kRoamingStateUnknown;
+}
+
+void CellularCapabilityUniversal::GetSignalQuality() {
+  // TODO(jglasgow): implement
+  NOTIMPLEMENTED();
+}
+
+void CellularCapabilityUniversal::OnModemManagerPropertiesChanged(
+    const DBusPropertiesMap &properties) {
+
+  // TODO(jglasgow): When CreateDeviceFromModemProperties is fixed DCHECK
+  LOG(ERROR) << "Unexpected call to OnModemPropertiesChanged.";
+
+  uint32 access_technologies = MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN;
+  if (DBusProperties::GetUint32(properties,
+                                MM_MODEM_PROPERTY_ACCESSTECHNOLOGIES,
+                                &access_technologies)) {
+    SetAccessTechnologies(access_technologies);
+  }
+  // Unlockrequired and SimLock
+  bool emit = false;
+
+  uint32_t lock_required;  // This is really of type MMModemLock
+  if (DBusProperties::GetUint32(properties,
+                                MM_MODEM_PROPERTY_UNLOCKREQUIRED,
+                                &lock_required)) {
+    // TODO(jglasgow): set sim_lock_status_.lock_type
+    emit = true;
+  }
+  // TODO(jglasgow): Update PIN retries which are a{uu} and require parsing
+  // Get the property MM_MODEM_PROPERTY_UNLOCKRETRIES
+  // Set sim_lock_status_.retries_left
+
+  if (emit) {
+    cellular()->adaptor()->EmitKeyValueStoreChanged(
+        flimflam::kSIMLockStatusProperty, SimLockStatusToProperty(NULL));
+  }
+}
+
+void CellularCapabilityUniversal::OnDBusPropertiesChanged(
+    const string &interface,
+    const DBusPropertiesMap &changed_properties,
+    const vector<std::string> &invalidated_properties) {
+  // TODO(jglasgow): implement properties changed handling
+  NOTIMPLEMENTED();
+}
+
+void CellularCapabilityUniversal::OnModem3GPPPropertiesChanged(
+    const DBusPropertiesMap &properties) {
+  bool emit = false;
+  uint32 locks = 0;
+  if (DBusProperties::GetUint32(
+          properties, MM_MODEM_MODEM3GPP_PROPERTY_ENABLEDFACILITYLOCKS,
+          &locks)) {
+    sim_lock_status_.enabled = locks & MM_MODEM_3GPP_FACILITY_SIM;
+    emit = true;
+  }
+  // TODO(jglasgow): coordinate with changes to Modem properties
+  if (emit) {
+    cellular()->adaptor()->EmitKeyValueStoreChanged(
+        flimflam::kSIMLockStatusProperty, SimLockStatusToProperty(NULL));
+  }
+}
+
+void CellularCapabilityUniversal::OnNetworkModeSignal(uint32 /*mode*/) {
+  // TODO(petkov): Implement this.
+  NOTIMPLEMENTED();
+}
+
+void CellularCapabilityUniversal::On3GPPRegistrationChanged(
+    MMModem3gppRegistrationState state,
+    const string &operator_code,
+    const string &operator_name) {
+  VLOG(2) << __func__ << ": regstate=" << state
+          << ", opercode=" << operator_code
+          << ", opername=" << operator_name;
+  registration_state_ = state;
+  serving_operator_.SetCode(operator_code);
+  serving_operator_.SetName(operator_name);
+  UpdateOperatorInfo();
+  cellular()->HandleNewRegistrationState();
+}
+
+void CellularCapabilityUniversal::OnSignalQualityChanged(uint32 quality) {
+  cellular()->HandleNewSignalQuality(quality);
+}
+
+}  // namespace shill