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 7605452d2b..af017f325a 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 95ebb78226..80666e62ad 100644 --- a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java +++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java @@ -195,6 +195,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; @@ -301,6 +302,7 @@ public class TetheringTest { private OffloadController mOffloadCtrl; private PrivateAddressCoordinator mPrivateAddressCoordinator; private SoftApCallback mSoftApCallback; + private SoftApCallback mLocalOnlyHotspotCallback; private UpstreamNetworkMonitor mUpstreamNetworkMonitor; private TetheredInterfaceCallbackShim mTetheredInterfaceCallbackShim; @@ -671,6 +673,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); } @@ -2537,12 +2547,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. @@ -2552,25 +2561,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(); @@ -2578,18 +2582,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, @@ -2599,13 +2659,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,