Merge "Add Gale compatibility and enable 40 MHz mode" 

Test: Done
Bug: 65563975

Change-Id: Ie1bc6607919a44621a86622da80bc3ec641bcede
(cherry picked from commit 7e7a65cdfebf09d07ef90ecbfba56900746693f6)
diff --git a/acts/framework/acts/controllers/access_point.py b/acts/framework/acts/controllers/access_point.py
index a8ea506..422e59f 100755
--- a/acts/framework/acts/controllers/access_point.py
+++ b/acts/framework/acts/controllers/access_point.py
@@ -16,7 +16,10 @@
 
 import collections
 import ipaddress
+import logging
+import time
 
+from acts.controllers.ap_lib import ap_get_interface
 from acts.controllers.ap_lib import bridge_interface
 from acts.controllers.ap_lib import dhcp_config
 from acts.controllers.ap_lib import dhcp_server
@@ -27,9 +30,11 @@
 from acts.controllers.utils_lib.commands import shell
 from acts.controllers.utils_lib.ssh import connection
 from acts.controllers.utils_lib.ssh import settings
+from acts.libs.proc import job
 
 ACTS_CONTROLLER_CONFIG_NAME = 'AccessPoint'
 ACTS_CONTROLLER_REFERENCE_NAME = 'access_points'
+_BRCTL = 'brctl'
 
 
 def create(configs):
@@ -78,11 +83,6 @@
 
 _ApInstance = collections.namedtuple('_ApInstance', ['hostapd', 'subnet'])
 
-# We use these today as part of a hardcoded mapping of interface name to
-# capabilities.  However, medium term we need to start inspecting
-# interfaces to determine their capabilities.
-_AP_2GHZ_INTERFACE = 'wlan0'
-_AP_5GHZ_INTERFACE = 'wlan1'
 # These ranges were split this way since each physical radio can have up
 # to 8 SSIDs so for the 2GHz radio the DHCP range will be
 # 192.168.1 - 8 and the 5Ghz radio will be 192.168.9 - 16
@@ -90,7 +90,6 @@
 _AP_5GHZ_SUBNET_STR = '192.168.9.0/24'
 _AP_2GHZ_SUBNET = dhcp_config.Subnet(ipaddress.ip_network(_AP_2GHZ_SUBNET_STR))
 _AP_5GHZ_SUBNET = dhcp_config.Subnet(ipaddress.ip_network(_AP_5GHZ_SUBNET_STR))
-LAN_INTERFACE = 'eth1'
 # The last digit of the ip for the bridge interface
 BRIDGE_IP_LAST = '100'
 
@@ -119,7 +118,40 @@
         # A map from network interface name to _ApInstance objects representing
         # the hostapd instance running against the interface.
         self._aps = dict()
-        self.bridge = bridge_interface.BridgeInterface(self.ssh)
+        self.bridge = bridge_interface.BridgeInterface(self)
+        self.interfaces = ap_get_interface.ApInterfaces(self)
+
+        # Get needed interface names and initialize the unneccessary ones.
+        self.wan = self.interfaces.get_wan_interface()
+        self.wlan = self.interfaces.get_wlan_interface()
+        self.wlan_2g = self.wlan[0]
+        self.wlan_5g = self.wlan[1]
+        self.lan = self.interfaces.get_lan_interface()
+        self.__initial_ap()
+
+    def __initial_ap(self):
+        """Initial AP interfaces.
+
+        Bring down hostapd if instance is running, bring down all bridge
+        interfaces.
+        """
+        # Stop hostapd instance if running
+        try:
+            self.ssh.run('stop hostapd')
+        except job.Error:
+            logging.debug('No hostapd running')
+        # Bring down all wireless interfaces
+        for iface in self.wlan:
+            WLAN_DOWN = 'ifconfig {} down'.format(iface)
+            self.ssh.run(WLAN_DOWN)
+        # Bring down all bridge interfaces
+        bridge_interfaces = self.interfaces.get_bridge_interface()
+        if bridge_interfaces:
+            for iface in bridge_interfaces:
+                BRIDGE_DOWN = 'ifconfig {} down'.format(iface)
+                BRIDGE_DEL = 'brctl delbr {}'.format(iface)
+                self.ssh.run(BRIDGE_DOWN)
+                self.ssh.run(BRIDGE_DEL)
 
     def start_ap(self, hostapd_config, additional_parameters=None):
         """Starts as an ap using a set of configurations.
@@ -145,15 +177,12 @@
         Raises:
             Error: When the ap can't be brought up.
         """
