Merge "[Thread] make Thread state customisable via resource overlay" into main
diff --git a/common/flags.aconfig b/common/flags.aconfig
index 8c448e6..19b522c 100644
--- a/common/flags.aconfig
+++ b/common/flags.aconfig
@@ -26,13 +26,6 @@
 }
 
 flag {
-  name: "register_nsd_offload_engine"
-  namespace: "android_core_networking"
-  description: "The flag controls the access for registerOffloadEngine API in NsdManager"
-  bug: "294777050"
-}
-
-flag {
   name: "ipsec_transform_state"
   namespace: "android_core_networking_ipsec"
   description: "The flag controls the access for getIpSecTransformState and IpSecTransformState"
diff --git a/netbpfload/NetBpfLoad.cpp b/netbpfload/NetBpfLoad.cpp
index 2bfaee4..9dc7cdc 100644
--- a/netbpfload/NetBpfLoad.cpp
+++ b/netbpfload/NetBpfLoad.cpp
@@ -259,9 +259,11 @@
     }
 
     if (is_platform) {
+        ALOGI("Executing apex netbpfload...");
         const char * args[] = { apexNetBpfLoad, NULL, };
         execve(args[0], (char**)args, envp);
-        ALOGW("exec '%s' fail: %d[%s]", apexNetBpfLoad, errno, strerror(errno));
+        ALOGE("exec '%s' fail: %d[%s]", apexNetBpfLoad, errno, strerror(errno));
+        return 1;
     }
 
     if (!has_platform_bpfloader_rc && !has_platform_netbpfload_rc) {
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsServiceTypeClient.java b/service-t/src/com/android/server/connectivity/mdns/MdnsServiceTypeClient.java
index 4cb88b4..e222fcf 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsServiceTypeClient.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsServiceTypeClient.java
@@ -140,8 +140,7 @@
                     // before sending the query, it needs to be called just before sending it.
                     final List<MdnsResponse> servicesToResolve = makeResponsesForResolve(socketKey);
                     final QueryTask queryTask = new QueryTask(taskArgs, servicesToResolve,
-                            getAllDiscoverySubtypes(),
-                            servicesToResolve.size() < listeners.size() /* sendDiscoveryQueries */);
+                            getAllDiscoverySubtypes(), needSendDiscoveryQueries(listeners));
                     executor.submit(queryTask);
                     break;
                 }
@@ -388,8 +387,7 @@
             final QueryTask queryTask = new QueryTask(
                     mdnsQueryScheduler.scheduleFirstRun(taskConfig, now,
                             minRemainingTtl, currentSessionId), servicesToResolve,
-                    getAllDiscoverySubtypes(),
-                    servicesToResolve.size() < listeners.size() /* sendDiscoveryQueries */);
+                    getAllDiscoverySubtypes(), needSendDiscoveryQueries(listeners));
             executor.submit(queryTask);
         }
 
@@ -627,6 +625,10 @@
             if (resolveName == null) {
                 continue;
             }
+            if (CollectionUtils.any(resolveResponses,
+                    r -> MdnsUtils.equalsIgnoreDnsCase(resolveName, r.getServiceInstanceName()))) {
+                continue;
+            }
             MdnsResponse knownResponse =
                     serviceCache.getCachedService(resolveName, cacheKey);
             if (knownResponse == null) {
@@ -643,6 +645,17 @@
         return resolveResponses;
     }
 
