Merge "Add yuyanghuang@ to OWNERS_core_networking_xts for APF firmware tests" into main
diff --git a/staticlibs/device/com/android/net/module/util/netlink/RtNetlinkLinkMessage.java b/staticlibs/device/com/android/net/module/util/netlink/RtNetlinkLinkMessage.java
index c19a124..5d49fa3 100644
--- a/staticlibs/device/com/android/net/module/util/netlink/RtNetlinkLinkMessage.java
+++ b/staticlibs/device/com/android/net/module/util/netlink/RtNetlinkLinkMessage.java
@@ -22,7 +22,6 @@
 import static com.android.net.module.util.netlink.NetlinkConstants.IFF_UP;
 import static com.android.net.module.util.netlink.NetlinkConstants.RTM_GETLINK;
 import static com.android.net.module.util.netlink.NetlinkConstants.RTM_NEWLINK;
-import static com.android.net.module.util.netlink.StructNlMsgHdr.NLM_F_REQUEST;
 import static com.android.net.module.util.netlink.StructNlMsgHdr.NLM_F_REQUEST_ACK;
 
 import android.net.MacAddress;
@@ -307,7 +306,7 @@
         }
 
         return RtNetlinkLinkMessage.build(
-                new StructNlMsgHdr(0, RTM_GETLINK, NLM_F_REQUEST, sequenceNumber),
+                new StructNlMsgHdr(0, RTM_GETLINK, NLM_F_REQUEST_ACK, sequenceNumber),
                 new StructIfinfoMsg((short) AF_UNSPEC, (short) 0, interfaceIndex, 0, 0),
                 DEFAULT_MTU, null, null);
     }
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/netlink/RtNetlinkLinkMessageTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/RtNetlinkLinkMessageTest.java
index 13710b1..08cab03 100644
--- a/staticlibs/tests/unit/src/com/android/net/module/util/netlink/RtNetlinkLinkMessageTest.java
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/RtNetlinkLinkMessageTest.java
@@ -290,7 +290,7 @@
     @Test
     public void testCreateGetLinkMessage() {
         final String expectedHexBytes =
-                "20000000120001006824000000000000"    // struct nlmsghdr
+                "20000000120005006824000000000000"    // struct nlmsghdr
                 + "00000000080000000000000000000000"; // struct ifinfomsg
         final String interfaceName = "wlan0";
         final int interfaceIndex = 8;
diff --git a/thread/service/java/com/android/server/thread/TunInterfaceController.java b/thread/service/java/com/android/server/thread/TunInterfaceController.java
index 520a434..2f2a5d1 100644
--- a/thread/service/java/com/android/server/thread/TunInterfaceController.java
+++ b/thread/service/java/com/android/server/thread/TunInterfaceController.java
@@ -326,12 +326,13 @@
     private static LinkAddress newLinkAddress(
             Ipv6AddressInfo addressInfo, boolean hasActiveOmrAddress) {
         // Mesh-local addresses and OMR address have the same scope, to distinguish them we set
-        // mesh-local addresses as deprecated when there is an active OMR address.
+        // mesh-local addresses as deprecated when there is an active OMR address. If OMR address
+        // is missing, only ML-EID in mesh-local addresses will be set preferred.
         // For OMR address and link-local address we only use the value isPreferred set by
         // ot-daemon.
         boolean isPreferred = addressInfo.isPreferred;
-        if (addressInfo.isMeshLocal && hasActiveOmrAddress) {
-            isPreferred = false;
+        if (addressInfo.isMeshLocal) {
+            isPreferred = (!hasActiveOmrAddress && addressInfo.isMeshLocalEid);
         }
 
         final long deprecationTimeMillis =
diff --git a/thread/tests/cts/src/android/net/thread/cts/ThreadNetworkManagerTest.java b/thread/tests/cts/src/android/net/thread/cts/ThreadNetworkManagerTest.java
index 5ba76b8..5be8f49 100644
--- a/thread/tests/cts/src/android/net/thread/cts/ThreadNetworkManagerTest.java
+++ b/thread/tests/cts/src/android/net/thread/cts/ThreadNetworkManagerTest.java
@@ -19,12 +19,10 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.junit.Assume.assumeFalse;
-import static org.junit.Assume.assumeNotNull;
 import static org.junit.Assume.assumeTrue;
 
 import android.content.Context;
 import android.content.pm.PackageManager;
-import android.net.thread.ThreadNetworkController;
 import android.net.thread.ThreadNetworkManager;
 import android.os.Build;
 
@@ -41,8 +39,6 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import java.util.List;
-
 /** Tests for {@link ThreadNetworkManager}. */
 @SmallTest
 @RunWith(AndroidJUnit4.class)
diff --git a/thread/tests/integration/src/android/net/thread/ThreadIntegrationTest.java b/thread/tests/integration/src/android/net/thread/ThreadIntegrationTest.java
index 195f6d2..5b07e0a 100644
--- a/thread/tests/integration/src/android/net/thread/ThreadIntegrationTest.java
+++ b/thread/tests/integration/src/android/net/thread/ThreadIntegrationTest.java
@@ -22,12 +22,14 @@
 import static android.net.thread.ThreadNetworkController.DEVICE_ROLE_STOPPED;
 import static android.net.thread.utils.IntegrationTestUtils.CALLBACK_TIMEOUT;
 import static android.net.thread.utils.IntegrationTestUtils.RESTART_JOIN_TIMEOUT;
+import static android.net.thread.utils.IntegrationTestUtils.getIpv6Addresses;
 import static android.net.thread.utils.IntegrationTestUtils.getIpv6LinkAddresses;
 import static android.net.thread.utils.IntegrationTestUtils.getPrefixesFromNetData;
 import static android.net.thread.utils.IntegrationTestUtils.getThreadNetwork;
 import static android.net.thread.utils.IntegrationTestUtils.isInMulticastGroup;
 import static android.net.thread.utils.IntegrationTestUtils.waitFor;
 import static android.net.thread.utils.ThreadNetworkControllerWrapper.JOIN_TIMEOUT;
+import static android.os.SystemClock.elapsedRealtime;
 
 import static com.android.compatibility.common.util.SystemUtil.runShellCommand;
 import static com.android.compatibility.common.util.SystemUtil.runShellCommandOrThrow;
@@ -38,6 +40,7 @@
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.Truth.assertWithMessage;
 
+import static org.junit.Assume.assumeFalse;
 import static org.junit.Assume.assumeTrue;
 
 import static java.util.concurrent.TimeUnit.SECONDS;
@@ -46,23 +49,26 @@
 import android.net.ConnectivityManager;
 import android.net.InetAddresses;
 import android.net.IpPrefix;
-import android.net.LinkAddress;
 import android.net.LinkProperties;
 import android.net.Network;
 import android.net.NetworkCapabilities;
 import android.net.NetworkRequest;
 import android.net.thread.utils.FullThreadDevice;
 import android.net.thread.utils.OtDaemonController;
+import android.net.thread.utils.TapTestNetworkTracker;
 import android.net.thread.utils.ThreadFeatureCheckerRule;
 import android.net.thread.utils.ThreadFeatureCheckerRule.RequiresSimulationThreadDevice;
 import android.net.thread.utils.ThreadFeatureCheckerRule.RequiresThreadFeature;
 import android.net.thread.utils.ThreadNetworkControllerWrapper;
 import android.net.thread.utils.ThreadStateListener;
+import android.os.HandlerThread;
 import android.os.SystemClock;
 
 import androidx.test.core.app.ApplicationProvider;
 import androidx.test.filters.LargeTest;
 
+import com.google.common.collect.FluentIterable;
+
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Rule;
@@ -102,6 +108,12 @@
 
     private static final Duration NETWORK_CALLBACK_TIMEOUT = Duration.ofSeconds(10);
 
+    // The duration between attached and OMR address shows up on thread-wpan
+    private static final Duration OMR_LINK_ADDR_TIMEOUT = Duration.ofSeconds(30);
+
+    // The duration between attached and addresses show up on thread-wpan
+    private static final Duration LINK_ADDR_TIMEOUT = Duration.ofSeconds(2);
+
     // A valid Thread Active Operational Dataset generated from OpenThread CLI "dataset init new".
     private static final byte[] DEFAULT_DATASET_TLVS =
             base16().decode(
@@ -128,6 +140,8 @@
             ThreadNetworkControllerWrapper.newInstance(mContext);
     private OtDaemonController mOtCtl;
     private FullThreadDevice mFtd;
+    private HandlerThread mHandlerThread;
+    private TapTestNetworkTracker mTestNetworkTracker;
 
     public final boolean mIsBorderRouterEnabled;
     private final ThreadConfiguration mConfig;
@@ -152,6 +166,14 @@
         mController.setEnabledAndWait(true);
         mController.setConfigurationAndWait(mConfig);
         mController.leaveAndWait();
+
+        mHandlerThread = new HandlerThread("ThreadIntegrationTest");
+        mHandlerThread.start();
+
+        mTestNetworkTracker = new TapTestNetworkTracker(mContext, mHandlerThread.getLooper());
+        assertThat(mTestNetworkTracker).isNotNull();
+        mController.setTestNetworkAsUpstreamAndWait(mTestNetworkTracker.getInterfaceName());
+
         mFtd = new FullThreadDevice(10 /* nodeId */);
     }
 
@@ -159,6 +181,13 @@
     public void tearDown() throws Exception {
         ThreadStateListener.stopAllListeners();
 
+        if (mTestNetworkTracker != null) {
+            mTestNetworkTracker.tearDown();
+        }
+        if (mHandlerThread != null) {
+            mHandlerThread.quitSafely();
+            mHandlerThread.join();
+        }
         mController.setTestNetworkAsUpstreamAndWait(null);
         mController.leaveAndWait();
 
@@ -265,23 +294,51 @@
     }
 
     @Test
-    public void joinNetworkWithBrDisabled_meshLocalAddressesArePreferred() throws Exception {
-        // When BR feature is disabled, there is no OMR address, so the mesh-local addresses are
-        // expected to be preferred.
-        mOtCtl.executeCommand("br disable");
+    public void joinNetwork_borderRouterEnabled_allMlAddrAreNotPreferredAndOmrIsPreferred()
+            throws Exception {
+        assumeTrue(mConfig.isBorderRouterEnabled());
+
+        mController.setTestNetworkAsUpstreamAndWait(mTestNetworkTracker.getInterfaceName());
         mController.joinAndWait(DEFAULT_DATASET);
+        waitFor(
+                () -> getIpv6Addresses("thread-wpan").contains(mOtCtl.getOmrAddress()),
+                OMR_LINK_ADDR_TIMEOUT);
 
         IpPrefix meshLocalPrefix = DEFAULT_DATASET.getMeshLocalPrefix();
-        List<LinkAddress> linkAddresses = getIpv6LinkAddresses("thread-wpan");
-        for (LinkAddress address : linkAddresses) {
-            if (meshLocalPrefix.contains(address.getAddress())) {
-                assertThat(address.getDeprecationTime())
-                        .isGreaterThan(SystemClock.elapsedRealtime());
-                assertThat(address.isPreferred()).isTrue();
-            }
-        }
+        var linkAddrs = FluentIterable.from(getIpv6LinkAddresses("thread-wpan"));
+        var meshLocalAddrs = linkAddrs.filter(addr -> meshLocalPrefix.contains(addr.getAddress()));
+        assertThat(meshLocalAddrs).isNotEmpty();
+        assertThat(meshLocalAddrs.allMatch(addr -> !addr.isPreferred())).isTrue();
+        assertThat(meshLocalAddrs.allMatch(addr -> addr.getDeprecationTime() <= elapsedRealtime()))
+                .isTrue();
+        var omrAddrs = linkAddrs.filter(addr -> addr.getAddress().equals(mOtCtl.getOmrAddress()));
+        assertThat(omrAddrs).hasSize(1);
+        assertThat(omrAddrs.get(0).isPreferred()).isTrue();
+        assertThat(omrAddrs.get(0).getDeprecationTime() > elapsedRealtime()).isTrue();
+    }
 
-        mOtCtl.executeCommand("br enable");
+    @Test
+    public void joinNetwork_borderRouterDisabled_onlyMlEidIsPreferred() throws Exception {
+        assumeFalse(mConfig.isBorderRouterEnabled());
+
+        mController.joinAndWait(DEFAULT_DATASET);
+        waitFor(
+                () -> getIpv6Addresses("thread-wpan").contains(mOtCtl.getMlEid()),
+                LINK_ADDR_TIMEOUT);
+
+        IpPrefix meshLocalPrefix = DEFAULT_DATASET.getMeshLocalPrefix();
+        var linkAddrs = FluentIterable.from(getIpv6LinkAddresses("thread-wpan"));
+        var meshLocalAddrs = linkAddrs.filter(addr -> meshLocalPrefix.contains(addr.getAddress()));
+        var mlEidAddrs = meshLocalAddrs.filter(addr -> addr.getAddress().equals(mOtCtl.getMlEid()));
+        var nonMlEidAddrs = meshLocalAddrs.filter(addr -> !mlEidAddrs.contains(addr));
+        assertThat(mlEidAddrs).hasSize(1);
+        assertThat(mlEidAddrs.allMatch(addr -> addr.isPreferred())).isTrue();
+        assertThat(mlEidAddrs.allMatch(addr -> addr.getDeprecationTime() > elapsedRealtime()))
+                .isTrue();
+        assertThat(nonMlEidAddrs).isNotEmpty();
+        assertThat(nonMlEidAddrs.allMatch(addr -> !addr.isPreferred())).isTrue();
+        assertThat(nonMlEidAddrs.allMatch(addr -> addr.getDeprecationTime() <= elapsedRealtime()))
+                .isTrue();
     }
 
     @Test
diff --git a/thread/tests/integration/src/android/net/thread/utils/IntegrationTestUtils.kt b/thread/tests/integration/src/android/net/thread/utils/IntegrationTestUtils.kt
index f41e903..f7b4d19 100644
--- a/thread/tests/integration/src/android/net/thread/utils/IntegrationTestUtils.kt
+++ b/thread/tests/integration/src/android/net/thread/utils/IntegrationTestUtils.kt
@@ -479,6 +479,12 @@
         return addresses
     }
 
+    /** Returns the list of [InetAddress] of the given network. */
+    @JvmStatic
+    fun getIpv6Addresses(interfaceName: String): List<InetAddress> {
+        return getIpv6LinkAddresses(interfaceName).map { it.address }
+    }
+
     /** Return the first discovered service of `serviceType`. */
     @JvmStatic
     @Throws(Exception::class)
diff --git a/thread/tests/integration/src/android/net/thread/utils/OtDaemonController.java b/thread/tests/integration/src/android/net/thread/utils/OtDaemonController.java
index 272685f..d35b94e 100644
--- a/thread/tests/integration/src/android/net/thread/utils/OtDaemonController.java
+++ b/thread/tests/integration/src/android/net/thread/utils/OtDaemonController.java
@@ -24,9 +24,11 @@
 import com.android.compatibility.common.util.SystemUtil;
 
 import java.net.Inet6Address;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
+import java.util.stream.Collectors;
 
 /**
  * Wrapper of the "/system/bin/ot-ctl" which can be used to send CLI commands to ot-daemon to
@@ -72,6 +74,25 @@
                 .toList();
     }
 
+    /** Returns the OMR address of this device or {@code null} if it doesn't exist. */
+    @Nullable
+    public Inet6Address getOmrAddress() {
+        List<Inet6Address> allAddresses = new ArrayList<>(getAddresses());
+        allAddresses.removeAll(getMeshLocalAddresses());
+
+        List<Inet6Address> omrAddresses =
+                allAddresses.stream()
+                        .filter(addr -> !addr.isLinkLocalAddress())
+                        .collect(Collectors.toList());
+        if (omrAddresses.isEmpty()) {
+            return null;
+        } else if (omrAddresses.size() > 1) {
+            throw new IllegalStateException();
+        }
+
+        return omrAddresses.getFirst();
+    }
+
     /** Returns {@code true} if the Thread interface is up. */
     public boolean isInterfaceUp() {
         String output = executeCommand("ifconfig");