-        # Right now, we hardcode that a frequency maps to a particular
-        # network interface.  This is true of the hardware we're running
-        # against right now, but in general, we'll want to do some
-        # dynamic discovery of interface capabilities.  See b/32582843
+
         if hostapd_config.frequency < 5000:
-            interface = _AP_2GHZ_INTERFACE
+            interface = self.wlan_2g
             subnet = _AP_2GHZ_SUBNET
         else:
-            interface = _AP_5GHZ_INTERFACE
+            interface = self.wlan_5g
             subnet = _AP_5GHZ_SUBNET
 
         # In order to handle dhcp servers on any interface, the initiation of
@@ -202,14 +231,14 @@
                         bss].bssid = interface_mac_orig.stdout[:-1] + str(
                             counter)
                 self._route_cmd.clear_routes(net_interface=str(bss))
-                if interface is _AP_2GHZ_INTERFACE:
+                if interface is self.wlan_2g:
                     starting_ip_range = _AP_2GHZ_SUBNET_STR
                 else:
                     starting_ip_range = _AP_5GHZ_SUBNET_STR
                 a, b, c, d = starting_ip_range.split('.')
                 dhcp_bss[bss] = dhcp_config.Subnet(
-                    ipaddress.ip_network('%s.%s.%s.%s' % (a, b, str(
-                        int(c) + counter), d)))
+                    ipaddress.ip_network('%s.%s.%s.%s' %
+                                         (a, b, str(int(c) + counter), d)))
                 counter = counter + 1
 
         apd.start(hostapd_config, additional_parameters=additional_parameters)
@@ -238,6 +267,15 @@
 
         self._dhcp.start(config=dhcp_config.DhcpConfig(configured_subnets))
 
+        # The following three commands are needed to enable bridging between
+        # the WAN and LAN/WLAN ports.  This means anyone connecting to the
+        # WLAN/LAN ports will be able to access the internet if the WAN port
+        # is connected to the internet.
+        self.ssh.run('iptables -t nat -F')
+        self.ssh.run(
+            'iptables -t nat -A POSTROUTING -o %s -j MASQUERADE' % self.wan)
+        self.ssh.run('echo 1 > /proc/sys/net/ipv4/ip_forward')
+
         return interface
 
     def get_bssid_from_ssid(self, ssid):
@@ -248,7 +286,7 @@
         Returns: The BSSID if on the AP or None if SSID could not be found.
         """
 
-        interfaces = [_AP_2GHZ_INTERFACE, _AP_5GHZ_INTERFACE, ssid]
+        interfaces = [self.wlan_2g, self.wlan_5g, ssid]
         # Get the interface name associated with the given ssid.
         for interface in interfaces:
             cmd = "iw dev %s info|grep ssid|awk -F' ' '{print $2}'" % (
@@ -311,7 +349,7 @@
             self.stop_all_aps()
         self.ssh.close()
 
-    def generate_bridge_configs(self, channel, iface_lan=LAN_INTERFACE):
+    def generate_bridge_configs(self, channel):
         """Generate a list of configs for a bridge between LAN and WLAN.
 
         Args:
@@ -322,13 +360,13 @@
         """
 
         if channel < 15:
-            iface_wlan = _AP_2GHZ_INTERFACE
+            iface_wlan = self.wlan_2g
             subnet_str = _AP_2GHZ_SUBNET_STR
         else:
-            iface_wlan = _AP_5GHZ_INTERFACE
+            iface_wlan = self.wlan_5g
             subnet_str = _AP_5GHZ_SUBNET_STR
 
-        iface_lan = iface_lan
+        iface_lan = self.lan
 
         a, b, c, d = subnet_str.strip('/24').split('.')
         bridge_ip = "%s.%s.%s.%s" % (a, b, c, BRIDGE_IP_LAST)
diff --git a/acts/framework/acts/controllers/ap_lib/ap_get_interface.py b/acts/framework/acts/controllers/ap_lib/ap_get_interface.py
new file mode 100644
index 0000000..65c8938
--- /dev/null
+++ b/acts/framework/acts/controllers/ap_lib/ap_get_interface.py
@@ -0,0 +1,184 @@
+#!/usr/bin/env python3.4
+#
+#   Copyright 2017 - Google, Inc.
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+import logging
+from acts.libs.proc import job
+
+GET_ALL_INTERFACE = 'ls /sys/class/net'
+GET_VIRTUAL_INTERFACE = 'ls /sys/devices/virtual/net'
+BRCTL_SHOW = 'brctl show'
+
+
+class ApInterfacesError(Exception):
+    """Error related to AP interfaces."""
+
+
+class ApInterfaces(object):
+    """Class to get network interface information for the device.
+
+    """
+
+    def __init__(self, ap):
+        """Initialize the ApInterface class.
+
+        Args:
+            ap: the ap object within ACTS
+        """
+        self.ssh = ap.ssh
+
+    def get_all_interface(self):
+        """Get all network interfaces on the device.
+
+        Returns:
+            interfaces_all: list of all the network interfaces on device
+        """
+        output = self.ssh.run(GET_ALL_INTERFACE)
+        interfaces_all = output.stdout.split('\n')
+
+        return interfaces_all
+
+    def get_virtual_interface(self):
+        """Get all virtual interfaces on the device.
+
+        Returns:
+            interfaces_virtual: list of all the virtual interfaces on device
+        """
+        output = self.ssh.run(GET_VIRTUAL_INTERFACE)
+        interfaces_virtual = output.stdout.split('\n')
+
+        return interfaces_virtual
+
+    def get_physical_interface(self):
+        """Get all the physical interfaces of the device.
+
+        Get all physical interfaces such as eth ports and wlan ports
+        Returns:
+            interfaces_phy: list of all the physical interfaces
+        """
+        interfaces_all = self.get_all_interface()
+        interfaces_virtual = self.get_virtual_interface()
+        interfaces_phy = list(set(interfaces_all) - set(interfaces_virtual))
+
+        return interfaces_phy
+
+    def get_bridge_interface(self):
+        """Get all the bridge interfaces of the device.
+
+        Returns:
+            interfaces_bridge: the list of bridge interfaces, return None if
+                bridge utility is not available on the device
+        """
+        interfaces_bridge = []
+        try:
+            output = self.ssh.run(BRCTL_SHOW)
+            lines = output.stdout.split('\n')
+            for line in lines:
+                interfaces_bridge.append(line.split('\t')[0])
+            interfaces_bridge.pop(0)
+            interfaces_bridge = [x for x in interfaces_bridge if x is not '']
+            return interfaces_bridge
+        except job.Error:
+            logging.info('No brctl utility is available')
+            return None
+
+    def get_wlan_interface(self):
+        """Get all WLAN interfaces and specify 2.4 GHz and 5 GHz interfaces.
+
+        Returns:
+            interfaces_wlan: all wlan interfaces
+        Raises:
+            ApInterfacesError: Missing at least one WLAN interface
+        """
+        wlan_2g = None
+        wlan_5g = None
+        interfaces_phy = self.get_physical_interface()
+        for iface in interfaces_phy:
+            IW_LIST_FREQ = 'iwlist %s freq' % iface
+            output = self.ssh.run(IW_LIST_FREQ)
+            if 'Channel 06' in output.stdout and 'Channel 36' not in output.stdout:
+                wlan_2g = iface
+            elif 'Channel 36' in output.stdout and 'Channel 06' not in output.stdout:
+                wlan_5g = iface
+
+        interfaces_wlan = [wlan_2g, wlan_5g]
+
+        if None not in interfaces_wlan:
+            return interfaces_wlan
+
+        raise ApInterfacesError('Missing at least one WLAN interface')
+
+    def get_wan_interface(self):
+        """Get the WAN interface which has internet connectivity.
+
+        Returns:
+            wan: the only one WAN interface
+        Raises:
+            ApInterfacesError: no running WAN can be found
+        """
+        wan = None
+        interfaces_phy = self.get_physical_interface()
+        interfaces_wlan = self.get_wlan_interface()
+        interfaces_eth = list(set(interfaces_phy) - set(interfaces_wlan))
+        for iface in interfaces_eth:
+            network_status = self.check_ping(iface)
+            if network_status == 1:
+                wan = iface
+                break
+        if wan:
+            return wan
+
+        raise ApInterfacesError('No WAN interface available')
+
+    def get_lan_interface(self):
+        """Get the LAN interface connecting to local devices.
+
+        Returns:
+            lan: the only one running LAN interface of the devices
+        Raises:
+            ApInterfacesError: no running LAN can be found
+        """
+        lan = None
+        interfaces_phy = self.get_physical_interface()
+        interfaces_wlan = self.get_wlan_interface()
+        interfaces_eth = list(set(interfaces_phy) - set(interfaces_wlan))
+        interface_wan = self.get_wan_interface()
+        interfaces_eth.remove(interface_wan)
+        for iface in interfaces_eth:
+            LAN_CHECK = 'ifconfig %s' % iface
+            output = self.ssh.run(LAN_CHECK)
+            if 'RUNNING' in output.stdout:
+                lan = iface
+                break
+        if lan:
+            return lan
+
+        raise ApInterfacesError(
+            'No running LAN interface available, check connection')
+
+    def check_ping(self, iface):
+        """Check the ping status on specific interface to determine the WAN.
+
+        Args:
+            iface: the specific interface to check
+        Returns:
+            network_status: the connectivity status of the interface
+        """
+        PING = 'ping -c 1 -I %s 8.8.8.8' % iface
+        try:
+            self.ssh.run(PING)
+            return 1
+        except job.Error:
+            return 0
diff --git a/acts/framework/acts/controllers/ap_lib/bridge_interface.py b/acts/framework/acts/controllers/ap_lib/bridge_interface.py
index af3d072..be4d291 100644
--- a/acts/framework/acts/controllers/ap_lib/bridge_interface.py
+++ b/acts/framework/acts/controllers/ap_lib/bridge_interface.py
@@ -18,9 +18,8 @@
 import time
 from acts.libs.proc import job
 
-# TODO(@qijiang): will change to brctl when it's built in image
-_BRCTL = '/home/root/bridge-utils/sbin/brctl'
-BRIDGE_NAME = 'br0'
+_BRCTL = 'brctl'
+BRIDGE_NAME = 'br-lan'
 CREATE_BRIDGE = '%s addbr %s' % (_BRCTL, BRIDGE_NAME)
 DELETE_BRIDGE = '%s delbr %s' % (_BRCTL, BRIDGE_NAME)
 BRING_DOWN_BRIDGE = 'ifconfig %s down' % BRIDGE_NAME
@@ -48,16 +47,14 @@
     """Class object for bridge interface betwen WLAN and LAN
 
     """
-
-    def __init__(self, ssh_session):
+    def __init__(self, ap):
         """Initialize the BridgeInterface class.
 
         Bridge interface will be added between ethernet LAN port and WLAN port.
         Args:
