shill: Lookup the mobile provider database to obtain GSM network info.

Load the provider database in ModemInfo. Use the database to lookup operator
name and country when the GSM registration state changes.

This also adds a small mobile provider database used for unit test purposes.

BUG=chromium-os:19699
TEST=unit tests

Change-Id: I5cf0b6d0682d1e0f47fe4b92a45ec6c48837b650
Reviewed-on: https://gerrit.chromium.org/gerrit/10718
Commit-Ready: Darin Petkov <petkov@chromium.org>
Reviewed-by: Darin Petkov <petkov@chromium.org>
Tested-by: Darin Petkov <petkov@chromium.org>
diff --git a/Makefile b/Makefile
index 8ca362a..ca2bd82 100644
--- a/Makefile
+++ b/Makefile
@@ -12,7 +12,7 @@
 
 # libevent, gdk and gtk-2.0 are needed to leverage chrome's MessageLoop
 # TODO(cmasone): explore if newer versions of libbase let us avoid this.
-BASE_LIBS = -lbase -lchromeos -levent -lpthread -lrt -lcares
+BASE_LIBS = -lbase -lchromeos -levent -lpthread -lrt -lcares -lmobile-provider
 BASE_INCLUDE_DIRS = -I..
 BASE_LIB_DIRS =
 
diff --git a/cellular.cc b/cellular.cc
index 495b479..8bef36d 100644
--- a/cellular.cc
+++ b/cellular.cc
@@ -16,6 +16,7 @@
 #include <base/stringprintf.h>
 #include <chromeos/dbus/service_constants.h>
 #include <mm/mm-modem.h>
+#include <mobile_provider.h>
 
 #include "shill/cellular_capability_cdma.h"
 #include "shill/cellular_capability_gsm.h"
@@ -106,7 +107,8 @@
                    int interface_index,
                    Type type,
                    const string &owner,
-                   const string &path)
+                   const string &path,
+                   mobile_provider_db *provider_db)
     : Device(control_interface,
              dispatcher,
              manager,
@@ -119,6 +121,7 @@
       modem_state_(kModemStateUnknown),
       dbus_owner_(owner),
       dbus_path_(path),
+      provider_db_(provider_db),
       task_factory_(this),
       allow_roaming_(false),
       scanning_(false),
@@ -374,7 +377,7 @@
   DBusProperties::GetString(properties, "imei", &imei_);
   if (DBusProperties::GetString(properties, "imsi", &imsi_) &&
       type_ == kTypeGSM) {
-    // TODO(petkov): Set GSM provider.
+    // TODO(petkov): Set GSM provider based on IMSI and SPN.
   }
   DBusProperties::GetString(properties, "esn", &esn_);
   DBusProperties::GetString(properties, "mdn", &mdn_);
@@ -612,6 +615,7 @@
   gsm_.operator_name = info._3;
   VLOG(2) << "GSM Registration: " << gsm_.registration_state << ", "
           << gsm_.network_id << ", "  << gsm_.operator_name;
+  UpdateGSMOperatorInfo();
 }
 
 void Cellular::HandleNewRegistrationState() {
@@ -649,8 +653,6 @@
   }
   service_->set_network_tech(network_tech);
   service_->set_roaming_state(GetRoamingStateString());
-  // TODO(petkov): For GSM, update the serving operator based on the network id
-  // and the mobile provider database.
 }
 
 void Cellular::GetModemSignalQuality() {
@@ -697,12 +699,12 @@
   switch (type_) {
     case kTypeGSM:
       service_->set_activation_state(flimflam::kActivationStateActivated);
-      // TODO(petkov): Set serving operator.
+      UpdateServingOperator();
       break;
     case kTypeCDMA:
       service_->set_payment_url(cdma_.payment_url);
       service_->set_usage_url(cdma_.usage_url);
-      service_->set_serving_operator(home_provider_);
+      UpdateServingOperator();
       HandleNewCDMAActivationState(MM_MODEM_CDMA_ACTIVATION_ERROR_NO_ERROR);
       break;
     default:
@@ -833,9 +835,6 @@
       "current",
       "forbidden",
     };
-    // TODO(petkov): Do we need the finer level of granularity here or can we
-    // use the same granularity as GetNetworkTechnologyString (e.g.,
-    // HSDPA->"HSPA").
     static const char * const kTechnologyString[] = {
       flimflam::kNetworkTechnologyGsm,
       "GSM Compact",
@@ -874,8 +873,21 @@
       LOG(WARNING) << "Unknown network property ignored: " << it->first;
     }
   }
