shill: Create an APN list for the home GSM provider and broadcast it.

BUG=chromium-os:23201
TEST=unit tests

Change-Id: I7eda725099cb83d118099af75530f776f9f73d7d
Reviewed-on: https://gerrit.chromium.org/gerrit/11981
Tested-by: Darin Petkov <petkov@chromium.org>
Reviewed-by: mukesh agrawal <quiche@chromium.org>
Reviewed-by: Eric Shienbrood <ers@chromium.org>
Commit-Ready: Darin Petkov <petkov@chromium.org>
diff --git a/adaptor_interfaces.h b/adaptor_interfaces.h
index 03d804f..10e4f2c 100644
--- a/adaptor_interfaces.h
+++ b/adaptor_interfaces.h
@@ -10,6 +10,8 @@
 
 #include <base/basictypes.h>
 
+#include "shill/accessor_interface.h"
+
 namespace shill {
 
 // These are the functions that a Device adaptor must support
@@ -27,11 +29,13 @@
 
   virtual void UpdateEnabled() = 0;
 
-  virtual void EmitBoolChanged(const std::string& name, bool value) = 0;
-  virtual void EmitUintChanged(const std::string& name, uint32 value) = 0;
-  virtual void EmitIntChanged(const std::string& name, int value) = 0;
-  virtual void EmitStringChanged(const std::string& name,
-                                 const std::string& value) = 0;
+  virtual void EmitBoolChanged(const std::string &name, bool value) = 0;
+  virtual void EmitUintChanged(const std::string &name, uint32 value) = 0;
+  virtual void EmitIntChanged(const std::string &name, int value) = 0;
+  virtual void EmitStringChanged(const std::string &name,
+                                 const std::string &value) = 0;
+  virtual void EmitStringmapsChanged(const std::string &name,
+                                     const Stringmaps &value) = 0;
 };
 
 // These are the functions that an IPConfig adaptor must support
diff --git a/cellular_capability_gsm.cc b/cellular_capability_gsm.cc
index 842e30f..c33cf14 100644
--- a/cellular_capability_gsm.cc
+++ b/cellular_capability_gsm.cc
@@ -11,6 +11,7 @@
 #include <mm/mm-modem.h>
 #include <mobile_provider.h>
 
+#include "shill/adaptor_interfaces.h"
 #include "shill/cellular_service.h"
 #include "shill/property_accessor.h"
 #include "shill/proxy_factory.h"
@@ -52,6 +53,8 @@
   HelpRegisterDerivedStrIntPair(flimflam::kSIMLockStatusProperty,
                                 &CellularCapabilityGSM::SimLockStatusToProperty,
                                 NULL);
+  store->RegisterConstStringmaps(flimflam::kCellularApnListProperty,
+                                 &apn_list_);
 }
 
 StrIntPair CellularCapabilityGSM::SimLockStatusToProperty(Error */*error*/) {
@@ -201,7 +204,7 @@
     oper.SetName(spn_);
   }
   cellular()->set_home_provider(oper);
-  // TODO(petkov): Create APN list (crosbug.com/23201).
+  InitAPNList();
 }
 
 void CellularCapabilityGSM::UpdateOperatorInfo() {
@@ -236,6 +239,49 @@
   }
 }
 
+void CellularCapabilityGSM::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_);
+}
+
 void CellularCapabilityGSM::Register() {
   LOG(INFO) << __func__ << " \"" << selected_network_ << "\"";
   // TODO(petkov): Switch to asynchronous calls (crosbug.com/17583).
diff --git a/cellular_capability_gsm.h b/cellular_capability_gsm.h
index 470730a..45e9fb4 100644
--- a/cellular_capability_gsm.h
+++ b/cellular_capability_gsm.h
@@ -64,6 +64,7 @@
 
  private:
   friend class CellularCapabilityGSMTest;
+  FRIEND_TEST(CellularCapabilityGSMTest, InitAPNList);
   FRIEND_TEST(CellularCapabilityGSMTest, ParseScanResult);
   FRIEND_TEST(CellularCapabilityGSMTest, ParseScanResultProviderLookup);
   FRIEND_TEST(CellularCapabilityGSMTest, RegisterOnNetwork);
@@ -111,6 +112,9 @@
   // Updates the serving operator on the active service.
   void UpdateServingOperator();
 
+  // Initializes the |apn_list_| property based on the current |home_provider_|.
+  void InitAPNList();
+
   Stringmap ParseScanResult(
       const ModemGSMNetworkProxyInterface::ScanResult &result);
 
@@ -145,6 +149,7 @@
   bool scanning_;
   uint16 scan_interval_;
   SimLockStatus sim_lock_status_;
+  Stringmaps apn_list_;
 
   DISALLOW_COPY_AND_ASSIGN(CellularCapabilityGSM);
 };
