From 4ccb419602059bdb48e5383d94ea35533f3b5673 Mon Sep 17 00:00:00 2001 From: markchien Date: Tue, 14 Dec 2021 15:02:22 +0800 Subject: [PATCH] Register localOnly softapCallback for local only hotspot Before T, wifi local only mode won't update onConnectedClientsChanged. To support updating tethered client information for localOnly hotspot, register localOnly softap callback to get connected wifi client update. Bug: 171928468 Test: atest TetheringTests Change-Id: I0e974feea9497809ea02a4138c7b9c2c584a8a45 --- .../tethering/PrivateAddressCoordinator.java | 3 + .../networkstack/tethering/Tethering.java | 23 +++- .../networkstack/tethering/TetheringTest.java | 118 +++++++++++++----- 3 files changed, 114 insertions(+), 30 deletions(-) diff --git a/Tethering/src/com/android/networkstack/tethering/PrivateAddressCoordinator.java b/Tethering/src/com/android/networkstack/tethering/PrivateAddressCoordinator.java index cc2422f877..41a10ae60e 100644 --- a/Tethering/src/com/android/networkstack/tethering/PrivateAddressCoordinator.java +++ b/Tethering/src/com/android/networkstack/tethering/PrivateAddressCoordinator.java @@ -172,6 +172,9 @@ public class PrivateAddressCoordinator { return new LinkAddress(LEGACY_WIFI_P2P_IFACE_ADDRESS); } + // This ensures that tethering isn't started on 2 different interfaces with the same type. + // Once tethering could support multiple interface with the same type, + // TetheringSoftApCallback would need to handle it among others. final LinkAddress cachedAddress = mCachedAddresses.get(ipServer.interfaceType()); if (useLastAddress && cachedAddress != null && !isConflictWithUpstream(asIpPrefix(cachedAddress))) { diff --git a/Tethering/src/com/android/networkstack/tethering/Tethering.java b/Tethering/src/com/android/networkstack/tethering/Tethering.java index 551fd633e1..d188b6c92d 100644 --- a/Tethering/src/com/android/networkstack/tethering/Tethering.java +++ b/Tethering/src/com/android/networkstack/tethering/Tethering.java @@ -448,8 +448,22 @@ public class Tethering { mStateReceiver, noUpstreamFilter, PERMISSION_MAINLINE_NETWORK_STACK, mHandler); final WifiManager wifiManager = getWifiManager(); + TetheringSoftApCallback softApCallback = new TetheringSoftApCallback(); if (wifiManager != null) { - wifiManager.registerSoftApCallback(mExecutor, new TetheringSoftApCallback()); + wifiManager.registerSoftApCallback(mExecutor, softApCallback); + } + if (SdkLevel.isAtLeastT() && wifiManager != null) { + try { + // Although WifiManager#registerLocalOnlyHotspotSoftApCallback document that it need + // NEARBY_WIFI_DEVICES permission, but actually a caller who have NETWORK_STACK + // or MAINLINE_NETWORK_STACK permission would also able to use this API. + wifiManager.registerLocalOnlyHotspotSoftApCallback(mExecutor, softApCallback); + } catch (UnsupportedOperationException e) { + // Since wifi module development in internal branch, + // #registerLocalOnlyHotspotSoftApCallback currently doesn't supported in AOSP + // before AOSP switch to Android T + 1. + Log.wtf(TAG, "registerLocalOnlyHotspotSoftApCallback API is not supported"); + } } startTrackDefaultNetwork(); @@ -545,6 +559,13 @@ public class Tethering { } // Called by wifi when the number of soft AP clients changed. + // Currently multiple softAp would not behave well in PrivateAddressCoordinator + // (where it gets the address from cache), it ensure tethering only support one ipServer for + // TETHERING_WIFI. Once tethering support multiple softAp enabled simultaneously, + // onConnectedClientsChanged should also be updated to support tracking different softAp's + // clients individually. + // TODO: Add wtf log and have check to reject request duplicated type with different + // interface. @Override public void onConnectedClientsChanged(final List clients) { updateConnectedClients(clients); 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 4662c968a3..f99d45efdf 100644 --- a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java +++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java @@ -194,6 +194,7 @@ import com.android.networkstack.apishim.common.BluetoothPanShim.TetheredInterfac import com.android.networkstack.apishim.common.UnsupportedApiLevelException; import com.android.networkstack.tethering.TestConnectivityManager.TestNetworkAgent; import com.android.networkstack.tethering.metrics.TetheringMetrics; +import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo; import com.android.testutils.MiscAsserts; import org.junit.After; @@ -300,6 +301,7 @@ public class TetheringTest { private OffloadController mOffloadCtrl; private PrivateAddressCoordinator mPrivateAddressCoordinator; private SoftApCallback mSoftApCallback; + private SoftApCallback mLocalOnlyHotspotCallback; private UpstreamNetworkMonitor mUpstreamNetworkMonitor; private TetheredInterfaceCallbackShim mTetheredInterfaceCallbackShim; @@ -670,6 +672,14 @@ public class TetheringTest { verify(mWifiManager).registerSoftApCallback(any(), softApCallbackCaptor.capture()); mSoftApCallback = softApCallbackCaptor.getValue(); + if (isAtLeastT()) { + final ArgumentCaptor localOnlyCallbackCaptor = + ArgumentCaptor.forClass(SoftApCallback.class); + verify(mWifiManager).registerLocalOnlyHotspotSoftApCallback(any(), + localOnlyCallbackCaptor.capture()); + mLocalOnlyHotspotCallback = localOnlyCallbackCaptor.getValue(); + } + when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_WIFI)).thenReturn(true); when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_WIFI_DIRECT)).thenReturn(true); } @@ -2530,12 +2540,11 @@ public class TetheringTest { eventCallbacks = dhcpEventCbsCaptor.getValue(); // Update lease for local only tethering. final MacAddress testMac1 = MacAddress.fromString("11:11:11:11:11:11"); - final ArrayList p2pLeases = new ArrayList<>(); - p2pLeases.add(createDhcpLeaseParcelable("clientId1", testMac1, "192.168.50.24", 24, - Long.MAX_VALUE, "test1")); - notifyDhcpLeasesChanged(p2pLeases, eventCallbacks); - final List clients = toTetheredClients(p2pLeases, TETHERING_WIFI_P2P); - callback.expectTetheredClientChanged(clients); + final DhcpLeaseParcelable p2pLease = createDhcpLeaseParcelable("clientId1", testMac1, + "192.168.50.24", 24, Long.MAX_VALUE, "test1"); + final List p2pClients = notifyDhcpLeasesChanged(TETHERING_WIFI_P2P, + eventCallbacks, p2pLease); + callback.expectTetheredClientChanged(p2pClients); reset(mDhcpServer); // Run wifi tethering. @@ -2545,25 +2554,20 @@ public class TetheringTest { any(), dhcpEventCbsCaptor.capture()); eventCallbacks = dhcpEventCbsCaptor.getValue(); // Update mac address from softAp callback before getting dhcp lease. - final ArrayList 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); + final TetheredClient noAddrClient = notifyConnectedWifiClientsChanged(testMac2, + false /* isLocalOnly */); + final List p2pAndNoAddrClients = new ArrayList<>(p2pClients); + p2pAndNoAddrClients.add(noAddrClient); + callback.expectTetheredClientChanged(p2pAndNoAddrClients); // Update dhcp lease for wifi tethering. - clients.remove(noAddrClient); - final ArrayList 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); + final DhcpLeaseParcelable wifiLease = createDhcpLeaseParcelable("clientId2", testMac2, + "192.168.43.24", 24, Long.MAX_VALUE, "test2"); + final List p2pAndWifiClients = new ArrayList<>(p2pClients); + p2pAndWifiClients.addAll(notifyDhcpLeasesChanged(TETHERING_WIFI, + eventCallbacks, wifiLease)); + callback.expectTetheredClientChanged(p2pAndWifiClients); // Test onStarted callback that register second callback when tethering is running. TestTetheringEventCallback callback2 = new TestTetheringEventCallback(); @@ -2571,18 +2575,74 @@ public class TetheringTest { mTethering.registerTetheringEventCallback(callback2); mLooper.dispatchAll(); }); - callback2.expectTetheredClientChanged(clients); + callback2.expectTetheredClientChanged(p2pAndWifiClients); } - private void notifyDhcpLeasesChanged(List leaseParcelables, - IDhcpEventCallbacks callback) throws Exception { - callback.onLeasesChanged(leaseParcelables); + @Test + @IgnoreUpTo(Build.VERSION_CODES.S_V2) + public void testUpdateConnectedClientsForLocalOnlyHotspot() throws Exception { + TestTetheringEventCallback callback = new TestTetheringEventCallback(); + runAsShell(NETWORK_SETTINGS, () -> { + mTethering.registerTetheringEventCallback(callback); + mLooper.dispatchAll(); + }); + callback.expectTetheredClientChanged(Collections.emptyList()); + + // Run local only hotspot. + mTethering.interfaceStatusChanged(TEST_WLAN_IFNAME, true); + sendWifiApStateChanged(WIFI_AP_STATE_ENABLED, TEST_WLAN_IFNAME, IFACE_IP_MODE_LOCAL_ONLY); + + final ArgumentCaptor dhcpEventCbsCaptor = + ArgumentCaptor.forClass(IDhcpEventCallbacks.class); + verify(mDhcpServer, timeout(DHCPSERVER_START_TIMEOUT_MS)).startWithCallbacks( + any(), dhcpEventCbsCaptor.capture()); + final IDhcpEventCallbacks eventCallbacks = dhcpEventCbsCaptor.getValue(); + // Update mac address from softAp callback before getting dhcp lease. + final MacAddress testMac = MacAddress.fromString("22:22:22:22:22:22"); + final TetheredClient noAddrClient = notifyConnectedWifiClientsChanged(testMac, + true /* isLocalOnly */); + final List noAddrLocalOnlyClients = new ArrayList<>(); + noAddrLocalOnlyClients.add(noAddrClient); + callback.expectTetheredClientChanged(noAddrLocalOnlyClients); + + // Update dhcp lease for local only hotspot. + final DhcpLeaseParcelable wifiLease = createDhcpLeaseParcelable("clientId", testMac, + "192.168.43.24", 24, Long.MAX_VALUE, "test"); + final List localOnlyClients = notifyDhcpLeasesChanged(TETHERING_WIFI, + eventCallbacks, wifiLease); + callback.expectTetheredClientChanged(localOnlyClients); + + // Client disconnect from local only hotspot. + mLocalOnlyHotspotCallback.onConnectedClientsChanged(Collections.emptyList()); + callback.expectTetheredClientChanged(Collections.emptyList()); + } + + private TetheredClient notifyConnectedWifiClientsChanged(final MacAddress mac, + boolean isLocalOnly) throws Exception { + final ArrayList wifiClients = new ArrayList<>(); + final WifiClient testClient = mock(WifiClient.class); + when(testClient.getMacAddress()).thenReturn(mac); + wifiClients.add(testClient); + if (isLocalOnly) { + mLocalOnlyHotspotCallback.onConnectedClientsChanged(wifiClients); + } else { + mSoftApCallback.onConnectedClientsChanged(wifiClients); + } + return new TetheredClient(mac, Collections.emptyList() /* addresses */, TETHERING_WIFI); + } + + private List notifyDhcpLeasesChanged(int type, IDhcpEventCallbacks callback, + DhcpLeaseParcelable... leases) throws Exception { + final List dhcpLeases = Arrays.asList(leases); + callback.onLeasesChanged(dhcpLeases); mLooper.dispatchAll(); + + return toTetheredClients(dhcpLeases, type); } private List toTetheredClients(List leaseParcelables, int type) throws Exception { - final ArrayList leases = new ArrayList<>(); + final ArrayList clients = new ArrayList<>(); for (DhcpLeaseParcelable lease : leaseParcelables) { final LinkAddress address = new LinkAddress( intToInet4AddressHTH(lease.netAddr), lease.prefixLength, @@ -2592,13 +2652,13 @@ public class TetheringTest { final MacAddress macAddress = MacAddress.fromBytes(lease.hwAddr); final AddressInfo addressInfo = new TetheredClient.AddressInfo(address, lease.hostname); - leases.add(new TetheredClient( + clients.add(new TetheredClient( macAddress, Collections.singletonList(addressInfo), type)); } - return leases; + return clients; } private DhcpLeaseParcelable createDhcpLeaseParcelable(final String clientId,