-  // TODO(petkov): If long name is not set and there's a network ID, do a mobile
-  // database lookup (crosbug.com/19699).
+  // 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(
+            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;
 }
 
@@ -963,6 +975,7 @@
   gsm_.registration_state = status;
   gsm_.network_id = operator_code;
   gsm_.operator_name = operator_name;
+  UpdateGSMOperatorInfo();
   HandleNewRegistrationState();
 }
 
@@ -986,6 +999,47 @@
   }
 }
 
+void Cellular::UpdateGSMOperatorInfo() {
+  if (!gsm_.network_id.empty()) {
+    VLOG(2) << "Looking up network id: " << gsm_.network_id;
+    mobile_provider *provider =
+        mobile_provider_lookup_by_network(provider_db_,
+                                          gsm_.network_id.c_str());
+    if (provider) {
+      const char *provider_name = mobile_provider_get_name(provider);
+      if (provider_name && *provider_name) {
+        gsm_.operator_name = provider_name;
+        gsm_.operator_country = provider->country;
+        VLOG(2) << "Operator name: " << gsm_.operator_name
+                << ", country: " << gsm_.operator_country;
+      }
+    } else {
+      VLOG(2) << "GSM provider not found.";
+    }
+  }
+  UpdateServingOperator();
+}
+
+void Cellular::UpdateServingOperator() {
+  if (!service_.get()) {
+    return;
+  }
+  switch (type_) {
+    case kTypeGSM: {
+      Operator oper;
+      oper.SetName(gsm_.operator_name);
+      oper.SetCode(gsm_.network_id);
+      oper.SetCountry(gsm_.operator_country);
+      service_->set_serving_operator(oper);
+      break;
+    }
+    case kTypeCDMA:
+      service_->set_serving_operator(home_provider_);
+      break;
+    default: NOTREACHED();
+  }
+}
+
 StrIntPair Cellular::SimLockStatusToProperty() {
   return StrIntPair(make_pair(flimflam::kSIMLockTypeProperty,
                               sim_lock_status_.lock_type),
diff --git a/cellular.h b/cellular.h
index 66eb306..a2ab074 100644
--- a/cellular.h
+++ b/cellular.h
@@ -20,6 +20,8 @@
 #include "shill/modem_proxy_interface.h"
 #include "shill/refptr_types.h"
 
+struct mobile_provider_db;
+
 namespace shill {
 
 class CellularCapability;
@@ -117,7 +119,8 @@
            int interface_index,
            Type type,
            const std::string &owner,
-           const std::string &path);
+           const std::string &path,
+           mobile_provider_db *provider_db);
   virtual ~Cellular();
 
   // Asynchronously connects the modem to the network. Populates |error| on
@@ -195,6 +198,7 @@
   FRIEND_TEST(CellularTest, InitProxiesCDMA);
   FRIEND_TEST(CellularTest, InitProxiesGSM);
   FRIEND_TEST(CellularTest, ParseScanResult);
+  FRIEND_TEST(CellularTest, ParseScanResultProviderLookup);
   FRIEND_TEST(CellularTest, RegisterOnNetwork);
   FRIEND_TEST(CellularTest, RegisterOnNetworkError);
   FRIEND_TEST(CellularTest, RequirePIN);
@@ -207,6 +211,7 @@
   FRIEND_TEST(CellularTest, StartLinked);
   FRIEND_TEST(CellularTest, UnblockPIN);
   FRIEND_TEST(CellularTest, UnblockPINError);
+  FRIEND_TEST(CellularTest, UpdateGSMOperatorInfo);
 
   struct CDMA {
     CDMA();
@@ -227,6 +232,7 @@
     uint32 access_technology;
     std::string network_id;
     std::string operator_name;
+    std::string operator_country;
     std::string spn;
   };
 
@@ -307,6 +313,13 @@
 
   void HandleNewCDMAActivationState(uint32 error);
 
+  // Updates the GSM operator name and country based on a newly obtained network
+  // id.
+  void UpdateGSMOperatorInfo();
+
+  // Updates the serving operator on the active service.
+  void UpdateServingOperator();
+
   Stringmap ParseScanResult(
       const ModemGSMNetworkProxyInterface::ScanResult &result);
 
@@ -348,6 +361,8 @@
   scoped_ptr<ModemGSMCardProxyInterface> gsm_card_proxy_;
   scoped_ptr<ModemGSMNetworkProxyInterface> gsm_network_proxy_;
 
+  mobile_provider_db *provider_db_;
+
   CDMA cdma_;
   GSM gsm_;
 
diff --git a/cellular_unittest.cc b/cellular_unittest.cc
index 34bcf67..5e11d3e 100644
--- a/cellular_unittest.cc
+++ b/cellular_unittest.cc
@@ -10,6 +10,7 @@
 
 #include <chromeos/dbus/service_constants.h>
 #include <mm/mm-modem.h>
+#include <mobile_provider.h>
 
 #include "shill/cellular_service.h"
 #include "shill/error.h"
@@ -49,7 +50,8 @@
                              3,
                              Cellular::kTypeGSM,
                              "",
-                             "")) {}
+                             "",
+                             NULL)) {}
   virtual ~CellularPropertyTest() {}
 
  protected:
@@ -128,8 +130,14 @@
                              3,
                              Cellular::kTypeGSM,
                              kDBusOwner,
-                             kDBusPath)) {}
-  virtual ~CellularTest() {}
+                             kDBusPath,
+                             NULL)),
+        provider_db_(NULL) {}
+
+  virtual ~CellularTest() {
+    mobile_provider_close_db(provider_db_);
+    provider_db_ = NULL;
+  }
 
   virtual void SetUp() {
     device_->proxy_factory_ = &proxy_factory_;
@@ -200,6 +208,7 @@
   static const char kMSISDN[];
   static const char kPIN[];
   static const char kPUK[];
+  static const char kTestMobileProviderDBPath[];
 
   void StartRTNLHandler();
   void StopRTNLHandler();
@@ -222,6 +231,7 @@
   scoped_refptr<MockDHCPConfig> dhcp_config_;
 
   CellularRefPtr device_;
+  mobile_provider_db *provider_db_;
 };
 
 const char CellularTest::kTestDeviceName[] = "usb0";
@@ -235,6 +245,8 @@
 const char CellularTest::kMSISDN[] = "12345678901";
 const char CellularTest::kPIN[] = "9876";
 const char CellularTest::kPUK[] = "8765";
+const char CellularTest::kTestMobileProviderDBPath[] =
+    "provider_db_unittest.bfd";
 
 TEST_F(CellularTest, GetTypeString) {
   EXPECT_EQ("CellularTypeGSM", device_->GetTypeString());
@@ -414,6 +426,9 @@
 
 TEST_F(CellularTest, StartGSMRegister) {
   device_->type_ = Cellular::kTypeGSM;
+  provider_db_ = mobile_provider_open_db(kTestMobileProviderDBPath);
+  ASSERT_TRUE(provider_db_);
+  device_->provider_db_ = provider_db_;
   static const char kNetwork[] = "My Favorite GSM Network";
   const int kStrength = 70;
   device_->selected_network_ = kNetwork;
@@ -429,7 +444,9 @@
       .WillOnce(Return(MM_MODEM_GSM_ACCESS_TECH_EDGE));
   EXPECT_CALL(*proxy_, GetInfo()).WillOnce(Return(ModemProxyInterface::Info()));
   ModemGSMNetworkProxyInterface::RegistrationInfo reg_info;
+  static const char kNetworkID[] = "22803";
   reg_info._1 = MM_MODEM_GSM_NETWORK_REG_STATUS_ROAMING;
+  reg_info._2 = kNetworkID;
   EXPECT_CALL(*gsm_network_proxy_, GetRegistrationInfo())
       .WillOnce(Return(reg_info));
   EXPECT_CALL(*gsm_network_proxy_, GetSignalQuality())
@@ -446,6 +463,9 @@
             device_->service_->network_tech());
   EXPECT_EQ(kStrength, device_->service_->strength());
   EXPECT_EQ(flimflam::kRoamingStateRoaming, device_->service_->roaming_state());
+  EXPECT_EQ(kNetworkID, device_->service_->serving_operator().GetCode());
+  EXPECT_EQ("Orange", device_->service_->serving_operator().GetName());
+  EXPECT_EQ("ch", device_->service_->serving_operator().GetCountry());
 }
 
 TEST_F(CellularTest, StartConnected) {
@@ -843,6 +863,19 @@
             parsed[flimflam::kTechnologyProperty]);
 }
 
