shill: Installs progressive scan in shill.

BUG=chromium:222088
TEST=unittest and manual.  The manual tests consist of:
  - Enable progressive scan.  On target, in a crosh window, do:
      o progressive_scan on
  - Start shill with log=-10/wifi.  On the target, in a shell window,
    do the following:
      o stop shill
      o shill --log-level=-10 --log-scopes=wifi
  - Wait five seconds (the code will do everything it needs to at
    startup).
  - Look in /var/log/net.log and verify the existence of the following:
      o Doing progressive scan

      o ProgressiveScanTask - scan requested for XXXX
        Initiating a scan -- returning

      o ProgressiveScanTask - scan requested for XXXX
        Ignoring scan request wile connecting to an AP

Change-Id: Ie577869aedc03230e1fab62fc8cb9f0b3850c7fb
Reviewed-on: https://gerrit.chromium.org/gerrit/50377
Reviewed-by: mukesh agrawal <quiche@chromium.org>
Commit-Queue: Wade Guthrie <wdg@chromium.org>
Reviewed-by: Wade Guthrie <wdg@chromium.org>
Tested-by: Wade Guthrie <wdg@chromium.org>
diff --git a/Makefile b/Makefile
index 58507e9..5fc8edd 100644
--- a/Makefile
+++ b/Makefile
@@ -422,6 +422,7 @@
 	mock_metrics.o \
 	mock_minijail.o \
 	mock_modem_info.o \
+	mock_netlink_manager.o \
 	mock_nss.o \
 	mock_pending_activation_store.o \
 	mock_portal_detector.o \
@@ -434,6 +435,7 @@
 	mock_resolver.o \
 	mock_routing_table.o \
 	mock_rtnl_handler.o \
+	mock_scan_session.o \
 	mock_service.o \
 	mock_socket_info_reader.o \
 	mock_sockets.o \
diff --git a/attribute_list.cc b/attribute_list.cc
index 849259d..a0843ea 100644
--- a/attribute_list.cc
+++ b/attribute_list.cc
@@ -29,7 +29,10 @@
 
 bool AttributeList::CreateAttribute(
     int id, AttributeList::NewFromIdMethod factory) {
-  LOG_IF(INFO, ContainsKey(attributes_, id)) << "Re-adding attribute: " << id;
+  if (ContainsKey(attributes_, id)) {
+    LOG(WARNING) << "Trying to re-add attribute " << id << ", not overwriting";
+    return true;
+  }
   attributes_[id] = AttributePointer(factory.Run(id));
   return true;
 }
diff --git a/metrics.h b/metrics.h
index c4971f2..0e2656d 100644
--- a/metrics.h
+++ b/metrics.h
@@ -500,7 +500,7 @@
   void NotifyDeviceScanStarted(int interface_index);
 
   // Notifies this object that a device has completed the scanning process.
-  void NotifyDeviceScanFinished(int interface_index);
+  virtual void NotifyDeviceScanFinished(int interface_index);
 
   // Notifies this object that a device has started the connect process.
   void NotifyDeviceConnectStarted(int interface_index,
diff --git a/mock_metrics.h b/mock_metrics.h
index 00f78c1..40ac679 100644
--- a/mock_metrics.h
+++ b/mock_metrics.h
@@ -5,19 +5,20 @@
 #ifndef SHILL_MOCK_METRICS_
 #define SHILL_MOCK_METRICS_
 
-#include <gmock/gmock.h>
-
 #include "shill/metrics.h"
 
+#include <gmock/gmock.h>
+
 namespace shill {
 
 class MockMetrics : public Metrics {
  public:
-  MockMetrics(EventDispatcher *dispatcher);
+  explicit MockMetrics(EventDispatcher *dispatcher);
   virtual ~MockMetrics();
 
   MOCK_METHOD0(Start, void());
   MOCK_METHOD0(Stop, void());
+  MOCK_METHOD1(NotifyDeviceScanFinished, void(int interface_index));
   MOCK_METHOD1(NotifyDefaultServiceChanged, void(const Service *service));
   MOCK_METHOD2(NotifyServiceStateChanged,
                void(const Service *service, Service::ConnectState new_state));
@@ -31,6 +32,6 @@
   DISALLOW_COPY_AND_ASSIGN(MockMetrics);
 };
 
-}
+}  // namespace shill
 
 #endif  // SHILL_MOCK_METRICS_
diff --git a/mock_netlink_manager.cc b/mock_netlink_manager.cc
new file mode 100644
index 0000000..55234be
--- /dev/null
+++ b/mock_netlink_manager.cc
@@ -0,0 +1,20 @@
+// Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "shill/mock_netlink_manager.h"
+
+#include <string>
+
+#include <base/stl_util.h>
+
+#include "shill/netlink_message.h"
+
+using std::string;
+
+namespace shill {
+
+MockNetlinkManager::MockNetlinkManager() {}
+MockNetlinkManager::~MockNetlinkManager() {}
+
+}  // namespace shill
diff --git a/mock_netlink_manager.h b/mock_netlink_manager.h
index 1e2cf7a..6717e68 100644
--- a/mock_netlink_manager.h
+++ b/mock_netlink_manager.h
@@ -5,19 +5,39 @@
 #ifndef SHILL_MOCK_NETLINK_MANAGER_H_
 #define SHILL_MOCK_NETLINK_MANAGER_H_
 
-#include <gmock/gmock.h>
-
 #include "shill/netlink_manager.h"
 
+#include <map>
+
+#include <gmock/gmock.h>
+
 namespace shill {
 
+class NetlinkMessage;
+
 class MockNetlinkManager : public NetlinkManager {
  public:
-  MockNetlinkManager() {}
-  ~MockNetlinkManager() {}
+  MockNetlinkManager();
+  virtual ~MockNetlinkManager();
+  MOCK_METHOD1(RemoveBroadcastHandler,
+               bool(const NetlinkMessageHandler &message_handler));
+  MOCK_METHOD1(AddBroadcastHandler,
+               bool(const NetlinkMessageHandler &messge_handler));
+  MOCK_METHOD2(SendMessage,
+               bool(NetlinkMessage *message,
+                    const NetlinkMessageHandler &message_handler));
+  MOCK_METHOD2(SubscribeToEvents,
+               bool(const std::string &family, const std::string &group));
+  MOCK_CONST_METHOD1(GetMessageType, uint16_t(const std::string &name));
 
-  MOCK_METHOD2(SendMessage, bool(NetlinkMessage *message,
-                                 const NetlinkMessageHandler &message_handler));
+  void SetMessageType(std::string name, uint16_t type);
+
+ private:
+  uint16_t DoGetMessageType(std::string name);
+
+  std::map<std::string, uint16_t> message_types_;
+
+  DISALLOW_COPY_AND_ASSIGN(MockNetlinkManager);
 };
 
 }  // namespace shill
diff --git a/mock_scan_session.cc b/mock_scan_session.cc
new file mode 100644
index 0000000..2791e5d
--- /dev/null
+++ b/mock_scan_session.cc
@@ -0,0 +1,39 @@
+// Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "shill/mock_scan_session.h"
+
+#include <gmock/gmock.h>
+
+#include "shill/wifi_provider.h"
+
+namespace shill {
+
+MockScanSession::MockScanSession(NetlinkManager *netlink_manager,
+                                 EventDispatcher *dispatcher,
+                                 const WiFiProvider::FrequencyCountList
+                                     &previous_frequencies,
+                                 const std::set<uint16_t>
+                                     &available_frequencies,
+                                 uint32_t ifindex,
+                                 const FractionList &fractions,
+                                 int min_frequencies,
+                                 int max_frequencies,
+                                 OnScanFailed on_scan_failed)
+    : ScanSession(netlink_manager,
+                  dispatcher,
+                  previous_frequencies,
+                  available_frequencies,
+                  ifindex,
+                  fractions,
+                  min_frequencies,
+                  max_frequencies,
+                  on_scan_failed) {
+  ON_CALL(*this, HasMoreFrequencies()).WillByDefault(testing::Return(true));
+}
+
+MockScanSession::~MockScanSession() {}
+
+
+}  // namespace shill
diff --git a/mock_scan_session.h b/mock_scan_session.h
new file mode 100644
index 0000000..71ca9f9
--- /dev/null
+++ b/mock_scan_session.h
@@ -0,0 +1,45 @@
+// Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SHILL_MOCK_SCAN_SESSION_H_
+#define SHILL_MOCK_SCAN_SESSION_H_
+
+#include "shill/scan_session.h"
+
+#include <set>
+
+#include <gmock/gmock.h>
+
+#include "shill/wifi_provider.h"
+
+namespace shill {
+
+class ByteString;
+class EventDispatcher;
+class NetlinkManager;
+
+class MockScanSession : public ScanSession {
+ public:
+  MockScanSession(NetlinkManager *netlink_manager,
+                  EventDispatcher *dispatcher,
+                  const WiFiProvider::FrequencyCountList &previous_frequencies,
+                  const std::set<uint16_t> &available_frequencies,
+                  uint32_t ifindex,
+                  const FractionList &fractions,
+                  int min_frequencies,
+                  int max_frequencies,
+                  OnScanFailed on_scan_failed);
+  virtual ~MockScanSession();
+
+  MOCK_CONST_METHOD0(HasMoreFrequencies, bool());
+  MOCK_METHOD1(AddSsid, void(const ByteString &ssid));
+  MOCK_METHOD0(InitiateScan, void());
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(MockScanSession);
+};
+
+}  // namespace shill
+
+#endif  // SHILL_MOCK_SCAN_SESSION_H_
diff --git a/netlink_attribute.cc b/netlink_attribute.cc
index 0ab8db4..5eb84df 100644
--- a/netlink_attribute.cc
+++ b/netlink_attribute.cc
@@ -6,6 +6,7 @@
 
 #include <netlink/attr.h>
 
