shill: add FrequencyList property to WiFiServices

The main intended change in this CL is to expose the known frequencies
for a WiFi network via D-Bus properties. This required adding a new
property type (array of uint16).

While there, though, I made some changes to the DBusAdaptor and
PropertyStore unit tests. These changes should make it easier to
identify and update the relevant unit tests when we add new property
types.

Specifically:
- add a GetProperties test to dbus_adaptor_unittest.cc
- add a GetProperty test to property_store_unittest.cc
- make the list of PropertyTypes accessible from outside property_store_unittest
- make the RegisterProperty shims accessible from outside property_store_unittest
- create GetProperty shims, and make them accessible as well

Additional changes:
- remove some unnecessary (as of c++11) whitespace between angle brackets
- make initialization of DBusAdaptorTest fields more uniform, by using
  uniform initialization syntax and initializer lists
- update service-api documentation for WiFi.Frequency

BUG=chromium:248791
TEST=unit tests, manual
CQ-DEPEND=CL:59119

Manual test
-----------
1. chrome://system
2. network-services -> Expand...
3. find GoogleGuest, check that it has at least one entry for
   "WiFi.FrequencyList". e.g., "WiFi.FrequencyList/0: 2414"

Change-Id: Ie3b4d89853e22b7c4e80ad017738223d9753dd7c
Reviewed-on: https://gerrit.chromium.org/gerrit/59149
Commit-Queue: mukesh agrawal <quiche@chromium.org>
Reviewed-by: mukesh agrawal <quiche@chromium.org>
Tested-by: mukesh agrawal <quiche@chromium.org>
diff --git a/accessor_interface.h b/accessor_interface.h
index 73fc6db..682695b 100644
--- a/accessor_interface.h
+++ b/accessor_interface.h
@@ -56,29 +56,31 @@
 typedef std::vector<std::string> Strings;
 typedef std::map<std::string, std::string> Stringmap;
 typedef std::vector<Stringmap> Stringmaps;
+typedef std::vector<uint16> Uint16s;
 
 // Using a smart pointer here allows pointers to classes derived from
 // AccessorInterface<> to be stored in maps and other STL container types.
-typedef std::tr1::shared_ptr<AccessorInterface<bool> > BoolAccessor;
-typedef std::tr1::shared_ptr<AccessorInterface<int16> > Int16Accessor;
-typedef std::tr1::shared_ptr<AccessorInterface<int32> > Int32Accessor;
+typedef std::tr1::shared_ptr<AccessorInterface<bool>> BoolAccessor;
+typedef std::tr1::shared_ptr<AccessorInterface<int16>> Int16Accessor;
+typedef std::tr1::shared_ptr<AccessorInterface<int32>> Int32Accessor;
 // See comment above RpcIdentifiers typedef, for the reason why the
 // RpcIdentifiersAccessor exists (even though it has the same
 // underlying type as StringsAccessor).
 typedef std::tr1::shared_ptr<
-  AccessorInterface<RpcIdentifier> > RpcIdentifierAccessor;
+    AccessorInterface<RpcIdentifier>> RpcIdentifierAccessor;
 typedef std::tr1::shared_ptr<
-  AccessorInterface<std::vector<std::string> > >RpcIdentifiersAccessor;
-typedef std::tr1::shared_ptr<AccessorInterface<std::string> > StringAccessor;
-typedef std::tr1::shared_ptr<AccessorInterface<Stringmap> > StringmapAccessor;
-typedef std::tr1::shared_ptr<AccessorInterface<Stringmaps> > StringmapsAccessor;
-typedef std::tr1::shared_ptr<AccessorInterface<Strings> > StringsAccessor;
+    AccessorInterface<std::vector<std::string>>>RpcIdentifiersAccessor;
+typedef std::tr1::shared_ptr<AccessorInterface<std::string>> StringAccessor;
+typedef std::tr1::shared_ptr<AccessorInterface<Stringmap>> StringmapAccessor;
+typedef std::tr1::shared_ptr<AccessorInterface<Stringmaps>> StringmapsAccessor;
+typedef std::tr1::shared_ptr<AccessorInterface<Strings>> StringsAccessor;
 typedef std::tr1::shared_ptr<
-  AccessorInterface<KeyValueStore> > KeyValueStoreAccessor;
-typedef std::tr1::shared_ptr<AccessorInterface<uint8> > Uint8Accessor;
-typedef std::tr1::shared_ptr<AccessorInterface<uint16> > Uint16Accessor;
-typedef std::tr1::shared_ptr<AccessorInterface<uint32> > Uint32Accessor;
-typedef std::tr1::shared_ptr<AccessorInterface<uint64> > Uint64Accessor;
+    AccessorInterface<KeyValueStore>> KeyValueStoreAccessor;
+typedef std::tr1::shared_ptr<AccessorInterface<uint8>> Uint8Accessor;
+typedef std::tr1::shared_ptr<AccessorInterface<uint16>> Uint16Accessor;
+typedef std::tr1::shared_ptr<AccessorInterface<Uint16s>> Uint16sAccessor;
+typedef std::tr1::shared_ptr<AccessorInterface<uint32>> Uint32Accessor;
+typedef std::tr1::shared_ptr<AccessorInterface<uint64>> Uint64Accessor;
 
 }  // namespace shill
 
diff --git a/adaptor_interfaces.h b/adaptor_interfaces.h
index 86a0d45..0136b0f 100644
--- a/adaptor_interfaces.h
+++ b/adaptor_interfaces.h
@@ -134,6 +134,8 @@
   virtual void EmitBoolChanged(const std::string &name, bool value) = 0;
   virtual void EmitUint8Changed(const std::string &name, uint8 value) = 0;
   virtual void EmitUint16Changed(const std::string &name, uint16 value) = 0;