+TEST_F(CellularTest, ParseScanResultProviderLookup) {
+  provider_db_ = mobile_provider_open_db(kTestMobileProviderDBPath);
+  ASSERT_TRUE(provider_db_);
+  device_->provider_db_ = provider_db_;
+  static const char kID[] = "310210";
+  ModemGSMNetworkProxyInterface::ScanResult result;
+  result[Cellular::kNetworkPropertyID] = kID;
+  Stringmap parsed = device_->ParseScanResult(result);
+  EXPECT_EQ(2, parsed.size());
+  EXPECT_EQ(kID, parsed[flimflam::kNetworkIdProperty]);
+  EXPECT_EQ("T-Mobile", parsed[flimflam::kLongNameProperty]);
+}
+
 TEST_F(CellularTest, Activate) {
   Error error;
   device_->type_ = Cellular::kTypeCDMA;
@@ -902,4 +935,18 @@
             device_->service_->network_tech());
 }
 
+TEST_F(CellularTest, UpdateGSMOperatorInfo) {
+  static const char kOperatorName[] = "Swisscom";
+  provider_db_ = mobile_provider_open_db(kTestMobileProviderDBPath);
+  ASSERT_TRUE(provider_db_);
+  device_->provider_db_ = provider_db_;
+  device_->gsm_.network_id = "22801";
+  device_->service_ = new CellularService(
+      &control_interface_, &dispatcher_, &manager_, device_);
+  device_->UpdateGSMOperatorInfo();
+  EXPECT_EQ(kOperatorName, device_->gsm_.operator_name);
+  EXPECT_EQ("ch", device_->gsm_.operator_country);
+  EXPECT_EQ(kOperatorName, device_->service_->serving_operator().GetName());
+}
+
 }  // namespace shill
diff --git a/modem.cc b/modem.cc
index f1fc98e..a5569b9 100644
--- a/modem.cc
+++ b/modem.cc
@@ -30,14 +30,16 @@
              const std::string &path,
              ControlInterface *control_interface,
              EventDispatcher *dispatcher,
-             Manager *manager)
+             Manager *manager,
+             mobile_provider_db *provider_db)
     : proxy_factory_(ProxyFactory::GetInstance()),
       owner_(owner),
       path_(path),
       task_factory_(this),
       control_interface_(control_interface),
       dispatcher_(dispatcher),
-      manager_(manager) {
+      manager_(manager),
+      provider_db_(provider_db) {
   LOG(INFO) << "Modem created: " << owner << " at " << path;
 }
 
@@ -120,7 +122,8 @@
                          interface_index,
                          type,
                          owner_,
-                         path_);
+                         path_,
+                         provider_db_);
 
   uint32 modem_state = Cellular::kModemStateUnknown;
   DBusProperties::GetUint32(properties, kPropertyState, &modem_state);
diff --git a/modem.h b/modem.h
index dac7409..d016169 100644
--- a/modem.h
+++ b/modem.h
@@ -15,6 +15,8 @@
 #include "shill/dbus_properties_proxy_interface.h"
 #include "shill/refptr_types.h"
 
