cellular: add a property to enable/disable passing the attach APN

The use_attach_apn_ cellular property exported as
'Cellular.UseAttachAPN' decides whether shill should explicitly set in
the modem the desired 'attach APN' before registering to the network by
using the data from the mobile operator database.

When this property is false, the host side sends no configuration and
the modem will use its own internal database or get the value from the
network.

It is currently disabled by default.
This property can set/read over D-Bus, this will be used to implement
the Chrome flags enabling it.

BUG=b:160812573
BUG=b:170367556
TEST=unit-tests
TEST=On Jinlon, run 'set-device-property /device/wwan0 Cellular.UseAttachAPN true'

Change-Id: Ib8c8a1c3c384915c1dc00c80df2ea94b11eff973
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform2/+/2586427
Tested-by: Vincent Palatin <vpalatin@chromium.org>
Commit-Queue: Vincent Palatin <vpalatin@chromium.org>
Reviewed-by: Eric Caruso <ejcaruso@chromium.org>
diff --git a/shill/cellular/cellular.cc b/shill/cellular/cellular.cc
index 8ed153f..56b8480 100644
--- a/shill/cellular/cellular.cc
+++ b/shill/cellular/cellular.cc
@@ -128,6 +128,7 @@
 
 // static
 const char Cellular::kAllowRoaming[] = "AllowRoaming";
+const char Cellular::kUseAttachApn[] = "UseAttachAPN";
 const int64_t Cellular::kDefaultScanningTimeoutMilliseconds = 60000;
 const int64_t Cellular::kPollLocationIntervalMilliseconds = 300000;  // 5 mins
 const char Cellular::kGenericServiceNamePrefix[] = "MobileNetwork";
@@ -164,6 +165,7 @@
       ppp_device_factory_(PPPDeviceFactory::GetInstance()),
       process_manager_(ProcessManager::GetInstance()),
       allow_roaming_(false),
+      use_attach_apn_(false),
       inhibited_(false),
       proposed_scan_in_progress_(false),
       explicit_disconnect_(false),
@@ -230,12 +232,14 @@
     return false;
   }
   storage->GetBool(id, kAllowRoaming, &allow_roaming_);
+  storage->GetBool(id, kUseAttachApn, &use_attach_apn_);
   return Device::Load(storage);
 }
 
 bool Cellular::Save(StoreInterface* storage) {
   const string id = GetStorageIdentifier();
   storage->SetBool(id, kAllowRoaming, allow_roaming_);
+  storage->SetBool(id, kUseAttachApn, use_attach_apn_);
   return Device::Save(storage);
 }
 
@@ -1188,6 +1192,25 @@
   return true;
 }
 
+bool Cellular::SetUseAttachApn(const bool& value, Error* error) {
+  SLOG(this, 2) << __func__ << "(" << use_attach_apn_ << "->" << value << ")";
+  if (use_attach_apn_ == value) {
+    return false;
+  }
+
+  use_attach_apn_ = value;
+
+  if (capability_) {
+    // Re-creating the service will set again the attach APN
+    // and eventually re-attach if needed.
+    DestroyService();
+    CreateService();
+  }
+
+  adaptor()->EmitBoolChanged(kUseAttachAPNProperty, value);
+  return true;
+}
+
 bool Cellular::GetInhibited(Error* error) {
   return inhibited_;
 }
@@ -1461,6 +1484,8 @@
   HelpRegisterDerivedBool(kCellularAllowRoamingProperty,
                           &Cellular::GetAllowRoaming,
                           &Cellular::SetAllowRoaming);
+  HelpRegisterDerivedBool(kUseAttachAPNProperty, &Cellular::GetUseAttachApn,
+                          &Cellular::SetUseAttachApn);
   HelpRegisterDerivedBool(kInhibited, &Cellular::GetInhibited,
                           &Cellular::SetInhibited);
 
diff --git a/shill/cellular/cellular.h b/shill/cellular/cellular.h
index 428c9d0..cb17ecd 100644
--- a/shill/cellular/cellular.h
+++ b/shill/cellular/cellular.h
@@ -301,6 +301,7 @@
   bool sim_present() const { return sim_present_; }
   const Stringmaps& apn_list() const { return apn_list_; }
   const std::string& iccid() const { return iccid_; }