+  virtual void EmitUint16sChanged(const std::string &name,
+                                  const Uint16s &value) = 0;
   virtual void EmitUintChanged(const std::string &name, uint32 value) = 0;
   virtual void EmitIntChanged(const std::string &name, int value) = 0;
   virtual void EmitRpcIdentifierChanged(const std::string &name,
diff --git a/dbus_adaptor.cc b/dbus_adaptor.cc
index 6e28833..a02fd3a 100644
--- a/dbus_adaptor.cc
+++ b/dbus_adaptor.cc
@@ -33,6 +33,7 @@
 const char DBusAdaptor::kStringmapSig[] = "a{ss}";
 const char DBusAdaptor::kStringmapsSig[] = "aa{ss}";
 const char DBusAdaptor::kStringsSig[] = "as";
+const char DBusAdaptor::kUint16sSig[] = "aq";
 
 DBusAdaptor::DBusAdaptor(DBus::Connection* conn, const string &object_path)
     : DBus::ObjectAdaptor(*conn, object_path) {
@@ -73,6 +74,8 @@
     ret = store->SetStringsProperty(name, value.operator vector<string>(), &e);
   else if (DBusAdaptor::IsUint16(value.signature()))
     ret = store->SetUint16Property(name, value.reader().get_uint16(), &e);
+  else if (DBusAdaptor::IsUint16s(value.signature()))
+    ret = store->SetUint16sProperty(name, value.operator vector<uint16>(), &e);
   else if (DBusAdaptor::IsUint32(value.signature()))
     ret = store->SetUint32Property(name, value.reader().get_uint32(), &e);
   else if (DBusAdaptor::IsUint64(value.signature()))
@@ -177,6 +180,13 @@
     }
   }
   {
+    ReadablePropertyConstIterator<Uint16s> it =
+        store.GetUint16sPropertiesIter();
+    for ( ; !it.AtEnd(); it.Advance()) {
+      (*out)[it.Key()] = Uint16sToVariant(it.value());
+    }
+  }
+  {
     ReadablePropertyConstIterator<uint32> it = store.GetUint32PropertiesIter();
     for ( ; !it.AtEnd(); it.Advance()) {
       (*out)[it.Key()] = Uint32ToVariant(it.value());
@@ -358,6 +368,14 @@
 }
 
 // static
+::DBus::Variant DBusAdaptor::Uint16sToVariant(const Uint16s &value) {
+  ::DBus::Variant v;
+  ::DBus::MessageIter writer = v.writer();
+  writer << value;
+  return v;
+}
+
+// static
 ::DBus::Variant DBusAdaptor::Uint32ToVariant(uint32 value) {
   ::DBus::Variant v;
   v.writer().append_uint32(value);
@@ -432,6 +450,11 @@
 }
 
 // static
+bool DBusAdaptor::IsUint16s(::DBus::Signature signature) {
+  return signature == DBusAdaptor::kUint16sSig;
+}
+
+// static
 bool DBusAdaptor::IsUint32(::DBus::Signature signature) {
   return signature == ::DBus::type<uint32>::sig();
 }
diff --git a/dbus_adaptor.h b/dbus_adaptor.h
index f7d2836..2b89fb1 100644
--- a/dbus_adaptor.h
+++ b/dbus_adaptor.h
@@ -77,6 +77,7 @@
   static ::DBus::Variant StringmapsToVariant(const Stringmaps &value);
   static ::DBus::Variant StringsToVariant(const Strings &value);
   static ::DBus::Variant Uint16ToVariant(uint16 value);
+  static ::DBus::Variant Uint16sToVariant(const Uint16s &value);
   static ::DBus::Variant Uint32ToVariant(uint32 value);
   static ::DBus::Variant Uint64ToVariant(uint64 value);
 
@@ -92,6 +93,7 @@
   static bool IsStringmaps(::DBus::Signature signature);
   static bool IsStrings(::DBus::Signature signature);
   static bool IsUint16(::DBus::Signature signature);
+  static bool IsUint16s(::DBus::Signature signature);
   static bool IsUint32(::DBus::Signature signature);
   static bool IsUint64(::DBus::Signature signature);
   static bool IsKeyValueStore(::DBus::Signature signature);
@@ -139,6 +141,7 @@
   static const char kStringmapSig[];
   static const char kStringmapsSig[];
   static const char kStringsSig[];
+  static const char kUint16sSig[];
 
   void MethodReplyCallback(const DBus::Tag *tag, const Error &error);
   void StringMethodReplyCallback(const DBus::Tag *tag, const Error &error,
diff --git a/dbus_adaptor_unittest.cc b/dbus_adaptor_unittest.cc
index b56bfa2..f879efc 100644
--- a/dbus_adaptor_unittest.cc
+++ b/dbus_adaptor_unittest.cc
@@ -41,22 +41,32 @@
   DBusAdaptorTest()
       : ex_bool_(true),
         ex_byte_(0xff),
+        ex_bytearrays_{ByteArray()},
         ex_uint16_(65535),
+        ex_uint16s_{ex_uint16_},
         ex_uint32_(2000000),
         ex_uint64_(8589934591LL),
         ex_int16_(-32768),
         ex_int32_(-65536),
         ex_path_("/"),
+        ex_paths_{ex_path_},
         ex_string_("something"),
+        ex_stringmap_{{ex_string_, ex_string_}},
+        ex_stringmaps_{ex_stringmap_},
         ex_strings_(1, ex_string_),
         bool_v_(DBusAdaptor::BoolToVariant(ex_bool_)),
         byte_v_(DBusAdaptor::ByteToVariant(ex_byte_)),
+        bytearrays_v_(DBusAdaptor::ByteArraysToVariant(ex_bytearrays_)),
         int16_v_(DBusAdaptor::Int16ToVariant(ex_int16_)),
         int32_v_(DBusAdaptor::Int32ToVariant(ex_int32_)),
         path_v_(DBusAdaptor::PathToVariant(ex_path_)),
+        paths_v_(DBusAdaptor::PathsToVariant(ex_paths_)),
         string_v_(DBusAdaptor::StringToVariant(ex_string_)),
+        stringmap_v_(DBusAdaptor::StringmapToVariant(ex_stringmap_)),
+        stringmaps_v_(DBusAdaptor::StringmapsToVariant(ex_stringmaps_)),
         strings_v_(DBusAdaptor::StringsToVariant(ex_strings_)),
         uint16_v_(DBusAdaptor::Uint16ToVariant(ex_uint16_)),
+        uint16s_v_(DBusAdaptor::Uint16sToVariant(ex_uint16s_)),
         uint32_v_(DBusAdaptor::Uint32ToVariant(ex_uint32_)),
         uint64_v_(DBusAdaptor::Uint64ToVariant(ex_uint64_)),
         device_(new MockDevice(control_interface(),
@@ -69,19 +79,7 @@
         service_(new MockService(control_interface(),
                                  dispatcher(),
                                  metrics(),
-                                 manager())) {
-    ex_bytearrays_.push_back(ByteArray());
-    bytearrays_v_ = DBusAdaptor::ByteArraysToVariant(ex_bytearrays_);
-
-    ex_stringmap_[ex_string_] = ex_string_;
-    stringmap_v_ = DBusAdaptor::StringmapToVariant(ex_stringmap_);
-
-    ex_stringmaps_.push_back(ex_stringmap_);
-    stringmaps_v_ = DBusAdaptor::StringmapsToVariant(ex_stringmaps_);
-
-    ex_paths_.push_back(ex_path_);
-    paths_v_ = DBusAdaptor::PathsToVariant(ex_paths_);
-  }
+                                 manager())) {}
 
   virtual ~DBusAdaptorTest() {}
 
@@ -90,6 +88,7 @@
   uint8 ex_byte_;
   ByteArrays ex_bytearrays_;
   uint16 ex_uint16_;
+  vector<uint16> ex_uint16s_;
   uint32 ex_uint32_;
   uint64 ex_uint64_;
   int16 ex_int16_;
@@ -113,6 +112,7 @@
   ::DBus::Variant stringmaps_v_;
   ::DBus::Variant strings_v_;
   ::DBus::Variant uint16_v_;
+  ::DBus::Variant uint16s_v_;
   ::DBus::Variant uint32_v_;
   ::DBus::Variant uint64_v_;
 
@@ -158,6 +158,8 @@
   EXPECT_THAT(
       ex_stringmaps_,
       ContainerEq(stringmaps_v_.operator vector<map<string, string> >()));
+  EXPECT_THAT(ex_uint16s_,
+              ContainerEq(uint16s_v_.operator vector<uint16>()));
 }
 
 TEST_F(DBusAdaptorTest, Signatures) {
@@ -172,6 +174,7 @@
   EXPECT_TRUE(DBusAdaptor::IsStringmap(stringmap_v_.signature()));
   EXPECT_TRUE(DBusAdaptor::IsStrings(strings_v_.signature()));
   EXPECT_TRUE(DBusAdaptor::IsUint16(uint16_v_.signature()));
+  EXPECT_TRUE(DBusAdaptor::IsUint16s(uint16s_v_.signature()));
   EXPECT_TRUE(DBusAdaptor::IsUint32(uint32_v_.signature()));
   EXPECT_TRUE(DBusAdaptor::IsUint64(uint64_v_.signature()));
 
@@ -179,9 +182,36 @@
   EXPECT_FALSE(DBusAdaptor::IsStrings(string_v_.signature()));
 }
 
+template <typename T>
+class DBusAdaptorTypedTest : public DBusAdaptorTest {
+ public:
+  DBusAdaptorTypedTest()
+      : property_(T()) // value-initialize primitives
+  {}
+
+ protected:
+  T *property() { return &property_; }
+
+ private:
+  T property_;
+};
+
+TYPED_TEST_CASE(DBusAdaptorTypedTest, PropertyStoreTest::PropertyTypes);
+
+TYPED_TEST(DBusAdaptorTypedTest, GetProperties) {
+  MockPropertyStore store;
+  const string kPropertyName("some property");
+  PropertyStoreTest::RegisterProperty(&store, kPropertyName, this->property());
+
+  map<string, ::DBus::Variant> properties;
+  ::DBus::Error error;
+  EXPECT_TRUE(DBusAdaptor::GetProperties(store, &properties, &error));
+  EXPECT_TRUE(ContainsKey(properties, kPropertyName));
+}
+
 TEST_F(DBusAdaptorTest, SetProperty) {
   MockPropertyStore store;
-  ::DBus::Error e1, e2, e3, e4, e5, e6, e7, e8, e9, e10, e11;
+  ::DBus::Error e1, e2, e3, e4, e5, e6, e7, e8, e9, e10, e11, e12;
 
   EXPECT_CALL(store, Contains(_)).WillRepeatedly(Return(true));
   EXPECT_CALL(store, SetBoolProperty("", _, _)).WillOnce(Return(true));
@@ -195,6 +225,7 @@
       .WillOnce(Return(true));
   EXPECT_CALL(store, SetUint8Property("", _, _)).WillOnce(Return(true));
   EXPECT_CALL(store, SetUint16Property("", _, _)).WillOnce(Return(true));
+  EXPECT_CALL(store, SetUint16sProperty("", _, _)).WillOnce(Return(true));
   EXPECT_CALL(store, SetUint32Property("", _, _)).WillOnce(Return(true));
   EXPECT_CALL(store, SetUint64Property("", _, _)).WillOnce(Return(true));
 
@@ -211,10 +242,11 @@
   EXPECT_TRUE(DBusAdaptor::SetProperty(&store, "", int16_v_, &e5));
   EXPECT_TRUE(DBusAdaptor::SetProperty(&store, "", int32_v_, &e6));
   EXPECT_TRUE(DBusAdaptor::SetProperty(&store, "", uint16_v_, &e7));
-  EXPECT_TRUE(DBusAdaptor::SetProperty(&store, "", uint32_v_, &e8));
-  EXPECT_TRUE(DBusAdaptor::SetProperty(&store, "", uint64_v_, &e9));
-  EXPECT_TRUE(DBusAdaptor::SetProperty(&store, "", stringmap_v_, &e10));
-  EXPECT_TRUE(DBusAdaptor::SetProperty(&store, "", byte_v_, &e11));
+  EXPECT_TRUE(DBusAdaptor::SetProperty(&store, "", uint16s_v_, &e8));
+  EXPECT_TRUE(DBusAdaptor::SetProperty(&store, "", uint32_v_, &e9));
+  EXPECT_TRUE(DBusAdaptor::SetProperty(&store, "", uint64_v_, &e10));
+  EXPECT_TRUE(DBusAdaptor::SetProperty(&store, "", stringmap_v_, &e11));
+  EXPECT_TRUE(DBusAdaptor::SetProperty(&store, "", byte_v_, &e12));
 }
 
 // SetProperty method should propagate failures.  This should happen
@@ -223,7 +255,7 @@
 // false, without setting an error.)
 TEST_F(DBusAdaptorTest, SetPropertyFailure) {
   MockPropertyStore store;
-  ::DBus::Error e1, e2, e3, e4, e5, e6, e7, e8, e9, e10, e11;
+  ::DBus::Error e1, e2, e3, e4, e5, e6, e7, e8, e9, e10, e11, e12;
 
   EXPECT_CALL(store, Contains(_)).WillRepeatedly(Return(false));
   EXPECT_CALL(store, SetBoolProperty("", _, _)).WillOnce(Return(false));
@@ -237,6 +269,7 @@
       .WillOnce(Return(false));
   EXPECT_CALL(store, SetUint8Property("", _, _)).WillOnce(Return(false));
   EXPECT_CALL(store, SetUint16Property("", _, _)).WillOnce(Return(false));
+  EXPECT_CALL(store, SetUint16sProperty("", _, _)).WillOnce(Return(false));
   EXPECT_CALL(store, SetUint32Property("", _, _)).WillOnce(Return(false));
   EXPECT_CALL(store, SetUint64Property("", _, _)).WillOnce(Return(false));
 
@@ -253,10 +286,11 @@
   EXPECT_FALSE(DBusAdaptor::SetProperty(&store, "", int16_v_, &e5));
   EXPECT_FALSE(DBusAdaptor::SetProperty(&store, "", int32_v_, &e6));
   EXPECT_FALSE(DBusAdaptor::SetProperty(&store, "", uint16_v_, &e7));
-  EXPECT_FALSE(DBusAdaptor::SetProperty(&store, "", uint32_v_, &e8));
-  EXPECT_FALSE(DBusAdaptor::SetProperty(&store, "", uint64_v_, &e9));
-  EXPECT_FALSE(DBusAdaptor::SetProperty(&store, "", stringmap_v_, &e10));
-  EXPECT_FALSE(DBusAdaptor::SetProperty(&store, "", byte_v_, &e11));
+  EXPECT_FALSE(DBusAdaptor::SetProperty(&store, "", uint16s_v_, &e8));
+  EXPECT_FALSE(DBusAdaptor::SetProperty(&store, "", uint32_v_, &e9));
+  EXPECT_FALSE(DBusAdaptor::SetProperty(&store, "", uint64_v_, &e10));
+  EXPECT_FALSE(DBusAdaptor::SetProperty(&store, "", stringmap_v_, &e11));
+  EXPECT_FALSE(DBusAdaptor::SetProperty(&store, "", byte_v_, &e12));
 }
 
 void SetError(const string &/*name*/, Error *error) {
diff --git a/doc/service-api.txt b/doc/service-api.txt
index 30361ea..749a66d 100644
--- a/doc/service-api.txt
+++ b/doc/service-api.txt
@@ -1312,7 +1312,22 @@
 		uint16 WiFi.Frequency [readonly]
 
 			(WiFi only) The operating frequency in MHz of
-			the network.
+			the Service. If the Service is connected, this
+			is the frequency on which it is connected.
+			Otherwise, this is the frequency of the best
+			available BSS (roughly, AP) for this Service.
+
+		array{uint16} WiFi.FrequencyList [readonly]
+
+			(WiFi only) The operating frequencies in MHz of
+			the Service. This lists all of the frequencies
+			where this Service has recently been seen.
+
+			This list is not necessarily complete, as:
+			a) not all scans check every channel,
+			b) an AP may not have responded in time, and
+			c) some scan results may have expired from the
+                           cache.
 
 		string WiFi.PhyMode [readonly]
 
