Restrict channel range in ACS

This allows the device to select one of the specified
channels when ACS is enabled.

Bug: 116839455
Test: Manual
Test: Unit test
Signed-off-by: Daichi Ueura <daichi.ueura@sony.com>
Change-Id: Ieb4aff67eba80e93d428d8adc6498483e26a440f
(cherry-picked from e53b8e0d87587aaa0757870b5120cf5146d3570b)
diff --git a/service/java/com/android/server/wifi/HostapdHal.java b/service/java/com/android/server/wifi/HostapdHal.java
index 57d99e6..d8f6f39 100644
--- a/service/java/com/android/server/wifi/HostapdHal.java
+++ b/service/java/com/android/server/wifi/HostapdHal.java
@@ -33,7 +33,9 @@
 import com.android.server.wifi.WifiNative.HostapdDeathEventHandler;
 import com.android.server.wifi.util.NativeUtil;
 
+import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.List;
 import java.util.NoSuchElementException;
 
 import javax.annotation.concurrent.ThreadSafe;
@@ -52,6 +54,8 @@
     private boolean mVerboseLoggingEnabled = false;
     private final boolean mEnableAcs;
     private final boolean mEnableIeee80211AC;
+    private final List<android.hardware.wifi.hostapd.V1_1.IHostapd.AcsChannelRange>
+            mAcsChannelRanges;
 
     // Hostapd HAL interface objects
     private IServiceManager mIServiceManager = null;
@@ -97,6 +101,8 @@
         mEnableAcs = context.getResources().getBoolean(R.bool.config_wifi_softap_acs_supported);
         mEnableIeee80211AC =
                 context.getResources().getBoolean(R.bool.config_wifi_softap_ieee80211ac_supported);
