Merge "Support for hotspot client visibility."
diff --git a/api/system-current.txt b/api/system-current.txt
index 79650fc..88f0379 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -4733,6 +4733,13 @@
     field @Deprecated public byte id;
   }
 
+  public final class WifiClient implements android.os.Parcelable {
+    method public int describeContents();
+    method @NonNull public android.net.MacAddress getMacAddress();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.net.wifi.WifiClient> CREATOR;
+  }
+
   @Deprecated public class WifiConfiguration implements android.os.Parcelable {
     method @Deprecated public boolean hasNoInternetAccess();
     method @Deprecated public boolean isEphemeral();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotControllerImpl.java
index 8f201c7..a3a9322 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotControllerImpl.java
@@ -19,6 +19,7 @@
 import android.app.ActivityManager;
 import android.content.Context;
 import android.net.ConnectivityManager;
+import android.net.wifi.WifiClient;
 import android.net.wifi.WifiManager;
 import android.os.Handler;
 import android.os.UserManager;
@@ -29,11 +30,13 @@
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.List;
 
 import javax.inject.Inject;
 import javax.inject.Singleton;
 
 /**
+ * Controller used to retrieve information related to a hotspot.
  */
 @Singleton
 public class HotspotControllerImpl implements HotspotController, WifiManager.SoftApCallback {
@@ -48,11 +51,12 @@
     private final Context mContext;
 
     private int mHotspotState;
-    private int mNumConnectedDevices;
+    private volatile int mNumConnectedDevices;
     private boolean mWaitingForTerminalState;
     private boolean mListening;
 
     /**
+     * Controller used to retrieve information related to a hotspot.
      */
     @Inject
     public HotspotControllerImpl(Context context, @MainHandler Handler mainHandler) {
@@ -96,7 +100,6 @@
     /**
      * Adds {@code callback} to the controller. The controller will update the callback on state
      * changes. It will immediately trigger the callback added to notify current state.
-     * @param callback
      */
     @Override
     public void addCallback(Callback callback) {
@@ -110,8 +113,8 @@
                         mWifiManager.registerSoftApCallback(this, mMainHandler);
                     } else {
                         // mWifiManager#registerSoftApCallback triggers a call to
-                        // onNumClientsChanged on the Main Handler. In order to always update the
-                        // callback on added, we make this call when adding callbacks after the
+                        // onConnectedClientsChanged on the Main Handler. In order to always update
+                        // the callback on added, we make this call when adding callbacks after the
                         // first.
                         mMainHandler.post(() ->
                                 callback.onHotspotChanged(isHotspotEnabled(),
@@ -232,8 +235,8 @@
     }
 
     @Override
-    public void onNumClientsChanged(int numConnectedDevices) {
-        mNumConnectedDevices = numConnectedDevices;
+    public void onConnectedClientsChanged(List<WifiClient> clients) {
+        mNumConnectedDevices = clients.size();
         fireHotspotChangedCallback();
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HotspotControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HotspotControllerImplTest.java
index 556ed5c..4ccf8a4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HotspotControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HotspotControllerImplTest.java
@@ -42,6 +42,8 @@
 import org.mockito.MockitoAnnotations;
 import org.mockito.invocation.InvocationOnMock;
 
+import java.util.ArrayList;
+
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper
@@ -67,7 +69,8 @@
         mContext.addMockSystemService(WifiManager.class, mWifiManager);
 
         doAnswer((InvocationOnMock invocation) -> {
-            ((WifiManager.SoftApCallback) invocation.getArgument(0)).onNumClientsChanged(1);
+            ((WifiManager.SoftApCallback) invocation.getArgument(0))
+                    .onConnectedClientsChanged(new ArrayList<>());
             return null;
         }).when(mWifiManager).registerSoftApCallback(any(WifiManager.SoftApCallback.class),
                 any(Handler.class));
diff --git a/wifi/java/android/net/wifi/ISoftApCallback.aidl b/wifi/java/android/net/wifi/ISoftApCallback.aidl
index b8d2971..8a252dd 100644
--- a/wifi/java/android/net/wifi/ISoftApCallback.aidl
+++ b/wifi/java/android/net/wifi/ISoftApCallback.aidl
@@ -16,6 +16,8 @@
 
 package android.net.wifi;
 
+import android.net.wifi.WifiClient;
+
 /**
  * Interface for Soft AP callback.
  *
@@ -36,9 +38,9 @@
     void onStateChanged(int state, int failureReason);
 
     /**
-     * Service to manager callback providing number of connected clients.
+     * Service to manager callback providing connected client's information.
      *
-     * @param numClients number of connected clients
+     * @param clients the currently connected clients
      */
-    void onNumClientsChanged(int numClients);
+    void onConnectedClientsChanged(in List<WifiClient> clients);
 }
diff --git a/wifi/java/android/net/wifi/WifiClient.aidl b/wifi/java/android/net/wifi/WifiClient.aidl
new file mode 100644
index 0000000..accdadd
--- /dev/null
+++ b/wifi/java/android/net/wifi/WifiClient.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * 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.
+ */
+
+package android.net.wifi;
+
+@JavaOnlyStableParcelable parcelable WifiClient;
\ No newline at end of file
diff --git a/wifi/java/android/net/wifi/WifiClient.java b/wifi/java/android/net/wifi/WifiClient.java
new file mode 100644
index 0000000..3e09580
--- /dev/null
+++ b/wifi/java/android/net/wifi/WifiClient.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * 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.
+ */
+
+package android.net.wifi;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.net.MacAddress;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.Objects;
+
+/** @hide */
+@SystemApi
+public final class WifiClient implements Parcelable {
+
+    private final MacAddress mMacAddress;
+
+    /**
+     * The mac address of this client.
+     */
+    @NonNull
+    public MacAddress getMacAddress() {
+        return mMacAddress;
+    }
+
+    private WifiClient(Parcel in) {
+        mMacAddress = in.readParcelable(null);
+    }
+
+    /** @hide */
+    public WifiClient(@NonNull MacAddress macAddress) {
+        Preconditions.checkNotNull(macAddress, "mMacAddress must not be null.");
+
+        this.mMacAddress = macAddress;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeParcelable(mMacAddress, flags);
+    }
+
+    @NonNull
+    public static final Creator<WifiClient> CREATOR = new Creator<WifiClient>() {
+        public WifiClient createFromParcel(Parcel in) {
+            return new WifiClient(in);
+        }
+
+        public WifiClient[] newArray(int size) {
+            return new WifiClient[size];
+        }
+    };
+
+    @NonNull
+    @Override
+    public String toString() {
+        return "WifiClient{"
+                + "mMacAddress=" + mMacAddress
+                + '}';
+    }
+
+    @Override
+    public boolean equals(@NonNull Object o) {
+        if (this == o) return true;
+        if (!(o instanceof WifiClient)) return false;
+        WifiClient client = (WifiClient) o;
+        return mMacAddress.equals(client.mMacAddress);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mMacAddress);
+    }
+}
+
+
diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java
index f626b0c..3b7690b 100644
--- a/wifi/java/android/net/wifi/WifiManager.java
+++ b/wifi/java/android/net/wifi/WifiManager.java
@@ -3254,21 +3254,22 @@
         /**
          * Called when soft AP state changes.
          *
-         * @param state new new AP state. One of {@link #WIFI_AP_STATE_DISABLED},
-         *        {@link #WIFI_AP_STATE_DISABLING}, {@link #WIFI_AP_STATE_ENABLED},
-         *        {@link #WIFI_AP_STATE_ENABLING}, {@link #WIFI_AP_STATE_FAILED}
+         * @param state         new new AP state. One of {@link #WIFI_AP_STATE_DISABLED},
+         *                      {@link #WIFI_AP_STATE_DISABLING}, {@link #WIFI_AP_STATE_ENABLED},
+         *                      {@link #WIFI_AP_STATE_ENABLING}, {@link #WIFI_AP_STATE_FAILED}
          * @param failureReason reason when in failed state. One of
-         *        {@link #SAP_START_FAILURE_GENERAL}, {@link #SAP_START_FAILURE_NO_CHANNEL}
+         *                      {@link #SAP_START_FAILURE_GENERAL},
+         *                      {@link #SAP_START_FAILURE_NO_CHANNEL}
          */
-        public abstract void onStateChanged(@WifiApState int state,
+        void onStateChanged(@WifiApState int state,
                 @SapStartFailure int failureReason);
 
         /**
-         * Called when number of connected clients to soft AP changes.
+         * Called when the connected clients to soft AP changes.
          *
-         * @param numClients number of connected clients
+         * @param clients the currently connected clients
          */
-        public abstract void onNumClientsChanged(int numClients);
+        void onConnectedClientsChanged(@NonNull List<WifiClient> clients);
     }
 
     /**
@@ -3291,18 +3292,21 @@
                 Log.v(TAG, "SoftApCallbackProxy: onStateChanged: state=" + state
                         + ", failureReason=" + failureReason);
             }
+
             mHandler.post(() -> {
                 mCallback.onStateChanged(state, failureReason);
             });
         }
 
         @Override
-        public void onNumClientsChanged(int numClients) {
+        public void onConnectedClientsChanged(List<WifiClient> clients) {
             if (mVerboseLoggingEnabled) {
-                Log.v(TAG, "SoftApCallbackProxy: onNumClientsChanged: numClients=" + numClients);
+                Log.v(TAG, "SoftApCallbackProxy: onConnectedClientsChanged: clients="
+                        + clients.size() + " clients");
             }
+
             mHandler.post(() -> {
-                mCallback.onNumClientsChanged(numClients);
+                mCallback.onConnectedClientsChanged(clients);
             });
         }
     }
diff --git a/wifi/tests/Android.mk b/wifi/tests/Android.mk
index 401b652..3453d6e 100644
--- a/wifi/tests/Android.mk
+++ b/wifi/tests/Android.mk
@@ -49,6 +49,7 @@
     core-test-rules \
     guava \
     mockito-target-minus-junit4 \
+    net-tests-utils \
     frameworks-base-testutils \
     truth-prebuilt \
 
diff --git a/wifi/tests/src/android/net/wifi/WifiClientTest.java b/wifi/tests/src/android/net/wifi/WifiClientTest.java
new file mode 100644
index 0000000..42cab55
--- /dev/null
+++ b/wifi/tests/src/android/net/wifi/WifiClientTest.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * 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.
+ */
+
+package android.net.wifi;
+
+import static com.android.testutils.MiscAssertsKt.assertFieldCountEquals;
+import static com.android.testutils.ParcelUtilsKt.assertParcelSane;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+
+import android.net.MacAddress;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+
+/**
+ * Unit tests for {@link android.net.wifi.WifiClient}.
+ */
+@SmallTest
+public class WifiClientTest {
+    private static final String INTERFACE_NAME = "wlan0";
+    private static final String MAC_ADDRESS_STRING = "00:0a:95:9d:68:16";
+    private static final MacAddress MAC_ADDRESS = MacAddress.fromString(MAC_ADDRESS_STRING);
+
+    /**
+     *  Verify parcel write/read with WifiClient.
+     */
+    @Test
+    public void testWifiClientParcelWriteRead() throws Exception {
+        WifiClient writeWifiClient = new WifiClient(MAC_ADDRESS);
+
+        assertParcelSane(writeWifiClient, 1);
+    }
+
+    /**
+     *  Verify equals with WifiClient.
+     */
+    @Test
+    public void testWifiClientEquals() throws Exception {
+        WifiClient writeWifiClient = new WifiClient(MAC_ADDRESS);
+        WifiClient writeWifiClientEquals = new WifiClient(MAC_ADDRESS);
+
+        assertEquals(writeWifiClient, writeWifiClientEquals);
+        assertEquals(writeWifiClient.hashCode(), writeWifiClientEquals.hashCode());
+        assertFieldCountEquals(1, WifiClient.class);
+    }
+
+    /**
+     *  Verify not-equals with WifiClient.
+     */
+    @Test
+    public void testWifiClientNotEquals() throws Exception {
+        final MacAddress macAddressNotEquals = MacAddress.fromString("00:00:00:00:00:00");
+        WifiClient writeWifiClient = new WifiClient(MAC_ADDRESS);
+        WifiClient writeWifiClientNotEquals = new WifiClient(macAddressNotEquals);
+
+        assertNotEquals(writeWifiClient, writeWifiClientNotEquals);
+        assertNotEquals(writeWifiClient.hashCode(), writeWifiClientNotEquals.hashCode());
+    }
+}
diff --git a/wifi/tests/src/android/net/wifi/WifiManagerTest.java b/wifi/tests/src/android/net/wifi/WifiManagerTest.java
index 881dcbb..8d0579b 100644
--- a/wifi/tests/src/android/net/wifi/WifiManagerTest.java
+++ b/wifi/tests/src/android/net/wifi/WifiManagerTest.java
@@ -99,8 +99,7 @@
     private static final String[] TEST_MAC_ADDRESSES = {"da:a1:19:0:0:0"};
 
     @Mock Context mContext;
-    @Mock
-    android.net.wifi.IWifiManager mWifiService;
+    @Mock android.net.wifi.IWifiManager mWifiService;
     @Mock ApplicationInfo mApplicationInfo;
     @Mock WifiConfiguration mApConfig;
     @Mock SoftApCallback mSoftApCallback;
@@ -115,7 +114,8 @@
     private TestLooper mLooper;
     private WifiManager mWifiManager;
 
-    @Before public void setUp() throws Exception {
+    @Before
+    public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
         mLooper = new TestLooper();
         mHandler = spy(new Handler(mLooper.getLooper()));
@@ -210,7 +210,7 @@
     @Test
     public void testCreationOfLocalOnlyHotspotSubscription() throws Exception {
         try (WifiManager.LocalOnlyHotspotSubscription sub =
-                mWifiManager.new LocalOnlyHotspotSubscription()) {
+                     mWifiManager.new LocalOnlyHotspotSubscription()) {
             sub.close();
         }
     }
@@ -752,17 +752,17 @@
      * Verify client-provided callback is being called through callback proxy
      */
     @Test
-    public void softApCallbackProxyCallsOnNumClientsChanged() throws Exception {
+    public void softApCallbackProxyCallsOnConnectedClientsChanged() throws Exception {
         ArgumentCaptor<ISoftApCallback.Stub> callbackCaptor =
                 ArgumentCaptor.forClass(ISoftApCallback.Stub.class);
         mWifiManager.registerSoftApCallback(mSoftApCallback, mHandler);
         verify(mWifiService).registerSoftApCallback(any(IBinder.class), callbackCaptor.capture(),
                 anyInt());
 
-        final int testNumClients = 3;
-        callbackCaptor.getValue().onNumClientsChanged(testNumClients);
+        final List<WifiClient> testClients = new ArrayList();
+        callbackCaptor.getValue().onConnectedClientsChanged(testClients);
         mLooper.dispatchAll();
-        verify(mSoftApCallback).onNumClientsChanged(testNumClients);
+        verify(mSoftApCallback).onConnectedClientsChanged(testClients);
     }
 
     /*
@@ -776,14 +776,14 @@
         verify(mWifiService).registerSoftApCallback(any(IBinder.class), callbackCaptor.capture(),
                 anyInt());
 
-        final int testNumClients = 5;
+        final List<WifiClient> testClients = new ArrayList();
         callbackCaptor.getValue().onStateChanged(WIFI_AP_STATE_ENABLING, 0);
-        callbackCaptor.getValue().onNumClientsChanged(testNumClients);
+        callbackCaptor.getValue().onConnectedClientsChanged(testClients);
         callbackCaptor.getValue().onStateChanged(WIFI_AP_STATE_FAILED, SAP_START_FAILURE_GENERAL);
 
         mLooper.dispatchAll();
         verify(mSoftApCallback).onStateChanged(WIFI_AP_STATE_ENABLING, 0);
-        verify(mSoftApCallback).onNumClientsChanged(testNumClients);
+        verify(mSoftApCallback).onConnectedClientsChanged(testClients);
         verify(mSoftApCallback).onStateChanged(WIFI_AP_STATE_FAILED, SAP_START_FAILURE_GENERAL);
     }
 
@@ -1020,8 +1020,8 @@
         verifyNoMoreInteractions(mWifiService);
     }
 
-   /**
-i     * Verify that a call to cancel WPS immediately returns a failure.
+    /**
+     * Verify that a call to cancel WPS immediately returns a failure.
      */
     @Test
     public void testCancelWpsImmediatelyFailsWithCallback() {
@@ -1324,7 +1324,6 @@
 
     /**
      * Verify getting the factory MAC address.
-     * @throws Exception
      */
     @Test
     public void testGetFactoryMacAddress() throws Exception {
@@ -1371,7 +1370,6 @@
 
     /**
      * Test behavior of isEnhancedOpenSupported
-     * @throws Exception
      */
     @Test
     public void testIsEnhancedOpenSupported() throws Exception {
@@ -1385,7 +1383,6 @@
 
     /**
      * Test behavior of isWpa3SaeSupported
-     * @throws Exception
      */
     @Test
     public void testIsWpa3SaeSupported() throws Exception {
@@ -1399,7 +1396,6 @@
 
     /**
      * Test behavior of isWpa3SuiteBSupported
-     * @throws Exception
      */
     @Test
     public void testIsWpa3SuiteBSupported() throws Exception {
@@ -1413,7 +1409,6 @@
 
     /**
      * Test behavior of isEasyConnectSupported
-     * @throws Exception
      */
     @Test
     public void testIsEasyConnectSupported() throws Exception {
@@ -1427,7 +1422,6 @@
 
     /**
      * Test behavior of {@link WifiManager#addNetwork(WifiConfiguration)}
-     * @throws Exception
      */
     @Test
     public void testAddNetwork() throws Exception {
@@ -1444,7 +1438,6 @@
 
     /**
      * Test behavior of {@link WifiManager#addNetwork(WifiConfiguration)}
-     * @throws Exception
      */
     @Test
     public void testUpdateNetwork() throws Exception {
@@ -1466,7 +1459,6 @@
 
     /**
      * Test behavior of {@link WifiManager#enableNetwork(int, boolean)}
-     * @throws Exception
      */
     @Test
     public void testEnableNetwork() throws Exception {
@@ -1478,7 +1470,6 @@
 
     /**
      * Test behavior of {@link WifiManager#disableNetwork(int)}
-     * @throws Exception
      */
     @Test
     public void testDisableNetwork() throws Exception {
@@ -1500,7 +1491,6 @@
 
     /**
      * Test behavior of {@link WifiManager#disconnect()}
-     * @throws Exception
      */
     @Test
     public void testDisconnect() throws Exception {
@@ -1511,7 +1501,6 @@
 
     /**
      * Test behavior of {@link WifiManager#reconnect()}
-     * @throws Exception
      */
     @Test
     public void testReconnect() throws Exception {
@@ -1522,7 +1511,6 @@
 
     /**
      * Test behavior of {@link WifiManager#reassociate()}
-     * @throws Exception
      */
     @Test
     public void testReassociate() throws Exception {
@@ -1533,7 +1521,6 @@
 
     /**
      * Test behavior of {@link WifiManager#getSupportedFeatures()}
-     * @throws Exception
      */
     @Test
     public void testGetSupportedFeatures() throws Exception {
@@ -1560,7 +1547,6 @@
 
     /**
      * Test behavior of {@link WifiManager#getControllerActivityEnergyInfo()}
-     * @throws Exception
      */
     @Test
     public void testGetControllerActivityEnergyInfo() throws Exception {
@@ -1573,7 +1559,6 @@
 
     /**
      * Test behavior of {@link WifiManager#getConnectionInfo()}
-     * @throws Exception
      */
     @Test
     public void testGetConnectionInfo() throws Exception {
@@ -1585,7 +1570,6 @@
 
     /**
      * Test behavior of {@link WifiManager#isDualModeSupported()} ()}
-     * @throws Exception
      */
     @Test
     public void testIsDualModeSupported() throws Exception {
@@ -1596,7 +1580,6 @@
 
     /**
      * Test behavior of {@link WifiManager#isDualBandSupported()}
-     * @throws Exception
      */
     @Test
     public void testIsDualBandSupported() throws Exception {
@@ -1607,7 +1590,6 @@
 
     /**
      * Test behavior of {@link WifiManager#getDhcpInfo()}
-     * @throws Exception
      */
     @Test
     public void testGetDhcpInfo() throws Exception {
@@ -1620,7 +1602,6 @@
 
     /**
      * Test behavior of {@link WifiManager#setWifiEnabled(boolean)}
-     * @throws Exception
      */
     @Test
     public void testSetWifiEnabled() throws Exception {