+#include <map>
 #include <string>
 
 #include <base/format_macros.h>
diff --git a/netlink_manager.cc b/netlink_manager.cc
index cef93e3..6abd857 100644
--- a/netlink_manager.cc
+++ b/netlink_manager.cc
@@ -256,7 +256,7 @@
   return NetlinkMessage::kIllegalMessageType;
 }
 
-uint16_t NetlinkManager::GetMessageType(string name) const {
+uint16_t NetlinkManager::GetMessageType(const string &name) const {
   map<const string, MessageType>::const_iterator family =
       message_types_.find(name);
   if (family == message_types_.end()) {
diff --git a/netlink_manager.h b/netlink_manager.h
index a65ad6b..c0ef9c6 100644
--- a/netlink_manager.h
+++ b/netlink_manager.h
@@ -143,17 +143,19 @@
 
   // Retrieves a family id (message type) given the |name| string describing
   // the message family.
-  uint16_t GetMessageType(std::string name) const;
+  virtual uint16_t GetMessageType(const std::string &name) const;
 
   // Install a NetlinkManager NetlinkMessageHandler.  The handler is a
   // user-supplied object to be called by the system for user-bound messages
   // that do not have a corresponding messaage-specific callback.
   // |AddBroadcastHandler| should be called before |SubscribeToEvents| since
   // the result of this call are used for that call.
-  bool AddBroadcastHandler(const NetlinkMessageHandler &messge_handler);
+  virtual bool AddBroadcastHandler(
+      const NetlinkMessageHandler &message_handler);
 
   // Uninstall a NetlinkMessage Handler.
-  bool RemoveBroadcastHandler(const NetlinkMessageHandler &message_handler);
+  virtual bool RemoveBroadcastHandler(
+      const NetlinkMessageHandler &message_handler);
 
   // Determines whether a handler is in the list of broadcast handlers.
   bool FindBroadcastHandler(const NetlinkMessageHandler &message_handler) const;
@@ -173,7 +175,8 @@
 
   // Sign-up to receive and log multicast events of a specific type (once wifi
   // is up).
-  bool SubscribeToEvents(const std::string &family, const std::string &group);
+  virtual bool SubscribeToEvents(const std::string &family,
+                                 const std::string &group);
 
   // Gets the next sequence number for a NetlinkMessage to be sent over
   // NetlinkManager's netlink socket.
diff --git a/netlink_message.h b/netlink_message.h
index 5b059e1..3815bb7 100644
--- a/netlink_message.h
+++ b/netlink_message.h
@@ -123,6 +123,8 @@
   static const uint16_t kMessageType;
 
   ErrorAckMessage() : NetlinkMessage(kMessageType), error_(0) {}
+  explicit ErrorAckMessage(uint32_t err)
+      : NetlinkMessage(kMessageType), error_(err) {}
   virtual bool InitFromNlmsg(const nlmsghdr *const_msg);
   virtual ByteString Encode(uint32_t sequence_number);
   virtual void Print(int header_log_level, int detail_log_level) const;
diff --git a/netlink_message_matchers.h b/netlink_message_matchers.h
new file mode 100644
index 0000000..50d403d
--- /dev/null
+++ b/netlink_message_matchers.h
@@ -0,0 +1,116 @@
+// Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SHILL_NETLINK_MESSAGE_MATCHERS_H_
+#define SHILL_NETLINK_MESSAGE_MATCHERS_H_
+
+#include <gmock/gmock.h>
+
+#include "shill/logging.h"
+#include "shill/netlink_message.h"
+#include "shill/nl80211_message.h"
+
+namespace shill {
+
+// Given a netlink message, verifies that it is an Nl80211Message and verifies,
+// further that it is the specified command.
+MATCHER_P2(IsNl80211Command, nl80211_message_type, command, "") {
+  if (!arg) {
+    LOG(INFO) << "Null message";
+    return false;
+  }
+  if (arg->message_type() != nl80211_message_type) {
+    LOG(INFO) << "Not an nl80211 message";
+    return false;
+  }
+  const Nl80211Message *msg = dynamic_cast<const Nl80211Message *>(arg);
+  if (msg->command() != command) {
+    LOG(INFO) << "Not a message of type " << command
+               << " (it's a " << +msg->command() << ")";
+    return false;
+  }
+  return true;
+}
+
+// Verifies that a NetlinkMessage is an NL80211_CMD_TRIGGER_SCAN message that
+// contains exactly one SSID along with the requisite empty one.
+MATCHER_P(HasHiddenSSID, nl80211_message_type, "") {
+  if (!arg) {
+    LOG(INFO) << "Null message";
+    return false;
+  }
+  if (arg->message_type() != nl80211_message_type) {
+    LOG(INFO) << "Not an nl80211 message";
+    return false;
+  }
+  const Nl80211Message *msg = reinterpret_cast<const Nl80211Message *>(arg);
+  if (msg->command() != NL80211_CMD_TRIGGER_SCAN) {
+    LOG(INFO) << "Not a NL80211_CMD_TRIGGER_SCAN message";
+    return false;
+  }
+  AttributeListConstRefPtr ssids;
+  if (!msg->const_attributes()->ConstGetNestedAttributeList(
+      NL80211_ATTR_SCAN_SSIDS, &ssids)) {
+    LOG(INFO) << "No SSID list in message";
+    return false;
+  }
+  ByteString ssid;
+  AttributeIdIterator ssid_iter(*ssids);
+  if (!ssids->GetRawAttributeValue(ssid_iter.GetId(), &ssid)) {
+    LOG(INFO) << "SSID list contains no (hidden) SSIDs";
+    return false;
+  }
+
+  // A valid Scan containing a single hidden SSID should contain
+  // two SSID entries: one containing the SSID we are looking for,
+  // and an empty entry, signifying that we also want to do a
+  // broadcast probe request for all non-hidden APs as well.
+  ByteString empty_ssid;
+  if (ssid_iter.AtEnd()) {
+    LOG(INFO) << "SSID list doesn't contain an empty SSIDs (but should)";
+    return false;
+  }
+  ssid_iter.Advance();
+  if (!ssids->GetRawAttributeValue(ssid_iter.GetId(), &empty_ssid) ||
+      !empty_ssid.IsEmpty()) {
+    LOG(INFO) << "SSID list doesn't contain an empty SSID (but should)";
+    return false;
+  }
+
+  return true;
+}
+
+// Verifies that a NetlinkMessage is an NL80211_CMD_TRIGGER_SCAN message that
+// contains no SSIDs.
+MATCHER_P(HasNoHiddenSSID, nl80211_message_type, "") {
+  if (!arg) {
+    LOG(INFO) << "Null message";
+    return false;
+  }
+  if (arg->message_type() != nl80211_message_type) {
+    LOG(INFO) << "Not an nl80211 message";
+    return false;
+  }
+  const Nl80211Message *msg = reinterpret_cast<const Nl80211Message *>(arg);
+  if (msg->command() != NL80211_CMD_TRIGGER_SCAN) {
+    LOG(INFO) << "Not a NL80211_CMD_TRIGGER_SCAN message";
+    return false;
+  }
+  AttributeListConstRefPtr ssids;
+  if (!msg->const_attributes()->ConstGetNestedAttributeList(
+      NL80211_ATTR_SCAN_SSIDS, &ssids)) {
+    return true;
+  }
+  AttributeIdIterator ssid_iter(*ssids);
+  if (ssid_iter.AtEnd()) {
+    return true;
+  }
+
+  LOG(INFO) << "SSID list contains at least one (hidden) SSID";
+  return false;
+}
+
+}  // namespace shill
+
+#endif  // SHILL_NETLINK_MESSAGE_MATCHERS_H_
diff --git a/scan_session.cc b/scan_session.cc
index 67f2131..920c3a2 100644
--- a/scan_session.cc
+++ b/scan_session.cc
@@ -39,8 +39,8 @@
     const set<uint16_t> &available_frequencies,
     uint32_t ifindex,
     const FractionList &fractions,
-    int min_frequencies,
-    int max_frequencies,
+    size_t min_frequencies,
+    size_t max_frequencies,
     OnScanFailed on_scan_failed)
     : weak_ptr_factory_(this),
       netlink_manager_(netlink_manager),
@@ -55,7 +55,7 @@
       min_frequencies_(min_frequencies),
       max_frequencies_(max_frequencies),
       on_scan_failed_(on_scan_failed),
-      scan_tries_left_(kScanRetryCount + 1) {
+      scan_tries_left_(kScanRetryCount) {
   sort(frequency_list_.begin(), frequency_list_.end(),
        &ScanSession::CompareFrequencyCount);
   // Add to |frequency_list_| all the frequencies from |available_frequencies|
@@ -130,6 +130,10 @@
 }
 
 void ScanSession::DoScan(const vector<uint16_t> &scan_frequencies) {
+  if (scan_frequencies.empty()) {
+    LOG(INFO) << "Not sending empty frequency list";
+    return;
+  }
   TriggerScanMessage trigger_scan;
   trigger_scan.attributes()->SetU32AttributeValue(NL80211_ATTR_IFINDEX,
                                                   wifi_interface_index_);
@@ -193,14 +197,16 @@
     LOG(ERROR) << __func__ << ": Message failed: "
                << error_ack_message->ToString();
     if (error_ack_message->error() == EBUSY) {
-      if (--scan_tries_left_ == 0) {
+      if (scan_tries_left_ == 0) {
         LOG(ERROR) << "Retried progressive scan " << kScanRetryCount
                    << " times and failed each time.  Giving up.";
         on_scan_failed_.Run();
-        scan_tries_left_ = kScanRetryCount + 1;
+        scan_tries_left_ = kScanRetryCount;
         return;
       }
-      SLOG(WiFi, 3) << __func__ << " - trying again";
+      --scan_tries_left_;
+      SLOG(WiFi, 3) << __func__ << " - trying again (" << scan_tries_left_
+                    << " remaining after this)";
       dispatcher_->PostDelayedTask(Bind(&ScanSession::ReInitiateScan,
                                         weak_ptr_factory_.GetWeakPtr()),
                                    kScanRetryDelayMilliseconds);
diff --git a/scan_session.h b/scan_session.h
index 5d897af..258dd3d 100644
--- a/scan_session.h
+++ b/scan_session.h
@@ -99,18 +99,18 @@
               const std::set<uint16_t> &available_frequencies,
               uint32_t ifindex,
               const FractionList &fractions,
-              int min_frequencies,
-              int max_frequencies,
+              size_t min_frequencies,
+              size_t max_frequencies,
               OnScanFailed on_scan_failed);
 
   virtual ~ScanSession();
 
   // Returns true if |ScanSession| contains unscanned frequencies.
-  bool HasMoreFrequencies() const;
+  virtual bool HasMoreFrequencies() const;
 
   // Adds an SSID to the list of things for which to scan.  Useful for hidden
   // SSIDs.
-  void AddSsid(const ByteString &ssid);
+  virtual void AddSsid(const ByteString &ssid);
 
   // Start a wifi scan of the next set of frequencies (derived from the
   // constructor's parameters) after saving those frequencies for the potential
@@ -125,6 +125,11 @@
 
  private:
   friend class ScanSessionTest;
+  friend class WiFiObjectTest;  // OnTriggerScanResponse.
+  FRIEND_TEST(ScanSessionTest, EBusy);
+  FRIEND_TEST(ScanSessionTest, OnError);
+  FRIEND_TEST(ScanSessionTest, OnTriggerScanResponse);
+
   // Milliseconds to wait before retrying a failed scan.
   static const uint64_t kScanRetryDelayMilliseconds;
   // Number of times to retry a failed scan before giving up and calling
diff --git a/scan_session_unittest.cc b/scan_session_unittest.cc
index 4022416..b55cce2 100644
--- a/scan_session_unittest.cc
+++ b/scan_session_unittest.cc
@@ -4,15 +4,21 @@
 
 #include "shill/scan_session.h"
 
+#include <errno.h>
+
 #include <limits>
 #include <set>
 #include <vector>
 
+#include <base/memory/weak_ptr.h>
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
 
+#include "shill/mock_event_dispatcher.h"
 #include "shill/mock_netlink_manager.h"
 #include "shill/netlink_manager.h"
+#include "shill/netlink_message_matchers.h"
+#include "shill/nl80211_message.h"
 
 using std::set;
 using std::vector;
@@ -50,11 +56,13 @@
   kExpectedFreq2412
 };
 
+static const uint16_t kNl80211FamilyId = 0x13;
+
 class ScanSessionTest : public Test {
  public:
   // Test set of "all the other frequencies this device can support" in
   // sorted order.
-  ScanSessionTest() {
+  ScanSessionTest() : weak_ptr_factory_(this) {
     WiFiProvider::FrequencyCountList connected_frequencies(
         kConnectedFrequencies,
         kConnectedFrequencies + arraysize(kConnectedFrequencies));
@@ -64,16 +72,16 @@
         kUnconnectedFrequencies + arraysize(kUnconnectedFrequencies));
     const int kArbitraryMinimum = 1;
     const int kArbitraryMaximum = std::numeric_limits<int>::max();
-    ScanSession::OnScanFailed null_error_handler;
     scan_session_.reset(new ScanSession(&netlink_manager_,
-                                        NULL,
+                                        &dispatcher_,
                                         connected_frequencies,
                                         unconnected_frequencies,
                                         0,
                                         ScanSession::FractionList(),
                                         kArbitraryMinimum,
                                         kArbitraryMaximum,
-                                        null_error_handler));
+                                        Bind(&ScanSessionTest::OnScanError,
+                                             weak_ptr_factory_.GetWeakPtr())));
   }
 
   virtual std::vector<uint16_t> GetScanFrequencies(float scan_fraction,
@@ -84,13 +92,29 @@
   }
   ScanSession *scan_session() { return scan_session_.get(); }
 
- private:
-  scoped_ptr<ScanSession> scan_session_;
+  void SetScanSize(size_t min_frequencies, size_t max_frequencies) {
+    scan_session_->min_frequencies_ = min_frequencies;
+    scan_session_->max_frequencies_ = max_frequencies;
+  }
+
+  size_t GetScanFrequencyCount() {
+    return arraysize(kConnectedFrequencies) +
+        arraysize(kUnconnectedFrequencies);
+  }
+
+ protected:
+  MOCK_METHOD0(OnScanError, void());
+  MockNetlinkManager *netlink_manager() { return &netlink_manager_; }
+  MockEventDispatcher *dispatcher() { return &dispatcher_; }
+
+  MockEventDispatcher dispatcher_;
   MockNetlinkManager netlink_manager_;
+  scoped_ptr<ScanSession> scan_session_;
+  base::WeakPtrFactory<ScanSessionTest> weak_ptr_factory_;
 };
 
 // Test that we can get a bunch of frequencies up to a specified fraction.
-TEST_F(ScanSessionTest, FractionTest) {
+TEST_F(ScanSessionTest, Fraction) {
   vector<uint16_t> result;
 
   // Get the first 83% of the connected values.
@@ -131,7 +155,7 @@
 
 // Test that we can get a bunch of frequencies up to a specified fraction,
 // followed by another group up to a specified fraction.
-TEST_F(ScanSessionTest, TwoFractionsTest) {
+TEST_F(ScanSessionTest, TwoFractions) {
   vector<uint16_t> result;
 
   // Get the first 60% of the connected values.
@@ -173,7 +197,7 @@
 
 // Test that we can get a bunch of frequencies up to a minimum count, even
 // when the requested fraction has already been reached.
-TEST_F(ScanSessionTest, MinTest) {
+TEST_F(ScanSessionTest, Min) {
   vector<uint16_t> result;
 
   // Get the first 3 previously seen values.
@@ -214,7 +238,7 @@
 }
 
 // Test that we can get up to a specified maximum number of frequencies.
-TEST_F(ScanSessionTest, MaxTest) {
+TEST_F(ScanSessionTest, Max) {
   vector<uint16_t> result;
 
   // Get the first 7 values (crosses seen/unseen boundary).
@@ -247,7 +271,7 @@
 
 // Test that we can get exactly the seen frequencies and exactly the unseen
 // ones.
-TEST_F(ScanSessionTest, ExactTest) {
+TEST_F(ScanSessionTest, Exact) {
   vector<uint16_t> result;
 
   // Get the first 5 values -- exectly on the seen/unseen border.
@@ -278,8 +302,7 @@
 }
 
 // Test that we can get everything in one read.
-TEST_F(ScanSessionTest, AllOneReadTest) {
-
+TEST_F(ScanSessionTest, AllOneRead) {
   vector<uint16_t> expected;
   expected.push_back(kExpectedFreq5640);
   expected.push_back(kExpectedFreq5600);
@@ -301,7 +324,7 @@
 
 // Test that we can get all the previously seen frequencies (and only the
 // previously seen frequencies) via the requested fraction.
-TEST_F(ScanSessionTest, EverythingFractionTest) {
+TEST_F(ScanSessionTest, EverythingFraction) {
   vector<uint16_t> result;
 
   // Get the first 100% of the connected values.
@@ -334,7 +357,7 @@
 }
 
 // Test that we can get each value individually.
-TEST_F(ScanSessionTest, IndividualReadsTest) {
+TEST_F(ScanSessionTest, IndividualReads) {
   vector<uint16_t> result;
   static const float kArbitraryFraction = 0.83;
 
@@ -410,4 +433,81 @@
   }
 }
 
+TEST_F(ScanSessionTest, OnTriggerScanResponse) {
+  Nl80211Message::SetMessageType(kNl80211FamilyId);
+
+  EXPECT_CALL(*netlink_manager(), SendMessage(
+      IsNl80211Command(kNl80211FamilyId, NL80211_CMD_TRIGGER_SCAN), _));
+  scan_session()->InitiateScan();
+
+  EXPECT_CALL(*this, OnScanError());
+  NewScanResultsMessage not_supposed_to_get_this_message;
+  scan_session()->OnTriggerScanResponse(not_supposed_to_get_this_message);
+}
+
+TEST_F(ScanSessionTest, ExhaustFrequencies) {
+  // Set min & max scan frequency count to 1 so each scan will be of a single
+  // frequency.
+  SetScanSize(1, 1);
+
+  // Perform all the progressive scans until the frequencies are exhausted.
+  for (size_t i = 0; i < GetScanFrequencyCount(); ++i) {
+    EXPECT_TRUE(scan_session()->HasMoreFrequencies());
+    EXPECT_CALL(*netlink_manager(), SendMessage(
+        IsNl80211Command(kNl80211FamilyId, NL80211_CMD_TRIGGER_SCAN), _));
+    scan_session()->InitiateScan();
+  }
+
+  EXPECT_FALSE(scan_session()->HasMoreFrequencies());
+  EXPECT_CALL(*netlink_manager(), SendMessage(
+      IsNl80211Command(kNl80211FamilyId, NL80211_CMD_TRIGGER_SCAN), _))
+      .Times(0);
+  scan_session()->InitiateScan();
+}
+
+TEST_F(ScanSessionTest, OnError) {
+  Nl80211Message::SetMessageType(kNl80211FamilyId);
+
+  EXPECT_CALL(*netlink_manager(), SendMessage(
+      IsNl80211Command(kNl80211FamilyId, NL80211_CMD_TRIGGER_SCAN), _));
+  scan_session()->InitiateScan();
+
+  EXPECT_CALL(*this, OnScanError());
+  ErrorAckMessage error_message(-EINTR);
+  scan_session()->OnTriggerScanResponse(error_message);
+}
+
+TEST_F(ScanSessionTest, EBusy) {
+  const size_t kSmallRetryNumber = 3;
+  Nl80211Message::SetMessageType(kNl80211FamilyId);
+  scan_session()->scan_tries_left_ = kSmallRetryNumber;
+
+  EXPECT_CALL(*netlink_manager(), SendMessage(
+      IsNl80211Command(kNl80211FamilyId, NL80211_CMD_TRIGGER_SCAN), _));
+  scan_session()->InitiateScan();
+
+  ErrorAckMessage error_message(-EBUSY);
+  for (size_t i = 0; i < kSmallRetryNumber; ++i) {
+    EXPECT_CALL(*this, OnScanError()).Times(0);
+    EXPECT_CALL(*dispatcher(), PostDelayedTask(_, _));
+    scan_session()->OnTriggerScanResponse(error_message);
+  }
+
+  EXPECT_CALL(*this, OnScanError());
+  scan_session()->OnTriggerScanResponse(error_message);
+}
+
+TEST_F(ScanSessionTest, ScanHidden) {
+  scan_session_->AddSsid(ByteString("a", 1));
+  EXPECT_CALL(netlink_manager_,
+              SendMessage(HasHiddenSSID(kNl80211FamilyId), _));
+  scan_session()->InitiateScan();
+}
+
+TEST_F(ScanSessionTest, ScanNoHidden) {
+  EXPECT_CALL(netlink_manager_,
+              SendMessage(HasNoHiddenSSID(kNl80211FamilyId), _));
+  scan_session()->InitiateScan();
+}
+
 }  // namespace shill
diff --git a/wifi.cc b/wifi.cc
index 2e35ced..d73ed65 100644
--- a/wifi.cc
+++ b/wifi.cc
@@ -10,13 +10,16 @@
 #include <string.h>
 
 #include <algorithm>
+#include <limits>
 #include <map>
 #include <string>
 #include <vector>
 
 #include <base/bind.h>
-#include <base/stringprintf.h>
+#include <base/file_path.h>
+#include <base/file_util.h>
 #include <base/string_util.h>
+#include <base/stringprintf.h>
 #include <chromeos/dbus/service_constants.h>
 #include <glib.h>
 
@@ -35,6 +38,7 @@
 #include "shill/property_accessor.h"
 #include "shill/proxy_factory.h"
 #include "shill/rtnl_handler.h"
+#include "shill/scan_session.h"
 #include "shill/scope_logger.h"
 #include "shill/shill_time.h"
 #include "shill/supplicant_eap_state_handler.h"
@@ -49,6 +53,7 @@
 
 using base::Bind;
 using base::StringPrintf;
+using file_util::PathExists;
 using std::map;
 using std::string;
 using std::vector;
@@ -72,6 +77,8 @@
 const int WiFi::kFastScanIntervalSeconds = 10;
 const int WiFi::kPendingTimeoutSeconds = 15;
 const int WiFi::kReconnectTimeoutSeconds = 10;
+const size_t WiFi::kMinumumFrequenciesToScan = 4;  // Arbitrary but > 0.
+const char WiFi::kProgressiveScanFlagFile[] = "/home/chronos/.progressive_scan";
 
 WiFi::WiFi(ControlInterface *control_interface,
            EventDispatcher *dispatcher,
@@ -105,7 +112,10 @@
       bgscan_signal_threshold_dbm_(kDefaultBgscanSignalThresholdDbm),
       scan_pending_(false),
       scan_interval_seconds_(kDefaultScanIntervalSeconds),
-      netlink_manager_(NetlinkManager::GetInstance()) {
+      progressive_scan_enabled_(false),
+      netlink_manager_(NetlinkManager::GetInstance()),
+      min_frequencies_to_scan_(kMinumumFrequenciesToScan),
+      max_frequencies_to_scan_(std::numeric_limits<int>::max()) {
   PropertyStore *store = this->mutable_store();
   store->RegisterDerivedString(
       flimflam::kBgscanMethodProperty,
@@ -141,6 +151,7 @@
       ScopeLogger::kWiFi,
       Bind(&WiFi::OnWiFiDebugScopeChanged, weak_ptr_factory_.GetWeakPtr()));
   CHECK(netlink_manager_);
+  progressive_scan_enabled_ = PathExists(FilePath(kProgressiveScanFlagFile));
   SLOG(WiFi, 2) << "WiFi device " << link_name() << " initialized.";
 }
 
@@ -224,14 +235,47 @@
 
 void WiFi::Scan(ScanType scan_type, Error */*error*/) {
   LOG(INFO) << __func__;
-
-  if (scan_type == kProgressiveScan)
+  // Measure scans that are supposed to be "progressive" regardless of whether
+  // we are actually doing a progressive scan.
+  if (scan_type == kProgressiveScan) {
     metrics()->NotifyDeviceScanStarted(interface_index());
+  }
 
-  // Needs to send a D-Bus message, but may be called from D-Bus
-  // signal handler context (via Manager::RequestScan). So defer work
-  // to event loop.
-  dispatcher()->PostTask(Bind(&WiFi::ScanTask, weak_ptr_factory_.GetWeakPtr()));
+  if (progressive_scan_enabled_ && scan_type == kProgressiveScan) {
+    SLOG(WiFi, 4) << "Doing progressive scan on " << link_name();
+    if (!scan_session_) {
+      // TODO(wdg): Perform in-depth testing to determine the best values for
+      // the different scans. chromium:235293
+      ScanSession::FractionList scan_fractions;
+      scan_fractions.push_back(.33);  // First scan gets 33 percentile.
+      scan_fractions.push_back(.33);  // Second scan gets 66th percentile.
+      scan_fractions.push_back(ScanSession::kAllFrequencies);
+      scan_session_.reset(
+          new ScanSession(netlink_manager_,
+                          dispatcher(),
+                          provider_->GetScanFrequencies(),
+                          all_scan_frequencies_,
+                          interface_index(),
+                          scan_fractions,
+                          min_frequencies_to_scan_,
+                          max_frequencies_to_scan_,
+                          Bind(&WiFi::OnFailedProgressiveScan,
+                               weak_ptr_factory_.GetWeakPtr())));
+      for (const auto &ssid : provider_->GetHiddenSSIDList()) {
+        scan_session_->AddSsid(ByteString(&ssid.front(), ssid.size()));
+      }
+    }
+    dispatcher()->PostTask(
+        Bind(&WiFi::ProgressiveScanTask, weak_ptr_factory_.GetWeakPtr()));
+  } else {
+    SLOG(WiFi, 4) << "Doing full scan - progressive scan "
+                  << (progressive_scan_enabled_ ? "ENABLED" : "DISABLED");
+    // Needs to send a D-Bus message, but may be called from D-Bus
+    // signal handler context (via Manager::RequestScan). So defer work
+    // to event loop.
+    dispatcher()->PostTask(
+        Bind(&WiFi::ScanTask, weak_ptr_factory_.GetWeakPtr()));
+  }
 }
 
 void WiFi::BSSAdded(const ::DBus::Path &path,
@@ -277,7 +321,6 @@
   // may require the the registration of new D-Bus objects. And such
   // registration can't be done in the context of a D-Bus signal
   // handler.
-  metrics()->NotifyDeviceScanFinished(interface_index());
   dispatcher()->PostTask(Bind(&WiFi::ScanDoneTask,
                               weak_ptr_factory_.GetWeakPtr()));
 }
@@ -989,6 +1032,16 @@
 
 void WiFi::ScanDoneTask() {
   SLOG(WiFi, 2) << __func__ << " need_bss_flush_ " << need_bss_flush_;
+
+  if (scan_session_) {
+    // Post |ProgressiveScanTask| so it runs after any |BSSAddedTask|s that have
+    // been posted.  This allows connections on new BSSes to be started before
+    // we decide whether to abort the progressive scan or continue scanning.
+    dispatcher()->PostTask(
+        Bind(&WiFi::ProgressiveScanTask, weak_ptr_factory_.GetWeakPtr()));
+  } else {
+    metrics()->NotifyDeviceScanFinished(interface_index());
+  }
   if (need_bss_flush_) {
     CHECK(supplicant_interface_proxy_ != NULL);
     // Compute |max_age| relative to |resumed_at_|, to account for the
@@ -1051,6 +1104,45 @@
   }
 }
 
+void WiFi::ProgressiveScanTask() {
+  SLOG(WiFi, 2) << __func__ << " - scan requested for " << link_name();
+  if (!enabled()) {
+    LOG(INFO) << "Ignoring scan request while device is not enabled.";
+    metrics()->NotifyDeviceScanFinished(interface_index());
+    return;
+  }
+  if (!scan_session_) {
+    SLOG(WiFi, 2) << "No scan session -- returning";
+    metrics()->NotifyDeviceScanFinished(interface_index());
+    return;
+  }
+  if (!IsIdle()) {
+    SLOG(WiFi, 2) << "Ignoring scan request while connecting to an AP.";
+    scan_session_.reset();
+    metrics()->NotifyDeviceScanFinished(interface_index());
+    return;
+  }
+  if (scan_session_->HasMoreFrequencies()) {
+    SLOG(WiFi, 2) << "Initiating a scan -- returning";
+    SetScanPending(true);
+    // After us initiating a scan, supplicant will gather the scan results and
+    // send us zero or more |BSSAdded| events followed by a |ScanDone|.
+    scan_session_->InitiateScan();
+    return;
+  }
+  LOG(ERROR) << "A complete progressive scan turned-up nothing -- "
+             << "do a regular scan";
+  scan_session_.reset();
+  Scan(kFullScan, NULL);
+}
+
+void WiFi::OnFailedProgressiveScan() {
+  LOG(ERROR) << "Couldn't issue a scan on " << link_name()
+             << " -- doing a regular scan";
+  scan_session_.reset();
+  Scan(kFullScan, NULL);
+}
+
 void WiFi::SetScanPending(bool pending) {
   if (scan_pending_ != pending) {
     scan_pending_ = pending;
diff --git a/wifi.h b/wifi.h
index 6639828..2dfadad 100644
--- a/wifi.h
+++ b/wifi.h
@@ -103,6 +103,7 @@
 class NetlinkManager;
 class NetlinkMessage;
 class ProxyFactory;
+class ScanSession;
 class SupplicantEAPStateHandler;
 class SupplicantInterfaceProxyInterface;
 class SupplicantProcessProxyInterface;
@@ -222,6 +223,8 @@
   static const int kFastScanIntervalSeconds;
   static const int kPendingTimeoutSeconds;
   static const int kReconnectTimeoutSeconds;
+  static const size_t kMinumumFrequenciesToScan;
+  static const char kProgressiveScanFlagFile[];
 
   // Gets the list of frequencies supported by this device.
   void ConfigureScanFrequencies();
@@ -300,6 +303,10 @@
   // with the reason for failure.
   virtual bool RemoveNetworkForService(
       const WiFiService *service, Error *error);
+  // Perform the next in a series of progressive scans.
+  void ProgressiveScanTask();
+  // Recovers from failed progressive scan.
+  void OnFailedProgressiveScan();
   // Restart fast scanning after disconnection.
   void RestartFastScanAttempts();
   // Schedules a scan attempt at time |scan_interval_seconds_| in the
@@ -410,8 +417,12 @@
   bool scan_pending_;
   uint16 scan_interval_seconds_;
 
+  bool progressive_scan_enabled_;
   NetlinkManager *netlink_manager_;
   std::set<uint16_t> all_scan_frequencies_;
+  scoped_ptr<ScanSession> scan_session_;
+  size_t min_frequencies_to_scan_;
+  size_t max_frequencies_to_scan_;
 
   DISALLOW_COPY_AND_ASSIGN(WiFi);
 };
diff --git a/wifi_provider.cc b/wifi_provider.cc
index 00f8d54..b257916 100644
--- a/wifi_provider.cc
+++ b/wifi_provider.cc
@@ -508,9 +508,6 @@
   // Freeze the accumulation of frequency counts when the total maxes-out.
   // This ensures that no count wraps and that the relative values are
   // consistent.
-  // TODO(wdg): In future CL, |total_frequency_connections_| is used to
-  // calculate percentiles for progressive scan.  This check needs to be in
-  // place so _that_ value doesn't wrap, either.
   // TODO(wdg): Replace this, simple, 'forever' collection of connection
   // statistics with a more clever 'aging' algorithm.  crbug.com/227233
   if (total_frequency_connections_ + 1 == std::numeric_limits<int64_t>::max()) {
@@ -537,4 +534,13 @@
       Metrics::kMetricFrequenciesConnectedNumBuckets);
 }
 
+WiFiProvider::FrequencyCountList WiFiProvider::GetScanFrequencies() const {
+  FrequencyCountList freq_connects_list;
+  for (const auto freq_count : connect_count_by_frequency_) {
+    freq_connects_list.push_back(FrequencyCount(freq_count.first,
+                                                freq_count.second));
+  }
+  return freq_connects_list;
+}
+
 }  // namespace shill
diff --git a/wifi_provider.h b/wifi_provider.h
index 0e7640c..79e375c 100644
--- a/wifi_provider.h
+++ b/wifi_provider.h
@@ -105,6 +105,10 @@
 
   virtual void IncrementConnectCount(uint16 frequency_mhz);
 
+  // Returns a list of all of the frequencies on which this device has
+  // connected.  This data is accumulated across multiple shill runs.
+  virtual FrequencyCountList GetScanFrequencies() const;
+
  private:
   friend class WiFiProviderTest;
   FRIEND_TEST(WiFiProviderTest, FrequencyMapToStringList);
diff --git a/wifi_unittest.cc b/wifi_unittest.cc
index 5077d46..bdf9fb0 100644
--- a/wifi_unittest.cc
+++ b/wifi_unittest.cc
@@ -4,10 +4,10 @@
 
 #include "shill/wifi.h"
 
-#include <netinet/ether.h>
 #include <linux/if.h>
-#include <sys/socket.h>
 #include <linux/netlink.h>  // Needs typedefs from sys/socket.h.
+#include <netinet/ether.h>
+#include <sys/socket.h>
 
 #include <map>
 #include <string>
@@ -16,10 +16,10 @@
 #include <base/file_util.h>
 #include <base/memory/ref_counted.h>
 #include <base/memory/scoped_ptr.h>
-#include <base/stringprintf.h>
 #include <base/string_number_conversions.h>
 #include <base/string_split.h>
 #include <base/string_util.h>
+#include <base/stringprintf.h>
 #include <chromeos/dbus/service_constants.h>
 #include <dbus-c++/dbus.h>
 #include <gmock/gmock.h>
@@ -45,6 +45,7 @@
 #include "shill/mock_netlink_manager.h"
 #include "shill/mock_profile.h"
 #include "shill/mock_rtnl_handler.h"
+#include "shill/mock_scan_session.h"
 #include "shill/mock_store.h"
 #include "shill/mock_supplicant_bss_proxy.h"
 #include "shill/mock_supplicant_eap_state_handler.h"
@@ -54,9 +55,12 @@
 #include "shill/mock_time.h"
 #include "shill/mock_wifi_provider.h"
 #include "shill/mock_wifi_service.h"
+#include "shill/netlink_message_matchers.h"
 #include "shill/nice_mock_control.h"
+#include "shill/nl80211_message.h"
 #include "shill/property_store_unittest.h"
 #include "shill/proxy_factory.h"
+#include "shill/scan_session.h"
 #include "shill/technology.h"
 #include "shill/wifi_endpoint.h"
 #include "shill/wifi_service.h"
@@ -76,6 +80,10 @@
 using ::testing::InSequence;
 using ::testing::Invoke;
 using ::testing::InvokeWithoutArgs;
+using ::testing::MakeMatcher;
+using ::testing::Matcher;
+using ::testing::MatcherInterface;
+using ::testing::MatchResultListener;
 using ::testing::Mock;
 using ::testing::NiceMock;
 using ::testing::NotNull;
@@ -91,6 +99,15 @@
 
 namespace shill {
 
+namespace {
+
+const uint16_t kNl80211FamilyId = 0x13;
+const uint16_t kRandomScanFrequency1 = 5600;
+const uint16_t kRandomScanFrequency2 = 5560;
+const uint16_t kRandomScanFrequency3 = 2422;
+
+}  // namespace
+
 class WiFiPropertyTest : public PropertyStoreTest {
  public:
   WiFiPropertyTest()
@@ -187,7 +204,6 @@
   EXPECT_TRUE(device_->bgscan_method_.empty());
 }
 
-
 MATCHER_P(EndpointMatch, endpoint, "") {
   return
       arg->ssid() == endpoint->ssid() &&
@@ -195,6 +211,7 @@
       arg->security_mode() == endpoint->security_mode();
 }
 
+
 class WiFiObjectTest : public ::testing::TestWithParam<string> {
  public:
   explicit WiFiObjectTest(EventDispatcher *dispatcher)
@@ -220,6 +237,7 @@
         supplicant_interface_proxy_(
             new NiceMock<MockSupplicantInterfaceProxy>()),
         proxy_factory_(this) {
+    InstallMockScanSession();
     ::testing::DefaultValue< ::DBus::Path>::Set("/default/path");
 
     ON_CALL(dhcp_provider_, CreateConfig(_, _, _, _)).
@@ -229,6 +247,9 @@
     ON_CALL(proxy_factory_, CreateSupplicantNetworkProxy(_, _)).
         WillByDefault(InvokeWithoutArgs(
             this, &WiFiObjectTest::CreateSupplicantNetworkProxy));
+    ON_CALL(netlink_manager_,
+            GetMessageType(Nl80211Message::kMessageTypeString)).
+        WillByDefault(Return(kNl80211FamilyId));
 
     // Transfers ownership.
     manager_.dbus_manager_.reset(dbus_manager_);
@@ -236,8 +257,14 @@
 
     wifi_->provider_ = &wifi_provider_;
     wifi_->time_ = &time_;
-    // TODO(wdg): Flesh out the wifi tests to include netlink_manager.
     wifi_->netlink_manager_ = &netlink_manager_;
+    wifi_->progressive_scan_enabled_ = true;
+
+    // The following is only useful when a real |ScanSession| is used; it is
+    // ignored by |MockScanSession|.
+    wifi_->all_scan_frequencies_.insert(kRandomScanFrequency1);
+    wifi_->all_scan_frequencies_.insert(kRandomScanFrequency2);
+    wifi_->all_scan_frequencies_.insert(kRandomScanFrequency3);
   }
 
   virtual void SetUp() {
@@ -283,6 +310,51 @@
     SetPendingService(NULL);
   }
 
+  size_t GetScanFrequencyCount() const {
+    return wifi_->all_scan_frequencies_.size();
+  }
+
+  void SetScanSize(int min, int max) {
+    wifi_->min_frequencies_to_scan_ = min;
+    wifi_->max_frequencies_to_scan_ = max;
+  }
+
+  // This clears WiFi::scan_session_, thereby allowing WiFi::Scan to create a
+  // real scan session.
+  void ClearScanSession() {
+    wifi_->scan_session_.reset();
+  }
+
+  bool IsScanSessionNull() {
+    return !wifi_->scan_session_;
+  }
+
+  void InstallMockScanSession() {
+    WiFiProvider::FrequencyCountList previous_frequencies;
+    std::set<uint16_t> available_frequencies;
+    ScanSession::FractionList fractions;
+    ScanSession::OnScanFailed null_callback;
+    scan_session_ = new MockScanSession(&netlink_manager_,
+                                        event_dispatcher_,
+                                        previous_frequencies,
+                                        available_frequencies,
+                                        0,
+                                        fractions,
+                                        0,
+                                        0,
+                                        null_callback);
+    wifi_->scan_session_.reset(scan_session_);
+  }
+
+  // Or DisableProgressiveScan()...
+  void EnableFullScan() {
+    wifi_->progressive_scan_enabled_ = false;
+  }
+
+  void OnTriggerScanResponse(const NetlinkMessage &message) {
+    wifi_->scan_session_->OnTriggerScanResponse(message);
+  }
+
  protected:
   typedef scoped_refptr<MockWiFiService> MockWiFiServiceRefPtr;
 
@@ -502,7 +574,7 @@
   void FireScanTimer() {
     wifi_->ScanTimerHandler();
   }
-  void TriggerScan() {
+  void TriggerFullScan() {
     wifi_->Scan(Device::kFullScan, NULL);
   }
   const WiFiServiceRefPtr &GetCurrentService() {
@@ -576,6 +648,13 @@
     wifi_->LinkEvent(IFF_LOWER_UP, IFF_LOWER_UP);
   }
   void ReportScanDone() {
+    // Eliminate |scan_session| so |ScanDoneTask| doesn't launch another scan.
+    wifi_->scan_session_.reset();
+    wifi_->ScanDoneTask();
+    // Make a new |scan_session| so that future scanning is done with the mock.
+    InstallMockScanSession();
+  }
+  void ReportScanDoneKeepScanSession() {
     wifi_->ScanDoneTask();
   }
   void ReportCurrentBSSChanged(const string &new_bss) {
@@ -708,6 +787,7 @@
   }
 
   EventDispatcher *event_dispatcher_;
+  MockScanSession *scan_session_;  // Owned by |wifi_|.
   NiceMock<MockRTNLHandler> rtnl_handler_;
   MockTime time_;
 
@@ -887,7 +967,8 @@
   EXPECT_TRUE(GetCurrentService() == NULL);
 }
 
-TEST_F(WiFiMainTest, CleanStart) {
+TEST_F(WiFiMainTest, CleanStart_FullScan) {
+  EnableFullScan();
   EXPECT_CALL(*supplicant_process_proxy_, CreateInterface(_));
   EXPECT_CALL(*supplicant_process_proxy_, GetInterface(_))
       .Times(AnyNumber())
@@ -902,6 +983,21 @@
   EXPECT_FALSE(GetScanTimer().IsCancelled());
 }
 
+TEST_F(WiFiMainTest, CleanStart) {
+  EXPECT_CALL(*supplicant_process_proxy_, CreateInterface(_));
+  EXPECT_CALL(*supplicant_process_proxy_, GetInterface(_))
+      .Times(AnyNumber())
+      .WillRepeatedly(Throw(
+          DBus::Error(
+              "fi.w1.wpa_supplicant1.InterfaceUnknown",
+              "test threw fi.w1.wpa_supplicant1.InterfaceUnknown")));
+  EXPECT_TRUE(GetScanTimer().IsCancelled());
+  StartWiFi();
+  EXPECT_CALL(*scan_session_, InitiateScan());
+  dispatcher_.DispatchPendingEvents();
+  EXPECT_FALSE(GetScanTimer().IsCancelled());
+}
+
 TEST_F(WiFiMainTest, ClearCachedCredentials) {
   StartWiFi();
   DBus::Path network = "/test/path";
@@ -963,7 +1059,8 @@
   EXPECT_FALSE(RemoveNetwork(network));
 }
 
-TEST_F(WiFiMainTest, Restart) {
+TEST_F(WiFiMainTest, Restart_FullScan) {
+  EnableFullScan();
   EXPECT_CALL(*supplicant_process_proxy_, CreateInterface(_))
       .Times(AnyNumber())
       .WillRepeatedly(Throw(
@@ -976,13 +1073,26 @@
   dispatcher_.DispatchPendingEvents();
 }
 
+TEST_F(WiFiMainTest, Restart) {
+  EXPECT_CALL(*supplicant_process_proxy_, CreateInterface(_))
+      .Times(AnyNumber())
+      .WillRepeatedly(Throw(
+          DBus::Error(
+              "fi.w1.wpa_supplicant1.InterfaceExists",
+              "test threw fi.w1.wpa_supplicant1.InterfaceExists")));
+  EXPECT_CALL(*scan_session_, InitiateScan());
+  StartWiFi();
+  dispatcher_.DispatchPendingEvents();
+}
+
 TEST_F(WiFiMainTest, StartClearsState) {
   EXPECT_CALL(*GetSupplicantInterfaceProxy(), RemoveAllNetworks());
   EXPECT_CALL(*GetSupplicantInterfaceProxy(), FlushBSS(_));
   StartWiFi();
 }
 
-TEST_F(WiFiMainTest, NoScansWhileConnecting) {
+TEST_F(WiFiMainTest, NoScansWhileConnecting_FullScan) {
+  EnableFullScan();
   StartWiFi();
   EXPECT_CALL(*GetSupplicantInterfaceProxy(), Scan(_)).Times(1);
   dispatcher_.DispatchPendingEvents();
@@ -992,13 +1102,13 @@
   // If we're connecting, we ignore scan requests to stay on channel.
   EXPECT_CALL(*service, IsConnecting()).WillOnce(Return(true));
   EXPECT_CALL(*GetSupplicantInterfaceProxy(), Scan(_)).Times(0);
-  TriggerScan();
+  TriggerFullScan();
   dispatcher_.DispatchPendingEvents();
   Mock::VerifyAndClearExpectations(GetSupplicantInterfaceProxy());
   Mock::VerifyAndClearExpectations(service);
   EXPECT_CALL(*service, IsConnecting()).WillOnce(Return(false));
   EXPECT_CALL(*GetSupplicantInterfaceProxy(), Scan(_)).Times(1);
-  TriggerScan();
+  TriggerFullScan();
   dispatcher_.DispatchPendingEvents();
   Mock::VerifyAndClearExpectations(GetSupplicantInterfaceProxy());
   Mock::VerifyAndClearExpectations(service);
@@ -1007,20 +1117,63 @@
   SetCurrentService(service);
   EXPECT_CALL(*service, IsConnecting()).WillOnce(Return(true));
   EXPECT_CALL(*GetSupplicantInterfaceProxy(), Scan(_)).Times(0);
-  TriggerScan();
+  TriggerFullScan();
   dispatcher_.DispatchPendingEvents();
   Mock::VerifyAndClearExpectations(GetSupplicantInterfaceProxy());
   Mock::VerifyAndClearExpectations(service);
   // But otherwise we'll honor the request.
   EXPECT_CALL(*service, IsConnecting()).WillOnce(Return(false));
   EXPECT_CALL(*GetSupplicantInterfaceProxy(), Scan(_)).Times(1);
-  TriggerScan();
+  TriggerFullScan();
   dispatcher_.DispatchPendingEvents();
   Mock::VerifyAndClearExpectations(GetSupplicantInterfaceProxy());
   Mock::VerifyAndClearExpectations(service);
 }
 
-TEST_F(WiFiMainTest, ResumeStartsScanWhenIdle) {
+TEST_F(WiFiMainTest, NoScansWhileConnecting) {
+  StartWiFi();
+  EXPECT_CALL(*scan_session_, InitiateScan());
+  dispatcher_.DispatchPendingEvents();
+  Mock::VerifyAndClearExpectations(GetSupplicantInterfaceProxy());
+  MockWiFiServiceRefPtr service = MakeMockService(flimflam::kSecurityNone);
+  SetPendingService(service);
+  // If we're connecting, we ignore scan requests to stay on channel.
+  EXPECT_CALL(*service, IsConnecting()).WillOnce(Return(true));
+  EXPECT_CALL(*GetSupplicantInterfaceProxy(), Scan(_)).Times(0);
+  EXPECT_CALL(*scan_session_, InitiateScan()).Times(0);
+  TriggerFullScan();
+  dispatcher_.DispatchPendingEvents();
+  Mock::VerifyAndClearExpectations(GetSupplicantInterfaceProxy());
+  Mock::VerifyAndClearExpectations(service);
+  EXPECT_CALL(*service, IsConnecting()).WillOnce(Return(false));
+  EXPECT_CALL(*GetSupplicantInterfaceProxy(), Scan(_)).Times(1);
+  EXPECT_CALL(*scan_session_, InitiateScan()).Times(0);
+  TriggerFullScan();
+  dispatcher_.DispatchPendingEvents();
+  Mock::VerifyAndClearExpectations(GetSupplicantInterfaceProxy());
+  Mock::VerifyAndClearExpectations(service);
+  // Similarly, ignore scans when our connected service is reconnecting.
+  SetPendingService(NULL);
+  SetCurrentService(service);
+  EXPECT_CALL(*service, IsConnecting()).WillOnce(Return(true));
+  EXPECT_CALL(*GetSupplicantInterfaceProxy(), Scan(_)).Times(0);
+  EXPECT_CALL(*scan_session_, InitiateScan()).Times(0);
+  TriggerFullScan();
+  dispatcher_.DispatchPendingEvents();
+  Mock::VerifyAndClearExpectations(GetSupplicantInterfaceProxy());
+  Mock::VerifyAndClearExpectations(service);
+  // But otherwise we'll honor the request.
+  EXPECT_CALL(*service, IsConnecting()).WillOnce(Return(false));
+  EXPECT_CALL(*GetSupplicantInterfaceProxy(), Scan(_)).Times(1);
+  EXPECT_CALL(*scan_session_, InitiateScan()).Times(0);
+  TriggerFullScan();
+  dispatcher_.DispatchPendingEvents();
+  Mock::VerifyAndClearExpectations(GetSupplicantInterfaceProxy());
+  Mock::VerifyAndClearExpectations(service);
+}
+
+TEST_F(WiFiMainTest, ResumeStartsScanWhenIdle_FullScan) {
+  EnableFullScan();
   EXPECT_CALL(*GetSupplicantInterfaceProxy(), Scan(_));
   StartWiFi();
   dispatcher_.DispatchPendingEvents();
@@ -1032,7 +1185,21 @@
   dispatcher_.DispatchPendingEvents();
 }
 
-TEST_F(WiFiMainTest, SuspendDoesNotStartScan) {
+TEST_F(WiFiMainTest, ResumeStartsScanWhenIdle) {
+  EXPECT_CALL(*scan_session_, InitiateScan());
+  StartWiFi();
+  dispatcher_.DispatchPendingEvents();
+  Mock::VerifyAndClearExpectations(GetSupplicantInterfaceProxy());
+  ReportScanDone();
+  ASSERT_TRUE(wifi()->IsIdle());
+  dispatcher_.DispatchPendingEvents();
+  EXPECT_CALL(*scan_session_, InitiateScan());
+  OnAfterResume();
+  dispatcher_.DispatchPendingEvents();
+}
+
+TEST_F(WiFiMainTest, SuspendDoesNotStartScan_FullScan) {
+  EnableFullScan();
   EXPECT_CALL(*GetSupplicantInterfaceProxy(), Scan(_));
   StartWiFi();
   dispatcher_.DispatchPendingEvents();
@@ -1043,7 +1210,20 @@
   dispatcher_.DispatchPendingEvents();
 }
 
-TEST_F(WiFiMainTest, ResumeDoesNotStartScanWhenNotIdle) {
+TEST_F(WiFiMainTest, SuspendDoesNotStartScan) {
+  EXPECT_CALL(*scan_session_, InitiateScan());
+  StartWiFi();
+  dispatcher_.DispatchPendingEvents();
+  Mock::VerifyAndClearExpectations(GetSupplicantInterfaceProxy());
+  ASSERT_TRUE(wifi()->IsIdle());
+  EXPECT_CALL(*GetSupplicantInterfaceProxy(), Scan(_)).Times(0);
+  EXPECT_CALL(*scan_session_, InitiateScan()).Times(0);
+  OnBeforeSuspend();
+  dispatcher_.DispatchPendingEvents();
+}
+
+TEST_F(WiFiMainTest, ResumeDoesNotStartScanWhenNotIdle_FullScan) {
+  EnableFullScan();
   EXPECT_CALL(*GetSupplicantInterfaceProxy(), Scan(_));
   StartWiFi();
   dispatcher_.DispatchPendingEvents();
@@ -1058,6 +1238,22 @@
   dispatcher_.DispatchPendingEvents();
 }
 
+TEST_F(WiFiMainTest, ResumeDoesNotStartScanWhenNotIdle) {
+  EXPECT_CALL(*scan_session_, InitiateScan());
+  StartWiFi();
+  dispatcher_.DispatchPendingEvents();
+  Mock::VerifyAndClearExpectations(GetSupplicantInterfaceProxy());
+  WiFiServiceRefPtr service(SetupConnectedService(DBus::Path(), NULL, NULL));
+  EXPECT_FALSE(wifi()->IsIdle());
+  ScopedMockLog log;
+  EXPECT_CALL(log, Log(_, _, _)).Times(AnyNumber());
+  EXPECT_CALL(log, Log(_, _, EndsWith("already scanning or connected.")));
+  EXPECT_CALL(*GetSupplicantInterfaceProxy(), Scan(_)).Times(0);
+  EXPECT_CALL(*scan_session_, InitiateScan()).Times(0);
+  OnAfterResume();
+  dispatcher_.DispatchPendingEvents();
+}
+
 TEST_F(WiFiMainTest, ScanResults) {
   EXPECT_CALL(*wifi_provider(), OnEndpointAdded(_)).Times(5);
   StartWiFi();
@@ -1374,7 +1570,7 @@
 }
 
 
-MATCHER_P(HasHiddenSSID, ssid, "") {
+MATCHER_P(HasHiddenSSID_FullScan, ssid, "") {
   map<string, DBus::Variant>::const_iterator it =
       arg.find(WPASupplicant::kPropertyScanSSIDs);
   if (it == arg.end()) {
@@ -1391,28 +1587,68 @@
   return ssids.size() == 2 && ssids[0] == ssid && ssids[1].empty();
 }
 
-MATCHER(HasNoHiddenSSID, "") {
+MATCHER(HasNoHiddenSSID_FullScan, "") {
   map<string, DBus::Variant>::const_iterator it =
       arg.find(WPASupplicant::kPropertyScanSSIDs);
   return it == arg.end();
 }
 
-TEST_F(WiFiMainTest, ScanHidden) {
+TEST_F(WiFiMainTest, ScanHidden_FullScan) {
+  EnableFullScan();
   vector<uint8_t>kSSID(1, 'a');
   ByteArrays ssids;
   ssids.push_back(kSSID);
 
   StartWiFi();
   EXPECT_CALL(*wifi_provider(), GetHiddenSSIDList()).WillOnce(Return(ssids));
-  EXPECT_CALL(*GetSupplicantInterfaceProxy(), Scan(HasHiddenSSID(kSSID)));
+  EXPECT_CALL(*GetSupplicantInterfaceProxy(),
+              Scan(HasHiddenSSID_FullScan(kSSID)));
   dispatcher_.DispatchPendingEvents();
 }
 
-TEST_F(WiFiMainTest, ScanNoHidden) {
+// This test is slightly different from the test in scan_session_unittest.cc
+// because this tests the piece of WiFi that builds the SSID list.
+TEST_F(WiFiMainTest, ScanHidden) {
+  // Clear the Mock ScanSession because hidden SSIDs are added when wifi
+  // instantiates a new ScanSession (and it won't instantiate a new ScanSession
+  // if there's already one there).
+  ClearScanSession();
+  vector<uint8_t>kSSID(1, 'a');
+  ByteArrays ssids;
+  ssids.push_back(kSSID);
+
+  EXPECT_CALL(netlink_manager_, SendMessage(
+      IsNl80211Command(kNl80211FamilyId, NL80211_CMD_GET_WIPHY), _));
+  EXPECT_CALL(*wifi_provider(), GetHiddenSSIDList()).WillOnce(Return(ssids));
+  StartWiFi();
+  EXPECT_CALL(netlink_manager_,
+              SendMessage(HasHiddenSSID(kNl80211FamilyId), _));
+  dispatcher_.DispatchPendingEvents();
+}
+
+TEST_F(WiFiMainTest, ScanNoHidden_FullScan) {
+  EnableFullScan();
   StartWiFi();
   EXPECT_CALL(*wifi_provider(), GetHiddenSSIDList())
       .WillOnce(Return(ByteArrays()));
-  EXPECT_CALL(*GetSupplicantInterfaceProxy(), Scan(HasNoHiddenSSID()));
+  EXPECT_CALL(*GetSupplicantInterfaceProxy(), Scan(HasNoHiddenSSID_FullScan()));
+  dispatcher_.DispatchPendingEvents();
+}
+
+// This test is slightly different from the test in scan_session_unittest.cc
+// because this tests the piece of WiFi that builds the SSID list.
+TEST_F(WiFiMainTest, ScanNoHidden) {
+  // Clear the Mock ScanSession because hidden SSIDs are added when wifi
+  // instantiates a new ScanSession (and it won't instantiate a new ScanSession
+  // if there's already one there).
+  ClearScanSession();
+  EXPECT_CALL(netlink_manager_, SendMessage(
+      IsNl80211Command(kNl80211FamilyId, NL80211_CMD_GET_WIPHY), _));
+  EXPECT_CALL(*wifi_provider(), GetHiddenSSIDList())
+      .WillOnce(Return(ByteArrays()));
+  StartWiFi();
+  EXPECT_CALL(netlink_manager_,
+              SendMessage(HasNoHiddenSSID(kNl80211FamilyId), _));
   dispatcher_.DispatchPendingEvents();
 }
 
@@ -1422,6 +1658,7 @@
   EXPECT_CALL(log, Log(_, _, EndsWith(
       "Ignoring scan request while device is not enabled."))).Times(1);
   EXPECT_CALL(*GetSupplicantInterfaceProxy(), Scan(_)).Times(0);
+  EXPECT_CALL(*scan_session_, InitiateScan()).Times(0);
   StartWiFi();
   StopWiFi();
   // A scan is queued when WiFi resumes.
@@ -1429,6 +1666,81 @@
   dispatcher_.DispatchPendingEvents();
 }
 
+TEST_F(WiFiMainTest, ProgressiveScanFound) {
+  // Set min & max scan frequency count to 1 so each scan will be of a single
+  // frequency.
+  SetScanSize(1, 1);
+
+  // Do the first scan (finds nothing).
+  EXPECT_CALL(*scan_session_, InitiateScan());
+  StartWiFi();
+  dispatcher_.DispatchPendingEvents();
+  ReportScanDoneKeepScanSession();
+
+  // Do the second scan (connects afterwards).
+  EXPECT_CALL(*scan_session_, InitiateScan());
+  dispatcher_.DispatchPendingEvents();
+  ReportScanDoneKeepScanSession();
+
+  // Connect after second scan.
+  MockWiFiServiceRefPtr service = MakeMockService(flimflam::kSecurityNone);
+  SetPendingService(service);
+
+  // Verify that the third scan aborts and there is no further scan.
+  ScopedMockLog log;
+  EXPECT_CALL(log, Log(_, _, _)).Times(AnyNumber());
+  EXPECT_CALL(log, Log(_, _, EndsWith(
+      "Ignoring scan request while connecting to an AP."))).Times(1);
+  EXPECT_CALL(*metrics(), NotifyDeviceScanFinished(_));
+  EXPECT_CALL(*scan_session_, InitiateScan()).Times(0);
+  EXPECT_CALL(*GetSupplicantInterfaceProxy(), Scan(_)).Times(0);
+  dispatcher_.DispatchPendingEvents();
+}
+
+TEST_F(WiFiMainTest, ProgressiveScanNotFound) {
+  // Set min & max scan frequency count to 1 so each scan will be of a single
+  // frequency.
+  SetScanSize(1, 1);
+
+  // Do the first scan (finds nothing).
+  EXPECT_CALL(*scan_session_, InitiateScan());
+  StartWiFi();
+  dispatcher_.DispatchPendingEvents();
+  ReportScanDoneKeepScanSession();
+
+  // Do the second scan (finds nothing).
+  EXPECT_CALL(*scan_session_, InitiateScan());
+  dispatcher_.DispatchPendingEvents();
+  ReportScanDoneKeepScanSession();
+
+  // Do the third scan. After (simulated) exhausting of search frequencies,
+  // verify that this scan uses supplicant rather than internal (progressive)
+  // scan.
+  EXPECT_CALL(*scan_session_, HasMoreFrequencies()).WillOnce(Return(false));
+  EXPECT_CALL(*scan_session_, InitiateScan()).Times(0);
+  EXPECT_CALL(*GetSupplicantInterfaceProxy(), Scan(_));
+  dispatcher_.DispatchPendingEvents();
+
+  // And verify that ScanDone reports a complete scan (i.e., the
+  // wifi_::scan_session_ has truly been cleared).
+  EXPECT_CALL(*metrics(), NotifyDeviceScanFinished(_));
+  ReportScanDoneKeepScanSession();
+}
+
+TEST_F(WiFiMainTest, ProgressiveScanError) {
+  // Clear the Mock ScanSession so I can get an actual ScanSession.
+  ClearScanSession();
+  StartWiFi();
+
+  // This should call |Scan| which posts |ScanTask|
+  NewScanResultsMessage not_supposed_to_get_this_message;
+  OnTriggerScanResponse(not_supposed_to_get_this_message);
+
+  EXPECT_TRUE(IsScanSessionNull());
+  EXPECT_CALL(*GetSupplicantInterfaceProxy(), Scan(_)).Times(1);
+  dispatcher_.DispatchPendingEvents();
+}
+
 TEST_F(WiFiMainTest, InitialSupplicantState) {
   EXPECT_EQ(WiFi::kInterfaceStateUnknown, GetSupplicantState());
 }
@@ -1746,7 +2058,8 @@
   ReportScanDone();
 }
 
-TEST_F(WiFiMainTest, ScanTimerIdle) {
+TEST_F(WiFiMainTest, ScanTimerIdle_FullScan) {
+  EnableFullScan();
   StartWiFi();
   dispatcher_.DispatchPendingEvents();
   ReportScanDone();
@@ -1759,6 +2072,18 @@
   EXPECT_FALSE(GetScanTimer().IsCancelled());  // Automatically re-armed.
 }
 
+TEST_F(WiFiMainTest, ScanTimerIdle) {
+  StartWiFi();
+  dispatcher_.DispatchPendingEvents();
+  ReportScanDone();
+  CancelScanTimer();
+  EXPECT_TRUE(GetScanTimer().IsCancelled());
+  EXPECT_CALL(*scan_session_, InitiateScan());
+  FireScanTimer();
+  dispatcher_.DispatchPendingEvents();
+  EXPECT_FALSE(GetScanTimer().IsCancelled());  // Automatically re-armed.
+}
+
 TEST_F(WiFiMainTest, ScanTimerScanning) {
   StartWiFi();
   dispatcher_.DispatchPendingEvents();
@@ -1768,6 +2093,7 @@
   // Should not call Scan, since we're already scanning.
   // (Scanning is triggered by StartWiFi.)
   EXPECT_CALL(*GetSupplicantInterfaceProxy(), Scan(_)).Times(0);
+  EXPECT_CALL(*scan_session_, InitiateScan()).Times(0);
   FireScanTimer();
   dispatcher_.DispatchPendingEvents();
   EXPECT_FALSE(GetScanTimer().IsCancelled());  // Automatically re-armed.
@@ -1782,6 +2108,7 @@
   EXPECT_TRUE(GetScanTimer().IsCancelled());
 
   EXPECT_CALL(*GetSupplicantInterfaceProxy(), Scan(_)).Times(0);
+  EXPECT_CALL(*scan_session_, InitiateScan()).Times(0);
   FireScanTimer();
   dispatcher_.DispatchPendingEvents();
   EXPECT_FALSE(GetScanTimer().IsCancelled());  // Automatically re-armed.
@@ -1813,7 +2140,8 @@
   EXPECT_TRUE(GetScanTimer().IsCancelled());
 }
 
-TEST_F(WiFiMainTest, ScanOnDisconnectWithHidden) {
+TEST_F(WiFiMainTest, ScanOnDisconnectWithHidden_FullScan) {
+  EnableFullScan();
   StartWiFi();
   dispatcher_.DispatchPendingEvents();
   SetupConnectedService(DBus::Path(), NULL, NULL);
@@ -1822,7 +2150,23 @@
   ssids.push_back(kSSID);
   EXPECT_CALL(*wifi_provider(), GetHiddenSSIDList())
       .WillRepeatedly(Return(ssids));
-  EXPECT_CALL(*GetSupplicantInterfaceProxy(), Scan(HasHiddenSSID(kSSID)));
+  EXPECT_CALL(*GetSupplicantInterfaceProxy(),
+              Scan(HasHiddenSSID_FullScan(kSSID)));
+  ReportCurrentBSSChanged(WPASupplicant::kCurrentBSSNull);
+  dispatcher_.DispatchPendingEvents();
+}
+
+TEST_F(WiFiMainTest, ScanOnDisconnectWithHidden) {
+  StartWiFi();
+  dispatcher_.DispatchPendingEvents();
+  ReportScanDone();
+  SetupConnectedService(DBus::Path(), NULL, NULL);
+  vector<uint8_t>kSSID(1, 'a');
+  ByteArrays ssids;
+  ssids.push_back(kSSID);
+  EXPECT_CALL(*wifi_provider(), GetHiddenSSIDList())
+      .WillRepeatedly(Return(ssids));
+  EXPECT_CALL(*scan_session_, InitiateScan());
   ReportCurrentBSSChanged(WPASupplicant::kCurrentBSSNull);
   dispatcher_.DispatchPendingEvents();
 }
@@ -1832,6 +2176,7 @@
   dispatcher_.DispatchPendingEvents();
   SetupConnectedService(DBus::Path(), NULL, NULL);
   EXPECT_CALL(*GetSupplicantInterfaceProxy(), Scan(_)).Times(0);
+  EXPECT_CALL(*scan_session_, InitiateScan()).Times(0);
   EXPECT_CALL(*wifi_provider(), GetHiddenSSIDList())
       .WillRepeatedly(Return(ByteArrays()));
   ReportCurrentBSSChanged(WPASupplicant::kCurrentBSSNull);