diff --git a/mock_adaptors.h b/mock_adaptors.h
index 014370d..0a7557a 100644
--- a/mock_adaptors.h
+++ b/mock_adaptors.h
@@ -139,6 +139,8 @@
   MOCK_METHOD2(EmitBoolChanged, void(const std::string &name, bool value));
   MOCK_METHOD2(EmitUint8Changed, void(const std::string &name, uint8 value));
   MOCK_METHOD2(EmitUint16Changed, void(const std::string &name, uint16 value));
+  MOCK_METHOD2(EmitUint16sChanged, void(const std::string &name,
+                                        const Uint16s &value));
   MOCK_METHOD2(EmitUintChanged, void(const std::string &name, uint32 value));
   MOCK_METHOD2(EmitIntChanged, void(const std::string &name, int value));
   MOCK_METHOD2(EmitRpcIdentifierChanged,
diff --git a/mock_property_store.h b/mock_property_store.h
index c0b8984..04bee69 100644
--- a/mock_property_store.h
+++ b/mock_property_store.h
@@ -34,6 +34,8 @@
                                         Error*));
   MOCK_METHOD3(SetUint8Property, bool(const std::string&, uint8, Error*));
   MOCK_METHOD3(SetUint16Property, bool(const std::string&, uint16, Error*));
