Allow SAP and LOHS wifi clients exist at the same time

This change store localOnly wifi clients in its own field so that
tethered and localOnly hotspot clients can exist at the same time.

Currently, there are no tethered and localOnly hotspot clients at
the same time because PrivateAddressCoordinator does not support
SAP + LOHS. A follow-up change will be made to allow this.

When both SAP and LOHS are enabled, the SAP and LOHS clients from
TetheringEventCallback#onClientsChanged are all TETHERING_WIFI.
Currently, there is no way for the listeners to distinguish between
SAP and LOHS clients.

Bug: 233175023
Test: atest TetheringTests
Change-Id: I01b0a6abb084f7135f7825e0c5303e49c16a4c39
This commit is contained in:
Mark
2023-03-11 05:01:48 +00:00
parent 3ec851ef03
commit ae3abdfa4b
3 changed files with 168 additions and 67 deletions

View File

@@ -29,6 +29,8 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import com.android.modules.utils.build.SdkLevel;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
@@ -49,6 +51,8 @@ public class ConnectedClientsTracker {
@NonNull
private List<WifiClient> mLastWifiClients = Collections.emptyList();
@NonNull
private List<WifiClient> mLastLocalOnlyClients = Collections.emptyList();
@NonNull
private List<TetheredClient> mLastTetheredClients = Collections.emptyList();
@VisibleForTesting
@@ -72,25 +76,44 @@ public class ConnectedClientsTracker {
*
* <p>The new list can be obtained through {@link #getLastTetheredClients()}.
* @param ipServers The IpServers used to assign addresses to clients.
* @param wifiClients The list of L2-connected WiFi clients. Null for no change since last
* update.
* @param wifiClients The list of L2-connected WiFi clients that are connected to a global
* hotspot. Null for no change since last update.
* @param localOnlyClients The list of L2-connected WiFi clients that are connected to localOnly
* hotspot. Null for no change since last update.
* @return True if the list of clients changed since the last calculation.
*/
public boolean updateConnectedClients(
Iterable<IpServer> ipServers, @Nullable List<WifiClient> wifiClients) {
Iterable<IpServer> ipServers, @Nullable List<WifiClient> wifiClients,
@Nullable List<WifiClient> localOnlyClients) {
final long now = mClock.elapsedRealtime();
if (wifiClients != null) {
mLastWifiClients = wifiClients;
}
if (wifiClients != null) mLastWifiClients = wifiClients;
if (localOnlyClients != null) mLastLocalOnlyClients = localOnlyClients;
final Set<MacAddress> wifiClientMacs = getClientMacs(mLastWifiClients);
final Set<MacAddress> localOnlyClientMacs = getClientMacs(mLastLocalOnlyClients);
// Build the list of non-expired leases from all IpServers, grouped by mac address
final Map<MacAddress, TetheredClient> clientsMap = new HashMap<>();
for (IpServer server : ipServers) {
final Set<MacAddress> connectedClientMacs;
switch (server.servingMode()) {
case IpServer.STATE_TETHERED:
connectedClientMacs = wifiClientMacs;
break;
case IpServer.STATE_LOCAL_ONLY:
// Before T, SAP and LOHS both use wifiClientMacs because
// registerLocalOnlyHotspotSoftApCallback didn't exist.
connectedClientMacs = SdkLevel.isAtLeastT()
? localOnlyClientMacs : wifiClientMacs;
break;
default:
continue;
}
for (TetheredClient client : server.getAllLeases()) {
if (client.getTetheringType() == TETHERING_WIFI
&& !wifiClientMacs.contains(client.getMacAddress())) {
&& !connectedClientMacs.contains(client.getMacAddress())) {
// Skip leases of WiFi clients that are not (or no longer) L2-connected
continue;
}
@@ -104,11 +127,8 @@ public class ConnectedClientsTracker {
// TODO: add IPv6 addresses from netlink
// Add connected WiFi clients that do not have any known address
for (MacAddress client : wifiClientMacs) {
if (clientsMap.containsKey(client)) continue;
clientsMap.put(client, new TetheredClient(
client, Collections.emptyList() /* addresses */, TETHERING_WIFI));
}
addWifiClientsIfNoLeases(clientsMap, wifiClientMacs);
addWifiClientsIfNoLeases(clientsMap, localOnlyClientMacs);
final HashSet<TetheredClient> clients = new HashSet<>(clientsMap.values());
final boolean clientsChanged = clients.size() != mLastTetheredClients.size()
@@ -117,6 +137,15 @@ public class ConnectedClientsTracker {
return clientsChanged;
}
private static void addWifiClientsIfNoLeases(
final Map<MacAddress, TetheredClient> clientsMap, final Set<MacAddress> clientMacs) {
for (MacAddress mac : clientMacs) {
if (clientsMap.containsKey(mac)) continue;
clientsMap.put(mac, new TetheredClient(
mac, Collections.emptyList() /* addresses */, TETHERING_WIFI));
}
}
private static void addLease(Map<MacAddress, TetheredClient> clientsMap, TetheredClient lease) {
final TetheredClient aggregateClient = clientsMap.getOrDefault(
lease.getMacAddress(), lease);

View File

@@ -58,6 +58,7 @@ import static android.net.wifi.WifiManager.IFACE_IP_MODE_CONFIGURATION_ERROR;
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.IFACE_IP_MODE_UNSPECIFIED;
import static android.net.wifi.WifiManager.SoftApCallback;
import static android.net.wifi.WifiManager.WIFI_AP_STATE_DISABLED;
import static android.telephony.CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED;
import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
@@ -479,15 +480,15 @@ public class Tethering {
mStateReceiver, noUpstreamFilter, PERMISSION_MAINLINE_NETWORK_STACK, mHandler);
final WifiManager wifiManager = getWifiManager();
TetheringSoftApCallback softApCallback = new TetheringSoftApCallback();
if (wifiManager != null) {
wifiManager.registerSoftApCallback(mExecutor, softApCallback);
}
if (SdkLevel.isAtLeastT() && wifiManager != null) {
// 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);
wifiManager.registerSoftApCallback(mExecutor, new TetheringSoftApCallback());
if (SdkLevel.isAtLeastT()) {
// Although WifiManager#registerLocalOnlyHotspotSoftApCallback document that it need
// NEARBY_WIFI_DEVICES permission, but actually a caller who have NETWORK_STACK
// or MAINLINE_NETWORK_STACK permission can also use this API.
wifiManager.registerLocalOnlyHotspotSoftApCallback(mExecutor,
new LocalOnlyHotspotCallback());
}
}
startTrackDefaultNetwork();
@@ -573,26 +574,17 @@ public class Tethering {
}
}
private class TetheringSoftApCallback implements WifiManager.SoftApCallback {
// TODO: Remove onStateChanged override when this method has default on
// WifiManager#SoftApCallback interface.
// Wifi listener for state change of the soft AP
@Override
public void onStateChanged(final int state, final int failureReason) {
// Nothing
}
// 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.
private class TetheringSoftApCallback implements SoftApCallback {
@Override
public void onConnectedClientsChanged(final List<WifiClient> clients) {
updateConnectedClients(clients);
updateConnectedClients(clients, null);
}
}
private class LocalOnlyHotspotCallback implements SoftApCallback {
@Override
public void onConnectedClientsChanged(final List<WifiClient> clients) {
updateConnectedClients(null, clients);
}
}
@@ -1968,7 +1960,7 @@ public class Tethering {
mIPv6TetheringCoordinator.removeActiveDownstream(who);
mOffload.excludeDownstreamInterface(who.interfaceName());
mForwardedDownstreams.remove(who);
updateConnectedClients(null /* wifiClients */);
maybeDhcpLeasesChanged();
// If this is a Wi-Fi interface, tell WifiManager of any errors
// or the inactive serving state.
@@ -2710,9 +2702,15 @@ public class Tethering {
if (e != null) throw e;
}
private void updateConnectedClients(final List<WifiClient> wifiClients) {
private void maybeDhcpLeasesChanged() {
// null means wifi clients did not change.
updateConnectedClients(null, null);
}
private void updateConnectedClients(final List<WifiClient> wifiClients,
final List<WifiClient> localOnlyClients) {
if (mConnectedClientsTracker.updateConnectedClients(mTetherMainSM.getAllDownstreams(),
wifiClients)) {
wifiClients, localOnlyClients)) {
reportTetherClientsChanged(mConnectedClientsTracker.getLastTetheredClients());
}
}
@@ -2731,7 +2729,7 @@ public class Tethering {
@Override
public void dhcpLeasesChanged() {
updateConnectedClients(null /* wifiClients */);
maybeDhcpLeasesChanged();
}
@Override