+struct mobile_provider_db;
+
 namespace shill {
 
 class ControlInterface;
@@ -33,7 +35,8 @@
         const std::string &path,
         ControlInterface *control_interface,
         EventDispatcher *dispatcher,
-        Manager *manager);
+        Manager *manager,
+        mobile_provider_db *provider_db);
   ~Modem();
 
   // Asynchronously initializes support for the modem, possibly constructing a
@@ -88,6 +91,7 @@
   ControlInterface *control_interface_;
   EventDispatcher *dispatcher_;
   Manager *manager_;
+  mobile_provider_db *provider_db_;
 
   DISALLOW_COPY_AND_ASSIGN(Modem);
 };
diff --git a/modem_info.cc b/modem_info.cc
index 230e927..0a29c5b 100644
--- a/modem_info.cc
+++ b/modem_info.cc
@@ -4,7 +4,9 @@
 
 #include "shill/modem_info.h"
 
+#include <base/logging.h>
 #include <mm/mm-modem.h>
+#include <mobile_provider.h>
 
 #include "shill/modem_manager.h"
 
@@ -14,6 +16,8 @@
 
 const char ModemInfo::kCromoService[] = "org.chromium.ModemManager";
 const char ModemInfo::kCromoPath[] = "/org/chromium/ModemManager";
+const char ModemInfo::kMobileProviderDBPath[] =
+    "/usr/share/mobile-broadband-provider-info/serviceproviders.bfd";
 
 ModemInfo::ModemInfo(ControlInterface *control_interface,
                      EventDispatcher *dispatcher,
@@ -22,18 +26,27 @@
     : control_interface_(control_interface),
       dispatcher_(dispatcher),
       manager_(manager),
-      glib_(glib) {}
+      glib_(glib),
+      provider_db_path_(kMobileProviderDBPath),
+      provider_db_(NULL) {}
 
 ModemInfo::~ModemInfo() {
   Stop();
 }
 
 void ModemInfo::Start() {
+  // TODO(petkov): Consider initializing the mobile provider database lazily
+  // only if a GSM modem needs to be registered.
+  provider_db_ = mobile_provider_open_db(provider_db_path_.c_str());
+  PLOG_IF(WARNING, !provider_db_)
+      << "Unable to load mobile provider database: ";
   RegisterModemManager(MM_MODEMMANAGER_SERVICE, MM_MODEMMANAGER_PATH);
   RegisterModemManager(kCromoService, kCromoPath);
 }
 
 void ModemInfo::Stop() {
+  mobile_provider_close_db(provider_db_);
+  provider_db_ = NULL;
   modem_managers_.reset();
 }
 
@@ -44,7 +57,8 @@
                                            control_interface_,
                                            dispatcher_,
                                            manager_,
-                                           glib_);
+                                           glib_,
+                                           provider_db_);
   modem_managers_.push_back(manager);  // Passes ownership.
   manager->Start();
 }
diff --git a/modem_info.h b/modem_info.h
index e94c678..2678e37 100644
--- a/modem_info.h
+++ b/modem_info.h
@@ -10,6 +10,8 @@
 #include <base/memory/scoped_vector.h>
 #include <gtest/gtest_prod.h>  // for FRIEND_TEST
 
