apmanager: configurable device control for AP service

Add a Config property "FullDeviceControl" for controlling the wifi device
when starting an AP service on it. This property is set to true by default.

When it is set to true, apmanager will claim all interfaces resided on the
wifi device used for starting an AP service.

When it is set to false, apmanager will only claim the actual interface on
the wifi device used for starting an AP service. This will allow shill to
maintain the control of the managed mode interface when an AP service is
started on the same wifi device, which will allow the wifi device to support
both client mode and AP mode operation simultaneously (when dedicated interfaces
are created for each operation mode by the driver).

BUG=brillo:541
TEST=USE="asan clang" FEATURES=test emerge-$BOARD apmanager
Manual Test:
1. Grab a peach_pit device, which supports both client mode and AP
   mode operation simultaneously with constraint of both needs to
   operate on the same channel.
2. Start an AP service with default settings, verify shill is not
   managing the managed mode interface "mlan0" anymore via
   "./usr/local/lib/flimflam/test/list-devices".
3. Tear down the AP service, and then connect the device to a wifi
   network ("ChromeOS-Test-AP").
4. Start an AP service with "FullDeviceControl" set to false and
   "Channel" set to the same channel number as the client connection,
   verify shill is still in control of the managed mode interface
   "mlan0" via "./usr/local/lib/flimflam/test/list-devices" and "ifconfig".
5. Verify another client device can connect to that AP with IP connectivity.

Change-Id: I71f58e4b50b3a8f92f1be339157850c37772e29a
Reviewed-on: https://chromium-review.googlesource.com/257611
Trybot-Ready: Zeping Qiu <zqiu@chromium.org>
Tested-by: Zeping Qiu <zqiu@chromium.org>
Reviewed-by: Paul Stewart <pstew@chromium.org>
Commit-Queue: Zeping Qiu <zqiu@chromium.org>
diff --git a/config.cc b/config.cc
index 1318fe7..8ad2d87 100644
--- a/config.cc
+++ b/config.cc
@@ -86,6 +86,7 @@
   SetServerAddressIndex(kPropertyDefaultServerAddressIndex);
   SetChannel(kPropertyDefaultChannel);
   SetHiddenNetwork(kPropertyDefaultHiddenNetwork);
+  SetFullDeviceControl(true);
 }
 
 Config::~Config() {}
@@ -254,7 +255,7 @@
     LOG(ERROR) << "Failed to claim device: device doesn't exist.";
     return false;
   }
-  return device_->ClaimDevice();
+  return device_->ClaimDevice(GetFullDeviceControl());
 }
 
 bool Config::ReleaseDevice() {
diff --git a/dbus_bindings/org.chromium.apmanager.Config.xml b/dbus_bindings/org.chromium.apmanager.Config.xml
index ec28cc3..815a617 100644
--- a/dbus_bindings/org.chromium.apmanager.Config.xml
+++ b/dbus_bindings/org.chromium.apmanager.Config.xml
@@ -12,5 +12,6 @@
     <property name="HiddenNetwork" type="b" access="readwrite"/>
     <property name="BridgeInterface" type="s" access="readwrite"/>
     <property name="ServerAddressIndex" type="q" access="readwrite"/>
+    <property name="FullDeviceControl" type="b" access="readwrite"/>
   </interface>
 </node>
diff --git a/device.cc b/device.cc
index e06fa0b..fe8c9d1 100644
--- a/device.cc
+++ b/device.cc
@@ -139,19 +139,22 @@
   }
 }
 
-bool Device::ClaimDevice() {
+bool Device::ClaimDevice(bool full_control) {
   if (GetInUsed()) {
     LOG(ERROR) << "Failed to claim device [" << GetDeviceName()
                << "]: already in used.";
     return false;
   }
 
-  // Issue DBus calls to shill to claim all interfaces on this
-  // device.
-  for (const auto& interface : interface_list_) {
-    manager_->ClaimInterface(interface.iface_name);
+  if (full_control) {
+    for (const auto& interface : interface_list_) {
+      manager_->ClaimInterface(interface.iface_name);
+      claimed_interfaces_.insert(interface.iface_name);
+    }
+  } else {
+    manager_->ClaimInterface(GetPreferredApInterface());
+    claimed_interfaces_.insert(GetPreferredApInterface());
   }
-
   SetInUsed(true);
   return true;
 }
@@ -163,12 +166,10 @@
     return false;
   }
 
-  // Issue DBus calls to shill to release all interfaces on this
-  // device.
-  for (const auto& interface : interface_list_) {
-    manager_->ReleaseInterface(interface.iface_name);
+  for (const auto& interface : claimed_interfaces_) {
+    manager_->ReleaseInterface(interface);
   }
-
+  claimed_interfaces_.clear();
   SetInUsed(false);
   return true;
 }
diff --git a/device.h b/device.h
index 20240ab..36be348 100644
--- a/device.h
+++ b/device.h
@@ -5,6 +5,7 @@
 #ifndef APMANAGER_DEVICE_H_
 #define APMANAGER_DEVICE_H_
 
+#include <set>
 #include <string>
 #include <vector>
 
@@ -70,10 +71,12 @@
   // Parse device capability from NL80211 message.
   void ParseWiphyCapability(const shill::Nl80211Message& msg);
 
-  // Function for claiming/releasing ownership of this device. This will invoke
-  // dbus calls to shill to claim/release all the interfaces reside on this
-  // device.
-  virtual bool ClaimDevice();
+  // Claim ownership of this device for AP operation. When |full_control| is
+  // set to true, this will claim all interfaces reside on this device.
+  // When it is set to false, this will only claim the interface used for AP
+  // operation.
+  virtual bool ClaimDevice(bool full_control);
+  // Release any claimed interfaces.
   virtual bool ReleaseDevice();
 
   // Return true if interface with |interface_name| resides on this device,
@@ -117,6 +120,9 @@
   // Wiphy band capabilities.
   std::vector<BandCapability> band_capability_;
 
+  // List of claimed interfaces.
+  std::set<std::string> claimed_interfaces_;
+
   DISALLOW_COPY_AND_ASSIGN(Device);
 };
 
