shill: Times-out scan frequency lists.

The frequency/count list that is saved in the default profile used to
save the data for all time.  This CL times the data out after 2-3 weeks.

BUG=chromium:236884
TEST=unittest

Change-Id: I8187e2945333ba03eb77c87e087d12f8b4933211
Reviewed-on: https://gerrit.chromium.org/gerrit/51019
Reviewed-by: Wade Guthrie <wdg@chromium.org>
Tested-by: Wade Guthrie <wdg@chromium.org>
Commit-Queue: Wade Guthrie <wdg@chromium.org>
diff --git a/mock_time.h b/mock_time.h
index 1d4ef9f..a140f0c 100644
--- a/mock_time.h
+++ b/mock_time.h
@@ -5,11 +5,11 @@
 #ifndef SHILL_MOCK_TIME_H_
 #define SHILL_MOCK_TIME_H_
 
+#include "shill/shill_time.h"
+
 #include <base/basictypes.h>
 #include <gmock/gmock.h>
 
-#include "shill/shill_time.h"
-
 namespace shill {
 
 class MockTime : public Time {
@@ -20,6 +20,7 @@
   MOCK_METHOD1(GetTimeMonotonic, int(struct timeval *tv));
   MOCK_METHOD2(GetTimeOfDay, int(struct timeval *tv, struct timezone *tz));
   MOCK_METHOD0(GetNow, Timestamp());
+  MOCK_CONST_METHOD0(GetSecondsSinceEpoch, time_t());
 
  private:
   DISALLOW_COPY_AND_ASSIGN(MockTime);
diff --git a/shill_time.cc b/shill_time.cc
index 7d3519d..45ebf82 100644
--- a/shill_time.cc
+++ b/shill_time.cc
@@ -47,11 +47,11 @@
 }
 
 Timestamp Time::GetNow() {
-  struct timeval now_monotonic = (const struct timeval){ 0 };
+  struct timeval now_monotonic = (const struct timeval) { 0 };
   GetTimeMonotonic(&now_monotonic);
-  struct timeval now_wall_clock = (const struct timeval){ 0 };
+  struct timeval now_wall_clock = (const struct timeval) { 0 };
   GetTimeOfDay(&now_wall_clock, NULL);
-  struct tm local_time = (const struct tm){ 0 };
+  struct tm local_time = (const struct tm) { 0 };
   localtime_r(&now_wall_clock.tv_sec, &local_time);
   // There're two levels of formatting -- first most of the timestamp is printed
   // into |format|, then the resulting string is used to insert the microseconds
@@ -66,4 +66,8 @@
   return Timestamp(now_monotonic, wall_clock);
 }
 
+time_t Time::GetSecondsSinceEpoch() const {
+  return time(NULL);
+}
+
 }  // namespace shill
diff --git a/shill_time.h b/shill_time.h
index c6b4a38..d145dff 100644
--- a/shill_time.h
+++ b/shill_time.h
@@ -6,6 +6,7 @@
 #define SHILL_TIME_H_
 
 #include <sys/time.h>
+#include <time.h>
 
 #include <string>
 
