Merge "Register localOnly softapCallback for local only hotspot"
diff --git a/Tethering/src/com/android/networkstack/tethering/PrivateAddressCoordinator.java b/Tethering/src/com/android/networkstack/tethering/PrivateAddressCoordinator.java
index cc2422f..41a10ae 100644
--- a/Tethering/src/com/android/networkstack/tethering/PrivateAddressCoordinator.java
+++ b/Tethering/src/com/android/networkstack/tethering/PrivateAddressCoordinator.java
@@ -172,6 +172,9 @@
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 7605452..af017f3 100644
--- a/Tethering/src/com/android/networkstack/tethering/Tethering.java
+++ b/Tethering/src/com/android/networkstack/tethering/Tethering.java
@@ -448,8 +448,22 @@
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 @@
}
// 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<WifiClient> 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 95ebb78..80666e6 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.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 @@
private OffloadController mOffloadCtrl;
private PrivateAddressCoordinator mPrivateAddressCoordinator;
private SoftApCallback mSoftApCallback;
+ private SoftApCallback mLocalOnlyHotspotCallback;
private UpstreamNetworkMonitor mUpstreamNetworkMonitor;
private TetheredInterfaceCallbackShim mTetheredInterfaceCallbackShim;
@@ -671,6 +673,14 @@
verify(mWifiManager).registerSoftApCallback(any(), softApCallbackCaptor.capture());
mSoftApCallback = softApCallbackCaptor.getValue();
+ if (isAtLeastT()) {
+ final ArgumentCaptor<SoftApCallback> 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 @@
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);
+ final DhcpLeaseParcelable p2pLease = createDhcpLeaseParcelable("clientId1", testMac1,
+ "192.168.50.24", 24, Long.MAX_VALUE, "test1");
+ final List<TetheredClient> p2pClients = notifyDhcpLeasesChanged(TETHERING_WIFI_P2P,
+ eventCallbacks, p2pLease);
+ callback.expectTetheredClientChanged(p2pClients);
reset(mDhcpServer);
// Run wifi tethering.
@@ -2552,25 +2561,20 @@
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);
+ final TetheredClient noAddrClient = notifyConnectedWifiClientsChanged(testMac2,
+ false /* isLocalOnly */);
+ final List<TetheredClient> p2pAndNoAddrClients = new ArrayList<>(p2pClients);
+ p2pAndNoAddrClients.add(noAddrClient);
+ callback.expectTetheredClientChanged(p2pAndNoAddrClients);
// 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);
+ final DhcpLeaseParcelable wifiLease = createDhcpLeaseParcelable("clientId2", testMac2,
+ "192.168.43.24", 24, Long.MAX_VALUE, "test2");
+ final List<TetheredClient> 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 @@
mTethering.registerTetheringEventCallback(callback2);
mLooper.dispatchAll();
});
- callback2.expectTetheredClientChanged(clients);
+ callback2.expectTetheredClientChanged(p2pAndWifiClients);
}
- private void notifyDhcpLeasesChanged(List<DhcpLeaseParcelable> 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<IDhcpEventCallbacks> 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<TetheredClient> 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<TetheredClient> 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<WifiClient> 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<TetheredClient> notifyDhcpLeasesChanged(int type, IDhcpEventCallbacks callback,
+ DhcpLeaseParcelable... leases) throws Exception {
+ final List<DhcpLeaseParcelable> dhcpLeases = Arrays.asList(leases);
+ callback.onLeasesChanged(dhcpLeases);
mLooper.dispatchAll();
+
+ return toTetheredClients(dhcpLeases, type);
}
private List<TetheredClient> toTetheredClients(List<DhcpLeaseParcelable> leaseParcelables,
int type) throws Exception {
- final ArrayList<TetheredClient> leases = new ArrayList<>();
+ final ArrayList<TetheredClient> clients = new ArrayList<>();
for (DhcpLeaseParcelable lease : leaseParcelables) {
final LinkAddress address = new LinkAddress(
intToInet4AddressHTH(lease.netAddr), lease.prefixLength,
@@ -2599,13 +2659,13 @@
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,