+  bool use_attach_apn() const { return use_attach_apn_; }
 
   Type type() const { return type_; }
   bool inhibited() const { return inhibited_; }
@@ -339,6 +340,8 @@
   void set_apn_list(const Stringmaps& apn_list);
   void set_iccid(const std::string& iccid);
 
+  void set_use_attach_apn_for_testing(bool on) { use_attach_apn_ = on; }
+
   // Takes ownership.
   void set_home_provider_info(MobileOperatorInfo* home_provider_info);
   // Takes ownership.
@@ -359,6 +362,7 @@
   friend class ModemTest;
   FRIEND_TEST(CellularCapability3gppMainTest, Connect);
   FRIEND_TEST(CellularCapability3gppMainTest, IsServiceActivationRequired);
+  FRIEND_TEST(CellularCapability3gppMainTest, SetInitialEpsBearer);
   FRIEND_TEST(CellularCapability3gppMainTest, UpdatePendingActivationState);
   FRIEND_TEST(CellularCapability3gppMainTest, UpdateRegistrationState);
   FRIEND_TEST(CellularCapability3gppMainTest,
@@ -404,6 +408,7 @@
   FRIEND_TEST(CellularTest, ScanSuccess);
   FRIEND_TEST(CellularTest, SetAllowRoaming);
   FRIEND_TEST(CellularTest, SetInhibited);
+  FRIEND_TEST(CellularTest, SetUseAttachApn);
   FRIEND_TEST(CellularTest, StopPPPOnDisconnect);
   FRIEND_TEST(CellularTest, StorageIdentifier);
   FRIEND_TEST(CellularTest, StartConnected);
@@ -417,6 +422,7 @@
 
   // Names of properties in storage
   static const char kAllowRoaming[];
+  static const char kUseAttachApn[];
 
   // the |kScanningProperty| exposed by Cellular device is sticky false. Every
   // time it is set to true, it must be reset to false after a time equal to
@@ -477,6 +483,10 @@
   void OnInhibitDevice(bool inhibited, const Error& error);
   KeyValueStore GetSimLockStatus(Error* error);
 
+  // DBUS accessors to read/modify the use of an Attach APN
+  bool GetUseAttachApn(Error* /*error*/) { return use_attach_apn_; }
+  bool SetUseAttachApn(const bool& value, Error* error);
+
   // When shill terminates or ChromeOS suspends, this function is called to
   // disconnect from the cellular network.
   void StartTermination();
@@ -593,6 +603,9 @@
   // User preference to allow or disallow roaming
   bool allow_roaming_;
 
+  // Chrome flags to enable setting the attach APN from the host
+  bool use_attach_apn_;
+
   // Reflects the Device property indicating that the modem is inhibted. The
   // property is not persisted and is reset to false when the modem starts.
   bool inhibited_;
diff --git a/shill/cellular/cellular_capability_3gpp.cc b/shill/cellular/cellular_capability_3gpp.cc
index 769bb70..bb2b1f0 100644
--- a/shill/cellular/cellular_capability_3gpp.cc
+++ b/shill/cellular/cellular_capability_3gpp.cc
@@ -1549,6 +1549,10 @@
   // The cellular object may need to update the APN list now.
   cellular()->OnOperatorChanged();
 
+  // Bail-out early if we don't want to setup the attach APN
+  if (!cellular()->use_attach_apn())
+    return;
+
   // Set the new parameters for the initial EPS bearer (e.g. LTE Attach APN)
   KeyValueStore properties;
   Error error;
diff --git a/shill/cellular/cellular_capability_3gpp_test.cc b/shill/cellular/cellular_capability_3gpp_test.cc
index 017dc9d..c2b9178 100644
--- a/shill/cellular/cellular_capability_3gpp_test.cc
+++ b/shill/cellular/cellular_capability_3gpp_test.cc
@@ -1258,6 +1258,8 @@
       .WillOnce(SaveArg<2>(&set_callback));
   EXPECT_CALL(*this, TestCallback(IsSuccess()));
   properties.Set<string>(CellularCapability3gpp::kConnectApn, kTestApn);
+
+  cellular_->set_use_attach_apn_for_testing(true);
   capability_->InitProxies();
   capability_->SetInitialEpsBearer(properties, &error, callback);
   set_callback.Run(Error(Error::kSuccess));
diff --git a/shill/cellular/cellular_test.cc b/shill/cellular/cellular_test.cc
index 5d05554..5c4c2cf 100644
--- a/shill/cellular/cellular_test.cc
+++ b/shill/cellular/cellular_test.cc
@@ -70,6 +70,7 @@
 using std::vector;
 using testing::_;
 using testing::AnyNumber;
+using testing::AtLeast;
 using testing::DoAll;
 using testing::Invoke;
 using testing::Mock;
@@ -1333,6 +1334,19 @@
   EXPECT_TRUE(device_->allow_roaming_);
 }
 