+  MOCK_METHOD3(SetUint16sProperty, bool(const std::string&,
+                                        const Uint16s &, Error*));
   MOCK_METHOD3(SetUint32Property, bool(const std::string&, uint32, Error*));
   MOCK_METHOD3(SetUint64Property, bool(const std::string&, uint64, Error*));
   MOCK_METHOD2(ClearProperty, bool(const std::string&, Error*));
diff --git a/property_store.cc b/property_store.cc
index df6359d..5620e6d 100644
--- a/property_store.cc
+++ b/property_store.cc
@@ -39,6 +39,7 @@
           ContainsKey(strings_properties_, prop) ||
           ContainsKey(uint8_properties_, prop) ||
           ContainsKey(uint16_properties_, prop) ||
+          ContainsKey(uint16s_properties_, prop) ||
           ContainsKey(uint32_properties_, prop) ||
           ContainsKey(uint64_properties_, prop) ||
           ContainsKey(rpc_identifier_properties_, prop) ||
@@ -115,6 +116,12 @@
   return GetProperty(name, value, error, uint16_properties_, "a uint16");
 }
 
+bool PropertyStore::GetUint16sProperty(const string &name,
+                                       Uint16s *value,
+                                       Error *error) const {
+  return GetProperty(name, value, error, uint16s_properties_, "a uint16 list");
+}
+
 bool PropertyStore::GetUint32Property(const string &name,
                                       uint32 *value,
                                       Error *error) const {
@@ -184,6 +191,12 @@
   return SetProperty(name, value, error, uint16_properties_, "a uint16");
 }
 
