shill: Make cellular Connect work for GSM networks.
Add the APN to the properties map passed to Connect.
Set up the list of APNs to try, and step through it when
a connect attempt fails with the InvalidApn error. The
order in which APNs are tried is
- APN which most recently resulted in a successful
connection on the current network
- a user-specified APN, specified by setting the Cellular.APN
property on the service
- the list of APNs found for the home network provider in the
mobile broadband provider database
- if all those fail, a null APN
BUG=chromium-os:23259
TEST=manual testing, some of which involved modifying the
mobile provider DB to create invalid APN entries. Created
two new unit tests as well, and ran all unit tests.
Change-Id: I38c869228fe1aaf7421de8a826c54e7b62c209b2
Reviewed-on: https://gerrit.chromium.org/gerrit/19122
Tested-by: Eric Shienbrood <ers@chromium.org>
Reviewed-by: Jason Glasgow <jglasgow@chromium.org>
Commit-Ready: Eric Shienbrood <ers@chromium.org>
diff --git a/cellular.cc b/cellular.cc
index bf49d2f..3410645 100644
--- a/cellular.cc
+++ b/cellular.cc
@@ -301,8 +301,7 @@
void Cellular::Connect(Error *error) {
VLOG(2) << __func__;
- if (state_ == kStateConnected ||
- state_ == kStateLinked) {
+ if (state_ == kStateConnected || state_ == kStateLinked) {
Error::PopulateAndLog(error, Error::kAlreadyConnected,
"Already connected; connection request ignored.");
return;
@@ -333,8 +332,8 @@
}
}
-void Cellular::OnConnectFailed() {
- // TODO(ers): Signal failure.
+void Cellular::OnConnectFailed(const Error &error) {
+ service()->SetFailure(Service::kFailureUnknown);
}
void Cellular::Disconnect(Error *error) {
diff --git a/cellular.h b/cellular.h
index 8d115c8..cbe2197 100644
--- a/cellular.h
+++ b/cellular.h
@@ -167,7 +167,7 @@
void OnModemStopped(const EnabledStateChangedCallback &callback,
const Error &error);
void OnConnected();
- void OnConnectFailed();
+ void OnConnectFailed(const Error &error);
void OnDisconnected();
void OnDisconnectFailed();
diff --git a/cellular_capability.cc b/cellular_capability.cc
index 4dd3cd9..10a068e 100644
--- a/cellular_capability.cc
+++ b/cellular_capability.cc
@@ -19,6 +19,10 @@
namespace shill {
+const char CellularCapability::kConnectPropertyApn[] = "apn";
+const char CellularCapability::kConnectPropertyApnUsername[] = "username";
+const char CellularCapability::kConnectPropertyApnPassword[] = "password";
+const char CellularCapability::kConnectPropertyHomeOnly[] = "home_only";
const char CellularCapability::kConnectPropertyPhoneNumber[] = "number";
const char CellularCapability::kPropertyIMSI[] = "imsi";
// All timeout values are in milliseconds
@@ -292,7 +296,7 @@
if (error.IsSuccess())
cellular()->OnConnected();
else
- cellular()->OnConnectFailed();
+ cellular()->OnConnectFailed(error);
if (!callback.is_null())
callback.Run(error);
}
diff --git a/cellular_capability.h b/cellular_capability.h
index cde66eb..105e119 100644
--- a/cellular_capability.h
+++ b/cellular_capability.h
@@ -38,6 +38,10 @@
static const int kTimeoutRegister;
static const int kTimeoutScan;
+ static const char kConnectPropertyApn[];
+ static const char kConnectPropertyApnUsername[];
+ static const char kConnectPropertyApnPassword[];
+ static const char kConnectPropertyHomeOnly[];
static const char kConnectPropertyPhoneNumber[];
static const char kPropertyIMSI[];
@@ -134,6 +138,8 @@
static void OnUnsupportedOperation(const char *operation, Error *error);
+ virtual void OnConnectReply(const ResultCallback &callback,
+ const Error &error);
// Run the next task in a list.
// Precondition: |tasks| is not empty.
void RunNextStep(CellularTaskList *tasks);
@@ -165,6 +171,7 @@
FRIEND_TEST(CellularCapabilityTest, FinishEnable);
FRIEND_TEST(CellularCapabilityTest, GetModemInfo);
FRIEND_TEST(CellularCapabilityTest, GetModemStatus);
+ FRIEND_TEST(CellularCapabilityTest, TryApns);
FRIEND_TEST(CellularServiceTest, FriendlyName);
FRIEND_TEST(CellularTest, StartCDMARegister);
FRIEND_TEST(CellularTest, StartConnected);
@@ -192,8 +199,6 @@
virtual void OnGetModemStatusReply(const ResultCallback &callback,
const DBusPropertiesMap &props,
const Error &error);
- virtual void OnConnectReply(const ResultCallback &callback,
- const Error &error);
virtual void OnDisconnectReply(const ResultCallback &callback,
const Error &error);
diff --git a/cellular_capability_gsm.cc b/cellular_capability_gsm.cc
index 6a7e88e..51f80f3 100644
--- a/cellular_capability_gsm.cc
+++ b/cellular_capability_gsm.cc
@@ -184,11 +184,81 @@
}
}
+// 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 CellularCapabilityGSM::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 CellularCapabilityGSM::SetupConnectProperties(
DBusPropertiesMap *properties) {
+ SetupApnTryList();
+ FillConnectPropertyMap(properties);
+}
+
+void CellularCapabilityGSM::FillConnectPropertyMap(
+ DBusPropertiesMap *properties) {
(*properties)[kConnectPropertyPhoneNumber].writer().append_string(
kPhoneNumber);
- // TODO(petkov): Setup apn and "home_only".
+
+ if (!allow_roaming_)
+ (*properties)[kConnectPropertyHomeOnly].writer().append_bool(true);
+
+ 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)[kConnectPropertyApn].writer().append_string(
+ apn_info[flimflam::kApnProperty].c_str());
+ if (ContainsKey(apn_info, flimflam::kApnUsernameProperty))
+ (*properties)[kConnectPropertyApnUsername].writer().append_string(
+ apn_info[flimflam::kApnUsernameProperty].c_str());
+ if (ContainsKey(apn_info, flimflam::kApnPasswordProperty))
+ (*properties)[kConnectPropertyApnPassword].writer().append_string(
+ apn_info[flimflam::kApnPasswordProperty].c_str());
+ }
+}
+
+void CellularCapabilityGSM::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;
+ }
+ } else if (!apn_try_list_.empty()) {
+ cellular()->service()->SetLastGoodApn(apn_try_list_.front());
+ apn_try_list_.clear();
+ }
+ CellularCapability::OnConnectReply(callback, error);
}
// always called from an async context
diff --git a/cellular_capability_gsm.h b/cellular_capability_gsm.h
index 916506b..2f22628 100644
--- a/cellular_capability_gsm.h
+++ b/cellular_capability_gsm.h
@@ -5,6 +5,8 @@
#ifndef SHILL_CELLULAR_CAPABILITY_GSM_
#define SHILL_CELLULAR_CAPABILITY_GSM_
+#include <deque>
+
#include <base/memory/scoped_ptr.h>
#include <base/memory/weak_ptr.h>
#include <gtest/gtest_prod.h> // for FRIEND_TEST
@@ -68,6 +70,10 @@
protected:
virtual void InitProxies();
virtual void ReleaseProxies();
+ // Override OnConnectReply in order to handle the possibility of
+ // retrying the Connect operation.
+ virtual void OnConnectReply(const ResultCallback &callback,
+ const Error &error);
private:
friend class CellularTest;
@@ -93,7 +99,9 @@
FRIEND_TEST(CellularCapabilityGSMTest, UpdateOperatorInfo);
FRIEND_TEST(CellularCapabilityGSMTest, GetRegistrationState);
FRIEND_TEST(CellularCapabilityGSMTest, OnModemManagerPropertiesChanged);
+ FRIEND_TEST(CellularCapabilityGSMTest, SetupApnTryList);
FRIEND_TEST(CellularCapabilityTest, AllowRoaming);
+ FRIEND_TEST(CellularCapabilityTest, TryApns);
FRIEND_TEST(CellularTest, StartGSMRegister);
FRIEND_TEST(ModemTest, CreateDeviceFromProperties);
@@ -138,6 +146,9 @@
KeyValueStore SimLockStatusToProperty(Error *error);
+ void SetupApnTryList();
+ void FillConnectPropertyMap(DBusPropertiesMap *properties);
+
void HelpRegisterDerivedKeyValueStore(
const std::string &name,
KeyValueStore(CellularCapabilityGSM::*get)(Error *error),
@@ -189,6 +200,7 @@
// Properties.
std::string selected_network_;
Stringmaps found_networks_;
+ std::deque<Stringmap> apn_try_list_;
bool scanning_;
uint16 scan_interval_;
SimLockStatus sim_lock_status_;
diff --git a/cellular_capability_gsm_unittest.cc b/cellular_capability_gsm_unittest.cc
index 5083736..c516ee9 100644
--- a/cellular_capability_gsm_unittest.cc
+++ b/cellular_capability_gsm_unittest.cc
@@ -18,6 +18,7 @@
#include "shill/mock_metrics.h"
#include "shill/mock_modem_gsm_card_proxy.h"
#include "shill/mock_modem_gsm_network_proxy.h"
+#include "shill/mock_profile.h"
#include "shill/nice_mock_control.h"
using base::Bind;
@@ -617,4 +618,71 @@
EXPECT_EQ(kRetries, capability_->sim_lock_status_.retries_left);
}
+TEST_F(CellularCapabilityGSMTest, SetupApnTryList) {
+ static const string kTmobileApn("epc.tmobile.com");
+ static const string kLastGoodApn("remembered.apn");
+ static const string kLastGoodUsername("remembered.user");
+ static const string kSuppliedApn("my.apn");
+
+ SetService();
+ capability_->imsi_ = "310240123456789";
+ InitProviderDB();
+ capability_->SetHomeProvider();
+ DBusPropertiesMap props;
+ capability_->SetupConnectProperties(&props);
+ EXPECT_FALSE(props.find(flimflam::kApnProperty) == props.end());
+ EXPECT_EQ(kTmobileApn, props[flimflam::kApnProperty].reader().get_string());
+
+ ProfileRefPtr profile(new NiceMock<MockProfile>(
+ &control_, reinterpret_cast<Manager *>(NULL)));
+ cellular_->service()->set_profile(profile);
+ Stringmap apn_info;
+ apn_info[flimflam::kApnProperty] = kLastGoodApn;
+ apn_info[flimflam::kApnUsernameProperty] = kLastGoodUsername;
+ cellular_->service()->SetLastGoodApn(apn_info);
+ props.clear();
+ EXPECT_TRUE(props.find(flimflam::kApnProperty) == props.end());
+ capability_->SetupConnectProperties(&props);
+ // We expect the list to contain the last good APN, plus
+ // the 4 APNs from the mobile provider info database.
+ EXPECT_EQ(5, capability_->apn_try_list_.size());
+ EXPECT_FALSE(props.find(flimflam::kApnProperty) == props.end());
+ EXPECT_EQ(kLastGoodApn, props[flimflam::kApnProperty].reader().get_string());
+ EXPECT_FALSE(props.find(flimflam::kApnUsernameProperty) == props.end());
+ EXPECT_EQ(kLastGoodUsername,
+ props[flimflam::kApnUsernameProperty].reader().get_string());
+
+ Error error;
+ apn_info.clear();
+ props.clear();
+ apn_info[flimflam::kApnProperty] = kSuppliedApn;
+ // Setting the APN has the side effect of clearing the LastGoodApn,
+ // so the try list will have 5 elements, with the first one being
+ // the supplied APN.
+ cellular_->service()->SetApn(apn_info, &error);
+ EXPECT_TRUE(props.find(flimflam::kApnProperty) == props.end());
+ capability_->SetupConnectProperties(&props);
+ EXPECT_EQ(5, capability_->apn_try_list_.size());
+ EXPECT_FALSE(props.find(flimflam::kApnProperty) == props.end());
+ EXPECT_EQ(kSuppliedApn, props[flimflam::kApnProperty].reader().get_string());
+
+ apn_info.clear();
+ props.clear();
+ apn_info[flimflam::kApnProperty] = kLastGoodApn;
+ apn_info[flimflam::kApnUsernameProperty] = kLastGoodUsername;
+ // Now when LastGoodAPN is set, it will be the one selected.
+ cellular_->service()->SetLastGoodApn(apn_info);
+ EXPECT_TRUE(props.find(flimflam::kApnProperty) == props.end());
+ capability_->SetupConnectProperties(&props);
+ // We expect the list to contain the last good APN, plus
+ // the user-supplied APN, plus the 4 APNs from the mobile
+ // provider info database.
+ EXPECT_EQ(6, capability_->apn_try_list_.size());
+ EXPECT_FALSE(props.find(flimflam::kApnProperty) == props.end());
+ EXPECT_EQ(kLastGoodApn, props[flimflam::kApnProperty].reader().get_string());
+ EXPECT_FALSE(props.find(flimflam::kApnUsernameProperty) == props.end());
+ EXPECT_EQ(kLastGoodUsername,
+ props[flimflam::kApnUsernameProperty].reader().get_string());
+}
+
} // namespace shill
diff --git a/cellular_capability_unittest.cc b/cellular_capability_unittest.cc
index ae5bead..62459c1 100644
--- a/cellular_capability_unittest.cc
+++ b/cellular_capability_unittest.cc
@@ -23,6 +23,7 @@
#include "shill/mock_modem_gsm_network_proxy.h"
#include "shill/mock_modem_proxy.h"
#include "shill/mock_modem_simple_proxy.h"
+#include "shill/mock_profile.h"
#include "shill/mock_rtnl_handler.h"
#include "shill/nice_mock_control.h"
#include "shill/proxy_factory.h"
@@ -65,12 +66,15 @@
gsm_network_proxy_(new MockModemGSMNetworkProxy()),
proxy_factory_(this),
capability_(NULL),
- device_adaptor_(NULL) {}
+ device_adaptor_(NULL),
+ provider_db_(NULL) {}
virtual ~CellularCapabilityTest() {
cellular_->service_ = NULL;
capability_ = NULL;
device_adaptor_ = NULL;
+ mobile_provider_close_db(provider_db_);
+ provider_db_ = NULL;
}
virtual void SetUp() {
@@ -84,6 +88,21 @@
capability_->proxy_factory_ = NULL;
}
+ void InitProviderDB() {
+ provider_db_ = mobile_provider_open_db(kTestMobileProviderDBPath);
+ ASSERT_TRUE(provider_db_);
+ cellular_->provider_db_ = provider_db_;
+ }
+
+ void SetService() {
+ cellular_->service_ = new CellularService(
+ &control_, &dispatcher_, &metrics_, NULL, cellular_);
+ }
+
+ CellularCapabilityGSM *GetGsmCapability() {
+ return dynamic_cast<CellularCapabilityGSM *>(cellular_->capability_.get());
+ }
+
void InvokeEnable(bool enable, Error *error,
const ResultCallback &callback, int timeout) {
callback.Run(Error());
@@ -116,6 +135,7 @@
MOCK_METHOD1(TestCallback, void(const Error &error));
protected:
+ static const char kTestMobileProviderDBPath[];
static const char kTestCarrier[];
static const char kManufacturer[];
static const char kModelID[];
@@ -193,8 +213,11 @@
TestProxyFactory proxy_factory_;
CellularCapability *capability_; // Owned by |cellular_|.
NiceMock<DeviceMockAdaptor> *device_adaptor_; // Owned by |cellular_|.
+ mobile_provider_db *provider_db_;
};
+const char CellularCapabilityTest::kTestMobileProviderDBPath[] =
+ "provider_db_unittest.bfd";
const char CellularCapabilityTest::kTestCarrier[] = "The Cellular Carrier";
const char CellularCapabilityTest::kManufacturer[] = "Company";
const char CellularCapabilityTest::kModelID[] = "Gobi 2000";
@@ -297,4 +320,76 @@
EXPECT_EQ(Cellular::kStateRegistered, cellular_->state_);
}
+MATCHER_P(HasApn, apn, "") {
+ DBusPropertiesMap::const_iterator it = arg.find(flimflam::kApnProperty);
+ return it != arg.end() && apn == it->second.reader().get_string();
+}
+
+MATCHER(HasNoApn, "") {
+ return arg.find(flimflam::kApnProperty) == arg.end();
+}
+
+TEST_F(CellularCapabilityTest, TryApns) {
+ static const string kLastGoodApn("remembered.apn");
+ static const string kSuppliedApn("my.apn");
+ static const string kTmobileApn1("epc.tmobile.com");
+ static const string kTmobileApn2("wap.voicestream.com");
+ static const string kTmobileApn3("internet2.voicestream.com");
+ static const string kTmobileApn4("internet3.voicestream.com");
+
+ using testing::InSequence;
+ {
+ InSequence dummy;
+ EXPECT_CALL(*simple_proxy_, Connect(HasApn(kLastGoodApn), _, _, _));
+ EXPECT_CALL(*simple_proxy_, Connect(HasApn(kSuppliedApn), _, _, _));
+ EXPECT_CALL(*simple_proxy_, Connect(HasApn(kTmobileApn1), _, _, _));
+ EXPECT_CALL(*simple_proxy_, Connect(HasApn(kTmobileApn2), _, _, _));
+ EXPECT_CALL(*simple_proxy_, Connect(HasApn(kTmobileApn3), _, _, _));
+ EXPECT_CALL(*simple_proxy_, Connect(HasApn(kTmobileApn4), _, _, _));
+ EXPECT_CALL(*simple_proxy_, Connect(HasNoApn(), _, _, _));
+ }
+ CellularCapabilityGSM *gsm_capability = GetGsmCapability();
+ SetService();
+ gsm_capability->imsi_ = "310240123456789";
+ InitProviderDB();
+ gsm_capability->SetHomeProvider();
+ ProfileRefPtr profile(new NiceMock<MockProfile>(
+ &control_, reinterpret_cast<Manager *>(NULL)));
+ cellular_->service()->set_profile(profile);
+
+ Error error;
+ Stringmap apn_info;
+ DBusPropertiesMap props;
+ apn_info[flimflam::kApnProperty] = kSuppliedApn;
+ cellular_->service()->SetApn(apn_info, &error);
+
+ apn_info.clear();
+ apn_info[flimflam::kApnProperty] = kLastGoodApn;
+ cellular_->service()->SetLastGoodApn(apn_info);
+
+ capability_->SetupConnectProperties(&props);
+ // We expect the list to contain the last good APN, plus
+ // the user-supplied APN, plus the 4 APNs from the mobile
+ // provider info database.
+ EXPECT_EQ(6, gsm_capability->apn_try_list_.size());
+ EXPECT_FALSE(props.find(flimflam::kApnProperty) == props.end());
+ EXPECT_EQ(kLastGoodApn, props[flimflam::kApnProperty].reader().get_string());
+
+ SetSimpleProxy();
+ capability_->Connect(props, &error, ResultCallback());
+ Error cerror(Error::kInvalidApn);
+ capability_->OnConnectReply(ResultCallback(), cerror);
+ EXPECT_EQ(5, gsm_capability->apn_try_list_.size());
+ capability_->OnConnectReply(ResultCallback(), cerror);
+ EXPECT_EQ(4, gsm_capability->apn_try_list_.size());
+ capability_->OnConnectReply(ResultCallback(), cerror);
+ EXPECT_EQ(3, gsm_capability->apn_try_list_.size());
+ capability_->OnConnectReply(ResultCallback(), cerror);
+ EXPECT_EQ(2, gsm_capability->apn_try_list_.size());
+ capability_->OnConnectReply(ResultCallback(), cerror);
+ EXPECT_EQ(1, gsm_capability->apn_try_list_.size());
+ capability_->OnConnectReply(ResultCallback(), cerror);
+ EXPECT_EQ(0, gsm_capability->apn_try_list_.size());
+}
+
} // namespace shill
diff --git a/cellular_error.cc b/cellular_error.cc
index 2e3818a..ca404e3 100644
--- a/cellular_error.cc
+++ b/cellular_error.cc
@@ -20,6 +20,8 @@
MM_MOBILE_ERROR(MM_ERROR_MODEM_GSM_SIMPINREQUIRED);
static const char *kErrorSimPukRequired =
MM_MOBILE_ERROR(MM_ERROR_MODEM_GSM_SIMPUKREQUIRED);
+static const char *kErrorGprsNotSubscribed =
+ MM_MOBILE_ERROR(MM_ERROR_MODEM_GSM_GPRSNOTSUBSCRIBED);
// static
void CellularError::FromDBusError(const DBus::Error &dbus_error,
@@ -42,6 +44,8 @@
type = Error::kPinRequired;
else if (name == kErrorSimPukRequired)
type = Error::kPinBlocked;
+ else if (name == kErrorGprsNotSubscribed)
+ type = Error::kInvalidApn;
else
type = Error::kOperationFailed;
diff --git a/cellular_service.cc b/cellular_service.cc
index 4c39a3e..74017bc 100644
--- a/cellular_service.cc
+++ b/cellular_service.cc
@@ -12,11 +12,16 @@
#include "shill/adaptor_interfaces.h"
#include "shill/cellular.h"
+#include "shill/property_accessor.h"
+#include "shill/store_interface.h"
using std::string;
namespace shill {
+const char CellularService::kStorageAPN[] = "Cellular.APN";
+const char CellularService::kStorageLastGoodAPN[] = "Cellular.LastGoodAPN";
+
// TODO(petkov): Add these to system_api/dbus/service_constants.h
namespace {
const char kKeyOLPURL[] = "url";
@@ -24,6 +29,17 @@
const char kKeyOLPPostData[] = "postdata";
} // namespace {}
+static bool GetNonEmptyField(const Stringmap &stringmap,
+ const string &fieldname,
+ string *value) {
+ Stringmap::const_iterator it = stringmap.find(fieldname);
+ if (it != stringmap.end() && !it->second.empty()) {
+ *value = it->second;
+ return true;
+ }
+ return false;
+}
+
CellularService::OLP::OLP() {
SetURL("");
SetMethod("");
@@ -79,7 +95,9 @@
PropertyStore *store = this->mutable_store();
store->RegisterConstString(flimflam::kActivationStateProperty,
&activation_state_);
- store->RegisterStringmap(flimflam::kCellularApnProperty, &apn_info_);
+ HelpRegisterDerivedStringmap(flimflam::kCellularApnProperty,
+ &CellularService::GetApn,
+ &CellularService::SetApn);
store->RegisterConstStringmap(flimflam::kCellularLastGoodApnProperty,
&last_good_apn_info_);
store->RegisterConstString(flimflam::kNetworkTechnologyProperty,
@@ -98,6 +116,144 @@
CellularService::~CellularService() { }
+void CellularService::HelpRegisterDerivedStringmap(
+ const string &name,
+ Stringmap(CellularService::*get)(Error *error),
+ void(CellularService::*set)(
+ const Stringmap &value, Error *error)) {
+ mutable_store()->RegisterDerivedStringmap(
+ name,
+ StringmapAccessor(
+ new CustomAccessor<CellularService, Stringmap>(this, get, set)));
+}
+
+Stringmap *CellularService::GetUserSpecifiedApn() {
+ Stringmap::iterator it = apn_info_.find(flimflam::kApnProperty);
+ if (it == apn_info_.end() || it->second.empty())
+ return NULL;
+ return &apn_info_;
+}
+
+Stringmap *CellularService::GetLastGoodApn() {
+ Stringmap::iterator it =
+ last_good_apn_info_.find(flimflam::kApnProperty);
+ if (it == last_good_apn_info_.end() || it->second.empty())
+ return NULL;
+ return &last_good_apn_info_;
+}
+
+Stringmap CellularService::GetApn(Error */*error*/) {
+ return apn_info_;
+}
+
+void CellularService::SetApn(const Stringmap &value, Error *error) {
+ // Only copy in the fields we care about, and validate the contents.
+ // The "apn" field is mandatory.
+ string str;
+ if (!GetNonEmptyField(value, flimflam::kApnProperty, &str)) {
+ error->Populate(Error::kInvalidArguments,
+ "supplied APN info is missing the apn");
+ return;
+ }
+ apn_info_[flimflam::kApnProperty] = str;
+ if (GetNonEmptyField(value, flimflam::kApnUsernameProperty, &str))
+ apn_info_[flimflam::kApnUsernameProperty] = str;
+ if (GetNonEmptyField(value, flimflam::kApnPasswordProperty, &str))
+ apn_info_[flimflam::kApnPasswordProperty] = str;
+
+ ClearLastGoodApn();
+ adaptor()->EmitStringmapChanged(flimflam::kCellularApnProperty, apn_info_);
+ SaveToCurrentProfile();
+}
+
+void CellularService::SetLastGoodApn(const Stringmap &apn_info) {
+ last_good_apn_info_ = apn_info;
+ adaptor()->EmitStringmapChanged(flimflam::kCellularLastGoodApnProperty,
+ last_good_apn_info_);
+ SaveToCurrentProfile();
+}
+
+void CellularService::ClearLastGoodApn() {
+ last_good_apn_info_.clear();
+ adaptor()->EmitStringmapChanged(flimflam::kCellularLastGoodApnProperty,
+ last_good_apn_info_);
+ SaveToCurrentProfile();
+}
+
+bool CellularService::Load(StoreInterface *storage) {
+ // Load properties common to all Services.
+ if (!Service::Load(storage))
+ return false;
+
+ const string id = GetStorageIdentifier();
+ LoadApn(storage, id, kStorageAPN, &apn_info_);
+ LoadApn(storage, id, kStorageLastGoodAPN, &last_good_apn_info_);
+ return true;
+}
+
+void CellularService::LoadApn(StoreInterface *storage,
+ const string &storage_group,
+ const string &keytag,
+ Stringmap *apn_info) {
+ if (!LoadApnField(storage, storage_group, keytag,
+ flimflam::kApnProperty, apn_info))
+ return;
+ LoadApnField(storage, storage_group, keytag,
+ flimflam::kApnUsernameProperty, apn_info);
+ LoadApnField(storage, storage_group, keytag,
+ flimflam::kApnPasswordProperty, apn_info);
+}
+
+bool CellularService::LoadApnField(StoreInterface *storage,
+ const string &storage_group,
+ const string &keytag,
+ const string &apntag,
+ Stringmap *apn_info) {
+ string value;
+ if (storage->GetString(storage_group, keytag + "." + apntag, &value) &&
+ !value.empty()) {
+ (*apn_info)[apntag] = value;
+ return true;
+ }
+ return false;
+}
+
+bool CellularService::Save(StoreInterface *storage) {
+ // Save properties common to all Services.
+ if (!Service::Save(storage))
+ return false;
+
+ const string id = GetStorageIdentifier();
+ SaveApn(storage, id, GetUserSpecifiedApn(), kStorageAPN);
+ SaveApn(storage, id, GetLastGoodApn(), kStorageLastGoodAPN);
+ return true;
+}
+
+void CellularService::SaveApn(StoreInterface *storage,
+ const string &storage_group,
+ const Stringmap *apn_info,
+ const string &keytag) {
+ SaveApnField(storage, storage_group, apn_info, keytag,
+ flimflam::kApnProperty);
+ SaveApnField(storage, storage_group, apn_info, keytag,
+ flimflam::kApnUsernameProperty);
+ SaveApnField(storage, storage_group, apn_info, keytag,
+ flimflam::kApnPasswordProperty);
+}
+
+void CellularService::SaveApnField(StoreInterface *storage,
+ const string &storage_group,
+ const Stringmap *apn_info,
+ const string &keytag,
+ const string &apntag) {
+ const string key = keytag + "." + apntag;
+ string str;
+ if (apn_info && GetNonEmptyField(*apn_info, apntag, &str))
+ storage->SetString(storage_group, key, str);
+ else
+ storage->DeleteKey(storage_group, key);
+}
+
void CellularService::Connect(Error *error) {
Service::Connect(error);
cellular_->Connect(error);
diff --git a/cellular_service.h b/cellular_service.h
index 8e2a3a1..a502f8a 100644
--- a/cellular_service.h
+++ b/cellular_service.h
@@ -24,6 +24,9 @@
class CellularService : public Service {
public:
+ static const char kStorageAPN[];
+ static const char kStorageLastGoodAPN[];
+
// Online payment portal.
class OLP {
public:
@@ -88,12 +91,50 @@
void SetRoamingState(const std::string &state);
const std::string &roaming_state() const { return roaming_state_; }
+ // Overrride Load and Save from parent Service class. We will call
+ // the parent method.
+ virtual bool Load(StoreInterface *storage);
+ virtual bool Save(StoreInterface *storage);
+
+ Stringmap *GetUserSpecifiedApn();
+ Stringmap *GetLastGoodApn();
+ void SetLastGoodApn(const Stringmap &apn_info);
+ void ClearLastGoodApn();
+
private:
friend class CellularServiceTest;
+ FRIEND_TEST(CellularCapabilityGSMTest, SetupApnTryList);
+ FRIEND_TEST(CellularCapabilityTest, TryApns);
FRIEND_TEST(CellularTest, Connect);
+ void HelpRegisterDerivedStringmap(
+ const std::string &name,
+ Stringmap(CellularService::*get)(Error *error),
+ void(CellularService::*set)(const Stringmap &value, Error *error));
+
virtual std::string GetDeviceRpcId(Error *error);
+ Stringmap GetApn(Error *error);
+ void SetApn(const Stringmap &value, Error *error);
+ static void SaveApn(StoreInterface *storage,
+ const std::string &storage_group,
+ const Stringmap *apn_info,
+ const std::string &keytag);
+ static void SaveApnField(StoreInterface *storage,
+ const std::string &storage_group,
+ const Stringmap *apn_info,
+ const std::string &keytag,
+ const std::string &apntag);
+ static void LoadApn(StoreInterface *storage,
+ const std::string &storage_group,
+ const std::string &keytag,
+ Stringmap *apn_info);
+ static bool LoadApnField(StoreInterface *storage,
+ const std::string &storage_group,
+ const std::string &keytag,
+ const std::string &apntag,
+ Stringmap *apn_info);
+
// Properties
std::string activation_state_;
Cellular::Operator serving_operator_;
@@ -102,8 +143,8 @@
OLP olp_;
std::string usage_url_;
- std::map<std::string, std::string> apn_info_;
- std::map<std::string, std::string> last_good_apn_info_;
+ Stringmap apn_info_;
+ Stringmap last_good_apn_info_;
std::string storage_identifier_;
diff --git a/dbus_adaptor.cc b/dbus_adaptor.cc
index 90352cf..3873c6f 100644
--- a/dbus_adaptor.cc
+++ b/dbus_adaptor.cc
@@ -142,6 +142,12 @@
(*out)[it.Key()]= StringmapToVariant(it.Value(&e));
}
{
+ ReadablePropertyConstIterator<Stringmaps> it =
+ store.GetStringmapsPropertiesIter();
+ for ( ; !it.AtEnd(); it.Advance())
+ (*out)[it.Key()]= StringmapsToVariant(it.Value(&e));
+ }
+ {
ReadablePropertyConstIterator<Strings> it =
store.GetStringsPropertiesIter();
for ( ; !it.AtEnd(); it.Advance())
diff --git a/error.cc b/error.cc
index 23aa4ff..476c42b 100644
--- a/error.cc
+++ b/error.cc
@@ -40,6 +40,7 @@
{ "IncorrectPin", "Incorrect PIN" },
{ "PinRequired", "SIM PIN is required"},
{ "PinBlocked", "SIM PIN is blocked"},
+ { "InvalidApn", "Invalid APN" },
{ "PermissionDenied", "Permission denied" }
};
diff --git a/error.h b/error.h
index 001be8a..ca1adee 100644
--- a/error.h
+++ b/error.h
@@ -42,6 +42,7 @@
kIncorrectPin,
kPinRequired,
kPinBlocked,
+ kInvalidApn,
kPermissionDenied,
kNumErrors
};
diff --git a/property_store.cc b/property_store.cc
index 8e8a35c..2959370 100644
--- a/property_store.cc
+++ b/property_store.cc
@@ -428,6 +428,13 @@
strings_properties_[name] = accessor;
}
+void PropertyStore::RegisterDerivedStringmap(const string &name,
+ const StringmapAccessor &acc) {
+ DCHECK(!Contains(name) || ContainsKey(stringmap_properties_, name))
+ << "(Already registered " << name << ")";
+ stringmap_properties_[name] = acc;
+}
+
void PropertyStore::RegisterDerivedStringmaps(const string &name,
const StringmapsAccessor &acc) {
DCHECK(!Contains(name) || ContainsKey(stringmaps_properties_, name))
diff --git a/property_store.h b/property_store.h
index f3c0b2e..54c8056 100644
--- a/property_store.h
+++ b/property_store.h
@@ -156,6 +156,8 @@
const RpcIdentifiersAccessor &accessor);
void RegisterDerivedString(const std::string &name,
const StringAccessor &accessor);
+ void RegisterDerivedStringmap(const std::string &name,
+ const StringmapAccessor &accessor);
void RegisterDerivedStringmaps(const std::string &name,
const StringmapsAccessor &accessor);
void RegisterDerivedStrings(const std::string &name,