SupplicantP2pIfaceHal: Support for P2P client list and save config

Also,
Moved mac address to long conversion methods to NativeUtil to help with
unit tests.

Bug: 36042785
Test: Added unit tests.
Change-Id: I6c1a78abf8c0f8159195c2542dfe217a7b954df0
diff --git a/service/java/com/android/server/wifi/SupplicantP2pIfaceHal.java b/service/java/com/android/server/wifi/SupplicantP2pIfaceHal.java
index ba7064b..6a90f87 100644
--- a/service/java/com/android/server/wifi/SupplicantP2pIfaceHal.java
+++ b/service/java/com/android/server/wifi/SupplicantP2pIfaceHal.java
@@ -45,9 +45,11 @@
 import java.nio.ByteBuffer;
 import java.nio.ByteOrder;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
+import java.util.stream.Collectors;
 
 /**
  * Native calls sending requests to the P2P Hals, and callbacks for receiving P2P events
@@ -1998,6 +2000,96 @@
             return result.isSuccess();
         }
     }
+
+    /**
+     * Set the client list for the provided network.
+     *
+     * @param networkId Id of the network.
+     * @param clientListStr Space separated list of clients.
+     * @return true, if operation was successful.
+     */
+    public boolean setClientList(int networkId, String clientListStr) {
+        synchronized (mLock) {
+            if (!checkSupplicantP2pIfaceAndLogFailure("setClientList")) return false;
+            if (TextUtils.isEmpty(clientListStr)) {
+                Log.e(TAG, "Invalid client list");
+                return false;
+            }
+            ISupplicantP2pNetwork network = getNetwork(networkId);
+            if (network == null) {
+                Log.e(TAG, "Invalid network id ");
+                return false;
+            }
+            SupplicantResult<Void> result = new SupplicantResult(
+                    "setClientList(" + networkId + ", " + clientListStr + ")");
+            ArrayList<byte[]> clients = new ArrayList<>();
+            for (String clientStr : Arrays.asList(clientListStr.split("\\s+"))) {
+                clients.add(NativeUtil.macAddressToByteArray(clientStr));
+            }
+            try {
+                result.setResult(network.setClientList(clients));
+            } catch (RemoteException e) {
+                Log.e(TAG, "ISupplicantP2pIface exception: " + e);
+                supplicantServiceDiedHandler();
+            }
+            return result.isSuccess();
+        }
+    }
+
+    /**
+     * Set the client list for the provided network.
+     *
+     * @param networkId Id of the network.
+     * @return  Space separated list of clients if successfull, null otherwise.
+     */
+    public String getClientList(int networkId) {
+        synchronized (mLock) {
+            if (!checkSupplicantP2pIfaceAndLogFailure("getClientList")) return null;
+            ISupplicantP2pNetwork network = getNetwork(networkId);
+            if (network == null) {
+                Log.e(TAG, "Invalid network id ");
+                return null;
+            }
+            SupplicantResult<ArrayList> result = new SupplicantResult(
+                    "getClientList(" + networkId + ")");
+            try {
+                network.getClientList(
+                        (SupplicantStatus status, ArrayList<byte[]> clients) -> {
+                            result.setResult(status, clients);
+                        });
+            } catch (RemoteException e) {
+                Log.e(TAG, "ISupplicantP2pIface exception: " + e);
+                supplicantServiceDiedHandler();
+            }
+            if (!result.isSuccess()) {
+                return null;
+            }
+            ArrayList<byte[]> clients = result.getResult();
+            return clients.stream()
+                    .map(NativeUtil::macAddressFromByteArray)
+                    .collect(Collectors.joining(" "));
+        }
+    }
+
+    /**
+     * Persist the current configurations to disk.
+     *
+     * @return true, if operation was successful.
+     */
+    public boolean saveConfig() {
+        synchronized (mLock) {
+            if (!checkSupplicantP2pIfaceAndLogFailure("saveConfig")) return false;
+            SupplicantResult<Void> result = new SupplicantResult("saveConfig()");
+            try {
+                result.setResult(mISupplicantP2pIface.saveConfig());
+            } catch (RemoteException e) {
+                Log.e(TAG, "ISupplicantP2pIface exception: " + e);
+                supplicantServiceDiedHandler();
+            }
+            return result.isSuccess();
+        }
+    }
+
     /** Container class allowing propagation of status and/or value
      * from callbacks.
      *
diff --git a/service/java/com/android/server/wifi/SupplicantStaIfaceHal.java b/service/java/com/android/server/wifi/SupplicantStaIfaceHal.java
index 8f141e6..8308b4c 100644
--- a/service/java/com/android/server/wifi/SupplicantStaIfaceHal.java
+++ b/service/java/com/android/server/wifi/SupplicantStaIfaceHal.java
@@ -1732,18 +1732,6 @@
             }
         }
 
-        /**
-         * Helper utility to convert the bssid bytes to long.
-         */
-        private Long toLongBssid(byte[] bssidBytes) {
-            try {
-                return ByteBufferReader.readInteger(
-                        ByteBuffer.wrap(bssidBytes), ByteOrder.BIG_ENDIAN, bssidBytes.length);
-            } catch (BufferUnderflowException | IllegalArgumentException e) {
-                return 0L;
-            }
-        }
-
         @Override
         public void onNetworkAdded(int id) {
             logCallback("onNetworkAdded");
@@ -1794,7 +1782,7 @@
                 addAnqpElementToMap(elementsMap, HSConnCapability, hs20Data.connectionCapability);
                 addAnqpElementToMap(elementsMap, HSOSUProviders, hs20Data.osuProvidersList);
                 mWifiMonitor.broadcastAnqpDoneEvent(
-                        mIfaceName, new AnqpEvent(toLongBssid(bssid), elementsMap));
+                        mIfaceName, new AnqpEvent(NativeUtil.macAddressToLong(bssid), elementsMap));
             }
         }
 