+bool PropertyStore::SetUint16sProperty(const string &name,
+                                       const vector<uint16> &value,
+                                       Error *error) {
+  return SetProperty(name, value, error, uint16s_properties_, "a uint16 list");
+}
+
 bool PropertyStore::SetUint32Property(const string &name,
                                       uint32 value,
                                       Error *error) {
@@ -226,6 +239,8 @@
     uint8_properties_[name]->Clear(error);
   } else if (ContainsKey(uint16_properties_, name)) {
     uint16_properties_[name]->Clear(error);
+  } else if (ContainsKey(uint16s_properties_, name)) {
+    uint16s_properties_[name]->Clear(error);
   } else if (ContainsKey(uint32_properties_, name)) {
     uint32_properties_[name]->Clear(error);
   } else if (ContainsKey(uint64_properties_, name)) {
@@ -310,6 +325,11 @@
   return ReadablePropertyConstIterator<uint16>(uint16_properties_);
 }
 
+ReadablePropertyConstIterator<Uint16s> PropertyStore::GetUint16sPropertiesIter()
+    const {
+  return ReadablePropertyConstIterator<Uint16s>(uint16s_properties_);
+}
+
 ReadablePropertyConstIterator<uint32> PropertyStore::GetUint32PropertiesIter()
     const {
   return ReadablePropertyConstIterator<uint32>(uint32_properties_);
@@ -494,6 +514,13 @@
   uint16_properties_[name] = Uint16Accessor(new PropertyAccessor<uint16>(prop));
 }
 
+void PropertyStore::RegisterUint16s(const string &name, Uint16s *prop) {
+  DCHECK(!Contains(name) || ContainsKey(uint16s_properties_, name))
+      << "(Already registered " << name << ")";
+  uint16s_properties_[name] =
+      Uint16sAccessor(new PropertyAccessor<Uint16s>(prop));
+}
+
 void PropertyStore::RegisterUint32(const std::string &name, uint32 *prop) {
   DCHECK(!Contains(name) || ContainsKey(uint32_properties_, name))
       << "(Already registered " << name << ")";
@@ -508,6 +535,14 @@
       Uint16Accessor(new ConstPropertyAccessor<uint16>(prop));
 }
 
+void PropertyStore::RegisterConstUint16s(const string &name,
+                                         const Uint16s *prop) {
+  DCHECK(!Contains(name) || ContainsKey(uint16s_properties_, name))
+      << "(Already registered " << name << ")";
+  uint16s_properties_[name] =
+      Uint16sAccessor(new ConstPropertyAccessor<Uint16s>(prop));
+}
+
 void PropertyStore::RegisterWriteOnlyUint16(const string &name, uint16 *prop) {
   DCHECK(!Contains(name) || ContainsKey(uint16_properties_, name))
       << "(Already registered " << name << ")";
diff --git a/property_store.h b/property_store.h
index 603c040..326cf04 100644
--- a/property_store.h
+++ b/property_store.h
@@ -52,6 +52,8 @@
                         Error *error) const;
   bool GetUint16Property(const std::string &name, uint16 *value,
                          Error *error) const;
+  bool GetUint16sProperty(const std::string &name, Uint16s *value,
+                          Error *error) const;
   bool GetUint32Property(const std::string &name, uint32 *value,
                          Error *error) const;
   bool GetUint64Property(const std::string &name, uint64 *value,
@@ -106,6 +108,10 @@
                                  uint16 value,
                                  Error *error);
 
+  virtual bool SetUint16sProperty(const std::string &name,
+                                  const std::vector<uint16> &value,
+                                  Error *error);
+
   virtual bool SetUint32Property(const std::string &name,
                                  uint32 value,
                                  Error *error);
@@ -149,6 +155,7 @@
   ReadablePropertyConstIterator<Strings> GetStringsPropertiesIter() const;
   ReadablePropertyConstIterator<uint8> GetUint8PropertiesIter() const;
   ReadablePropertyConstIterator<uint16> GetUint16PropertiesIter() const;
+  ReadablePropertyConstIterator<Uint16s> GetUint16sPropertiesIter() const;
   ReadablePropertyConstIterator<uint32> GetUint32PropertiesIter() const;
   ReadablePropertyConstIterator<uint64> GetUint64PropertiesIter() const;
 
@@ -191,7 +198,9 @@
   void RegisterConstUint8(const std::string &name, const uint8 *prop);
   void RegisterWriteOnlyUint8(const std::string &name, uint8 *prop);
   void RegisterUint16(const std::string &name, uint16 *prop);
+  void RegisterUint16s(const std::string &name, Uint16s *prop);
   void RegisterConstUint16(const std::string &name, const uint16 *prop);
+  void RegisterConstUint16s(const std::string &name, const Uint16s *prop);
   void RegisterWriteOnlyUint16(const std::string &name, uint16 *prop);
 
   void RegisterDerivedBool(const std::string &name,
@@ -249,6 +258,7 @@
   std::map<std::string, StringsAccessor> strings_properties_;
   std::map<std::string, Uint8Accessor> uint8_properties_;
   std::map<std::string, Uint16Accessor> uint16_properties_;
+  std::map<std::string, Uint16sAccessor> uint16s_properties_;
   std::map<std::string, Uint32Accessor> uint32_properties_;
   std::map<std::string, Uint64Accessor> uint64_properties_;
 
diff --git a/property_store_unittest.cc b/property_store_unittest.cc
index 2030c9e..03bdd61 100644
--- a/property_store_unittest.cc
+++ b/property_store_unittest.cc
@@ -61,6 +61,9 @@
 const ::DBus::Variant PropertyStoreTest::kUint16V =
     DBusAdaptor::Uint16ToVariant(0);
 // static
+const ::DBus::Variant PropertyStoreTest::kUint16sV =
+    DBusAdaptor::Uint16sToVariant(Uint16s{0});
+// static
 const ::DBus::Variant PropertyStoreTest::kUint32V =
     DBusAdaptor::Uint32ToVariant(0);
 // static
@@ -111,31 +114,48 @@
            PropertyStoreTest::kStringmapV,
            PropertyStoreTest::kStringsV,
            PropertyStoreTest::kUint16V,
+           PropertyStoreTest::kUint16sV,
            PropertyStoreTest::kUint32V,
            PropertyStoreTest::kUint64V));
 
 template <typename T>
 class PropertyStoreTypedTest : public PropertyStoreTest {
  protected:
-  void RegisterProperty(
-      PropertyStore &store, const string &name, T *storage);
   bool SetProperty(
       PropertyStore &store, const string &name, Error *error);
 };
 