@@ -18,7 +19,7 @@
 // be used for presenting the time in human-readable format. Note that the
 // monotonic clock does not necessarily advance during suspend.
 struct Timestamp {
-  Timestamp() : monotonic((const struct timeval){ 0 }) {}
+  Timestamp() : monotonic((const struct timeval) { 0 }) {}
   Timestamp(const struct timeval &in_monotonic,
             const std::string &in_wall_clock)
       : monotonic(in_monotonic),
@@ -44,6 +45,8 @@
   // Returns a snapshot of the current time.
   virtual Timestamp GetNow();
 
+  virtual time_t GetSecondsSinceEpoch() const;
+
  protected:
   Time();
 
diff --git a/wifi_provider.cc b/wifi_provider.cc
index 79a6c6d..7004a7e 100644
--- a/wifi_provider.cc
+++ b/wifi_provider.cc
@@ -12,7 +12,10 @@
 #include <vector>
 
 #include <base/bind.h>
+#include <base/format_macros.h>
 #include <base/string_number_conversions.h>
+#include <base/string_split.h>
+#include <base/string_util.h>
 
 #include "shill/error.h"
 #include "shill/event_dispatcher.h"
@@ -22,12 +25,14 @@
 #include "shill/manager.h"
 #include "shill/metrics.h"
 #include "shill/profile.h"
+#include "shill/shill_time.h"
 #include "shill/store_interface.h"
 #include "shill/technology.h"
 #include "shill/wifi_endpoint.h"
 #include "shill/wifi_service.h"
 
 using base::Bind;
+using base::SplitString;
 using std::set;
 using std::string;
 using std::vector;
@@ -45,9 +50,15 @@
     "security mode is unsupported";
 const char WiFiProvider::kManagerErrorUnsupportedServiceMode[] =
     "service mode is unsupported";
-const char WiFiProvider::kFrequencyDelimiter[] = ":";
+const char WiFiProvider::kFrequencyDelimiter = ':';
+const char WiFiProvider::kStartWeekHeader[] = "@";
+const time_t WiFiProvider::kIllegalStartWeek =
+    std::numeric_limits<time_t>::max();
 const char WiFiProvider::kStorageId[] = "provider_of_wifi";
 const char WiFiProvider::kStorageFrequencies[] = "Frequencies";
+const int WiFiProvider::kMaxStorageFrequencies = 20;
+const time_t WiFiProvider::kWeeksToKeepFrequencyCounts = 3;
+const time_t WiFiProvider::kSecondsPerWeek = 60 * 60 * 24 * 7;
 
 WiFiProvider::WiFiProvider(ControlInterface *control_interface,
                            EventDispatcher *dispatcher,
@@ -58,7 +69,8 @@
       metrics_(metrics),
       manager_(manager),
       running_(false),
-      total_frequency_connections_(-1L) {}
+      total_frequency_connections_(-1L),
+      time_(Time::GetInstance()) {}
 
 WiFiProvider::~WiFiProvider() {}
 
@@ -327,26 +339,61 @@
   // default profile except for autotests where a test_profile is pushed.  This
   // may need to be modified for that case.
   if (is_default_profile) {
-    vector<string> frequencies;
-    if (storage->GetStringList(kStorageId,
-                               kStorageFrequencies,
-                               &frequencies)) {
-      StringListToFrequencyMap(frequencies, &connect_count_by_frequency_);
-      total_frequency_connections_ = 0L;
-      WiFiProvider::ConnectFrequencyMap::const_iterator i;
-      for (i = connect_count_by_frequency_.begin();
-           i != connect_count_by_frequency_.end();
-           ++i) {
-        total_frequency_connections_ += i->second;
+    COMPILE_ASSERT(kMaxStorageFrequencies > kWeeksToKeepFrequencyCounts,
+                   persistently_storing_more_frequencies_than_we_can_hold);
+    total_frequency_connections_ = 0L;
+    connect_count_by_frequency_.clear();
+    time_t this_week = time_->GetSecondsSinceEpoch() / kSecondsPerWeek;
+    for (int freq = 0; freq < kMaxStorageFrequencies; ++freq) {
+      ConnectFrequencyMap connect_count_by_frequency;
+      string freq_string = StringPrintf("%s%d", kStorageFrequencies, freq);
+      vector<string> frequencies;
+      if (!storage->GetStringList(kStorageId, freq_string, &frequencies)) {
+        SLOG(WiFi, 7) << "Frequency list " << freq_string << " not found";
+        break;
+      }
+      time_t start_week = StringListToFrequencyMap(frequencies,
+                                                  &connect_count_by_frequency);
+      if (start_week == kIllegalStartWeek) {
+        continue;  // |StringListToFrequencyMap| will have output an error msg.
+      }
+
+      if (start_week > this_week) {
+        LOG(WARNING) << "Discarding frequency count info from the future";
+        continue;
+      }
+      connect_count_by_frequency_dated_[start_week] =
+          connect_count_by_frequency;
+
+      for (const auto &freq_count :
+           connect_count_by_frequency_dated_[start_week]) {
+        connect_count_by_frequency_[freq_count.first] += freq_count.second;
+        total_frequency_connections_ += freq_count.second;
       }
     }
+    SLOG(WiFi, 7) << __func__ << " - total count="
+                  << total_frequency_connections_;
   }
 }
 
 bool WiFiProvider::Save(StoreInterface *storage) const {
-  vector<string> frequencies;
-  FrequencyMapToStringList(connect_count_by_frequency_, &frequencies);
-  storage->SetStringList(kStorageId, kStorageFrequencies, frequencies);
+  int freq = 0;
+  // Iterating backwards since I want to make sure that I get the newest data.
+  ConnectFrequencyMapDated::const_reverse_iterator freq_count;
+  for (freq_count = connect_count_by_frequency_dated_.crbegin();
+       freq_count != connect_count_by_frequency_dated_.crend();
+       ++freq_count) {
+    vector<string> frequencies;
+    FrequencyMapToStringList(freq_count->first, freq_count->second,
+                             &frequencies);
+    string freq_string = StringPrintf("%s%d", kStorageFrequencies, freq);
+    storage->SetStringList(kStorageId, freq_string, frequencies);
+    if (++freq >= kMaxStorageFrequencies) {
+      LOG(WARNING) << "Internal frequency count list has more entries than the "
+                   << "string list we had allocated for it.";
+      break;
+    }
+  }
   return true;
 }
 
@@ -464,68 +511,106 @@
 }
 
 // static
-void WiFiProvider::StringListToFrequencyMap(const vector<string> &strings,
+time_t WiFiProvider::StringListToFrequencyMap(const vector<string> &strings,
                                             ConnectFrequencyMap *numbers) {
   if (!numbers) {
     LOG(ERROR) << "Null |numbers| parameter";
-    return;
+    return kIllegalStartWeek;
   }
 
-  vector<string>::const_iterator i;
-  for (i = strings.begin(); i != strings.end(); ++i) {
-    size_t delimiter = i->find(kFrequencyDelimiter);
-    if (delimiter == i->npos) {
-      LOG(WARNING) << "Found no '" << kFrequencyDelimiter << "' in '"
-                   << *i << "'";
-      continue;
-    }
-    uint16 freq = atoi(i->c_str());
-    uint64 connections = atoll(i->c_str() + delimiter + 1);
-    (*numbers)[freq] = connections;
+  // Extract the start week from the first string.
+  vector<string>::const_iterator strings_it = strings.begin();
+  if (strings_it == strings.end()) {
+    SLOG(WiFi, 7) << "Empty |strings|.";
+    return kIllegalStartWeek;
   }
+  time_t start_week = GetStringListStartWeek(*strings_it);
+  if (start_week == kIllegalStartWeek) {
+    return kIllegalStartWeek;
+  }
+
+  // Extract the frequency:count values from the remaining strings.
+  for (++strings_it; strings_it != strings.end(); ++strings_it) {
+    ParseStringListFreqCount(*strings_it, numbers);
+  }
+  return start_week;
 }
 
 // static
-void WiFiProvider::FrequencyMapToStringList(const ConnectFrequencyMap &numbers,
+time_t WiFiProvider::GetStringListStartWeek(const string &week_string) {
+  if (!StartsWithASCII(week_string, kStartWeekHeader, false)) {
+    LOG(ERROR) << "Found no leading '" << kStartWeekHeader << "' in '"
+               << week_string << "'";
+    return kIllegalStartWeek;
+  }
+  return atoll(week_string.c_str() + 1);
+}
+
+// static
+void WiFiProvider::ParseStringListFreqCount(const string &freq_count_string,
+                                            ConnectFrequencyMap *numbers) {
+  vector<string> freq_count;
+  SplitString(freq_count_string, kFrequencyDelimiter, &freq_count);
+  if (freq_count.size() != 2) {
+    LOG(WARNING) << "Found " << freq_count.size() - 1 << " '"
+                 << kFrequencyDelimiter << "' in '" << freq_count_string
+                 << "'.  Expected 1.";
+    return;
+  }
+  uint16 freq = atoi(freq_count[0].c_str());
+  uint64 connections = atoll(freq_count[1].c_str());
+  (*numbers)[freq] = connections;
+}
+
+// static
+void WiFiProvider::FrequencyMapToStringList(time_t start_week,
+                                            const ConnectFrequencyMap &numbers,
                                             vector<string> *strings) {
   if (!strings) {
     LOG(ERROR) << "Null |strings| parameter";
     return;
   }
 
-  ConnectFrequencyMap::const_iterator i;
-  for (i = numbers.begin(); i != numbers.end(); ++i) {
+  strings->push_back(StringPrintf("%s%" PRIu64, kStartWeekHeader,
+                                  static_cast<uint64_t>(start_week)));
+
+  for (const auto &freq_conn : numbers) {
     // Use base::Int64ToString() instead of using something like "%llu"
     // (not correct for native 64 bit architectures) or PRId64 (does not
     // work correctly using cros_workon_make due to include intricacies).
-    string result = StringPrintf("%u%s%s", i->first, kFrequencyDelimiter,
-                                  base::Int64ToString(i->second).c_str());
-    strings->push_back(result);
+    strings->push_back(StringPrintf("%u%c%s",
+        freq_conn.first, kFrequencyDelimiter,
+        base::Int64ToString(freq_conn.second).c_str()));
   }
 }
 
 void WiFiProvider::IncrementConnectCount(uint16 frequency_mhz) {
-  // 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): 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()) {
-    LOG(ERROR) << "Shill has logged " << total_frequency_connections_
-               << " connections -- must be an error.  Resetting connection "
-               << "count.";
-    connect_count_by_frequency_.clear();
-  }
+  CHECK(total_frequency_connections_ < std::numeric_limits<int64_t>::max());
 
-  int64 previous_value = 0;
-  if (ContainsKey(connect_count_by_frequency_, frequency_mhz)) {
-    previous_value = connect_count_by_frequency_[frequency_mhz];
-  }
-
-  connect_count_by_frequency_[frequency_mhz] = ++previous_value;
+  ++connect_count_by_frequency_[frequency_mhz];
   ++total_frequency_connections_;
-  manager_->UpdateWiFiProvider();
 
+  time_t this_week = time_->GetSecondsSinceEpoch() / kSecondsPerWeek;
+  ++connect_count_by_frequency_dated_[this_week][frequency_mhz];
+
+  ConnectFrequencyMapDated::iterator oldest =
+      connect_count_by_frequency_dated_.begin();
+  time_t oldest_legal_week = this_week - kWeeksToKeepFrequencyCounts;
+  while (oldest->first < oldest_legal_week) {
+    SLOG(WiFi, 7) << "Discarding frequency count info that's "
+                  << this_week - oldest->first << " weeks old";
+    for (const auto &freq_count : oldest->second) {
+      connect_count_by_frequency_[freq_count.first] -= freq_count.second;
+      if (connect_count_by_frequency_[freq_count.first] <= 0) {
+        connect_count_by_frequency_.erase(freq_count.first);
+      }
+      total_frequency_connections_ -= freq_count.second;
+    }
+    connect_count_by_frequency_dated_.erase(oldest);
+    oldest = connect_count_by_frequency_dated_.begin();
+  }
+
+  manager_->UpdateWiFiProvider();
   metrics_->SendToUMA(
       Metrics::kMetricFrequenciesConnectedEver,
       connect_count_by_frequency_.size(),
diff --git a/wifi_provider.h b/wifi_provider.h
index 79e375c..b77ed96 100644
--- a/wifi_provider.h
+++ b/wifi_provider.h
@@ -5,6 +5,8 @@
 #ifndef SHILL_WIFI_PROVIDER_H_
 #define SHILL_WIFI_PROVIDER_H_
 
+#include <time.h>
+
 #include <deque>
 #include <map>
 
@@ -22,6 +24,7 @@
 class Manager;
 class Metrics;
 class StoreInterface;
+class Time;
 class WiFiEndpoint;
 class WiFiService;
 
@@ -30,7 +33,12 @@
 // (created due to user or storage configuration) Services.
 class WiFiProvider {
  public:
+  static const char kStorageFrequencies[];
+  static const int kMaxStorageFrequencies;
   typedef std::map<uint16, int64> ConnectFrequencyMap;
+  // The key to |ConnectFrequencyMapDated| is the number of days since the
+  // Epoch.
+  typedef std::map<time_t, ConnectFrequencyMap> ConnectFrequencyMapDated;
   struct FrequencyCount {
     FrequencyCount() : frequency(0), connection_count(0) {}
     FrequencyCount(uint16 freq, size_t conn)
@@ -111,10 +119,16 @@
 
  private:
   friend class WiFiProviderTest;
+  FRIEND_TEST(WiFiProviderTest, FrequencyMapAgingIllegalDay);
+  FRIEND_TEST(WiFiProviderTest, FrequencyMapBasicAging);
   FRIEND_TEST(WiFiProviderTest, FrequencyMapToStringList);
+  FRIEND_TEST(WiFiProviderTest, FrequencyMapToStringListEmpty);
+  FRIEND_TEST(WiFiProviderTest, IncrementConnectCount);
+  FRIEND_TEST(WiFiProviderTest, IncrementConnectCountCreateNew);
   FRIEND_TEST(WiFiProviderTest, LoadAndFixupServiceEntries);
   FRIEND_TEST(WiFiProviderTest, LoadAndFixupServiceEntriesNothingToDo);
   FRIEND_TEST(WiFiProviderTest, StringListToFrequencyMap);
+  FRIEND_TEST(WiFiProviderTest, StringListToFrequencyMapEmpty);
 
   typedef std::map<const WiFiEndpoint *, WiFiServiceRefPtr> EndpointServiceMap;
 
@@ -123,9 +137,12 @@
   static const char kManagerErrorSSIDRequired[];
   static const char kManagerErrorUnsupportedSecurityMode[];
   static const char kManagerErrorUnsupportedServiceMode[];
-  static const char kFrequencyDelimiter[];
+  static const char kFrequencyDelimiter;
+  static const char kStartWeekHeader[];
+  static const time_t kIllegalStartWeek;
   static const char kStorageId[];
-  static const char kStorageFrequencies[];
+  static const time_t kWeeksToKeepFrequencyCounts;
+  static const time_t kSecondsPerWeek;
 
   // Add a service to the service_ vector and register it with the Manager.
   WiFiServiceRefPtr AddService(const std::vector<uint8_t> &ssid,
@@ -157,14 +174,29 @@
 
   // Converts frequency profile information from a list of strings of the form
   // "frequency:connection_count" to a form consistent with
-  // |connect_count_by_frequency_|
-  static void StringListToFrequencyMap(const std::vector<std::string> &strings,
+  // |connect_count_by_frequency_|.  The first string must be of the form
+  // [nnn] where |nnn| is a positive integer that represents the creation time
+  // (number of days since the Epoch) of the data.
+  static time_t StringListToFrequencyMap(
+      const std::vector<std::string> &strings,
+      ConnectFrequencyMap *numbers);
+
+  // Extracts the start week from the first string in the StringList for
+  // |StringListToFrequencyMap|.
+  static time_t GetStringListStartWeek(const std::string &week_string);
+
+  // Extracts frequency and connection count from a string from the StringList
+  // for |StringListToFrequencyMap|.  Places those values in |numbers|.
+  static void ParseStringListFreqCount(const std::string &freq_count_string,
                                        ConnectFrequencyMap *numbers);
 
   // Converts frequency profile information from a form consistent with
   // |connect_count_by_frequency_| to a list of strings of the form
-  // "frequency:connection_count"
-  static void FrequencyMapToStringList(const ConnectFrequencyMap &numbers,
+  // "frequency:connection_count".  The |creation_day| is the day that the
+  // data was first createed (represented as the number of days since the
+  // Epoch).
+  static void FrequencyMapToStringList(time_t creation_day,
+                                       const ConnectFrequencyMap &numbers,
                                        std::vector<std::string> *strings);
 
   ControlInterface *control_interface_;
@@ -181,10 +213,14 @@
   // successful connection has been made at that frequency.  Absent frequencies
   // have not had a successful connection.
   ConnectFrequencyMap connect_count_by_frequency_;
+  // A number of entries of |ConnectFrequencyMap| stored by date of creation.
+  ConnectFrequencyMapDated connect_count_by_frequency_dated_;
 
   // Count of successful wifi connections we've made.
   int64_t total_frequency_connections_;
 
+  Time *time_;
+
   DISALLOW_COPY_AND_ASSIGN(WiFiProvider);
 };
 
diff --git a/wifi_provider_unittest.cc b/wifi_provider_unittest.cc
index 8ff8f26..64aaf13 100644
--- a/wifi_provider_unittest.cc
+++ b/wifi_provider_unittest.cc
@@ -4,33 +4,39 @@
 
 #include "shill/wifi_provider.h"
 
+#include <map>
+#include <set>
+#include <string>
+#include <vector>
+
+#include <base/format_macros.h>
 #include <base/string_number_conversions.h>
 #include <base/string_util.h>
 #include <base/stringprintf.h>
 #include <chromeos/dbus/service_constants.h>
 #include <gtest/gtest.h>
 
-#include <set>
-#include <string>
-#include <vector>
-
 #include "shill/ieee80211.h"
 #include "shill/mock_event_dispatcher.h"
 #include "shill/mock_manager.h"
 #include "shill/mock_metrics.h"
 #include "shill/mock_profile.h"
 #include "shill/mock_store.h"
+#include "shill/mock_time.h"
 #include "shill/mock_wifi_service.h"
 #include "shill/nice_mock_control.h"
 #include "shill/technology.h"
 #include "shill/wifi_endpoint.h"
 #include "shill/wpa_supplicant.h"
 
+using std::map;
 using std::set;
 using std::string;
 using std::vector;
 using ::testing::_;
 using ::testing::ContainerEq;
+using ::testing::Eq;
+using ::testing::Invoke;
 using ::testing::Mock;
 using ::testing::NiceMock;
 using ::testing::Return;
@@ -39,6 +45,15 @@
 
 namespace shill {
 
+namespace {
+
+const time_t kFirstWeek = 50;
+const char kIllegalDayProfile[] = "IllegalDay";
+const time_t kSecondsPerWeek = 60 * 60 * 24 * 7;
+const time_t kTestDays = 20;
+
+}  // namespace
+
 class WiFiProviderTest : public testing::Test {
  public:
   WiFiProviderTest()
@@ -48,7 +63,33 @@
         provider_(&control_, &dispatcher_, &metrics_, &manager_),
         profile_(
             new NiceMock<MockProfile>(&control_, &metrics_, &manager_, "")),
-        storage_entry_index_(0) {}
+        storage_entry_index_(0) {
+    provider_.time_ = &time_;
+
+    string freq_string = StringPrintf("%s%d", WiFiProvider::kStorageFrequencies,
+                                      0);
+    profile_frequency_data_[freq_string].push_back(
+        base::StringPrintf("@%" PRIu64, static_cast<uint64_t>(kFirstWeek)));
+    profile_frequency_data_[freq_string].push_back("5001:1");
+    profile_frequency_data_[freq_string].push_back("5002:2");
+
+    freq_string = StringPrintf("%s%d", WiFiProvider::kStorageFrequencies, 1);
+    profile_frequency_data_[freq_string].push_back(
+        base::StringPrintf("@%" PRIu64, static_cast<uint64_t>(kFirstWeek) + 1));
+    // Overlap one entry with previous block.
+    profile_frequency_data_[freq_string].push_back("5001:1");
+    profile_frequency_data_[freq_string].push_back("6001:1");
+    profile_frequency_data_[freq_string].push_back("6002:2");
+
+    freq_string = StringPrintf("%s%d", WiFiProvider::kStorageFrequencies, 2);
+    profile_frequency_data_[freq_string].push_back(
+        base::StringPrintf("@%" PRIu64, static_cast<uint64_t>(kFirstWeek) + 2));
+    profile_frequency_data_[freq_string].push_back("7001:1");
+    profile_frequency_data_[freq_string].push_back("7002:2");
+
+    profile_frequency_data_[kIllegalDayProfile].push_back("7001:1");
+    profile_frequency_data_[kIllegalDayProfile].push_back("7002:2");
+  }
 
   virtual ~WiFiProviderTest() {}
 
@@ -56,6 +97,32 @@
     EXPECT_CALL(*profile_, GetConstStorage()).WillRepeatedly(Return(&storage_));
   }
 
+  bool GetStringList(const std::string &/*group*/,
+                     const std::string &key,
+                     std::vector<std::string> *value) {
+    if (!value) {
+      return false;
+    }
+    if (ContainsKey(profile_frequency_data_, key)) {
+      *value = profile_frequency_data_[key];
+      return true;
+    }
+    return false;
+  }
+
+  bool GetIllegalDayStringList(const std::string &/*group*/,
+                               const std::string &key,
+                               std::vector<std::string> *value) {
+    if (!value) {
+      return false;
+    }
+    if (ContainsKey(profile_frequency_data_, key)) {
+      *value = profile_frequency_data_[kIllegalDayProfile];
+      return true;
+    }
+    return false;
+  }
+
  protected:
   typedef scoped_refptr<MockWiFiService> MockWiFiServiceRefPtr;
 
@@ -67,6 +134,10 @@
     provider_.LoadAndFixupServiceEntries(&storage_, is_default_profile);
   }
 
+  void Save() {
+    provider_.Save(&storage_);
+  }
+
   const vector<WiFiServiceRefPtr> GetServices() {
     return provider_.services_;
   }
@@ -202,7 +273,7 @@
     // are also provided, here, in sorted order to match the frequency map
     // (iterators for which will provide them in frequency-sorted order).
     static const char *kStrings[] = {
-      "5180:14", "5240:16", "5745:7", "5765:4", "5785:14", "5805:5"
+      "@20", "5180:14", "5240:16", "5745:7", "5765:4", "5785:14", "5805:5"
     };
     if (!strings) {
       LOG(ERROR) << "NULL |strings|.";
@@ -235,9 +306,41 @@
     }
   }
 
+  void LoadConnectCountByFrequency(time_t today_seconds) {
+    provider_.time_ = &time_;
+    EXPECT_CALL(time_, GetSecondsSinceEpoch()).WillOnce(Return(today_seconds));
+    const string kGroupId =
+        StringPrintf("%s_0_0_%s_%s",
+                     flimflam::kTypeWifi,
+                     flimflam::kModeManaged,
+                     flimflam::kSecurityNone);
+    EXPECT_CALL(storage_,
+                GetString(kGroupId, _, _)).WillRepeatedly(Return(true));
+    set<string> groups;
+    groups.insert(kGroupId);
+    EXPECT_CALL(storage_, GetGroups()).WillOnce(Return(groups));
+    // Provide data for block[0] through block[2] (block[3] is empty).
+    EXPECT_CALL(storage_, GetStringList(WiFiProvider::kStorageId,
+        StringPrintf("%s%d", WiFiProvider::kStorageFrequencies, 0), _)).
+        WillOnce(Invoke(this, &WiFiProviderTest::GetStringList));
+    EXPECT_CALL(storage_, GetStringList(WiFiProvider::kStorageId,
+        StringPrintf("%s%d", WiFiProvider::kStorageFrequencies, 1), _)).
+        WillOnce(Invoke(this, &WiFiProviderTest::GetStringList));
+    EXPECT_CALL(storage_, GetStringList(WiFiProvider::kStorageId,
+        StringPrintf("%s%d", WiFiProvider::kStorageFrequencies, 2), _)).
+        WillOnce(Invoke(this, &WiFiProviderTest::GetStringList));
+    EXPECT_CALL(storage_, GetStringList(WiFiProvider::kStorageId,
+        StringPrintf("%s%d", WiFiProvider::kStorageFrequencies, 3), _)).
+        WillOnce(Invoke(this, &WiFiProviderTest::GetStringList));
+
+    LoadAndFixupServiceEntries(true);
+  }
+
+  map<string, vector<string>> profile_frequency_data_;
   NiceMockControl control_;
   MockEventDispatcher dispatcher_;
   MockMetrics metrics_;
+  MockTime time_;
   StrictMock<MockManager> manager_;
   WiFiProvider provider_;
   scoped_refptr<MockProfile> profile_;
@@ -1018,8 +1121,17 @@
   groups.insert(kGroupId);
   EXPECT_CALL(storage_, GetGroups()).WillRepeatedly(Return(groups));
   EXPECT_CALL(storage_, GetStringList(WiFiProvider::kStorageId,
-                                      WiFiProvider::kStorageFrequencies, _))
-      .WillOnce(Return(true));
+      StringPrintf("%s%d", WiFiProvider::kStorageFrequencies, 0), _)).
+      WillOnce(Return(true));
+  EXPECT_CALL(storage_, GetStringList(WiFiProvider::kStorageId,
+      StringPrintf("%s%d", WiFiProvider::kStorageFrequencies, 1), _)).
+      WillOnce(Return(true));
+  EXPECT_CALL(storage_, GetStringList(WiFiProvider::kStorageId,
+      StringPrintf("%s%d", WiFiProvider::kStorageFrequencies, 2), _)).
+      WillOnce(Return(true));
+  EXPECT_CALL(storage_, GetStringList(WiFiProvider::kStorageId,
+      StringPrintf("%s%d", WiFiProvider::kStorageFrequencies, 3), _)).
+      WillOnce(Return(false));
   LoadAndFixupServiceEntries(true);
   Mock::VerifyAndClearExpectations(&metrics_);
 
@@ -1046,8 +1158,17 @@
   groups.insert(kGroupId);
   EXPECT_CALL(storage_, GetGroups()).WillOnce(Return(groups));
   EXPECT_CALL(storage_, GetStringList(WiFiProvider::kStorageId,
-                                      WiFiProvider::kStorageFrequencies, _))
-      .WillOnce(Return(true));
+      StringPrintf("%s%d", WiFiProvider::kStorageFrequencies, 0), _)).
+      WillOnce(Return(true));
+  EXPECT_CALL(storage_, GetStringList(WiFiProvider::kStorageId,
+      StringPrintf("%s%d", WiFiProvider::kStorageFrequencies, 1), _)).
+      WillOnce(Return(true));
+  EXPECT_CALL(storage_, GetStringList(WiFiProvider::kStorageId,
+      StringPrintf("%s%d", WiFiProvider::kStorageFrequencies, 2), _)).
+      WillOnce(Return(true));
+  EXPECT_CALL(storage_, GetStringList(WiFiProvider::kStorageId,
+      StringPrintf("%s%d", WiFiProvider::kStorageFrequencies, 3), _)).
+      WillOnce(Return(false));
   LoadAndFixupServiceEntries(true);
 }
 
@@ -1105,22 +1226,199 @@
   vector<string> strings;
   BuildFreqCountStrings(&strings);
   WiFiProvider::ConnectFrequencyMap frequencies_result;
-  WiFiProvider::StringListToFrequencyMap(strings, &frequencies_result);
+  time_t days = WiFiProvider::StringListToFrequencyMap(strings,
+                                                       &frequencies_result);
 
   WiFiProvider::ConnectFrequencyMap frequencies_expect;
   BuildFreqCountMap(&frequencies_expect);
   EXPECT_THAT(frequencies_result, ContainerEq(frequencies_expect));
+  EXPECT_EQ(days, kTestDays);
+}
+
+TEST_F(WiFiProviderTest, StringListToFrequencyMapEmpty) {
+  vector<string> strings;
+  strings.push_back("@50");
+  WiFiProvider::ConnectFrequencyMap frequencies_result;
+  time_t days = WiFiProvider::StringListToFrequencyMap(strings,
+                                                       &frequencies_result);
+  EXPECT_TRUE(frequencies_result.empty());
+  EXPECT_EQ(days, 50);
 }
 
 TEST_F(WiFiProviderTest, FrequencyMapToStringList) {
   WiFiProvider::ConnectFrequencyMap frequencies;
   BuildFreqCountMap(&frequencies);
   vector<string> strings_result;
-  WiFiProvider::FrequencyMapToStringList(frequencies, &strings_result);
+  WiFiProvider::FrequencyMapToStringList(kTestDays, frequencies,
+                                         &strings_result);
 
   vector<string> strings_expect;
   BuildFreqCountStrings(&strings_expect);
   EXPECT_THAT(strings_result, ContainerEq(strings_expect));
 }
 
+TEST_F(WiFiProviderTest, FrequencyMapToStringListEmpty) {
+  WiFiProvider::ConnectFrequencyMap frequencies;
+  vector<string> strings_result;
+  WiFiProvider::FrequencyMapToStringList(kTestDays, frequencies,
+                                         &strings_result);
+  EXPECT_EQ(1, strings_result.size());
+  EXPECT_EQ(*strings_result.begin(), "@20");
+}
+
+TEST_F(WiFiProviderTest, FrequencyMapBasicAging) {
+  const time_t kThisWeek = kFirstWeek +
+      WiFiProvider::kWeeksToKeepFrequencyCounts - 1;
+  LoadConnectCountByFrequency(kThisWeek * kSecondsPerWeek);
+
+  // Make sure we have data for all 3 blocks.
+  WiFiProvider::ConnectFrequencyMap expected;
+  expected[5001] = 2;
+  expected[5002] = 2;
+  expected[6001] = 1;
+  expected[6002] = 2;
+  expected[7001] = 1;
+  expected[7002] = 2;
+  EXPECT_THAT(provider_.connect_count_by_frequency_, ContainerEq(expected));
+
+  // And, then, make sure we output the expected blocks of data.
+  EXPECT_CALL(storage_, SetStringList(WiFiProvider::kStorageId, _,
+      Eq(profile_frequency_data_[
+         StringPrintf("%s%d", WiFiProvider::kStorageFrequencies, 0)])));
+  EXPECT_CALL(storage_, SetStringList(WiFiProvider::kStorageId, _,
+      Eq(profile_frequency_data_[
+         StringPrintf("%s%d", WiFiProvider::kStorageFrequencies, 1)])));
+  EXPECT_CALL(storage_, SetStringList(WiFiProvider::kStorageId, _,
+      Eq(profile_frequency_data_[
+         StringPrintf("%s%d", WiFiProvider::kStorageFrequencies, 2)])));
+  vector<string> frequencies;
+  frequencies.push_back(base::StringPrintf("@%" PRIu64,
+                                           static_cast<uint64_t>(kThisWeek)));
+  EXPECT_CALL(storage_, SetStringList(WiFiProvider::kStorageId, _,
+                                      Eq(frequencies))).Times(0);
+  Save();
+}
+
+TEST_F(WiFiProviderTest, FrequencyMapAgingIllegalDay) {
+  provider_.time_ = &time_;
+  const time_t kThisWeek = kFirstWeek +
+      WiFiProvider::kWeeksToKeepFrequencyCounts - 1;
+  const time_t kThisWeekSeconds = kThisWeek * kSecondsPerWeek;
+  EXPECT_CALL(time_, GetSecondsSinceEpoch()).WillOnce(Return(kThisWeekSeconds));
+  const string kGroupId =
+      StringPrintf("%s_0_0_%s_%s",
+                   flimflam::kTypeWifi,
+                   flimflam::kModeManaged,
+                   flimflam::kSecurityNone);
+  EXPECT_CALL(storage_,
+              GetString(kGroupId, _, _)).WillRepeatedly(Return(true));
+  set<string> groups;
+  groups.insert(kGroupId);
+  // Instead of block[1], return a block without the date.
+  EXPECT_CALL(storage_, GetGroups()).WillOnce(Return(groups));
+  EXPECT_CALL(storage_, GetStringList(WiFiProvider::kStorageId,
+      StringPrintf("%s%d", WiFiProvider::kStorageFrequencies, 0), _)).
+      WillOnce(Invoke(this, &WiFiProviderTest::GetStringList));
+  EXPECT_CALL(storage_, GetStringList(WiFiProvider::kStorageId,
+      StringPrintf("%s%d", WiFiProvider::kStorageFrequencies, 1), _)).
+      WillOnce(Invoke(this, &WiFiProviderTest::GetIllegalDayStringList));
+  EXPECT_CALL(storage_, GetStringList(WiFiProvider::kStorageId,
+      StringPrintf("%s%d", WiFiProvider::kStorageFrequencies, 2), _)).
+      WillOnce(Invoke(this, &WiFiProviderTest::GetStringList));
+  EXPECT_CALL(storage_, GetStringList(WiFiProvider::kStorageId,
+      StringPrintf("%s%d", WiFiProvider::kStorageFrequencies, 3), _)).
+      WillOnce(Invoke(this, &WiFiProviderTest::GetStringList));
+
+  LoadAndFixupServiceEntries(true);
+
+  // Verify that the received information only includes block[0] and block[2].
+  WiFiProvider::ConnectFrequencyMap expected;
+  expected[5001] = 1;
+  expected[5002] = 2;
+  expected[7001] = 1;
+  expected[7002] = 2;
+  EXPECT_THAT(provider_.connect_count_by_frequency_, ContainerEq(expected));
+
+  // And, then, make sure we output the expected blocks of data.
+  EXPECT_CALL(storage_, SetStringList(WiFiProvider::kStorageId, _,
+      Eq(profile_frequency_data_[
+         StringPrintf("%s%d", WiFiProvider::kStorageFrequencies, 0)])));
+  EXPECT_CALL(storage_, SetStringList(WiFiProvider::kStorageId, _,
+      Eq(profile_frequency_data_[
+         StringPrintf("%s%d", WiFiProvider::kStorageFrequencies, 1)]))).
+      Times(0);
+  EXPECT_CALL(storage_, SetStringList(WiFiProvider::kStorageId, _,
+      Eq(profile_frequency_data_[
+         StringPrintf("%s%d", WiFiProvider::kStorageFrequencies, 2)])));
+  vector<string> frequencies;
+  frequencies.push_back(base::StringPrintf("@%" PRIu64,
+                                           static_cast<uint64_t>(kThisWeek)));
+  EXPECT_CALL(storage_, SetStringList(WiFiProvider::kStorageId, _,
+                                      Eq(frequencies))).Times(0);
+  Save();
+}
+
+TEST_F(WiFiProviderTest, IncrementConnectCount) {
+  const time_t kThisWeek = kFirstWeek +
+      WiFiProvider::kWeeksToKeepFrequencyCounts - 1;
+  const time_t kThisWeekSeconds = kThisWeek * kSecondsPerWeek;
+  LoadConnectCountByFrequency(kThisWeekSeconds);
+
+  EXPECT_CALL(time_, GetSecondsSinceEpoch()).WillOnce(Return(kThisWeekSeconds));
+  EXPECT_CALL(manager_, UpdateWiFiProvider());
+  EXPECT_CALL(metrics_, SendToUMA(_, _, _, _, _));
+  time_t newest_week_at_start =
+      provider_.connect_count_by_frequency_dated_.crbegin()->first;
+  provider_.IncrementConnectCount(6002);
+
+  // Make sure we have data for all 3 blocks.
+  WiFiProvider::ConnectFrequencyMap expected;
+  expected[5001] = 2;
+  expected[5002] = 2;
+  expected[6001] = 1;
+  expected[6002] = 3;
+  expected[7001] = 1;
+  expected[7002] = 2;
+  EXPECT_THAT(provider_.connect_count_by_frequency_, ContainerEq(expected));
+  // Make sure we didn't delete the oldest block.
+  EXPECT_TRUE(ContainsKey(provider_.connect_count_by_frequency_dated_,
+                          kFirstWeek));
+  // Make sure we didn't create a new block.
+  time_t newest_week_at_end =
+      provider_.connect_count_by_frequency_dated_.crbegin()->first;
+  EXPECT_EQ(newest_week_at_start, newest_week_at_end);
+}
+
+TEST_F(WiFiProviderTest, IncrementConnectCountCreateNew) {
+  time_t this_week = kFirstWeek + WiFiProvider::kWeeksToKeepFrequencyCounts - 1;
+  LoadConnectCountByFrequency(this_week * kSecondsPerWeek);
+
+  this_week += 2;
+  EXPECT_CALL(time_, GetSecondsSinceEpoch()).
+      WillOnce(Return(this_week * kSecondsPerWeek));
+  EXPECT_CALL(manager_, UpdateWiFiProvider());
+  EXPECT_CALL(metrics_, SendToUMA(_, _, _, _, _));
+  time_t newest_week_at_start =
+      provider_.connect_count_by_frequency_dated_.crbegin()->first;
+  provider_.IncrementConnectCount(6001);
+
+  // Make sure we have data for newest 2 blocks (only).
+  WiFiProvider::ConnectFrequencyMap expected;
+  expected[5001] = 1;
+  expected[6001] = 2;
+  expected[6002] = 2;
+  expected[7001] = 1;
+  expected[7002] = 2;
+  EXPECT_THAT(provider_.connect_count_by_frequency_, ContainerEq(expected));
+  // Verify that the oldest block is gone.
+  EXPECT_FALSE(ContainsKey(provider_.connect_count_by_frequency_dated_,
+                           kFirstWeek));
+  // Make sure we created a new block and that it is for the current week.
+  time_t newest_week_at_end =
+      provider_.connect_count_by_frequency_dated_.crbegin()->first;
+  EXPECT_NE(newest_week_at_start, newest_week_at_end);
+  EXPECT_TRUE(ContainsKey(provider_.connect_count_by_frequency_dated_,
+                          this_week));
+}
+
 }  // namespace shill