Merge "introduce BPFLOADER_MAINLINE_U_QPR3_VERSION" into main
diff --git a/framework/src/android/net/apf/ApfCapabilities.java b/framework/src/android/net/apf/ApfCapabilities.java
index 6b18629..f92cdbb 100644
--- a/framework/src/android/net/apf/ApfCapabilities.java
+++ b/framework/src/android/net/apf/ApfCapabilities.java
@@ -106,6 +106,8 @@
 
     @Override
     public int hashCode() {
+        // hashCode it is not implemented in R. Therefore it would be dangerous for
+        // NetworkStack to depend on it.
         return Objects.hash(apfVersionSupported, maximumApfProgramSize, apfPacketFormat);
     }
 
diff --git a/service-t/src/com/android/server/NsdService.java b/service-t/src/com/android/server/NsdService.java
index f8b0d53..64624ae 100644
--- a/service-t/src/com/android/server/NsdService.java
+++ b/service-t/src/com/android/server/NsdService.java
@@ -1923,13 +1923,13 @@
                         mContext, MdnsFeatureFlags.NSD_FORCE_DISABLE_MDNS_OFFLOAD))
                 .setIncludeInetAddressRecordsInProbing(mDeps.isFeatureEnabled(
                         mContext, MdnsFeatureFlags.INCLUDE_INET_ADDRESS_RECORDS_IN_PROBING))