-typedef ::testing::Types<
-  bool, int16, int32, string, Stringmap, Stringmaps, Strings, uint8, uint16,
-  uint32> PropertyTypes;
-TYPED_TEST_CASE(PropertyStoreTypedTest, PropertyTypes);
+TYPED_TEST_CASE(PropertyStoreTypedTest, PropertyStoreTest::PropertyTypes);
+
+TYPED_TEST(PropertyStoreTypedTest, RegisterProperty) {
+  PropertyStore store(Bind(&PropertyStoreTest::TestCallback,
+                           Unretained(this)));
+  Error error;
+  TypeParam property;
+  PropertyStoreTest::RegisterProperty(&store, "some property", &property);
+  EXPECT_TRUE(store.Contains("some property"));
+}
+
+TYPED_TEST(PropertyStoreTypedTest, GetProperty) {
+  PropertyStore store(Bind(&PropertyStoreTest::TestCallback,
+                           Unretained(this)));
+  Error error;
+  TypeParam property;
+  PropertyStoreTest::RegisterProperty(&store, "some property", &property);
+
+  TypeParam read_value;
+  EXPECT_CALL(*this, TestCallback(_)).Times(0);
+  EXPECT_TRUE(PropertyStoreTest::GetProperty(
+      store, "some property", &read_value, &error));
+  EXPECT_EQ(property, read_value);
+}
 
 TYPED_TEST(PropertyStoreTypedTest, ClearProperty) {
   PropertyStore store(Bind(&PropertyStoreTest::TestCallback,
                            Unretained(this)));
   Error error;
   TypeParam property;
-
-  // |this| required due to two-phase lookup.
-  this->RegisterProperty(store, "some property", &property);
+  PropertyStoreTest::RegisterProperty(&store, "some property", &property);
   EXPECT_CALL(*this, TestCallback(_));
   EXPECT_TRUE(store.ClearProperty("some property", &error));
 }
@@ -145,9 +165,7 @@
                            Unretained(this)));
   Error error;
   TypeParam property = TypeParam();  // value-initialize primitives
-
-  // |this| required due to two-phase lookup.
-  this->RegisterProperty(store, "some property", &property);
+  PropertyStoreTest::RegisterProperty(&store, "some property", &property);
 
   // Change the value from the default (initialized above).  Should
   // generate a change callback. The second SetProperty, however,
@@ -157,56 +175,6 @@
   EXPECT_FALSE(this->SetProperty(store, "some property", &error));
 }
 
-template<> void PropertyStoreTypedTest<bool>::RegisterProperty(
-    PropertyStore &store, const string &name, bool *storage) {
-  store.RegisterBool(name, storage);
-}
-
-template<> void PropertyStoreTypedTest<int16>::RegisterProperty(
-    PropertyStore &store, const string &name, int16 *storage) {
-  store.RegisterInt16(name, storage);
-}
-
-template<> void PropertyStoreTypedTest<int32>::RegisterProperty(
-    PropertyStore &store, const string &name, int32 *storage) {
-  store.RegisterInt32(name, storage);
-}
-
-template<> void PropertyStoreTypedTest<string>::RegisterProperty(
-    PropertyStore &store, const string &name, string *storage) {
-  store.RegisterString(name, storage);
-}
-
-template<> void PropertyStoreTypedTest<Stringmap>::RegisterProperty(
-    PropertyStore &store, const string &name, Stringmap *storage) {
-  store.RegisterStringmap(name, storage);
-}
-
-template<> void PropertyStoreTypedTest<Stringmaps>::RegisterProperty(
-    PropertyStore &store, const string &name, Stringmaps *storage) {
-  store.RegisterStringmaps(name, storage);
-}
-
-template<> void PropertyStoreTypedTest<Strings>::RegisterProperty(
-    PropertyStore &store, const string &name, Strings *storage) {
-  store.RegisterStrings(name, storage);
-}
-
-template<> void PropertyStoreTypedTest<uint8>::RegisterProperty(
-    PropertyStore &store, const string &name, uint8 *storage) {
-  store.RegisterUint8(name, storage);
-}
-
-template<> void PropertyStoreTypedTest<uint16>::RegisterProperty(
-    PropertyStore &store, const string &name, uint16 *storage) {
-  store.RegisterUint16(name, storage);
-}
-
-template<> void PropertyStoreTypedTest<uint32>::RegisterProperty(
-    PropertyStore &store, const string &name, uint32 *storage) {
-  store.RegisterUint32(name, storage);
-}
-
 template<> bool PropertyStoreTypedTest<bool>::SetProperty(
     PropertyStore &store, const string &name, Error *error) {
   bool new_value = true;
@@ -264,6 +232,12 @@
   return store.SetUint16Property(name, new_value, error);
 }
 