@@ -1805,7 +1793,7 @@
             synchronized (mLock) {
                 mWifiMonitor.broadcastIconDoneEvent(
                         mIfaceName,
-                        new IconEvent(toLongBssid(bssid), fileName, data.size(),
+                        new IconEvent(NativeUtil.macAddressToLong(bssid), fileName, data.size(),
                                 NativeUtil.byteArrayFromArrayList(data)));
             }
         }
@@ -1815,7 +1803,8 @@
             logCallback("onHs20SubscriptionRemediation");
             synchronized (mLock) {
                 mWifiMonitor.broadcastWnmEvent(
-                        mIfaceName, new WnmData(toLongBssid(bssid), url, osuMethod));
+                        mIfaceName,
+                        new WnmData(NativeUtil.macAddressToLong(bssid), url, osuMethod));
             }
         }
 
@@ -1826,8 +1815,8 @@
             synchronized (mLock) {
                 mWifiMonitor.broadcastWnmEvent(
                         mIfaceName,
-                        new WnmData(toLongBssid(bssid), url, reasonCode == WnmData.ESS,
-                                reAuthDelayInSec));
+                        new WnmData(NativeUtil.macAddressToLong(bssid), url,
+                                reasonCode == WnmData.ESS, reAuthDelayInSec));
             }
         }
 