-            ssh_session: ssh session to the AP
+            ap: AP object within ACTS
         """
-        self.ssh = ssh_session
-        self.log = logging.getLogger()
+        self.ssh = ap.ssh
 
     def startup(self, brconfigs):
         """Start up the bridge interface.
@@ -66,12 +63,12 @@
             brconfigs: the bridge interface config, type BridgeInterfaceConfigs
         """
 
-        self.log.info('Create bridge interface between LAN and WLAN')
+        logging.info('Create bridge interface between LAN and WLAN')
         # Create the bridge
         try:
             self.ssh.run(CREATE_BRIDGE)
         except job.Error:
-            self.log.warning(
+            logging.warning(
                 'Bridge interface {} already exists, no action needed'.format(
                     BRIDGE_NAME))
 
@@ -80,8 +77,8 @@
         try:
             self.ssh.run(ENABLE_4ADDR)
         except job.Error:
-            self.log.warning(
-                '4addr is already enabled on {}'.format(brconfigs.iface_wlan))
+            logging.warning('4addr is already enabled on {}'.format(
+                brconfigs.iface_wlan))
 
         # Add both LAN and WLAN interfaces to the bridge interface
         for interface in [brconfigs.iface_lan, brconfigs.iface_wlan]:
@@ -89,7 +86,7 @@
             try:
                 self.ssh.run(ADD_INTERFACE)
             except job.Error:
-                self.log.warning('{} has alrady been added to {}'.format(
+                logging.warning('{} has alrady been added to {}'.format(
                     interface, BRIDGE_NAME))
         time.sleep(5)
 
@@ -99,7 +96,7 @@
         time.sleep(2)
 
         # Bridge interface is up
-        self.log.info('Bridge interface is up and running')
+        logging.info('Bridge interface is up and running')
 
     def teardown(self, brconfigs):
         """Tear down the bridge interface.