+template<> bool PropertyStoreTypedTest<Uint16s>::SetProperty(
+    PropertyStore &store, const string &name, Error *error) {
+  Uint16s new_value{1};
+  return store.SetUint16sProperty(name, new_value, error);
+}
+
 template<> bool PropertyStoreTypedTest<uint32>::SetProperty(
     PropertyStore &store, const string &name, Error *error) {
   uint32 new_value = 1;
diff --git a/property_store_unittest.h b/property_store_unittest.h
index fb9c45a..99a4a89 100644
--- a/property_store_unittest.h
+++ b/property_store_unittest.h
@@ -28,6 +28,10 @@
 
 class PropertyStoreTest : public testing::TestWithParam< ::DBus::Variant > {
  public:
+  typedef ::testing::Types<bool, int16, int32, std::string, Stringmap,
+                           Stringmaps, Strings, uint8, uint16, Uint16s, uint32>
+      PropertyTypes;
+
   // In real code, it's frowned upon to have non-POD static members, as there
   // can be ordering issues if your constructors have side effects.
   // These constructors don't, and declaring these as static lets me
@@ -43,6 +47,7 @@
   static const ::DBus::Variant kStringmapsV;
   static const ::DBus::Variant kStringsV;
   static const ::DBus::Variant kUint16V;
+  static const ::DBus::Variant kUint16sV;
   static const ::DBus::Variant kUint32V;
   static const ::DBus::Variant kUint64V;
 
@@ -52,6 +57,120 @@
   virtual void SetUp();
   MOCK_METHOD1(TestCallback, void(const std::string &property_name));
 
+  // Convenience overloads for GetProperty. Normally, we don't overload
+  // functions. But this is extremely useful for type-parameterized tests.
+  static bool GetProperty(const PropertyStore &store, const std::string &name,
+                          bool *storage, Error *error) {
+    return store.GetBoolProperty(name, storage, error);
+  }
+
+  static bool GetProperty(const PropertyStore &store, const std::string &name,
+                          int16 *storage, Error *error) {
+    return store.GetInt16Property(name, storage, error);
+  }
+
+  static bool GetProperty(const PropertyStore &store, const std::string &name,
+                          int32 *storage, Error *error) {
+    return store.GetInt32Property(name, storage, error);
+  }
+
+  static bool GetProperty(const PropertyStore &store, const std::string &name,
+                          std::string *storage, Error *error) {
+    return store.GetStringProperty(name, storage, error);
+  }
+
+  static bool GetProperty(const PropertyStore &store, const std::string &name,
+                          Stringmap *storage, Error *error) {
+    return store.GetStringmapProperty(name, storage, error);
+  }
+
+  static bool GetProperty(const PropertyStore &store, const std::string &name,
+                          Stringmaps *storage, Error *error) {
+    return store.GetStringmapsProperty(name, storage, error);
+  }
+
+  static bool GetProperty(const PropertyStore &store, const std::string &name,
+                          Strings *storage, Error *error) {
+    return store.GetStringsProperty(name, storage, error);
+  }
+
+  static bool GetProperty(const PropertyStore &store, const std::string &name,
+                          uint8 *storage, Error *error) {
+    return store.GetUint8Property(name, storage, error);
+  }
+
+  static bool GetProperty(const PropertyStore &store, const std::string &name,
+                          uint16 *storage, Error *error) {
+    return store.GetUint16Property(name, storage, error);
+  }
+
+  static bool GetProperty(const PropertyStore &store, const std::string &name,
+                          Uint16s *storage, Error *error) {
+    return store.GetUint16sProperty(name, storage, error);
+  }
+
+  static bool GetProperty(const PropertyStore &store, const std::string &name,
+                          uint32 *storage, Error *error) {
+    return store.GetUint32Property(name, storage, error);
+  }
+
+  // Convenience overloads for RegisterProperty. Normally, we don't overload
+  // functions. But this is extremely useful for type-parameterized tests.
+  static void RegisterProperty(
+      PropertyStore *store, const std::string &name, bool *storage) {
+    store->RegisterBool(name, storage);
+  }
+
+  static void RegisterProperty(
+      PropertyStore *store, const std::string &name, int16 *storage) {
+    store->RegisterInt16(name, storage);
+  }
+
+  static void RegisterProperty(
+      PropertyStore *store, const std::string &name, int32 *storage) {
+    store->RegisterInt32(name, storage);
+  }
+
+  static void RegisterProperty(
+      PropertyStore *store, const std::string &name, std::string *storage) {
+    store->RegisterString(name, storage);
+  }
+
+  static void RegisterProperty(
+      PropertyStore *store, const std::string &name, Stringmap *storage) {
+    store->RegisterStringmap(name, storage);
+  }
+
+  static void RegisterProperty(
+      PropertyStore *store, const std::string &name, Stringmaps *storage) {
+    store->RegisterStringmaps(name, storage);
+  }
+
+  static void RegisterProperty(
+      PropertyStore *store, const std::string &name, Strings *storage) {
+    store->RegisterStrings(name, storage);
+  }
+
+  static void RegisterProperty(
+      PropertyStore *store, const std::string &name, uint8 *storage) {
+    store->RegisterUint8(name, storage);
+  }
+
+  static void RegisterProperty(
+      PropertyStore *store, const std::string &name, uint16 *storage) {
+    store->RegisterUint16(name, storage);
+  }
+
+  static void RegisterProperty(
+      PropertyStore *store, const std::string &name, Uint16s *storage) {
+    store->RegisterUint16s(name, storage);
+  }
+
+  static void RegisterProperty(
+      PropertyStore *store, const std::string &name, uint32 *storage) {
+    store->RegisterUint32(name, storage);
+  }
+
  protected:
   Manager *manager() { return &manager_; }
   MockControl *control_interface() { return &control_interface_; }
diff --git a/service_dbus_adaptor.cc b/service_dbus_adaptor.cc
index d7068bc..1dcc351 100644
--- a/service_dbus_adaptor.cc
+++ b/service_dbus_adaptor.cc
@@ -45,6 +45,12 @@
   PropertyChanged(name, DBusAdaptor::Uint16ToVariant(value));
 }
 
+void ServiceDBusAdaptor::EmitUint16sChanged(const string &name,
+                                            const Uint16s &value) {
+  SLOG(DBus, 2) << __func__ << ": " << name;
+  PropertyChanged(name, DBusAdaptor::Uint16sToVariant(value));
+}
+
 void ServiceDBusAdaptor::EmitUintChanged(const string &name, uint32 value) {
   SLOG(DBus, 2) << __func__ << ": " << name;
   PropertyChanged(name, DBusAdaptor::Uint32ToVariant(value));
diff --git a/service_dbus_adaptor.h b/service_dbus_adaptor.h
index 80e66f2..2d3d451 100644
--- a/service_dbus_adaptor.h
+++ b/service_dbus_adaptor.h
@@ -38,6 +38,8 @@
   virtual void EmitBoolChanged(const std::string &name, bool value);
   virtual void EmitUint8Changed(const std::string &name, uint8 value);
   virtual void EmitUint16Changed(const std::string &name, uint16 value);
+  virtual void EmitUint16sChanged(const std::string &name,
+                                  const Uint16s &value);
   virtual void EmitUintChanged(const std::string &name, uint32 value);
   virtual void EmitIntChanged(const std::string &name, int value);
   virtual void EmitRpcIdentifierChanged(
diff --git a/wifi_service.cc b/wifi_service.cc
index 4f2ba2e..22e69c8 100644
--- a/wifi_service.cc
+++ b/wifi_service.cc
@@ -90,6 +90,7 @@
   store->RegisterConstString(flimflam::kWifiAuthMode, &auth_mode_);
   store->RegisterBool(flimflam::kWifiHiddenSsid, &hidden_ssid_);
   store->RegisterConstUint16(flimflam::kWifiFrequency, &frequency_);
+  store->RegisterConstUint16s(kWifiFrequencyListProperty, &frequency_list_);
   store->RegisterConstUint16(flimflam::kWifiPhyMode, &physical_mode_);
   store->RegisterConstString(flimflam::kWifiBSsid, &bssid_);
   store->RegisterConstString(flimflam::kCountryProperty, &country_code_);
@@ -639,6 +640,12 @@
     }
   }
 
+  set<uint16> frequency_set;
+  for (const auto &endpoint : endpoints_) {
+    frequency_set.insert(endpoint->frequency());
+  }
+  frequency_list_.assign(frequency_set.begin(), frequency_set.end());
+
   if (Is8021x())
     cipher_8021x_ = ComputeCipher8021x(endpoints_);
 
@@ -681,6 +688,7 @@
     physical_mode_ = physical_mode;
     adaptor()->EmitUint16Changed(flimflam::kWifiPhyMode, physical_mode_);
   }