diff --git a/service/java/com/android/server/wifi/WifiNative.java b/service/java/com/android/server/wifi/WifiNative.java
index 87ace78..fb8c0e6 100644
--- a/service/java/com/android/server/wifi/WifiNative.java
+++ b/service/java/com/android/server/wifi/WifiNative.java
@@ -1276,29 +1276,33 @@
     }
 
     /**
-     * Get P2P client list for the given network ID.
-     * @return true on success, false otherwise.
+     * Set the client list for the provided network.
+     *
+     * @param netId Id of the network.
+     * @return  Space separated list of clients if successfull, null otherwise.
      */
     public String getP2pClientList(int netId) {
-        // TODO(b/36042785): Add HIDL method.
-        return null;
+        return mSupplicantP2pIfaceHal.getClientList(netId);
     }
 
     /**
-     * Set P2P client list for the given network ID.
-     * @return true on success, false otherwise.
+     * Set the client list for the provided network.
+     *
+     * @param netId Id of the network.
+     * @param list Space separated list of clients.
+     * @return true, if operation was successful.
      */
     public boolean setP2pClientList(int netId, String list) {
-        // TODO(b/36042785): Add HIDL method.
-        return false;
+        return mSupplicantP2pIfaceHal.setClientList(netId, list);
     }
 
     /**
-     * Save the current configuration to wpa_supplicant.conf.
+     * Save the current configuration to p2p_supplicant.conf.
+     *
+     * @return true on success, false otherwise.
      */
     public boolean saveConfig() {
-        // TODO(b/36042785): Add HIDL method.
-        return false;
+        return mSupplicantP2pIfaceHal.saveConfig();
     }
 
     /********************************************************
diff --git a/service/java/com/android/server/wifi/util/NativeUtil.java b/service/java/com/android/server/wifi/util/NativeUtil.java
index 50f32fa..a13b682 100644
--- a/service/java/com/android/server/wifi/util/NativeUtil.java
+++ b/service/java/com/android/server/wifi/util/NativeUtil.java
@@ -18,9 +18,13 @@
 
 import android.text.TextUtils;
 
+import com.android.server.wifi.ByteBufferReader;
+
 import libcore.util.HexEncoding;
 
+import java.nio.BufferUnderflowException;
 import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
 import java.nio.CharBuffer;
 import java.nio.charset.CharacterCodingException;
 import java.nio.charset.CharsetDecoder;
@@ -156,6 +160,28 @@
     }
 
     /**
+     * Converts an array of 6 bytes to a long representing the MAC address.
+     *
+     * @param macArray byte array of mac values, must have length 6
+     * @return Long value of the mac address.
+     * @throws IllegalArgumentException for malformed inputs.
+     */
+    public static Long macAddressToLong(byte[] macArray) {
+        if (macArray == null) {
+            throw new IllegalArgumentException("null mac bytes");
+        }
+        if (macArray.length != MAC_LENGTH) {
+            throw new IllegalArgumentException("invalid macArray length: " + macArray.length);
+        }
+        try {
+            return ByteBufferReader.readInteger(
+                    ByteBuffer.wrap(macArray), ByteOrder.BIG_ENDIAN, macArray.length);
+        } catch (BufferUnderflowException | IllegalArgumentException e) {
+            throw new IllegalArgumentException("invalid macArray");
+        }
+    }
+
+    /**
      * Remove enclosed quotes of the provided string.
      *
      * @param quotedStr String to be unquoted.
diff --git a/tests/wifitests/src/com/android/server/wifi/SupplicantP2pIfaceHalTest.java b/tests/wifitests/src/com/android/server/wifi/SupplicantP2pIfaceHalTest.java
index 19dc521..9f1607d 100644
--- a/tests/wifitests/src/com/android/server/wifi/SupplicantP2pIfaceHalTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/SupplicantP2pIfaceHalTest.java
@@ -20,6 +20,8 @@
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.app.test.MockAnswerUtil.AnswerWithArguments;
@@ -2259,6 +2261,168 @@
     }
 
     /**
+     * Sunny day scenario for setClientList()
+     */
+    @Test
+    public void testSetClientList() throws Exception {
+        int testNetworkId = 5;
+        final String client1 = mGroupOwnerMacAddress;
+        final String client2 = mPeerMacAddress;
+
+        executeAndValidateInitializationSequence(false, false, false);
+        doAnswer(new AnswerWithArguments() {
+            public void answer(final int networkId, ISupplicantP2pIface.getNetworkCallback cb) {
+                if (networkId == testNetworkId) {
+                    cb.onValues(mStatusSuccess, mISupplicantP2pNetworkMock);
+                } else {
+                    cb.onValues(mStatusFailure, null);
+                }
+                return;
+            }
+        }).when(mISupplicantP2pIfaceMock)
+                .getNetwork(anyInt(), any(ISupplicantP2pIface.getNetworkCallback.class));
+        when(mISupplicantP2pNetworkMock.setClientList(any(ArrayList.class)))
+                .thenReturn(mStatusSuccess);
+
+        String clientList = client1 + " " + client2;
+        assertTrue(mDut.setClientList(testNetworkId, clientList));
+        verify(mISupplicantP2pIfaceMock)
+                .getNetwork(anyInt(), any(ISupplicantP2pIface.getNetworkCallback.class));
+        ArgumentCaptor<ArrayList> capturedClients = ArgumentCaptor.forClass(ArrayList.class);
+        verify(mISupplicantP2pNetworkMock).setClientList(capturedClients.capture());
+
+        // Convert these to long to help with comparisons.
+        ArrayList<byte[]> clients = capturedClients.getValue();
+        ArrayList<Long> expectedClients = new ArrayList<Long>() {{
+                add(NativeUtil.macAddressToLong(mGroupOwnerMacAddressBytes));
+                add(NativeUtil.macAddressToLong(mPeerMacAddressBytes));
+            }};
+        ArrayList<Long> receivedClients = new ArrayList<Long>();
+        for (byte[] client : clients) {
+            receivedClients.add(NativeUtil.macAddressToLong(client));
+        }
+        assertEquals(expectedClients, receivedClients);
+    }
+
+    /**
+     * Failure scenario for setClientList() when getNetwork returns null.
+     */
+    @Test
+    public void testSetClientListFailureDueToGetNetwork() throws Exception {
+        int testNetworkId = 5;
+        final String client1 = mGroupOwnerMacAddress;
+        final String client2 = mPeerMacAddress;
+
+        executeAndValidateInitializationSequence(false, false, false);
+        doAnswer(new AnswerWithArguments() {
+            public void answer(final int networkId, ISupplicantP2pIface.getNetworkCallback cb) {
+                cb.onValues(mStatusFailure, null);
+                return;
+            }
+        }).when(mISupplicantP2pIfaceMock)
+                .getNetwork(anyInt(), any(ISupplicantP2pIface.getNetworkCallback.class));
+        when(mISupplicantP2pNetworkMock.setClientList(any(ArrayList.class)))
+                .thenReturn(mStatusSuccess);
+
+        String clientList = client1 + " " + client2;
+        assertFalse(mDut.setClientList(testNetworkId, clientList));
+        verify(mISupplicantP2pIfaceMock)
+                .getNetwork(anyInt(), any(ISupplicantP2pIface.getNetworkCallback.class));
+        verify(mISupplicantP2pNetworkMock, never()).setClientList(any(ArrayList.class));
+    }
+
+    /**
+     * Sunny day scenario for getClientList()
+     */
+    @Test
+    public void testGetClientList() throws Exception {
+        int testNetworkId = 5;
+        final String client1 = mGroupOwnerMacAddress;
+        final String client2 = mPeerMacAddress;
+
+        executeAndValidateInitializationSequence(false, false, false);
+        doAnswer(new AnswerWithArguments() {
+            public void answer(final int networkId, ISupplicantP2pIface.getNetworkCallback cb) {
+                if (networkId == testNetworkId) {
+                    cb.onValues(mStatusSuccess, mISupplicantP2pNetworkMock);
+                } else {
+                    cb.onValues(mStatusFailure, null);
+                }
+                return;
+            }
+        }).when(mISupplicantP2pIfaceMock)
+                .getNetwork(anyInt(), any(ISupplicantP2pIface.getNetworkCallback.class));
+        doAnswer(new AnswerWithArguments() {
+            public void answer(ISupplicantP2pNetwork.getClientListCallback cb) {
+                ArrayList<byte[]> clients = new ArrayList<byte[]>() {{
+                        add(mGroupOwnerMacAddressBytes);
+                        add(mPeerMacAddressBytes);
+                    }};
+                cb.onValues(mStatusSuccess, clients);
+                return;
+            }
+        }).when(mISupplicantP2pNetworkMock)
+                .getClientList(any(ISupplicantP2pNetwork.getClientListCallback.class));
+
+        String clientList = client1 + " " + client2;
+        assertEquals(clientList, mDut.getClientList(testNetworkId));
+        verify(mISupplicantP2pIfaceMock)
+                .getNetwork(anyInt(), any(ISupplicantP2pIface.getNetworkCallback.class));
+        verify(mISupplicantP2pNetworkMock)
+                .getClientList(any(ISupplicantP2pNetwork.getClientListCallback.class));
+    }
+
+    /**
+     * Failure scenario for getClientList() when getNetwork returns null.
+     */
+    @Test
+    public void testGetClientListFailureDueToGetNetwork() throws Exception {
+        int testNetworkId = 5;
+        final String client1 = mGroupOwnerMacAddress;
+        final String client2 = mPeerMacAddress;
+
+        executeAndValidateInitializationSequence(false, false, false);
+        doAnswer(new AnswerWithArguments() {
+            public void answer(final int networkId, ISupplicantP2pIface.getNetworkCallback cb) {
+                cb.onValues(mStatusFailure, null);
+                return;
+            }
+        }).when(mISupplicantP2pIfaceMock)
+                .getNetwork(anyInt(), any(ISupplicantP2pIface.getNetworkCallback.class));
+        doAnswer(new AnswerWithArguments() {
+            public void answer(ISupplicantP2pNetwork.getClientListCallback cb) {
+                ArrayList<byte[]> clients = new ArrayList<byte[]>() {{
+                        add(mGroupOwnerMacAddressBytes);
+                        add(mPeerMacAddressBytes);
+                    }};
+                cb.onValues(mStatusSuccess, clients);
+                return;
+            }
+        }).when(mISupplicantP2pNetworkMock)
+                .getClientList(any(ISupplicantP2pNetwork.getClientListCallback.class));
+
+        assertEquals(null, mDut.getClientList(testNetworkId));
+        verify(mISupplicantP2pIfaceMock)
+                .getNetwork(anyInt(), any(ISupplicantP2pIface.getNetworkCallback.class));
+        verify(mISupplicantP2pNetworkMock, never())
+                .getClientList(any(ISupplicantP2pNetwork.getClientListCallback.class));
+    }
+
+    /**
+     * Sunny day scenario for saveConfig()
+     */
+    @Test
+    public void testSaveConfig() throws Exception {
+        when(mISupplicantP2pIfaceMock.saveConfig()).thenReturn(mStatusSuccess);
+
+        // Should fail before initialization.
+        assertFalse(mDut.saveConfig());
+        executeAndValidateInitializationSequence(false, false, false);
+        assertTrue(mDut.saveConfig());
+        verify(mISupplicantP2pIfaceMock).saveConfig();
+    }
+
+    /**
      * Calls.initialize(), mocking various call back answers and verifying flow, asserting for the
      * expected result. Verifies if ISupplicantP2pIface manager is initialized or reset.
      * Each of the arguments will cause a different failure mode when set true.