@@ -107,7 +104,7 @@
         Args:
             brconfigs: the bridge interface config, type BridgeInterfaceConfigs
         """
-        self.log.info('Bringing down the bridge interface')
+        logging.info('Bringing down the bridge interface')
         # Delete the bridge interface
         self.ssh.run(BRING_DOWN_BRIDGE)
         time.sleep(1)
@@ -120,4 +117,4 @@
         DISABLE_4ADDR = 'iw dev %s set 4addr off' % (brconfigs.iface_wlan)
         self.ssh.run(DISABLE_4ADDR)
         time.sleep(1)
-        self.log.info('Bridge interface is down')
+        logging.info('Bridge interface is down')
diff --git a/acts/framework/acts/controllers/ap_lib/dhcp_server.py b/acts/framework/acts/controllers/ap_lib/dhcp_server.py
index 59f1f2f..6d81360 100644
--- a/acts/framework/acts/controllers/ap_lib/dhcp_server.py
+++ b/acts/framework/acts/controllers/ap_lib/dhcp_server.py
@@ -12,18 +12,9 @@
 #   See the License for the specific language governing permissions and
 #   limitations under the License.
 
-import collections
-import itertools
-import os
 import time
-
-from acts.controllers.ap_lib import dhcp_config
 from acts.controllers.utils_lib.commands import shell
 
-# The router wan interface will be hard set since this the default for the
-# whirlwind.  In the future it maybe desireable to see which interface has a
-# public address and assign it dynamically.
-_ROUTER_WAN_INTERFACE = 'eth2'
 _ROUTER_DNS = '8.8.8.8, 4.4.4.4'
 
 
@@ -81,15 +72,6 @@
         if self.is_alive():
             self.stop()
 
-        # The following three commands are needed to enable bridging between
-        # the WAN and LAN/WLAN ports.  This means anyone connecting to the
-        # WLAN/LAN ports will be able to access the internet if the WAN port
-        # is connected to the internet.
-        self._runner.run('iptables -t nat -F')
-        self._runner.run('iptables -t nat -A POSTROUTING -o %s -j MASQUERADE' %
-                         _ROUTER_WAN_INTERFACE)
-        self._runner.run('echo 1 > /proc/sys/net/ipv4/ip_forward')
-
         self._write_configs(config)
         self._shell.delete_file(self._log_file)
         self._shell.touch_file(self._lease_file)
diff --git a/acts/framework/acts/controllers/ap_lib/hostapd_ap_preset.py b/acts/framework/acts/controllers/ap_lib/hostapd_ap_preset.py
index a18f0c7..845f3d3 100644
--- a/acts/framework/acts/controllers/ap_lib/hostapd_ap_preset.py
+++ b/acts/framework/acts/controllers/ap_lib/hostapd_ap_preset.py
@@ -18,10 +18,14 @@
 
 def create_ap_preset(profile_name,
                      channel=None,
+                     dtim=2,
                      frequency=None,
                      security=None,
                      ssid=None,
-                     bss_settings=[]):
+                     vht_bandwidth=80,
+                     bss_settings=[],
+                     iface_wlan_2g=hostapd_constants.WLAN0_STRING,
+                     iface_wlan_5g=hostapd_constants.WLAN1_STRING):
     """AP preset config generator.  This a wrapper for hostapd_config but
        but supplies the default settings for the preset that is selected.
 
