Merge "Also update connected clients for local only tethering"
diff --git a/Tethering/src/com/android/networkstack/tethering/Tethering.java b/Tethering/src/com/android/networkstack/tethering/Tethering.java
index 2c91d10..62ae88c 100644
--- a/Tethering/src/com/android/networkstack/tethering/Tethering.java
+++ b/Tethering/src/com/android/networkstack/tethering/Tethering.java
@@ -140,8 +140,8 @@
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.HashSet;
 import java.util.Iterator;
-import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Set;
 import java.util.concurrent.Executor;
@@ -216,13 +216,12 @@
     private final ArrayMap<String, TetherState> mTetherStates;
     private final BroadcastReceiver mStateReceiver;
     private final Looper mLooper;
-    private final StateMachine mTetherMainSM;
+    private final TetherMainSM mTetherMainSM;
     private final OffloadController mOffloadController;
     private final UpstreamNetworkMonitor mUpstreamNetworkMonitor;
     // TODO: Figure out how to merge this and other downstream-tracking objects
     // into a single coherent structure.
-    // Use LinkedHashSet for predictable ordering order for ConnectedClientsTracker.
-    private final LinkedHashSet<IpServer> mForwardedDownstreams;
+    private final HashSet<IpServer> mForwardedDownstreams;
     private final VersionedBroadcastListener mCarrierConfigChange;
     private final TetheringDependencies mDeps;
     private final EntitlementManager mEntitlementMgr;
@@ -287,7 +286,7 @@
                 });
         mUpstreamNetworkMonitor = mDeps.getUpstreamNetworkMonitor(mContext, mTetherMainSM, mLog,
                 TetherMainSM.EVENT_UPSTREAM_CALLBACK);
-        mForwardedDownstreams = new LinkedHashSet<>();
+        mForwardedDownstreams = new HashSet<>();
 
         IntentFilter filter = new IntentFilter();
         filter.addAction(ACTION_CARRIER_CONFIG_CHANGED);
@@ -1423,6 +1422,7 @@
         //    interfaces.
         // 2) mNotifyList contains all state machines that may have outstanding tethering state
         //    that needs to be torn down.
+        // 3) Use mNotifyList for predictable ordering order for ConnectedClientsTracker.
         //
         // Because we excise interfaces immediately from mTetherStates, we must maintain mNotifyList
         // so that the garbage collector does not clean up the state machine before it has a chance
@@ -1459,6 +1459,15 @@
             setInitialState(mInitialState);
         }
 