+    private static boolean needSendDiscoveryQueries(
+            @NonNull ArrayMap<MdnsServiceBrowserListener, ListenerInfo> listeners) {
+        // Note iterators are discouraged on ArrayMap as per its documentation
+        for (int i = 0; i < listeners.size(); i++) {
+            if (listeners.valueAt(i).searchOptions.getResolveInstanceName() == null) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     private void tryRemoveServiceAfterTtlExpires() {
         if (!shouldRemoveServiceAfterTtlExpires()) return;
 
diff --git a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
index f0edee2..cdf8340 100644
--- a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
+++ b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
@@ -802,7 +802,9 @@
             assertNull(redactedNormal.getUids());
             assertNull(redactedNormal.getSsid());
             assertNull(redactedNormal.getUnderlyingNetworks());
-            assertEquals(0, redactedNormal.getSubscriptionIds().size());
+            // TODO: Make subIds public and update to verify the size is 2
+            final int subIdsSize = redactedNormal.getSubscriptionIds().size();
+            assertTrue(subIdsSize == 0 || subIdsSize == 2);
             assertEquals(WifiInfo.DEFAULT_MAC_ADDRESS,
                     ((WifiInfo) redactedNormal.getTransportInfo()).getBSSID());
             assertEquals(rssi, ((WifiInfo) redactedNormal.getTransportInfo()).getRssi());
diff --git a/tests/unit/java/com/android/metrics/ConnectivitySampleMetricsTest.kt b/tests/unit/java/com/android/metrics/ConnectivitySampleMetricsTest.kt
index 3043d50..53baee1 100644
--- a/tests/unit/java/com/android/metrics/ConnectivitySampleMetricsTest.kt
+++ b/tests/unit/java/com/android/metrics/ConnectivitySampleMetricsTest.kt
@@ -16,6 +16,7 @@
 import android.net.NetworkCapabilities.TRANSPORT_CELLULAR
 import android.net.NetworkCapabilities.TRANSPORT_WIFI
 import android.net.NetworkScore
+import android.net.NetworkScore.KEEP_CONNECTED_FOR_TEST
 import android.net.NetworkScore.POLICY_EXITING
 import android.net.NetworkScore.POLICY_TRANSPORT_PRIMARY
 import android.os.Build
@@ -86,7 +87,10 @@
                 .addCapability(NET_CAPABILITY_NOT_SUSPENDED)
                 .addCapability(NET_CAPABILITY_NOT_ROAMING)
                 .build()
-        val wifi1Score = NetworkScore.Builder().setExiting(true).build()
+        val wifi1Score = NetworkScore.Builder()
+                .setKeepConnectedReason(KEEP_CONNECTED_FOR_TEST)
+                .setExiting(true)
+                .build()
         val agentWifi1 = Agent(nc = wifi1Caps, score = FromS(wifi1Score)).also { it.connect() }
 
         val wifi2Caps = NetworkCapabilities.Builder()
@@ -96,7 +100,10 @@
                 .addCapability(NET_CAPABILITY_NOT_ROAMING)
                 .addEnterpriseId(NET_ENTERPRISE_ID_3)
                 .build()
-        val wifi2Score = NetworkScore.Builder().setTransportPrimary(true).build()
+        val wifi2Score = NetworkScore.Builder()
+                .setKeepConnectedReason(KEEP_CONNECTED_FOR_TEST)
+                .setTransportPrimary(true)
+                .build()
         val agentWifi2 = Agent(nc = wifi2Caps, score = FromS(wifi2Score)).also { it.connect() }
 
         val cellCaps = NetworkCapabilities.Builder()
@@ -107,7 +114,9 @@
                 .addCapability(NET_CAPABILITY_NOT_ROAMING)
                 .addEnterpriseId(NET_ENTERPRISE_ID_1)
                 .build()
-        val cellScore = NetworkScore.Builder().build()
+        val cellScore = NetworkScore.Builder()
+                .setKeepConnectedReason(KEEP_CONNECTED_FOR_TEST)
+                .build()
         val agentCell = Agent(nc = cellCaps, score = FromS(cellScore)).also { it.connect() }
 
         val stats = csHandler.onHandler { service.sampleConnectivityState() }
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceTypeClientTests.java b/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceTypeClientTests.java
index 58124f3..09236b1 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceTypeClientTests.java
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceTypeClientTests.java
@@ -43,6 +43,7 @@
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.when;
 
 import static java.nio.charset.StandardCharsets.UTF_8;
@@ -1207,10 +1208,14 @@
         final String ipV4Address = "192.0.2.0";
         final String ipV6Address = "2001:db8::";
 
-        final MdnsSearchOptions resolveOptions = MdnsSearchOptions.newBuilder()
+        final MdnsSearchOptions resolveOptions1 = MdnsSearchOptions.newBuilder()
+                .setResolveInstanceName(instanceName).build();
+        final MdnsSearchOptions resolveOptions2 = MdnsSearchOptions.newBuilder()
                 .setResolveInstanceName(instanceName).build();
 
-        startSendAndReceive(mockListenerOne, resolveOptions);
+        startSendAndReceive(mockListenerOne, resolveOptions1);
+        startSendAndReceive(mockListenerTwo, resolveOptions2);
+        // No need to verify order for both listeners; and order is not guaranteed between them
         InOrder inOrder = inOrder(mockListenerOne, mockSocketClient);
 
         // Verify a query for SRV/TXT was sent, but no PTR query
@@ -1223,13 +1228,19 @@
                 eq(socketKey), eq(false));
         verify(mockDeps, times(1)).sendMessage(any(), any(Message.class));
         assertNotNull(delayMessage);
+        inOrder.verify(mockListenerOne).onDiscoveryQuerySent(any(), anyInt());
+        verify(mockListenerTwo).onDiscoveryQuerySent(any(), anyInt());
 
         final MdnsPacket srvTxtQueryPacket = MdnsPacket.parse(
                 new MdnsPacketReader(srvTxtQueryCaptor.getValue()));
 
         final String[] serviceName = getTestServiceName(instanceName);
+        assertEquals(1, srvTxtQueryPacket.questions.size());
         assertFalse(hasQuestion(srvTxtQueryPacket, MdnsRecord.TYPE_PTR));
         assertTrue(hasQuestion(srvTxtQueryPacket, MdnsRecord.TYPE_ANY, serviceName));
+        assertEquals(0, srvTxtQueryPacket.answers.size());
+        assertEquals(0, srvTxtQueryPacket.authorityRecords.size());
+        assertEquals(0, srvTxtQueryPacket.additionalRecords.size());
 
         // Process a response with SRV+TXT
         final MdnsPacket srvTxtResponse = new MdnsPacket(
@@ -1246,6 +1257,10 @@
                 Collections.emptyList() /* additionalRecords */);
 
         processResponse(srvTxtResponse, socketKey);
+        inOrder.verify(mockListenerOne).onServiceNameDiscovered(
+                matchServiceName(instanceName), eq(false) /* isServiceFromCache */);
+        verify(mockListenerTwo).onServiceNameDiscovered(
+                matchServiceName(instanceName), eq(false) /* isServiceFromCache */);
 
         // Expect a query for A/AAAA
         dispatchMessage();
@@ -1255,11 +1270,18 @@
         inOrder.verify(mockSocketClient, times(2)).sendPacketRequestingMulticastResponse(
                 addressQueryCaptor.capture(),
                 eq(socketKey), eq(false));
+        inOrder.verify(mockListenerOne).onDiscoveryQuerySent(any(), anyInt());
+        // onDiscoveryQuerySent was called 2 times in total
+        verify(mockListenerTwo, times(2)).onDiscoveryQuerySent(any(), anyInt());
 
         final MdnsPacket addressQueryPacket = MdnsPacket.parse(
                 new MdnsPacketReader(addressQueryCaptor.getValue()));
+        assertEquals(2, addressQueryPacket.questions.size());
         assertTrue(hasQuestion(addressQueryPacket, MdnsRecord.TYPE_A, hostname));
         assertTrue(hasQuestion(addressQueryPacket, MdnsRecord.TYPE_AAAA, hostname));
+        assertEquals(0, addressQueryPacket.answers.size());
+        assertEquals(0, addressQueryPacket.authorityRecords.size());
+        assertEquals(0, addressQueryPacket.additionalRecords.size());
 
         // Process a response with address records
         final MdnsPacket addressResponse = new MdnsPacket(
@@ -1276,10 +1298,12 @@
                 Collections.emptyList() /* additionalRecords */);
 
         inOrder.verify(mockListenerOne, never()).onServiceNameDiscovered(any(), anyBoolean());
+        verifyNoMoreInteractions(mockListenerTwo);
         processResponse(addressResponse, socketKey);
 
         inOrder.verify(mockListenerOne).onServiceFound(
                 serviceInfoCaptor.capture(), eq(false) /* isServiceFromCache */);
+        verify(mockListenerTwo).onServiceFound(any(), anyBoolean());
         verifyServiceInfo(serviceInfoCaptor.getValue(),
                 instanceName,
                 SERVICE_TYPE_LABELS,
diff --git a/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java b/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java
index 56dd056..0623b87 100644
--- a/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java
+++ b/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java
@@ -16,7 +16,6 @@
 
 import static android.Manifest.permission.NETWORK_SETTINGS;
 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;
@@ -70,6 +69,7 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.net.ConnectivityManager;
+import android.net.InetAddresses;
 import android.net.LinkAddress;
 import android.net.LinkProperties;
 import android.net.LocalNetworkConfig;
@@ -108,6 +108,7 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.ServiceManagerWrapper;
+import com.android.server.thread.openthread.BackboneRouterState;
 import com.android.server.thread.openthread.BorderRouterConfigurationParcel;
 import com.android.server.thread.openthread.IChannelMasksReceiver;
 import com.android.server.thread.openthread.IOtDaemon;
@@ -123,6 +124,7 @@
 import java.security.SecureRandom;
 import java.time.Instant;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 import java.util.Objects;
 import java.util.Random;
@@ -1001,11 +1003,6 @@
         }
     }
 
-    private boolean isMulticastForwardingEnabled() {
-        return !(mUpstreamMulticastRoutingConfig.getForwardingMode() == FORWARD_NONE
-                && mDownstreamMulticastRoutingConfig.getForwardingMode() == FORWARD_NONE);
-    }
-
     private void sendLocalNetworkConfig() {
         if (mNetworkAgent == null) {
             return;
@@ -1015,72 +1012,44 @@
         Log.d(TAG, "Sent localNetworkConfig: " + localNetworkConfig);
     }
 
-    private void handleMulticastForwardingStateChanged(boolean isEnabled) {
-        if (isMulticastForwardingEnabled() == isEnabled) {
-            return;
-        }
+    private void handleMulticastForwardingChanged(BackboneRouterState state) {
+        MulticastRoutingConfig upstreamMulticastRoutingConfig;
+        MulticastRoutingConfig downstreamMulticastRoutingConfig;
 
-        Log.i(TAG, "Multicast forwaring is " + (isEnabled ? "enabled" : "disabled"));
-
-        if (isEnabled) {
+        if (state.multicastForwardingEnabled) {
             // 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 =
+            upstreamMulticastRoutingConfig =
                     new MulticastRoutingConfig.Builder(FORWARD_WITH_MIN_SCOPE, 4).build();
-            mDownstreamMulticastRoutingConfig =
-                    new MulticastRoutingConfig.Builder(FORWARD_SELECTED).build();
+            downstreamMulticastRoutingConfig =
+                    buildDownstreamMulticastRoutingConfigSelected(state.listeningAddresses);
         } else {
             // When multicast forwarding is disabled, set both upstream and downstream
             // forwarding config to FORWARD_NONE.
-            mUpstreamMulticastRoutingConfig = CONFIG_FORWARD_NONE;
-            mDownstreamMulticastRoutingConfig = CONFIG_FORWARD_NONE;
+            upstreamMulticastRoutingConfig = CONFIG_FORWARD_NONE;
+            downstreamMulticastRoutingConfig = CONFIG_FORWARD_NONE;
         }
+
+        if (upstreamMulticastRoutingConfig.equals(mUpstreamMulticastRoutingConfig)
+                && downstreamMulticastRoutingConfig.equals(mDownstreamMulticastRoutingConfig)) {
+            return;
+        }
+
+        mUpstreamMulticastRoutingConfig = upstreamMulticastRoutingConfig;
+        mDownstreamMulticastRoutingConfig = downstreamMulticastRoutingConfig;
         sendLocalNetworkConfig();
     }
 
-    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) {
+    private MulticastRoutingConfig buildDownstreamMulticastRoutingConfigSelected(
+            List<String> listeningAddresses) {
+        MulticastRoutingConfig.Builder builder =
+                new MulticastRoutingConfig.Builder(FORWARD_SELECTED);
+        for (String addressStr : listeningAddresses) {
+            Inet6Address address = (Inet6Address) InetAddresses.parseNumericAddress(addressStr);
             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();
-        }
+        return builder.build();
     }
 
     private static final class CallbackMetadata {
@@ -1248,7 +1217,6 @@
             onInterfaceStateChanged(newState.isInterfaceUp);
             onDeviceRoleChanged(newState.deviceRole, listenerId);
             onPartitionIdChanged(newState.partitionId, listenerId);
-            onMulticastForwardingStateChanged(newState.multicastForwardingEnabled);
             mState = newState;
 
             ActiveOperationalDataset newActiveDataset;
@@ -1357,19 +1325,14 @@
             }
         }
 
-        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));
+        public void onBackboneRouterStateChanged(BackboneRouterState state) {
+            mHandler.post(() -> handleMulticastForwardingChanged(state));
         }
     }
 }