-                .setIsExpiredServicesRemovalEnabled(mDeps.isFeatureEnabled(
+                .setIsExpiredServicesRemovalEnabled(mDeps.isTetheringFeatureNotChickenedOut(
                         mContext, MdnsFeatureFlags.NSD_EXPIRED_SERVICES_REMOVAL))
                 .setIsLabelCountLimitEnabled(mDeps.isTetheringFeatureNotChickenedOut(
                         mContext, MdnsFeatureFlags.NSD_LIMIT_LABEL_COUNT))
-                .setIsKnownAnswerSuppressionEnabled(mDeps.isFeatureEnabled(
+                .setIsKnownAnswerSuppressionEnabled(mDeps.isTetheringFeatureNotChickenedOut(
                         mContext, MdnsFeatureFlags.NSD_KNOWN_ANSWER_SUPPRESSION))
-                .setIsUnicastReplyEnabled(mDeps.isFeatureEnabled(
+                .setIsUnicastReplyEnabled(mDeps.isTetheringFeatureNotChickenedOut(
                         mContext, MdnsFeatureFlags.NSD_UNICAST_REPLY_ENABLED))
                 .setIsAggressiveQueryModeEnabled(mDeps.isFeatureEnabled(
                         mContext, MdnsFeatureFlags.NSD_AGGRESSIVE_QUERY_MODE))
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsFeatureFlags.java b/service-t/src/com/android/server/connectivity/mdns/MdnsFeatureFlags.java
index f4a08ba..c264f25 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsFeatureFlags.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsFeatureFlags.java
@@ -189,10 +189,10 @@
         public Builder() {
             mIsMdnsOffloadFeatureEnabled = false;
             mIncludeInetAddressRecordsInProbing = false;
-            mIsExpiredServicesRemovalEnabled = false;
+            mIsExpiredServicesRemovalEnabled = true; // Default enabled.
             mIsLabelCountLimitEnabled = true; // Default enabled.
-            mIsKnownAnswerSuppressionEnabled = false;
-            mIsUnicastReplyEnabled = true;
+            mIsKnownAnswerSuppressionEnabled = true; // Default enabled.
+            mIsUnicastReplyEnabled = true; // Default enabled.
             mIsAggressiveQueryModeEnabled = false;
             mIsQueryWithKnownAnswerEnabled = false;
             mOverrideProvider = null;
diff --git a/service/src/com/android/server/connectivity/NetworkNotificationManager.java b/service/src/com/android/server/connectivity/NetworkNotificationManager.java
index 7707122..fd41ee6 100644
--- a/service/src/com/android/server/connectivity/NetworkNotificationManager.java
+++ b/service/src/com/android/server/connectivity/NetworkNotificationManager.java
@@ -170,9 +170,11 @@
                     && !TextUtils.isEmpty(nai.linkProperties.getCaptivePortalData()
                     .getVenueFriendlyName())) {
                 name = nai.linkProperties.getCaptivePortalData().getVenueFriendlyName();
+            } else if (!TextUtils.isEmpty(extraInfo)) {
+                name = extraInfo;
             } else {
-                name = TextUtils.isEmpty(extraInfo)
-                        ? WifiInfo.sanitizeSsid(nai.networkCapabilities.getSsid()) : extraInfo;
+                final String ssid = WifiInfo.sanitizeSsid(nai.networkCapabilities.getSsid());
+                name = ssid == null ? "" : ssid;
             }
             // Only notify for Internet-capable networks.
             if (!nai.networkCapabilities.hasCapability(NET_CAPABILITY_INTERNET)) return;
diff --git a/tests/cts/net/AndroidTestTemplate.xml b/tests/cts/net/AndroidTestTemplate.xml
index 38f26d8..077c3ef 100644
--- a/tests/cts/net/AndroidTestTemplate.xml
+++ b/tests/cts/net/AndroidTestTemplate.xml
@@ -24,6 +24,11 @@
     <option name="config-descriptor:metadata" key="mainline-param" value="CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk" />
     <option name="config-descriptor:metadata" key="mainline-param" value="com.google.android.tethering.apex" />
     <option name="not-shardable" value="true" />
+    <target_preparer class="com.android.compatibility.common.tradefed.targetprep.DynamicConfigPusher">
+        <option name="target" value="device" />
+        <option name="config-filename" value="{MODULE}" />
+        <option name="version" value="1.0" />
+    </target_preparer>
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
         <option name="test-file-name" value="{MODULE}.apk" />
@@ -38,6 +43,7 @@
         <option name="runtime-hint" value="9m4s" />
         <option name="hidden-api-checks" value="false" />
         <option name="isolated-storage" value="false" />
+        <option name="instrumentation-arg" key="test-module-name" value="{MODULE}" />
         <!-- Test filter that allows test APKs to select which tests they want to run by annotating
              those tests with an annotation matching the name of the APK.
 
diff --git a/tests/cts/net/DynamicConfig.xml b/tests/cts/net/DynamicConfig.xml
new file mode 100644
index 0000000..af019c2
--- /dev/null
+++ b/tests/cts/net/DynamicConfig.xml
@@ -0,0 +1,24 @@
+<!--
+  ~ Copyright (C) 2024 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.
+  -->
+
+<dynamicConfig>
+    <entry key="remote_config_required">
+        <value>false</value>
+    </entry>
+    <entry key="IP_ADDRESS_ECHO_URL">
+        <value>https://google-ipv6test.appspot.com/ip.js?fmt=text</value>
+    </entry>
+</dynamicConfig>
diff --git a/tests/cts/net/src/android/net/cts/ApfIntegrationTest.kt b/tests/cts/net/src/android/net/cts/ApfIntegrationTest.kt
index 0b6637d..086f0e7 100644
--- a/tests/cts/net/src/android/net/cts/ApfIntegrationTest.kt
+++ b/tests/cts/net/src/android/net/cts/ApfIntegrationTest.kt
@@ -361,7 +361,8 @@
     @SkipPresubmit(reason = "This test takes longer than 1 minute, do not run it on presubmit.")
     // APF integration is mostly broken before V, only run the full read / write test on V+.
     @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
-    @Test
+    // Increase timeout for test to 15 minutes to accommodate device with large APF RAM.
+    @Test(timeout = 15 * 60 * 1000)
     fun testReadWriteProgram() {
         assumeApfVersionSupportAtLeast(4)
 
diff --git a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
index 0e926cc..60869b6 100644
--- a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
+++ b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
@@ -174,6 +174,7 @@
 import android.net.wifi.WifiManager;
 import android.os.Binder;
 import android.os.Build;
+import android.os.Bundle;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.MessageQueue;
@@ -192,10 +193,11 @@
 import android.util.Log;
 import android.util.Range;
 
-import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.RequiresDevice;
+import androidx.test.platform.app.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.compatibility.common.util.DynamicConfigDeviceSide;
 import com.android.internal.util.ArrayUtils;
 import com.android.modules.utils.build.SdkLevel;
 import com.android.net.module.util.CollectionUtils;
@@ -249,6 +251,7 @@
 import java.net.UnknownHostException;
 import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.List;
 import java.util.Objects;
@@ -335,6 +338,11 @@
     private static final String TEST_HTTPS_URL_PATH = "/https_path";
     private static final String TEST_HTTP_URL_PATH = "/http_path";
     private static final String LOCALHOST_HOSTNAME = "localhost";
+    private static final String TEST_MODULE_NAME_OPTION = "test-module-name";
+    private static final String IP_ADDRESS_ECHO_URL_KEY = "IP_ADDRESS_ECHO_URL";
+    private static final List<String> ALLOWED_IP_ADDRESS_ECHO_URLS = Arrays.asList(
+            "https://google-ipv6test.appspot.com/ip.js?fmt=text",
+            "https://ipv6test.googleapis-cn.com/ip.js?fmt=text");
     // Re-connecting to the AP, obtaining an IP address, revalidating can take a long time
     private static final long WIFI_CONNECT_TIMEOUT_MS = 60_000L;
 
@@ -855,7 +863,7 @@
      * Tests that connections can be opened on WiFi and cellphone networks,
      * and that they are made from different IP addresses.
      */
-    @AppModeFull(reason = "Cannot get WifiManager in instant app mode")
+    @AppModeFull(reason = "Cannot get WifiManager or access the SD card in instant app mode")
     @Test
     @RequiresDevice // Virtual devices use a single internet connection for all networks
     public void testOpenConnection() throws Exception {
@@ -865,7 +873,8 @@
         Network wifiNetwork = mCtsNetUtils.ensureWifiConnected();
         Network cellNetwork = networkCallbackRule.requestCell();
         // This server returns the requestor's IP address as the response body.
-        URL url = new URL("http://google-ipv6test.appspot.com/ip.js?fmt=text");
+        String ipAddressEchoUrl = getIpAddressEchoUrlFromConfig();
+        URL url = new URL(ipAddressEchoUrl);
         String wifiAddressString = httpGet(wifiNetwork, url);
         String cellAddressString = httpGet(cellNetwork, url);
 
@@ -882,6 +891,19 @@
     }
 
     /**
+     * Gets IP address echo url from dynamic config.
+     */
+    private static String getIpAddressEchoUrlFromConfig() throws Exception {
+        Bundle instrumentationArgs = InstrumentationRegistry.getArguments();
+        String testModuleName = instrumentationArgs.getString(TEST_MODULE_NAME_OPTION);
+        // Get the DynamicConfig.xml contents and extract the ipv6 test URL.
+        DynamicConfigDeviceSide dynamicConfig = new DynamicConfigDeviceSide(testModuleName);
+        String ipAddressEchoUrl = dynamicConfig.getValue(IP_ADDRESS_ECHO_URL_KEY);
+        assertContains(ALLOWED_IP_ADDRESS_ECHO_URLS, ipAddressEchoUrl);
+        return ipAddressEchoUrl;
+    }
+
+    /**
      * Performs a HTTP GET to the specified URL on the specified Network, and returns
      * the response body decoded as UTF-8.
      */
diff --git a/tests/unit/java/com/android/server/connectivity/NetworkNotificationManagerTest.java b/tests/unit/java/com/android/server/connectivity/NetworkNotificationManagerTest.java
index 7121ed4..727db58 100644
--- a/tests/unit/java/com/android/server/connectivity/NetworkNotificationManagerTest.java
+++ b/tests/unit/java/com/android/server/connectivity/NetworkNotificationManagerTest.java
@@ -113,6 +113,7 @@
     private static final NetworkCapabilities CELL_CAPABILITIES = new NetworkCapabilities();
     private static final NetworkCapabilities WIFI_CAPABILITIES = new NetworkCapabilities();
     private static final NetworkCapabilities VPN_CAPABILITIES = new NetworkCapabilities();
+    private static final NetworkCapabilities BT_CAPABILITIES = new NetworkCapabilities();
     static {
         CELL_CAPABILITIES.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR);
         CELL_CAPABILITIES.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
@@ -128,6 +129,9 @@
         VPN_CAPABILITIES.addTransportType(NetworkCapabilities.TRANSPORT_VPN);
         VPN_CAPABILITIES.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
         VPN_CAPABILITIES.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN);
+
+        BT_CAPABILITIES.addTransportType(NetworkCapabilities.TRANSPORT_BLUETOOTH);
+        BT_CAPABILITIES.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
     }
 
     /**
@@ -159,7 +163,9 @@
     @Mock NetworkAgentInfo mWifiNai;
     @Mock NetworkAgentInfo mCellNai;
     @Mock NetworkAgentInfo mVpnNai;
+    @Mock NetworkAgentInfo mBluetoothNai;
     @Mock NetworkInfo mNetworkInfo;
+    @Mock NetworkInfo mEmptyNetworkInfo;
     ArgumentCaptor<Notification> mCaptor;
 
     NetworkNotificationManager mManager;
@@ -174,6 +180,8 @@
         mCellNai.networkInfo = mNetworkInfo;
         mVpnNai.networkCapabilities = VPN_CAPABILITIES;
         mVpnNai.networkInfo = mNetworkInfo;
+        mBluetoothNai.networkCapabilities = BT_CAPABILITIES;
+        mBluetoothNai.networkInfo = mEmptyNetworkInfo;
         mDisplayMetrics.density = 2.275f;
         doReturn(true).when(mVpnNai).isVPN();
         doReturn(mResources).when(mCtx).getResources();
@@ -542,10 +550,11 @@
                 R.string.wifi_no_internet_detailed);
     }
 
-    private void runTelephonySignInNotificationTest(String testTitle, String testContents) {
+    private void runSignInNotificationTest(NetworkAgentInfo nai, String testTitle,
+            String testContents) {
         final int id = 101;
         final String tag = NetworkNotificationManager.tagFor(id);
-        mManager.showNotification(id, SIGN_IN, mCellNai, null, null, false);
+        mManager.showNotification(id, SIGN_IN, nai, null, null, false);
 
         final ArgumentCaptor<Notification> noteCaptor = ArgumentCaptor.forClass(Notification.class);
         verify(mNotificationManager).notify(eq(tag), eq(SIGN_IN.eventId), noteCaptor.capture());
@@ -565,7 +574,7 @@
         doReturn(testContents).when(mResources).getString(
                 R.string.mobile_network_available_no_internet_detailed, TEST_OPERATOR_NAME);
 
-        runTelephonySignInNotificationTest(testTitle, testContents);
+        runSignInNotificationTest(mCellNai, testTitle, testContents);
     }
 
     @Test
@@ -579,6 +588,21 @@
         doReturn(testContents).when(mResources).getString(
                 R.string.mobile_network_available_no_internet_detailed_unknown_carrier);
 
-        runTelephonySignInNotificationTest(testTitle, testContents);
+        runSignInNotificationTest(mCellNai, testTitle, testContents);
+    }
+
+    @Test
+    public void testBluetoothSignInNotification_EmptyNotificationContents() {
+        final String testTitle = "Test title";
+        final String testContents = "Test contents";
+        doReturn(testTitle).when(mResources).getString(
+                R.string.network_available_sign_in, 0);
+        doReturn(testContents).when(mResources).getString(
+                eq(R.string.network_available_sign_in_detailed), any());
+
+        runSignInNotificationTest(mBluetoothNai, testTitle, testContents);
+        // The details should be queried with an empty string argument. In practice the notification
+        // contents may just be an empty string, since the default translation just outputs the arg.
+        verify(mResources).getString(eq(R.string.network_available_sign_in_detailed), eq(""));
     }
 }