+struct mobile_provider_db;
+
 namespace shill {
 
 class ControlInterface;
@@ -37,6 +39,7 @@
 
   static const char kCromoService[];
   static const char kCromoPath[];
+  static const char kMobileProviderDBPath[];
 
   // Registers a new ModemManager service handler and starts it.
   void RegisterModemManager(const std::string &service,
@@ -49,6 +52,9 @@
   Manager *manager_;
   GLib *glib_;
 
+  std::string provider_db_path_;  // For testing.
+  mobile_provider_db *provider_db_;  // Database instance owned by |this|.
+
   DISALLOW_COPY_AND_ASSIGN(ModemInfo);
 };
 
diff --git a/modem_info_unittest.cc b/modem_info_unittest.cc
index 099914d..ff7c71c 100644
--- a/modem_info_unittest.cc
+++ b/modem_info_unittest.cc
@@ -4,6 +4,7 @@
 
 #include <base/stl_util-inl.h>
 #include <gtest/gtest.h>
+#include <mobile_provider.h>
 
 #include "shill/manager.h"
 #include "shill/mock_control.h"
@@ -25,6 +26,8 @@
         modem_info_(&control_interface_, &dispatcher_, &manager_, &glib_) {}
 
  protected:
+  static const char kTestMobileProviderDBPath[];
+
   MockGLib glib_;
   MockControl control_interface_;
   EventDispatcher dispatcher_;
@@ -32,6 +35,9 @@
   ModemInfo modem_info_;
 };
 
+const char ModemInfoTest::kTestMobileProviderDBPath[] =
+    "provider_db_unittest.bfd";
+
 TEST_F(ModemInfoTest, StartStop) {
   const int kWatcher1 = 123;
   const int kWatcher2 = 456;
@@ -39,13 +45,18 @@
   EXPECT_CALL(glib_, BusWatchName(_, _, _, _, _, _, _))
       .WillOnce(Return(kWatcher1))
       .WillOnce(Return(kWatcher2));
+  modem_info_.provider_db_path_ = kTestMobileProviderDBPath;
   modem_info_.Start();
   EXPECT_EQ(2, modem_info_.modem_managers_.size());
+  EXPECT_TRUE(modem_info_.provider_db_);
+  EXPECT_TRUE(mobile_provider_lookup_by_name(modem_info_.provider_db_, "AT&T"));
+  EXPECT_FALSE(mobile_provider_lookup_by_name(modem_info_.provider_db_, "xyz"));
 
   EXPECT_CALL(glib_, BusUnwatchName(kWatcher1)).Times(1);
   EXPECT_CALL(glib_, BusUnwatchName(kWatcher2)).Times(1);
   modem_info_.Stop();
   EXPECT_EQ(0, modem_info_.modem_managers_.size());
+  EXPECT_FALSE(modem_info_.provider_db_);
 }
 
 TEST_F(ModemInfoTest, RegisterModemManager) {
@@ -53,11 +64,15 @@
   static const char kService[] = "some.dbus.service";
   EXPECT_CALL(glib_, BusWatchName(_, _, _, _, _, _, _))
       .WillOnce(Return(kWatcher));
+  // Passes ownership of the database.
+  modem_info_.provider_db_ = mobile_provider_open_db(kTestMobileProviderDBPath);
+  EXPECT_TRUE(modem_info_.provider_db_);
   modem_info_.RegisterModemManager(kService, "/dbus/service/path");
   ASSERT_EQ(1, modem_info_.modem_managers_.size());
   ModemManager *manager = modem_info_.modem_managers_[0];
   EXPECT_EQ(kService, manager->service_);
   EXPECT_EQ(kWatcher, manager->watcher_id_);
+  EXPECT_EQ(modem_info_.provider_db_, manager->provider_db_);
   manager->watcher_id_ = 0;
 }
 
diff --git a/modem_manager.cc b/modem_manager.cc
index 91cad69..f335053 100644
--- a/modem_manager.cc
+++ b/modem_manager.cc
@@ -22,7 +22,8 @@
                            ControlInterface *control_interface,
                            EventDispatcher *dispatcher,
                            Manager *manager,
-                           GLib *glib)
+                           GLib *glib,
+                           mobile_provider_db *provider_db)
     : proxy_factory_(ProxyFactory::GetInstance()),
       service_(service),
       path_(path),
@@ -30,7 +31,8 @@
       control_interface_(control_interface),
       dispatcher_(dispatcher),
       manager_(manager),
-      glib_(glib) {}
+      glib_(glib),
+      provider_db_(provider_db) {}
 
 ModemManager::~ModemManager() {
   Stop();
@@ -100,8 +102,12 @@
     LOG(INFO) << "Modem already exists; ignored.";
     return;
   }