diff --git a/thread/tests/cts/Android.bp b/thread/tests/cts/Android.bp
index 522120c..676eb0e 100644
--- a/thread/tests/cts/Android.bp
+++ b/thread/tests/cts/Android.bp
@@ -32,6 +32,7 @@
     test_suites: [
         "cts",
         "general-tests",
+        "mcts-tethering",
         "mts-tethering",
     ],
     static_libs: [
diff --git a/thread/tests/integration/src/android/net/thread/BorderRoutingTest.java b/thread/tests/integration/src/android/net/thread/BorderRoutingTest.java
index 7554610..7aaae86 100644
--- a/thread/tests/integration/src/android/net/thread/BorderRoutingTest.java
+++ b/thread/tests/integration/src/android/net/thread/BorderRoutingTest.java
@@ -296,6 +296,16 @@
     }
 
     @Test
+    public void multicastRouting_inboundForwarding_afterBrRejoinFtdRepliesSubscribedAddress()
+            throws Exception {
+        assumeTrue(isKernelVersionAtLeast(KERNEL_VERSION_MULTICAST_ROUTING_SUPPORTED));
+
+        // TODO (b/327311034): Testing bbr state switch from primary mode to secondary mode and back
+        // to primary mode requires an additional BR in the Thread network. This is not currently
+        // supported, to be implemented when possible.
+    }
+
+    @Test
     public void multicastRouting_ftdSubscribedScope3MulticastAddress_cannotPingfromInfraLink()
             throws Exception {
         assumeTrue(isKernelVersionAtLeast(KERNEL_VERSION_MULTICAST_ROUTING_SUPPORTED));