+  adaptor()->EmitUint16sChanged(kWifiFrequencyListProperty, frequency_list_);
   SetStrength(SignalToStrength(signal));
   UpdateSecurity();
 }
diff --git a/wifi_service.h b/wifi_service.h
index 80844f0..d6d3ef4 100644
--- a/wifi_service.h
+++ b/wifi_service.h
@@ -89,6 +89,7 @@
   const std::string &key_management() const { return GetEAPKeyManagement(); }
   const std::vector<uint8_t> &ssid() const { return ssid_; }
   const std::string &bssid() const { return bssid_; }
+  const std::vector<uint16> &frequency_list() const { return frequency_list_; }
   uint16 physical_mode() const { return physical_mode_; }
 
   // WiFi services can load from profile entries other than their current
@@ -239,6 +240,7 @@
   std::string auth_mode_;
   bool hidden_ssid_;
   uint16 frequency_;
+  std::vector<uint16> frequency_list_;
   uint16 physical_mode_;
   // The raw dBm signal strength from the associated endpoint.
   int16 raw_signal_strength_;
diff --git a/wifi_service_unittest.cc b/wifi_service_unittest.cc
index 0d62e8e..75c7037 100644
--- a/wifi_service_unittest.cc
+++ b/wifi_service_unittest.cc
@@ -1549,6 +1549,65 @@
   service->RemoveEndpoint(ok_endpoint);
 }
 
+MATCHER_P(IsSetwiseEqual, expected_set, "") {
+  set<uint16> arg_set(arg.begin(), arg.end());
+  return arg_set == expected_set;
+}
+
+TEST_F(WiFiServiceUpdateFromEndpointsTest, FrequencyList) {
+  EXPECT_CALL(adaptor, EmitUint16Changed(_, _)).Times(AnyNumber());
+  EXPECT_CALL(adaptor, EmitStringChanged(_, _)).Times(AnyNumber());
+  EXPECT_CALL(adaptor, EmitUint8Changed(_, _)).Times(AnyNumber());
+  EXPECT_CALL(adaptor, EmitBoolChanged(_, _)).Times(AnyNumber());
+
+  // No endpoints -> empty list.
+  EXPECT_EQ(vector<uint16>(), service->frequency_list());
+
+  // Add endpoint -> endpoint's frequency in list.
+  EXPECT_CALL(adaptor, EmitUint16sChanged(
+      kWifiFrequencyListProperty, vector<uint16>{kGoodEndpointFrequency}));
+  service->AddEndpoint(good_endpoint);
+  Mock::VerifyAndClearExpectations(&adaptor);
+
+  // Add another endpoint -> both frequencies in list.
+  // Order doesn't matter.
+  set<uint16> expected_frequencies{kGoodEndpointFrequency,
+        kOkEndpointFrequency};
+  EXPECT_CALL(adaptor, EmitUint16sChanged(
+      kWifiFrequencyListProperty, IsSetwiseEqual(expected_frequencies)));
+  service->AddEndpoint(ok_endpoint);
+  Mock::VerifyAndClearExpectations(&adaptor);
+
+  // Remove endpoint -> other endpoint's frequency remains.
+  EXPECT_CALL(adaptor, EmitUint16sChanged(
+      kWifiFrequencyListProperty, vector<uint16>{kOkEndpointFrequency}));
+  service->RemoveEndpoint(good_endpoint);
+  Mock::VerifyAndClearExpectations(&adaptor);
+
+  // Endpoint with same frequency -> frequency remains.
+  // Notification may or may not occur -- don't care.
+  // Frequency may or may not be repeated in list -- don't care.
+  WiFiEndpointRefPtr same_freq_as_ok_endpoint = MakeOpenEndpoint(
+      simple_ssid_string(), "aa:bb:cc:dd:ee:ff", ok_endpoint->frequency(), 0);
+  service->AddEndpoint(same_freq_as_ok_endpoint);
+  EXPECT_THAT(service->frequency_list(),
+              IsSetwiseEqual(set<uint16>{kOkEndpointFrequency}));
+  Mock::VerifyAndClearExpectations(&adaptor);
+
+  // Remove endpoint with same frequency -> frequency remains.
+  // Notification may or may not occur -- don't care.
+  service->RemoveEndpoint(ok_endpoint);
+  EXPECT_EQ(vector<uint16>{same_freq_as_ok_endpoint->frequency()},
+            service->frequency_list());
+  Mock::VerifyAndClearExpectations(&adaptor);
+
+  // Remove last endpoint. Frequency list goes empty.
+  EXPECT_CALL(adaptor, EmitUint16sChanged(
+      kWifiFrequencyListProperty, vector<uint16>{}));
+  service->RemoveEndpoint(same_freq_as_ok_endpoint);
+  Mock::VerifyAndClearExpectations(&adaptor);
+}
+
 TEST_F(WiFiServiceTest, SecurityFromCurrentEndpoint) {
   WiFiServiceRefPtr service(MakeSimpleService(flimflam::kSecurityPsk));
   EXPECT_EQ(flimflam::kSecurityPsk, service->GetSecurity(NULL));