shill: Add code to track disconnect metric.

BUG=chromium-os:25958
TEST=Unit tests, network_WiFiManager

Change-Id: I4cee4d4380dc822c9bed61e006df304c6c6fbdde
Reviewed-on: https://gerrit.chromium.org/gerrit/15850
Commit-Ready: Thieu Le <thieule@chromium.org>
Reviewed-by: Thieu Le <thieule@chromium.org>
Tested-by: Thieu Le <thieule@chromium.org>
diff --git a/metrics.cc b/metrics.cc
index 629a10c..7eb631b 100644
--- a/metrics.cc
+++ b/metrics.cc
@@ -20,6 +20,11 @@
 static base::LazyInstance<Metrics> g_metrics(base::LINKER_INITIALIZED);
 
 // static
+const char Metrics::kMetricDisconnect[] = "Network.Shill.%s.Disconnect";
+const int Metrics::kMetricDisconnectMax = 1;
+const int Metrics::kMetricDisconnectMin = 0;
+const int Metrics::kMetricDisconnectNumBuckets = 2;
+
 const char Metrics::kMetricNetworkChannel[] = "Network.Shill.%s.Channel";
 const int Metrics::kMetricNetworkChannelMax = Metrics::kWiFiChannelMax;
 const char Metrics::kMetricNetworkPhyMode[] = "Network.Shill.%s.PhyMode";
@@ -252,10 +257,14 @@
   return base::StringPrintf(metric_name, technology.c_str());
 }
 
-void Metrics::NotifyServiceDisconnect(const Service */*service*/,
-                                      bool /*manual_disconnect*/) {
-  // TODO(thieule): Handle service disconnects.
-  // crosbug.com/23253
+void Metrics::NotifyServiceDisconnect(const Service *service) {
+  Technology::Identifier technology = service->technology();
+  string histogram = GetFullMetricName(kMetricDisconnect, technology);
+  SendToUMA(histogram,
+            service->explicitly_disconnected(),
+            kMetricDisconnectMin,
+            kMetricDisconnectMax,
+            kMetricDisconnectNumBuckets);
 }
 
 void Metrics::NotifyPower() {
diff --git a/metrics.h b/metrics.h
index 11cf0c4..2aac1f1 100644
--- a/metrics.h
+++ b/metrics.h
@@ -100,6 +100,10 @@
     kWiFiSecurityMax
   };
 
+  static const char kMetricDisconnect[];
+  static const int kMetricDisconnectMax;
+  static const int kMetricDisconnectMin;
+  static const int kMetricDisconnectNumBuckets;
   static const char kMetricNetworkChannel[];
   static const int kMetricNetworkChannelMax;
   static const char kMetricNetworkPhyMode[];
@@ -159,10 +163,8 @@
   virtual void NotifyServiceStateChanged(const Service *service,
                                          Service::ConnectState new_state);
 
-  // Notifies this object that |service| has been disconnected and whether
-  // the disconnect was requested by the user or not.
-  void NotifyServiceDisconnect(const Service *service,
-                               bool manual_disconnect);
+  // Notifies this object that |service| has been disconnected.
+  void NotifyServiceDisconnect(const Service *service);
 
   // Notifies this object of a power management event.
   void NotifyPower();
diff --git a/metrics_unittest.cc b/metrics_unittest.cc
index 5f1d642..dd62f86 100644
--- a/metrics_unittest.cc
+++ b/metrics_unittest.cc
@@ -199,4 +199,26 @@
   metrics_.NotifyDefaultServiceChanged(NULL);
 }
 
+TEST_F(MetricsTest, Disconnect) {
+  EXPECT_CALL(*service_.get(), technology()).
+      WillRepeatedly(Return(Technology::kWifi));
+  EXPECT_CALL(*service_.get(), explicitly_disconnected()).
+      WillOnce(Return(false));
+  EXPECT_CALL(library_, SendToUMA("Network.Shill.Wifi.Disconnect",
+                                  false,
+                                  Metrics::kMetricDisconnectMin,
+                                  Metrics::kMetricDisconnectMax,
+                                  Metrics::kMetricDisconnectNumBuckets));
+  metrics_.NotifyServiceDisconnect(service_);
+
+  EXPECT_CALL(*service_.get(), explicitly_disconnected()).
+      WillOnce(Return(true));
+  EXPECT_CALL(library_, SendToUMA("Network.Shill.Wifi.Disconnect",
+                                  true,
+                                  Metrics::kMetricDisconnectMin,
+                                  Metrics::kMetricDisconnectMax,
+                                  Metrics::kMetricDisconnectNumBuckets));
+  metrics_.NotifyServiceDisconnect(service_);
+}
+
 }  // namespace shill
diff --git a/mock_service.h b/mock_service.h
index a56ab43..e1942f3 100644
--- a/mock_service.h
+++ b/mock_service.h
@@ -46,6 +46,7 @@
   MOCK_METHOD0(Unload, void());
   MOCK_METHOD1(Save, bool(StoreInterface *store_interface));
   MOCK_METHOD1(SetConnection, void(ConnectionRefPtr connection));
+  MOCK_CONST_METHOD0(explicitly_disconnected, bool());
   MOCK_CONST_METHOD0(technology, Technology::Identifier());
   // Set a string for this Service via |store|.  Can be wired to Save() for
   // test purposes.
diff --git a/service.h b/service.h
index b1d4cbf..5e4f0cf 100644
--- a/service.h
+++ b/service.h
@@ -220,6 +220,10 @@
   bool connectable() const { return connectable_; }
   void set_connectable(bool connectable);
 
+  virtual bool explicitly_disconnected() const {
+    return explicitly_disconnected_;
+  }
+
   bool favorite() const { return favorite_; }
   // Setter is deliberately omitted; use MakeFavorite.
 
diff --git a/wifi.cc b/wifi.cc
index a1dc3ec..f7694a5 100644
--- a/wifi.cc
+++ b/wifi.cc
@@ -31,6 +31,7 @@
 #include "shill/key_value_store.h"
 #include "shill/ieee80211.h"
 #include "shill/manager.h"
+#include "shill/metrics.h"
 #include "shill/profile.h"
 #include "shill/property_accessor.h"
 #include "shill/proxy_factory.h"
@@ -509,6 +510,7 @@
   // TODO(quiche): If we initated the disconnect, we should probably
   // go to the idle state instead. crosbug.com/24700
   affected_service->SetFailure(Service::kFailureUnknown);
+  metrics()->NotifyServiceDisconnect(affected_service);
 
   if (affected_service == pending_service_.get()) {
     // The attempt to connect to |pending_service_| failed. Clear