+TEST_P(CellularTest, SetUseAttachApn) {
+  EXPECT_FALSE(device_->use_attach_apn_);
+  InitProxies();
+  // It's going to process again the mobile network information for the APN
+  SetMockMobileOperatorInfoObjects();
+  EXPECT_CALL(*mock_home_provider_info_, IsMobileNetworkOperatorKnown())
+      .Times(AtLeast(1));
+  Error error;
+  device_->SetUseAttachApn(true, &error);
+  EXPECT_TRUE(error.IsSuccess());
+  EXPECT_TRUE(device_->use_attach_apn_);
+}
+
 TEST_P(CellularTest, SetInhibited) {
   PopulateProxies();
 
diff --git a/shill/device.cc b/shill/device.cc
index 6ef1999..7aaf55a 100644
--- a/shill/device.cc
+++ b/shill/device.cc
@@ -188,6 +188,7 @@
   // kSIMLockStatusProperty: Registered in Cellular
   // kFoundNetworksProperty: Registered in Cellular
   // kDBusObjectProperty: Register in Cellular
+  // kUseAttachAPNProperty: Registered in Cellular
 
   store_.RegisterConstString(kInterfaceProperty, &link_name_);
   HelpRegisterDerivedBool(kIPv6DisabledProperty, &Device::GetIPv6Disabled,
diff --git a/shill/doc/device-api.txt b/shill/doc/device-api.txt
index c3aabed..f2c4276 100644
--- a/shill/doc/device-api.txt
+++ b/shill/doc/device-api.txt
@@ -393,6 +393,21 @@
 			(Cellular only) For GSM or LTE modems, indicates
 			whether a SIM card is present or not.
 
+		boolean Cellular.UseAttachAPN [readwrite]
+
+			(Cellular only) Whether shill should explicitly
+			set in the modem the desired 'attach APN' before
+			registering to the network by using the data from
+			the mobile operator database. When this property is
+			false, the modem will use its own internal database
+			or get the value from the network.
+
+			If the property is set true, and a data connection was
+			already established, the connection will be first
+			terminated.
+
+			By default Cellular.UseAttachAPN is false.
+
 		array{dict} Cellular.FoundNetworks [readonly] [GSM only]
 
 			(Cellular only) The result of the most recent
diff --git a/shill/test-scripts/set-device-property b/shill/test-scripts/set-device-property
index 91fb0f7..9501d02 100644
--- a/shill/test-scripts/set-device-property
+++ b/shill/test-scripts/set-device-property
@@ -22,7 +22,8 @@
     device.SetProperty(property_key, dbus.UInt16(value))
 elif property_key in ['BgscanSignalThreshold', ]:
     device.SetProperty(property_key, dbus.Int32(value))
-elif property_key in ['Cellular.AllowRoaming', 'Powered']:
+elif property_key in ['Cellular.AllowRoaming', 'Cellular.UseAttachAPN',
+                      'Powered']:
     device.SetProperty(property_key,
                        dbus.Boolean(value.lower() in ('true', '1')))
 else:
diff --git a/system_api/dbus/shill/dbus-constants.h b/system_api/dbus/shill/dbus-constants.h
index db8e178..d417574 100644
--- a/system_api/dbus/shill/dbus-constants.h
+++ b/system_api/dbus/shill/dbus-constants.h
@@ -361,6 +361,7 @@
 const char kSelectedNetworkProperty[] = "Cellular.SelectedNetwork";
 const char kSIMPresentProperty[] = "Cellular.SIMPresent";
 const char kSupportNetworkScanProperty[] = "Cellular.SupportNetworkScan";
+const char kUseAttachAPNProperty[] = "Cellular.UseAttachAPN";
 const char kDBusObjectProperty[] = "DBus.Object";
 const char kDBusServiceProperty[] = "DBus.Service";