diff --git a/device_unittest.cc b/device_unittest.cc
index 997d94f..516ffb3 100644
--- a/device_unittest.cc
+++ b/device_unittest.cc
@@ -272,23 +272,23 @@
   EXPECT_EQ(kBand5GHzHTCapability, band_5ghz_cap);
 }
 
-TEST_F(DeviceTest, ClaimAndReleaseDevice) {
+TEST_F(DeviceTest, ClaimAndReleaseDeviceWithFullControl) {
   EnableApModeSupport();
 
   // Register multiple interfaces.
   device_->RegisterInterface(kApModeInterface1);
   device_->RegisterInterface(kManagedModeInterface1);
 
-  // Claim the device should claim all interfaces registered on this device.
+  // Claim the device should claim all interfaces registered on this device..
   EXPECT_CALL(manager_, ClaimInterface(kApModeInterface1.iface_name)).Times(1);
   EXPECT_CALL(manager_,
               ClaimInterface(kManagedModeInterface1.iface_name)).Times(1);
-  EXPECT_TRUE(device_->ClaimDevice());
+  EXPECT_TRUE(device_->ClaimDevice(true));
   Mock::VerifyAndClearExpectations(&manager_);
 
   // Claim the device when it is already claimed.
   EXPECT_CALL(manager_, ClaimInterface(_)).Times(0);
-  EXPECT_FALSE(device_->ClaimDevice());
+  EXPECT_FALSE(device_->ClaimDevice(true));
   Mock::VerifyAndClearExpectations(&manager_);
 
   // Release the device should release all interfaces registered on this device.
@@ -305,4 +305,39 @@
   Mock::VerifyAndClearExpectations(&manager_);
 }
 
+TEST_F(DeviceTest, ClaimAndReleaseDeviceWithoutFullControl) {
+  EnableApModeSupport();
+
+  // Register multiple interfaces.
+  device_->RegisterInterface(kApModeInterface1);
+  device_->RegisterInterface(kManagedModeInterface1);
+
+  // Claim the device should only claim the preferred AP interface registered
+  // on this device.
+  EXPECT_CALL(manager_, ClaimInterface(kApModeInterface1.iface_name)).Times(1);
+  EXPECT_CALL(manager_,
+              ClaimInterface(kManagedModeInterface1.iface_name)).Times(0);
+  EXPECT_TRUE(device_->ClaimDevice(false));
+  Mock::VerifyAndClearExpectations(&manager_);
+
+  // Claim the device when it is already claimed.
+  EXPECT_CALL(manager_, ClaimInterface(_)).Times(0);
+  EXPECT_FALSE(device_->ClaimDevice(false));
+  Mock::VerifyAndClearExpectations(&manager_);
+
+  // Release the device should release the preferred AP interface registered
+  // on this device.
+  EXPECT_CALL(manager_,
+              ReleaseInterface(kApModeInterface1.iface_name)).Times(1);
+  EXPECT_CALL(manager_,
+              ReleaseInterface(kManagedModeInterface1.iface_name)).Times(0);
+  EXPECT_TRUE(device_->ReleaseDevice());
+  Mock::VerifyAndClearExpectations(&manager_);
+
+  // Release the device when it is not claimed.
+  EXPECT_CALL(manager_, ReleaseInterface(_)).Times(0);
+  EXPECT_FALSE(device_->ReleaseDevice());
+  Mock::VerifyAndClearExpectations(&manager_);
+}
+
 }  // namespace apmanager
diff --git a/mock_device.h b/mock_device.h
index 2ce9a4b..31f2944 100644
--- a/mock_device.h
+++ b/mock_device.h
@@ -25,7 +25,7 @@
                void(const WiFiInterface& interface));
   MOCK_METHOD1(ParseWiphyCapability,
                void(const shill::Nl80211Message& msg));
-  MOCK_METHOD0(ClaimDevice, bool());
+  MOCK_METHOD1(ClaimDevice, bool(bool full_control));
   MOCK_METHOD0(ReleaseDevice, bool());
   MOCK_METHOD1(InterfaceExists, bool(const std::string& interface_name));
   MOCK_METHOD2(GetHTCapability, bool(uint16_t channel, std::string* ht_capab));