@@ -33,10 +37,14 @@
         profile_name: The name of the device want the preset for.
                       Options: whirlwind
         channel: int, channel number.
+        dtim: int, DTIM value of the AP, default is 2.
         frequency: int, frequency of channel.
         security: Security, the secuirty settings to use.
         ssid: string, The name of the ssid to brodcast.
+        vht_bandwidth: VHT bandwidth for 11ac operation.
         bss_settings: The settings for all bss.
+        iface_wlan_2g: the wlan 2g interface name of the AP.
+        iface_wlan_5g: the wlan 5g interface name of the AP.
 
     Returns: A hostapd_config object that can be used by the hostapd object.
     """
@@ -60,10 +68,9 @@
     if (profile_name == 'whirlwind'):
         force_wmm = True
         beacon_interval = 100
-        dtim_period = 2
         short_preamble = True
         if frequency < 5000:
-            interface = hostapd_constants.WLAN0_STRING
+            interface = iface_wlan_2g
             mode = hostapd_constants.MODE_11N_MIXED
             n_capabilities = [
                 hostapd_constants.N_CAPABILITY_LDPC,
@@ -86,19 +93,30 @@
                 n_capabilities=n_capabilities,
                 bss_settings=bss_settings)
         else:
-            interface = hostapd_constants.WLAN1_STRING
+            interface = iface_wlan_5g
             mode = hostapd_constants.MODE_11AC_MIXED
             if hostapd_config.ht40_plus_allowed(channel):
                 extended_channel = hostapd_constants.N_CAPABILITY_HT40_PLUS
             elif hostapd_config.ht40_minus_allowed(channel):
                 extended_channel = hostapd_constants.N_CAPABILITY_HT40_MINUS
-            n_capabilities = [
-                hostapd_constants.N_CAPABILITY_LDPC, extended_channel,
-                hostapd_constants.N_CAPABILITY_SGI20,
-                hostapd_constants.N_CAPABILITY_SGI40,
-                hostapd_constants.N_CAPABILITY_TX_STBC,
-                hostapd_constants.N_CAPABILITY_RX_STBC1
-            ]
+            # Define the n capability vector for 20 MHz and higher bandwidth
+            if vht_bandwidth >= 40:
+                n_capabilities = [
+                    hostapd_constants.N_CAPABILITY_LDPC, extended_channel,
+                    hostapd_constants.N_CAPABILITY_SGI20,
+                    hostapd_constants.N_CAPABILITY_SGI40,
+                    hostapd_constants.N_CAPABILITY_TX_STBC,
+                    hostapd_constants.N_CAPABILITY_RX_STBC1
+                ]
+            else:
+                n_capabilities = [
+                    hostapd_constants.N_CAPABILITY_LDPC,
+                    hostapd_constants.N_CAPABILITY_SGI20,
+                    hostapd_constants.N_CAPABILITY_SGI40,
+                    hostapd_constants.N_CAPABILITY_TX_STBC,
+                    hostapd_constants.N_CAPABILITY_RX_STBC1,
+                    hostapd_constants.N_CAPABILITY_HT20
+                ]
             ac_capabilities = [
                 hostapd_constants.AC_CAPABILITY_MAX_MPDU_11454,
                 hostapd_constants.AC_CAPABILITY_RXLDPC,
@@ -115,6 +133,7 @@
                 interface=interface,
                 mode=mode,
                 force_wmm=force_wmm,
+                vht_channel_width=vht_bandwidth,
                 beacon_interval=beacon_interval,
                 dtim_period=dtim_period,
                 short_preamble=short_preamble,
diff --git a/acts/framework/acts/controllers/ap_lib/hostapd_config.py b/acts/framework/acts/controllers/ap_lib/hostapd_config.py
index 4c70bd7..bfc0ca3 100644
--- a/acts/framework/acts/controllers/ap_lib/hostapd_config.py
+++ b/acts/framework/acts/controllers/ap_lib/hostapd_config.py
@@ -428,7 +428,8 @@
         self._pmf_support = pmf_support
         self._obss_interval = obss_interval
         if self.is_11ac:
-            if str(vht_channel_width) == '40':
+            if str(vht_channel_width) == '40' or str(
+                    vht_channel_width) == '20':
                 self._vht_oper_chwidth = hostapd_constants.VHT_CHANNEL_WIDTH_40
             elif str(vht_channel_width) == '80':
                 self._vht_oper_chwidth = hostapd_constants.VHT_CHANNEL_WIDTH_80
@@ -442,9 +443,10 @@
                 logging.warning(
                     'No channel bandwidth specified.  Using 80MHz for 11ac.')
                 self._vht_oper_chwidth = 1
-            if not vht_center_channel:
-                self._vht_oper_centr_freq_seg0_idx = self._get_11ac_center_channel_from_channel(
-                    self.channel)
+            if not vht_channel_width == 20:
+                if not vht_center_channel:
+                    self._vht_oper_centr_freq_seg0_idx = self._get_11ac_center_channel_from_channel(
+                        self.channel)
             else:
                 self._vht_oper_centr_freq_seg0_idx = vht_center_channel
             self._ac_capabilities = set(ac_capabilities)
@@ -461,16 +463,16 @@
             self._bss_lookup[bss.name] = bss
 
     def __repr__(self):
-        return (
-            '%s(mode=%r, channel=%r, frequency=%r, '
-            'n_capabilities=%r, beacon_interval=%r, '
-            'dtim_period=%r, frag_threshold=%r, ssid=%r, bssid=%r, '
-            'wmm_enabled=%r, security_config=%r, '
-            'spectrum_mgmt_required=%r)' %
-            (self.__class__.__name__, self._mode, self.channel, self.frequency,
-             self._n_capabilities, self._beacon_interval, self._dtim_period,
-             self._frag_threshold, self._ssid, self._bssid, self._wmm_enabled,
-             self._security, self._spectrum_mgmt_required))
+        return ('%s(mode=%r, channel=%r, frequency=%r, '
+                'n_capabilities=%r, beacon_interval=%r, '
+                'dtim_period=%r, frag_threshold=%r, ssid=%r, bssid=%r, '
+                'wmm_enabled=%r, security_config=%r, '
+                'spectrum_mgmt_required=%r)' %
+                (self.__class__.__name__, self._mode, self.channel,
+                 self.frequency, self._n_capabilities, self._beacon_interval,
+                 self._dtim_period, self._frag_threshold, self._ssid,
+                 self._bssid, self._wmm_enabled, self._security,
+                 self._spectrum_mgmt_required))
 
     def supports_channel(self, value):
         """Check whether channel is supported by the current hardware mode.