diff --git a/cellular_capability_gsm_unittest.cc b/cellular_capability_gsm_unittest.cc
index 7c3fd98..ac7952a 100644
--- a/cellular_capability_gsm_unittest.cc
+++ b/cellular_capability_gsm_unittest.cc
@@ -13,10 +13,12 @@
 #include "shill/cellular_service.h"
 #include "shill/error.h"
 #include "shill/event_dispatcher.h"
+#include "shill/mock_adaptors.h"
 #include "shill/mock_modem_gsm_card_proxy.h"
 #include "shill/mock_modem_gsm_network_proxy.h"
 #include "shill/nice_mock_control.h"
 
+using testing::NiceMock;
 using testing::Return;
 
 namespace shill {
@@ -37,6 +39,7 @@
         card_proxy_(new MockModemGSMCardProxy()),
         network_proxy_(new MockModemGSMNetworkProxy()),
         capability_(NULL),
+        device_adaptor_(NULL),
         provider_db_(NULL) {}
 
   virtual ~CellularCapabilityGSMTest() {
@@ -44,11 +47,14 @@
     mobile_provider_close_db(provider_db_);
     provider_db_ = NULL;
     capability_ = NULL;
+    device_adaptor_ = NULL;
   }
 
   virtual void SetUp() {
     capability_ =
         dynamic_cast<CellularCapabilityGSM *>(cellular_->capability_.get());
+    device_adaptor_ =
+        dynamic_cast<NiceMock<DeviceMockAdaptor> *>(cellular_->adaptor());
   }
 
  protected:
@@ -93,6 +99,7 @@
   scoped_ptr<MockModemGSMCardProxy> card_proxy_;
   scoped_ptr<MockModemGSMNetworkProxy> network_proxy_;
   CellularCapabilityGSM *capability_;  // Owned by |cellular_|.
+  NiceMock<DeviceMockAdaptor> *device_adaptor_;  // Owned by |cellular_|.
   mobile_provider_db *provider_db_;
 };
 