+        /**
+         * Returns all downstreams that are serving clients, regardless of they are actually
+         * tethered or localOnly. This must be called on the tethering thread (not thread-safe).
+         */
+        @NonNull
+        public List<IpServer> getAllDownstreams() {
+            return mNotifyList;
+        }
+
         class InitialState extends State {
             @Override
             public boolean processMessage(Message message) {
@@ -2300,7 +2309,8 @@
     }
 
     private void updateConnectedClients(final List<WifiClient> wifiClients) {
-        if (mConnectedClientsTracker.updateConnectedClients(mForwardedDownstreams, wifiClients)) {
+        if (mConnectedClientsTracker.updateConnectedClients(mTetherMainSM.getAllDownstreams(),
+                wifiClients)) {
             reportTetherClientsChanged(mConnectedClientsTracker.getLastTetheredClients());
         }
     }
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
index 0a37f54..f4b3749 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
@@ -16,6 +16,7 @@
 
 package com.android.networkstack.tethering;
 
+import static android.Manifest.permission.NETWORK_SETTINGS;
 import static android.content.pm.PackageManager.GET_ACTIVITIES;
 import static android.hardware.usb.UsbManager.USB_CONFIGURED;
 import static android.hardware.usb.UsbManager.USB_CONNECTED;
@@ -36,6 +37,7 @@
 import static android.net.TetheringManager.TETHERING_NCM;
 import static android.net.TetheringManager.TETHERING_USB;
 import static android.net.TetheringManager.TETHERING_WIFI;
+import static android.net.TetheringManager.TETHERING_WIFI_P2P;
 import static android.net.TetheringManager.TETHER_ERROR_IFACE_CFG_ERROR;
 import static android.net.TetheringManager.TETHER_ERROR_NO_ERROR;
 import static android.net.TetheringManager.TETHER_ERROR_UNKNOWN_IFACE;
@@ -49,12 +51,15 @@
 import static android.net.wifi.WifiManager.IFACE_IP_MODE_LOCAL_ONLY;
 import static android.net.wifi.WifiManager.IFACE_IP_MODE_TETHERED;
 import static android.net.wifi.WifiManager.WIFI_AP_STATE_ENABLED;
+import static android.system.OsConstants.RT_SCOPE_UNIVERSE;
 import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
 
+import static com.android.net.module.util.Inet4AddressUtils.inet4AddressToIntHTH;
 import static com.android.net.module.util.Inet4AddressUtils.intToInet4AddressHTH;
 import static com.android.networkstack.tethering.Tethering.UserRestrictionActionListener;
 import static com.android.networkstack.tethering.TetheringNotificationUpdater.DOWNSTREAM_NONE;
 import static com.android.networkstack.tethering.UpstreamNetworkMonitor.EVENT_ON_CAPABILITIES;
+import static com.android.testutils.TestPermissionUtil.runAsShell;
 
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
@@ -108,11 +113,14 @@
 import android.net.RouteInfo;
 import android.net.TetherStatesParcel;
 import android.net.TetheredClient;
+import android.net.TetheredClient.AddressInfo;
 import android.net.TetheringCallbackStartedParcel;
 import android.net.TetheringConfigurationParcel;
 import android.net.TetheringRequestParcel;
+import android.net.dhcp.DhcpLeaseParcelable;
 import android.net.dhcp.DhcpServerCallbacks;
 import android.net.dhcp.DhcpServingParamsParcel;
+import android.net.dhcp.IDhcpEventCallbacks;
 import android.net.dhcp.IDhcpServer;
 import android.net.ip.DadProxy;
 import android.net.ip.IpNeighborMonitor;
@@ -122,7 +130,9 @@
 import android.net.util.NetworkConstants;
 import android.net.util.SharedLog;
 import android.net.wifi.SoftApConfiguration;
+import android.net.wifi.WifiClient;
 import android.net.wifi.WifiManager;
+import android.net.wifi.WifiManager.SoftApCallback;
 import android.net.wifi.p2p.WifiP2pGroup;
 import android.net.wifi.p2p.WifiP2pInfo;
 import android.net.wifi.p2p.WifiP2pManager;
@@ -168,6 +178,7 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.List;
 import java.util.Vector;
 
@@ -237,6 +248,7 @@
     private EntitlementManager mEntitleMgr;
     private OffloadController mOffloadCtrl;
     private PrivateAddressCoordinator mPrivateAddressCoordinator;
+    private SoftApCallback mSoftApCallback;
 
     private class TestContext extends BroadcastInterceptingContext {
         TestContext(Context base) {
@@ -568,8 +580,12 @@
                 ArgumentCaptor.forClass(PhoneStateListener.class);
         verify(mTelephonyManager).listen(phoneListenerCaptor.capture(),
                 eq(PhoneStateListener.LISTEN_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGE));
-        verify(mWifiManager).registerSoftApCallback(any(), any());
         mPhoneStateListener = phoneListenerCaptor.getValue();
+
+        final ArgumentCaptor<SoftApCallback> softApCallbackCaptor =
+                ArgumentCaptor.forClass(SoftApCallback.class);
+        verify(mWifiManager).registerSoftApCallback(any(), softApCallbackCaptor.capture());
+        mSoftApCallback = softApCallbackCaptor.getValue();
     }
 
     private void setTetheringSupported(final boolean supported) {
@@ -1293,6 +1309,7 @@
                 new ArrayList<>();
         private final ArrayList<TetherStatesParcel> mTetherStates = new ArrayList<>();
         private final ArrayList<Integer> mOffloadStatus = new ArrayList<>();
+        private final ArrayList<List<TetheredClient>> mTetheredClients = new ArrayList<>();
 
         // This function will remove the recorded callbacks, so it must be called once for
         // each callback. If this is called after multiple callback, the order matters.
@@ -1338,6 +1355,13 @@
             return mTetherStates.remove(0);
         }
 
+        public void expectTetheredClientChanged(List<TetheredClient> leases) {
+            assertFalse(mTetheredClients.isEmpty());
+            final List<TetheredClient> result = mTetheredClients.remove(0);
+            assertEquals(leases.size(), result.size());
+            assertTrue(leases.containsAll(result));
+        }
+
         @Override
         public void onUpstreamChanged(Network network) {
             mActualUpstreams.add(network);
@@ -1355,7 +1379,7 @@
 
         @Override
         public void onTetherClientsChanged(List<TetheredClient> clients) {
-            // TODO: check this
+            mTetheredClients.add(clients);
         }
 
         @Override
@@ -1369,6 +1393,7 @@
             mTetheringConfigs.add(parcel.config);
             mTetherStates.add(parcel.states);
             mOffloadStatus.add(parcel.offloadStatus);
+            mTetheredClients.add(parcel.tetheredClients);
         }
 
         @Override
@@ -1398,6 +1423,7 @@
             assertNoUpstreamChangeCallback();
             assertNoConfigChangeCallback();
             assertNoStateChangeCallback();
+            assertTrue(mTetheredClients.isEmpty());
         }
 
         private void assertTetherConfigParcelEqual(@NonNull TetheringConfigurationParcel actual,
@@ -1437,6 +1463,7 @@
         // 1. Register one callback before running any tethering.
         mTethering.registerTetheringEventCallback(callback);
         mLooper.dispatchAll();
+        callback.expectTetheredClientChanged(Collections.emptyList());
         callback.expectUpstreamChanged(new Network[] {null});
         callback.expectConfigurationChanged(
                 mTethering.getTetheringConfiguration().toStableParcelable());
@@ -1463,6 +1490,7 @@
         // 3. Register second callback.
         mTethering.registerTetheringEventCallback(callback2);
         mLooper.dispatchAll();
+        callback2.expectTetheredClientChanged(Collections.emptyList());
         callback2.expectUpstreamChanged(upstreamState.network);
         callback2.expectConfigurationChanged(
                 mTethering.getTetheringConfiguration().toStableParcelable());
@@ -2054,6 +2082,114 @@
         verify(mPackageManager).getPackageInfo(PROVISIONING_APP_NAME[0], GET_ACTIVITIES);
     }
 
+    @Test
+    public void testUpdateConnectedClients() throws Exception {
+        TestTetheringEventCallback callback = new TestTetheringEventCallback();
+        runAsShell(NETWORK_SETTINGS, () -> {
+            mTethering.registerTetheringEventCallback(callback);
+            mLooper.dispatchAll();
+        });
+        callback.expectTetheredClientChanged(Collections.emptyList());
+
+        IDhcpEventCallbacks eventCallbacks;
+        final ArgumentCaptor<IDhcpEventCallbacks> dhcpEventCbsCaptor =
+                 ArgumentCaptor.forClass(IDhcpEventCallbacks.class);
+        // Run local only tethering.
+        mTethering.interfaceStatusChanged(TEST_P2P_IFNAME, true);
+        sendWifiP2pConnectionChanged(true, true, TEST_P2P_IFNAME);
+        mLooper.dispatchAll();
+        verify(mDhcpServer, timeout(DHCPSERVER_START_TIMEOUT_MS)).startWithCallbacks(
+                any(), dhcpEventCbsCaptor.capture());
+        eventCallbacks = dhcpEventCbsCaptor.getValue();
+        // Update lease for local only tethering.
+        final MacAddress testMac1 = MacAddress.fromString("11:11:11:11:11:11");
+        final ArrayList<DhcpLeaseParcelable> p2pLeases = new ArrayList<>();
+        p2pLeases.add(createDhcpLeaseParcelable("clientId1", testMac1, "192.168.50.24", 24,
+                Long.MAX_VALUE, "test1"));
+        notifyDhcpLeasesChanged(p2pLeases, eventCallbacks);
+        final List<TetheredClient> clients = toTetheredClients(p2pLeases, TETHERING_WIFI_P2P);
+        callback.expectTetheredClientChanged(clients);
+        reset(mDhcpServer);
+
+        // Run wifi tethering.
+        mTethering.interfaceStatusChanged(TEST_WLAN_IFNAME, true);
+        sendWifiApStateChanged(WIFI_AP_STATE_ENABLED, TEST_WLAN_IFNAME, IFACE_IP_MODE_TETHERED);
+        mLooper.dispatchAll();
+        verify(mDhcpServer, timeout(DHCPSERVER_START_TIMEOUT_MS)).startWithCallbacks(
+                any(), dhcpEventCbsCaptor.capture());
+        eventCallbacks = dhcpEventCbsCaptor.getValue();
+        // Update mac address from softAp callback before getting dhcp lease.
+        final ArrayList<WifiClient> wifiClients = new ArrayList<>();
+        final MacAddress testMac2 = MacAddress.fromString("22:22:22:22:22:22");
+        final WifiClient testClient = mock(WifiClient.class);
+        when(testClient.getMacAddress()).thenReturn(testMac2);
+        wifiClients.add(testClient);
+        mSoftApCallback.onConnectedClientsChanged(wifiClients);
+        final TetheredClient noAddrClient = new TetheredClient(testMac2,
+                Collections.emptyList() /* addresses */, TETHERING_WIFI);
+        clients.add(noAddrClient);
+        callback.expectTetheredClientChanged(clients);
+
+        // Update dhcp lease for wifi tethering.
+        clients.remove(noAddrClient);
+        final ArrayList<DhcpLeaseParcelable> wifiLeases = new ArrayList<>();
+        wifiLeases.add(createDhcpLeaseParcelable("clientId2", testMac2, "192.168.43.24", 24,
+                Long.MAX_VALUE, "test2"));
+        notifyDhcpLeasesChanged(wifiLeases, eventCallbacks);
+        clients.addAll(toTetheredClients(wifiLeases, TETHERING_WIFI));
+        callback.expectTetheredClientChanged(clients);
+
+        // Test onStarted callback that register second callback when tethering is running.
+        TestTetheringEventCallback callback2 = new TestTetheringEventCallback();
+        runAsShell(NETWORK_SETTINGS, () -> {
+            mTethering.registerTetheringEventCallback(callback2);
+            mLooper.dispatchAll();
+        });
+        callback2.expectTetheredClientChanged(clients);
+    }
+
+    private void notifyDhcpLeasesChanged(List<DhcpLeaseParcelable> leaseParcelables,
+            IDhcpEventCallbacks callback) throws Exception {
+        callback.onLeasesChanged(leaseParcelables);
+        mLooper.dispatchAll();
+    }
+
+    private List<TetheredClient> toTetheredClients(List<DhcpLeaseParcelable> leaseParcelables,
+            int type) throws Exception {
+        final ArrayList<TetheredClient> leases = new ArrayList<>();
+        for (DhcpLeaseParcelable lease : leaseParcelables) {
+            final LinkAddress address = new LinkAddress(
+                    intToInet4AddressHTH(lease.netAddr), lease.prefixLength,
+                    0 /* flags */, RT_SCOPE_UNIVERSE /* as per RFC6724#3.2 */,
+                    lease.expTime /* deprecationTime */, lease.expTime /* expirationTime */);
+
+            final MacAddress macAddress = MacAddress.fromBytes(lease.hwAddr);
+
+            final AddressInfo addressInfo = new TetheredClient.AddressInfo(address, lease.hostname);
+            leases.add(new TetheredClient(
+                    macAddress,
+                    Collections.singletonList(addressInfo),
+                    type));
+        }
+
+        return leases;
+    }
+
+    private DhcpLeaseParcelable createDhcpLeaseParcelable(final String clientId,
+            final MacAddress hwAddr, final String netAddr, final int prefixLength,
+            final long expTime, final String hostname) throws Exception {
+        final DhcpLeaseParcelable lease = new DhcpLeaseParcelable();
+        lease.clientId = clientId.getBytes();
+        lease.hwAddr = hwAddr.toByteArray();
+        lease.netAddr = inet4AddressToIntHTH(
+                (Inet4Address) InetAddresses.parseNumericAddress(netAddr));
+        lease.prefixLength = prefixLength;
+        lease.expTime = expTime;
+        lease.hostname = hostname;
+
+        return lease;
+    }
+
     // TODO: Test that a request for hotspot mode doesn't interfere with an
     // already operating tethering mode interface.
 }