diff --git a/acts/framework/acts/controllers/ap_lib/hostapd_constants.py b/acts/framework/acts/controllers/ap_lib/hostapd_constants.py
index 4e43eab..11cfaa5 100755
--- a/acts/framework/acts/controllers/ap_lib/hostapd_constants.py
+++ b/acts/framework/acts/controllers/ap_lib/hostapd_constants.py
@@ -110,6 +110,7 @@
 MODE_11AC_PURE = 'ac-only'
 
 N_CAPABILITY_LDPC = object()
+N_CAPABILITY_HT20 = object()
 N_CAPABILITY_HT40_PLUS = object()
 N_CAPABILITY_HT40_MINUS = object()
 N_CAPABILITY_GREENFIELD = object()
@@ -122,6 +123,7 @@
 N_CAPABILITY_DSSS_CCK_40 = object()
 N_CAPABILITIES_MAPPING = {
     N_CAPABILITY_LDPC: '[LDPC]',
+    N_CAPABILITY_HT20: '[HT20]',
     N_CAPABILITY_HT40_PLUS: '[HT40+]',
     N_CAPABILITY_HT40_MINUS: '[HT40-]',
     N_CAPABILITY_GREENFIELD: '[GF]',
@@ -212,10 +214,12 @@
 # tolerate HT40+ on channel 7 (not allowed in the US).  We take the loose
 # definition so that we don't prohibit testing in either domain.
 HT40_ALLOW_MAP = {
-    N_CAPABILITY_HT40_MINUS_CHANNELS: tuple(
+    N_CAPABILITY_HT40_MINUS_CHANNELS:
+    tuple(
         itertools.chain(
             range(6, 14), range(40, 65, 8), range(104, 137, 8), [153, 161])),
-    N_CAPABILITY_HT40_PLUS_CHANNELS: tuple(
+    N_CAPABILITY_HT40_PLUS_CHANNELS:
+    tuple(
         itertools.chain(
             range(1, 8), range(36, 61, 8), range(100, 133, 8), [149, 157]))
 }
diff --git a/acts/framework/acts/test_utils/wifi/wifi_power_test_utils.py b/acts/framework/acts/test_utils/wifi/wifi_power_test_utils.py
index 1927d36..7fdbbf0 100644
--- a/acts/framework/acts/test_utils/wifi/wifi_power_test_utils.py
+++ b/acts/framework/acts/test_utils/wifi/wifi_power_test_utils.py
@@ -212,14 +212,15 @@
     color = ['navy'] * len(current_data)
 
     #Preparing the data and source link for bokehn java callback
-    source = ColumnDataSource(data=dict(
-        x0=time_relative, y0=current_data, color=color))
-    s2 = ColumnDataSource(data=dict(
-        z0=[mon_info['duration']],
-        y0=[round(avg_current, 2)],
-        x0=[round(avg_current * voltage, 2)],
-        z1=[round(avg_current * voltage * mon_info['duration'], 2)],
-        z2=[round(avg_current * mon_info['duration'], 2)]))
+    source = ColumnDataSource(
+        data=dict(x0=time_relative, y0=current_data, color=color))
+    s2 = ColumnDataSource(
+        data=dict(
+            z0=[mon_info['duration']],
+            y0=[round(avg_current, 2)],
+            x0=[round(avg_current * voltage, 2)],
+            z1=[round(avg_current * voltage * mon_info['duration'], 2)],
+            z2=[round(avg_current * mon_info['duration'], 2)]))
     #Setting up data table for the output
     columns = [
         TableColumn(field='z0', title='Total Duration (s)'),
@@ -349,15 +350,15 @@
     ad.log.info('DTIM updated and device back from reboot')
 
 
-def ap_setup(ap, network):
+def ap_setup(ap, network, bandwidth=80):
     """Set up the whirlwind AP with provided network info.
 
     Args:
         ap: access_point object of the AP
         network: dict with information of the network, including ssid, password
                  bssid, channel etc.
+        bandwidth: the operation bandwidth for the AP, default 80MHz
     """
-
     log = logging.getLogger()
     bss_settings = []
     ssid = network[wutils.WifiEnums.SSID_KEY]
@@ -373,7 +374,10 @@
         ssid=ssid,
         security=security,
         bss_settings=bss_settings,
-        profile_name='whirlwind')
+        vht_bandwidth=bandwidth,
+        profile_name='whirlwind',
+        iface_wlan_2g=ap.wlan_2g,
+        iface_wlan_5g=ap.wlan_5g)
     ap.start_ap(config)
     log.info("AP started on channel {} with SSID {}".format(channel, ssid))