@@ -143,7 +150,7 @@
 
 TEST_F(CellularCapabilityGSMTest, RegisterOnNetwork) {
   Error error;
-  EXPECT_CALL(*network_proxy_, Register(kTestCarrier)).Times(1);
+  EXPECT_CALL(*network_proxy_, Register(kTestCarrier));
   capability_->RegisterOnNetwork(kTestCarrier, &error);
   EXPECT_TRUE(error.IsSuccess());
   SetNetworkProxy();
@@ -153,7 +160,7 @@
 
 TEST_F(CellularCapabilityGSMTest, RequirePIN) {
   Error error;
-  EXPECT_CALL(*card_proxy_, EnablePIN(kPIN, true)).Times(1);
+  EXPECT_CALL(*card_proxy_, EnablePIN(kPIN, true));
   capability_->RequirePIN(kPIN, true, &error);
   EXPECT_TRUE(error.IsSuccess());
   SetCardProxy();
@@ -162,7 +169,7 @@
 
 TEST_F(CellularCapabilityGSMTest, EnterPIN) {
   Error error;
-  EXPECT_CALL(*card_proxy_, SendPIN(kPIN)).Times(1);
+  EXPECT_CALL(*card_proxy_, SendPIN(kPIN));
   capability_->EnterPIN(kPIN, &error);
   EXPECT_TRUE(error.IsSuccess());
   SetCardProxy();
@@ -171,7 +178,7 @@
 
 TEST_F(CellularCapabilityGSMTest, UnblockPIN) {
   Error error;
-  EXPECT_CALL(*card_proxy_, SendPUK(kPUK, kPIN)).Times(1);
+  EXPECT_CALL(*card_proxy_, SendPUK(kPUK, kPIN));
   capability_->UnblockPIN(kPUK, kPIN, &error);
   EXPECT_TRUE(error.IsSuccess());
   SetCardProxy();
@@ -181,7 +188,7 @@
 TEST_F(CellularCapabilityGSMTest, ChangePIN) {
   static const char kOldPIN[] = "1111";
   Error error;
-  EXPECT_CALL(*card_proxy_, ChangePIN(kOldPIN, kPIN)).Times(1);
+  EXPECT_CALL(*card_proxy_, ChangePIN(kOldPIN, kPIN));
   capability_->ChangePIN(kOldPIN, kPIN, &error);
   EXPECT_TRUE(error.IsSuccess());
   SetCardProxy();
@@ -288,6 +295,7 @@
   EXPECT_EQ("T-Mobile", cellular_->home_provider().GetName());
   EXPECT_EQ(kCountry, cellular_->home_provider().GetCountry());
   EXPECT_EQ(kCode, cellular_->home_provider().GetCode());
+  EXPECT_EQ(4, capability_->apn_list_.size());
 
   Cellular::Operator oper;
   cellular_->set_home_provider(oper);
@@ -298,6 +306,31 @@
   EXPECT_EQ(kCode, cellular_->home_provider().GetCode());
 }
 
+namespace {
+
+MATCHER(SizeIs4, "") {
+  return arg.size() == 4;
+}
+
+}  // namespace
+
+TEST_F(CellularCapabilityGSMTest, InitAPNList) {
+  InitProviderDB();
+  capability_->home_provider_ =
+      mobile_provider_lookup_by_name(cellular_->provider_db(), "T-Mobile");
+  ASSERT_TRUE(capability_->home_provider_);
+  EXPECT_EQ(0, capability_->apn_list_.size());
+  EXPECT_CALL(*device_adaptor_,
+              EmitStringmapsChanged(flimflam::kCellularApnListProperty,
+                                    SizeIs4()));
+  capability_->InitAPNList();
+  EXPECT_EQ(4, capability_->apn_list_.size());
+  EXPECT_EQ("wap.voicestream.com",
+            capability_->apn_list_[1][flimflam::kApnProperty]);
+  EXPECT_EQ("Web2Go/t-zones",
+            capability_->apn_list_[1][flimflam::kApnNameProperty]);
+}
+
 TEST_F(CellularCapabilityGSMTest, GetNetworkTechnologyString) {
   EXPECT_EQ("", capability_->GetNetworkTechnologyString());
   SetAccessTechnology(MM_MODEM_GSM_ACCESS_TECH_GSM);
diff --git a/device.h b/device.h
index 4e0f26a..bc8dd9d 100644
--- a/device.h
+++ b/device.h
@@ -101,6 +101,8 @@
 
   void set_dhcp_provider(DHCPProvider *provider) { dhcp_provider_ = provider; }
 
+  DeviceAdaptorInterface *adaptor() const { return adaptor_.get(); }
+
  protected:
   FRIEND_TEST(DeviceTest, AcquireDHCPConfig);
   FRIEND_TEST(DeviceTest, DestroyIPConfig);
diff --git a/device_dbus_adaptor.cc b/device_dbus_adaptor.cc
index 564b811..b3c2270 100644
--- a/device_dbus_adaptor.cc
+++ b/device_dbus_adaptor.cc
@@ -57,6 +57,11 @@
   PropertyChanged(name, DBusAdaptor::StringToVariant(value));
 }
 
+void DeviceDBusAdaptor::EmitStringmapsChanged(const std::string &name,
+                                              const Stringmaps &value) {
+  PropertyChanged(name, DBusAdaptor::StringmapsToVariant(value));
+}
+
 map<string, ::DBus::Variant> DeviceDBusAdaptor::GetProperties(
     ::DBus::Error &error) {
   map<string, ::DBus::Variant> properties;
diff --git a/device_dbus_adaptor.h b/device_dbus_adaptor.h
index dccc3d0..e90ac0f 100644
--- a/device_dbus_adaptor.h
+++ b/device_dbus_adaptor.h
@@ -41,6 +41,8 @@
   virtual void EmitIntChanged(const std::string& name, int value);
   virtual void EmitStringChanged(const std::string& name,
                                  const std::string& value);
+  virtual void EmitStringmapsChanged(const std::string &name,
+                                     const Stringmaps &value);
 
   // Implementation of Device_adaptor.
   virtual std::map<std::string, ::DBus::Variant> GetProperties(
diff --git a/mock_adaptors.h b/mock_adaptors.h
index 30b0901..a722fa7 100644
--- a/mock_adaptors.h
+++ b/mock_adaptors.h
@@ -29,6 +29,8 @@
   MOCK_METHOD2(EmitUintChanged, void(const std::string&, uint32));
   MOCK_METHOD2(EmitIntChanged, void(const std::string&, int));
   MOCK_METHOD2(EmitStringChanged, void(const std::string&, const std::string&));
+  MOCK_METHOD2(EmitStringmapsChanged, void(const std::string &,
+                                           const Stringmaps &));
 
  private:
   const std::string rpc_id;