shill: Disconnect and disable cellular modem when stopping the device.

Also, disconnect the cellular service on Service::Disconnect. A separate patch
wires this up to powering off the device (crosbug.com/25014).

BUG=chromium-os:25116,chromium-os:25013
TEST=unit tests, tested on chromebook with disconnect-service

Change-Id: Id3b8c10fbfd917702738b8a4385d7954c03de204
Reviewed-on: https://gerrit.chromium.org/gerrit/14230
Commit-Ready: Darin Petkov <petkov@chromium.org>
Reviewed-by: Darin Petkov <petkov@chromium.org>
Tested-by: Darin Petkov <petkov@chromium.org>
diff --git a/cellular.cc b/cellular.cc
index 1698292..9c8ff56 100644
--- a/cellular.cc
+++ b/cellular.cc
@@ -170,10 +170,11 @@
 
 void Cellular::Stop() {
   capability_->OnDeviceStopped();
+  DestroyService();
+  DisconnectModem();
+  DisableModem();
   proxy_.reset();
   simple_proxy_.reset();
-  DestroyService();
-  SetState(kStateDisabled);
   Device::Stop();
 }
 
@@ -210,6 +211,24 @@
   SetState(kStateEnabled);
 }
 
+void Cellular::DisableModem() {
+  VLOG(2) << __func__;
+  if (state_ == kStateDisabled) {
+    return;
+  }
+  // TODO(petkov): Switch to asynchronous calls (crosbug.com/17583). Note,
+  // however, that this is invoked by the Stop method, so some extra refactoring
+  // may be needed to prevent the device instance from being destroyed before
+  // the modem manager calls are complete. Maybe Disconnect and Disable need to
+  // be handled by the parent Modem class.
+  try {
+    proxy_->Enable(false);
+  } catch (const DBus::Error e) {
+    LOG(WARNING) << "Disable failed: " << e.what();
+  }
+  SetState(kStateDisabled);
+}
+
 void Cellular::GetModemStatus() {
   CHECK_EQ(kStateEnabled, state_);
   // TODO(petkov): Switch to asynchronous calls (crosbug.com/17583).
@@ -374,6 +393,43 @@
   EstablishLink();
 }
 
+void Cellular::Disconnect(Error *error) {
+  VLOG(2) << __func__;
+  if (state_ != kStateConnected &&
+      state_ != kStateLinked) {
+    Error::PopulateAndLog(
+        error, Error::kInProgress, "Not connected; request ignored.");
+    return;
+  }
+  // Defer because we may be in a dbus-c++ callback.
+  dispatcher()->PostTask(
+      task_factory_.NewRunnableMethod(&Cellular::DisconnectTask));
+}
+
+void Cellular::DisconnectTask() {
+  VLOG(2) << __func__;
+  DisconnectModem();
+}
+
+void Cellular::DisconnectModem() {
+  VLOG(2) << __func__;
+  if (state_ != kStateConnected &&
+      state_ != kStateLinked) {
+    return;
+  }
+  // TODO(petkov): Switch to asynchronous calls (crosbug.com/17583). Note,
+  // however, that this is invoked by the Stop method, so some extra refactoring
+  // may be needed to prevent the device instance from being destroyed before
+  // the modem manager calls are complete. Maybe Disconnect and Disable need to
+  // be handled by the parent Modem class.
+  try {
+    proxy_->Disconnect();
+  } catch (const DBus::Error e) {
+    LOG(WARNING) << "Disconnect failed: " << e.what();
+  }
+  SetState(kStateRegistered);
+}
+
 void Cellular::EstablishLink() {
   VLOG(2) << __func__;
   CHECK_EQ(kStateConnected, state_);