Add tethering client callbacks
The callbacks are fired when the list of connected clients or their IP addresses / hostname change. Test: flashed, connected 2 devices, verified callbacks Test: atest TetheringTests Bug: 135411507 Change-Id: I96291038cf7b39a67547a5f74fcd7cbedc1ca002
This commit is contained in:
committed by
markchien
parent
5cce783b41
commit
c8871c1b66
@@ -17,6 +17,7 @@
|
|||||||
java_defaults {
|
java_defaults {
|
||||||
name: "TetheringAndroidLibraryDefaults",
|
name: "TetheringAndroidLibraryDefaults",
|
||||||
// TODO (b/146757305): change to module API once available
|
// TODO (b/146757305): change to module API once available
|
||||||
|
// TODO (b/148190005): change to module-libs-api-stubs-current once it is ready.
|
||||||
sdk_version: "core_platform",
|
sdk_version: "core_platform",
|
||||||
srcs: [
|
srcs: [
|
||||||
"src/**/*.java",
|
"src/**/*.java",
|
||||||
@@ -34,7 +35,12 @@ java_defaults {
|
|||||||
"net-utils-framework-common",
|
"net-utils-framework-common",
|
||||||
],
|
],
|
||||||
libs: [
|
libs: [
|
||||||
|
// Order matters: framework-tethering needs to be before the system stubs, otherwise
|
||||||
|
// hidden fields in the framework-tethering classes (which are also used to generate stubs)
|
||||||
|
// will not be found.
|
||||||
"framework-tethering",
|
"framework-tethering",
|
||||||
|
"android_system_stubs_current",
|
||||||
|
"framework-res",
|
||||||
"unsupportedappusage",
|
"unsupportedappusage",
|
||||||
"android_system_stubs_current",
|
"android_system_stubs_current",
|
||||||
"framework-res",
|
"framework-res",
|
||||||
@@ -86,6 +92,7 @@ cc_library {
|
|||||||
java_defaults {
|
java_defaults {
|
||||||
name: "TetheringAppDefaults",
|
name: "TetheringAppDefaults",
|
||||||
// TODO (b/146757305): change to module API once available
|
// TODO (b/146757305): change to module API once available
|
||||||
|
// TODO (b/148190005): change to module-libs-api-stubs-current once it is ready.
|
||||||
sdk_version: "core_platform",
|
sdk_version: "core_platform",
|
||||||
privileged: true,
|
privileged: true,
|
||||||
// Build system doesn't track transitive dependeicies for jni_libs, list all the dependencies
|
// Build system doesn't track transitive dependeicies for jni_libs, list all the dependencies
|
||||||
@@ -99,6 +106,9 @@ java_defaults {
|
|||||||
"res",
|
"res",
|
||||||
],
|
],
|
||||||
libs: [
|
libs: [
|
||||||
|
// Order matters: framework-tethering needs to be before the system stubs, otherwise
|
||||||
|
// hidden fields in the framework-tethering classes (which are also used to generate stubs)
|
||||||
|
// will not be found.
|
||||||
"framework-tethering",
|
"framework-tethering",
|
||||||
"android_system_stubs_current",
|
"android_system_stubs_current",
|
||||||
"framework-res",
|
"framework-res",
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
/**
|
/*
|
||||||
* Copyright (c) 2019, The Android Open Source Project
|
* Copyright (C) 2020 The Android Open Source Project
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
* You may obtain a copy of the License at
|
* You may obtain a copy of the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
* See the License for the specific language governing perNmissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package android.net;
|
package android.net;
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
package android.net;
|
package android.net;
|
||||||
|
|
||||||
import android.net.Network;
|
import android.net.Network;
|
||||||
|
import android.net.TetheredClient;
|
||||||
import android.net.TetheringConfigurationParcel;
|
import android.net.TetheringConfigurationParcel;
|
||||||
import android.net.TetheringCallbackStartedParcel;
|
import android.net.TetheringCallbackStartedParcel;
|
||||||
import android.net.TetherStatesParcel;
|
import android.net.TetherStatesParcel;
|
||||||
@@ -33,4 +34,5 @@ oneway interface ITetheringEventCallback
|
|||||||
void onUpstreamChanged(in Network network);
|
void onUpstreamChanged(in Network network);
|
||||||
void onConfigurationChanged(in TetheringConfigurationParcel config);
|
void onConfigurationChanged(in TetheringConfigurationParcel config);
|
||||||
void onTetherStatesChanged(in TetherStatesParcel states);
|
void onTetherStatesChanged(in TetherStatesParcel states);
|
||||||
|
void onTetherClientsChanged(in List<TetheredClient> clients);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -191,6 +191,15 @@ public final class TetheredClient implements Parcelable {
|
|||||||
return new AddressInfo[size];
|
return new AddressInfo[size];
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "AddressInfo {"
|
||||||
|
+ mAddress
|
||||||
|
+ (mHostname != null ? ", hostname " + mHostname : "")
|
||||||
|
+ "}";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -212,4 +221,13 @@ public final class TetheredClient implements Parcelable {
|
|||||||
return new TetheredClient[size];
|
return new TetheredClient[size];
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "TetheredClient {hwAddr " + mMacAddress
|
||||||
|
+ ", addresses " + mAddresses
|
||||||
|
+ ", tetheringType " + mTetheringType
|
||||||
|
+ "}";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
package android.net;
|
package android.net;
|
||||||
|
|
||||||
import android.net.Network;
|
import android.net.Network;
|
||||||
|
import android.net.TetheredClient;
|
||||||
import android.net.TetheringConfigurationParcel;
|
import android.net.TetheringConfigurationParcel;
|
||||||
import android.net.TetherStatesParcel;
|
import android.net.TetherStatesParcel;
|
||||||
|
|
||||||
@@ -29,4 +30,5 @@ parcelable TetheringCallbackStartedParcel {
|
|||||||
Network upstreamNetwork;
|
Network upstreamNetwork;
|
||||||
TetheringConfigurationParcel config;
|
TetheringConfigurationParcel config;
|
||||||
TetherStatesParcel states;
|
TetherStatesParcel states;
|
||||||
|
List<TetheredClient> tetheredClients;
|
||||||
}
|
}
|
||||||
@@ -375,6 +375,9 @@ public class TetheringManager {
|
|||||||
mTetherStatesParcel = states;
|
mTetherStatesParcel = states;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onTetherClientsChanged(List<TetheredClient> clients) { }
|
||||||
|
|
||||||
public void waitForStarted() {
|
public void waitForStarted() {
|
||||||
mWaitForCallback.block(DEFAULT_TIMEOUT_MS);
|
mWaitForCallback.block(DEFAULT_TIMEOUT_MS);
|
||||||
throwIfPermissionFailure(mError);
|
throwIfPermissionFailure(mError);
|
||||||
@@ -921,6 +924,7 @@ public class TetheringManager {
|
|||||||
sendRegexpsChanged(parcel.config);
|
sendRegexpsChanged(parcel.config);
|
||||||
maybeSendTetherableIfacesChangedCallback(parcel.states);
|
maybeSendTetherableIfacesChangedCallback(parcel.states);
|
||||||
maybeSendTetheredIfacesChangedCallback(parcel.states);
|
maybeSendTetheredIfacesChangedCallback(parcel.states);
|
||||||
|
callback.onClientsChanged(parcel.tetheredClients);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -951,6 +955,11 @@ public class TetheringManager {
|
|||||||
maybeSendTetheredIfacesChangedCallback(states);
|
maybeSendTetheredIfacesChangedCallback(states);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onTetherClientsChanged(final List<TetheredClient> clients) {
|
||||||
|
executor.execute(() -> callback.onClientsChanged(clients));
|
||||||
|
}
|
||||||
};
|
};
|
||||||
getConnector(c -> c.registerTetheringEventCallback(remoteCallback, callerPkg));
|
getConnector(c -> c.registerTetheringEventCallback(remoteCallback, callerPkg));
|
||||||
mTetheringEventCallbacks.put(callback, remoteCallback);
|
mTetheringEventCallbacks.put(callback, remoteCallback);
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ package android.net.ip;
|
|||||||
import static android.net.InetAddresses.parseNumericAddress;
|
import static android.net.InetAddresses.parseNumericAddress;
|
||||||
import static android.net.RouteInfo.RTN_UNICAST;
|
import static android.net.RouteInfo.RTN_UNICAST;
|
||||||
import static android.net.dhcp.IDhcpServer.STATUS_SUCCESS;
|
import static android.net.dhcp.IDhcpServer.STATUS_SUCCESS;
|
||||||
|
import static android.net.shared.Inet4AddressUtils.intToInet4AddressHTH;
|
||||||
import static android.net.util.NetworkConstants.FF;
|
import static android.net.util.NetworkConstants.FF;
|
||||||
import static android.net.util.NetworkConstants.RFC7421_PREFIX_LENGTH;
|
import static android.net.util.NetworkConstants.RFC7421_PREFIX_LENGTH;
|
||||||
import static android.net.util.NetworkConstants.asByte;
|
import static android.net.util.NetworkConstants.asByte;
|
||||||
@@ -29,11 +30,15 @@ import android.net.INetworkStackStatusCallback;
|
|||||||
import android.net.IpPrefix;
|
import android.net.IpPrefix;
|
||||||
import android.net.LinkAddress;
|
import android.net.LinkAddress;
|
||||||
import android.net.LinkProperties;
|
import android.net.LinkProperties;
|
||||||
|
import android.net.MacAddress;
|
||||||
import android.net.RouteInfo;
|
import android.net.RouteInfo;
|
||||||
|
import android.net.TetheredClient;
|
||||||
import android.net.TetheringManager;
|
import android.net.TetheringManager;
|
||||||
|
import android.net.dhcp.DhcpLeaseParcelable;
|
||||||
import android.net.dhcp.DhcpServerCallbacks;
|
import android.net.dhcp.DhcpServerCallbacks;
|
||||||
import android.net.dhcp.DhcpServingParamsParcel;
|
import android.net.dhcp.DhcpServingParamsParcel;
|
||||||
import android.net.dhcp.DhcpServingParamsParcelExt;
|
import android.net.dhcp.DhcpServingParamsParcelExt;
|
||||||
|
import android.net.dhcp.IDhcpLeaseCallbacks;
|
||||||
import android.net.dhcp.IDhcpServer;
|
import android.net.dhcp.IDhcpServer;
|
||||||
import android.net.ip.RouterAdvertisementDaemon.RaParams;
|
import android.net.ip.RouterAdvertisementDaemon.RaParams;
|
||||||
import android.net.shared.NetdUtils;
|
import android.net.shared.NetdUtils;
|
||||||
@@ -48,6 +53,8 @@ import android.os.ServiceSpecificException;
|
|||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.util.SparseArray;
|
import android.util.SparseArray;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
import com.android.internal.util.MessageUtils;
|
import com.android.internal.util.MessageUtils;
|
||||||
import com.android.internal.util.State;
|
import com.android.internal.util.State;
|
||||||
import com.android.internal.util.StateMachine;
|
import com.android.internal.util.StateMachine;
|
||||||
@@ -57,7 +64,10 @@ import java.net.Inet6Address;
|
|||||||
import java.net.InetAddress;
|
import java.net.InetAddress;
|
||||||
import java.net.UnknownHostException;
|
import java.net.UnknownHostException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
@@ -130,6 +140,11 @@ public class IpServer extends StateMachine {
|
|||||||
* @param newLp the new LinkProperties to report
|
* @param newLp the new LinkProperties to report
|
||||||
*/
|
*/
|
||||||
public void updateLinkProperties(IpServer who, LinkProperties newLp) { }
|
public void updateLinkProperties(IpServer who, LinkProperties newLp) { }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notify that the DHCP leases changed in one of the IpServers.
|
||||||
|
*/
|
||||||
|
public void dhcpLeasesChanged() { }
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Capture IpServer dependencies, for injection. */
|
/** Capture IpServer dependencies, for injection. */
|
||||||
@@ -205,6 +220,8 @@ public class IpServer extends StateMachine {
|
|||||||
private IDhcpServer mDhcpServer;
|
private IDhcpServer mDhcpServer;
|
||||||
private RaParams mLastRaParams;
|
private RaParams mLastRaParams;
|
||||||
private LinkAddress mIpv4Address;
|
private LinkAddress mIpv4Address;
|
||||||
|
@NonNull
|
||||||
|
private List<TetheredClient> mDhcpLeases = Collections.emptyList();
|
||||||
|
|
||||||
public IpServer(
|
public IpServer(
|
||||||
String ifaceName, Looper looper, int interfaceType, SharedLog log,
|
String ifaceName, Looper looper, int interfaceType, SharedLog log,
|
||||||
@@ -262,6 +279,14 @@ public class IpServer extends StateMachine {
|
|||||||
return new LinkProperties(mLinkProperties);
|
return new LinkProperties(mLinkProperties);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the latest list of DHCP leases that was reported. Must be called on the IpServer looper
|
||||||
|
* thread.
|
||||||
|
*/
|
||||||
|
public List<TetheredClient> getAllLeases() {
|
||||||
|
return Collections.unmodifiableList(mDhcpLeases);
|
||||||
|
}
|
||||||
|
|
||||||
/** Stop this IpServer. After this is called this IpServer should not be used any more. */
|
/** Stop this IpServer. After this is called this IpServer should not be used any more. */
|
||||||
public void stop() {
|
public void stop() {
|
||||||
sendMessage(CMD_INTERFACE_DOWN);
|
sendMessage(CMD_INTERFACE_DOWN);
|
||||||
@@ -334,7 +359,7 @@ public class IpServer extends StateMachine {
|
|||||||
|
|
||||||
mDhcpServer = server;
|
mDhcpServer = server;
|
||||||
try {
|
try {
|
||||||
mDhcpServer.start(new OnHandlerStatusCallback() {
|
mDhcpServer.startWithCallbacks(new OnHandlerStatusCallback() {
|
||||||
@Override
|
@Override
|
||||||
public void callback(int startStatusCode) {
|
public void callback(int startStatusCode) {
|
||||||
if (startStatusCode != STATUS_SUCCESS) {
|
if (startStatusCode != STATUS_SUCCESS) {
|
||||||
@@ -342,7 +367,7 @@ public class IpServer extends StateMachine {
|
|||||||
handleError();
|
handleError();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
}, new DhcpLeaseCallback());
|
||||||
} catch (RemoteException e) {
|
} catch (RemoteException e) {
|
||||||
throw new IllegalStateException(e);
|
throw new IllegalStateException(e);
|
||||||
}
|
}
|
||||||
@@ -355,6 +380,48 @@ public class IpServer extends StateMachine {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class DhcpLeaseCallback extends IDhcpLeaseCallbacks.Stub {
|
||||||
|
@Override
|
||||||
|
public void onLeasesChanged(List<DhcpLeaseParcelable> leaseParcelables) {
|
||||||
|
final ArrayList<TetheredClient> leases = new ArrayList<>();
|
||||||
|
for (DhcpLeaseParcelable lease : leaseParcelables) {
|
||||||
|
final LinkAddress address = new LinkAddress(
|
||||||
|
intToInet4AddressHTH(lease.netAddr), lease.prefixLength);
|
||||||
|
|
||||||
|
final MacAddress macAddress;
|
||||||
|
try {
|
||||||
|
macAddress = MacAddress.fromBytes(lease.hwAddr);
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
Log.wtf(TAG, "Invalid address received from DhcpServer: "
|
||||||
|
+ Arrays.toString(lease.hwAddr));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final TetheredClient.AddressInfo addressInfo = new TetheredClient.AddressInfo(
|
||||||
|
address, lease.hostname, lease.expTime);
|
||||||
|
leases.add(new TetheredClient(
|
||||||
|
macAddress,
|
||||||
|
Collections.singletonList(addressInfo),
|
||||||
|
mInterfaceType));
|
||||||
|
}
|
||||||
|
|
||||||
|
getHandler().post(() -> {
|
||||||
|
mDhcpLeases = leases;
|
||||||
|
mCallback.dhcpLeasesChanged();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getInterfaceVersion() {
|
||||||
|
return this.VERSION;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getInterfaceHash() throws RemoteException {
|
||||||
|
return this.HASH;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private boolean startDhcp(Inet4Address addr, int prefixLen) {
|
private boolean startDhcp(Inet4Address addr, int prefixLen) {
|
||||||
if (mUsingLegacyDhcp) {
|
if (mUsingLegacyDhcp) {
|
||||||
return true;
|
return true;
|
||||||
@@ -388,6 +455,8 @@ public class IpServer extends StateMachine {
|
|||||||
mLastError = TetheringManager.TETHER_ERROR_DHCPSERVER_ERROR;
|
mLastError = TetheringManager.TETHER_ERROR_DHCPSERVER_ERROR;
|
||||||
// Not much more we can do here
|
// Not much more we can do here
|
||||||
}
|
}
|
||||||
|
mDhcpLeases.clear();
|
||||||
|
getHandler().post(mCallback::dhcpLeasesChanged);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
mDhcpServer = null;
|
mDhcpServer = null;
|
||||||
|
|||||||
@@ -0,0 +1,183 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2020 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.android.server.connectivity.tethering;
|
||||||
|
|
||||||
|
import static android.net.TetheringManager.TETHERING_WIFI;
|
||||||
|
|
||||||
|
import android.net.MacAddress;
|
||||||
|
import android.net.TetheredClient;
|
||||||
|
import android.net.TetheredClient.AddressInfo;
|
||||||
|
import android.net.ip.IpServer;
|
||||||
|
import android.net.wifi.WifiClient;
|
||||||
|
import android.os.SystemClock;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.annotation.VisibleForTesting;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tracker for clients connected to downstreams.
|
||||||
|
*
|
||||||
|
* <p>This class is not thread safe, it is intended to be used only from the tethering handler
|
||||||
|
* thread.
|
||||||
|
*/
|
||||||
|
public class ConnectedClientsTracker {
|
||||||
|
private final Clock mClock;
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private List<WifiClient> mLastWifiClients = Collections.emptyList();
|
||||||
|
@NonNull
|
||||||
|
private List<TetheredClient> mLastTetheredClients = Collections.emptyList();
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
static class Clock {
|
||||||
|
public long elapsedRealtime() {
|
||||||
|
return SystemClock.elapsedRealtime();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ConnectedClientsTracker() {
|
||||||
|
this(new Clock());
|
||||||
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
ConnectedClientsTracker(Clock clock) {
|
||||||
|
mClock = clock;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the tracker with new connected clients.
|
||||||
|
*
|
||||||
|
* <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.
|
||||||
|
* @return True if the list of clients changed since the last calculation.
|
||||||
|
*/
|
||||||
|
public boolean updateConnectedClients(
|
||||||
|
Iterable<IpServer> ipServers, @Nullable List<WifiClient> wifiClients) {
|
||||||
|
final long now = mClock.elapsedRealtime();
|
||||||
|
|
||||||
|
if (wifiClients != null) {
|
||||||
|
mLastWifiClients = wifiClients;
|
||||||
|
}
|
||||||
|
final Set<MacAddress> wifiClientMacs = getClientMacs(mLastWifiClients);
|
||||||
|
|
||||||
|
// 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) {
|
||||||
|
for (TetheredClient client : server.getAllLeases()) {
|
||||||
|
if (client.getTetheringType() == TETHERING_WIFI
|
||||||
|
&& !wifiClientMacs.contains(client.getMacAddress())) {
|
||||||
|
// Skip leases of WiFi clients that are not (or no longer) L2-connected
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
final TetheredClient prunedClient = pruneExpired(client, now);
|
||||||
|
if (prunedClient == null) continue; // All addresses expired
|
||||||
|
|
||||||
|
addLease(clientsMap, prunedClient);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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));
|
||||||
|
}
|
||||||
|
|
||||||
|
final HashSet<TetheredClient> clients = new HashSet<>(clientsMap.values());
|
||||||
|
final boolean clientsChanged = clients.size() != mLastTetheredClients.size()
|
||||||
|
|| !clients.containsAll(mLastTetheredClients);
|
||||||
|
mLastTetheredClients = Collections.unmodifiableList(new ArrayList<>(clients));
|
||||||
|
return clientsChanged;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void addLease(Map<MacAddress, TetheredClient> clientsMap, TetheredClient lease) {
|
||||||
|
final TetheredClient aggregateClient = clientsMap.getOrDefault(
|
||||||
|
lease.getMacAddress(), lease);
|
||||||
|
if (aggregateClient == lease) {
|
||||||
|
// This is the first lease with this mac address
|
||||||
|
clientsMap.put(lease.getMacAddress(), lease);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only add the address info; this assumes that the tethering type is the same when the mac
|
||||||
|
// address is the same. If a client is connected through different tethering types with the
|
||||||
|
// same mac address, connected clients callbacks will report all of its addresses under only
|
||||||
|
// one of these tethering types. This keeps the API simple considering that such a scenario
|
||||||
|
// would really be a rare edge case.
|
||||||
|
clientsMap.put(lease.getMacAddress(), aggregateClient.addAddresses(lease));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the last list of tethered clients, as calculated in {@link #updateConnectedClients}.
|
||||||
|
*
|
||||||
|
* <p>The returned list is immutable.
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
public List<TetheredClient> getLastTetheredClients() {
|
||||||
|
return mLastTetheredClients;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean hasExpiredAddress(List<AddressInfo> addresses, long now) {
|
||||||
|
for (AddressInfo info : addresses) {
|
||||||
|
if (info.getExpirationTime() <= now) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private static TetheredClient pruneExpired(TetheredClient client, long now) {
|
||||||
|
final List<AddressInfo> addresses = client.getAddresses();
|
||||||
|
if (addresses.size() == 0) return null;
|
||||||
|
if (!hasExpiredAddress(addresses, now)) return client;
|
||||||
|
|
||||||
|
final ArrayList<AddressInfo> newAddrs = new ArrayList<>(addresses.size() - 1);
|
||||||
|
for (AddressInfo info : addresses) {
|
||||||
|
if (info.getExpirationTime() > now) {
|
||||||
|
newAddrs.add(info);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newAddrs.size() == 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return new TetheredClient(client.getMacAddress(), newAddrs, client.getTetheringType());
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private static Set<MacAddress> getClientMacs(@NonNull List<WifiClient> clients) {
|
||||||
|
final Set<MacAddress> macs = new HashSet<>(clients.size());
|
||||||
|
for (WifiClient c : clients) {
|
||||||
|
macs.add(c.getMacAddress());
|
||||||
|
}
|
||||||
|
return macs;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -24,6 +24,7 @@ import static android.hardware.usb.UsbManager.USB_FUNCTION_RNDIS;
|
|||||||
import static android.net.ConnectivityManager.ACTION_RESTRICT_BACKGROUND_CHANGED;
|
import static android.net.ConnectivityManager.ACTION_RESTRICT_BACKGROUND_CHANGED;
|
||||||
import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
|
import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
|
||||||
import static android.net.ConnectivityManager.EXTRA_NETWORK_INFO;
|
import static android.net.ConnectivityManager.EXTRA_NETWORK_INFO;
|
||||||
|
import static android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK;
|
||||||
import static android.net.TetheringManager.ACTION_TETHER_STATE_CHANGED;
|
import static android.net.TetheringManager.ACTION_TETHER_STATE_CHANGED;
|
||||||
import static android.net.TetheringManager.EXTRA_ACTIVE_LOCAL_ONLY;
|
import static android.net.TetheringManager.EXTRA_ACTIVE_LOCAL_ONLY;
|
||||||
import static android.net.TetheringManager.EXTRA_ACTIVE_TETHER;
|
import static android.net.TetheringManager.EXTRA_ACTIVE_TETHER;
|
||||||
@@ -79,6 +80,7 @@ import android.net.LinkProperties;
|
|||||||
import android.net.Network;
|
import android.net.Network;
|
||||||
import android.net.NetworkInfo;
|
import android.net.NetworkInfo;
|
||||||
import android.net.TetherStatesParcel;
|
import android.net.TetherStatesParcel;
|
||||||
|
import android.net.TetheredClient;
|
||||||
import android.net.TetheringCallbackStartedParcel;
|
import android.net.TetheringCallbackStartedParcel;
|
||||||
import android.net.TetheringConfigurationParcel;
|
import android.net.TetheringConfigurationParcel;
|
||||||
import android.net.TetheringRequestParcel;
|
import android.net.TetheringRequestParcel;
|
||||||
@@ -89,6 +91,7 @@ import android.net.util.InterfaceSet;
|
|||||||
import android.net.util.PrefixUtils;
|
import android.net.util.PrefixUtils;
|
||||||
import android.net.util.SharedLog;
|
import android.net.util.SharedLog;
|
||||||
import android.net.util.VersionedBroadcastListener;
|
import android.net.util.VersionedBroadcastListener;
|
||||||
|
import android.net.wifi.WifiClient;
|
||||||
import android.net.wifi.WifiManager;
|
import android.net.wifi.WifiManager;
|
||||||
import android.net.wifi.p2p.WifiP2pGroup;
|
import android.net.wifi.p2p.WifiP2pGroup;
|
||||||
import android.net.wifi.p2p.WifiP2pInfo;
|
import android.net.wifi.p2p.WifiP2pInfo;
|
||||||
@@ -128,8 +131,10 @@ import java.net.InetAddress;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.HashSet;
|
import java.util.Collections;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
|
import java.util.LinkedHashSet;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
import java.util.concurrent.RejectedExecutionException;
|
import java.util.concurrent.RejectedExecutionException;
|
||||||
@@ -145,6 +150,10 @@ public class Tethering {
|
|||||||
private static final boolean DBG = false;
|
private static final boolean DBG = false;
|
||||||
private static final boolean VDBG = false;
|
private static final boolean VDBG = false;
|
||||||
|
|
||||||
|
// TODO: add the below permissions to @SystemApi
|
||||||
|
private static final String PERMISSION_NETWORK_SETTINGS = "android.permission.NETWORK_SETTINGS";
|
||||||
|
private static final String PERMISSION_NETWORK_STACK = "android.permission.NETWORK_STACK";
|
||||||
|
|
||||||
private static final Class[] sMessageClasses = {
|
private static final Class[] sMessageClasses = {
|
||||||
Tethering.class, TetherMasterSM.class, IpServer.class
|
Tethering.class, TetherMasterSM.class, IpServer.class
|
||||||
};
|
};
|
||||||
@@ -176,6 +185,17 @@ public class Tethering {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cookie added when registering {@link android.net.TetheringManager.TetheringEventCallback}.
|
||||||
|
*/
|
||||||
|
private static class CallbackCookie {
|
||||||
|
public final boolean hasListClientsPermission;
|
||||||
|
|
||||||
|
private CallbackCookie(boolean hasListClientsPermission) {
|
||||||
|
this.hasListClientsPermission = hasListClientsPermission;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private final SharedLog mLog = new SharedLog(TAG);
|
private final SharedLog mLog = new SharedLog(TAG);
|
||||||
private final RemoteCallbackList<ITetheringEventCallback> mTetheringEventCallbacks =
|
private final RemoteCallbackList<ITetheringEventCallback> mTetheringEventCallbacks =
|
||||||
new RemoteCallbackList<>();
|
new RemoteCallbackList<>();
|
||||||
@@ -191,7 +211,8 @@ public class Tethering {
|
|||||||
private final UpstreamNetworkMonitor mUpstreamNetworkMonitor;
|
private final UpstreamNetworkMonitor mUpstreamNetworkMonitor;
|
||||||
// TODO: Figure out how to merge this and other downstream-tracking objects
|
// TODO: Figure out how to merge this and other downstream-tracking objects
|
||||||
// into a single coherent structure.
|
// into a single coherent structure.
|
||||||
private final HashSet<IpServer> mForwardedDownstreams;
|
// Use LinkedHashSet for predictable ordering order for ConnectedClientsTracker.
|
||||||
|
private final LinkedHashSet<IpServer> mForwardedDownstreams;
|
||||||
private final VersionedBroadcastListener mCarrierConfigChange;
|
private final VersionedBroadcastListener mCarrierConfigChange;
|
||||||
private final TetheringDependencies mDeps;
|
private final TetheringDependencies mDeps;
|
||||||
private final EntitlementManager mEntitlementMgr;
|
private final EntitlementManager mEntitlementMgr;
|
||||||
@@ -200,6 +221,7 @@ public class Tethering {
|
|||||||
private final NetdCallback mNetdCallback;
|
private final NetdCallback mNetdCallback;
|
||||||
private final UserRestrictionActionListener mTetheringRestriction;
|
private final UserRestrictionActionListener mTetheringRestriction;
|
||||||
private final ActiveDataSubIdListener mActiveDataSubIdListener;
|
private final ActiveDataSubIdListener mActiveDataSubIdListener;
|
||||||
|
private final ConnectedClientsTracker mConnectedClientsTracker;
|
||||||
private int mActiveDataSubId = INVALID_SUBSCRIPTION_ID;
|
private int mActiveDataSubId = INVALID_SUBSCRIPTION_ID;
|
||||||
// All the usage of mTetheringEventCallback should run in the same thread.
|
// All the usage of mTetheringEventCallback should run in the same thread.
|
||||||
private ITetheringEventCallback mTetheringEventCallback = null;
|
private ITetheringEventCallback mTetheringEventCallback = null;
|
||||||
@@ -234,6 +256,7 @@ public class Tethering {
|
|||||||
mPublicSync = new Object();
|
mPublicSync = new Object();
|
||||||
|
|
||||||
mTetherStates = new ArrayMap<>();
|
mTetherStates = new ArrayMap<>();
|
||||||
|
mConnectedClientsTracker = new ConnectedClientsTracker();
|
||||||
|
|
||||||
mTetherMasterSM = new TetherMasterSM("TetherMaster", mLooper, deps);
|
mTetherMasterSM = new TetherMasterSM("TetherMaster", mLooper, deps);
|
||||||
mTetherMasterSM.start();
|
mTetherMasterSM.start();
|
||||||
@@ -246,7 +269,7 @@ public class Tethering {
|
|||||||
statsManager, mLog);
|
statsManager, mLog);
|
||||||
mUpstreamNetworkMonitor = mDeps.getUpstreamNetworkMonitor(mContext, mTetherMasterSM, mLog,
|
mUpstreamNetworkMonitor = mDeps.getUpstreamNetworkMonitor(mContext, mTetherMasterSM, mLog,
|
||||||
TetherMasterSM.EVENT_UPSTREAM_CALLBACK);
|
TetherMasterSM.EVENT_UPSTREAM_CALLBACK);
|
||||||
mForwardedDownstreams = new HashSet<>();
|
mForwardedDownstreams = new LinkedHashSet<>();
|
||||||
|
|
||||||
IntentFilter filter = new IntentFilter();
|
IntentFilter filter = new IntentFilter();
|
||||||
filter.addAction(ACTION_CARRIER_CONFIG_CHANGED);
|
filter.addAction(ACTION_CARRIER_CONFIG_CHANGED);
|
||||||
@@ -291,6 +314,9 @@ public class Tethering {
|
|||||||
|
|
||||||
startStateMachineUpdaters(mHandler);
|
startStateMachineUpdaters(mHandler);
|
||||||
startTrackDefaultNetwork();
|
startTrackDefaultNetwork();
|
||||||
|
getWifiManager().registerSoftApCallback(
|
||||||
|
mHandler::post /* executor */,
|
||||||
|
new TetheringSoftApCallback());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void startStateMachineUpdaters(Handler handler) {
|
private void startStateMachineUpdaters(Handler handler) {
|
||||||
@@ -385,6 +411,24 @@ 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.
|
||||||
|
@Override
|
||||||
|
public void onConnectedClientsChanged(final List<WifiClient> clients) {
|
||||||
|
if (mConnectedClientsTracker.updateConnectedClients(mForwardedDownstreams, clients)) {
|
||||||
|
reportTetherClientsChanged(mConnectedClientsTracker.getLastTetheredClients());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void interfaceStatusChanged(String iface, boolean up) {
|
void interfaceStatusChanged(String iface, boolean up) {
|
||||||
// Never called directly: only called from interfaceLinkStateChanged.
|
// Never called directly: only called from interfaceLinkStateChanged.
|
||||||
// See NetlinkHandler.cpp: notifyInterfaceChanged.
|
// See NetlinkHandler.cpp: notifyInterfaceChanged.
|
||||||
@@ -1938,14 +1982,21 @@ public class Tethering {
|
|||||||
|
|
||||||
/** Register tethering event callback */
|
/** Register tethering event callback */
|
||||||
void registerTetheringEventCallback(ITetheringEventCallback callback) {
|
void registerTetheringEventCallback(ITetheringEventCallback callback) {
|
||||||
|
final boolean hasListPermission =
|
||||||
|
hasCallingPermission(PERMISSION_NETWORK_SETTINGS)
|
||||||
|
|| hasCallingPermission(PERMISSION_MAINLINE_NETWORK_STACK)
|
||||||
|
|| hasCallingPermission(PERMISSION_NETWORK_STACK);
|
||||||
mHandler.post(() -> {
|
mHandler.post(() -> {
|
||||||
mTetheringEventCallbacks.register(callback);
|
mTetheringEventCallbacks.register(callback, new CallbackCookie(hasListPermission));
|
||||||
final TetheringCallbackStartedParcel parcel = new TetheringCallbackStartedParcel();
|
final TetheringCallbackStartedParcel parcel = new TetheringCallbackStartedParcel();
|
||||||
parcel.tetheringSupported = mDeps.isTetheringSupported();
|
parcel.tetheringSupported = mDeps.isTetheringSupported();
|
||||||
parcel.upstreamNetwork = mTetherUpstream;
|
parcel.upstreamNetwork = mTetherUpstream;
|
||||||
parcel.config = mConfig.toStableParcelable();
|
parcel.config = mConfig.toStableParcelable();
|
||||||
parcel.states =
|
parcel.states =
|
||||||
mTetherStatesParcel != null ? mTetherStatesParcel : emptyTetherStatesParcel();
|
mTetherStatesParcel != null ? mTetherStatesParcel : emptyTetherStatesParcel();
|
||||||
|
parcel.tetheredClients = hasListPermission
|
||||||
|
? mConnectedClientsTracker.getLastTetheredClients()
|
||||||
|
: Collections.emptyList();
|
||||||
try {
|
try {
|
||||||
callback.onCallbackStarted(parcel);
|
callback.onCallbackStarted(parcel);
|
||||||
} catch (RemoteException e) {
|
} catch (RemoteException e) {
|
||||||
@@ -1965,6 +2016,10 @@ public class Tethering {
|
|||||||
return parcel;
|
return parcel;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean hasCallingPermission(@NonNull String permission) {
|
||||||
|
return mContext.checkCallingPermission(permission) == PERMISSION_GRANTED;
|
||||||
|
}
|
||||||
|
|
||||||
/** Unregister tethering event callback */
|
/** Unregister tethering event callback */
|
||||||
void unregisterTetheringEventCallback(ITetheringEventCallback callback) {
|
void unregisterTetheringEventCallback(ITetheringEventCallback callback) {
|
||||||
mHandler.post(() -> {
|
mHandler.post(() -> {
|
||||||
@@ -2018,6 +2073,24 @@ public class Tethering {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void reportTetherClientsChanged(List<TetheredClient> clients) {
|
||||||
|
final int length = mTetheringEventCallbacks.beginBroadcast();
|
||||||
|
try {
|
||||||
|
for (int i = 0; i < length; i++) {
|
||||||
|
try {
|
||||||
|
final CallbackCookie cookie =
|
||||||
|
(CallbackCookie) mTetheringEventCallbacks.getBroadcastCookie(i);
|
||||||
|
if (!cookie.hasListClientsPermission) continue;
|
||||||
|
mTetheringEventCallbacks.getBroadcastItem(i).onTetherClientsChanged(clients);
|
||||||
|
} catch (RemoteException e) {
|
||||||
|
// Not really very much to do here.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
mTetheringEventCallbacks.finishBroadcast();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter writer, @Nullable String[] args) {
|
void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter writer, @Nullable String[] args) {
|
||||||
// Binder.java closes the resource for us.
|
// Binder.java closes the resource for us.
|
||||||
@SuppressWarnings("resource")
|
@SuppressWarnings("resource")
|
||||||
@@ -2109,6 +2182,14 @@ public class Tethering {
|
|||||||
public void updateLinkProperties(IpServer who, LinkProperties newLp) {
|
public void updateLinkProperties(IpServer who, LinkProperties newLp) {
|
||||||
notifyLinkPropertiesChanged(who, newLp);
|
notifyLinkPropertiesChanged(who, newLp);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void dhcpLeasesChanged() {
|
||||||
|
if (mConnectedClientsTracker.updateConnectedClients(
|
||||||
|
mForwardedDownstreams, null /* wifiClients */)) {
|
||||||
|
reportTetherClientsChanged(mConnectedClientsTracker.getLastTetheredClients());
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -469,7 +469,8 @@ public class IpServerTest {
|
|||||||
|
|
||||||
private void assertDhcpStarted(IpPrefix expectedPrefix) throws Exception {
|
private void assertDhcpStarted(IpPrefix expectedPrefix) throws Exception {
|
||||||
verify(mDependencies, times(1)).makeDhcpServer(eq(IFACE_NAME), any(), any());
|
verify(mDependencies, times(1)).makeDhcpServer(eq(IFACE_NAME), any(), any());
|
||||||
verify(mDhcpServer, timeout(MAKE_DHCPSERVER_TIMEOUT_MS).times(1)).start(any());
|
verify(mDhcpServer, timeout(MAKE_DHCPSERVER_TIMEOUT_MS).times(1)).startWithCallbacks(
|
||||||
|
any(), any());
|
||||||
final DhcpServingParamsParcel params = mDhcpParamsCaptor.getValue();
|
final DhcpServingParamsParcel params = mDhcpParamsCaptor.getValue();
|
||||||
// Last address byte is random
|
// Last address byte is random
|
||||||
assertTrue(expectedPrefix.contains(intToInet4AddressHTH(params.serverAddr)));
|
assertTrue(expectedPrefix.contains(intToInet4AddressHTH(params.serverAddr)));
|
||||||
|
|||||||
@@ -0,0 +1,157 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2020 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.android.server.connectivity.tethering
|
||||||
|
|
||||||
|
import android.net.LinkAddress
|
||||||
|
import android.net.MacAddress
|
||||||
|
import android.net.TetheredClient
|
||||||
|
import android.net.TetheredClient.AddressInfo
|
||||||
|
import android.net.TetheringManager.TETHERING_USB
|
||||||
|
import android.net.TetheringManager.TETHERING_WIFI
|
||||||
|
import android.net.ip.IpServer
|
||||||
|
import android.net.wifi.WifiClient
|
||||||
|
import androidx.test.filters.SmallTest
|
||||||
|
import androidx.test.runner.AndroidJUnit4
|
||||||
|
import org.junit.Test
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
import org.mockito.Mockito.doReturn
|
||||||
|
import org.mockito.Mockito.mock
|
||||||
|
import kotlin.test.assertEquals
|
||||||
|
import kotlin.test.assertFalse
|
||||||
|
import kotlin.test.assertTrue
|
||||||
|
|
||||||
|
@RunWith(AndroidJUnit4::class)
|
||||||
|
@SmallTest
|
||||||
|
class ConnectedClientsTrackerTest {
|
||||||
|
|
||||||
|
private val server1 = mock(IpServer::class.java)
|
||||||
|
private val server2 = mock(IpServer::class.java)
|
||||||
|
private val servers = listOf(server1, server2)
|
||||||
|
|
||||||
|
private val clock = TestClock(1324L)
|
||||||
|
|
||||||
|
private val client1Addr = MacAddress.fromString("01:23:45:67:89:0A")
|
||||||
|
private val client1 = TetheredClient(client1Addr, listOf(
|
||||||
|
AddressInfo(LinkAddress("192.168.43.44/32"), null /* hostname */, clock.time + 20)),
|
||||||
|
TETHERING_WIFI)
|
||||||
|
private val wifiClient1 = makeWifiClient(client1Addr)
|
||||||
|
private val client2Addr = MacAddress.fromString("02:34:56:78:90:AB")
|
||||||
|
private val client2Exp30AddrInfo = AddressInfo(
|
||||||
|
LinkAddress("192.168.43.45/32"), "my_hostname", clock.time + 30)
|
||||||
|
private val client2 = TetheredClient(client2Addr, listOf(
|
||||||
|
client2Exp30AddrInfo,
|
||||||
|
AddressInfo(LinkAddress("2001:db8:12::34/72"), "other_hostname", clock.time + 10)),
|
||||||
|
TETHERING_WIFI)
|
||||||
|
private val wifiClient2 = makeWifiClient(client2Addr)
|
||||||
|
private val client3Addr = MacAddress.fromString("03:45:67:89:0A:BC")
|
||||||
|
private val client3 = TetheredClient(client3Addr,
|
||||||
|
listOf(AddressInfo(LinkAddress("2001:db8:34::34/72"), "other_other_hostname",
|
||||||
|
clock.time + 10)),
|
||||||
|
TETHERING_USB)
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testUpdateConnectedClients() {
|
||||||
|
doReturn(emptyList<TetheredClient>()).`when`(server1).allLeases
|
||||||
|
doReturn(emptyList<TetheredClient>()).`when`(server2).allLeases
|
||||||
|
|
||||||
|
val tracker = ConnectedClientsTracker(clock)
|
||||||
|
assertFalse(tracker.updateConnectedClients(servers, null))
|
||||||
|
|
||||||
|
// Obtain a lease for client 1
|
||||||
|
doReturn(listOf(client1)).`when`(server1).allLeases
|
||||||
|
assertSameClients(listOf(client1), assertNewClients(tracker, servers, listOf(wifiClient1)))
|
||||||
|
|
||||||
|
// Client 2 L2-connected, no lease yet
|
||||||
|
val client2WithoutAddr = TetheredClient(client2Addr, emptyList(), TETHERING_WIFI)
|
||||||
|
assertSameClients(listOf(client1, client2WithoutAddr),
|
||||||
|
assertNewClients(tracker, servers, listOf(wifiClient1, wifiClient2)))
|
||||||
|
|
||||||
|
// Client 2 lease obtained
|
||||||
|
doReturn(listOf(client1, client2)).`when`(server1).allLeases
|
||||||
|
assertSameClients(listOf(client1, client2), assertNewClients(tracker, servers, null))
|
||||||
|
|
||||||
|
// Client 3 lease obtained
|
||||||
|
doReturn(listOf(client3)).`when`(server2).allLeases
|
||||||
|
assertSameClients(listOf(client1, client2, client3),
|
||||||
|
assertNewClients(tracker, servers, null))
|
||||||
|
|
||||||
|
// Client 2 L2-disconnected
|
||||||
|
assertSameClients(listOf(client1, client3),
|
||||||
|
assertNewClients(tracker, servers, listOf(wifiClient1)))
|
||||||
|
|
||||||
|
// Client 1 L2-disconnected
|
||||||
|
assertSameClients(listOf(client3), assertNewClients(tracker, servers, emptyList()))
|
||||||
|
|
||||||
|
// Client 1 comes back
|
||||||
|
assertSameClients(listOf(client1, client3),
|
||||||
|
assertNewClients(tracker, servers, listOf(wifiClient1)))
|
||||||
|
|
||||||
|
// Leases lost, client 1 still L2-connected
|
||||||
|
doReturn(emptyList<TetheredClient>()).`when`(server1).allLeases
|
||||||
|
doReturn(emptyList<TetheredClient>()).`when`(server2).allLeases
|
||||||
|
assertSameClients(listOf(TetheredClient(client1Addr, emptyList(), TETHERING_WIFI)),
|
||||||
|
assertNewClients(tracker, servers, null))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testUpdateConnectedClients_LeaseExpiration() {
|
||||||
|
val tracker = ConnectedClientsTracker(clock)
|
||||||
|
doReturn(listOf(client1, client2)).`when`(server1).allLeases
|
||||||
|
doReturn(listOf(client3)).`when`(server2).allLeases
|
||||||
|
assertSameClients(listOf(client1, client2, client3), assertNewClients(
|
||||||
|
tracker, servers, listOf(wifiClient1, wifiClient2)))
|
||||||
|
|
||||||
|
clock.time += 20
|
||||||
|
// Client 3 has no remaining lease: removed
|
||||||
|
val expectedClients = listOf(
|
||||||
|
// Client 1 has no remaining lease but is L2-connected
|
||||||
|
TetheredClient(client1Addr, emptyList(), TETHERING_WIFI),
|
||||||
|
// Client 2 has some expired leases
|
||||||
|
TetheredClient(
|
||||||
|
client2Addr,
|
||||||
|
// Only the "t + 30" address is left, the "t + 10" address expired
|
||||||
|
listOf(client2Exp30AddrInfo),
|
||||||
|
TETHERING_WIFI))
|
||||||
|
assertSameClients(expectedClients, assertNewClients(tracker, servers, null))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun assertNewClients(
|
||||||
|
tracker: ConnectedClientsTracker,
|
||||||
|
ipServers: Iterable<IpServer>,
|
||||||
|
wifiClients: List<WifiClient>?
|
||||||
|
): List<TetheredClient> {
|
||||||
|
assertTrue(tracker.updateConnectedClients(ipServers, wifiClients))
|
||||||
|
return tracker.lastTetheredClients
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun assertSameClients(expected: List<TetheredClient>, actual: List<TetheredClient>) {
|
||||||
|
val expectedSet = HashSet(expected)
|
||||||
|
assertEquals(expected.size, expectedSet.size)
|
||||||
|
assertEquals(expectedSet, HashSet(actual))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun makeWifiClient(macAddr: MacAddress): WifiClient {
|
||||||
|
// Use a mock WifiClient as the constructor is not part of the WiFi module exported API.
|
||||||
|
return mock(WifiClient::class.java).apply { doReturn(macAddr).`when`(this).macAddress }
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TestClock(var time: Long) : ConnectedClientsTracker.Clock() {
|
||||||
|
override fun elapsedRealtime(): Long {
|
||||||
|
return time
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -88,6 +88,7 @@ import android.net.NetworkCapabilities;
|
|||||||
import android.net.NetworkRequest;
|
import android.net.NetworkRequest;
|
||||||
import android.net.RouteInfo;
|
import android.net.RouteInfo;
|
||||||
import android.net.TetherStatesParcel;
|
import android.net.TetherStatesParcel;
|
||||||
|
import android.net.TetheredClient;
|
||||||
import android.net.TetheringCallbackStartedParcel;
|
import android.net.TetheringCallbackStartedParcel;
|
||||||
import android.net.TetheringConfigurationParcel;
|
import android.net.TetheringConfigurationParcel;
|
||||||
import android.net.TetheringRequestParcel;
|
import android.net.TetheringRequestParcel;
|
||||||
@@ -142,6 +143,7 @@ import java.net.Inet6Address;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Vector;
|
import java.util.Vector;
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4.class)
|
@RunWith(AndroidJUnit4.class)
|
||||||
@@ -470,6 +472,7 @@ public class TetheringTest {
|
|||||||
ArgumentCaptor.forClass(PhoneStateListener.class);
|
ArgumentCaptor.forClass(PhoneStateListener.class);
|
||||||
verify(mTelephonyManager).listen(phoneListenerCaptor.capture(),
|
verify(mTelephonyManager).listen(phoneListenerCaptor.capture(),
|
||||||
eq(PhoneStateListener.LISTEN_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGE));
|
eq(PhoneStateListener.LISTEN_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGE));
|
||||||
|
verify(mWifiManager).registerSoftApCallback(any(), any());
|
||||||
mPhoneStateListener = phoneListenerCaptor.getValue();
|
mPhoneStateListener = phoneListenerCaptor.getValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -728,7 +731,8 @@ public class TetheringTest {
|
|||||||
|
|
||||||
sendIPv6TetherUpdates(upstreamState);
|
sendIPv6TetherUpdates(upstreamState);
|
||||||
verify(mRouterAdvertisementDaemon, never()).buildNewRa(any(), notNull());
|
verify(mRouterAdvertisementDaemon, never()).buildNewRa(any(), notNull());
|
||||||
verify(mDhcpServer, timeout(DHCPSERVER_START_TIMEOUT_MS).times(1)).start(any());
|
verify(mDhcpServer, timeout(DHCPSERVER_START_TIMEOUT_MS).times(1)).startWithCallbacks(
|
||||||
|
any(), any());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -764,7 +768,8 @@ public class TetheringTest {
|
|||||||
verify(mNetd, times(1)).tetherAddForward(TEST_USB_IFNAME, TEST_MOBILE_IFNAME);
|
verify(mNetd, times(1)).tetherAddForward(TEST_USB_IFNAME, TEST_MOBILE_IFNAME);
|
||||||
verify(mNetd, times(1)).ipfwdAddInterfaceForward(TEST_USB_IFNAME, TEST_MOBILE_IFNAME);
|
verify(mNetd, times(1)).ipfwdAddInterfaceForward(TEST_USB_IFNAME, TEST_MOBILE_IFNAME);
|
||||||
verify(mRouterAdvertisementDaemon, times(1)).start();
|
verify(mRouterAdvertisementDaemon, times(1)).start();
|
||||||
verify(mDhcpServer, timeout(DHCPSERVER_START_TIMEOUT_MS).times(1)).start(any());
|
verify(mDhcpServer, timeout(DHCPSERVER_START_TIMEOUT_MS).times(1)).startWithCallbacks(
|
||||||
|
any(), any());
|
||||||
|
|
||||||
sendIPv6TetherUpdates(upstreamState);
|
sendIPv6TetherUpdates(upstreamState);
|
||||||
verify(mRouterAdvertisementDaemon, times(1)).buildNewRa(any(), notNull());
|
verify(mRouterAdvertisementDaemon, times(1)).buildNewRa(any(), notNull());
|
||||||
@@ -778,7 +783,8 @@ public class TetheringTest {
|
|||||||
|
|
||||||
verify(mNetd, times(1)).tetherAddForward(TEST_USB_IFNAME, TEST_XLAT_MOBILE_IFNAME);
|
verify(mNetd, times(1)).tetherAddForward(TEST_USB_IFNAME, TEST_XLAT_MOBILE_IFNAME);
|
||||||
verify(mNetd, times(1)).tetherAddForward(TEST_USB_IFNAME, TEST_MOBILE_IFNAME);
|
verify(mNetd, times(1)).tetherAddForward(TEST_USB_IFNAME, TEST_MOBILE_IFNAME);
|
||||||
verify(mDhcpServer, timeout(DHCPSERVER_START_TIMEOUT_MS).times(1)).start(any());
|
verify(mDhcpServer, timeout(DHCPSERVER_START_TIMEOUT_MS).times(1)).startWithCallbacks(
|
||||||
|
any(), any());
|
||||||
verify(mNetd, times(1)).ipfwdAddInterfaceForward(TEST_USB_IFNAME, TEST_XLAT_MOBILE_IFNAME);
|
verify(mNetd, times(1)).ipfwdAddInterfaceForward(TEST_USB_IFNAME, TEST_XLAT_MOBILE_IFNAME);
|
||||||
verify(mNetd, times(1)).ipfwdAddInterfaceForward(TEST_USB_IFNAME, TEST_MOBILE_IFNAME);
|
verify(mNetd, times(1)).ipfwdAddInterfaceForward(TEST_USB_IFNAME, TEST_MOBILE_IFNAME);
|
||||||
|
|
||||||
@@ -794,7 +800,8 @@ public class TetheringTest {
|
|||||||
runUsbTethering(upstreamState);
|
runUsbTethering(upstreamState);
|
||||||
|
|
||||||
verify(mNetd, times(1)).tetherAddForward(TEST_USB_IFNAME, TEST_MOBILE_IFNAME);
|
verify(mNetd, times(1)).tetherAddForward(TEST_USB_IFNAME, TEST_MOBILE_IFNAME);
|
||||||
verify(mDhcpServer, timeout(DHCPSERVER_START_TIMEOUT_MS).times(1)).start(any());
|
verify(mDhcpServer, timeout(DHCPSERVER_START_TIMEOUT_MS).times(1)).startWithCallbacks(
|
||||||
|
any(), any());
|
||||||
verify(mNetd, times(1)).ipfwdAddInterfaceForward(TEST_USB_IFNAME, TEST_MOBILE_IFNAME);
|
verify(mNetd, times(1)).ipfwdAddInterfaceForward(TEST_USB_IFNAME, TEST_MOBILE_IFNAME);
|
||||||
|
|
||||||
// Then 464xlat comes up
|
// Then 464xlat comes up
|
||||||
@@ -817,7 +824,8 @@ public class TetheringTest {
|
|||||||
verify(mNetd, times(1)).tetherAddForward(TEST_USB_IFNAME, TEST_MOBILE_IFNAME);
|
verify(mNetd, times(1)).tetherAddForward(TEST_USB_IFNAME, TEST_MOBILE_IFNAME);
|
||||||
verify(mNetd, times(1)).ipfwdAddInterfaceForward(TEST_USB_IFNAME, TEST_MOBILE_IFNAME);
|
verify(mNetd, times(1)).ipfwdAddInterfaceForward(TEST_USB_IFNAME, TEST_MOBILE_IFNAME);
|
||||||
// DHCP not restarted on downstream (still times(1))
|
// DHCP not restarted on downstream (still times(1))
|
||||||
verify(mDhcpServer, timeout(DHCPSERVER_START_TIMEOUT_MS).times(1)).start(any());
|
verify(mDhcpServer, timeout(DHCPSERVER_START_TIMEOUT_MS).times(1)).startWithCallbacks(
|
||||||
|
any(), any());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -847,7 +855,8 @@ public class TetheringTest {
|
|||||||
public void workingNcmTethering() throws Exception {
|
public void workingNcmTethering() throws Exception {
|
||||||
runNcmTethering();
|
runNcmTethering();
|
||||||
|
|
||||||
verify(mDhcpServer, timeout(DHCPSERVER_START_TIMEOUT_MS).times(1)).start(any());
|
verify(mDhcpServer, timeout(DHCPSERVER_START_TIMEOUT_MS).times(1)).startWithCallbacks(
|
||||||
|
any(), any());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -1170,6 +1179,11 @@ public class TetheringTest {
|
|||||||
mTetherStates.add(states);
|
mTetherStates.add(states);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onTetherClientsChanged(List<TetheredClient> clients) {
|
||||||
|
// TODO: check this
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCallbackStarted(TetheringCallbackStartedParcel parcel) {
|
public void onCallbackStarted(TetheringCallbackStartedParcel parcel) {
|
||||||
mActualUpstreams.add(parcel.upstreamNetwork);
|
mActualUpstreams.add(parcel.upstreamNetwork);
|
||||||
|
|||||||
Reference in New Issue
Block a user