-  shared_ptr<Modem> modem(
-      new Modem(owner_, path, control_interface_, dispatcher_, manager_));
+  shared_ptr<Modem> modem(new Modem(owner_,
+                                    path,
+                                    control_interface_,
+                                    dispatcher_,
+                                    manager_,
+                                    provider_db_));
   modems_[path] = modem;
   modem->Init();
 }
diff --git a/modem_manager.h b/modem_manager.h
index f706b2b..1c926cb 100644
--- a/modem_manager.h
+++ b/modem_manager.h
@@ -14,6 +14,8 @@
 
 #include "shill/glib.h"
 
+struct mobile_provider_db;
+
 namespace shill {
 
 class ControlInterface;
@@ -31,7 +33,8 @@
                ControlInterface *control_interface,
                EventDispatcher *dispatcher,
                Manager *manager,
-               GLib *glib);
+               GLib *glib,
+               mobile_provider_db *provider_db);
   ~ModemManager();
 
   // Starts watching for and handling the DBus modem manager service.
@@ -91,6 +94,7 @@
   EventDispatcher *dispatcher_;
   Manager *manager_;
   GLib *glib_;
+  mobile_provider_db *provider_db_;
 
   DISALLOW_COPY_AND_ASSIGN(ModemManager);
 };
diff --git a/modem_manager_unittest.cc b/modem_manager_unittest.cc
index 0eebd34..74f0f34 100644
--- a/modem_manager_unittest.cc
+++ b/modem_manager_unittest.cc
@@ -33,7 +33,8 @@
                        &control_interface_,
                        &dispatcher_,
                        &manager_,
-                       &glib_),
+                       &glib_,
+                       NULL),
         proxy_(new MockModemManagerProxy()),
         proxy_factory_(this) {
   }
diff --git a/modem_unittest.cc b/modem_unittest.cc
index 0c63e14..4f99d90 100644
--- a/modem_unittest.cc
+++ b/modem_unittest.cc
@@ -52,7 +52,12 @@
       : manager_(&control_interface_, &dispatcher_, &glib_),
         proxy_(new MockDBusPropertiesProxy()),
         proxy_factory_(this),
-        modem_(kOwner, kPath, &control_interface_, &dispatcher_, &manager_) {}
+        modem_(kOwner,
+               kPath,
+               &control_interface_,
+               &dispatcher_,
+               &manager_,
+               NULL) {}
 
   virtual void SetUp();
   virtual void TearDown();
diff --git a/provider_db_unittest.bfd b/provider_db_unittest.bfd
new file mode 100644
index 0000000..2a7d3f0
--- /dev/null
+++ b/provider_db_unittest.bfd
@@ -0,0 +1,39 @@
+# Copyright (c) 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.
+serviceproviders:2.0
+country:ch
+provider:1,3,0
+name:,Orange
+networks:22803
+apn:1,mobileoffice3g,,
+name:,Internet Everywhere - Standard
+apn:1,click,,
+name:,Internet Everywhere - Prepaid
+apn:1,intranetaccess,,
+name:,Internet Everywhere - Intranet Access
+provider:1,1,0
+name:,Swisscom
+networks:22801
+apn:0,gprs.swisscom.ch,,
+country:us
+provider:1,3,0
+name:,AT&T
+networks:310038,310090,310150,310410,310560,310680
+apn:1,wap.cingular,,
+name:,MEdia Net (phones)
+apn:1,Broadband,,
+name:,LaptopConnect (data cards)
+apn:1,isp.cingular,,
+name:,Data Connect (old)
+provider:1,4,0
+name:,T-Mobile
+networks:310160,310200,310210,310220,310230,310240,310250,310260,310270,310310,310490,310580,310660,310800
+apn:1,epc.tmobile.com,,
+name:,Internet/WebConnect
+apn:1,wap.voicestream.com,,
+name:,Web2Go/t-zones
+apn:1,internet2.voicestream.com,,
+name:,Internet (old)
+apn:1,internet3.voicestream.com,,
+name:,Internet with VPN (old)