+        mAcsChannelRanges = toAcsChannelRanges(context.getResources().getString(
+                R.string.config_wifi_softap_acs_supported_channel_list));
     }
 
     /**
@@ -320,7 +326,18 @@
             nwParams.pskPassphrase = (config.preSharedKey != null) ? config.preSharedKey : "";
             if (!checkHostapdAndLogFailure(methodStr)) return false;
             try {
-                HostapdStatus status = mIHostapd.addAccessPoint(ifaceParams, nwParams);
+                HostapdStatus status;
+                if (isV1_1()) {
+                    android.hardware.wifi.hostapd.V1_1.IHostapd.IfaceParams ifaceParams1_1 =
+                            new android.hardware.wifi.hostapd.V1_1.IHostapd.IfaceParams();
+                    ifaceParams1_1.V1_0 = ifaceParams;
+                    if (mEnableAcs) {
+                        ifaceParams1_1.channelParams.acsChannelRanges.addAll(mAcsChannelRanges);
+                    }
+                    status = getHostapdMockableV1_1().addAccessPoint_1_1(ifaceParams1_1, nwParams);
+                } else {
+                    status = mIHostapd.addAccessPoint(ifaceParams, nwParams);
+                }
                 if (!checkStatusAndLogFailure(status, methodStr)) {
                     return false;
                 }
@@ -528,6 +545,46 @@
     }
 
     /**
+     * Convert channel list string like '1-6,11' to list of AcsChannelRanges
+     */
+    private List<android.hardware.wifi.hostapd.V1_1.IHostapd.AcsChannelRange>
+            toAcsChannelRanges(String channelListStr) {
+        ArrayList<android.hardware.wifi.hostapd.V1_1.IHostapd.AcsChannelRange> acsChannelRanges =
+                new ArrayList<>();
+        String[] channelRanges = channelListStr.split(",");
+        for (String channelRange : channelRanges) {
+            android.hardware.wifi.hostapd.V1_1.IHostapd.AcsChannelRange acsChannelRange =
+                    new android.hardware.wifi.hostapd.V1_1.IHostapd.AcsChannelRange();
+            try {
+                if (channelRange.contains("-")) {
+                    String[] channels  = channelRange.split("-");
+                    if (channels.length != 2) {
+                        Log.e(TAG, "Unrecognized channel range, length is " + channels.length);
+                        continue;
+                    }
+                    int start = Integer.parseInt(channels[0]);
+                    int end = Integer.parseInt(channels[1]);
+                    if (start > end) {
+                        Log.e(TAG, "Invalid channel range, from " + start + " to " + end);
+                        continue;
+                    }
+                    acsChannelRange.start = start;
+                    acsChannelRange.end = end;
+                } else {
+                    acsChannelRange.start = Integer.parseInt(channelRange);
+                    acsChannelRange.end = acsChannelRange.start;
+                }
+            } catch (NumberFormatException e) {
+                // Ignore malformed value
+                Log.e(TAG, "Malformed channel value detected: " + e);
+                continue;
+            }
+            acsChannelRanges.add(acsChannelRange);
+        }
+        return acsChannelRanges;
+    }
+
+    /**
      * Returns false if Hostapd is null, and logs failure to call methodStr
      */
     private boolean checkHostapdAndLogFailure(String methodStr) {
diff --git a/service/tests/wifitests/src/com/android/server/wifi/HostapdHalTest.java b/service/tests/wifitests/src/com/android/server/wifi/HostapdHalTest.java
index 453187c..20fd4fd 100644
--- a/service/tests/wifitests/src/com/android/server/wifi/HostapdHalTest.java
+++ b/service/tests/wifitests/src/com/android/server/wifi/HostapdHalTest.java
@@ -46,6 +46,8 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import java.util.ArrayList;
+
 /**
  * Unit tests for HostapdHal
  */
@@ -74,6 +76,9 @@
             ArgumentCaptor.forClass(IServiceNotification.Stub.class);
     private ArgumentCaptor<IHostapd.IfaceParams> mIfaceParamsCaptor =
             ArgumentCaptor.forClass(IHostapd.IfaceParams.class);
+    private ArgumentCaptor<android.hardware.wifi.hostapd.V1_1.IHostapd.IfaceParams>
+            mIfaceParamsCaptorV1_1 =
+            ArgumentCaptor.forClass(android.hardware.wifi.hostapd.V1_1.IHostapd.IfaceParams.class);
     private ArgumentCaptor<IHostapd.NetworkParams> mNetworkParamsCaptor =
             ArgumentCaptor.forClass(IHostapd.NetworkParams.class);
     private InOrder mInOrder;
@@ -106,6 +111,7 @@
         mResources = new MockResources();
         mResources.setBoolean(R.bool.config_wifi_softap_acs_supported, false);
         mResources.setBoolean(R.bool.config_wifi_softap_ieee80211ac_supported, false);
+        mResources.setString(R.string.config_wifi_softap_acs_supported_channel_list, "");
 
         mStatusSuccess = createHostapdStatus(HostapdStatusCode.SUCCESS);
         mStatusFailure = createHostapdStatus(HostapdStatusCode.FAILURE_UNKNOWN);
@@ -441,6 +447,73 @@
     }
 
     /**
+     * Verifies the successful addition of access point.
+     * Verifies that channel info for ACS is handled.
+     */
+    @Test
+    public void testAddAccessPointSuccess_Psk_BandAny_WithACS_AcsChannels() throws Exception {
+        when(mServiceManagerMock.getTransport(anyString(), anyString()))
+                .thenReturn(IServiceManager.Transport.HWBINDER);
+        mIHostapdMockV1_1 = mock(android.hardware.wifi.hostapd.V1_1.IHostapd.class);
+        // Enable ACS and set available channels in the config.
+        final String acsChannelStr = "1,6,11-13,40";
+        android.hardware.wifi.hostapd.V1_1.IHostapd.AcsChannelRange channelRange1 =
+                new android.hardware.wifi.hostapd.V1_1.IHostapd.AcsChannelRange();
+        android.hardware.wifi.hostapd.V1_1.IHostapd.AcsChannelRange channelRange2 =
+                new android.hardware.wifi.hostapd.V1_1.IHostapd.AcsChannelRange();
+        android.hardware.wifi.hostapd.V1_1.IHostapd.AcsChannelRange channelRange3 =
+                new android.hardware.wifi.hostapd.V1_1.IHostapd.AcsChannelRange();
+        android.hardware.wifi.hostapd.V1_1.IHostapd.AcsChannelRange channelRange4 =
+                new android.hardware.wifi.hostapd.V1_1.IHostapd.AcsChannelRange();
+        channelRange1.start = channelRange1.end = 1;
+        channelRange2.start = channelRange2.end = 6;
+        channelRange3.start = 11;
+        channelRange3.end = 13;
+        channelRange4.start = channelRange4.end = 40;
+        ArrayList<android.hardware.wifi.hostapd.V1_1.IHostapd.AcsChannelRange> acsChannelRanges =
+                new ArrayList<>();
+        acsChannelRanges.add(channelRange1);
+        acsChannelRanges.add(channelRange2);
+        acsChannelRanges.add(channelRange3);
+        acsChannelRanges.add(channelRange4);
+        mResources.setBoolean(R.bool.config_wifi_softap_acs_supported, true);
+        mResources.setString(R.string.config_wifi_softap_acs_supported_channel_list, acsChannelStr);
+        mHostapdHal = new HostapdHalSpy();
+
+        when(mIHostapdMockV1_1.addAccessPoint_1_1(
+                mIfaceParamsCaptorV1_1.capture(), mNetworkParamsCaptor.capture()))
+                .thenReturn(mStatusSuccess);
+
+        executeAndValidateInitializationSequenceV1_1(false);
+
+        WifiConfiguration configuration = new WifiConfiguration();
+        configuration.SSID = NETWORK_SSID;
+        configuration.hiddenSSID = false;
+        configuration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA2_PSK);
+        configuration.preSharedKey = NETWORK_PSK;
+        configuration.apBand = WifiConfiguration.AP_BAND_ANY;
+
+        assertTrue(mHostapdHal.addAccessPoint(IFACE_NAME, configuration, mSoftApListener));
+        verify(mIHostapdMockV1_1).addAccessPoint_1_1(any(), any());
+
+        assertEquals(IFACE_NAME, mIfaceParamsCaptorV1_1.getValue().V1_0.ifaceName);
+        assertTrue(mIfaceParamsCaptorV1_1.getValue().V1_0.hwModeParams.enable80211N);
+        assertFalse(mIfaceParamsCaptorV1_1.getValue().V1_0.hwModeParams.enable80211AC);
+        assertEquals(IHostapd.Band.BAND_ANY,
+                mIfaceParamsCaptorV1_1.getValue().V1_0.channelParams.band);
+        assertTrue(mIfaceParamsCaptorV1_1.getValue().V1_0.channelParams.enableAcs);
+        assertTrue(mIfaceParamsCaptorV1_1.getValue().V1_0.channelParams.acsShouldExcludeDfs);
+        assertEquals(acsChannelRanges,
+                mIfaceParamsCaptorV1_1.getValue().channelParams.acsChannelRanges);
+
+        assertEquals(NativeUtil.stringToByteArrayList(NETWORK_SSID),
+                mNetworkParamsCaptor.getValue().ssid);
+        assertFalse(mNetworkParamsCaptor.getValue().isHidden);
+        assertEquals(IHostapd.EncryptionType.WPA2, mNetworkParamsCaptor.getValue().encryptionType);
+        assertEquals(NETWORK_PSK, mNetworkParamsCaptor.getValue().pskPassphrase);
+    }
+
+    /**
      * Verifies the failure handling in addition of access point with an invalid band.
      */
     @Test
@@ -537,6 +610,9 @@
         when(mServiceManagerMock.getTransport(anyString(), anyString()))
                 .thenReturn(IServiceManager.Transport.HWBINDER);
         mIHostapdMockV1_1 = mock(android.hardware.wifi.hostapd.V1_1.IHostapd.class);
+        when(mIHostapdMockV1_1.addAccessPoint_1_1(
+                mIfaceParamsCaptorV1_1.capture(), mNetworkParamsCaptor.capture()))
+                .thenReturn(mStatusSuccess);
         executeAndValidateInitializationSequenceV1_1(false);
 
         WifiConfiguration configuration = new WifiConfiguration();
@@ -544,7 +620,7 @@
         configuration.apBand = WifiConfiguration.AP_BAND_2GHZ;
 
         assertTrue(mHostapdHal.addAccessPoint(IFACE_NAME, configuration, mSoftApListener));
-        verify(mIHostapdMock).addAccessPoint(any(), any());
+        verify(mIHostapdMockV1_1).addAccessPoint_1_1(any(), any());
 
         // Trigger on failure.
         mIHostapdCallback.onFailure(IFACE_NAME);