Merge "Improve message for SIM cards not supporting data" into main
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/DevSdkIgnoreRunner.kt b/staticlibs/testutils/devicetests/com/android/testutils/DevSdkIgnoreRunner.kt
index 1ba83ca..10accd4 100644
--- a/staticlibs/testutils/devicetests/com/android/testutils/DevSdkIgnoreRunner.kt
+++ b/staticlibs/testutils/devicetests/com/android/testutils/DevSdkIgnoreRunner.kt
@@ -17,9 +17,9 @@
package com.android.testutils
import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.net.module.util.LinkPropertiesUtils.CompareOrUpdateResult
import com.android.testutils.DevSdkIgnoreRule.IgnoreAfter
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
-import java.lang.IllegalStateException
import java.lang.reflect.Modifier
import org.junit.runner.Description
import org.junit.runner.Runner
@@ -110,10 +110,19 @@
notifier.fireTestStarted(leakMonitorDesc)
val threadCountsAfterTest = getAllThreadNameCounts()
- if (threadCountsBeforeTest != threadCountsAfterTest) {
+ // TODO : move CompareOrUpdateResult to its own util instead of LinkProperties.
+ val threadsDiff = CompareOrUpdateResult(
+ threadCountsBeforeTest.entries,
+ threadCountsAfterTest.entries
+ ) { it.key }
+ // Ignore removed threads, which typically are generated by previous tests.
+ // Because this is in the threadsDiff.updated member, for sure there is a
+ // corresponding key in threadCountsBeforeTest.
+ val increasedThreads = threadsDiff.updated
+ .filter { threadCountsBeforeTest[it.key]!! < it.value }
+ if (threadsDiff.added.isNotEmpty() || increasedThreads.isNotEmpty()) {
notifier.fireTestFailure(Failure(leakMonitorDesc,
- IllegalStateException("Expected threads: $threadCountsBeforeTest " +
- "but got: $threadCountsAfterTest")))
+ IllegalStateException("Unexpected thread changes: $threadsDiff")))
}
notifier.fireTestFinished(leakMonitorDesc)
}
@@ -121,9 +130,13 @@
private fun getAllThreadNameCounts(): Map<String, Int> {
// Get the counts of threads in the group per name.
// Filter system thread groups.
+ // Also ignore threads with 1 count, this effectively filtered out threads created by the
+ // test runner or other system components. e.g. hwuiTask*, queued-work-looper,
+ // SurfaceSyncGroupTimer, RenderThread, Time-limited test, etc.
return Thread.getAllStackTraces().keys
.filter { it.threadGroup?.name != "system" }
.groupingBy { it.name }.eachCount()
+ .filter { it.value != 1 }
}
override fun getDescription(): Description {
diff --git a/tests/unit/java/com/android/server/ConnectivityServiceTest.java b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
index b8cf08e..fff9a30 100755
--- a/tests/unit/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
@@ -1659,8 +1659,7 @@
waitForIdle();
}
- public void startLegacyVpnPrivileged(VpnProfile profile,
- @Nullable Network underlying, @NonNull LinkProperties egress) {
+ public void startLegacyVpnPrivileged(VpnProfile profile) {
switch (profile.type) {
case VpnProfile.TYPE_IKEV2_IPSEC_RSA:
case VpnProfile.TYPE_IKEV2_IPSEC_USER_PASS:
@@ -10252,7 +10251,7 @@
b.expectBroadcast();
// Simulate LockdownVpnTracker attempting to start the VPN since it received the
// systemDefault callback.
- mMockVpn.startLegacyVpnPrivileged(profile, mCellAgent.getNetwork(), cellLp);
+ mMockVpn.startLegacyVpnPrivileged(profile);
if (expectSetVpnDefaultForUids) {
// setVpnDefaultForUids() releases the original network request and creates a VPN
// request so LOST callback is received.
@@ -10323,7 +10322,7 @@
// callback with different network.
final ExpectedBroadcast b6 = expectConnectivityAction(TYPE_VPN, DetailedState.DISCONNECTED);
mMockVpn.stopVpnRunnerPrivileged();
- mMockVpn.startLegacyVpnPrivileged(profile, mWiFiAgent.getNetwork(), wifiLp);
+ mMockVpn.startLegacyVpnPrivileged(profile);
// VPN network is disconnected (to restart)
callback.expect(LOST, mMockVpn);
defaultCallback.expect(LOST, mMockVpn);
diff --git a/tests/unit/java/com/android/server/connectivity/VpnTest.java b/tests/unit/java/com/android/server/connectivity/VpnTest.java
index 109c132..ea2228e 100644
--- a/tests/unit/java/com/android/server/connectivity/VpnTest.java
+++ b/tests/unit/java/com/android/server/connectivity/VpnTest.java
@@ -2039,16 +2039,7 @@
private Vpn startLegacyVpn(final Vpn vpn, final VpnProfile vpnProfile) throws Exception {
setMockedUsers(PRIMARY_USER);
-
- // Dummy egress interface
- final LinkProperties lp = new LinkProperties();
- lp.setInterfaceName(EGRESS_IFACE);
-
- final RouteInfo defaultRoute = new RouteInfo(new IpPrefix(Inet4Address.ANY, 0),
- InetAddresses.parseNumericAddress("192.0.2.0"), EGRESS_IFACE);
- lp.addRoute(defaultRoute);
-
- vpn.startLegacyVpn(vpnProfile, EGRESS_NETWORK, lp);
+ vpn.startLegacyVpn(vpnProfile);
return vpn;
}
diff --git a/thread/service/Android.bp b/thread/service/Android.bp
index bd265e6..35ae3c2 100644
--- a/thread/service/Android.bp
+++ b/thread/service/Android.bp
@@ -43,6 +43,9 @@
"ot-daemon-aidl-java",
],
apex_available: ["com.android.tethering"],
+ optimize: {
+ proguard_flags_files: ["proguard.flags"],
+ },
}
cc_library_shared {
diff --git a/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java b/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java
index 33516aa..60c97bf 100644
--- a/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java
+++ b/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java
@@ -14,6 +14,10 @@
package com.android.server.thread;
+import static android.net.MulticastRoutingConfig.CONFIG_FORWARD_NONE;
+import static android.net.MulticastRoutingConfig.FORWARD_NONE;
+import static android.net.MulticastRoutingConfig.FORWARD_SELECTED;
+import static android.net.MulticastRoutingConfig.FORWARD_WITH_MIN_SCOPE;
import static android.net.thread.ActiveOperationalDataset.CHANNEL_PAGE_24_GHZ;
import static android.net.thread.ActiveOperationalDataset.LENGTH_EXTENDED_PAN_ID;
import static android.net.thread.ActiveOperationalDataset.LENGTH_MESH_LOCAL_PREFIX_BITS;
@@ -51,13 +55,20 @@
import android.annotation.NonNull;
import android.content.Context;
import android.net.ConnectivityManager;
+import android.net.IpPrefix;
import android.net.LinkAddress;
import android.net.LinkProperties;
+import android.net.LocalNetworkConfig;
+import android.net.MulticastRoutingConfig;
+import android.net.LocalNetworkInfo;
+import android.net.Network;
import android.net.NetworkAgent;
import android.net.NetworkAgentConfig;
import android.net.NetworkCapabilities;
import android.net.NetworkProvider;
+import android.net.NetworkRequest;
import android.net.NetworkScore;
+import android.net.RouteInfo;
import android.net.thread.ActiveOperationalDataset;
import android.net.thread.ActiveOperationalDataset.SecurityPolicy;
import android.net.thread.IActiveOperationalDatasetReceiver;
@@ -85,8 +96,10 @@
import com.android.server.thread.openthread.IOtStatusReceiver;
import com.android.server.thread.openthread.Ipv6AddressInfo;
import com.android.server.thread.openthread.OtDaemonState;
+import com.android.server.thread.openthread.BorderRouterConfigurationParcel;
import java.io.IOException;
+import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.security.SecureRandom;
@@ -131,6 +144,14 @@
private IOtDaemon mOtDaemon;
private NetworkAgent mNetworkAgent;
+ private MulticastRoutingConfig mUpstreamMulticastRoutingConfig = CONFIG_FORWARD_NONE;
+ private MulticastRoutingConfig mDownstreamMulticastRoutingConfig = CONFIG_FORWARD_NONE;
+ private Network mUpstreamNetwork;
+ private final NetworkRequest mUpstreamNetworkRequest;
+ private final HashMap<Network, String> mNetworkToInterface;
+ private final LocalNetworkConfig mLocalNetworkConfig;
+
+ private BorderRouterConfigurationParcel mBorderRouterConfig;
@VisibleForTesting
ThreadNetworkControllerService(
@@ -147,6 +168,18 @@
mOtDaemonSupplier = otDaemonSupplier;
mConnectivityManager = connectivityManager;
mTunIfController = tunIfController;
+ mUpstreamNetworkRequest =
+ new NetworkRequest.Builder()
+ .clearCapabilities()
+ .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
+ .addTransportType(NetworkCapabilities.TRANSPORT_ETHERNET)
+ .build();
+ mLocalNetworkConfig =
+ new LocalNetworkConfig.Builder()
+ .setUpstreamSelector(mUpstreamNetworkRequest)
+ .build();
+ mNetworkToInterface = new HashMap<Network, String>();
+ mBorderRouterConfig = new BorderRouterConfigurationParcel();
}
public static ThreadNetworkControllerService newInstance(Context context) {
@@ -167,19 +200,24 @@
private static NetworkCapabilities newNetworkCapabilities() {
return new NetworkCapabilities.Builder()
.addTransportType(NetworkCapabilities.TRANSPORT_THREAD)
+ .addCapability(NetworkCapabilities.NET_CAPABILITY_LOCAL_NETWORK)
.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED)
.build();
}
- private static InetAddress addressInfoToInetAddress(Ipv6AddressInfo addressInfo) {
+ private static Inet6Address bytesToInet6Address(byte[] addressBytes) {
try {
- return InetAddress.getByAddress(addressInfo.address);
+ return (Inet6Address) Inet6Address.getByAddress(addressBytes);
} catch (UnknownHostException e) {
- // This is impossible unless the Thread daemon is critically broken
+ // This is unlikely to happen unless the Thread daemon is critically broken
return null;
}
}
+ private static InetAddress addressInfoToInetAddress(Ipv6AddressInfo addressInfo) {
+ return bytesToInet6Address(addressInfo.address);
+ }
+
private static LinkAddress newLinkAddress(Ipv6AddressInfo addressInfo) {
long deprecationTimeMillis =
addressInfo.isPreferred
@@ -244,11 +282,77 @@
mLinkProperties.setInterfaceName(TUN_IF_NAME);
mLinkProperties.setMtu(TunInterfaceController.MTU);
mConnectivityManager.registerNetworkProvider(mNetworkProvider);
+ requestUpstreamNetwork();
initializeOtDaemon();
});
}
+ private void requestUpstreamNetwork() {
+ mConnectivityManager.registerNetworkCallback(
+ mUpstreamNetworkRequest,
+ new ConnectivityManager.NetworkCallback() {
+ @Override
+ public void onAvailable(@NonNull Network network) {
+ Log.i(TAG, "onAvailable: " + network);
+ }
+
+ @Override
+ public void onLost(@NonNull Network network) {
+ Log.i(TAG, "onLost: " + network);
+ }
+
+ @Override
+ public void onLinkPropertiesChanged(
+ @NonNull Network network, @NonNull LinkProperties linkProperties) {
+ Log.i(
+ TAG,
+ String.format(
+ "onLinkPropertiesChanged: {network: %s, interface: %s}",
+ network, linkProperties.getInterfaceName()));
+ mNetworkToInterface.put(network, linkProperties.getInterfaceName());
+ if (network.equals(mUpstreamNetwork)) {
+ enableBorderRouting(mNetworkToInterface.get(mUpstreamNetwork));
+ }
+ }
+ },
+ mHandler);
+ }
+
+ private final class ThreadNetworkCallback extends ConnectivityManager.NetworkCallback {
+ @Override
+ public void onAvailable(@NonNull Network network) {
+ Log.i(TAG, "onAvailable: Thread network Available");
+ }
+
+ @Override
+ public void onLocalNetworkInfoChanged(
+ @NonNull Network network, @NonNull LocalNetworkInfo localNetworkInfo) {
+ Log.i(TAG, "onLocalNetworkInfoChanged: " + localNetworkInfo);
+ if (localNetworkInfo.getUpstreamNetwork() == null) {
+ mUpstreamNetwork = null;
+ return;
+ }
+ if (!localNetworkInfo.getUpstreamNetwork().equals(mUpstreamNetwork)) {
+ mUpstreamNetwork = localNetworkInfo.getUpstreamNetwork();
+ if (mNetworkToInterface.containsKey(mUpstreamNetwork)) {
+ enableBorderRouting(mNetworkToInterface.get(mUpstreamNetwork));
+ }
+ }
+ }
+ }
+
+ private void requestThreadNetwork() {
+ mConnectivityManager.registerNetworkCallback(
+ new NetworkRequest.Builder()
+ .clearCapabilities()
+ .addTransportType(NetworkCapabilities.TRANSPORT_THREAD)
+ .removeForbiddenCapability(NetworkCapabilities.NET_CAPABILITY_LOCAL_NETWORK)
+ .build(),
+ new ThreadNetworkCallback(),
+ mHandler);
+ }
+
private void registerThreadNetwork() {
if (mNetworkAgent != null) {
return;
@@ -258,6 +362,7 @@
new NetworkScore.Builder()
.setKeepConnectedReason(NetworkScore.KEEP_CONNECTED_LOCAL_NETWORK)
.build();
+ requestThreadNetwork();
mNetworkAgent =
new NetworkAgent(
mContext,
@@ -265,6 +370,7 @@
TAG,
netCaps,
mLinkProperties,
+ mLocalNetworkConfig,
score,
new NetworkAgentConfig.Builder().build(),
mNetworkProvider) {};
@@ -304,10 +410,19 @@
}
private void updateNetworkLinkProperties(LinkAddress linkAddress, boolean isAdded) {
+ RouteInfo routeInfo =
+ new RouteInfo(
+ new IpPrefix(linkAddress.getAddress(), 64),
+ null,
+ TUN_IF_NAME,
+ RouteInfo.RTN_UNICAST,
+ TunInterfaceController.MTU);
if (isAdded) {
mLinkProperties.addLinkAddress(linkAddress);
+ mLinkProperties.addRoute(routeInfo);
} else {
mLinkProperties.removeLinkAddress(linkAddress);
+ mLinkProperties.removeRoute(routeInfo);
}
// The Thread daemon can send link property updates before the networkAgent is
@@ -557,6 +672,39 @@
}
}
+ private void enableBorderRouting(String infraIfName) {
+ if (mBorderRouterConfig.isBorderRoutingEnabled
+ && infraIfName.equals(mBorderRouterConfig.infraInterfaceName)) {
+ return;
+ }
+ Log.i(TAG, "enableBorderRouting on AIL: " + infraIfName);
+ try {
+ mBorderRouterConfig.infraInterfaceName = infraIfName;
+ mBorderRouterConfig.infraInterfaceIcmp6Socket =
+ InfraInterfaceController.createIcmp6Socket(infraIfName);
+ mBorderRouterConfig.isBorderRoutingEnabled = true;
+
+ mOtDaemon.configureBorderRouter(
+ mBorderRouterConfig,
+ new IOtStatusReceiver.Stub() {
+ @Override
+ public void onSuccess() {
+ Log.i(TAG, "configure border router successfully");
+ }
+
+ @Override
+ public void onError(int i, String s) {
+ Log.w(
+ TAG,
+ String.format(
+ "failed to configure border router: %d %s", i, s));
+ }
+ });
+ } catch (Exception e) {
+ Log.w(TAG, "enableBorderRouting failed: " + e);
+ }
+ }
+
private void handleThreadInterfaceStateChanged(boolean isUp) {
try {
mTunIfController.setInterfaceUp(isUp);
@@ -597,6 +745,100 @@
updateNetworkLinkProperties(linkAddress, isAdded);
}
+ private boolean isMulticastForwardingEnabled() {
+ return !(mUpstreamMulticastRoutingConfig.getForwardingMode() == FORWARD_NONE
+ && mDownstreamMulticastRoutingConfig.getForwardingMode() == FORWARD_NONE);
+ }
+
+ private void sendLocalNetworkConfig() {
+ if (mNetworkAgent == null) {
+ return;
+ }
+ final LocalNetworkConfig.Builder configBuilder = new LocalNetworkConfig.Builder();
+ LocalNetworkConfig localNetworkConfig =
+ configBuilder
+ .setUpstreamMulticastRoutingConfig(mUpstreamMulticastRoutingConfig)
+ .setDownstreamMulticastRoutingConfig(mDownstreamMulticastRoutingConfig)
+ .setUpstreamSelector(mUpstreamNetworkRequest)
+ .build();
+ mNetworkAgent.sendLocalNetworkConfig(localNetworkConfig);
+ Log.d(
+ TAG,
+ "Sent localNetworkConfig with upstreamConfig "
+ + mUpstreamMulticastRoutingConfig
+ + " downstreamConfig"
+ + mDownstreamMulticastRoutingConfig);
+ }
+
+ private void handleMulticastForwardingStateChanged(boolean isEnabled) {
+ if (isMulticastForwardingEnabled() == isEnabled) {
+ return;
+ }
+ if (isEnabled) {
+ // When multicast forwarding is enabled, setup upstream forwarding to any address
+ // with minimal scope 4
+ // setup downstream forwarding with addresses subscribed from Thread network
+ mUpstreamMulticastRoutingConfig =
+ new MulticastRoutingConfig.Builder(FORWARD_WITH_MIN_SCOPE, 4).build();
+ mDownstreamMulticastRoutingConfig =
+ new MulticastRoutingConfig.Builder(FORWARD_SELECTED).build();
+ } else {
+ // When multicast forwarding is disabled, set both upstream and downstream
+ // forwarding config to FORWARD_NONE.
+ mUpstreamMulticastRoutingConfig = CONFIG_FORWARD_NONE;
+ mDownstreamMulticastRoutingConfig = CONFIG_FORWARD_NONE;
+ }
+ sendLocalNetworkConfig();
+ Log.d(
+ TAG,
+ "Sent updated localNetworkConfig with multicast forwarding "
+ + (isEnabled ? "enabled" : "disabled"));
+ }
+
+ private void handleMulticastForwardingAddressChanged(byte[] addressBytes, boolean isAdded) {
+ Inet6Address address = bytesToInet6Address(addressBytes);
+ MulticastRoutingConfig newDownstreamConfig;
+ MulticastRoutingConfig.Builder builder;
+
+ if (mDownstreamMulticastRoutingConfig.getForwardingMode() !=
+ MulticastRoutingConfig.FORWARD_SELECTED) {
+ Log.e(
+ TAG,
+ "Ignore multicast listening address updates when downstream multicast "
+ + "forwarding mode is not FORWARD_SELECTED");
+ // Don't update the address set if downstream multicast forwarding is disabled.
+ return;
+ }
+ if (isAdded ==
+ mDownstreamMulticastRoutingConfig.getListeningAddresses().contains(address)) {
+ return;
+ }
+
+ builder = new MulticastRoutingConfig.Builder(FORWARD_SELECTED);
+ for (Inet6Address listeningAddress :
+ mDownstreamMulticastRoutingConfig.getListeningAddresses()) {
+ builder.addListeningAddress(listeningAddress);
+ }
+
+ if (isAdded) {
+ builder.addListeningAddress(address);
+ } else {
+ builder.clearListeningAddress(address);
+ }
+
+ newDownstreamConfig = builder.build();
+ if (!newDownstreamConfig.equals(mDownstreamMulticastRoutingConfig)) {
+ Log.d(
+ TAG,
+ "Multicast listening address "
+ + address.getHostAddress()
+ + " is "
+ + (isAdded ? "added" : "removed"));
+ mDownstreamMulticastRoutingConfig = newDownstreamConfig;
+ sendLocalNetworkConfig();
+ }
+ }
+
private static final class CallbackMetadata {
private static long gId = 0;
@@ -728,6 +970,7 @@
onInterfaceStateChanged(newState.isInterfaceUp);
onDeviceRoleChanged(newState.deviceRole, listenerId);
onPartitionIdChanged(newState.partitionId, listenerId);
+ onMulticastForwardingStateChanged(newState.multicastForwardingEnabled);
mState = newState;
ActiveOperationalDataset newActiveDataset;
@@ -836,9 +1079,19 @@
}
}
+ private void onMulticastForwardingStateChanged(boolean isEnabled) {
+ checkOnHandlerThread();
+ handleMulticastForwardingStateChanged(isEnabled);
+ }
+
@Override
public void onAddressChanged(Ipv6AddressInfo addressInfo, boolean isAdded) {
mHandler.post(() -> handleAddressChanged(addressInfo, isAdded));
}
+
+ @Override
+ public void onMulticastForwardingAddressChanged(byte[] address, boolean isAdded) {
+ mHandler.post(() -> handleMulticastForwardingAddressChanged(address, isAdded));
+ }
}
}
diff --git a/thread/service/proguard.flags b/thread/service/proguard.flags
new file mode 100644
index 0000000..5028982
--- /dev/null
+++ b/thread/service/proguard.flags
@@ -0,0 +1,4 @@
+# Ensure the callback methods are not stripped
+-keepclassmembers class **.ThreadNetworkControllerService$ThreadNetworkCallback {
+ *;
+}