Merge "[Cronet] delete copy of import_cronet.sh" into main
diff --git a/Cronet/tests/common/Android.bp b/Cronet/tests/common/Android.bp
index a484adb..edeb0b3 100644
--- a/Cronet/tests/common/Android.bp
+++ b/Cronet/tests/common/Android.bp
@@ -17,6 +17,7 @@
 // They must be fast and stable, and exercise public or test APIs.
 
 package {
+    default_team: "trendy_team_fwk_core_networking",
     // See: http://go/android-license-faq
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
diff --git a/Cronet/tests/cts/Android.bp b/Cronet/tests/cts/Android.bp
index 7b52694..92b73d9 100644
--- a/Cronet/tests/cts/Android.bp
+++ b/Cronet/tests/cts/Android.bp
@@ -15,6 +15,7 @@
 //
 
 package {
+    default_team: "trendy_team_fwk_core_networking",
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
@@ -46,7 +47,9 @@
         "framework-connectivity",
         "org.apache.http.legacy",
     ],
-    lint: { test: true }
+    lint: {
+        test: true,
+    },
 }
 
 android_test {
diff --git a/Cronet/tests/cts/src/android/net/http/cts/HttpEngineTest.java b/Cronet/tests/cts/src/android/net/http/cts/HttpEngineTest.java
index 9fc4389..f86ac29 100644
--- a/Cronet/tests/cts/src/android/net/http/cts/HttpEngineTest.java
+++ b/Cronet/tests/cts/src/android/net/http/cts/HttpEngineTest.java
@@ -247,10 +247,8 @@
     @Test
     public void testHttpEngine_requestUsesDefaultUserAgent() throws Exception {
         mEngine = mEngineBuilder.build();
-        HttpCtsTestServer server =
-                new HttpCtsTestServer(ApplicationProvider.getApplicationContext());
 
-        String url = server.getUserAgentUrl();
+        String url = mTestServer.getUserAgentUrl();
         UrlRequest request =
                 mEngine.newUrlRequestBuilder(url, mCallback.getExecutor(), mCallback).build();
         request.start();
@@ -266,14 +264,12 @@
     @Test
     public void testHttpEngine_requestUsesCustomUserAgent() throws Exception {
         String userAgent = "CtsTests User Agent";
-        HttpCtsTestServer server =
-                new HttpCtsTestServer(ApplicationProvider.getApplicationContext());
         mEngine =
                 new HttpEngine.Builder(ApplicationProvider.getApplicationContext())
                         .setUserAgent(userAgent)
                         .build();
 
-        String url = server.getUserAgentUrl();
+        String url = mTestServer.getUserAgentUrl();
         UrlRequest request =
                 mEngine.newUrlRequestBuilder(url, mCallback.getExecutor(), mCallback).build();
         request.start();
diff --git a/Cronet/tests/mts/Android.bp b/Cronet/tests/mts/Android.bp
index 743a1ca..9486e1f 100644
--- a/Cronet/tests/mts/Android.bp
+++ b/Cronet/tests/mts/Android.bp
@@ -13,6 +13,7 @@
 // limitations under the License.
 
 package {
+    default_team: "trendy_team_fwk_core_networking",
     // See: http://go/android-license-faq
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
diff --git a/Cronet/tests/mts/AndroidTest.xml b/Cronet/tests/mts/AndroidTest.xml
index bccbe29..513b988 100644
--- a/Cronet/tests/mts/AndroidTest.xml
+++ b/Cronet/tests/mts/AndroidTest.xml
@@ -53,6 +53,8 @@
         <option name="exclude-filter" value="org.chromium.net.NetworkChangesTest" />
         <!-- b/316550794 -->
         <option name="exclude-filter" value="org.chromium.net.impl.CronetLoggerTest#testEngineCreation" />
+        <!-- b/327182569 -->
+        <option name="exclude-filter" value="org.chromium.net.urlconnection.CronetURLStreamHandlerFactoryTest#testSetUrlStreamFactoryUsesCronetForNative" />
         <option name="hidden-api-checks" value="false"/>
         <option name="isolated-storage" value="false"/>
     </test>
@@ -62,4 +64,4 @@
             class="com.android.tradefed.testtype.suite.module.MainlineTestModuleController">
         <option name="mainline-module-package-name" value="com.google.android.tethering" />
     </object>
-</configuration>
\ No newline at end of file
+</configuration>
diff --git a/DnsResolver/Android.bp b/DnsResolver/Android.bp
index d133034..716eb10 100644
--- a/DnsResolver/Android.bp
+++ b/DnsResolver/Android.bp
@@ -14,6 +14,7 @@
 // limitations under the License.
 
 package {
+    default_team: "trendy_team_fwk_core_networking",
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
@@ -56,7 +57,10 @@
 cc_test {
     name: "dns_helper_unit_test",
     defaults: ["netd_defaults"],
-    test_suites: ["general-tests", "mts-tethering"],
+    test_suites: [
+        "general-tests",
+        "mts-tethering",
+    ],
     test_config_template: ":net_native_test_config_template",
     header_libs: [
         "bpf_connectivity_headers",
@@ -68,8 +72,8 @@
         "libcom.android.tethering.dns_helper",
     ],
     shared_libs: [
-       "libbase",
-       "libcutils",
+        "libbase",
+        "libcutils",
     ],
     compile_multilib: "both",
     multilib: {
diff --git a/Tethering/Android.bp b/Tethering/Android.bp
index 73c11ba..e4e6c70 100644
--- a/Tethering/Android.bp
+++ b/Tethering/Android.bp
@@ -15,6 +15,7 @@
 //
 
 package {
+    default_team: "trendy_team_fwk_core_networking",
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
@@ -82,7 +83,6 @@
     ],
     manifest: "AndroidManifestBase.xml",
     lint: {
-        strict_updatability_linting: true,
         error_checks: ["NewApi"],
     },
 }
@@ -102,7 +102,6 @@
     ],
     apex_available: ["com.android.tethering"],
     lint: {
-        strict_updatability_linting: true,
         baseline_filename: "lint-baseline.xml",
     },
 }
@@ -120,7 +119,6 @@
     ],
     apex_available: ["com.android.tethering"],
     lint: {
-        strict_updatability_linting: true,
         baseline_filename: "lint-baseline.xml",
     },
 }
@@ -195,9 +193,6 @@
     optimize: {
         proguard_flags_files: ["proguard.flags"],
     },
-    lint: {
-        strict_updatability_linting: true,
-    },
 }
 
 // Updatable tethering packaged for finalized API
@@ -213,10 +208,6 @@
     use_embedded_native_libs: true,
     privapp_allowlist: ":privapp_allowlist_com.android.tethering",
     apex_available: ["com.android.tethering"],
-    lint: {
-        strict_updatability_linting: true,
-        baseline_filename: "lint-baseline.xml",
-    },
 }
 
 android_app {
@@ -233,9 +224,7 @@
     privapp_allowlist: ":privapp_allowlist_com.android.tethering",
     apex_available: ["com.android.tethering"],
     lint: {
-        strict_updatability_linting: true,
         error_checks: ["NewApi"],
-        baseline_filename: "lint-baseline.xml",
     },
 }
 
@@ -263,9 +252,6 @@
     static_libs: ["tetheringprotos"],
     apex_available: ["com.android.tethering"],
     min_sdk_version: "30",
-    lint: {
-        baseline_filename: "lint-baseline.xml",
-    },
 }
 
 genrule {
diff --git a/Tethering/apex/Android.bp b/Tethering/apex/Android.bp
index de9017a..30bdf37 100644
--- a/Tethering/apex/Android.bp
+++ b/Tethering/apex/Android.bp
@@ -15,6 +15,7 @@
 //
 
 package {
+    default_team: "trendy_team_fwk_core_networking",
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
@@ -24,6 +25,7 @@
     name: "ConnectivityNextEnableDefaults",
     enabled: true,
 }
+
 java_defaults {
     name: "NetworkStackApiShimSettingsForCurrentBranch",
     // API shims to include in the networking modules built from the branch. Branches that disable
@@ -31,6 +33,7 @@
     // (X_current API level).
     static_libs: ["NetworkStackApiCurrentShims"],
 }
+
 apex_defaults {
     name: "ConnectivityApexDefaults",
     // Tethering app to include in the AOSP apex. Branches that disable the "next" targets may use
@@ -38,6 +41,7 @@
     // package names and keys, so that apex will be unused anyway.
     apps: ["TetheringNext"], // Replace to "Tethering" if ConnectivityNextEnableDefaults is false.
 }
+
 enable_tethering_next_apex = true
 // This is a placeholder comment to avoid merge conflicts
 // as the above target may have different "enabled" values
@@ -50,16 +54,6 @@
         "//external/cronet/third_party/boringssl:libcrypto",
         "//external/cronet/third_party/boringssl:libssl",
     ],
-    arch: {
-        riscv64: {
-            // TODO: remove this when there is a riscv64 libcronet
-            exclude_jni_libs: [
-                "cronet_aml_components_cronet_android_cronet",
-                "//external/cronet/third_party/boringssl:libcrypto",
-                "//external/cronet/third_party/boringssl:libssl",
-            ],
-        },
-    },
 }
 
 apex {
@@ -92,7 +86,7 @@
         both: {
             jni_libs: [
                 "libframework-connectivity-jni",
-                "libframework-connectivity-tiramisu-jni"
+                "libframework-connectivity-tiramisu-jni",
             ],
         },
     },
@@ -117,8 +111,9 @@
         "ServiceConnectivityResources",
     ],
     prebuilts: [
-        "ot-daemon.init.34rc",
         "current_sdkinfo",
+        "netbpfload.mainline.rc",
+        "ot-daemon.init.34rc",
     ],
     manifest: "manifest.json",
     key: "com.android.tethering.key",
diff --git a/Tethering/apex/permissions/Android.bp b/Tethering/apex/permissions/Android.bp
index 69c1aa2..20772a8 100644
--- a/Tethering/apex/permissions/Android.bp
+++ b/Tethering/apex/permissions/Android.bp
@@ -15,6 +15,7 @@
 //
 
 package {
+    default_team: "trendy_team_fwk_core_networking",
     default_applicable_licenses: ["Android-Apache-2.0"],
     default_visibility: ["//packages/modules/Connectivity/Tethering:__subpackages__"],
 }
@@ -22,4 +23,4 @@
 filegroup {
     name: "privapp_allowlist_com.android.tethering",
     srcs: ["permissions.xml"],
-}
\ No newline at end of file
+}
diff --git a/Tethering/common/TetheringLib/Android.bp b/Tethering/common/TetheringLib/Android.bp
index bcea425..9fa073b 100644
--- a/Tethering/common/TetheringLib/Android.bp
+++ b/Tethering/common/TetheringLib/Android.bp
@@ -14,6 +14,7 @@
 // limitations under the License.
 
 package {
+    default_team: "trendy_team_fwk_core_networking",
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
@@ -35,7 +36,7 @@
         "//frameworks/base/core/tests/bandwidthtests",
         "//frameworks/base/core/tests/benchmarks",
         "//frameworks/base/core/tests/utillib",
-        "//frameworks/base/packages/Connectivity/tests:__subpackages__",
+        "//frameworks/base/services/tests/VpnTests",
         "//frameworks/base/tests/vcn",
         "//frameworks/opt/telephony/tests/telephonytests",
         "//packages/modules/CaptivePortalLogin/tests",
@@ -55,14 +56,19 @@
 
     hostdex: true, // for hiddenapi check
     permitted_packages: ["android.net"],
-    lint: { strict_updatability_linting: true },
+    lint: {
+        strict_updatability_linting: true,
+    },
+    aconfig_declarations: [
+        "com.android.net.flags-aconfig",
+    ],
 }
 
 java_library {
-  name: "framework-tethering-pre-jarjar",
-  defaults: [
-    "framework-tethering-defaults",
-  ],
+    name: "framework-tethering-pre-jarjar",
+    defaults: [
+        "framework-tethering-defaults",
+    ],
 }
 
 java_genrule {
@@ -88,7 +94,7 @@
     name: "framework-tethering-defaults",
     defaults: ["framework-module-defaults"],
     srcs: [
-      ":framework-tethering-srcs"
+        ":framework-tethering-srcs",
     ],
     libs: ["framework-connectivity.stubs.module_lib"],
     aidl: {
@@ -107,5 +113,5 @@
         "src/**/*.aidl",
         "src/**/*.java",
     ],
-    path: "src"
+    path: "src",
 }
diff --git a/Tethering/common/TetheringLib/api/system-current.txt b/Tethering/common/TetheringLib/api/system-current.txt
index 844ff64..a287b42 100644
--- a/Tethering/common/TetheringLib/api/system-current.txt
+++ b/Tethering/common/TetheringLib/api/system-current.txt
@@ -95,13 +95,16 @@
     method public default void onUpstreamChanged(@Nullable android.net.Network);
   }
 
-  public static class TetheringManager.TetheringRequest {
+  public static final class TetheringManager.TetheringRequest implements android.os.Parcelable {
+    method @FlaggedApi("com.android.net.flags.tethering_request_with_soft_ap_config") public int describeContents();
     method @Nullable public android.net.LinkAddress getClientStaticIpv4Address();
     method public int getConnectivityScope();
     method @Nullable public android.net.LinkAddress getLocalIpv4Address();
     method public boolean getShouldShowEntitlementUi();
     method public int getTetheringType();
     method public boolean isExemptFromEntitlementCheck();
+    method @FlaggedApi("com.android.net.flags.tethering_request_with_soft_ap_config") public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @FlaggedApi("com.android.net.flags.tethering_request_with_soft_ap_config") @NonNull public static final android.os.Parcelable.Creator<android.net.TetheringManager.TetheringRequest> CREATOR;
   }
 
   public static class TetheringManager.TetheringRequest.Builder {
diff --git a/Tethering/common/TetheringLib/src/android/net/TetheringManager.java b/Tethering/common/TetheringLib/src/android/net/TetheringManager.java
index cd914d3..7b769d4 100644
--- a/Tethering/common/TetheringLib/src/android/net/TetheringManager.java
+++ b/Tethering/common/TetheringLib/src/android/net/TetheringManager.java
@@ -18,6 +18,7 @@
 import static android.annotation.SystemApi.Client.MODULE_LIBRARIES;
 
 import android.Manifest;
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -28,6 +29,8 @@
 import android.os.Bundle;
 import android.os.ConditionVariable;
 import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Parcelable;
 import android.os.RemoteException;
 import android.os.ResultReceiver;
 import android.util.ArrayMap;
@@ -59,6 +62,14 @@
  */
 @SystemApi
 public class TetheringManager {
+    // TODO : remove this class when udc-mainline-prod is abandoned and android.net.flags.Flags is
+    // available here
+    /** @hide */
+    public static class Flags {
+        static final String TETHERING_REQUEST_WITH_SOFT_AP_CONFIG =
+                "com.android.net.flags.tethering_request_with_soft_ap_config";
+    }
+
     private static final String TAG = TetheringManager.class.getSimpleName();
     private static final int DEFAULT_TIMEOUT_MS = 60_000;
     private static final long CONNECTOR_POLL_INTERVAL_MILLIS = 200L;
@@ -673,14 +684,44 @@
     /**
      *  Use with {@link #startTethering} to specify additional parameters when starting tethering.
      */
-    public static class TetheringRequest {
+    public static final class TetheringRequest implements Parcelable {
         /** A configuration set for TetheringRequest. */
         private final TetheringRequestParcel mRequestParcel;
 
-        private TetheringRequest(final TetheringRequestParcel request) {
+        private TetheringRequest(@NonNull final TetheringRequestParcel request) {
             mRequestParcel = request;
         }
 
+        private TetheringRequest(@NonNull Parcel in) {
+            mRequestParcel = in.readParcelable(TetheringRequestParcel.class.getClassLoader());
+        }
+
+        @FlaggedApi(Flags.TETHERING_REQUEST_WITH_SOFT_AP_CONFIG)
+        @NonNull
+        public static final Creator<TetheringRequest> CREATOR = new Creator<>() {
+            @Override
+            public TetheringRequest createFromParcel(@NonNull Parcel in) {
+                return new TetheringRequest(in);
+            }
+
+            @Override
+            public TetheringRequest[] newArray(int size) {
+                return new TetheringRequest[size];
+            }
+        };
+
+        @FlaggedApi(Flags.TETHERING_REQUEST_WITH_SOFT_AP_CONFIG)
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @FlaggedApi(Flags.TETHERING_REQUEST_WITH_SOFT_AP_CONFIG)
+        @Override
+        public void writeToParcel(@NonNull Parcel dest, int flags) {
+            dest.writeParcelable(mRequestParcel, flags);
+        }
+
         /** Builder used to create TetheringRequest. */
         public static class Builder {
             private final TetheringRequestParcel mBuilderParcel;
diff --git a/Tethering/lint-baseline.xml b/Tethering/lint-baseline.xml
index 37511c6..4f92c9c 100644
--- a/Tethering/lint-baseline.xml
+++ b/Tethering/lint-baseline.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 8.0.0-dev" type="baseline" dependencies="true" variant="all" version="8.0.0-dev">
+<issues format="6" by="lint 8.4.0-alpha01" type="baseline" client="" dependencies="true" name="" variant="all" version="8.4.0-alpha01">
 
     <issue
         id="NewApi"
@@ -8,7 +8,7 @@
         errorLine2="                                           ~~~~~~~~~~~~~~~~~~~~">
         <location
             file="packages/modules/Connectivity/Tethering/src/com/android/networkstack/tethering/OffloadController.java"
-            line="293"
+            line="283"
             column="44"/>
     </issue>
 
diff --git a/Tethering/res/values-af/strings.xml b/Tethering/res/values-af/strings.xml
index 056168b..3790142 100644
--- a/Tethering/res/values-af/strings.xml
+++ b/Tethering/res/values-af/strings.xml
@@ -16,14 +16,14 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="tethered_notification_title" msgid="6426563586025792944">"Verbinding of warmkol is aktief"</string>
-    <string name="tethered_notification_message" msgid="64800879503420696">"Tik om op te stel."</string>
-    <string name="disable_tether_notification_title" msgid="3004509127903564191">"Verbinding is gedeaktiveer"</string>
-    <string name="disable_tether_notification_message" msgid="6717523799293901476">"Kontak jou administrateur vir besonderhede"</string>
-    <string name="notification_channel_tethering_status" msgid="2663463891530932727">"Warmkol- en verbindingstatus"</string>
-    <string name="no_upstream_notification_title" msgid="1204601824631788482"></string>
-    <string name="no_upstream_notification_message" msgid="8586582938243032621"></string>
-    <string name="no_upstream_notification_disable_button" msgid="8800919436924640822"></string>
-    <string name="upstream_roaming_notification_title" msgid="4772373823198997030"></string>
-    <string name="upstream_roaming_notification_message" msgid="3985577843181551650"></string>
+    <string name="tethered_notification_title" msgid="5350162111436634622">"Verbinding of warmkol is aktief"</string>
+    <string name="tethered_notification_message" msgid="2338023450330652098">"Tik om op te stel."</string>
+    <string name="disable_tether_notification_title" msgid="3183576627492925522">"Verbinding is gedeaktiveer"</string>
+    <string name="disable_tether_notification_message" msgid="6655882039707534929">"Kontak jou admin vir besonderhede"</string>
+    <string name="notification_channel_tethering_status" msgid="7030733422705019001">"Warmkol- en verbindingstatus"</string>
+    <string name="no_upstream_notification_title" msgid="2052743091868702475"></string>
+    <string name="no_upstream_notification_message" msgid="6932020551635470134"></string>
+    <string name="no_upstream_notification_disable_button" msgid="8836277213343697023"></string>
+    <string name="upstream_roaming_notification_title" msgid="8614262557406849762"></string>
+    <string name="upstream_roaming_notification_message" msgid="5999740876323106599"></string>
 </resources>
diff --git a/Tethering/res/values-am/strings.xml b/Tethering/res/values-am/strings.xml
index ac468dd..bb89d6e 100644
--- a/Tethering/res/values-am/strings.xml
+++ b/Tethering/res/values-am/strings.xml
@@ -16,14 +16,14 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="tethered_notification_title" msgid="6426563586025792944">"እንደ ሞደም መሰካት ወይም መገናኛ ነጥብ ገባሪ"</string>
-    <string name="tethered_notification_message" msgid="64800879503420696">"ለማዋቀር መታ ያድርጉ።"</string>
-    <string name="disable_tether_notification_title" msgid="3004509127903564191">"እንደ ሞደም መሰካት ተሰናክሏል"</string>
-    <string name="disable_tether_notification_message" msgid="6717523799293901476">"ለዝርዝሮች የእርስዎን አስተዳዳሪ ያነጋግሩ"</string>
-    <string name="notification_channel_tethering_status" msgid="2663463891530932727">"መገናኛ ነጥብ እና እንደ ሞደም የመሰካት ሁኔታ"</string>
-    <string name="no_upstream_notification_title" msgid="1204601824631788482"></string>
-    <string name="no_upstream_notification_message" msgid="8586582938243032621"></string>
-    <string name="no_upstream_notification_disable_button" msgid="8800919436924640822"></string>
-    <string name="upstream_roaming_notification_title" msgid="4772373823198997030"></string>
-    <string name="upstream_roaming_notification_message" msgid="3985577843181551650"></string>
+    <string name="tethered_notification_title" msgid="5350162111436634622">"እንደ ሞደም መሰካት ወይም መገናኛ ነጥብ ገባሪ"</string>
+    <string name="tethered_notification_message" msgid="2338023450330652098">"ለማዋቀር መታ ያድርጉ።"</string>
+    <string name="disable_tether_notification_title" msgid="3183576627492925522">"እንደ ሞደም መሰካት ተሰናክሏል"</string>
+    <string name="disable_tether_notification_message" msgid="6655882039707534929">"ለዝርዝሮች የእርስዎን አስተዳዳሪ ያነጋግሩ"</string>
+    <string name="notification_channel_tethering_status" msgid="7030733422705019001">"መገናኛ ነጥብ እና እንደ ሞደም የመሰካት ሁኔታ"</string>
+    <string name="no_upstream_notification_title" msgid="2052743091868702475"></string>
+    <string name="no_upstream_notification_message" msgid="6932020551635470134"></string>
+    <string name="no_upstream_notification_disable_button" msgid="8836277213343697023"></string>
+    <string name="upstream_roaming_notification_title" msgid="8614262557406849762"></string>
+    <string name="upstream_roaming_notification_message" msgid="5999740876323106599"></string>
 </resources>
diff --git a/Tethering/res/values-ar/strings.xml b/Tethering/res/values-ar/strings.xml
index 7d5bad3..ef98a01 100644
--- a/Tethering/res/values-ar/strings.xml
+++ b/Tethering/res/values-ar/strings.xml
@@ -16,14 +16,14 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="tethered_notification_title" msgid="6426563586025792944">"النطاق نشط أو نقطة الاتصال نشطة"</string>
-    <string name="tethered_notification_message" msgid="64800879503420696">"انقر للإعداد."</string>
-    <string name="disable_tether_notification_title" msgid="3004509127903564191">"التوصيل متوقف."</string>
-    <string name="disable_tether_notification_message" msgid="6717523799293901476">"تواصَل مع المشرف للحصول على التفاصيل."</string>
-    <string name="notification_channel_tethering_status" msgid="2663463891530932727">"حالة نقطة الاتصال والتوصيل"</string>
-    <string name="no_upstream_notification_title" msgid="1204601824631788482"></string>
-    <string name="no_upstream_notification_message" msgid="8586582938243032621"></string>
-    <string name="no_upstream_notification_disable_button" msgid="8800919436924640822"></string>
-    <string name="upstream_roaming_notification_title" msgid="4772373823198997030"></string>
-    <string name="upstream_roaming_notification_message" msgid="3985577843181551650"></string>
+    <string name="tethered_notification_title" msgid="5350162111436634622">"نقطة التوصيل نشطة أو الاتصال نشط"</string>
+    <string name="tethered_notification_message" msgid="2338023450330652098">"انقر لإعداد التوصيل."</string>
+    <string name="disable_tether_notification_title" msgid="3183576627492925522">"التوصيل غير مفعَّل"</string>
+    <string name="disable_tether_notification_message" msgid="6655882039707534929">"تواصَل مع المشرف للحصول على التفاصيل."</string>
+    <string name="notification_channel_tethering_status" msgid="7030733422705019001">"حالة نقطة الاتصال والتوصيل"</string>
+    <string name="no_upstream_notification_title" msgid="2052743091868702475"></string>
+    <string name="no_upstream_notification_message" msgid="6932020551635470134"></string>
+    <string name="no_upstream_notification_disable_button" msgid="8836277213343697023"></string>
+    <string name="upstream_roaming_notification_title" msgid="8614262557406849762"></string>
+    <string name="upstream_roaming_notification_message" msgid="5999740876323106599"></string>
 </resources>
diff --git a/Tethering/res/values-as/strings.xml b/Tethering/res/values-as/strings.xml
index 0913504..9b9e8d6 100644
--- a/Tethering/res/values-as/strings.xml
+++ b/Tethering/res/values-as/strings.xml
@@ -16,14 +16,14 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="tethered_notification_title" msgid="6426563586025792944">"টে\'ডাৰিং অথবা হ\'টস্প\'ট সক্ৰিয় অৱস্থাত আছে"</string>
-    <string name="tethered_notification_message" msgid="64800879503420696">"ছেট আপ কৰিবলৈ টিপক।"</string>
-    <string name="disable_tether_notification_title" msgid="3004509127903564191">"টে\'ডাৰিঙৰ সুবিধাটো অক্ষম কৰি থোৱা হৈছে"</string>
-    <string name="disable_tether_notification_message" msgid="6717523799293901476">"সবিশেষ জানিবলৈ আপোনাৰ প্ৰশাসকৰ সৈতে যোগাযোগ কৰক"</string>
-    <string name="notification_channel_tethering_status" msgid="2663463891530932727">"হ’টস্প\'ট আৰু টে\'ডাৰিঙৰ স্থিতি"</string>
-    <string name="no_upstream_notification_title" msgid="1204601824631788482"></string>
-    <string name="no_upstream_notification_message" msgid="8586582938243032621"></string>
-    <string name="no_upstream_notification_disable_button" msgid="8800919436924640822"></string>
-    <string name="upstream_roaming_notification_title" msgid="4772373823198997030"></string>
-    <string name="upstream_roaming_notification_message" msgid="3985577843181551650"></string>
+    <string name="tethered_notification_title" msgid="5350162111436634622">"টে’ডাৰিং অথবা হ’টস্প’ট সক্ৰিয় অৱস্থাত আছে"</string>
+    <string name="tethered_notification_message" msgid="2338023450330652098">"ছেট আপ কৰিবলৈ টিপক।"</string>
+    <string name="disable_tether_notification_title" msgid="3183576627492925522">"টে’ডাৰিঙৰ সুবিধাটো অক্ষম কৰি থোৱা হৈছে"</string>
+    <string name="disable_tether_notification_message" msgid="6655882039707534929">"সবিশেষ জানিবলৈ আপোনাৰ প্ৰশাসকৰ সৈতে যোগাযোগ কৰক"</string>
+    <string name="notification_channel_tethering_status" msgid="7030733422705019001">"হ’টস্প’ট আৰু টে’ডাৰিঙৰ স্থিতি"</string>
+    <string name="no_upstream_notification_title" msgid="2052743091868702475"></string>
+    <string name="no_upstream_notification_message" msgid="6932020551635470134"></string>
+    <string name="no_upstream_notification_disable_button" msgid="8836277213343697023"></string>
+    <string name="upstream_roaming_notification_title" msgid="8614262557406849762"></string>
+    <string name="upstream_roaming_notification_message" msgid="5999740876323106599"></string>
 </resources>
diff --git a/Tethering/res/values-az/strings.xml b/Tethering/res/values-az/strings.xml
index dce70da..b091f15 100644
--- a/Tethering/res/values-az/strings.xml
+++ b/Tethering/res/values-az/strings.xml
@@ -16,14 +16,14 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="tethered_notification_title" msgid="6426563586025792944">"Birləşmə və ya hotspot aktivdir"</string>
-    <string name="tethered_notification_message" msgid="64800879503420696">"Ayarlamaq üçün toxunun."</string>
-    <string name="disable_tether_notification_title" msgid="3004509127903564191">"Birləşmə deaktivdir"</string>
-    <string name="disable_tether_notification_message" msgid="6717523799293901476">"Detallar üçün adminlə əlaqə saxlayın"</string>
-    <string name="notification_channel_tethering_status" msgid="2663463891530932727">"Hotspot &amp; birləşmə statusu"</string>
-    <string name="no_upstream_notification_title" msgid="1204601824631788482"></string>
-    <string name="no_upstream_notification_message" msgid="8586582938243032621"></string>
-    <string name="no_upstream_notification_disable_button" msgid="8800919436924640822"></string>
-    <string name="upstream_roaming_notification_title" msgid="4772373823198997030"></string>
-    <string name="upstream_roaming_notification_message" msgid="3985577843181551650"></string>
+    <string name="tethered_notification_title" msgid="5350162111436634622">"Modem rejimi və ya hotspot aktivdir"</string>
+    <string name="tethered_notification_message" msgid="2338023450330652098">"Ayarlamaq üçün toxunun."</string>
+    <string name="disable_tether_notification_title" msgid="3183576627492925522">"Modem rejimi deaktivdir"</string>
+    <string name="disable_tether_notification_message" msgid="6655882039707534929">"Detallar üçün adminlə əlaqə saxlayın"</string>
+    <string name="notification_channel_tethering_status" msgid="7030733422705019001">"Hotspot və modem rejimi statusu"</string>
+    <string name="no_upstream_notification_title" msgid="2052743091868702475"></string>
+    <string name="no_upstream_notification_message" msgid="6932020551635470134"></string>
+    <string name="no_upstream_notification_disable_button" msgid="8836277213343697023"></string>
+    <string name="upstream_roaming_notification_title" msgid="8614262557406849762"></string>
+    <string name="upstream_roaming_notification_message" msgid="5999740876323106599"></string>
 </resources>
diff --git a/Tethering/res/values-b+sr+Latn/strings.xml b/Tethering/res/values-b+sr+Latn/strings.xml
index b0774ec..aa6c6fd 100644
--- a/Tethering/res/values-b+sr+Latn/strings.xml
+++ b/Tethering/res/values-b+sr+Latn/strings.xml
@@ -16,14 +16,14 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="tethered_notification_title" msgid="6426563586025792944">"Privezivanje ili hotspot je aktivan"</string>
-    <string name="tethered_notification_message" msgid="64800879503420696">"Dodirnite da biste podesili."</string>
-    <string name="disable_tether_notification_title" msgid="3004509127903564191">"Privezivanje je onemogućeno"</string>
-    <string name="disable_tether_notification_message" msgid="6717523799293901476">"Potražite detalje od administratora"</string>
-    <string name="notification_channel_tethering_status" msgid="2663463891530932727">"Status hotspota i privezivanja"</string>
-    <string name="no_upstream_notification_title" msgid="1204601824631788482"></string>
-    <string name="no_upstream_notification_message" msgid="8586582938243032621"></string>
-    <string name="no_upstream_notification_disable_button" msgid="8800919436924640822"></string>
-    <string name="upstream_roaming_notification_title" msgid="4772373823198997030"></string>
-    <string name="upstream_roaming_notification_message" msgid="3985577843181551650"></string>
+    <string name="tethered_notification_title" msgid="5350162111436634622">"Aktivno je privezivanje ili hotspot"</string>
+    <string name="tethered_notification_message" msgid="2338023450330652098">"Dodirnite da biste podesili."</string>
+    <string name="disable_tether_notification_title" msgid="3183576627492925522">"Privezivanje je onemogućeno"</string>
+    <string name="disable_tether_notification_message" msgid="6655882039707534929">"Potražite detalje od administratora"</string>
+    <string name="notification_channel_tethering_status" msgid="7030733422705019001">"Status hotspota i privezivanja"</string>
+    <string name="no_upstream_notification_title" msgid="2052743091868702475"></string>
+    <string name="no_upstream_notification_message" msgid="6932020551635470134"></string>
+    <string name="no_upstream_notification_disable_button" msgid="8836277213343697023"></string>
+    <string name="upstream_roaming_notification_title" msgid="8614262557406849762"></string>
+    <string name="upstream_roaming_notification_message" msgid="5999740876323106599"></string>
 </resources>
diff --git a/Tethering/res/values-be/strings.xml b/Tethering/res/values-be/strings.xml
index a8acebe..5da8828 100644
--- a/Tethering/res/values-be/strings.xml
+++ b/Tethering/res/values-be/strings.xml
@@ -16,14 +16,14 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="tethered_notification_title" msgid="6426563586025792944">"Мадэм або хот-спот актыўныя"</string>
-    <string name="tethered_notification_message" msgid="64800879503420696">"Дакраніцеся, каб наладзіць."</string>
-    <string name="disable_tether_notification_title" msgid="3004509127903564191">"Рэжым мадэма выключаны"</string>
-    <string name="disable_tether_notification_message" msgid="6717523799293901476">"Звярніцеся да адміністратара па падрабязную інфармацыю"</string>
-    <string name="notification_channel_tethering_status" msgid="2663463891530932727">"Стан \"Хот-спот і мадэм\""</string>
-    <string name="no_upstream_notification_title" msgid="1204601824631788482"></string>
-    <string name="no_upstream_notification_message" msgid="8586582938243032621"></string>
-    <string name="no_upstream_notification_disable_button" msgid="8800919436924640822"></string>
-    <string name="upstream_roaming_notification_title" msgid="4772373823198997030"></string>
-    <string name="upstream_roaming_notification_message" msgid="3985577843181551650"></string>
+    <string name="tethered_notification_title" msgid="5350162111436634622">"Мадэм або хот-спот актыўныя"</string>
+    <string name="tethered_notification_message" msgid="2338023450330652098">"Націсніце, каб наладзіць."</string>
+    <string name="disable_tether_notification_title" msgid="3183576627492925522">"Рэжым мадэма выключаны"</string>
+    <string name="disable_tether_notification_message" msgid="6655882039707534929">"Звярніцеся да адміністратара па падрабязную інфармацыю"</string>
+    <string name="notification_channel_tethering_status" msgid="7030733422705019001">"Стан \"Хот-спот і мадэм\""</string>
+    <string name="no_upstream_notification_title" msgid="2052743091868702475"></string>
+    <string name="no_upstream_notification_message" msgid="6932020551635470134"></string>
+    <string name="no_upstream_notification_disable_button" msgid="8836277213343697023"></string>
+    <string name="upstream_roaming_notification_title" msgid="8614262557406849762"></string>
+    <string name="upstream_roaming_notification_message" msgid="5999740876323106599"></string>
 </resources>
diff --git a/Tethering/res/values-bg/strings.xml b/Tethering/res/values-bg/strings.xml
index 94fb2d8..0ce2ac7 100644
--- a/Tethering/res/values-bg/strings.xml
+++ b/Tethering/res/values-bg/strings.xml
@@ -16,14 +16,14 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="tethered_notification_title" msgid="6426563586025792944">"Има активна споделена връзка или точка за достъп"</string>
-    <string name="tethered_notification_message" msgid="64800879503420696">"Докоснете, за да настроите."</string>
-    <string name="disable_tether_notification_title" msgid="3004509127903564191">"Функцията за тетъринг е деактивирана"</string>
-    <string name="disable_tether_notification_message" msgid="6717523799293901476">"Свържете се с администратора си за подробности"</string>
-    <string name="notification_channel_tethering_status" msgid="2663463891530932727">"Състояние на функцията за точка за достъп и тетъринг"</string>
-    <string name="no_upstream_notification_title" msgid="1204601824631788482"></string>
-    <string name="no_upstream_notification_message" msgid="8586582938243032621"></string>
-    <string name="no_upstream_notification_disable_button" msgid="8800919436924640822"></string>
-    <string name="upstream_roaming_notification_title" msgid="4772373823198997030"></string>
-    <string name="upstream_roaming_notification_message" msgid="3985577843181551650"></string>
+    <string name="tethered_notification_title" msgid="5350162111436634622">"Има активна споделена връзка или точка за достъп"</string>
+    <string name="tethered_notification_message" msgid="2338023450330652098">"Докоснете, за да настроите."</string>
+    <string name="disable_tether_notification_title" msgid="3183576627492925522">"Функцията за тетъринг е деактивирана"</string>
+    <string name="disable_tether_notification_message" msgid="6655882039707534929">"Свържете се с администратора си за подробности"</string>
+    <string name="notification_channel_tethering_status" msgid="7030733422705019001">"Състояние на функцията за точка за достъп и тетъринг"</string>
+    <string name="no_upstream_notification_title" msgid="2052743091868702475"></string>
+    <string name="no_upstream_notification_message" msgid="6932020551635470134"></string>
+    <string name="no_upstream_notification_disable_button" msgid="8836277213343697023"></string>
+    <string name="upstream_roaming_notification_title" msgid="8614262557406849762"></string>
+    <string name="upstream_roaming_notification_message" msgid="5999740876323106599"></string>
 </resources>
diff --git a/Tethering/res/values-bn/strings.xml b/Tethering/res/values-bn/strings.xml
index aea02b9..787a65c 100644
--- a/Tethering/res/values-bn/strings.xml
+++ b/Tethering/res/values-bn/strings.xml
@@ -16,14 +16,14 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="tethered_notification_title" msgid="6426563586025792944">"টিথারিং বা হটস্পট চালু আছে"</string>
-    <string name="tethered_notification_message" msgid="64800879503420696">"সেট-আপ করতে ট্যাপ করুন।"</string>
-    <string name="disable_tether_notification_title" msgid="3004509127903564191">"টিথারিং বন্ধ করা আছে"</string>
-    <string name="disable_tether_notification_message" msgid="6717523799293901476">"বিশদে জানতে অ্যাডমিনের সাথে যোগাযোগ করুন"</string>
-    <string name="notification_channel_tethering_status" msgid="2663463891530932727">"হটস্পট ও টিথারিং স্ট্যাটাস"</string>
-    <string name="no_upstream_notification_title" msgid="1204601824631788482"></string>
-    <string name="no_upstream_notification_message" msgid="8586582938243032621"></string>
-    <string name="no_upstream_notification_disable_button" msgid="8800919436924640822"></string>
-    <string name="upstream_roaming_notification_title" msgid="4772373823198997030"></string>
-    <string name="upstream_roaming_notification_message" msgid="3985577843181551650"></string>
+    <string name="tethered_notification_title" msgid="5350162111436634622">"টেথারিং বা হটস্পট অ্যাক্টিভ আছে"</string>
+    <string name="tethered_notification_message" msgid="2338023450330652098">"সেট-আপ করতে ট্যাপ করুন।"</string>
+    <string name="disable_tether_notification_title" msgid="3183576627492925522">"টেথারিং বন্ধ করা আছে"</string>
+    <string name="disable_tether_notification_message" msgid="6655882039707534929">"বিস্তারিত বিবরণ পেতে, অ্যাডমিনের সাথে যোগাযোগ করুন"</string>
+    <string name="notification_channel_tethering_status" msgid="7030733422705019001">"হটস্পট ও টেথারিং সংক্রান্ত স্ট্যাটাস"</string>
+    <string name="no_upstream_notification_title" msgid="2052743091868702475"></string>
+    <string name="no_upstream_notification_message" msgid="6932020551635470134"></string>
+    <string name="no_upstream_notification_disable_button" msgid="8836277213343697023"></string>
+    <string name="upstream_roaming_notification_title" msgid="8614262557406849762"></string>
+    <string name="upstream_roaming_notification_message" msgid="5999740876323106599"></string>
 </resources>
diff --git a/Tethering/res/values-bs/strings.xml b/Tethering/res/values-bs/strings.xml
index de23272..b6073fd 100644
--- a/Tethering/res/values-bs/strings.xml
+++ b/Tethering/res/values-bs/strings.xml
@@ -16,14 +16,14 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="tethered_notification_title" msgid="6426563586025792944">"Aktivno je povezivanje putem mobitela ili pristupna tačka"</string>
-    <string name="tethered_notification_message" msgid="64800879503420696">"Dodirnite da postavite."</string>
-    <string name="disable_tether_notification_title" msgid="3004509127903564191">"Povezivanje putem mobitela je onemogućeno"</string>
-    <string name="disable_tether_notification_message" msgid="6717523799293901476">"Kontaktirajte svog administratora za detalje"</string>
-    <string name="notification_channel_tethering_status" msgid="2663463891530932727">"Status pristupne tačke i povezivanja putem mobitela"</string>
-    <string name="no_upstream_notification_title" msgid="1204601824631788482"></string>
-    <string name="no_upstream_notification_message" msgid="8586582938243032621"></string>
-    <string name="no_upstream_notification_disable_button" msgid="8800919436924640822"></string>
-    <string name="upstream_roaming_notification_title" msgid="4772373823198997030"></string>
-    <string name="upstream_roaming_notification_message" msgid="3985577843181551650"></string>
+    <string name="tethered_notification_title" msgid="5350162111436634622">"Dijeljenje internetske veze ili pristupna tačka su aktivni"</string>
+    <string name="tethered_notification_message" msgid="2338023450330652098">"Dodirnite da postavite."</string>
+    <string name="disable_tether_notification_title" msgid="3183576627492925522">"Dijeljenje internetske veze je onemogućeno"</string>
+    <string name="disable_tether_notification_message" msgid="6655882039707534929">"Kontaktirajte administratora za detalje"</string>
+    <string name="notification_channel_tethering_status" msgid="7030733422705019001">"Status pristupne tačke i dijeljenja internetske veze"</string>
+    <string name="no_upstream_notification_title" msgid="2052743091868702475"></string>
+    <string name="no_upstream_notification_message" msgid="6932020551635470134"></string>
+    <string name="no_upstream_notification_disable_button" msgid="8836277213343697023"></string>
+    <string name="upstream_roaming_notification_title" msgid="8614262557406849762"></string>
+    <string name="upstream_roaming_notification_message" msgid="5999740876323106599"></string>
 </resources>
diff --git a/Tethering/res/values-ca/strings.xml b/Tethering/res/values-ca/strings.xml
index 88b795c..2513989 100644
--- a/Tethering/res/values-ca/strings.xml
+++ b/Tethering/res/values-ca/strings.xml
@@ -16,14 +16,14 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="tethered_notification_title" msgid="6426563586025792944">"Compartició de xarxa o punt d\'accés Wi‑Fi actius"</string>
-    <string name="tethered_notification_message" msgid="64800879503420696">"Toca per configurar."</string>
-    <string name="disable_tether_notification_title" msgid="3004509127903564191">"La compartició de xarxa està desactivada"</string>
-    <string name="disable_tether_notification_message" msgid="6717523799293901476">"Contacta amb el teu administrador per obtenir més informació"</string>
-    <string name="notification_channel_tethering_status" msgid="2663463891530932727">"Estat del punt d\'accés Wi‑Fi i de la compartició de xarxa"</string>
-    <string name="no_upstream_notification_title" msgid="1204601824631788482"></string>
-    <string name="no_upstream_notification_message" msgid="8586582938243032621"></string>
-    <string name="no_upstream_notification_disable_button" msgid="8800919436924640822"></string>
-    <string name="upstream_roaming_notification_title" msgid="4772373823198997030"></string>
-    <string name="upstream_roaming_notification_message" msgid="3985577843181551650"></string>
+    <string name="tethered_notification_title" msgid="5350162111436634622">"Compartició de xarxa o punt d\'accés Wi‑Fi actius"</string>
+    <string name="tethered_notification_message" msgid="2338023450330652098">"Toca per configurar."</string>
+    <string name="disable_tether_notification_title" msgid="3183576627492925522">"La compartició de xarxa està desactivada"</string>
+    <string name="disable_tether_notification_message" msgid="6655882039707534929">"Contacta amb el teu administrador per obtenir més informació"</string>
+    <string name="notification_channel_tethering_status" msgid="7030733422705019001">"Estat del punt d\'accés Wi‑Fi i de la compartició de xarxa"</string>
+    <string name="no_upstream_notification_title" msgid="2052743091868702475"></string>
+    <string name="no_upstream_notification_message" msgid="6932020551635470134"></string>
+    <string name="no_upstream_notification_disable_button" msgid="8836277213343697023"></string>
+    <string name="upstream_roaming_notification_title" msgid="8614262557406849762"></string>
+    <string name="upstream_roaming_notification_message" msgid="5999740876323106599"></string>
 </resources>
diff --git a/Tethering/res/values-cs/strings.xml b/Tethering/res/values-cs/strings.xml
index 8c1b83b..a749915 100644
--- a/Tethering/res/values-cs/strings.xml
+++ b/Tethering/res/values-cs/strings.xml
@@ -16,14 +16,14 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="tethered_notification_title" msgid="6426563586025792944">"Tethering nebo hotspot je aktivní"</string>
-    <string name="tethered_notification_message" msgid="64800879503420696">"Klepnutím zahájíte nastavení."</string>
-    <string name="disable_tether_notification_title" msgid="3004509127903564191">"Tethering je zakázán"</string>
-    <string name="disable_tether_notification_message" msgid="6717523799293901476">"O podrobnosti požádejte administrátora"</string>
-    <string name="notification_channel_tethering_status" msgid="2663463891530932727">"Stav hotspotu a tetheringu"</string>
-    <string name="no_upstream_notification_title" msgid="1204601824631788482"></string>
-    <string name="no_upstream_notification_message" msgid="8586582938243032621"></string>
-    <string name="no_upstream_notification_disable_button" msgid="8800919436924640822"></string>
-    <string name="upstream_roaming_notification_title" msgid="4772373823198997030"></string>
-    <string name="upstream_roaming_notification_message" msgid="3985577843181551650"></string>
+    <string name="tethered_notification_title" msgid="5350162111436634622">"Tethering nebo hotspot je aktivní"</string>
+    <string name="tethered_notification_message" msgid="2338023450330652098">"Klepnutím ho nastavíte."</string>
+    <string name="disable_tether_notification_title" msgid="3183576627492925522">"Tethering je zakázán"</string>
+    <string name="disable_tether_notification_message" msgid="6655882039707534929">"O podrobnosti požádejte administrátora"</string>
+    <string name="notification_channel_tethering_status" msgid="7030733422705019001">"Stav hotspotu a tetheringu"</string>
+    <string name="no_upstream_notification_title" msgid="2052743091868702475"></string>
+    <string name="no_upstream_notification_message" msgid="6932020551635470134"></string>
+    <string name="no_upstream_notification_disable_button" msgid="8836277213343697023"></string>
+    <string name="upstream_roaming_notification_title" msgid="8614262557406849762"></string>
+    <string name="upstream_roaming_notification_message" msgid="5999740876323106599"></string>
 </resources>
diff --git a/Tethering/res/values-da/strings.xml b/Tethering/res/values-da/strings.xml
index f413e70..dddf097 100644
--- a/Tethering/res/values-da/strings.xml
+++ b/Tethering/res/values-da/strings.xml
@@ -16,14 +16,14 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="tethered_notification_title" msgid="6426563586025792944">"Netdeling eller hotspot er aktivt"</string>
-    <string name="tethered_notification_message" msgid="64800879503420696">"Tryk for at konfigurere."</string>
-    <string name="disable_tether_notification_title" msgid="3004509127903564191">"Netdeling er deaktiveret"</string>
-    <string name="disable_tether_notification_message" msgid="6717523799293901476">"Kontakt din administrator for at få oplysninger"</string>
-    <string name="notification_channel_tethering_status" msgid="2663463891530932727">"Status for hotspot og netdeling"</string>
-    <string name="no_upstream_notification_title" msgid="1204601824631788482"></string>
-    <string name="no_upstream_notification_message" msgid="8586582938243032621"></string>
-    <string name="no_upstream_notification_disable_button" msgid="8800919436924640822"></string>
-    <string name="upstream_roaming_notification_title" msgid="4772373823198997030"></string>
-    <string name="upstream_roaming_notification_message" msgid="3985577843181551650"></string>
+    <string name="tethered_notification_title" msgid="5350162111436634622">"Netdeling eller hotspot er aktiveret"</string>
+    <string name="tethered_notification_message" msgid="2338023450330652098">"Tryk for at konfigurere."</string>
+    <string name="disable_tether_notification_title" msgid="3183576627492925522">"Netdeling er deaktiveret"</string>
+    <string name="disable_tether_notification_message" msgid="6655882039707534929">"Kontakt din administrator for at få oplysninger"</string>
+    <string name="notification_channel_tethering_status" msgid="7030733422705019001">"Status for hotspot og netdeling"</string>
+    <string name="no_upstream_notification_title" msgid="2052743091868702475"></string>
+    <string name="no_upstream_notification_message" msgid="6932020551635470134"></string>
+    <string name="no_upstream_notification_disable_button" msgid="8836277213343697023"></string>
+    <string name="upstream_roaming_notification_title" msgid="8614262557406849762"></string>
+    <string name="upstream_roaming_notification_message" msgid="5999740876323106599"></string>
 </resources>
diff --git a/Tethering/res/values-de/strings.xml b/Tethering/res/values-de/strings.xml
index f057d78..ab7b8c9 100644
--- a/Tethering/res/values-de/strings.xml
+++ b/Tethering/res/values-de/strings.xml
@@ -16,14 +16,14 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="tethered_notification_title" msgid="6426563586025792944">"Tethering oder Hotspot aktiv"</string>
-    <string name="tethered_notification_message" msgid="64800879503420696">"Zum Einrichten tippen."</string>
-    <string name="disable_tether_notification_title" msgid="3004509127903564191">"Tethering ist deaktiviert"</string>
-    <string name="disable_tether_notification_message" msgid="6717523799293901476">"Bitte wende dich für weitere Informationen an den Administrator"</string>
-    <string name="notification_channel_tethering_status" msgid="2663463891530932727">"Hotspot- und Tethering-Status"</string>
-    <string name="no_upstream_notification_title" msgid="1204601824631788482"></string>
-    <string name="no_upstream_notification_message" msgid="8586582938243032621"></string>
-    <string name="no_upstream_notification_disable_button" msgid="8800919436924640822"></string>
-    <string name="upstream_roaming_notification_title" msgid="4772373823198997030"></string>
-    <string name="upstream_roaming_notification_message" msgid="3985577843181551650"></string>
+    <string name="tethered_notification_title" msgid="5350162111436634622">"Tethering oder Hotspot aktiv"</string>
+    <string name="tethered_notification_message" msgid="2338023450330652098">"Zum Einrichten tippen."</string>
+    <string name="disable_tether_notification_title" msgid="3183576627492925522">"Tethering ist deaktiviert"</string>
+    <string name="disable_tether_notification_message" msgid="6655882039707534929">"Bitte wende dich für weitere Informationen an den Administrator"</string>
+    <string name="notification_channel_tethering_status" msgid="7030733422705019001">"Hotspot- und Tethering-Status"</string>
+    <string name="no_upstream_notification_title" msgid="2052743091868702475"></string>
+    <string name="no_upstream_notification_message" msgid="6932020551635470134"></string>
+    <string name="no_upstream_notification_disable_button" msgid="8836277213343697023"></string>
+    <string name="upstream_roaming_notification_title" msgid="8614262557406849762"></string>
+    <string name="upstream_roaming_notification_message" msgid="5999740876323106599"></string>
 </resources>
diff --git a/Tethering/res/values-el/strings.xml b/Tethering/res/values-el/strings.xml
index b3c986b..4ed3ec5 100644
--- a/Tethering/res/values-el/strings.xml
+++ b/Tethering/res/values-el/strings.xml
@@ -16,14 +16,14 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="tethered_notification_title" msgid="6426563586025792944">"Πρόσδεση ή σύνδεση σημείου πρόσβασης ενεργή"</string>
-    <string name="tethered_notification_message" msgid="64800879503420696">"Πατήστε για ρύθμιση."</string>
-    <string name="disable_tether_notification_title" msgid="3004509127903564191">"Η σύνδεση είναι απενεργοποιημένη"</string>
-    <string name="disable_tether_notification_message" msgid="6717523799293901476">"Επικοινωνήστε με τον διαχειριστή σας για λεπτομέρειες"</string>
-    <string name="notification_channel_tethering_status" msgid="2663463891530932727">"Κατάσταση σημείου πρόσβασης Wi-Fi και σύνδεσης"</string>
-    <string name="no_upstream_notification_title" msgid="1204601824631788482"></string>
-    <string name="no_upstream_notification_message" msgid="8586582938243032621"></string>
-    <string name="no_upstream_notification_disable_button" msgid="8800919436924640822"></string>
-    <string name="upstream_roaming_notification_title" msgid="4772373823198997030"></string>
-    <string name="upstream_roaming_notification_message" msgid="3985577843181551650"></string>
+    <string name="tethered_notification_title" msgid="5350162111436634622">"Ενεργή σύνδεση ή ενεργό σημείο πρόσβασης Wi-Fi"</string>
+    <string name="tethered_notification_message" msgid="2338023450330652098">"Πατήστε για ρύθμιση."</string>
+    <string name="disable_tether_notification_title" msgid="3183576627492925522">"Η σύνδεση είναι απενεργοποιημένη"</string>
+    <string name="disable_tether_notification_message" msgid="6655882039707534929">"Επικοινωνήστε με τον διαχειριστή για λεπτομέρειες"</string>
+    <string name="notification_channel_tethering_status" msgid="7030733422705019001">"Κατάσταση σημείου πρόσβασης Wi-Fi και σύνδεσης"</string>
+    <string name="no_upstream_notification_title" msgid="2052743091868702475"></string>
+    <string name="no_upstream_notification_message" msgid="6932020551635470134"></string>
+    <string name="no_upstream_notification_disable_button" msgid="8836277213343697023"></string>
+    <string name="upstream_roaming_notification_title" msgid="8614262557406849762"></string>
+    <string name="upstream_roaming_notification_message" msgid="5999740876323106599"></string>
 </resources>
diff --git a/Tethering/res/values-en-rAU/strings.xml b/Tethering/res/values-en-rAU/strings.xml
index 769e012..2dc7689 100644
--- a/Tethering/res/values-en-rAU/strings.xml
+++ b/Tethering/res/values-en-rAU/strings.xml
@@ -16,14 +16,14 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="tethered_notification_title" msgid="6426563586025792944">"Tethering or hotspot active"</string>
-    <string name="tethered_notification_message" msgid="64800879503420696">"Tap to set up."</string>
-    <string name="disable_tether_notification_title" msgid="3004509127903564191">"Tethering is disabled"</string>
-    <string name="disable_tether_notification_message" msgid="6717523799293901476">"Contact your admin for details"</string>
-    <string name="notification_channel_tethering_status" msgid="2663463891530932727">"Hotspot and tethering status"</string>
-    <string name="no_upstream_notification_title" msgid="1204601824631788482"></string>
-    <string name="no_upstream_notification_message" msgid="8586582938243032621"></string>
-    <string name="no_upstream_notification_disable_button" msgid="8800919436924640822"></string>
-    <string name="upstream_roaming_notification_title" msgid="4772373823198997030"></string>
-    <string name="upstream_roaming_notification_message" msgid="3985577843181551650"></string>
+    <string name="tethered_notification_title" msgid="5350162111436634622">"Tethering or hotspot active"</string>
+    <string name="tethered_notification_message" msgid="2338023450330652098">"Tap to set up."</string>
+    <string name="disable_tether_notification_title" msgid="3183576627492925522">"Tethering is disabled"</string>
+    <string name="disable_tether_notification_message" msgid="6655882039707534929">"Contact your admin for details"</string>
+    <string name="notification_channel_tethering_status" msgid="7030733422705019001">"Hotspot and tethering status"</string>
+    <string name="no_upstream_notification_title" msgid="2052743091868702475"></string>
+    <string name="no_upstream_notification_message" msgid="6932020551635470134"></string>
+    <string name="no_upstream_notification_disable_button" msgid="8836277213343697023"></string>
+    <string name="upstream_roaming_notification_title" msgid="8614262557406849762"></string>
+    <string name="upstream_roaming_notification_message" msgid="5999740876323106599"></string>
 </resources>
diff --git a/Tethering/res/values-en-rCA/strings.xml b/Tethering/res/values-en-rCA/strings.xml
index 769e012..066cd82 100644
--- a/Tethering/res/values-en-rCA/strings.xml
+++ b/Tethering/res/values-en-rCA/strings.xml
@@ -16,14 +16,14 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="tethered_notification_title" msgid="6426563586025792944">"Tethering or hotspot active"</string>
-    <string name="tethered_notification_message" msgid="64800879503420696">"Tap to set up."</string>
-    <string name="disable_tether_notification_title" msgid="3004509127903564191">"Tethering is disabled"</string>
-    <string name="disable_tether_notification_message" msgid="6717523799293901476">"Contact your admin for details"</string>
-    <string name="notification_channel_tethering_status" msgid="2663463891530932727">"Hotspot and tethering status"</string>
-    <string name="no_upstream_notification_title" msgid="1204601824631788482"></string>
-    <string name="no_upstream_notification_message" msgid="8586582938243032621"></string>
-    <string name="no_upstream_notification_disable_button" msgid="8800919436924640822"></string>
-    <string name="upstream_roaming_notification_title" msgid="4772373823198997030"></string>
-    <string name="upstream_roaming_notification_message" msgid="3985577843181551650"></string>
+    <string name="tethered_notification_title" msgid="5350162111436634622">"Tethering or hotspot active"</string>
+    <string name="tethered_notification_message" msgid="2338023450330652098">"Tap to set up."</string>
+    <string name="disable_tether_notification_title" msgid="3183576627492925522">"Tethering is disabled"</string>
+    <string name="disable_tether_notification_message" msgid="6655882039707534929">"Contact your admin for details"</string>
+    <string name="notification_channel_tethering_status" msgid="7030733422705019001">"Hotspot &amp; tethering status"</string>
+    <string name="no_upstream_notification_title" msgid="2052743091868702475"></string>
+    <string name="no_upstream_notification_message" msgid="6932020551635470134"></string>
+    <string name="no_upstream_notification_disable_button" msgid="8836277213343697023"></string>
+    <string name="upstream_roaming_notification_title" msgid="8614262557406849762"></string>
+    <string name="upstream_roaming_notification_message" msgid="5999740876323106599"></string>
 </resources>
diff --git a/Tethering/res/values-en-rGB/strings.xml b/Tethering/res/values-en-rGB/strings.xml
index 769e012..2dc7689 100644
--- a/Tethering/res/values-en-rGB/strings.xml
+++ b/Tethering/res/values-en-rGB/strings.xml
@@ -16,14 +16,14 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="tethered_notification_title" msgid="6426563586025792944">"Tethering or hotspot active"</string>
-    <string name="tethered_notification_message" msgid="64800879503420696">"Tap to set up."</string>
-    <string name="disable_tether_notification_title" msgid="3004509127903564191">"Tethering is disabled"</string>
-    <string name="disable_tether_notification_message" msgid="6717523799293901476">"Contact your admin for details"</string>
-    <string name="notification_channel_tethering_status" msgid="2663463891530932727">"Hotspot and tethering status"</string>
-    <string name="no_upstream_notification_title" msgid="1204601824631788482"></string>
-    <string name="no_upstream_notification_message" msgid="8586582938243032621"></string>
-    <string name="no_upstream_notification_disable_button" msgid="8800919436924640822"></string>
-    <string name="upstream_roaming_notification_title" msgid="4772373823198997030"></string>
-    <string name="upstream_roaming_notification_message" msgid="3985577843181551650"></string>
+    <string name="tethered_notification_title" msgid="5350162111436634622">"Tethering or hotspot active"</string>
+    <string name="tethered_notification_message" msgid="2338023450330652098">"Tap to set up."</string>
+    <string name="disable_tether_notification_title" msgid="3183576627492925522">"Tethering is disabled"</string>
+    <string name="disable_tether_notification_message" msgid="6655882039707534929">"Contact your admin for details"</string>
+    <string name="notification_channel_tethering_status" msgid="7030733422705019001">"Hotspot and tethering status"</string>
+    <string name="no_upstream_notification_title" msgid="2052743091868702475"></string>
+    <string name="no_upstream_notification_message" msgid="6932020551635470134"></string>
+    <string name="no_upstream_notification_disable_button" msgid="8836277213343697023"></string>
+    <string name="upstream_roaming_notification_title" msgid="8614262557406849762"></string>
+    <string name="upstream_roaming_notification_message" msgid="5999740876323106599"></string>
 </resources>
diff --git a/Tethering/res/values-en-rIN/strings.xml b/Tethering/res/values-en-rIN/strings.xml
index 769e012..2dc7689 100644
--- a/Tethering/res/values-en-rIN/strings.xml
+++ b/Tethering/res/values-en-rIN/strings.xml
@@ -16,14 +16,14 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="tethered_notification_title" msgid="6426563586025792944">"Tethering or hotspot active"</string>
-    <string name="tethered_notification_message" msgid="64800879503420696">"Tap to set up."</string>
-    <string name="disable_tether_notification_title" msgid="3004509127903564191">"Tethering is disabled"</string>
-    <string name="disable_tether_notification_message" msgid="6717523799293901476">"Contact your admin for details"</string>
-    <string name="notification_channel_tethering_status" msgid="2663463891530932727">"Hotspot and tethering status"</string>
-    <string name="no_upstream_notification_title" msgid="1204601824631788482"></string>
-    <string name="no_upstream_notification_message" msgid="8586582938243032621"></string>
-    <string name="no_upstream_notification_disable_button" msgid="8800919436924640822"></string>
-    <string name="upstream_roaming_notification_title" msgid="4772373823198997030"></string>
-    <string name="upstream_roaming_notification_message" msgid="3985577843181551650"></string>
+    <string name="tethered_notification_title" msgid="5350162111436634622">"Tethering or hotspot active"</string>
+    <string name="tethered_notification_message" msgid="2338023450330652098">"Tap to set up."</string>
+    <string name="disable_tether_notification_title" msgid="3183576627492925522">"Tethering is disabled"</string>
+    <string name="disable_tether_notification_message" msgid="6655882039707534929">"Contact your admin for details"</string>
+    <string name="notification_channel_tethering_status" msgid="7030733422705019001">"Hotspot and tethering status"</string>
+    <string name="no_upstream_notification_title" msgid="2052743091868702475"></string>
+    <string name="no_upstream_notification_message" msgid="6932020551635470134"></string>
+    <string name="no_upstream_notification_disable_button" msgid="8836277213343697023"></string>
+    <string name="upstream_roaming_notification_title" msgid="8614262557406849762"></string>
+    <string name="upstream_roaming_notification_message" msgid="5999740876323106599"></string>
 </resources>
diff --git a/Tethering/res/values-en-rXC/strings.xml b/Tethering/res/values-en-rXC/strings.xml
index f1674be..a83caac 100644
--- a/Tethering/res/values-en-rXC/strings.xml
+++ b/Tethering/res/values-en-rXC/strings.xml
@@ -16,14 +16,14 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="tethered_notification_title" msgid="6426563586025792944">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‎‎‏‎‎‏‎‏‏‏‏‏‎‏‏‏‏‎‏‏‎‎‎‏‎‎‎‎‎‏‏‎‏‏‏‏‎‎‎‎‏‏‏‎‎‏‎‎‏‎‏‏‎‏‏‎‎‎‎‎Tethering or hotspot active‎‏‎‎‏‎"</string>
-    <string name="tethered_notification_message" msgid="64800879503420696">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‎‎‎‏‏‏‎‎‏‏‎‎‎‏‏‏‎‎‎‎‎‎‎‏‏‏‎‎‏‏‏‏‎‏‏‏‏‏‎‏‏‏‏‎‎‏‎‏‏‎‏‎‎‎‏‏‎‎‎‎Tap to set up.‎‏‎‎‏‎"</string>
-    <string name="disable_tether_notification_title" msgid="3004509127903564191">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‎‎‏‏‎‏‏‎‎‏‎‎‎‏‎‏‎‎‏‎‎‏‎‎‎‏‎‎‎‎‏‏‏‎‏‎‎‎‏‏‏‎‎‎‏‎‏‎‎‎‏‏‎‎‏‏‏‏‏‎Tethering is disabled‎‏‎‎‏‎"</string>
-    <string name="disable_tether_notification_message" msgid="6717523799293901476">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‏‎‏‎‎‏‏‏‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‏‎‏‏‏‏‏‎‎‏‏‎‎‏‎‎‏‎‏‎‎‏‏‏‎‏‎‏‎‏‎‎‏‎‎‎Contact your admin for details‎‏‎‎‏‎"</string>
-    <string name="notification_channel_tethering_status" msgid="2663463891530932727">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‏‎‎‏‏‏‏‎‏‏‎‏‎‎‎‎‏‏‎‎‏‎‎‏‏‎‎‎‎‏‏‎‏‏‎‏‏‎‎‎‏‏‏‏‏‏‎‎‎‎‏‏‏‏‏‎‏‏‏‎Hotspot &amp; tethering status‎‏‎‎‏‎"</string>
-    <string name="no_upstream_notification_title" msgid="1204601824631788482"></string>
-    <string name="no_upstream_notification_message" msgid="8586582938243032621"></string>
-    <string name="no_upstream_notification_disable_button" msgid="8800919436924640822"></string>
-    <string name="upstream_roaming_notification_title" msgid="4772373823198997030"></string>
-    <string name="upstream_roaming_notification_message" msgid="3985577843181551650"></string>
+    <string name="tethered_notification_title" msgid="5350162111436634622">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‎‏‎‎‎‏‏‏‏‏‏‏‎‎‏‏‎‎‎‎‎‎‏‏‎‎‎‎‎‎‎‏‏‏‎‏‏‏‎‏‏‏‏‎‎‏‏‏‎‎‏‏‏‏‏‏‏‏‎‎Tethering or hotspot active‎‏‎‎‏‎"</string>
+    <string name="tethered_notification_message" msgid="2338023450330652098">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‎‎‎‎‏‏‏‎‎‏‎‎‏‎‏‎‎‏‏‏‏‏‏‎‎‎‎‏‏‎‏‎‎‎‏‎‏‏‏‏‏‎‏‏‏‎‏‏‏‎‏‏‏‎‎‎‎‏‎‎Tap to set up.‎‏‎‎‏‎"</string>
+    <string name="disable_tether_notification_title" msgid="3183576627492925522">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‏‎‎‎‎‏‎‏‏‏‎‎‏‎‏‎‏‏‎‎‎‎‏‎‎‏‏‎‏‎‎‏‏‎‎‏‏‏‏‎‎‏‏‏‎‏‏‎‎‎‎‎‏‎‏‎‎‏‎‎Tethering is disabled‎‏‎‎‏‎"</string>
+    <string name="disable_tether_notification_message" msgid="6655882039707534929">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‏‎‎‎‏‎‏‏‏‏‎‎‏‏‏‎‎‎‏‎‏‏‏‎‎‏‏‎‏‏‎‎‎‏‏‏‎‏‎‏‎‏‏‏‎‎‎‎‏‏‎‎‏‎‏‎‎‎‏‎Contact your admin for details‎‏‎‎‏‎"</string>
+    <string name="notification_channel_tethering_status" msgid="7030733422705019001">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‎‎‏‏‎‎‏‎‎‏‎‎‎‏‎‏‏‏‎‏‏‎‎‏‏‎‎‎‎‏‎‏‎‏‎‏‏‎‎‏‏‎‎‏‎‎‎‏‎‎‎‎‏‏‏‏‎‎‏‎Hotspot &amp; tethering status‎‏‎‎‏‎"</string>
+    <string name="no_upstream_notification_title" msgid="2052743091868702475"></string>
+    <string name="no_upstream_notification_message" msgid="6932020551635470134"></string>
+    <string name="no_upstream_notification_disable_button" msgid="8836277213343697023"></string>
+    <string name="upstream_roaming_notification_title" msgid="8614262557406849762"></string>
+    <string name="upstream_roaming_notification_message" msgid="5999740876323106599"></string>
 </resources>
diff --git a/Tethering/res/values-es-rUS/strings.xml b/Tethering/res/values-es-rUS/strings.xml
index 63689f4..69bd4e7 100644
--- a/Tethering/res/values-es-rUS/strings.xml
+++ b/Tethering/res/values-es-rUS/strings.xml
@@ -16,14 +16,14 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="tethered_notification_title" msgid="6426563586025792944">"Conexión a red o hotspot conectados"</string>
-    <string name="tethered_notification_message" msgid="64800879503420696">"Presiona para configurar esta opción."</string>
-    <string name="disable_tether_notification_title" msgid="3004509127903564191">"Se inhabilitó la conexión mediante dispositivo portátil"</string>
-    <string name="disable_tether_notification_message" msgid="6717523799293901476">"Para obtener más información, comunícate con el administrador"</string>
-    <string name="notification_channel_tethering_status" msgid="2663463891530932727">"Estado del hotspot y la conexión mediante dispositivo portátil"</string>
-    <string name="no_upstream_notification_title" msgid="1204601824631788482"></string>
-    <string name="no_upstream_notification_message" msgid="8586582938243032621"></string>
-    <string name="no_upstream_notification_disable_button" msgid="8800919436924640822"></string>
-    <string name="upstream_roaming_notification_title" msgid="4772373823198997030"></string>
-    <string name="upstream_roaming_notification_message" msgid="3985577843181551650"></string>
+    <string name="tethered_notification_title" msgid="5350162111436634622">"Conexión mediante dispositivo móvil o hotspot activos"</string>
+    <string name="tethered_notification_message" msgid="2338023450330652098">"Presiona para configurar esta opción."</string>
+    <string name="disable_tether_notification_title" msgid="3183576627492925522">"Se inhabilitó la conexión mediante dispositivo móvil"</string>
+    <string name="disable_tether_notification_message" msgid="6655882039707534929">"Para obtener más información, comunícate con el administrador"</string>
+    <string name="notification_channel_tethering_status" msgid="7030733422705019001">"Estado del hotspot y de la conexión mediante dispositivo portátil"</string>
+    <string name="no_upstream_notification_title" msgid="2052743091868702475"></string>
+    <string name="no_upstream_notification_message" msgid="6932020551635470134"></string>
+    <string name="no_upstream_notification_disable_button" msgid="8836277213343697023"></string>
+    <string name="upstream_roaming_notification_title" msgid="8614262557406849762"></string>
+    <string name="upstream_roaming_notification_message" msgid="5999740876323106599"></string>
 </resources>
diff --git a/Tethering/res/values-es/strings.xml b/Tethering/res/values-es/strings.xml
index 9a34ed5..6bef387 100644
--- a/Tethering/res/values-es/strings.xml
+++ b/Tethering/res/values-es/strings.xml
@@ -16,14 +16,14 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="tethered_notification_title" msgid="6426563586025792944">"Conexión compartida o punto de acceso activos"</string>
-    <string name="tethered_notification_message" msgid="64800879503420696">"Toca para configurar."</string>
-    <string name="disable_tether_notification_title" msgid="3004509127903564191">"La conexión compartida está inhabilitada"</string>
-    <string name="disable_tether_notification_message" msgid="6717523799293901476">"Solicita más información a tu administrador"</string>
-    <string name="notification_channel_tethering_status" msgid="2663463891530932727">"Estado del punto de acceso y de la conexión compartida"</string>
-    <string name="no_upstream_notification_title" msgid="1204601824631788482"></string>
-    <string name="no_upstream_notification_message" msgid="8586582938243032621"></string>
-    <string name="no_upstream_notification_disable_button" msgid="8800919436924640822"></string>
-    <string name="upstream_roaming_notification_title" msgid="4772373823198997030"></string>
-    <string name="upstream_roaming_notification_message" msgid="3985577843181551650"></string>
+    <string name="tethered_notification_title" msgid="5350162111436634622">"Conexión compartida o punto de acceso activos"</string>
+    <string name="tethered_notification_message" msgid="2338023450330652098">"Toca para configurarla."</string>
+    <string name="disable_tether_notification_title" msgid="3183576627492925522">"La conexión compartida está inhabilitada"</string>
+    <string name="disable_tether_notification_message" msgid="6655882039707534929">"Ponte en contacto con el administrador para obtener más información"</string>
+    <string name="notification_channel_tethering_status" msgid="7030733422705019001">"Estado del punto de acceso y la conexión compartida"</string>
+    <string name="no_upstream_notification_title" msgid="2052743091868702475"></string>
+    <string name="no_upstream_notification_message" msgid="6932020551635470134"></string>
+    <string name="no_upstream_notification_disable_button" msgid="8836277213343697023"></string>
+    <string name="upstream_roaming_notification_title" msgid="8614262557406849762"></string>
+    <string name="upstream_roaming_notification_message" msgid="5999740876323106599"></string>
 </resources>
diff --git a/Tethering/res/values-et/strings.xml b/Tethering/res/values-et/strings.xml
index 0970341..68088ce 100644
--- a/Tethering/res/values-et/strings.xml
+++ b/Tethering/res/values-et/strings.xml
@@ -16,14 +16,14 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="tethered_notification_title" msgid="6426563586025792944">"Jagamine või kuumkoht on aktiivne"</string>
-    <string name="tethered_notification_message" msgid="64800879503420696">"Puudutage seadistamiseks."</string>
-    <string name="disable_tether_notification_title" msgid="3004509127903564191">"Jagamine on keelatud"</string>
-    <string name="disable_tether_notification_message" msgid="6717523799293901476">"Lisateabe saamiseks võtke ühendust oma administraatoriga"</string>
-    <string name="notification_channel_tethering_status" msgid="2663463891530932727">"Kuumkoha ja jagamise olek"</string>
-    <string name="no_upstream_notification_title" msgid="1204601824631788482"></string>
-    <string name="no_upstream_notification_message" msgid="8586582938243032621"></string>
-    <string name="no_upstream_notification_disable_button" msgid="8800919436924640822"></string>
-    <string name="upstream_roaming_notification_title" msgid="4772373823198997030"></string>
-    <string name="upstream_roaming_notification_message" msgid="3985577843181551650"></string>
+    <string name="tethered_notification_title" msgid="5350162111436634622">"Jagamine või kuumkoht on aktiivne"</string>
+    <string name="tethered_notification_message" msgid="2338023450330652098">"Puudutage seadistamiseks."</string>
+    <string name="disable_tether_notification_title" msgid="3183576627492925522">"Jagamine on keelatud"</string>
+    <string name="disable_tether_notification_message" msgid="6655882039707534929">"Lisateabe saamiseks võtke ühendust oma administraatoriga"</string>
+    <string name="notification_channel_tethering_status" msgid="7030733422705019001">"Kuumkoha ja jagamise olek"</string>
+    <string name="no_upstream_notification_title" msgid="2052743091868702475"></string>
+    <string name="no_upstream_notification_message" msgid="6932020551635470134"></string>
+    <string name="no_upstream_notification_disable_button" msgid="8836277213343697023"></string>
+    <string name="upstream_roaming_notification_title" msgid="8614262557406849762"></string>
+    <string name="upstream_roaming_notification_message" msgid="5999740876323106599"></string>
 </resources>
diff --git a/Tethering/res/values-eu/strings.xml b/Tethering/res/values-eu/strings.xml
index 632019e..37b35a8 100644
--- a/Tethering/res/values-eu/strings.xml
+++ b/Tethering/res/values-eu/strings.xml
@@ -16,14 +16,14 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="tethered_notification_title" msgid="6426563586025792944">"Konexioa partekatzea edo wifi-gunea aktibo dago"</string>
-    <string name="tethered_notification_message" msgid="64800879503420696">"Sakatu konfiguratzeko."</string>
-    <string name="disable_tether_notification_title" msgid="3004509127903564191">"Desgaituta dago konexioa partekatzeko aukera"</string>
-    <string name="disable_tether_notification_message" msgid="6717523799293901476">"Xehetasunak lortzeko, jarri administratzailearekin harremanetan"</string>
-    <string name="notification_channel_tethering_status" msgid="2663463891530932727">"Wifi-gunearen eta konexioa partekatzeko eginbidearen egoera"</string>
-    <string name="no_upstream_notification_title" msgid="1204601824631788482"></string>
-    <string name="no_upstream_notification_message" msgid="8586582938243032621"></string>
-    <string name="no_upstream_notification_disable_button" msgid="8800919436924640822"></string>
-    <string name="upstream_roaming_notification_title" msgid="4772373823198997030"></string>
-    <string name="upstream_roaming_notification_message" msgid="3985577843181551650"></string>
+    <string name="tethered_notification_title" msgid="5350162111436634622">"Konexioa partekatzeko aukera edo wifi-gunea aktibo dago"</string>
+    <string name="tethered_notification_message" msgid="2338023450330652098">"Konfiguratzeko, sakatu hau."</string>
+    <string name="disable_tether_notification_title" msgid="3183576627492925522">"Konexioa partekatzeko aukera desgaituta dago"</string>
+    <string name="disable_tether_notification_message" msgid="6655882039707534929">"Xehetasunak lortzeko, jarri administratzailearekin harremanetan"</string>
+    <string name="notification_channel_tethering_status" msgid="7030733422705019001">"Wifi-gunearen eta konexioa partekatzeko aukeraren egoera"</string>
+    <string name="no_upstream_notification_title" msgid="2052743091868702475"></string>
+    <string name="no_upstream_notification_message" msgid="6932020551635470134"></string>
+    <string name="no_upstream_notification_disable_button" msgid="8836277213343697023"></string>
+    <string name="upstream_roaming_notification_title" msgid="8614262557406849762"></string>
+    <string name="upstream_roaming_notification_message" msgid="5999740876323106599"></string>
 </resources>
diff --git a/Tethering/res/values-fa/strings.xml b/Tethering/res/values-fa/strings.xml
index 2e21c85..d7f2543 100644
--- a/Tethering/res/values-fa/strings.xml
+++ b/Tethering/res/values-fa/strings.xml
@@ -16,14 +16,14 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="tethered_notification_title" msgid="6426563586025792944">"اشتراک‌گذاری اینترنت یا نقطه اتصال فعال"</string>
-    <string name="tethered_notification_message" msgid="64800879503420696">"برای راه‌اندازی ضربه بزنید."</string>
-    <string name="disable_tether_notification_title" msgid="3004509127903564191">"اشتراک‌گذاری اینترنت غیرفعال است"</string>
-    <string name="disable_tether_notification_message" msgid="6717523799293901476">"برای جزئیات، با سرپرستتان تماس بگیرید"</string>
-    <string name="notification_channel_tethering_status" msgid="2663463891530932727">"وضعیت نقطه اتصال و اشتراک‌گذاری اینترنت"</string>
-    <string name="no_upstream_notification_title" msgid="1204601824631788482"></string>
-    <string name="no_upstream_notification_message" msgid="8586582938243032621"></string>
-    <string name="no_upstream_notification_disable_button" msgid="8800919436924640822"></string>
-    <string name="upstream_roaming_notification_title" msgid="4772373823198997030"></string>
-    <string name="upstream_roaming_notification_message" msgid="3985577843181551650"></string>
+    <string name="tethered_notification_title" msgid="5350162111436634622">"اشتراک‌گذاری اینترنت یا نقطه اتصال فعال است"</string>
+    <string name="tethered_notification_message" msgid="2338023450330652098">"برای راه‌اندازی، ضربه بزنید."</string>
+    <string name="disable_tether_notification_title" msgid="3183576627492925522">"اشتراک‌گذاری اینترنت غیرفعال است"</string>
+    <string name="disable_tether_notification_message" msgid="6655882039707534929">"برای جزئیات، با سرپرستتان تماس بگیرید"</string>
+    <string name="notification_channel_tethering_status" msgid="7030733422705019001">"وضعیت نقطه اتصال و اشتراک‌گذاری اینترنت"</string>
+    <string name="no_upstream_notification_title" msgid="2052743091868702475"></string>
+    <string name="no_upstream_notification_message" msgid="6932020551635470134"></string>
+    <string name="no_upstream_notification_disable_button" msgid="8836277213343697023"></string>
+    <string name="upstream_roaming_notification_title" msgid="8614262557406849762"></string>
+    <string name="upstream_roaming_notification_message" msgid="5999740876323106599"></string>
 </resources>
diff --git a/Tethering/res/values-fi/strings.xml b/Tethering/res/values-fi/strings.xml
index 413db3f..4bf09fec 100644
--- a/Tethering/res/values-fi/strings.xml
+++ b/Tethering/res/values-fi/strings.xml
@@ -16,14 +16,14 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="tethered_notification_title" msgid="6426563586025792944">"Yhteyden jakaminen tai hotspot käytössä"</string>
-    <string name="tethered_notification_message" msgid="64800879503420696">"Ota käyttöön napauttamalla."</string>
-    <string name="disable_tether_notification_title" msgid="3004509127903564191">"Yhteyden jakaminen on poistettu käytöstä"</string>
-    <string name="disable_tether_notification_message" msgid="6717523799293901476">"Pyydä lisätietoja järjestelmänvalvojalta"</string>
-    <string name="notification_channel_tethering_status" msgid="2663463891530932727">"Hotspotin ja yhteyden jakamisen tila"</string>
-    <string name="no_upstream_notification_title" msgid="1204601824631788482"></string>
-    <string name="no_upstream_notification_message" msgid="8586582938243032621"></string>
-    <string name="no_upstream_notification_disable_button" msgid="8800919436924640822"></string>
-    <string name="upstream_roaming_notification_title" msgid="4772373823198997030"></string>
-    <string name="upstream_roaming_notification_message" msgid="3985577843181551650"></string>
+    <string name="tethered_notification_title" msgid="5350162111436634622">"Puhelimen käyttäminen modeemina tai hotspot käytössä"</string>
+    <string name="tethered_notification_message" msgid="2338023450330652098">"Ota käyttöön napauttamalla."</string>
+    <string name="disable_tether_notification_title" msgid="3183576627492925522">"Puhelimen käyttäminen modeemina on poistettu käytöstä"</string>
+    <string name="disable_tether_notification_message" msgid="6655882039707534929">"Pyydä lisätietoa järjestelmänvalvojalta"</string>
+    <string name="notification_channel_tethering_status" msgid="7030733422705019001">"Hotspotin ja modeemina toimivan puhelimen tila"</string>
+    <string name="no_upstream_notification_title" msgid="2052743091868702475"></string>
+    <string name="no_upstream_notification_message" msgid="6932020551635470134"></string>
+    <string name="no_upstream_notification_disable_button" msgid="8836277213343697023"></string>
+    <string name="upstream_roaming_notification_title" msgid="8614262557406849762"></string>
+    <string name="upstream_roaming_notification_message" msgid="5999740876323106599"></string>
 </resources>
diff --git a/Tethering/res/values-fr-rCA/strings.xml b/Tethering/res/values-fr-rCA/strings.xml
index eb2e4ba..66b4684 100644
--- a/Tethering/res/values-fr-rCA/strings.xml
+++ b/Tethering/res/values-fr-rCA/strings.xml
@@ -16,14 +16,14 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="tethered_notification_title" msgid="6426563586025792944">"Partage de connexion ou point d\'accès sans fil activé"</string>
-    <string name="tethered_notification_message" msgid="64800879503420696">"Touchez pour configurer."</string>
-    <string name="disable_tether_notification_title" msgid="3004509127903564191">"Le partage de connexion est désactivé"</string>
-    <string name="disable_tether_notification_message" msgid="6717523799293901476">"Communiquez avec votre administrateur pour obtenir plus de détails"</string>
-    <string name="notification_channel_tethering_status" msgid="2663463891530932727">"Point d\'accès et partage de connexion"</string>
-    <string name="no_upstream_notification_title" msgid="1204601824631788482"></string>
-    <string name="no_upstream_notification_message" msgid="8586582938243032621"></string>
-    <string name="no_upstream_notification_disable_button" msgid="8800919436924640822"></string>
-    <string name="upstream_roaming_notification_title" msgid="4772373823198997030"></string>
-    <string name="upstream_roaming_notification_message" msgid="3985577843181551650"></string>
+    <string name="tethered_notification_title" msgid="5350162111436634622">"Partage de connexion ou point d\'accès sans fil activé"</string>
+    <string name="tethered_notification_message" msgid="2338023450330652098">"Touchez pour configurer."</string>
+    <string name="disable_tether_notification_title" msgid="3183576627492925522">"Le partage de connexion est désactivé"</string>
+    <string name="disable_tether_notification_message" msgid="6655882039707534929">"Communiquez avec votre administrateur pour obtenir plus de détails"</string>
+    <string name="notification_channel_tethering_status" msgid="7030733422705019001">"État du point d\'accès sans fil et du partage de connexion"</string>
+    <string name="no_upstream_notification_title" msgid="2052743091868702475"></string>
+    <string name="no_upstream_notification_message" msgid="6932020551635470134"></string>
+    <string name="no_upstream_notification_disable_button" msgid="8836277213343697023"></string>
+    <string name="upstream_roaming_notification_title" msgid="8614262557406849762"></string>
+    <string name="upstream_roaming_notification_message" msgid="5999740876323106599"></string>
 </resources>
diff --git a/Tethering/res/values-fr/strings.xml b/Tethering/res/values-fr/strings.xml
index 22259c5..9440d95 100644
--- a/Tethering/res/values-fr/strings.xml
+++ b/Tethering/res/values-fr/strings.xml
@@ -16,14 +16,14 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="tethered_notification_title" msgid="6426563586025792944">"Partage de connexion ou point d\'accès activé"</string>
-    <string name="tethered_notification_message" msgid="64800879503420696">"Appuyez pour effectuer la configuration."</string>
-    <string name="disable_tether_notification_title" msgid="3004509127903564191">"Le partage de connexion est désactivé"</string>
-    <string name="disable_tether_notification_message" msgid="6717523799293901476">"Pour en savoir plus, contactez votre administrateur"</string>
-    <string name="notification_channel_tethering_status" msgid="2663463891530932727">"État du point d\'accès et du partage de connexion"</string>
-    <string name="no_upstream_notification_title" msgid="1204601824631788482"></string>
-    <string name="no_upstream_notification_message" msgid="8586582938243032621"></string>
-    <string name="no_upstream_notification_disable_button" msgid="8800919436924640822"></string>
-    <string name="upstream_roaming_notification_title" msgid="4772373823198997030"></string>
-    <string name="upstream_roaming_notification_message" msgid="3985577843181551650"></string>
+    <string name="tethered_notification_title" msgid="5350162111436634622">"Partage de connexion ou point d\'accès activé"</string>
+    <string name="tethered_notification_message" msgid="2338023450330652098">"Appuyez pour configurer."</string>
+    <string name="disable_tether_notification_title" msgid="3183576627492925522">"Le partage de connexion est désactivé"</string>
+    <string name="disable_tether_notification_message" msgid="6655882039707534929">"Pour en savoir plus, contactez votre administrateur"</string>
+    <string name="notification_channel_tethering_status" msgid="7030733422705019001">"État du point d\'accès et du partage de connexion"</string>
+    <string name="no_upstream_notification_title" msgid="2052743091868702475"></string>
+    <string name="no_upstream_notification_message" msgid="6932020551635470134"></string>
+    <string name="no_upstream_notification_disable_button" msgid="8836277213343697023"></string>
+    <string name="upstream_roaming_notification_title" msgid="8614262557406849762"></string>
+    <string name="upstream_roaming_notification_message" msgid="5999740876323106599"></string>
 </resources>
diff --git a/Tethering/res/values-gl/strings.xml b/Tethering/res/values-gl/strings.xml
index ded82fc..74bb7f2 100644
--- a/Tethering/res/values-gl/strings.xml
+++ b/Tethering/res/values-gl/strings.xml
@@ -16,14 +16,14 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="tethered_notification_title" msgid="6426563586025792944">"Conexión compartida ou zona wifi activada"</string>
-    <string name="tethered_notification_message" msgid="64800879503420696">"Toca para configurar."</string>
-    <string name="disable_tether_notification_title" msgid="3004509127903564191">"A conexión compartida está desactivada"</string>
-    <string name="disable_tether_notification_message" msgid="6717523799293901476">"Contacta co administrador para obter información"</string>
-    <string name="notification_channel_tethering_status" msgid="2663463891530932727">"Estado da zona wifi e da conexión compartida"</string>
-    <string name="no_upstream_notification_title" msgid="1204601824631788482"></string>
-    <string name="no_upstream_notification_message" msgid="8586582938243032621"></string>
-    <string name="no_upstream_notification_disable_button" msgid="8800919436924640822"></string>
-    <string name="upstream_roaming_notification_title" msgid="4772373823198997030"></string>
-    <string name="upstream_roaming_notification_message" msgid="3985577843181551650"></string>
+    <string name="tethered_notification_title" msgid="5350162111436634622">"Conexión compartida ou zona wifi activada"</string>
+    <string name="tethered_notification_message" msgid="2338023450330652098">"Toca para configurar."</string>
+    <string name="disable_tether_notification_title" msgid="3183576627492925522">"A conexión compartida está desactivada"</string>
+    <string name="disable_tether_notification_message" msgid="6655882039707534929">"Contacta co administrador para obter información"</string>
+    <string name="notification_channel_tethering_status" msgid="7030733422705019001">"Estado da zona wifi e da conexión compartida"</string>
+    <string name="no_upstream_notification_title" msgid="2052743091868702475"></string>
+    <string name="no_upstream_notification_message" msgid="6932020551635470134"></string>
+    <string name="no_upstream_notification_disable_button" msgid="8836277213343697023"></string>
+    <string name="upstream_roaming_notification_title" msgid="8614262557406849762"></string>
+    <string name="upstream_roaming_notification_message" msgid="5999740876323106599"></string>
 </resources>
diff --git a/Tethering/res/values-gu/strings.xml b/Tethering/res/values-gu/strings.xml
index 7cbbc2d..c463499 100644
--- a/Tethering/res/values-gu/strings.xml
+++ b/Tethering/res/values-gu/strings.xml
@@ -16,14 +16,14 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="tethered_notification_title" msgid="6426563586025792944">"ઇન્ટરનેટ શેર કરવાની સુવિધા અથવા હૉટસ્પૉટ સક્રિય છે"</string>
-    <string name="tethered_notification_message" msgid="64800879503420696">"સેટઅપ કરવા માટે ટૅપ કરો."</string>
-    <string name="disable_tether_notification_title" msgid="3004509127903564191">"ઇન્ટરનેટ શેર કરવાની સુવિધા બંધ કરી છે"</string>
-    <string name="disable_tether_notification_message" msgid="6717523799293901476">"વિગતો માટે તમારા વ્યવસ્થાપકનો સંપર્ક કરો"</string>
-    <string name="notification_channel_tethering_status" msgid="2663463891530932727">"હૉટસ્પૉટ અને ઇન્ટરનેટ શેર કરવાની સુવિધાનું સ્ટેટસ"</string>
-    <string name="no_upstream_notification_title" msgid="1204601824631788482"></string>
-    <string name="no_upstream_notification_message" msgid="8586582938243032621"></string>
-    <string name="no_upstream_notification_disable_button" msgid="8800919436924640822"></string>
-    <string name="upstream_roaming_notification_title" msgid="4772373823198997030"></string>
-    <string name="upstream_roaming_notification_message" msgid="3985577843181551650"></string>
+    <string name="tethered_notification_title" msgid="5350162111436634622">"ઇન્ટરનેટ શેર કરવાની સુવિધા અથવા હૉટસ્પૉટ સક્રિય છે"</string>
+    <string name="tethered_notification_message" msgid="2338023450330652098">"સેટઅપ કરવા માટે ટૅપ કરો."</string>
+    <string name="disable_tether_notification_title" msgid="3183576627492925522">"ઇન્ટરનેટ શેર કરવાની સુવિધા બંધ કરી છે"</string>
+    <string name="disable_tether_notification_message" msgid="6655882039707534929">"વિગતો માટે તમારા ઍડમિનનો સંપર્ક કરો"</string>
+    <string name="notification_channel_tethering_status" msgid="7030733422705019001">"હૉટસ્પૉટ અને ઇન્ટરનેટ શેર કરવાની સુવિધાનું સ્ટેટસ"</string>
+    <string name="no_upstream_notification_title" msgid="2052743091868702475"></string>
+    <string name="no_upstream_notification_message" msgid="6932020551635470134"></string>
+    <string name="no_upstream_notification_disable_button" msgid="8836277213343697023"></string>
+    <string name="upstream_roaming_notification_title" msgid="8614262557406849762"></string>
+    <string name="upstream_roaming_notification_message" msgid="5999740876323106599"></string>
 </resources>
diff --git a/Tethering/res/values-hi/strings.xml b/Tethering/res/values-hi/strings.xml
index 08af81b..12f7961 100644
--- a/Tethering/res/values-hi/strings.xml
+++ b/Tethering/res/values-hi/strings.xml
@@ -16,14 +16,14 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="tethered_notification_title" msgid="6426563586025792944">"टेदरिंग या हॉटस्पॉट चालू है"</string>
-    <string name="tethered_notification_message" msgid="64800879503420696">"सेट अप करने के लिए टैप करें."</string>
-    <string name="disable_tether_notification_title" msgid="3004509127903564191">"टेदरिंग बंद है"</string>
-    <string name="disable_tether_notification_message" msgid="6717523799293901476">"जानकारी के लिए अपने एडमिन से संपर्क करें"</string>
-    <string name="notification_channel_tethering_status" msgid="2663463891530932727">"हॉटस्पॉट और टेदरिंग की स्थिति"</string>
-    <string name="no_upstream_notification_title" msgid="1204601824631788482"></string>
-    <string name="no_upstream_notification_message" msgid="8586582938243032621"></string>
-    <string name="no_upstream_notification_disable_button" msgid="8800919436924640822"></string>
-    <string name="upstream_roaming_notification_title" msgid="4772373823198997030"></string>
-    <string name="upstream_roaming_notification_message" msgid="3985577843181551650"></string>
+    <string name="tethered_notification_title" msgid="5350162111436634622">"टेदरिंग या हॉटस्पॉट चालू है"</string>
+    <string name="tethered_notification_message" msgid="2338023450330652098">"सेट अप करने के लिए टैप करें."</string>
+    <string name="disable_tether_notification_title" msgid="3183576627492925522">"टेदरिंग बंद है"</string>
+    <string name="disable_tether_notification_message" msgid="6655882039707534929">"जानकारी के लिए अपने एडमिन से संपर्क करें"</string>
+    <string name="notification_channel_tethering_status" msgid="7030733422705019001">"हॉटस्पॉट और टेदरिंग की स्थिति"</string>
+    <string name="no_upstream_notification_title" msgid="2052743091868702475"></string>
+    <string name="no_upstream_notification_message" msgid="6932020551635470134"></string>
+    <string name="no_upstream_notification_disable_button" msgid="8836277213343697023"></string>
+    <string name="upstream_roaming_notification_title" msgid="8614262557406849762"></string>
+    <string name="upstream_roaming_notification_message" msgid="5999740876323106599"></string>
 </resources>
diff --git a/Tethering/res/values-hr/strings.xml b/Tethering/res/values-hr/strings.xml
index 827c135..19b7b45 100644
--- a/Tethering/res/values-hr/strings.xml
+++ b/Tethering/res/values-hr/strings.xml
@@ -16,14 +16,14 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="tethered_notification_title" msgid="6426563586025792944">"Modemsko povezivanje ili žarišna točka aktivni"</string>
-    <string name="tethered_notification_message" msgid="64800879503420696">"Dodirnite da biste postavili."</string>
-    <string name="disable_tether_notification_title" msgid="3004509127903564191">"Modemsko je povezivanje onemogućeno"</string>
-    <string name="disable_tether_notification_message" msgid="6717523799293901476">"Obratite se administratoru da biste saznali pojedinosti"</string>
-    <string name="notification_channel_tethering_status" msgid="2663463891530932727">"Status žarišne točke i modemskog povezivanja"</string>
-    <string name="no_upstream_notification_title" msgid="1204601824631788482"></string>
-    <string name="no_upstream_notification_message" msgid="8586582938243032621"></string>
-    <string name="no_upstream_notification_disable_button" msgid="8800919436924640822"></string>
-    <string name="upstream_roaming_notification_title" msgid="4772373823198997030"></string>
-    <string name="upstream_roaming_notification_message" msgid="3985577843181551650"></string>
+    <string name="tethered_notification_title" msgid="5350162111436634622">"Modemsko povezivanje ili žarišna točka aktivni"</string>
+    <string name="tethered_notification_message" msgid="2338023450330652098">"Dodirnite da biste ih postavili."</string>
+    <string name="disable_tether_notification_title" msgid="3183576627492925522">"Modemsko je povezivanje onemogućeno"</string>
+    <string name="disable_tether_notification_message" msgid="6655882039707534929">"Obratite se administratoru da biste saznali pojedinosti"</string>
+    <string name="notification_channel_tethering_status" msgid="7030733422705019001">"Status žarišne točke i modemskog povezivanja"</string>
+    <string name="no_upstream_notification_title" msgid="2052743091868702475"></string>
+    <string name="no_upstream_notification_message" msgid="6932020551635470134"></string>
+    <string name="no_upstream_notification_disable_button" msgid="8836277213343697023"></string>
+    <string name="upstream_roaming_notification_title" msgid="8614262557406849762"></string>
+    <string name="upstream_roaming_notification_message" msgid="5999740876323106599"></string>
 </resources>
diff --git a/Tethering/res/values-hu/strings.xml b/Tethering/res/values-hu/strings.xml
index eb68d6b..419f434 100644
--- a/Tethering/res/values-hu/strings.xml
+++ b/Tethering/res/values-hu/strings.xml
@@ -16,14 +16,14 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="tethered_notification_title" msgid="6426563586025792944">"Megosztás vagy aktív hotspot"</string>
-    <string name="tethered_notification_message" msgid="64800879503420696">"Koppintson a beállításhoz."</string>
-    <string name="disable_tether_notification_title" msgid="3004509127903564191">"Az internetmegosztás le van tiltva"</string>
-    <string name="disable_tether_notification_message" msgid="6717523799293901476">"A részletekért forduljon rendszergazdájához"</string>
-    <string name="notification_channel_tethering_status" msgid="2663463891530932727">"Hotspot és internetmegosztás állapota"</string>
-    <string name="no_upstream_notification_title" msgid="1204601824631788482"></string>
-    <string name="no_upstream_notification_message" msgid="8586582938243032621"></string>
-    <string name="no_upstream_notification_disable_button" msgid="8800919436924640822"></string>
-    <string name="upstream_roaming_notification_title" msgid="4772373823198997030"></string>
-    <string name="upstream_roaming_notification_message" msgid="3985577843181551650"></string>
+    <string name="tethered_notification_title" msgid="5350162111436634622">"Az internetmegosztás vagy a hotspot aktív"</string>
+    <string name="tethered_notification_message" msgid="2338023450330652098">"Koppintson a beállításhoz."</string>
+    <string name="disable_tether_notification_title" msgid="3183576627492925522">"Az internetmegosztás le van tiltva"</string>
+    <string name="disable_tether_notification_message" msgid="6655882039707534929">"A részletekért forduljon rendszergazdájához"</string>
+    <string name="notification_channel_tethering_status" msgid="7030733422705019001">"Hotspot és internetmegosztás állapota"</string>
+    <string name="no_upstream_notification_title" msgid="2052743091868702475"></string>
+    <string name="no_upstream_notification_message" msgid="6932020551635470134"></string>
+    <string name="no_upstream_notification_disable_button" msgid="8836277213343697023"></string>
+    <string name="upstream_roaming_notification_title" msgid="8614262557406849762"></string>
+    <string name="upstream_roaming_notification_message" msgid="5999740876323106599"></string>
 </resources>
diff --git a/Tethering/res/values-hy/strings.xml b/Tethering/res/values-hy/strings.xml
index 912941e..c8842b6 100644
--- a/Tethering/res/values-hy/strings.xml
+++ b/Tethering/res/values-hy/strings.xml
@@ -16,14 +16,14 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="tethered_notification_title" msgid="6426563586025792944">"Մոդեմի ռեժիմը միացված է"</string>
-    <string name="tethered_notification_message" msgid="64800879503420696">"Հպեք՝ կարգավորելու համար։"</string>
-    <string name="disable_tether_notification_title" msgid="3004509127903564191">"Մոդեմի ռեժիմն անջատված է"</string>
-    <string name="disable_tether_notification_message" msgid="6717523799293901476">"Մանրամասների համար դիմեք ձեր ադմինիստրատորին"</string>
-    <string name="notification_channel_tethering_status" msgid="2663463891530932727">"Թեժ կետի և մոդեմի ռեժիմի կարգավիճակը"</string>
-    <string name="no_upstream_notification_title" msgid="1204601824631788482"></string>
-    <string name="no_upstream_notification_message" msgid="8586582938243032621"></string>
-    <string name="no_upstream_notification_disable_button" msgid="8800919436924640822"></string>
-    <string name="upstream_roaming_notification_title" msgid="4772373823198997030"></string>
-    <string name="upstream_roaming_notification_message" msgid="3985577843181551650"></string>
+    <string name="tethered_notification_title" msgid="5350162111436634622">"Մոդեմի ռեժիմը միացված է"</string>
+    <string name="tethered_notification_message" msgid="2338023450330652098">"Հպեք՝ կարգավորելու համար։"</string>
+    <string name="disable_tether_notification_title" msgid="3183576627492925522">"Մոդեմի ռեժիմն անջատված է"</string>
+    <string name="disable_tether_notification_message" msgid="6655882039707534929">"Մանրամասների համար դիմեք ձեր ադմինիստրատորին"</string>
+    <string name="notification_channel_tethering_status" msgid="7030733422705019001">"Թեժ կետի և մոդեմի ռեժիմի կարգավիճակը"</string>
+    <string name="no_upstream_notification_title" msgid="2052743091868702475"></string>
+    <string name="no_upstream_notification_message" msgid="6932020551635470134"></string>
+    <string name="no_upstream_notification_disable_button" msgid="8836277213343697023"></string>
+    <string name="upstream_roaming_notification_title" msgid="8614262557406849762"></string>
+    <string name="upstream_roaming_notification_message" msgid="5999740876323106599"></string>
 </resources>
diff --git a/Tethering/res/values-in/strings.xml b/Tethering/res/values-in/strings.xml
index a4e175a..4ae35d4 100644
--- a/Tethering/res/values-in/strings.xml
+++ b/Tethering/res/values-in/strings.xml
@@ -16,14 +16,14 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="tethered_notification_title" msgid="6426563586025792944">"Tethering atau hotspot aktif"</string>
-    <string name="tethered_notification_message" msgid="64800879503420696">"Ketuk untuk menyiapkan."</string>
-    <string name="disable_tether_notification_title" msgid="3004509127903564191">"Tethering dinonaktifkan"</string>
-    <string name="disable_tether_notification_message" msgid="6717523799293901476">"Hubungi admin untuk mengetahui detailnya"</string>
-    <string name="notification_channel_tethering_status" msgid="2663463891530932727">"Status hotspot &amp; tethering"</string>
-    <string name="no_upstream_notification_title" msgid="1204601824631788482"></string>
-    <string name="no_upstream_notification_message" msgid="8586582938243032621"></string>
-    <string name="no_upstream_notification_disable_button" msgid="8800919436924640822"></string>
-    <string name="upstream_roaming_notification_title" msgid="4772373823198997030"></string>
-    <string name="upstream_roaming_notification_message" msgid="3985577843181551650"></string>
+    <string name="tethered_notification_title" msgid="5350162111436634622">"Tethering atau hotspot aktif"</string>
+    <string name="tethered_notification_message" msgid="2338023450330652098">"Ketuk untuk menyiapkan."</string>
+    <string name="disable_tether_notification_title" msgid="3183576627492925522">"Tethering dinonaktifkan"</string>
+    <string name="disable_tether_notification_message" msgid="6655882039707534929">"Hubungi admin untuk mengetahui detailnya"</string>
+    <string name="notification_channel_tethering_status" msgid="7030733422705019001">"Status hotspot &amp; tethering"</string>
+    <string name="no_upstream_notification_title" msgid="2052743091868702475"></string>
+    <string name="no_upstream_notification_message" msgid="6932020551635470134"></string>
+    <string name="no_upstream_notification_disable_button" msgid="8836277213343697023"></string>
+    <string name="upstream_roaming_notification_title" msgid="8614262557406849762"></string>
+    <string name="upstream_roaming_notification_message" msgid="5999740876323106599"></string>
 </resources>
diff --git a/Tethering/res/values-is/strings.xml b/Tethering/res/values-is/strings.xml
index e9f6670..df69fb4 100644
--- a/Tethering/res/values-is/strings.xml
+++ b/Tethering/res/values-is/strings.xml
@@ -16,14 +16,14 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="tethered_notification_title" msgid="6426563586025792944">"Kveikt á tjóðrun eða aðgangsstað"</string>
-    <string name="tethered_notification_message" msgid="64800879503420696">"Ýttu til að setja upp."</string>
-    <string name="disable_tether_notification_title" msgid="3004509127903564191">"Slökkt er á tjóðrun"</string>
-    <string name="disable_tether_notification_message" msgid="6717523799293901476">"Hafðu samband við kerfisstjórann til að fá upplýsingar"</string>
-    <string name="notification_channel_tethering_status" msgid="2663463891530932727">"Staða heits reits og tjóðrunar"</string>
-    <string name="no_upstream_notification_title" msgid="1204601824631788482"></string>
-    <string name="no_upstream_notification_message" msgid="8586582938243032621"></string>
-    <string name="no_upstream_notification_disable_button" msgid="8800919436924640822"></string>
-    <string name="upstream_roaming_notification_title" msgid="4772373823198997030"></string>
-    <string name="upstream_roaming_notification_message" msgid="3985577843181551650"></string>
+    <string name="tethered_notification_title" msgid="5350162111436634622">"Kveikt á tjóðrun eða heitum reit"</string>
+    <string name="tethered_notification_message" msgid="2338023450330652098">"Ýttu til að setja upp."</string>
+    <string name="disable_tether_notification_title" msgid="3183576627492925522">"Slökkt er á tjóðrun"</string>
+    <string name="disable_tether_notification_message" msgid="6655882039707534929">"Hafðu samband við stjórnanda til að fá upplýsingar"</string>
+    <string name="notification_channel_tethering_status" msgid="7030733422705019001">"Staða heits reits og tjóðrunar"</string>
+    <string name="no_upstream_notification_title" msgid="2052743091868702475"></string>
+    <string name="no_upstream_notification_message" msgid="6932020551635470134"></string>
+    <string name="no_upstream_notification_disable_button" msgid="8836277213343697023"></string>
+    <string name="upstream_roaming_notification_title" msgid="8614262557406849762"></string>
+    <string name="upstream_roaming_notification_message" msgid="5999740876323106599"></string>
 </resources>
diff --git a/Tethering/res/values-it/strings.xml b/Tethering/res/values-it/strings.xml
index ffb9196..b13ee92 100644
--- a/Tethering/res/values-it/strings.xml
+++ b/Tethering/res/values-it/strings.xml
@@ -16,14 +16,14 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="tethered_notification_title" msgid="6426563586025792944">"Hotspot o tethering attivo"</string>
-    <string name="tethered_notification_message" msgid="64800879503420696">"Tocca per impostare."</string>
-    <string name="disable_tether_notification_title" msgid="3004509127903564191">"Tethering disattivato"</string>
-    <string name="disable_tether_notification_message" msgid="6717523799293901476">"Contatta il tuo amministratore per avere informazioni dettagliate"</string>
-    <string name="notification_channel_tethering_status" msgid="2663463891530932727">"Stato hotspot e tethering"</string>
-    <string name="no_upstream_notification_title" msgid="1204601824631788482"></string>
-    <string name="no_upstream_notification_message" msgid="8586582938243032621"></string>
-    <string name="no_upstream_notification_disable_button" msgid="8800919436924640822"></string>
-    <string name="upstream_roaming_notification_title" msgid="4772373823198997030"></string>
-    <string name="upstream_roaming_notification_message" msgid="3985577843181551650"></string>
+    <string name="tethered_notification_title" msgid="5350162111436634622">"Hotspot o tethering attivo"</string>
+    <string name="tethered_notification_message" msgid="2338023450330652098">"Tocca per impostare."</string>
+    <string name="disable_tether_notification_title" msgid="3183576627492925522">"Tethering disattivato"</string>
+    <string name="disable_tether_notification_message" msgid="6655882039707534929">"Contatta il tuo amministratore per avere informazioni dettagliate"</string>
+    <string name="notification_channel_tethering_status" msgid="7030733422705019001">"Stato hotspot e tethering"</string>
+    <string name="no_upstream_notification_title" msgid="2052743091868702475"></string>
+    <string name="no_upstream_notification_message" msgid="6932020551635470134"></string>
+    <string name="no_upstream_notification_disable_button" msgid="8836277213343697023"></string>
+    <string name="upstream_roaming_notification_title" msgid="8614262557406849762"></string>
+    <string name="upstream_roaming_notification_message" msgid="5999740876323106599"></string>
 </resources>
diff --git a/Tethering/res/values-iw/strings.xml b/Tethering/res/values-iw/strings.xml
index 7adcb47..f7fb4d5 100644
--- a/Tethering/res/values-iw/strings.xml
+++ b/Tethering/res/values-iw/strings.xml
@@ -16,14 +16,14 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="tethered_notification_title" msgid="6426563586025792944">"נקודה לשיתוף אינטרנט או שיתוף אינטרנט בין מכשירים: בסטטוס פעיל"</string>
-    <string name="tethered_notification_message" msgid="64800879503420696">"יש להקיש כדי להגדיר."</string>
-    <string name="disable_tether_notification_title" msgid="3004509127903564191">"שיתוף האינטרנט בין מכשירים מושבת"</string>
-    <string name="disable_tether_notification_message" msgid="6717523799293901476">"לפרטים, יש לפנות למנהל המערכת"</string>
-    <string name="notification_channel_tethering_status" msgid="2663463891530932727">"סטטוס של נקודה לשיתוף אינטרנט ושיתוף אינטרנט בין מכשירים"</string>
-    <string name="no_upstream_notification_title" msgid="1204601824631788482"></string>
-    <string name="no_upstream_notification_message" msgid="8586582938243032621"></string>
-    <string name="no_upstream_notification_disable_button" msgid="8800919436924640822"></string>
-    <string name="upstream_roaming_notification_title" msgid="4772373823198997030"></string>
-    <string name="upstream_roaming_notification_message" msgid="3985577843181551650"></string>
+    <string name="tethered_notification_title" msgid="5350162111436634622">"שיתוף האינטרנט או הנקודה לשיתוף אינטרנט פעילים"</string>
+    <string name="tethered_notification_message" msgid="2338023450330652098">"יש להקיש כדי להגדיר."</string>
+    <string name="disable_tether_notification_title" msgid="3183576627492925522">"שיתוף האינטרנט בין מכשירים מושבת"</string>
+    <string name="disable_tether_notification_message" msgid="6655882039707534929">"לפרטים, יש לפנות לאדמין"</string>
+    <string name="notification_channel_tethering_status" msgid="7030733422705019001">"סטטוס של נקודה לשיתוף אינטרנט ושיתוף אינטרנט בין מכשירים"</string>
+    <string name="no_upstream_notification_title" msgid="2052743091868702475"></string>
+    <string name="no_upstream_notification_message" msgid="6932020551635470134"></string>
+    <string name="no_upstream_notification_disable_button" msgid="8836277213343697023"></string>
+    <string name="upstream_roaming_notification_title" msgid="8614262557406849762"></string>
+    <string name="upstream_roaming_notification_message" msgid="5999740876323106599"></string>
 </resources>
diff --git a/Tethering/res/values-ja/strings.xml b/Tethering/res/values-ja/strings.xml
index f68a730..172e771 100644
--- a/Tethering/res/values-ja/strings.xml
+++ b/Tethering/res/values-ja/strings.xml
@@ -16,14 +16,14 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="tethered_notification_title" msgid="6426563586025792944">"テザリングまたはアクセス ポイントが有効です"</string>
-    <string name="tethered_notification_message" msgid="64800879503420696">"タップしてセットアップします。"</string>
-    <string name="disable_tether_notification_title" msgid="3004509127903564191">"テザリングは無効に設定されています"</string>
-    <string name="disable_tether_notification_message" msgid="6717523799293901476">"詳しくは、管理者にお問い合わせください"</string>
-    <string name="notification_channel_tethering_status" msgid="2663463891530932727">"アクセス ポイントとテザリングのステータス"</string>
-    <string name="no_upstream_notification_title" msgid="1204601824631788482"></string>
-    <string name="no_upstream_notification_message" msgid="8586582938243032621"></string>
-    <string name="no_upstream_notification_disable_button" msgid="8800919436924640822"></string>
-    <string name="upstream_roaming_notification_title" msgid="4772373823198997030"></string>
-    <string name="upstream_roaming_notification_message" msgid="3985577843181551650"></string>
+    <string name="tethered_notification_title" msgid="5350162111436634622">"テザリングまたはアクセス ポイントが有効です"</string>
+    <string name="tethered_notification_message" msgid="2338023450330652098">"タップしてセットアップしてください。"</string>
+    <string name="disable_tether_notification_title" msgid="3183576627492925522">"テザリングは無効に設定されています"</string>
+    <string name="disable_tether_notification_message" msgid="6655882039707534929">"詳しくは、管理者にお問い合わせください"</string>
+    <string name="notification_channel_tethering_status" msgid="7030733422705019001">"アクセス ポイントとテザリングのステータス"</string>
+    <string name="no_upstream_notification_title" msgid="2052743091868702475"></string>
+    <string name="no_upstream_notification_message" msgid="6932020551635470134"></string>
+    <string name="no_upstream_notification_disable_button" msgid="8836277213343697023"></string>
+    <string name="upstream_roaming_notification_title" msgid="8614262557406849762"></string>
+    <string name="upstream_roaming_notification_message" msgid="5999740876323106599"></string>
 </resources>
diff --git a/Tethering/res/values-ka/strings.xml b/Tethering/res/values-ka/strings.xml
index 7c22e82..b4e1191 100644
--- a/Tethering/res/values-ka/strings.xml
+++ b/Tethering/res/values-ka/strings.xml
@@ -16,14 +16,14 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="tethered_notification_title" msgid="6426563586025792944">"ტეტერინგი ან უსადენო ქსელი აქტიურია"</string>
-    <string name="tethered_notification_message" msgid="64800879503420696">"შეეხეთ დასაყენებლად."</string>
-    <string name="disable_tether_notification_title" msgid="3004509127903564191">"ტეტერინგი გათიშულია"</string>
-    <string name="disable_tether_notification_message" msgid="6717523799293901476">"დამატებითი ინფორმაციისთვის დაუკავშირდით თქვენს ადმინისტრატორს"</string>
-    <string name="notification_channel_tethering_status" msgid="2663463891530932727">"უსადენო ქსელის და ტეტერინგის სტატუსი"</string>
-    <string name="no_upstream_notification_title" msgid="1204601824631788482"></string>
-    <string name="no_upstream_notification_message" msgid="8586582938243032621"></string>
-    <string name="no_upstream_notification_disable_button" msgid="8800919436924640822"></string>
-    <string name="upstream_roaming_notification_title" msgid="4772373823198997030"></string>
-    <string name="upstream_roaming_notification_message" msgid="3985577843181551650"></string>
+    <string name="tethered_notification_title" msgid="5350162111436634622">"ტეტერინგი ან უსადენო ქსელი აქტიურია"</string>
+    <string name="tethered_notification_message" msgid="2338023450330652098">"შეეხეთ დასაყენებლად."</string>
+    <string name="disable_tether_notification_title" msgid="3183576627492925522">"ტეტერინგი გათიშულია"</string>
+    <string name="disable_tether_notification_message" msgid="6655882039707534929">"დამატებითი ინფორმაციისთვის დაუკავშირდით თქვენს ადმინისტრატორს"</string>
+    <string name="notification_channel_tethering_status" msgid="7030733422705019001">"უსადენო ქსელის და ტეტერინგის სტატუსი"</string>
+    <string name="no_upstream_notification_title" msgid="2052743091868702475"></string>
+    <string name="no_upstream_notification_message" msgid="6932020551635470134"></string>
+    <string name="no_upstream_notification_disable_button" msgid="8836277213343697023"></string>
+    <string name="upstream_roaming_notification_title" msgid="8614262557406849762"></string>
+    <string name="upstream_roaming_notification_message" msgid="5999740876323106599"></string>
 </resources>
diff --git a/Tethering/res/values-kk/strings.xml b/Tethering/res/values-kk/strings.xml
index 0857d06..0116381 100644
--- a/Tethering/res/values-kk/strings.xml
+++ b/Tethering/res/values-kk/strings.xml
@@ -16,14 +16,14 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="tethered_notification_title" msgid="6426563586025792944">"Тетеринг немесе хотспот қосулы"</string>
-    <string name="tethered_notification_message" msgid="64800879503420696">"Реттеу үшін түртіңіз."</string>
-    <string name="disable_tether_notification_title" msgid="3004509127903564191">"Тетеринг өшірілді."</string>
-    <string name="disable_tether_notification_message" msgid="6717523799293901476">"Мәліметтерді әкімшіден алыңыз."</string>
-    <string name="notification_channel_tethering_status" msgid="2663463891530932727">"Хотспот және тетеринг күйі"</string>
-    <string name="no_upstream_notification_title" msgid="1204601824631788482"></string>
-    <string name="no_upstream_notification_message" msgid="8586582938243032621"></string>
-    <string name="no_upstream_notification_disable_button" msgid="8800919436924640822"></string>
-    <string name="upstream_roaming_notification_title" msgid="4772373823198997030"></string>
-    <string name="upstream_roaming_notification_message" msgid="3985577843181551650"></string>
+    <string name="tethered_notification_title" msgid="5350162111436634622">"Тетеринг немесе хотспот іске қосылған"</string>
+    <string name="tethered_notification_message" msgid="2338023450330652098">"Реттеу үшін түртіңіз."</string>
+    <string name="disable_tether_notification_title" msgid="3183576627492925522">"Тетеринг өшірілді."</string>
+    <string name="disable_tether_notification_message" msgid="6655882039707534929">"Мәлімет алу үшін әкімшіге хабарласыңыз."</string>
+    <string name="notification_channel_tethering_status" msgid="7030733422705019001">"Хотспот және тетеринг күйі"</string>
+    <string name="no_upstream_notification_title" msgid="2052743091868702475"></string>
+    <string name="no_upstream_notification_message" msgid="6932020551635470134"></string>
+    <string name="no_upstream_notification_disable_button" msgid="8836277213343697023"></string>
+    <string name="upstream_roaming_notification_title" msgid="8614262557406849762"></string>
+    <string name="upstream_roaming_notification_message" msgid="5999740876323106599"></string>
 </resources>
diff --git a/Tethering/res/values-km/strings.xml b/Tethering/res/values-km/strings.xml
index 536e3d1..52667e8 100644
--- a/Tethering/res/values-km/strings.xml
+++ b/Tethering/res/values-km/strings.xml
@@ -16,14 +16,14 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="tethered_notification_title" msgid="6426563586025792944">"ការភ្ជាប់ ឬហតស្ប៉ត​កំពុងដំណើរការ"</string>
-    <string name="tethered_notification_message" msgid="64800879503420696">"ចុច​ដើម្បី​រៀបចំ។"</string>
-    <string name="disable_tether_notification_title" msgid="3004509127903564191">"ការភ្ជាប់​ត្រូវបានបិទ"</string>
-    <string name="disable_tether_notification_message" msgid="6717523799293901476">"ទាក់ទងអ្នកគ្រប់គ្រង​របស់អ្នក ដើម្បីទទួលបានព័ត៌មានលម្អិត"</string>
-    <string name="notification_channel_tethering_status" msgid="2663463891530932727">"ស្ថានភាពនៃការភ្ជាប់ និងហតស្ប៉ត"</string>
-    <string name="no_upstream_notification_title" msgid="1204601824631788482"></string>
-    <string name="no_upstream_notification_message" msgid="8586582938243032621"></string>
-    <string name="no_upstream_notification_disable_button" msgid="8800919436924640822"></string>
-    <string name="upstream_roaming_notification_title" msgid="4772373823198997030"></string>
-    <string name="upstream_roaming_notification_message" msgid="3985577843181551650"></string>
+    <string name="tethered_notification_title" msgid="5350162111436634622">"ការភ្ជាប់ ឬហតស្ប៉ត​កំពុងដំណើរការ"</string>
+    <string name="tethered_notification_message" msgid="2338023450330652098">"ចុច​ដើម្បី​រៀបចំ។"</string>
+    <string name="disable_tether_notification_title" msgid="3183576627492925522">"ការភ្ជាប់​ត្រូវបានបិទ"</string>
+    <string name="disable_tether_notification_message" msgid="6655882039707534929">"ទាក់ទងអ្នកគ្រប់គ្រង​របស់អ្នក ដើម្បីទទួលបានព័ត៌មានលម្អិត"</string>
+    <string name="notification_channel_tethering_status" msgid="7030733422705019001">"ស្ថានភាពនៃការភ្ជាប់ និងហតស្ប៉ត"</string>
+    <string name="no_upstream_notification_title" msgid="2052743091868702475"></string>
+    <string name="no_upstream_notification_message" msgid="6932020551635470134"></string>
+    <string name="no_upstream_notification_disable_button" msgid="8836277213343697023"></string>
+    <string name="upstream_roaming_notification_title" msgid="8614262557406849762"></string>
+    <string name="upstream_roaming_notification_message" msgid="5999740876323106599"></string>
 </resources>
diff --git a/Tethering/res/values-kn/strings.xml b/Tethering/res/values-kn/strings.xml
index 32f5492..a0a3607 100644
--- a/Tethering/res/values-kn/strings.xml
+++ b/Tethering/res/values-kn/strings.xml
@@ -16,14 +16,14 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="tethered_notification_title" msgid="6426563586025792944">"ಟೆಥರಿಂಗ್ ಅಥವಾ ಹಾಟ್‌ಸ್ಪಾಟ್ ಸಕ್ರಿಯವಾಗಿದೆ"</string>
-    <string name="tethered_notification_message" msgid="64800879503420696">"ಸೆಟಪ್ ಮಾಡಲು ಟ್ಯಾಪ್ ಮಾಡಿ."</string>
-    <string name="disable_tether_notification_title" msgid="3004509127903564191">"ಟೆಥರಿಂಗ್ ಅನ್ನು ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಲಾಗಿದೆ"</string>
-    <string name="disable_tether_notification_message" msgid="6717523799293901476">"ವಿವರಗಳಿಗಾಗಿ ನಿಮ್ಮ ನಿರ್ವಾಹಕರನ್ನು ಸಂಪರ್ಕಿಸಿ"</string>
-    <string name="notification_channel_tethering_status" msgid="2663463891530932727">"ಹಾಟ್‌ಸ್ಪಾಟ್ ಮತ್ತು ಟೆಥರಿಂಗ್‌ ಸ್ಥಿತಿ"</string>
-    <string name="no_upstream_notification_title" msgid="1204601824631788482"></string>
-    <string name="no_upstream_notification_message" msgid="8586582938243032621"></string>
-    <string name="no_upstream_notification_disable_button" msgid="8800919436924640822"></string>
-    <string name="upstream_roaming_notification_title" msgid="4772373823198997030"></string>
-    <string name="upstream_roaming_notification_message" msgid="3985577843181551650"></string>
+    <string name="tethered_notification_title" msgid="5350162111436634622">"ಟೆಥರಿಂಗ್ ಅಥವಾ ಹಾಟ್‌ಸ್ಪಾಟ್ ಸಕ್ರಿಯವಾಗಿದೆ"</string>
+    <string name="tethered_notification_message" msgid="2338023450330652098">"ಸೆಟಪ್ ಮಾಡಲು ಟ್ಯಾಪ್ ಮಾಡಿ."</string>
+    <string name="disable_tether_notification_title" msgid="3183576627492925522">"ಟೆಥರಿಂಗ್ ಅನ್ನು ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಲಾಗಿದೆ"</string>
+    <string name="disable_tether_notification_message" msgid="6655882039707534929">"ವಿವರಗಳಿಗಾಗಿ ನಿಮ್ಮ ನಿರ್ವಾಹಕರನ್ನು ಸಂಪರ್ಕಿಸಿ"</string>
+    <string name="notification_channel_tethering_status" msgid="7030733422705019001">"ಹಾಟ್‌ಸ್ಪಾಟ್ ಮತ್ತು ಟೆಥರಿಂಗ್‌ ಸ್ಥಿತಿ"</string>
+    <string name="no_upstream_notification_title" msgid="2052743091868702475"></string>
+    <string name="no_upstream_notification_message" msgid="6932020551635470134"></string>
+    <string name="no_upstream_notification_disable_button" msgid="8836277213343697023"></string>
+    <string name="upstream_roaming_notification_title" msgid="8614262557406849762"></string>
+    <string name="upstream_roaming_notification_message" msgid="5999740876323106599"></string>
 </resources>
diff --git a/Tethering/res/values-ko/strings.xml b/Tethering/res/values-ko/strings.xml
index 156b247..f7b8da0 100644
--- a/Tethering/res/values-ko/strings.xml
+++ b/Tethering/res/values-ko/strings.xml
@@ -16,14 +16,14 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="tethered_notification_title" msgid="6426563586025792944">"테더링 또는 핫스팟 사용"</string>
-    <string name="tethered_notification_message" msgid="64800879503420696">"설정하려면 탭하세요."</string>
-    <string name="disable_tether_notification_title" msgid="3004509127903564191">"테더링이 사용 중지됨"</string>
-    <string name="disable_tether_notification_message" msgid="6717523799293901476">"자세한 정보는 관리자에게 문의하세요."</string>
-    <string name="notification_channel_tethering_status" msgid="2663463891530932727">"핫스팟 및 테더링 상태"</string>
-    <string name="no_upstream_notification_title" msgid="1204601824631788482"></string>
-    <string name="no_upstream_notification_message" msgid="8586582938243032621"></string>
-    <string name="no_upstream_notification_disable_button" msgid="8800919436924640822"></string>
-    <string name="upstream_roaming_notification_title" msgid="4772373823198997030"></string>
-    <string name="upstream_roaming_notification_message" msgid="3985577843181551650"></string>
+    <string name="tethered_notification_title" msgid="5350162111436634622">"테더링 또는 핫스팟 사용 중"</string>
+    <string name="tethered_notification_message" msgid="2338023450330652098">"설정하려면 탭하세요."</string>
+    <string name="disable_tether_notification_title" msgid="3183576627492925522">"테더링이 사용 중지됨"</string>
+    <string name="disable_tether_notification_message" msgid="6655882039707534929">"자세한 정보는 관리자에게 문의하세요."</string>
+    <string name="notification_channel_tethering_status" msgid="7030733422705019001">"핫스팟 및 테더링 상태"</string>
+    <string name="no_upstream_notification_title" msgid="2052743091868702475"></string>
+    <string name="no_upstream_notification_message" msgid="6932020551635470134"></string>
+    <string name="no_upstream_notification_disable_button" msgid="8836277213343697023"></string>
+    <string name="upstream_roaming_notification_title" msgid="8614262557406849762"></string>
+    <string name="upstream_roaming_notification_message" msgid="5999740876323106599"></string>
 </resources>
diff --git a/Tethering/res/values-ky/strings.xml b/Tethering/res/values-ky/strings.xml
index 18ee5fd..4696a72 100644
--- a/Tethering/res/values-ky/strings.xml
+++ b/Tethering/res/values-ky/strings.xml
@@ -16,14 +16,14 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="tethered_notification_title" msgid="6426563586025792944">"Модем режими күйүп турат"</string>
-    <string name="tethered_notification_message" msgid="64800879503420696">"Жөндөө үчүн таптап коюңуз."</string>
-    <string name="disable_tether_notification_title" msgid="3004509127903564191">"Телефонду модем катары колдонууга болбойт"</string>
-    <string name="disable_tether_notification_message" msgid="6717523799293901476">"Кеңири маалымат үчүн администраторуңузга кайрылыңыз"</string>
-    <string name="notification_channel_tethering_status" msgid="2663463891530932727">"Байланыш түйүнүнүн жана модем режиминин статусу"</string>
-    <string name="no_upstream_notification_title" msgid="1204601824631788482"></string>
-    <string name="no_upstream_notification_message" msgid="8586582938243032621"></string>
-    <string name="no_upstream_notification_disable_button" msgid="8800919436924640822"></string>
-    <string name="upstream_roaming_notification_title" msgid="4772373823198997030"></string>
-    <string name="upstream_roaming_notification_message" msgid="3985577843181551650"></string>
+    <string name="tethered_notification_title" msgid="5350162111436634622">"Модем режими күйүп турат"</string>
+    <string name="tethered_notification_message" msgid="2338023450330652098">"Тууралоо үчүн басыңыз."</string>
+    <string name="disable_tether_notification_title" msgid="3183576627492925522">"Модем режими өчүк"</string>
+    <string name="disable_tether_notification_message" msgid="6655882039707534929">"Кеңири маалымат үчүн администраторуңузга кайрылыңыз"</string>
+    <string name="notification_channel_tethering_status" msgid="7030733422705019001">"Байланыш түйүнү жана байланыш түйүнүүн статусу"</string>
+    <string name="no_upstream_notification_title" msgid="2052743091868702475"></string>
+    <string name="no_upstream_notification_message" msgid="6932020551635470134"></string>
+    <string name="no_upstream_notification_disable_button" msgid="8836277213343697023"></string>
+    <string name="upstream_roaming_notification_title" msgid="8614262557406849762"></string>
+    <string name="upstream_roaming_notification_message" msgid="5999740876323106599"></string>
 </resources>
diff --git a/Tethering/res/values-lo/strings.xml b/Tethering/res/values-lo/strings.xml
index b127670..046551d 100644
--- a/Tethering/res/values-lo/strings.xml
+++ b/Tethering/res/values-lo/strings.xml
@@ -16,14 +16,14 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="tethered_notification_title" msgid="6426563586025792944">"ເປີດການປ່ອຍສັນຍານ ຫຼື ຮັອດສະປອດແລ້ວ"</string>
-    <string name="tethered_notification_message" msgid="64800879503420696">"ແຕະເພື່ອຕັ້ງຄ່າ."</string>
-    <string name="disable_tether_notification_title" msgid="3004509127903564191">"ການປ່ອຍສັນຍານຖືກປິດໄວ້"</string>
-    <string name="disable_tether_notification_message" msgid="6717523799293901476">"ຕິດຕໍ່ຜູ້ເບິ່ງແຍງລະບົບສຳລັບລາຍລະອຽດ"</string>
-    <string name="notification_channel_tethering_status" msgid="2663463891530932727">"ສະຖານະຮັອດສະປອດ ແລະ ການປ່ອຍສັນຍານ"</string>
-    <string name="no_upstream_notification_title" msgid="1204601824631788482"></string>
-    <string name="no_upstream_notification_message" msgid="8586582938243032621"></string>
-    <string name="no_upstream_notification_disable_button" msgid="8800919436924640822"></string>
-    <string name="upstream_roaming_notification_title" msgid="4772373823198997030"></string>
-    <string name="upstream_roaming_notification_message" msgid="3985577843181551650"></string>
+    <string name="tethered_notification_title" msgid="5350162111436634622">"ການປ່ອຍສັນຍານ ຫຼື ຮັອດສະປອດເປີດນຳໃຊ້ຢູ່"</string>
+    <string name="tethered_notification_message" msgid="2338023450330652098">"ແຕະເພື່ອຕັ້ງຄ່າ."</string>
+    <string name="disable_tether_notification_title" msgid="3183576627492925522">"ການປ່ອຍສັນຍານຖືກປິດໄວ້"</string>
+    <string name="disable_tether_notification_message" msgid="6655882039707534929">"ຕິດຕໍ່ຜູ້ເບິ່ງແຍງລະບົບຂອງທ່ານສຳລັບລາຍລະອຽດ"</string>
+    <string name="notification_channel_tethering_status" msgid="7030733422705019001">"ສະຖານະຮັອດສະປອດ ແລະ ການປ່ອຍສັນຍານ"</string>
+    <string name="no_upstream_notification_title" msgid="2052743091868702475"></string>
+    <string name="no_upstream_notification_message" msgid="6932020551635470134"></string>
+    <string name="no_upstream_notification_disable_button" msgid="8836277213343697023"></string>
+    <string name="upstream_roaming_notification_title" msgid="8614262557406849762"></string>
+    <string name="upstream_roaming_notification_message" msgid="5999740876323106599"></string>
 </resources>
diff --git a/Tethering/res/values-lt/strings.xml b/Tethering/res/values-lt/strings.xml
index 8427baf..c685318 100644
--- a/Tethering/res/values-lt/strings.xml
+++ b/Tethering/res/values-lt/strings.xml
@@ -16,14 +16,14 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="tethered_notification_title" msgid="6426563586025792944">"Įrenginys naudojamas kaip modemas arba įjungtas viešosios interneto prieigos taškas"</string>
-    <string name="tethered_notification_message" msgid="64800879503420696">"Palieskite, kad nustatytumėte."</string>
-    <string name="disable_tether_notification_title" msgid="3004509127903564191">"Įrenginio kaip modemo naudojimas išjungtas"</string>
-    <string name="disable_tether_notification_message" msgid="6717523799293901476">"Jei reikia išsamios informacijos, susisiekite su administratoriumi"</string>
-    <string name="notification_channel_tethering_status" msgid="2663463891530932727">"Viešosios interneto prieigos taško ir įrenginio kaip modemo naudojimo būsena"</string>
-    <string name="no_upstream_notification_title" msgid="1204601824631788482"></string>
-    <string name="no_upstream_notification_message" msgid="8586582938243032621"></string>
-    <string name="no_upstream_notification_disable_button" msgid="8800919436924640822"></string>
-    <string name="upstream_roaming_notification_title" msgid="4772373823198997030"></string>
-    <string name="upstream_roaming_notification_message" msgid="3985577843181551650"></string>
+    <string name="tethered_notification_title" msgid="5350162111436634622">"Įrenginys naudojamas kaip modemas arba įjungtas viešosios interneto prieigos taškas"</string>
+    <string name="tethered_notification_message" msgid="2338023450330652098">"Palieskite, kad nustatytumėte."</string>
+    <string name="disable_tether_notification_title" msgid="3183576627492925522">"Įrenginio kaip modemo naudojimas išjungtas"</string>
+    <string name="disable_tether_notification_message" msgid="6655882039707534929">"Jei reikia išsamios informacijos, susisiekite su administratoriumi"</string>
+    <string name="notification_channel_tethering_status" msgid="7030733422705019001">"Viešosios interneto prieigos taško ir įrenginio kaip modemo naudojimo būsena"</string>
+    <string name="no_upstream_notification_title" msgid="2052743091868702475"></string>
+    <string name="no_upstream_notification_message" msgid="6932020551635470134"></string>
+    <string name="no_upstream_notification_disable_button" msgid="8836277213343697023"></string>
+    <string name="upstream_roaming_notification_title" msgid="8614262557406849762"></string>
+    <string name="upstream_roaming_notification_message" msgid="5999740876323106599"></string>
 </resources>
diff --git a/Tethering/res/values-lv/strings.xml b/Tethering/res/values-lv/strings.xml
index aa2d699..fd8751c 100644
--- a/Tethering/res/values-lv/strings.xml
+++ b/Tethering/res/values-lv/strings.xml
@@ -16,14 +16,14 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="tethered_notification_title" msgid="6426563586025792944">"Piesaiste vai tīklājs ir aktīvs."</string>
-    <string name="tethered_notification_message" msgid="64800879503420696">"Pieskarieties, lai to iestatītu."</string>
-    <string name="disable_tether_notification_title" msgid="3004509127903564191">"Piesaiste ir atspējota"</string>
-    <string name="disable_tether_notification_message" msgid="6717523799293901476">"Lai iegūtu detalizētu informāciju, sazinieties ar savu administratoru."</string>
-    <string name="notification_channel_tethering_status" msgid="2663463891530932727">"Tīklāja un piesaistes statuss"</string>
-    <string name="no_upstream_notification_title" msgid="1204601824631788482"></string>
-    <string name="no_upstream_notification_message" msgid="8586582938243032621"></string>
-    <string name="no_upstream_notification_disable_button" msgid="8800919436924640822"></string>
-    <string name="upstream_roaming_notification_title" msgid="4772373823198997030"></string>
-    <string name="upstream_roaming_notification_message" msgid="3985577843181551650"></string>
+    <string name="tethered_notification_title" msgid="5350162111436634622">"Piesaiste vai tīklājs ir aktīvs"</string>
+    <string name="tethered_notification_message" msgid="2338023450330652098">"Pieskarieties, lai iestatītu."</string>
+    <string name="disable_tether_notification_title" msgid="3183576627492925522">"Piesaiste ir atspējota"</string>
+    <string name="disable_tether_notification_message" msgid="6655882039707534929">"Lai iegūtu detalizētu informāciju, sazinieties ar savu administratoru."</string>
+    <string name="notification_channel_tethering_status" msgid="7030733422705019001">"Tīklāja un piesaistes statuss"</string>
+    <string name="no_upstream_notification_title" msgid="2052743091868702475"></string>
+    <string name="no_upstream_notification_message" msgid="6932020551635470134"></string>
+    <string name="no_upstream_notification_disable_button" msgid="8836277213343697023"></string>
+    <string name="upstream_roaming_notification_title" msgid="8614262557406849762"></string>
+    <string name="upstream_roaming_notification_message" msgid="5999740876323106599"></string>
 </resources>
diff --git a/Tethering/res/values-mcc310-mnc004-af/strings.xml b/Tethering/res/values-mcc310-mnc004-af/strings.xml
index 19d659c..216c02c 100644
--- a/Tethering/res/values-mcc310-mnc004-af/strings.xml
+++ b/Tethering/res/values-mcc310-mnc004-af/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="5030042590486713460">"Verbinding het nie internet nie"</string>
-    <string name="no_upstream_notification_message" msgid="3843613362272973447">"Toestelle kan nie koppel nie"</string>
-    <string name="no_upstream_notification_disable_button" msgid="6385491461813507624">"Skakel verbinding af"</string>
-    <string name="upstream_roaming_notification_title" msgid="3015912166812283303">"Warmkol of verbinding is aan"</string>
-    <string name="upstream_roaming_notification_message" msgid="6724434706748439902">"Bykomende heffings kan geld terwyl jy swerf"</string>
+    <string name="no_upstream_notification_title" msgid="3584617491053416666">"Verbinding het nie internet nie"</string>
+    <string name="no_upstream_notification_message" msgid="5626323795587558017">"Toestelle kan nie koppel nie"</string>
+    <string name="no_upstream_notification_disable_button" msgid="868677179945695858">"Skakel verbinding af"</string>
+    <string name="upstream_roaming_notification_title" msgid="2870229486619751829">"Warmkol of verbinding is aan"</string>
+    <string name="upstream_roaming_notification_message" msgid="5229740963392849544">"Bykomende heffings kan geld terwyl jy swerf"</string>
 </resources>
diff --git a/Tethering/res/values-mcc310-mnc004-am/strings.xml b/Tethering/res/values-mcc310-mnc004-am/strings.xml
index 8995430..666630a 100644
--- a/Tethering/res/values-mcc310-mnc004-am/strings.xml
+++ b/Tethering/res/values-mcc310-mnc004-am/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="5030042590486713460">"ማስተሳሰር ምንም በይነመረብ የለውም"</string>
-    <string name="no_upstream_notification_message" msgid="3843613362272973447">"መሣሪያዎችን ማገናኘት አይቻልም"</string>
-    <string name="no_upstream_notification_disable_button" msgid="6385491461813507624">"ማስተሳሰርን አጥፋ"</string>
-    <string name="upstream_roaming_notification_title" msgid="3015912166812283303">"መገናኛ ነጥብ ወይም ማስተሳሰር በርቷል"</string>
-    <string name="upstream_roaming_notification_message" msgid="6724434706748439902">"በሚያንዣብብበት ጊዜ ተጨማሪ ክፍያዎች ተፈጻሚ ሊሆኑ ይችላሉ"</string>
+    <string name="no_upstream_notification_title" msgid="3584617491053416666">"እንደ ሞደም መሰካት ምንም በይነመረብ የለውም"</string>
+    <string name="no_upstream_notification_message" msgid="5626323795587558017">"መሣሪያዎችን ማገናኘት አልተቻልም"</string>
+    <string name="no_upstream_notification_disable_button" msgid="868677179945695858">"እንደ ሞደም መሰካትን አጥፋ"</string>
+    <string name="upstream_roaming_notification_title" msgid="2870229486619751829">"መገናኛ ነጥብ ወይም እንደ ሞደም መሰካት በርቷል"</string>
+    <string name="upstream_roaming_notification_message" msgid="5229740963392849544">"በሚያንዣብብበት ጊዜ ተጨማሪ ክፍያዎች ተፈጻሚ ሊሆኑ ይችላሉ"</string>
 </resources>
diff --git a/Tethering/res/values-mcc310-mnc004-ar/strings.xml b/Tethering/res/values-mcc310-mnc004-ar/strings.xml
index 54f3b53..2859803 100644
--- a/Tethering/res/values-mcc310-mnc004-ar/strings.xml
+++ b/Tethering/res/values-mcc310-mnc004-ar/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="5030042590486713460">"ما مِن اتصال بالإنترنت خلال التوصيل"</string>
-    <string name="no_upstream_notification_message" msgid="3843613362272973447">"تعذّر اتصال الأجهزة"</string>
-    <string name="no_upstream_notification_disable_button" msgid="6385491461813507624">"إيقاف التوصيل"</string>
-    <string name="upstream_roaming_notification_title" msgid="3015912166812283303">"نقطة الاتصال أو التوصيل مفعّلان"</string>
-    <string name="upstream_roaming_notification_message" msgid="6724434706748439902">"قد يتم تطبيق رسوم إضافية أثناء التجوال."</string>
+    <string name="no_upstream_notification_title" msgid="3584617491053416666">"ما مِن اتصال بالإنترنت خلال التوصيل"</string>
+    <string name="no_upstream_notification_message" msgid="5626323795587558017">"يتعذّر اتصال الأجهزة"</string>
+    <string name="no_upstream_notification_disable_button" msgid="868677179945695858">"إيقاف التوصيل"</string>
+    <string name="upstream_roaming_notification_title" msgid="2870229486619751829">"نقطة الاتصال مفعَّلة أو التوصيل مفعَّل"</string>
+    <string name="upstream_roaming_notification_message" msgid="5229740963392849544">"قد يتم تحصيل رسوم إضافية أثناء التجوال."</string>
 </resources>
diff --git a/Tethering/res/values-mcc310-mnc004-as/strings.xml b/Tethering/res/values-mcc310-mnc004-as/strings.xml
index e215141..360c8ca 100644
--- a/Tethering/res/values-mcc310-mnc004-as/strings.xml
+++ b/Tethering/res/values-mcc310-mnc004-as/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="5030042590486713460">"টে\'ডাৰিঙৰ ইণ্টাৰনেট নাই"</string>
-    <string name="no_upstream_notification_message" msgid="3843613362272973447">"ডিভাইচসমূহ সংযোগ কৰিব নোৱাৰি"</string>
-    <string name="no_upstream_notification_disable_button" msgid="6385491461813507624">"টে\'ডাৰিং অফ কৰক"</string>
-    <string name="upstream_roaming_notification_title" msgid="3015912166812283303">"হটস্পট অথবা টে\'ডাৰিং অন আছে"</string>
-    <string name="upstream_roaming_notification_message" msgid="6724434706748439902">"ৰ\'মিঙত থাকিলে অতিৰিক্ত মাচুল প্ৰযোজ্য হ’ব পাৰে"</string>
+    <string name="no_upstream_notification_title" msgid="3584617491053416666">"টে’ডাৰিঙৰ ইণ্টাৰনেট নাই"</string>
+    <string name="no_upstream_notification_message" msgid="5626323795587558017">"ডিভাইচসমূহ সংযোগ কৰিব নোৱাৰি"</string>
+    <string name="no_upstream_notification_disable_button" msgid="868677179945695858">"টে’ডাৰিং অফ কৰক"</string>
+    <string name="upstream_roaming_notification_title" msgid="2870229486619751829">"হ’টস্প’ট অথবা টে’ডাৰিং অন আছে"</string>
+    <string name="upstream_roaming_notification_message" msgid="5229740963392849544">"ৰ’মিঙত থাকিলে অতিৰিক্ত মাচুল প্ৰযোজ্য হ’ব পাৰে"</string>
 </resources>
diff --git a/Tethering/res/values-mcc310-mnc004-az/strings.xml b/Tethering/res/values-mcc310-mnc004-az/strings.xml
index 1fd8e4c..b7fdd70 100644
--- a/Tethering/res/values-mcc310-mnc004-az/strings.xml
+++ b/Tethering/res/values-mcc310-mnc004-az/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="5030042590486713460">"Modemin internetə girişi yoxdur"</string>
-    <string name="no_upstream_notification_message" msgid="3843613362272973447">"Cihazları qoşmaq mümkün deyil"</string>
-    <string name="no_upstream_notification_disable_button" msgid="6385491461813507624">"Modemi deaktiv edin"</string>
-    <string name="upstream_roaming_notification_title" msgid="3015912166812283303">"Hotspot və ya modem aktivdir"</string>
-    <string name="upstream_roaming_notification_message" msgid="6724434706748439902">"Rouminq zamanı əlavə ödənişlər tətbiq edilə bilər"</string>
+    <string name="no_upstream_notification_title" msgid="3584617491053416666">"Modem rejimi internetə qoşulmayıb"</string>
+    <string name="no_upstream_notification_message" msgid="5626323795587558017">"Cihazları qoşmaq olmur"</string>
+    <string name="no_upstream_notification_disable_button" msgid="868677179945695858">"Modem rejimini deaktiv edin"</string>
+    <string name="upstream_roaming_notification_title" msgid="2870229486619751829">"Hotspot və ya modem rejimi aktivdir"</string>
+    <string name="upstream_roaming_notification_message" msgid="5229740963392849544">"Rouminq zamanı əlavə ödəniş çıxıla bilər"</string>
 </resources>
diff --git a/Tethering/res/values-mcc310-mnc004-b+sr+Latn/strings.xml b/Tethering/res/values-mcc310-mnc004-b+sr+Latn/strings.xml
index 1abe4f3..a214f93 100644
--- a/Tethering/res/values-mcc310-mnc004-b+sr+Latn/strings.xml
+++ b/Tethering/res/values-mcc310-mnc004-b+sr+Latn/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="5030042590486713460">"Privezivanje nema pristup internetu"</string>
-    <string name="no_upstream_notification_message" msgid="3843613362272973447">"Povezivanje uređaja nije uspelo"</string>
-    <string name="no_upstream_notification_disable_button" msgid="6385491461813507624">"Isključi privezivanje"</string>
-    <string name="upstream_roaming_notification_title" msgid="3015912166812283303">"Uključen je hotspot ili privezivanje"</string>
-    <string name="upstream_roaming_notification_message" msgid="6724434706748439902">"Možda važe dodatni troškovi u romingu"</string>
+    <string name="no_upstream_notification_title" msgid="3584617491053416666">"Privezivanje nema pristup internetu"</string>
+    <string name="no_upstream_notification_message" msgid="5626323795587558017">"Povezivanje uređaja nije uspelo"</string>
+    <string name="no_upstream_notification_disable_button" msgid="868677179945695858">"Isključi privezivanje"</string>
+    <string name="upstream_roaming_notification_title" msgid="2870229486619751829">"Uključen je hotspot ili privezivanje"</string>
+    <string name="upstream_roaming_notification_message" msgid="5229740963392849544">"Možda važe dodatni troškovi u romingu"</string>
 </resources>
diff --git a/Tethering/res/values-mcc310-mnc004-be/strings.xml b/Tethering/res/values-mcc310-mnc004-be/strings.xml
index 38dbd1e..316e856 100644
--- a/Tethering/res/values-mcc310-mnc004-be/strings.xml
+++ b/Tethering/res/values-mcc310-mnc004-be/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="5030042590486713460">"Рэжым мадэма выкарыстоўваецца без доступу да інтэрнэту"</string>
-    <string name="no_upstream_notification_message" msgid="3843613362272973447">"Не ўдалося падключыць прылады"</string>
-    <string name="no_upstream_notification_disable_button" msgid="6385491461813507624">"Выключыць рэжым мадэма"</string>
-    <string name="upstream_roaming_notification_title" msgid="3015912166812283303">"Хот-спот або рэжым мадэма ўключаны"</string>
-    <string name="upstream_roaming_notification_message" msgid="6724434706748439902">"Пры выкарыстанні роўмінгу можа спаганяцца дадатковая плата"</string>
+    <string name="no_upstream_notification_title" msgid="3584617491053416666">"Рэжым мадэма выкарыстоўваецца без доступу да інтэрнэту"</string>
+    <string name="no_upstream_notification_message" msgid="5626323795587558017">"Не ўдалося падключыць прылады"</string>
+    <string name="no_upstream_notification_disable_button" msgid="868677179945695858">"Выключыць рэжым мадэма"</string>
+    <string name="upstream_roaming_notification_title" msgid="2870229486619751829">"Хот-спот або рэжым мадэма ўключаны"</string>
+    <string name="upstream_roaming_notification_message" msgid="5229740963392849544">"Пры выкарыстанні роўмінгу можа спаганяцца дадатковая плата"</string>
 </resources>
diff --git a/Tethering/res/values-mcc310-mnc004-bg/strings.xml b/Tethering/res/values-mcc310-mnc004-bg/strings.xml
index 04b44db..a31c06a 100644
--- a/Tethering/res/values-mcc310-mnc004-bg/strings.xml
+++ b/Tethering/res/values-mcc310-mnc004-bg/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="5030042590486713460">"Тетърингът няма връзка с интернет"</string>
-    <string name="no_upstream_notification_message" msgid="3843613362272973447">"Устройствата не могат да установят връзка"</string>
-    <string name="no_upstream_notification_disable_button" msgid="6385491461813507624">"Изключване на тетъринга"</string>
-    <string name="upstream_roaming_notification_title" msgid="3015912166812283303">"Точката за достъп или тетърингът са включени"</string>
-    <string name="upstream_roaming_notification_message" msgid="6724434706748439902">"Възможно е да ви бъдат начислени допълнителни такси при роуминг"</string>
+    <string name="no_upstream_notification_title" msgid="3584617491053416666">"Няма връзка с интернет за тетъринг"</string>
+    <string name="no_upstream_notification_message" msgid="5626323795587558017">"Устройствата не могат да установят връзка"</string>
+    <string name="no_upstream_notification_disable_button" msgid="868677179945695858">"Изключване на функцията за тетъринг"</string>
+    <string name="upstream_roaming_notification_title" msgid="2870229486619751829">"Точката за достъп или функцията за тетъринг са включени"</string>
+    <string name="upstream_roaming_notification_message" msgid="5229740963392849544">"Възможно е да ви бъдат начислени допълнителни такси при роуминг"</string>
 </resources>
diff --git a/Tethering/res/values-mcc310-mnc004-bn/strings.xml b/Tethering/res/values-mcc310-mnc004-bn/strings.xml
index 579d1be..f49b14c 100644
--- a/Tethering/res/values-mcc310-mnc004-bn/strings.xml
+++ b/Tethering/res/values-mcc310-mnc004-bn/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="5030042590486713460">"টিথারিং করার জন্য কোনও ইন্টারনেট কানেকশন নেই"</string>
-    <string name="no_upstream_notification_message" msgid="3843613362272973447">"ডিভাইস কানেক্ট করতে পারছে না"</string>
-    <string name="no_upstream_notification_disable_button" msgid="6385491461813507624">"টিথারিং বন্ধ করুন"</string>
-    <string name="upstream_roaming_notification_title" msgid="3015912166812283303">"হটস্পট বা টিথারিং চালু আছে"</string>
-    <string name="upstream_roaming_notification_message" msgid="6724434706748439902">"রোমিংয়ের সময় অতিরিক্ত চার্জ করা হতে পারে"</string>
+    <string name="no_upstream_notification_title" msgid="3584617491053416666">"টেথারিং করার জন্য কোনও ইন্টারনেট কানেকশন লাগে না"</string>
+    <string name="no_upstream_notification_message" msgid="5626323795587558017">"ডিভাইস কানেক্ট করা যাচ্ছে না"</string>
+    <string name="no_upstream_notification_disable_button" msgid="868677179945695858">"টেথারিং বন্ধ করুন"</string>
+    <string name="upstream_roaming_notification_title" msgid="2870229486619751829">"হটস্পট বা টেথারিং চালু আছে"</string>
+    <string name="upstream_roaming_notification_message" msgid="5229740963392849544">"রোমিংয়ে থাকার সময় অতিরিক্ত চার্জ লাগতে পারে"</string>
 </resources>
diff --git a/Tethering/res/values-mcc310-mnc004-bs/strings.xml b/Tethering/res/values-mcc310-mnc004-bs/strings.xml
index 9ce3efe..ed269c6 100644
--- a/Tethering/res/values-mcc310-mnc004-bs/strings.xml
+++ b/Tethering/res/values-mcc310-mnc004-bs/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="5030042590486713460">"Povezivanje putem mobitela nema internet"</string>
-    <string name="no_upstream_notification_message" msgid="3843613362272973447">"Uređaji se ne mogu povezati"</string>
-    <string name="no_upstream_notification_disable_button" msgid="6385491461813507624">"Isključi povezivanje putem mobitela"</string>
-    <string name="upstream_roaming_notification_title" msgid="3015912166812283303">"Pristupna tačka ili povezivanje putem mobitela je uključeno"</string>
-    <string name="upstream_roaming_notification_message" msgid="6724434706748439902">"Mogu nastati dodatni troškovi u romingu"</string>
+    <string name="no_upstream_notification_title" msgid="3584617491053416666">"Dijeljenje internetske veze nema internet"</string>
+    <string name="no_upstream_notification_message" msgid="5626323795587558017">"Nije moguće povezati uređaje"</string>
+    <string name="no_upstream_notification_disable_button" msgid="868677179945695858">"Isključi dijeljenje internetske veze"</string>
+    <string name="upstream_roaming_notification_title" msgid="2870229486619751829">"Pristupna tačka ili dijeljenje internetske veze su uključeni"</string>
+    <string name="upstream_roaming_notification_message" msgid="5229740963392849544">"Mogu nastati dodatni troškovi u romingu"</string>
 </resources>
diff --git a/Tethering/res/values-mcc310-mnc004-ca/strings.xml b/Tethering/res/values-mcc310-mnc004-ca/strings.xml
index 46d4c35..0826f4e 100644
--- a/Tethering/res/values-mcc310-mnc004-ca/strings.xml
+++ b/Tethering/res/values-mcc310-mnc004-ca/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="5030042590486713460">"La compartició de xarxa no té accés a Internet"</string>
-    <string name="no_upstream_notification_message" msgid="3843613362272973447">"No es poden connectar els dispositius"</string>
-    <string name="no_upstream_notification_disable_button" msgid="6385491461813507624">"Desactiva la compartició de xarxa"</string>
-    <string name="upstream_roaming_notification_title" msgid="3015912166812283303">"S\'ha activat el punt d\'accés Wi‑Fi o la compartició de xarxa"</string>
-    <string name="upstream_roaming_notification_message" msgid="6724434706748439902">"És possible que s\'apliquin costos addicionals en itinerància"</string>
+    <string name="no_upstream_notification_title" msgid="3584617491053416666">"La compartició de xarxa no té accés a Internet"</string>
+    <string name="no_upstream_notification_message" msgid="5626323795587558017">"No es poden connectar els dispositius"</string>
+    <string name="no_upstream_notification_disable_button" msgid="868677179945695858">"Desactiva la compartició de xarxa"</string>
+    <string name="upstream_roaming_notification_title" msgid="2870229486619751829">"El punt d\'accés Wi‑Fi o la compartició de xarxa estan activats"</string>
+    <string name="upstream_roaming_notification_message" msgid="5229740963392849544">"És possible que s\'apliquin costos addicionals en itinerància"</string>
 </resources>
diff --git a/Tethering/res/values-mcc310-mnc004-cs/strings.xml b/Tethering/res/values-mcc310-mnc004-cs/strings.xml
index cc13860..6899f71 100644
--- a/Tethering/res/values-mcc310-mnc004-cs/strings.xml
+++ b/Tethering/res/values-mcc310-mnc004-cs/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="5030042590486713460">"Tethering nemá připojení k internetu"</string>
-    <string name="no_upstream_notification_message" msgid="3843613362272973447">"Zařízení se nemůžou připojit"</string>
-    <string name="no_upstream_notification_disable_button" msgid="6385491461813507624">"Vypnout tethering"</string>
-    <string name="upstream_roaming_notification_title" msgid="3015912166812283303">"Je zapnutý hotspot nebo tethering"</string>
-    <string name="upstream_roaming_notification_message" msgid="6724434706748439902">"Při roamingu mohou být účtovány dodatečné poplatky"</string>
+    <string name="no_upstream_notification_title" msgid="3584617491053416666">"Tethering nemá připojení k internetu"</string>
+    <string name="no_upstream_notification_message" msgid="5626323795587558017">"Zařízení se nemůžou připojit"</string>
+    <string name="no_upstream_notification_disable_button" msgid="868677179945695858">"Vypnout tethering"</string>
+    <string name="upstream_roaming_notification_title" msgid="2870229486619751829">"Je zapnutý hotspot nebo tethering"</string>
+    <string name="upstream_roaming_notification_message" msgid="5229740963392849544">"Při roamingu mohou být účtovány dodatečné poplatky"</string>
 </resources>
diff --git a/Tethering/res/values-mcc310-mnc004-da/strings.xml b/Tethering/res/values-mcc310-mnc004-da/strings.xml
index 92c3ae1..dbca93b 100644
--- a/Tethering/res/values-mcc310-mnc004-da/strings.xml
+++ b/Tethering/res/values-mcc310-mnc004-da/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="5030042590486713460">"Netdeling har ingen internetforbindelse"</string>
-    <string name="no_upstream_notification_message" msgid="3843613362272973447">"Enheder kan ikke oprette forbindelse"</string>
-    <string name="no_upstream_notification_disable_button" msgid="6385491461813507624">"Deaktiver netdeling"</string>
-    <string name="upstream_roaming_notification_title" msgid="3015912166812283303">"Hotspot eller netdeling er aktiveret"</string>
-    <string name="upstream_roaming_notification_message" msgid="6724434706748439902">"Der opkræves muligvis yderligere gebyrer ved roaming"</string>
+    <string name="no_upstream_notification_title" msgid="3584617491053416666">"Netdeling har ingen internetforbindelse"</string>
+    <string name="no_upstream_notification_message" msgid="5626323795587558017">"Enheder kan ikke oprette forbindelse"</string>
+    <string name="no_upstream_notification_disable_button" msgid="868677179945695858">"Deaktiver netdeling"</string>
+    <string name="upstream_roaming_notification_title" msgid="2870229486619751829">"Hotspot eller netdeling er aktiveret"</string>
+    <string name="upstream_roaming_notification_message" msgid="5229740963392849544">"Der opkræves muligvis yderligere gebyrer ved roaming"</string>
 </resources>
diff --git a/Tethering/res/values-mcc310-mnc004-de/strings.xml b/Tethering/res/values-mcc310-mnc004-de/strings.xml
index 967eb4d..139b4e0 100644
--- a/Tethering/res/values-mcc310-mnc004-de/strings.xml
+++ b/Tethering/res/values-mcc310-mnc004-de/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="5030042590486713460">"Tethering hat keinen Internetzugriff"</string>
-    <string name="no_upstream_notification_message" msgid="3843613362272973447">"Geräte können sich nicht verbinden"</string>
-    <string name="no_upstream_notification_disable_button" msgid="6385491461813507624">"Tethering deaktivieren"</string>
-    <string name="upstream_roaming_notification_title" msgid="3015912166812283303">"Hotspot oder Tethering ist aktiviert"</string>
-    <string name="upstream_roaming_notification_message" msgid="6724434706748439902">"Für das Roaming können zusätzliche Gebühren anfallen"</string>
+    <string name="no_upstream_notification_title" msgid="3584617491053416666">"Tethering hat keinen Internetzugriff"</string>
+    <string name="no_upstream_notification_message" msgid="5626323795587558017">"Geräte können sich nicht verbinden"</string>
+    <string name="no_upstream_notification_disable_button" msgid="868677179945695858">"Tethering deaktivieren"</string>
+    <string name="upstream_roaming_notification_title" msgid="2870229486619751829">"Hotspot oder Tethering ist aktiviert"</string>
+    <string name="upstream_roaming_notification_message" msgid="5229740963392849544">"Für das Roaming können zusätzliche Gebühren anfallen"</string>
 </resources>
diff --git a/Tethering/res/values-mcc310-mnc004-el/strings.xml b/Tethering/res/values-mcc310-mnc004-el/strings.xml
index 5fb4974..d778b03 100644
--- a/Tethering/res/values-mcc310-mnc004-el/strings.xml
+++ b/Tethering/res/values-mcc310-mnc004-el/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="5030042590486713460">"Η σύνδεση δεν έχει πρόσβαση στο διαδίκτυο"</string>
-    <string name="no_upstream_notification_message" msgid="3843613362272973447">"Δεν είναι δυνατή η σύνδεση των συσκευών"</string>
-    <string name="no_upstream_notification_disable_button" msgid="6385491461813507624">"Απενεργοποιήστε τη σύνδεση"</string>
-    <string name="upstream_roaming_notification_title" msgid="3015912166812283303">"Ενεργό σημείο πρόσβασης Wi-Fi ή ενεργή σύνδεση"</string>
-    <string name="upstream_roaming_notification_message" msgid="6724434706748439902">"Ενδέχεται να ισχύουν επιπλέον χρεώσεις κατά την περιαγωγή."</string>
+    <string name="no_upstream_notification_title" msgid="3584617491053416666">"Η σύνδεση δεν έχει πρόσβαση στο διαδίκτυο"</string>
+    <string name="no_upstream_notification_message" msgid="5626323795587558017">"Δεν είναι δυνατή η σύνδεση των συσκευών"</string>
+    <string name="no_upstream_notification_disable_button" msgid="868677179945695858">"Απενεργοποίηση σύνδεσης"</string>
+    <string name="upstream_roaming_notification_title" msgid="2870229486619751829">"Ενεργό σημείο πρόσβασης Wi-Fi ή ενεργή σύνδεση"</string>
+    <string name="upstream_roaming_notification_message" msgid="5229740963392849544">"Ενδέχεται να ισχύουν επιπλέον χρεώσεις κατά την περιαγωγή."</string>
 </resources>
diff --git a/Tethering/res/values-mcc310-mnc004-en-rAU/strings.xml b/Tethering/res/values-mcc310-mnc004-en-rAU/strings.xml
index 45647f9..bc68d00 100644
--- a/Tethering/res/values-mcc310-mnc004-en-rAU/strings.xml
+++ b/Tethering/res/values-mcc310-mnc004-en-rAU/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="5030042590486713460">"Tethering has no Internet"</string>
-    <string name="no_upstream_notification_message" msgid="3843613362272973447">"Devices can’t connect"</string>
-    <string name="no_upstream_notification_disable_button" msgid="6385491461813507624">"Turn off tethering"</string>
-    <string name="upstream_roaming_notification_title" msgid="3015912166812283303">"Hotspot or tethering is on"</string>
-    <string name="upstream_roaming_notification_message" msgid="6724434706748439902">"Additional charges may apply while roaming"</string>
+    <string name="no_upstream_notification_title" msgid="3584617491053416666">"Tethering has no Internet"</string>
+    <string name="no_upstream_notification_message" msgid="5626323795587558017">"Devices can\'t connect"</string>
+    <string name="no_upstream_notification_disable_button" msgid="868677179945695858">"Turn off tethering"</string>
+    <string name="upstream_roaming_notification_title" msgid="2870229486619751829">"Hotspot or tethering is on"</string>
+    <string name="upstream_roaming_notification_message" msgid="5229740963392849544">"Additional charges may apply while roaming"</string>
 </resources>
diff --git a/Tethering/res/values-mcc310-mnc004-en-rCA/strings.xml b/Tethering/res/values-mcc310-mnc004-en-rCA/strings.xml
index 45647f9..4f39489 100644
--- a/Tethering/res/values-mcc310-mnc004-en-rCA/strings.xml
+++ b/Tethering/res/values-mcc310-mnc004-en-rCA/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="5030042590486713460">"Tethering has no Internet"</string>
-    <string name="no_upstream_notification_message" msgid="3843613362272973447">"Devices can’t connect"</string>
-    <string name="no_upstream_notification_disable_button" msgid="6385491461813507624">"Turn off tethering"</string>
-    <string name="upstream_roaming_notification_title" msgid="3015912166812283303">"Hotspot or tethering is on"</string>
-    <string name="upstream_roaming_notification_message" msgid="6724434706748439902">"Additional charges may apply while roaming"</string>
+    <string name="no_upstream_notification_title" msgid="3584617491053416666">"Tethering has no internet"</string>
+    <string name="no_upstream_notification_message" msgid="5626323795587558017">"Devices can’t connect"</string>
+    <string name="no_upstream_notification_disable_button" msgid="868677179945695858">"Turn off tethering"</string>
+    <string name="upstream_roaming_notification_title" msgid="2870229486619751829">"Hotspot or tethering is on"</string>
+    <string name="upstream_roaming_notification_message" msgid="5229740963392849544">"Additional charges may apply while roaming"</string>
 </resources>
diff --git a/Tethering/res/values-mcc310-mnc004-en-rGB/strings.xml b/Tethering/res/values-mcc310-mnc004-en-rGB/strings.xml
index 45647f9..bc68d00 100644
--- a/Tethering/res/values-mcc310-mnc004-en-rGB/strings.xml
+++ b/Tethering/res/values-mcc310-mnc004-en-rGB/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="5030042590486713460">"Tethering has no Internet"</string>
-    <string name="no_upstream_notification_message" msgid="3843613362272973447">"Devices can’t connect"</string>
-    <string name="no_upstream_notification_disable_button" msgid="6385491461813507624">"Turn off tethering"</string>
-    <string name="upstream_roaming_notification_title" msgid="3015912166812283303">"Hotspot or tethering is on"</string>
-    <string name="upstream_roaming_notification_message" msgid="6724434706748439902">"Additional charges may apply while roaming"</string>
+    <string name="no_upstream_notification_title" msgid="3584617491053416666">"Tethering has no Internet"</string>
+    <string name="no_upstream_notification_message" msgid="5626323795587558017">"Devices can\'t connect"</string>
+    <string name="no_upstream_notification_disable_button" msgid="868677179945695858">"Turn off tethering"</string>
+    <string name="upstream_roaming_notification_title" msgid="2870229486619751829">"Hotspot or tethering is on"</string>
+    <string name="upstream_roaming_notification_message" msgid="5229740963392849544">"Additional charges may apply while roaming"</string>
 </resources>
diff --git a/Tethering/res/values-mcc310-mnc004-en-rIN/strings.xml b/Tethering/res/values-mcc310-mnc004-en-rIN/strings.xml
index 45647f9..bc68d00 100644
--- a/Tethering/res/values-mcc310-mnc004-en-rIN/strings.xml
+++ b/Tethering/res/values-mcc310-mnc004-en-rIN/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="5030042590486713460">"Tethering has no Internet"</string>
-    <string name="no_upstream_notification_message" msgid="3843613362272973447">"Devices can’t connect"</string>
-    <string name="no_upstream_notification_disable_button" msgid="6385491461813507624">"Turn off tethering"</string>
-    <string name="upstream_roaming_notification_title" msgid="3015912166812283303">"Hotspot or tethering is on"</string>
-    <string name="upstream_roaming_notification_message" msgid="6724434706748439902">"Additional charges may apply while roaming"</string>
+    <string name="no_upstream_notification_title" msgid="3584617491053416666">"Tethering has no Internet"</string>
+    <string name="no_upstream_notification_message" msgid="5626323795587558017">"Devices can\'t connect"</string>
+    <string name="no_upstream_notification_disable_button" msgid="868677179945695858">"Turn off tethering"</string>
+    <string name="upstream_roaming_notification_title" msgid="2870229486619751829">"Hotspot or tethering is on"</string>
+    <string name="upstream_roaming_notification_message" msgid="5229740963392849544">"Additional charges may apply while roaming"</string>
 </resources>
diff --git a/Tethering/res/values-mcc310-mnc004-en-rXC/strings.xml b/Tethering/res/values-mcc310-mnc004-en-rXC/strings.xml
index 7877074..be00edf 100644
--- a/Tethering/res/values-mcc310-mnc004-en-rXC/strings.xml
+++ b/Tethering/res/values-mcc310-mnc004-en-rXC/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="5030042590486713460">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‏‎‏‏‏‎‎‏‏‏‎‎‏‎‎‏‏‎‏‎‎‎‏‎‏‎‏‏‏‏‏‎‎‏‏‎‎‎‎‏‎‏‏‏‏‎‏‎‏‎‎‎‏‏‏‎‏‎‎‎Tethering has no internet‎‏‎‎‏‎"</string>
-    <string name="no_upstream_notification_message" msgid="3843613362272973447">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‏‎‏‎‏‎‏‎‏‏‏‎‏‎‎‎‎‎‏‏‏‏‏‏‏‎‏‎‎‎‏‏‎‎‎‎‎‏‏‏‎‎‏‏‎‏‏‎‎‏‎‏‎‎‎‎‏‏‏‎Devices can’t connect‎‏‎‎‏‎"</string>
-    <string name="no_upstream_notification_disable_button" msgid="6385491461813507624">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‎‎‎‏‎‎‏‏‏‎‏‏‏‎‏‎‎‏‎‏‎‏‎‎‏‏‏‎‎‎‏‏‎‎‏‏‎‏‎‏‏‏‏‎‎‎‏‎‏‏‎‎‎‏‎‏‎‎‎‎Turn off tethering‎‏‎‎‏‎"</string>
-    <string name="upstream_roaming_notification_title" msgid="3015912166812283303">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‎‎‏‏‏‎‏‏‎‏‎‏‎‏‎‏‏‎‎‎‎‏‎‎‎‏‏‎‎‎‎‎‎‎‎‏‎‎‏‏‏‏‏‏‏‏‏‎‎‎‏‏‎‏‎‎‏‏‏‎Hotspot or tethering is on‎‏‎‎‏‎"</string>
-    <string name="upstream_roaming_notification_message" msgid="6724434706748439902">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‏‎‏‎‏‎‏‎‎‎‏‏‏‏‏‏‏‎‏‏‎‏‏‏‏‎‎‏‏‏‏‏‎‏‏‎‎‎‎‏‏‎‎‎‏‏‏‏‏‎‏‎‏‎‏‏‏‏‎‎Additional charges may apply while roaming‎‏‎‎‏‎"</string>
+    <string name="no_upstream_notification_title" msgid="3584617491053416666">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‎‎‏‏‎‏‏‏‏‏‏‎‎‎‏‏‏‏‎‏‎‎‏‏‏‎‏‏‎‏‏‎‎‎‏‏‏‏‎‎‏‎‎‎‎‎‏‎‎‎‎‏‏‎‏‏‎‏‎‎Tethering has no internet‎‏‎‎‏‎"</string>
+    <string name="no_upstream_notification_message" msgid="5626323795587558017">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‏‏‎‎‎‎‏‎‏‎‎‏‎‏‏‎‏‏‏‏‎‏‎‏‏‏‎‏‎‏‏‏‎‏‏‎‏‎‏‎‎‏‎‏‏‏‎‏‎‏‎‏‎‎‎‎‎‎‏‎Devices can’t connect‎‏‎‎‏‎"</string>
+    <string name="no_upstream_notification_disable_button" msgid="868677179945695858">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‏‎‎‎‎‎‎‏‏‏‎‎‎‏‎‏‎‎‏‎‏‎‎‏‎‏‎‏‏‏‎‏‏‏‏‎‎‏‎‎‏‏‎‏‎‏‏‏‎‏‎‎‏‏‏‎‎‏‎‎Turn off tethering‎‏‎‎‏‎"</string>
+    <string name="upstream_roaming_notification_title" msgid="2870229486619751829">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‏‏‏‏‏‎‏‎‏‎‏‎‎‎‏‏‎‏‎‏‎‎‎‎‎‏‎‏‏‎‎‏‏‎‏‏‏‎‏‎‎‏‎‎‏‏‏‎‏‎‏‏‎‎‏‎‏‎‏‎Hotspot or tethering is on‎‏‎‎‏‎"</string>
+    <string name="upstream_roaming_notification_message" msgid="5229740963392849544">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‎‎‎‏‎‎‏‎‎‏‏‏‏‎‎‎‏‎‏‏‎‏‏‎‎‏‎‏‎‏‎‎‏‏‎‎‏‎‏‎‏‏‏‎‎‎‎‎‏‏‎‏‎‎‎‏‎‎‎‎Additional charges may apply while roaming‎‏‎‎‏‎"</string>
 </resources>
diff --git a/Tethering/res/values-mcc310-mnc004-es-rUS/strings.xml b/Tethering/res/values-mcc310-mnc004-es-rUS/strings.xml
index 08edd81..e00a7a0 100644
--- a/Tethering/res/values-mcc310-mnc004-es-rUS/strings.xml
+++ b/Tethering/res/values-mcc310-mnc004-es-rUS/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="5030042590486713460">"La conexión mediante dispositivo móvil no tiene Internet"</string>
-    <string name="no_upstream_notification_message" msgid="3843613362272973447">"No se pueden conectar los dispositivos"</string>
-    <string name="no_upstream_notification_disable_button" msgid="6385491461813507624">"Desactivar conexión mediante dispositivo móvil"</string>
-    <string name="upstream_roaming_notification_title" msgid="3015912166812283303">"Se activó el hotspot o la conexión mediante dispositivo móvil"</string>
-    <string name="upstream_roaming_notification_message" msgid="6724434706748439902">"Es posible que se apliquen cargos adicionales por roaming"</string>
+    <string name="no_upstream_notification_title" msgid="3584617491053416666">"La conexión mediante dispositivo móvil no tiene Internet"</string>
+    <string name="no_upstream_notification_message" msgid="5626323795587558017">"No se pueden conectar los dispositivos"</string>
+    <string name="no_upstream_notification_disable_button" msgid="868677179945695858">"Desactivar conexión mediante dispositivo móvil"</string>
+    <string name="upstream_roaming_notification_title" msgid="2870229486619751829">"Se activó el hotspot o la conexión mediante dispositivo móvil"</string>
+    <string name="upstream_roaming_notification_message" msgid="5229740963392849544">"Es posible que se apliquen cargos adicionales por roaming"</string>
 </resources>
diff --git a/Tethering/res/values-mcc310-mnc004-es/strings.xml b/Tethering/res/values-mcc310-mnc004-es/strings.xml
index 79f51d0..6c7e983 100644
--- a/Tethering/res/values-mcc310-mnc004-es/strings.xml
+++ b/Tethering/res/values-mcc310-mnc004-es/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="5030042590486713460">"La conexión no se puede compartir, porque no hay acceso a Internet"</string>
-    <string name="no_upstream_notification_message" msgid="3843613362272973447">"Los dispositivos no se pueden conectar"</string>
-    <string name="no_upstream_notification_disable_button" msgid="6385491461813507624">"Desactivar conexión compartida"</string>
-    <string name="upstream_roaming_notification_title" msgid="3015912166812283303">"Punto de acceso o conexión compartida activados"</string>
-    <string name="upstream_roaming_notification_message" msgid="6724434706748439902">"Puede que se apliquen cargos adicionales en itinerancia"</string>
+    <string name="no_upstream_notification_title" msgid="3584617491053416666">"La conexión no se puede compartir, porque no hay acceso a Internet"</string>
+    <string name="no_upstream_notification_message" msgid="5626323795587558017">"Los dispositivos no se pueden conectar"</string>
+    <string name="no_upstream_notification_disable_button" msgid="868677179945695858">"Desactivar conexión compartida"</string>
+    <string name="upstream_roaming_notification_title" msgid="2870229486619751829">"Punto de acceso o conexión compartida activados"</string>
+    <string name="upstream_roaming_notification_message" msgid="5229740963392849544">"Pueden aplicarse cargos adicionales en roaming"</string>
 </resources>
diff --git a/Tethering/res/values-mcc310-mnc004-et/strings.xml b/Tethering/res/values-mcc310-mnc004-et/strings.xml
index 2da5f8a..2f108fc 100644
--- a/Tethering/res/values-mcc310-mnc004-et/strings.xml
+++ b/Tethering/res/values-mcc310-mnc004-et/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="5030042590486713460">"Jagamisel puudub internetiühendus"</string>
-    <string name="no_upstream_notification_message" msgid="3843613362272973447">"Seadmed ei saa ühendust luua"</string>
-    <string name="no_upstream_notification_disable_button" msgid="6385491461813507624">"Lülita jagamine välja"</string>
-    <string name="upstream_roaming_notification_title" msgid="3015912166812283303">"Kuumkoht või jagamine on sisse lülitatud"</string>
-    <string name="upstream_roaming_notification_message" msgid="6724434706748439902">"Rändluse kasutamisega võivad kaasneda lisatasud"</string>
+    <string name="no_upstream_notification_title" msgid="3584617491053416666">"Jagamisel puudub internetiühendus"</string>
+    <string name="no_upstream_notification_message" msgid="5626323795587558017">"Seadmed ei saa ühendust luua"</string>
+    <string name="no_upstream_notification_disable_button" msgid="868677179945695858">"Lülita jagamine välja"</string>
+    <string name="upstream_roaming_notification_title" msgid="2870229486619751829">"Kuumkoht või jagamine on sisse lülitatud"</string>
+    <string name="upstream_roaming_notification_message" msgid="5229740963392849544">"Rändluse kasutamisega võivad kaasneda lisatasud"</string>
 </resources>
diff --git a/Tethering/res/values-mcc310-mnc004-eu/strings.xml b/Tethering/res/values-mcc310-mnc004-eu/strings.xml
index 2073f28..c970dd7 100644
--- a/Tethering/res/values-mcc310-mnc004-eu/strings.xml
+++ b/Tethering/res/values-mcc310-mnc004-eu/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="5030042590486713460">"Konexioa partekatzeko aukerak ez du Interneteko konexiorik"</string>
-    <string name="no_upstream_notification_message" msgid="3843613362272973447">"Ezin dira konektatu gailuak"</string>
-    <string name="no_upstream_notification_disable_button" msgid="6385491461813507624">"Desaktibatu konexioa partekatzeko aukera"</string>
-    <string name="upstream_roaming_notification_title" msgid="3015912166812283303">"Wifi-gunea edo konexioa partekatzeko aukera aktibatuta dago"</string>
-    <string name="upstream_roaming_notification_message" msgid="6724434706748439902">"Baliteke kostu gehigarriak ordaindu behar izatea ibiltaritzan"</string>
+    <string name="no_upstream_notification_title" msgid="3584617491053416666">"Konexioa partekatzeko aukerak ez du Interneteko konexiorik"</string>
+    <string name="no_upstream_notification_message" msgid="5626323795587558017">"Ezin dira konektatu gailuak"</string>
+    <string name="no_upstream_notification_disable_button" msgid="868677179945695858">"Desaktibatu konexioa partekatzeko aukera"</string>
+    <string name="upstream_roaming_notification_title" msgid="2870229486619751829">"Wifi-gunea edo konexioa partekatzeko aukera aktibatuta dago"</string>
+    <string name="upstream_roaming_notification_message" msgid="5229740963392849544">"Baliteke tarifa gehigarriak ordaindu behar izatea ibiltaritza erabili bitartean"</string>
 </resources>
diff --git a/Tethering/res/values-mcc310-mnc004-fa/strings.xml b/Tethering/res/values-mcc310-mnc004-fa/strings.xml
index e21b2a0..7333e2f 100644
--- a/Tethering/res/values-mcc310-mnc004-fa/strings.xml
+++ b/Tethering/res/values-mcc310-mnc004-fa/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="5030042590486713460">"«اشتراک‌گذاری اینترنت» به اینترنت دسترسی ندارد"</string>
-    <string name="no_upstream_notification_message" msgid="3843613362272973447">"دستگاه‌ها متصل نمی‌شوند"</string>
-    <string name="no_upstream_notification_disable_button" msgid="6385491461813507624">"خاموش کردن «اشتراک‌گذاری اینترنت»"</string>
-    <string name="upstream_roaming_notification_title" msgid="3015912166812283303">"«نقطه اتصال» یا «اشتراک‌گذاری اینترنت» روشن است"</string>
-    <string name="upstream_roaming_notification_message" msgid="6724434706748439902">"ممکن است درحین فراگردی تغییرات دیگر اعمال شود"</string>
+    <string name="no_upstream_notification_title" msgid="3584617491053416666">"«اشتراک‌گذاری اینترنت» به اینترنت دسترسی ندارد"</string>
+    <string name="no_upstream_notification_message" msgid="5626323795587558017">"دستگاه‌ها متصل نمی‌شوند"</string>
+    <string name="no_upstream_notification_disable_button" msgid="868677179945695858">"خاموش کردن «اشتراک‌گذاری اینترنت»"</string>
+    <string name="upstream_roaming_notification_title" msgid="2870229486619751829">"«نقطه اتصال» یا «اشتراک‌گذاری اینترنت» روشن است"</string>
+    <string name="upstream_roaming_notification_message" msgid="5229740963392849544">"هنگام فراگردی ممکن است هزینه‌های اضافی کسر شود"</string>
 </resources>
diff --git a/Tethering/res/values-mcc310-mnc004-fi/strings.xml b/Tethering/res/values-mcc310-mnc004-fi/strings.xml
index 88b0b13..3faed5b 100644
--- a/Tethering/res/values-mcc310-mnc004-fi/strings.xml
+++ b/Tethering/res/values-mcc310-mnc004-fi/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="5030042590486713460">"Ei jaettavaa internetyhteyttä"</string>
-    <string name="no_upstream_notification_message" msgid="3843613362272973447">"Laitteet eivät voi muodostaa yhteyttä"</string>
-    <string name="no_upstream_notification_disable_button" msgid="6385491461813507624">"Laita yhteyden jakaminen pois päältä"</string>
-    <string name="upstream_roaming_notification_title" msgid="3015912166812283303">"Hotspot tai yhteyden jakaminen on päällä"</string>
-    <string name="upstream_roaming_notification_message" msgid="6724434706748439902">"Roaming voi aiheuttaa lisämaksuja"</string>
+    <string name="no_upstream_notification_title" msgid="3584617491053416666">"Puhelinta ei voi käyttää modeemina, koska sillä ei ole internet-yhteyttä"</string>
+    <string name="no_upstream_notification_message" msgid="5626323795587558017">"Laitteet eivät voi muodostaa yhteyttä"</string>
+    <string name="no_upstream_notification_disable_button" msgid="868677179945695858">"Laita puhelimen käyttäminen modeemina pois päältä"</string>
+    <string name="upstream_roaming_notification_title" msgid="2870229486619751829">"Hotspot tai puhelimen käyttäminen modeemina on päällä"</string>
+    <string name="upstream_roaming_notification_message" msgid="5229740963392849544">"Roaming voi aiheuttaa lisämaksuja"</string>
 </resources>
diff --git a/Tethering/res/values-mcc310-mnc004-fr-rCA/strings.xml b/Tethering/res/values-mcc310-mnc004-fr-rCA/strings.xml
index 3b781bc..0659c40 100644
--- a/Tethering/res/values-mcc310-mnc004-fr-rCA/strings.xml
+++ b/Tethering/res/values-mcc310-mnc004-fr-rCA/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="5030042590486713460">"Le partage de connexion n\'est pas connecté à Internet"</string>
-    <string name="no_upstream_notification_message" msgid="3843613362272973447">"Impossible de connecter les appareils"</string>
-    <string name="no_upstream_notification_disable_button" msgid="6385491461813507624">"Désactiver le partage de connexion"</string>
-    <string name="upstream_roaming_notification_title" msgid="3015912166812283303">"Le point d\'accès ou le partage de connexion est activé"</string>
-    <string name="upstream_roaming_notification_message" msgid="6724434706748439902">"En itinérance, des frais supplémentaires peuvent s\'appliquer"</string>
+    <string name="no_upstream_notification_title" msgid="3584617491053416666">"Le partage de connexion n\'est pas connecté à Internet"</string>
+    <string name="no_upstream_notification_message" msgid="5626323795587558017">"Impossible de connecter les appareils"</string>
+    <string name="no_upstream_notification_disable_button" msgid="868677179945695858">"Désactiver le partage de connexion"</string>
+    <string name="upstream_roaming_notification_title" msgid="2870229486619751829">"Le point d\'accès sans fil ou le partage de connexion est activé"</string>
+    <string name="upstream_roaming_notification_message" msgid="5229740963392849544">"En itinérance, des frais supplémentaires peuvent s\'appliquer"</string>
 </resources>
diff --git a/Tethering/res/values-mcc310-mnc004-fr/strings.xml b/Tethering/res/values-mcc310-mnc004-fr/strings.xml
index 51d7203..26065f8 100644
--- a/Tethering/res/values-mcc310-mnc004-fr/strings.xml
+++ b/Tethering/res/values-mcc310-mnc004-fr/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="5030042590486713460">"Aucune connexion à Internet n\'est disponible pour le partage de connexion"</string>
-    <string name="no_upstream_notification_message" msgid="3843613362272973447">"Impossible de connecter les appareils"</string>
-    <string name="no_upstream_notification_disable_button" msgid="6385491461813507624">"Désactiver le partage de connexion"</string>
-    <string name="upstream_roaming_notification_title" msgid="3015912166812283303">"Le point d\'accès ou le partage de connexion est activé"</string>
-    <string name="upstream_roaming_notification_message" msgid="6724434706748439902">"En itinérance, des frais supplémentaires peuvent s\'appliquer"</string>
+    <string name="no_upstream_notification_title" msgid="3584617491053416666">"Aucune connexion à Internet disponible pour le partage de connexion"</string>
+    <string name="no_upstream_notification_message" msgid="5626323795587558017">"Impossible de connecter les appareils"</string>
+    <string name="no_upstream_notification_disable_button" msgid="868677179945695858">"Désactiver le partage de connexion"</string>
+    <string name="upstream_roaming_notification_title" msgid="2870229486619751829">"Le point d\'accès ou le partage de connexion est activé"</string>
+    <string name="upstream_roaming_notification_message" msgid="5229740963392849544">"En itinérance, des frais supplémentaires peuvent s\'appliquer"</string>
 </resources>
diff --git a/Tethering/res/values-mcc310-mnc004-gl/strings.xml b/Tethering/res/values-mcc310-mnc004-gl/strings.xml
index 008ccb4..924e71b 100644
--- a/Tethering/res/values-mcc310-mnc004-gl/strings.xml
+++ b/Tethering/res/values-mcc310-mnc004-gl/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="5030042590486713460">"A conexión compartida non ten Internet"</string>
-    <string name="no_upstream_notification_message" msgid="3843613362272973447">"Non se puideron conectar os dispositivos"</string>
-    <string name="no_upstream_notification_disable_button" msgid="6385491461813507624">"Desactivar conexión compartida"</string>
-    <string name="upstream_roaming_notification_title" msgid="3015912166812283303">"Está activada a zona wifi ou a conexión compartida"</string>
-    <string name="upstream_roaming_notification_message" msgid="6724434706748439902">"Pódense aplicar cargos adicionais en itinerancia"</string>
+    <string name="no_upstream_notification_title" msgid="3584617491053416666">"A conexión compartida non ten acceso a Internet"</string>
+    <string name="no_upstream_notification_message" msgid="5626323795587558017">"Non se puideron conectar os dispositivos"</string>
+    <string name="no_upstream_notification_disable_button" msgid="868677179945695858">"Desactivar conexión compartida"</string>
+    <string name="upstream_roaming_notification_title" msgid="2870229486619751829">"Está activada a zona wifi ou a conexión compartida"</string>
+    <string name="upstream_roaming_notification_message" msgid="5229740963392849544">"Pódense aplicar cargos adicionais en itinerancia"</string>
 </resources>
diff --git a/Tethering/res/values-mcc310-mnc004-gu/strings.xml b/Tethering/res/values-mcc310-mnc004-gu/strings.xml
index f2e3b4d..ab446df 100644
--- a/Tethering/res/values-mcc310-mnc004-gu/strings.xml
+++ b/Tethering/res/values-mcc310-mnc004-gu/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="5030042590486713460">"ઇન્ટરનેટ શેર કરવાની સુવિધામાં ઇન્ટરનેટ નથી"</string>
-    <string name="no_upstream_notification_message" msgid="3843613362272973447">"ડિવાઇસ કનેક્ટ કરી શકાતા નથી"</string>
-    <string name="no_upstream_notification_disable_button" msgid="6385491461813507624">"ઇન્ટરનેટ શેર કરવાની સુવિધા બંધ કરો"</string>
-    <string name="upstream_roaming_notification_title" msgid="3015912166812283303">"હૉટસ્પૉટ અથવા ઇન્ટરનેટ શેર કરવાની સુવિધા ચાલુ છે"</string>
-    <string name="upstream_roaming_notification_message" msgid="6724434706748439902">"રોમિંગમાં વધારાના શુલ્ક લાગી શકે છે"</string>
+    <string name="no_upstream_notification_title" msgid="3584617491053416666">"ઇન્ટરનેટ શેર કરવાની સુવિધામાં ઇન્ટરનેટ નથી"</string>
+    <string name="no_upstream_notification_message" msgid="5626323795587558017">"ડિવાઇસ કનેક્ટ કરી શકાતા નથી"</string>
+    <string name="no_upstream_notification_disable_button" msgid="868677179945695858">"ઇન્ટરનેટ શેર કરવાની સુવિધા બંધ કરો"</string>
+    <string name="upstream_roaming_notification_title" msgid="2870229486619751829">"હૉટસ્પૉટ અથવા ઇન્ટરનેટ શેર કરવાની સુવિધા ચાલુ છે"</string>
+    <string name="upstream_roaming_notification_message" msgid="5229740963392849544">"રોમિંગ દરમિયાન વધારાના શુલ્ક લાગુ થઈ શકે છે"</string>
 </resources>
diff --git a/Tethering/res/values-mcc310-mnc004-hi/strings.xml b/Tethering/res/values-mcc310-mnc004-hi/strings.xml
index b11839d..073a680 100644
--- a/Tethering/res/values-mcc310-mnc004-hi/strings.xml
+++ b/Tethering/res/values-mcc310-mnc004-hi/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="5030042590486713460">"टेदरिंग से इंटरनेट नहीं चल रहा"</string>
-    <string name="no_upstream_notification_message" msgid="3843613362272973447">"डिवाइस कनेक्ट नहीं हो पा रहे"</string>
-    <string name="no_upstream_notification_disable_button" msgid="6385491461813507624">"टेदरिंग बंद करें"</string>
-    <string name="upstream_roaming_notification_title" msgid="3015912166812283303">"हॉटस्पॉट या टेदरिंग चालू है"</string>
-    <string name="upstream_roaming_notification_message" msgid="6724434706748439902">"रोमिंग के दौरान अतिरिक्त शुल्क लग सकता है"</string>
+    <string name="no_upstream_notification_title" msgid="3584617491053416666">"टेदरिंग से इंटरनेट नहीं चल रहा है"</string>
+    <string name="no_upstream_notification_message" msgid="5626323795587558017">"डिवाइस कनेक्ट नहीं हो पा रहे"</string>
+    <string name="no_upstream_notification_disable_button" msgid="868677179945695858">"टेदरिंग बंद करें"</string>
+    <string name="upstream_roaming_notification_title" msgid="2870229486619751829">"हॉटस्पॉट या टेदरिंग चालू है"</string>
+    <string name="upstream_roaming_notification_message" msgid="5229740963392849544">"रोमिंग के दौरान अतिरिक्त शुल्क काटा जा सकता है"</string>
 </resources>
diff --git a/Tethering/res/values-mcc310-mnc004-hr/strings.xml b/Tethering/res/values-mcc310-mnc004-hr/strings.xml
index 0a5aca2..6cc8415 100644
--- a/Tethering/res/values-mcc310-mnc004-hr/strings.xml
+++ b/Tethering/res/values-mcc310-mnc004-hr/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="5030042590486713460">"Modemsko povezivanje nema internet"</string>
-    <string name="no_upstream_notification_message" msgid="3843613362272973447">"Uređaji se ne mogu povezati"</string>
-    <string name="no_upstream_notification_disable_button" msgid="6385491461813507624">"Isključivanje modemskog povezivanja"</string>
-    <string name="upstream_roaming_notification_title" msgid="3015912166812283303">"Uključena je žarišna točka ili modemsko povezivanje"</string>
-    <string name="upstream_roaming_notification_message" msgid="6724434706748439902">"U roamingu su mogući dodatni troškovi"</string>
+    <string name="no_upstream_notification_title" msgid="3584617491053416666">"Modemsko povezivanje nema internet"</string>
+    <string name="no_upstream_notification_message" msgid="5626323795587558017">"Uređaji se ne mogu povezati"</string>
+    <string name="no_upstream_notification_disable_button" msgid="868677179945695858">"Isključi modemsko povezivanje"</string>
+    <string name="upstream_roaming_notification_title" msgid="2870229486619751829">"Uključena je žarišna točka ili modemsko povezivanje"</string>
+    <string name="upstream_roaming_notification_message" msgid="5229740963392849544">"U roamingu su mogući dodatni troškovi"</string>
 </resources>
diff --git a/Tethering/res/values-mcc310-mnc004-hu/strings.xml b/Tethering/res/values-mcc310-mnc004-hu/strings.xml
index 21c689a..6ab9565 100644
--- a/Tethering/res/values-mcc310-mnc004-hu/strings.xml
+++ b/Tethering/res/values-mcc310-mnc004-hu/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="5030042590486713460">"Nincs internetkapcsolat az internet megosztásához"</string>
-    <string name="no_upstream_notification_message" msgid="3843613362272973447">"Az eszközök nem tudnak csatlakozni"</string>
-    <string name="no_upstream_notification_disable_button" msgid="6385491461813507624">"Internetmegosztás kikapcsolása"</string>
-    <string name="upstream_roaming_notification_title" msgid="3015912166812283303">"A hotspot vagy az internetmegosztás be van kapcsolva"</string>
-    <string name="upstream_roaming_notification_message" msgid="6724434706748439902">"Roaming során további díjak léphetnek fel"</string>
+    <string name="no_upstream_notification_title" msgid="3584617491053416666">"Nincs internetkapcsolat az internet megosztásához"</string>
+    <string name="no_upstream_notification_message" msgid="5626323795587558017">"Az eszközök nem tudnak csatlakozni"</string>
+    <string name="no_upstream_notification_disable_button" msgid="868677179945695858">"Internetmegosztás kikapcsolása"</string>
+    <string name="upstream_roaming_notification_title" msgid="2870229486619751829">"A hotspot vagy az internetmegosztás be van kapcsolva"</string>
+    <string name="upstream_roaming_notification_message" msgid="5229740963392849544">"Roaming során további díjak léphetnek fel"</string>
 </resources>
diff --git a/Tethering/res/values-mcc310-mnc004-hy/strings.xml b/Tethering/res/values-mcc310-mnc004-hy/strings.xml
index 689d928..75b1c3e 100644
--- a/Tethering/res/values-mcc310-mnc004-hy/strings.xml
+++ b/Tethering/res/values-mcc310-mnc004-hy/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="5030042590486713460">"Մոդեմի ռեժիմի կապը բացակայում է"</string>
-    <string name="no_upstream_notification_message" msgid="3843613362272973447">"Չհաջողվեց միացնել սարքը"</string>
-    <string name="no_upstream_notification_disable_button" msgid="6385491461813507624">"Անջատել մոդեմի ռեժիմը"</string>
-    <string name="upstream_roaming_notification_title" msgid="3015912166812283303">"Թեժ կետը կամ մոդեմի ռեժիմը միացված է"</string>
-    <string name="upstream_roaming_notification_message" msgid="6724434706748439902">"Ռոումինգում կարող են լրացուցիչ վճարներ գանձվել"</string>
+    <string name="no_upstream_notification_title" msgid="3584617491053416666">"Մոդեմի ռեժիմի ինտերնետ կապը բացակայում է"</string>
+    <string name="no_upstream_notification_message" msgid="5626323795587558017">"Չհաջողվեց միացնել սարքերը"</string>
+    <string name="no_upstream_notification_disable_button" msgid="868677179945695858">"Անջատել մոդեմի ռեժիմը"</string>
+    <string name="upstream_roaming_notification_title" msgid="2870229486619751829">"Թեժ կետը կամ մոդեմի ռեժիմը միացված է"</string>
+    <string name="upstream_roaming_notification_message" msgid="5229740963392849544">"Ռոումինգում կարող են լրացուցիչ վճարներ գանձվել"</string>
 </resources>
diff --git a/Tethering/res/values-mcc310-mnc004-in/strings.xml b/Tethering/res/values-mcc310-mnc004-in/strings.xml
index a5f4d19..7289d63 100644
--- a/Tethering/res/values-mcc310-mnc004-in/strings.xml
+++ b/Tethering/res/values-mcc310-mnc004-in/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="5030042590486713460">"Tidak ada koneksi internet di tethering"</string>
-    <string name="no_upstream_notification_message" msgid="3843613362272973447">"Perangkat tidak dapat terhubung"</string>
-    <string name="no_upstream_notification_disable_button" msgid="6385491461813507624">"Nonaktifkan tethering"</string>
-    <string name="upstream_roaming_notification_title" msgid="3015912166812283303">"Hotspot atau tethering aktif"</string>
-    <string name="upstream_roaming_notification_message" msgid="6724434706748439902">"Biaya tambahan mungkin berlaku saat roaming"</string>
+    <string name="no_upstream_notification_title" msgid="3584617491053416666">"Tidak ada koneksi internet di tethering"</string>
+    <string name="no_upstream_notification_message" msgid="5626323795587558017">"Perangkat tidak dapat terhubung"</string>
+    <string name="no_upstream_notification_disable_button" msgid="868677179945695858">"Nonaktifkan tethering"</string>
+    <string name="upstream_roaming_notification_title" msgid="2870229486619751829">"Hotspot atau tethering aktif"</string>
+    <string name="upstream_roaming_notification_message" msgid="5229740963392849544">"Biaya tambahan mungkin berlaku saat roaming"</string>
 </resources>
diff --git a/Tethering/res/values-mcc310-mnc004-is/strings.xml b/Tethering/res/values-mcc310-mnc004-is/strings.xml
index fc7e8aa..e2f2f9d 100644
--- a/Tethering/res/values-mcc310-mnc004-is/strings.xml
+++ b/Tethering/res/values-mcc310-mnc004-is/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="5030042590486713460">"Tjóðrun er ekki með internettengingu"</string>
-    <string name="no_upstream_notification_message" msgid="3843613362272973447">"Tæki geta ekki tengst"</string>
-    <string name="no_upstream_notification_disable_button" msgid="6385491461813507624">"Slökkva á tjóðrun"</string>
-    <string name="upstream_roaming_notification_title" msgid="3015912166812283303">"Kveikt er á heitum reit eða tjóðrun"</string>
-    <string name="upstream_roaming_notification_message" msgid="6724434706748439902">"Viðbótargjöld kunna að eiga við í reiki"</string>
+    <string name="no_upstream_notification_title" msgid="3584617491053416666">"Tjóðrun er ekki með internettengingu"</string>
+    <string name="no_upstream_notification_message" msgid="5626323795587558017">"Tæki geta ekki tengst"</string>
+    <string name="no_upstream_notification_disable_button" msgid="868677179945695858">"Slökkva á tjóðrun"</string>
+    <string name="upstream_roaming_notification_title" msgid="2870229486619751829">"Kveikt er á heitum reit eða tjóðrun"</string>
+    <string name="upstream_roaming_notification_message" msgid="5229740963392849544">"Viðbótargjöld kunna að eiga við í reiki"</string>
 </resources>
diff --git a/Tethering/res/values-mcc310-mnc004-it/strings.xml b/Tethering/res/values-mcc310-mnc004-it/strings.xml
index 6456dd1..44805bd 100644
--- a/Tethering/res/values-mcc310-mnc004-it/strings.xml
+++ b/Tethering/res/values-mcc310-mnc004-it/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="5030042590486713460">"Nessuna connessione a Internet per il tethering"</string>
-    <string name="no_upstream_notification_message" msgid="3843613362272973447">"Impossibile connettere i dispositivi"</string>
-    <string name="no_upstream_notification_disable_button" msgid="6385491461813507624">"Disattiva il tethering"</string>
-    <string name="upstream_roaming_notification_title" msgid="3015912166812283303">"Hotspot o tethering attivi"</string>
-    <string name="upstream_roaming_notification_message" msgid="6724434706748439902">"Potrebbero essere applicati costi aggiuntivi durante il roaming"</string>
+    <string name="no_upstream_notification_title" msgid="3584617491053416666">"Nessuna connessione a internet per il tethering"</string>
+    <string name="no_upstream_notification_message" msgid="5626323795587558017">"Impossibile connettere i dispositivi"</string>
+    <string name="no_upstream_notification_disable_button" msgid="868677179945695858">"Disattiva il tethering"</string>
+    <string name="upstream_roaming_notification_title" msgid="2870229486619751829">"Hotspot o tethering attivo"</string>
+    <string name="upstream_roaming_notification_message" msgid="5229740963392849544">"Potrebbero essere applicati costi aggiuntivi durante il roaming"</string>
 </resources>
diff --git a/Tethering/res/values-mcc310-mnc004-iw/strings.xml b/Tethering/res/values-mcc310-mnc004-iw/strings.xml
index 46b24bd..0618169 100644
--- a/Tethering/res/values-mcc310-mnc004-iw/strings.xml
+++ b/Tethering/res/values-mcc310-mnc004-iw/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="5030042590486713460">"אי אפשר להפעיל את תכונת שיתוף האינטרנט בין מכשירים כי אין חיבור לאינטרנט"</string>
-    <string name="no_upstream_notification_message" msgid="3843613362272973447">"למכשירים אין אפשרות להתחבר"</string>
-    <string name="no_upstream_notification_disable_button" msgid="6385491461813507624">"השבתה של שיתוף האינטרנט בין מכשירים"</string>
-    <string name="upstream_roaming_notification_title" msgid="3015912166812283303">"תכונת הנקודה לשיתוף אינטרנט או תכונת שיתוף האינטרנט בין מכשירים פועלת"</string>
-    <string name="upstream_roaming_notification_message" msgid="6724434706748439902">"ייתכנו חיובים נוספים בעת נדידה"</string>
+    <string name="no_upstream_notification_title" msgid="3584617491053416666">"אי אפשר להפעיל את תכונת שיתוף האינטרנט בין מכשירים כי אין חיבור לאינטרנט"</string>
+    <string name="no_upstream_notification_message" msgid="5626323795587558017">"למכשירים אין אפשרות להתחבר"</string>
+    <string name="no_upstream_notification_disable_button" msgid="868677179945695858">"השבתה של שיתוף האינטרנט בין מכשירים"</string>
+    <string name="upstream_roaming_notification_title" msgid="2870229486619751829">"תכונת הנקודה לשיתוף אינטרנט או תכונת שיתוף האינטרנט בין מכשירים פועלת"</string>
+    <string name="upstream_roaming_notification_message" msgid="5229740963392849544">"ייתכנו חיובים נוספים במהלך נדידה"</string>
 </resources>
diff --git a/Tethering/res/values-mcc310-mnc004-ja/strings.xml b/Tethering/res/values-mcc310-mnc004-ja/strings.xml
index e6eb277..344167d 100644
--- a/Tethering/res/values-mcc310-mnc004-ja/strings.xml
+++ b/Tethering/res/values-mcc310-mnc004-ja/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="5030042590486713460">"テザリングがインターネットに接続されていません"</string>
-    <string name="no_upstream_notification_message" msgid="3843613362272973447">"デバイスを接続できません"</string>
-    <string name="no_upstream_notification_disable_button" msgid="6385491461813507624">"テザリングを OFF にする"</string>
-    <string name="upstream_roaming_notification_title" msgid="3015912166812283303">"アクセス ポイントまたはテザリングが ON です"</string>
-    <string name="upstream_roaming_notification_message" msgid="6724434706748439902">"ローミング時に追加料金が発生することがあります"</string>
+    <string name="no_upstream_notification_title" msgid="3584617491053416666">"テザリングがインターネットに接続されていません"</string>
+    <string name="no_upstream_notification_message" msgid="5626323795587558017">"デバイスを接続できません"</string>
+    <string name="no_upstream_notification_disable_button" msgid="868677179945695858">"テザリングを OFF にする"</string>
+    <string name="upstream_roaming_notification_title" msgid="2870229486619751829">"アクセス ポイントまたはテザリングが ON です"</string>
+    <string name="upstream_roaming_notification_message" msgid="5229740963392849544">"ローミング時に追加料金が発生することがあります"</string>
 </resources>
diff --git a/Tethering/res/values-mcc310-mnc004-ka/strings.xml b/Tethering/res/values-mcc310-mnc004-ka/strings.xml
index aeddd71..20db7fc 100644
--- a/Tethering/res/values-mcc310-mnc004-ka/strings.xml
+++ b/Tethering/res/values-mcc310-mnc004-ka/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="5030042590486713460">"ტეტერინგს არ აქვს ინტერნეტზე წვდომა"</string>
-    <string name="no_upstream_notification_message" msgid="3843613362272973447">"მოწყობილობები ვერ ახერხებენ დაკავშირებას"</string>
-    <string name="no_upstream_notification_disable_button" msgid="6385491461813507624">"ტეტერინგის გამორთვა"</string>
-    <string name="upstream_roaming_notification_title" msgid="3015912166812283303">"ჩართულია უსადენო ქსელი ან ტეტერინგი"</string>
-    <string name="upstream_roaming_notification_message" msgid="6724434706748439902">"როუმინგის გამოყენებისას შეიძლება ჩამოგეჭრათ დამატებითი საფასური"</string>
+    <string name="no_upstream_notification_title" msgid="3584617491053416666">"ტეტერინგს არ აქვს ინტერნეტზე წვდომა"</string>
+    <string name="no_upstream_notification_message" msgid="5626323795587558017">"მოწყობილობები ვერ ახერხებენ დაკავშირებას"</string>
+    <string name="no_upstream_notification_disable_button" msgid="868677179945695858">"ტეტერინგის გამორთვა"</string>
+    <string name="upstream_roaming_notification_title" msgid="2870229486619751829">"ჩართულია უსადენო ქსელი ან ტეტერინგი"</string>
+    <string name="upstream_roaming_notification_message" msgid="5229740963392849544">"როუმინგის გამოყენებისას შეიძლება ჩამოგეჭრათ დამატებითი საფასური"</string>
 </resources>
diff --git a/Tethering/res/values-mcc310-mnc004-kk/strings.xml b/Tethering/res/values-mcc310-mnc004-kk/strings.xml
index 255f0a2..35f1738 100644
--- a/Tethering/res/values-mcc310-mnc004-kk/strings.xml
+++ b/Tethering/res/values-mcc310-mnc004-kk/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="5030042590486713460">"Тетеринг режимі интернет байланысынсыз пайдаланылуда"</string>
-    <string name="no_upstream_notification_message" msgid="3843613362272973447">"Құрылғыларды байланыстыру мүмкін емес"</string>
-    <string name="no_upstream_notification_disable_button" msgid="6385491461813507624">"Тетерингіні өшіру"</string>
-    <string name="upstream_roaming_notification_title" msgid="3015912166812283303">"Хотспот немесе тетеринг қосулы"</string>
-    <string name="upstream_roaming_notification_message" msgid="6724434706748439902">"Роуминг кезінде қосымша ақы алынуы мүмкін."</string>
+    <string name="no_upstream_notification_title" msgid="3584617491053416666">"Тетеринг кезінде интернет байланысы жоқ"</string>
+    <string name="no_upstream_notification_message" msgid="5626323795587558017">"Құрылғыларды байланыстыру мүмкін емес"</string>
+    <string name="no_upstream_notification_disable_button" msgid="868677179945695858">"Тетерингіні өшіру"</string>
+    <string name="upstream_roaming_notification_title" msgid="2870229486619751829">"Хотспот немесе тетеринг қосулы"</string>
+    <string name="upstream_roaming_notification_message" msgid="5229740963392849544">"Роуминг кезінде қосымша ақы алынуы мүмкін."</string>
 </resources>
diff --git a/Tethering/res/values-mcc310-mnc004-km/strings.xml b/Tethering/res/values-mcc310-mnc004-km/strings.xml
index 2bceb1c..2af80b1 100644
--- a/Tethering/res/values-mcc310-mnc004-km/strings.xml
+++ b/Tethering/res/values-mcc310-mnc004-km/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="5030042590486713460">"ការភ្ជាប់​មិនមានអ៊ីនធឺណិត​ទេ"</string>
-    <string name="no_upstream_notification_message" msgid="3843613362272973447">"មិនអាច​ភ្ជាប់ឧបករណ៍​បានទេ"</string>
-    <string name="no_upstream_notification_disable_button" msgid="6385491461813507624">"បិទការភ្ជាប់"</string>
-    <string name="upstream_roaming_notification_title" msgid="3015912166812283303">"ហតស្ប៉ត ឬការភ្ជាប់​ត្រូវបានបើក"</string>
-    <string name="upstream_roaming_notification_message" msgid="6724434706748439902">"អាចមាន​ការគិតថ្លៃ​បន្ថែម នៅពេល​រ៉ូមីង"</string>
+    <string name="no_upstream_notification_title" msgid="3584617491053416666">"ការភ្ជាប់​មិនមានអ៊ីនធឺណិត​ទេ"</string>
+    <string name="no_upstream_notification_message" msgid="5626323795587558017">"មិនអាច​ភ្ជាប់ឧបករណ៍​បានទេ"</string>
+    <string name="no_upstream_notification_disable_button" msgid="868677179945695858">"បិទការភ្ជាប់"</string>
+    <string name="upstream_roaming_notification_title" msgid="2870229486619751829">"ហតស្ប៉ត ឬការភ្ជាប់​ត្រូវបានបើក"</string>
+    <string name="upstream_roaming_notification_message" msgid="5229740963392849544">"អាចមាន​ការគិតថ្លៃ​បន្ថែម នៅពេល​រ៉ូមីង"</string>
 </resources>
diff --git a/Tethering/res/values-mcc310-mnc004-kn/strings.xml b/Tethering/res/values-mcc310-mnc004-kn/strings.xml
index ed76930..16c55d0 100644
--- a/Tethering/res/values-mcc310-mnc004-kn/strings.xml
+++ b/Tethering/res/values-mcc310-mnc004-kn/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="5030042590486713460">"ಟೆಥರಿಂಗ್‌ ಯಾವುದೇ ಇಂಟರ್ನೆಟ್ ಕನೆಕ್ಷನ್ ಹೊಂದಿಲ್ಲ"</string>
-    <string name="no_upstream_notification_message" msgid="3843613362272973447">"ಸಾಧನಗಳನ್ನು ಕನೆಕ್ಟ್ ಮಾಡಲು ಸಾಧ್ಯವಿಲ್ಲ"</string>
-    <string name="no_upstream_notification_disable_button" msgid="6385491461813507624">"ಟೆಥರಿಂಗ್‌ ಆಫ್ ಮಾಡಿ"</string>
-    <string name="upstream_roaming_notification_title" msgid="3015912166812283303">"ಹಾಟ್‌ಸ್ಪಾಟ್ ಅಥವಾ ಟೆಥರಿಂಗ್‌ ಆನ್ ಆಗಿದೆ"</string>
-    <string name="upstream_roaming_notification_message" msgid="6724434706748439902">"ರೋಮಿಂಗ್‌ನಲ್ಲಿರುವಾಗ ಹೆಚ್ಚುವರಿ ಶುಲ್ಕಗಳು ಅನ್ವಯವಾಗಬಹುದು"</string>
+    <string name="no_upstream_notification_title" msgid="3584617491053416666">"ಟೆಥರಿಂಗ್‌ ಯಾವುದೇ ಇಂಟರ್ನೆಟ್ ಕನೆಕ್ಷನ್ ಹೊಂದಿಲ್ಲ"</string>
+    <string name="no_upstream_notification_message" msgid="5626323795587558017">"ಸಾಧನಗಳನ್ನು ಕನೆಕ್ಟ್ ಮಾಡಲು ಸಾಧ್ಯವಿಲ್ಲ"</string>
+    <string name="no_upstream_notification_disable_button" msgid="868677179945695858">"ಟೆಥರಿಂಗ್‌ ಆಫ್ ಮಾಡಿ"</string>
+    <string name="upstream_roaming_notification_title" msgid="2870229486619751829">"ಹಾಟ್‌ಸ್ಪಾಟ್ ಅಥವಾ ಟೆಥರಿಂಗ್‌ ಆನ್ ಆಗಿದೆ"</string>
+    <string name="upstream_roaming_notification_message" msgid="5229740963392849544">"ರೋಮಿಂಗ್‌ನಲ್ಲಿರುವಾಗ ಹೆಚ್ಚುವರಿ ಶುಲ್ಕಗಳು ಅನ್ವಯವಾಗಬಹುದು"</string>
 </resources>
diff --git a/Tethering/res/values-mcc310-mnc004-ko/strings.xml b/Tethering/res/values-mcc310-mnc004-ko/strings.xml
index 6e50494..619504f 100644
--- a/Tethering/res/values-mcc310-mnc004-ko/strings.xml
+++ b/Tethering/res/values-mcc310-mnc004-ko/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="5030042590486713460">"테더링으로 인터넷을 사용할 수 없음"</string>
-    <string name="no_upstream_notification_message" msgid="3843613362272973447">"기기에서 연결할 수 없음"</string>
-    <string name="no_upstream_notification_disable_button" msgid="6385491461813507624">"테더링 사용 중지"</string>
-    <string name="upstream_roaming_notification_title" msgid="3015912166812283303">"핫스팟 또는 테더링 켜짐"</string>
-    <string name="upstream_roaming_notification_message" msgid="6724434706748439902">"로밍 중에는 추가 요금이 발생할 수 있습니다."</string>
+    <string name="no_upstream_notification_title" msgid="3584617491053416666">"테더링으로 인터넷을 사용할 수 없음"</string>
+    <string name="no_upstream_notification_message" msgid="5626323795587558017">"기기에서 연결할 수 없음"</string>
+    <string name="no_upstream_notification_disable_button" msgid="868677179945695858">"테더링 사용 중지"</string>
+    <string name="upstream_roaming_notification_title" msgid="2870229486619751829">"핫스팟 또는 테더링이 켜짐"</string>
+    <string name="upstream_roaming_notification_message" msgid="5229740963392849544">"로밍 중에는 추가 요금이 부과될 수 있습니다."</string>
 </resources>
diff --git a/Tethering/res/values-mcc310-mnc004-ky/strings.xml b/Tethering/res/values-mcc310-mnc004-ky/strings.xml
index d68128b..f52dd90 100644
--- a/Tethering/res/values-mcc310-mnc004-ky/strings.xml
+++ b/Tethering/res/values-mcc310-mnc004-ky/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="5030042590486713460">"Модем режими Интернети жок колдонулууда"</string>
-    <string name="no_upstream_notification_message" msgid="3843613362272973447">"Түзмөктөр туташпай жатат"</string>
-    <string name="no_upstream_notification_disable_button" msgid="6385491461813507624">"Модем режимин өчүрүү"</string>
-    <string name="upstream_roaming_notification_title" msgid="3015912166812283303">"Байланыш түйүнү же модем режими күйүк"</string>
-    <string name="upstream_roaming_notification_message" msgid="6724434706748439902">"Роумингде кошумча акы алынышы мүмкүн"</string>
+    <string name="no_upstream_notification_title" msgid="3584617491053416666">"Модем режими Интернети жок колдонулууда"</string>
+    <string name="no_upstream_notification_message" msgid="5626323795587558017">"Түзмөктөр туташпай жатат"</string>
+    <string name="no_upstream_notification_disable_button" msgid="868677179945695858">"Модем режимин өчүрүү"</string>
+    <string name="upstream_roaming_notification_title" msgid="2870229486619751829">"Байланыш түйүнү же модем режими күйүк"</string>
+    <string name="upstream_roaming_notification_message" msgid="5229740963392849544">"Роумингде кошумча акы алынышы мүмкүн"</string>
 </resources>
diff --git a/Tethering/res/values-mcc310-mnc004-lo/strings.xml b/Tethering/res/values-mcc310-mnc004-lo/strings.xml
index 03e134a..d3184f7 100644
--- a/Tethering/res/values-mcc310-mnc004-lo/strings.xml
+++ b/Tethering/res/values-mcc310-mnc004-lo/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="5030042590486713460">"ການປ່ອຍສັນຍານບໍ່ມີອິນເຕີເນັດ"</string>
-    <string name="no_upstream_notification_message" msgid="3843613362272973447">"ອຸປະກອນບໍ່ສາມາດເຊື່ອມຕໍ່ໄດ້"</string>
-    <string name="no_upstream_notification_disable_button" msgid="6385491461813507624">"ປິດການປ່ອຍສັນຍານ"</string>
-    <string name="upstream_roaming_notification_title" msgid="3015912166812283303">"ເປີດໃຊ້ຮັອດສະປອດ ຫຼື ການປ່ອຍສັນຍານຢູ່"</string>
-    <string name="upstream_roaming_notification_message" msgid="6724434706748439902">"ອາດມີຄ່າໃຊ້ຈ່າຍເພີ່ມເຕີມໃນລະຫວ່າງການໂຣມມິງ"</string>
+    <string name="no_upstream_notification_title" msgid="3584617491053416666">"ການປ່ອຍສັນຍານບໍ່ມີອິນເຕີເນັດ"</string>
+    <string name="no_upstream_notification_message" msgid="5626323795587558017">"ອຸປະກອນບໍ່ສາມາດເຊື່ອມຕໍ່ໄດ້"</string>
+    <string name="no_upstream_notification_disable_button" msgid="868677179945695858">"ປິດການປ່ອຍສັນຍານ"</string>
+    <string name="upstream_roaming_notification_title" msgid="2870229486619751829">"ຮັອດສະປອດ ຫຼື ການປ່ອຍສັນຍານເປີດຢູ່"</string>
+    <string name="upstream_roaming_notification_message" msgid="5229740963392849544">"ອາດມີຄ່າບໍລິການເພີ່ມເຕີມໃນລະຫວ່າງການໂຣມມິງ"</string>
 </resources>
diff --git a/Tethering/res/values-mcc310-mnc004-lt/strings.xml b/Tethering/res/values-mcc310-mnc004-lt/strings.xml
index 652cedc..a07644d 100644
--- a/Tethering/res/values-mcc310-mnc004-lt/strings.xml
+++ b/Tethering/res/values-mcc310-mnc004-lt/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="5030042590486713460">"Nėra įrenginio kaip modemo naudojimo interneto ryšio"</string>
-    <string name="no_upstream_notification_message" msgid="3843613362272973447">"Nepavyko susieti įrenginių"</string>
-    <string name="no_upstream_notification_disable_button" msgid="6385491461813507624">"Išjungti įrenginio kaip modemo naudojimą"</string>
-    <string name="upstream_roaming_notification_title" msgid="3015912166812283303">"Įjungtas viešosios interneto prieigos taškas arba įrenginio kaip modemo naudojimas"</string>
-    <string name="upstream_roaming_notification_message" msgid="6724434706748439902">"Veikiant tarptinkliniam ryšiui gali būti taikomi papildomi mokesčiai"</string>
+    <string name="no_upstream_notification_title" msgid="3584617491053416666">"Nėra įrenginio kaip modemo naudojimo interneto ryšio"</string>
+    <string name="no_upstream_notification_message" msgid="5626323795587558017">"Nepavyko susieti įrenginių"</string>
+    <string name="no_upstream_notification_disable_button" msgid="868677179945695858">"Išjungti įrenginio kaip modemo naudojimą"</string>
+    <string name="upstream_roaming_notification_title" msgid="2870229486619751829">"Įjungtas viešosios interneto prieigos taškas arba įrenginio kaip modemo naudojimas"</string>
+    <string name="upstream_roaming_notification_message" msgid="5229740963392849544">"Veikiant tarptinkliniam ryšiui gali būti taikomi papildomi mokesčiai"</string>
 </resources>
diff --git a/Tethering/res/values-mcc310-mnc004-lv/strings.xml b/Tethering/res/values-mcc310-mnc004-lv/strings.xml
index 2219722..5090ecf 100644
--- a/Tethering/res/values-mcc310-mnc004-lv/strings.xml
+++ b/Tethering/res/values-mcc310-mnc004-lv/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="5030042590486713460">"Piesaistei nav interneta savienojuma"</string>
-    <string name="no_upstream_notification_message" msgid="3843613362272973447">"Nevar savienot ierīces"</string>
-    <string name="no_upstream_notification_disable_button" msgid="6385491461813507624">"Izslēgt piesaisti"</string>
-    <string name="upstream_roaming_notification_title" msgid="3015912166812283303">"Ir ieslēgts tīklājs vai piesaiste"</string>
-    <string name="upstream_roaming_notification_message" msgid="6724434706748439902">"Viesabonēšanas laikā var tikt piemērota papildu samaksa"</string>
+    <string name="no_upstream_notification_title" msgid="3584617491053416666">"Piesaistei nav interneta savienojuma"</string>
+    <string name="no_upstream_notification_message" msgid="5626323795587558017">"Nevar savienot ierīces"</string>
+    <string name="no_upstream_notification_disable_button" msgid="868677179945695858">"Izslēgt piesaisti"</string>
+    <string name="upstream_roaming_notification_title" msgid="2870229486619751829">"Ir ieslēgts tīklājs vai piesaiste"</string>
+    <string name="upstream_roaming_notification_message" msgid="5229740963392849544">"Viesabonēšanas laikā var tikt piemērota papildu maksa."</string>
 </resources>
diff --git a/Tethering/res/values-mcc310-mnc004-mk/strings.xml b/Tethering/res/values-mcc310-mnc004-mk/strings.xml
index 227f9e3..c95c80e 100644
--- a/Tethering/res/values-mcc310-mnc004-mk/strings.xml
+++ b/Tethering/res/values-mcc310-mnc004-mk/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="5030042590486713460">"Нема интернет преку мобилен"</string>
-    <string name="no_upstream_notification_message" msgid="3843613362272973447">"Уредите не може да се поврзат"</string>
-    <string name="no_upstream_notification_disable_button" msgid="6385491461813507624">"Исклучи интернет преку мобилен"</string>
-    <string name="upstream_roaming_notification_title" msgid="3015912166812283303">"Точката на пристап или интернетот преку мобилен е вклучен"</string>
-    <string name="upstream_roaming_notification_message" msgid="6724434706748439902">"При роаминг може да се наплатат дополнителни трошоци"</string>
+    <string name="no_upstream_notification_title" msgid="3584617491053416666">"Нема интернет преку мобилен"</string>
+    <string name="no_upstream_notification_message" msgid="5626323795587558017">"Уредите не може да се поврзат"</string>
+    <string name="no_upstream_notification_disable_button" msgid="868677179945695858">"Исклучи интернет преку мобилен"</string>
+    <string name="upstream_roaming_notification_title" msgid="2870229486619751829">"Вклучено: точка на пристап или интернет преку мобилен"</string>
+    <string name="upstream_roaming_notification_message" msgid="5229740963392849544">"При роаминг може да се наплатат дополнителни трошоци"</string>
 </resources>
diff --git a/Tethering/res/values-mcc310-mnc004-ml/strings.xml b/Tethering/res/values-mcc310-mnc004-ml/strings.xml
index ec43885..7bad5c1 100644
--- a/Tethering/res/values-mcc310-mnc004-ml/strings.xml
+++ b/Tethering/res/values-mcc310-mnc004-ml/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="5030042590486713460">"ടെതറിംഗിന് ഇന്റർനെറ്റ് ഇല്ല"</string>
-    <string name="no_upstream_notification_message" msgid="3843613362272973447">"ഉപകരണങ്ങൾ കണക്റ്റ് ചെയ്യാനാവില്ല"</string>
-    <string name="no_upstream_notification_disable_button" msgid="6385491461813507624">"ടെതറിംഗ് ഓഫാക്കുക"</string>
-    <string name="upstream_roaming_notification_title" msgid="3015912166812283303">"ഹോട്ട്‌സ്‌പോട്ട് അല്ലെങ്കിൽ ടെതറിംഗ് ഓണാണ്"</string>
-    <string name="upstream_roaming_notification_message" msgid="6724434706748439902">"റോമിംഗ് ചെയ്യുമ്പോൾ അധിക നിരക്കുകൾ ബാധകമായേക്കാം"</string>
+    <string name="no_upstream_notification_title" msgid="3584617491053416666">"ടെതറിംഗിന് ഇന്റർനെറ്റ് ഇല്ല"</string>
+    <string name="no_upstream_notification_message" msgid="5626323795587558017">"ഉപകരണങ്ങൾ കണക്റ്റ് ചെയ്യാനാവില്ല"</string>
+    <string name="no_upstream_notification_disable_button" msgid="868677179945695858">"ടെതറിംഗ് ഓഫാക്കുക"</string>
+    <string name="upstream_roaming_notification_title" msgid="2870229486619751829">"ഹോട്ട്‌സ്‌പോട്ട് അല്ലെങ്കിൽ ടെതറിംഗ് ഓണാണ്"</string>
+    <string name="upstream_roaming_notification_message" msgid="5229740963392849544">"റോമിംഗ് ചെയ്യുമ്പോൾ അധിക നിരക്കുകൾ ബാധകമായേക്കാം"</string>
 </resources>
diff --git a/Tethering/res/values-mcc310-mnc004-mn/strings.xml b/Tethering/res/values-mcc310-mnc004-mn/strings.xml
index e263573..ff76236 100644
--- a/Tethering/res/values-mcc310-mnc004-mn/strings.xml
+++ b/Tethering/res/values-mcc310-mnc004-mn/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="5030042590486713460">"Модемд интернэт алга байна"</string>
-    <string name="no_upstream_notification_message" msgid="3843613362272973447">"Төхөөрөмжүүд холбогдох боломжгүй байна"</string>
-    <string name="no_upstream_notification_disable_button" msgid="6385491461813507624">"Модем болгохыг унтраах"</string>
-    <string name="upstream_roaming_notification_title" msgid="3015912166812283303">"Сүлжээний цэг эсвэл модем болгох асаалттай байна"</string>
-    <string name="upstream_roaming_notification_message" msgid="6724434706748439902">"Роумингийн үеэр нэмэлт төлбөр нэхэмжилж болзошгүй"</string>
+    <string name="no_upstream_notification_title" msgid="3584617491053416666">"Модем болгоход ямар ч интернэт байхгүй байна"</string>
+    <string name="no_upstream_notification_message" msgid="5626323795587558017">"Төхөөрөмжүүд холбогдох боломжгүй байна"</string>
+    <string name="no_upstream_notification_disable_button" msgid="868677179945695858">"Модем болгохыг унтраах"</string>
+    <string name="upstream_roaming_notification_title" msgid="2870229486619751829">"Сүлжээний цэг эсвэл модем болгох асаалттай байна"</string>
+    <string name="upstream_roaming_notification_message" msgid="5229740963392849544">"Роумингийн үеэр нэмэлт төлбөр тооцогдож магадгүй"</string>
 </resources>
diff --git a/Tethering/res/values-mcc310-mnc004-mr/strings.xml b/Tethering/res/values-mcc310-mnc004-mr/strings.xml
index adf845d..1754dd4 100644
--- a/Tethering/res/values-mcc310-mnc004-mr/strings.xml
+++ b/Tethering/res/values-mcc310-mnc004-mr/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="5030042590486713460">"टेदरिंगला इंटरनेट नाही"</string>
-    <string name="no_upstream_notification_message" msgid="3843613362272973447">"डिव्हाइस कनेक्ट होऊ शकत नाहीत"</string>
-    <string name="no_upstream_notification_disable_button" msgid="6385491461813507624">"टेदरिंग बंद करा"</string>
-    <string name="upstream_roaming_notification_title" msgid="3015912166812283303">"हॉटस्पॉट किंवा टेदरिंग सुरू आहे"</string>
-    <string name="upstream_roaming_notification_message" msgid="6724434706748439902">"रोमिंगदरम्यान अतिरिक्त शुल्क लागू होऊ शकतात"</string>
+    <string name="no_upstream_notification_title" msgid="3584617491053416666">"टेदरिंगसाठी इंटरनेट नाही"</string>
+    <string name="no_upstream_notification_message" msgid="5626323795587558017">"डिव्हाइस कनेक्ट होऊ शकत नाहीत"</string>
+    <string name="no_upstream_notification_disable_button" msgid="868677179945695858">"टेदरिंग बंद करा"</string>
+    <string name="upstream_roaming_notification_title" msgid="2870229486619751829">"हॉटस्पॉट किंवा टेदरिंग सुरू आहे"</string>
+    <string name="upstream_roaming_notification_message" msgid="5229740963392849544">"रोमिंगदरम्यान अतिरिक्त शुल्के लागू होऊ शकतात"</string>
 </resources>
diff --git a/Tethering/res/values-mcc310-mnc004-ms/strings.xml b/Tethering/res/values-mcc310-mnc004-ms/strings.xml
index f65c451..343e6fa 100644
--- a/Tethering/res/values-mcc310-mnc004-ms/strings.xml
+++ b/Tethering/res/values-mcc310-mnc004-ms/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="5030042590486713460">"Penambatan tiada Internet"</string>
-    <string name="no_upstream_notification_message" msgid="3843613362272973447">"Peranti tidak dapat disambungkan"</string>
-    <string name="no_upstream_notification_disable_button" msgid="6385491461813507624">"Matikan penambatan"</string>
-    <string name="upstream_roaming_notification_title" msgid="3015912166812283303">"Tempat liputan atau penambatan dihidupkan"</string>
-    <string name="upstream_roaming_notification_message" msgid="6724434706748439902">"Caj tambahan mungkin digunakan semasa perayauan"</string>
+    <string name="no_upstream_notification_title" msgid="3584617491053416666">"Penambatan tiada Internet"</string>
+    <string name="no_upstream_notification_message" msgid="5626323795587558017">"Peranti tidak dapat disambungkan"</string>
+    <string name="no_upstream_notification_disable_button" msgid="868677179945695858">"Matikan penambatan"</string>
+    <string name="upstream_roaming_notification_title" msgid="2870229486619751829">"Tempat liputan atau penambatan dihidupkan"</string>
+    <string name="upstream_roaming_notification_message" msgid="5229740963392849544">"Caj tambahan boleh dikenakan semasa perayauan"</string>
 </resources>
diff --git a/Tethering/res/values-mcc310-mnc004-my/strings.xml b/Tethering/res/values-mcc310-mnc004-my/strings.xml
index 4118e77..152f468 100644
--- a/Tethering/res/values-mcc310-mnc004-my/strings.xml
+++ b/Tethering/res/values-mcc310-mnc004-my/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="5030042590486713460">"မိုဘိုင်းဖုန်းသုံး ချိတ်ဆက်မျှဝေခြင်းတွင် အင်တာနက် မရှိပါ"</string>
-    <string name="no_upstream_notification_message" msgid="3843613362272973447">"စက်များ ချိတ်ဆက်၍ မရပါ"</string>
-    <string name="no_upstream_notification_disable_button" msgid="6385491461813507624">"မိုဘိုင်းဖုန်းသုံး ချိတ်ဆက်မျှဝေခြင်း ပိတ်ရန်"</string>
-    <string name="upstream_roaming_notification_title" msgid="3015912166812283303">"ဟော့စပေါ့ (သို့) မိုဘိုင်းဖုန်းသုံး ချိတ်ဆက်မျှဝေခြင်း ဖွင့်ထားသည်"</string>
-    <string name="upstream_roaming_notification_message" msgid="6724434706748439902">"ပြင်ပကွန်ရက်နှင့် ချိတ်ဆက်သည့်အခါ နောက်ထပ်ကျသင့်မှုများ ရှိနိုင်သည်"</string>
+    <string name="no_upstream_notification_title" msgid="3584617491053416666">"မိုဘိုင်းသုံး၍ ချိတ်ဆက်ခြင်းတွင် အင်တာနက် မရှိပါ"</string>
+    <string name="no_upstream_notification_message" msgid="5626323795587558017">"စက်ပစ္စည်းများကို ချိတ်ဆက်၍မရပါ"</string>
+    <string name="no_upstream_notification_disable_button" msgid="868677179945695858">"မိုဘိုင်းသုံး၍ ချိတ်ဆက်ခြင်း ပိတ်ရန်"</string>
+    <string name="upstream_roaming_notification_title" msgid="2870229486619751829">"ဟော့စပေါ့ (သို့) မိုဘိုင်းသုံး၍ ချိတ်ဆက်ခြင်း ဖွင့်ထားသည်"</string>
+    <string name="upstream_roaming_notification_message" msgid="5229740963392849544">"ပြင်ပကွန်ရက်သုံးနေစဉ် နောက်ထပ်ကျသင့်ငွေ ပေးရနိုင်သည်"</string>
 </resources>
diff --git a/Tethering/res/values-mcc310-mnc004-nb/strings.xml b/Tethering/res/values-mcc310-mnc004-nb/strings.xml
index 3685358..31895d1 100644
--- a/Tethering/res/values-mcc310-mnc004-nb/strings.xml
+++ b/Tethering/res/values-mcc310-mnc004-nb/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="5030042590486713460">"Internettdeling har ikke internettilgang"</string>
-    <string name="no_upstream_notification_message" msgid="3843613362272973447">"Enhetene kan ikke koble til"</string>
-    <string name="no_upstream_notification_disable_button" msgid="6385491461813507624">"Slå av internettdeling"</string>
-    <string name="upstream_roaming_notification_title" msgid="3015912166812283303">"Wi-Fi-sone eller internettdeling er på"</string>
-    <string name="upstream_roaming_notification_message" msgid="6724434706748439902">"Ytterligere kostnader kan påløpe under roaming"</string>
+    <string name="no_upstream_notification_title" msgid="3584617491053416666">"Internettdeling har ikke internettilgang"</string>
+    <string name="no_upstream_notification_message" msgid="5626323795587558017">"Enheter kan ikke koble til"</string>
+    <string name="no_upstream_notification_disable_button" msgid="868677179945695858">"Slå av internettdeling"</string>
+    <string name="upstream_roaming_notification_title" msgid="2870229486619751829">"Wifi-sone eller internettdeling er på"</string>
+    <string name="upstream_roaming_notification_message" msgid="5229740963392849544">"Det kan påløpe flere kostnader ved roaming"</string>
 </resources>
diff --git a/Tethering/res/values-mcc310-mnc004-ne/strings.xml b/Tethering/res/values-mcc310-mnc004-ne/strings.xml
index d074f15..4b50773 100644
--- a/Tethering/res/values-mcc310-mnc004-ne/strings.xml
+++ b/Tethering/res/values-mcc310-mnc004-ne/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="5030042590486713460">"टेदरिङमार्फत इन्टरनेट कनेक्सन प्राप्त हुन सकेन"</string>
-    <string name="no_upstream_notification_message" msgid="3843613362272973447">"यन्त्रहरू कनेक्ट गर्न सकिएन"</string>
-    <string name="no_upstream_notification_disable_button" msgid="6385491461813507624">"टेदरिङ निष्क्रिय पार्नुहोस्"</string>
-    <string name="upstream_roaming_notification_title" msgid="3015912166812283303">"हटस्पट वा टेदरिङ सक्रिय छ"</string>
-    <string name="upstream_roaming_notification_message" msgid="6724434706748439902">"रोमिङ सेवा प्रयोग गर्दा अतिरिक्त शुल्क लाग्न सक्छ"</string>
+    <string name="no_upstream_notification_title" msgid="3584617491053416666">"टेदरिङमार्फत इन्टरनेट कनेक्ट गर्न सकिएन"</string>
+    <string name="no_upstream_notification_message" msgid="5626323795587558017">"डिभाइसहरू कनेक्ट गर्न सकिएन"</string>
+    <string name="no_upstream_notification_disable_button" msgid="868677179945695858">"टेदरिङ अफ गर्नुहोस्"</string>
+    <string name="upstream_roaming_notification_title" msgid="2870229486619751829">"हटस्पट वा टेदरिङ अन छ"</string>
+    <string name="upstream_roaming_notification_message" msgid="5229740963392849544">"रोमिङ सेवा प्रयोग गर्दा अतिरिक्त शुल्क लाग्न सक्छ"</string>
 </resources>
diff --git a/Tethering/res/values-mcc310-mnc004-nl/strings.xml b/Tethering/res/values-mcc310-mnc004-nl/strings.xml
index 1d88894..8af41fd 100644
--- a/Tethering/res/values-mcc310-mnc004-nl/strings.xml
+++ b/Tethering/res/values-mcc310-mnc004-nl/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="5030042590486713460">"Tethering heeft geen internet"</string>
-    <string name="no_upstream_notification_message" msgid="3843613362272973447">"Apparaten kunnen niet worden verbonden"</string>
-    <string name="no_upstream_notification_disable_button" msgid="6385491461813507624">"Tethering uitschakelen"</string>
-    <string name="upstream_roaming_notification_title" msgid="3015912166812283303">"Hotspot of tethering is ingeschakeld"</string>
-    <string name="upstream_roaming_notification_message" msgid="6724434706748439902">"Er kunnen extra kosten voor roaming in rekening worden gebracht."</string>
+    <string name="no_upstream_notification_title" msgid="3584617491053416666">"Tethering heeft geen internet"</string>
+    <string name="no_upstream_notification_message" msgid="5626323795587558017">"Apparaten kunnen geen verbinding maken"</string>
+    <string name="no_upstream_notification_disable_button" msgid="868677179945695858">"Tethering uitzetten"</string>
+    <string name="upstream_roaming_notification_title" msgid="2870229486619751829">"Hotspot of tethering staat aan"</string>
+    <string name="upstream_roaming_notification_message" msgid="5229740963392849544">"Er kunnen extra kosten voor roaming in rekening worden gebracht"</string>
 </resources>
diff --git a/Tethering/res/values-mcc310-mnc004-or/strings.xml b/Tethering/res/values-mcc310-mnc004-or/strings.xml
index 8038815..6eb0773 100644
--- a/Tethering/res/values-mcc310-mnc004-or/strings.xml
+++ b/Tethering/res/values-mcc310-mnc004-or/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="5030042590486713460">"ଟିଥରିଂ ପାଇଁ କୌଣସି ଇଣ୍ଟର୍ନେଟ୍ ସଂଯୋଗ ନାହିଁ"</string>
-    <string name="no_upstream_notification_message" msgid="3843613362272973447">"ଡିଭାଇସଗୁଡ଼ିକ ସଂଯୋଗ କରାଯାଇପାରିବ ନାହିଁ"</string>
-    <string name="no_upstream_notification_disable_button" msgid="6385491461813507624">"ଟିଥରିଂ ବନ୍ଦ କରନ୍ତୁ"</string>
-    <string name="upstream_roaming_notification_title" msgid="3015912166812283303">"ହଟସ୍ପଟ୍ କିମ୍ବା ଟିଥରିଂ ଚାଲୁ ଅଛି"</string>
-    <string name="upstream_roaming_notification_message" msgid="6724434706748439902">"ରୋମିଂରେ ଥିବା ସମୟରେ ଅତିରିକ୍ତ ଶୁଳ୍କ ଲାଗୁ ହୋଇପାରେ"</string>
+    <string name="no_upstream_notification_title" msgid="3584617491053416666">"ଟିଥରିଂ ପାଇଁ କୌଣସି ଇଣ୍ଟରନେଟ କନେକ୍ସନ ନାହିଁ"</string>
+    <string name="no_upstream_notification_message" msgid="5626323795587558017">"ଡିଭାଇସଗୁଡ଼ିକୁ କନେକ୍ଟ କରାଯାଇପାରିବ ନାହିଁ"</string>
+    <string name="no_upstream_notification_disable_button" msgid="868677179945695858">"ଟିଥରିଂକୁ ବନ୍ଦ କରନ୍ତୁ"</string>
+    <string name="upstream_roaming_notification_title" msgid="2870229486619751829">"ହଟସ୍ପଟ କିମ୍ବା ଟିଥରିଂ ଚାଲୁ ଅଛି"</string>
+    <string name="upstream_roaming_notification_message" msgid="5229740963392849544">"ରୋମିଂ ସମୟରେ ଅତିରିକ୍ତ ଚାର୍ଜ ଲାଗୁ ହୋଇପାରେ"</string>
 </resources>
diff --git a/Tethering/res/values-mcc310-mnc004-pa/strings.xml b/Tethering/res/values-mcc310-mnc004-pa/strings.xml
index 819833e..28181e2 100644
--- a/Tethering/res/values-mcc310-mnc004-pa/strings.xml
+++ b/Tethering/res/values-mcc310-mnc004-pa/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="5030042590486713460">"ਟੈਦਰਿੰਗ ਕੋਲ ਇੰਟਰਨੈੱਟ ਪਹੁੰਚ ਨਹੀਂ ਹੈ"</string>
-    <string name="no_upstream_notification_message" msgid="3843613362272973447">"ਡੀਵਾਈਸ ਕਨੈਕਟ ਨਹੀਂ ਕੀਤੇ ਜਾ ਸਕਦੇ"</string>
-    <string name="no_upstream_notification_disable_button" msgid="6385491461813507624">"ਟੈਦਰਿੰਗ ਬੰਦ ਕਰੋ"</string>
-    <string name="upstream_roaming_notification_title" msgid="3015912166812283303">"ਹੌਟਸਪੌਟ ਜਾਂ ਟੈਦਰਿੰਗ ਚਾਲੂ ਹੈ"</string>
-    <string name="upstream_roaming_notification_message" msgid="6724434706748439902">"ਰੋਮਿੰਗ ਦੌਰਾਨ ਵਧੀਕ ਖਰਚੇ ਲਾਗੂ ਹੋ ਸਕਦੇ ਹਨ"</string>
+    <string name="no_upstream_notification_title" msgid="3584617491053416666">"ਟੈਦਰਿੰਗ ਕੋਲ ਇੰਟਰਨੈੱਟ ਪਹੁੰਚ ਨਹੀਂ ਹੈ"</string>
+    <string name="no_upstream_notification_message" msgid="5626323795587558017">"ਡੀਵਾਈਸ ਕਨੈਕਟ ਨਹੀਂ ਕੀਤੇ ਜਾ ਸਕਦੇ"</string>
+    <string name="no_upstream_notification_disable_button" msgid="868677179945695858">"ਟੈਦਰਿੰਗ ਬੰਦ ਕਰੋ"</string>
+    <string name="upstream_roaming_notification_title" msgid="2870229486619751829">"ਹੌਟਸਪੌਟ ਜਾਂ ਟੈਦਰਿੰਗ ਚਾਲੂ ਹੈ"</string>
+    <string name="upstream_roaming_notification_message" msgid="5229740963392849544">"ਰੋਮਿੰਗ ਦੌਰਾਨ ਵਧੀਕ ਖਰਚੇ ਲਾਗੂ ਹੋ ਸਕਦੇ ਹਨ"</string>
 </resources>
diff --git a/Tethering/res/values-mcc310-mnc004-pl/strings.xml b/Tethering/res/values-mcc310-mnc004-pl/strings.xml
index 65e4380..816302a 100644
--- a/Tethering/res/values-mcc310-mnc004-pl/strings.xml
+++ b/Tethering/res/values-mcc310-mnc004-pl/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="5030042590486713460">"Tethering nie ma internetu"</string>
-    <string name="no_upstream_notification_message" msgid="3843613362272973447">"Urządzenia nie mogą się połączyć"</string>
-    <string name="no_upstream_notification_disable_button" msgid="6385491461813507624">"Wyłącz tethering"</string>
-    <string name="upstream_roaming_notification_title" msgid="3015912166812283303">"Hotspot lub tethering jest włączony"</string>
-    <string name="upstream_roaming_notification_message" msgid="6724434706748439902">"Podczas korzystania z roamingu mogą zostać naliczone dodatkowe opłaty"</string>
+    <string name="no_upstream_notification_title" msgid="3584617491053416666">"Tethering nie ma internetu"</string>
+    <string name="no_upstream_notification_message" msgid="5626323795587558017">"Urządzenia nie mogą się połączyć"</string>
+    <string name="no_upstream_notification_disable_button" msgid="868677179945695858">"Wyłącz tethering"</string>
+    <string name="upstream_roaming_notification_title" msgid="2870229486619751829">"Hotspot lub tethering jest włączony"</string>
+    <string name="upstream_roaming_notification_message" msgid="5229740963392849544">"Podczas korzystania z roamingu mogą zostać naliczone dodatkowe opłaty"</string>
 </resources>
diff --git a/Tethering/res/values-mcc310-mnc004-pt-rBR/strings.xml b/Tethering/res/values-mcc310-mnc004-pt-rBR/strings.xml
index d886617..55767c2 100644
--- a/Tethering/res/values-mcc310-mnc004-pt-rBR/strings.xml
+++ b/Tethering/res/values-mcc310-mnc004-pt-rBR/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="5030042590486713460">"O tethering não tem Internet"</string>
-    <string name="no_upstream_notification_message" msgid="3843613362272973447">"Não é possível conectar os dispositivos"</string>
-    <string name="no_upstream_notification_disable_button" msgid="6385491461813507624">"Desativar o tethering"</string>
-    <string name="upstream_roaming_notification_title" msgid="3015912166812283303">"Ponto de acesso ou tethering ativado"</string>
-    <string name="upstream_roaming_notification_message" msgid="6724434706748439902">"Pode haver cobranças extras durante o roaming"</string>
+    <string name="no_upstream_notification_title" msgid="3584617491053416666">"O tethering não tem uma conexão de Internet"</string>
+    <string name="no_upstream_notification_message" msgid="5626323795587558017">"Não é possível conectar os dispositivos"</string>
+    <string name="no_upstream_notification_disable_button" msgid="868677179945695858">"Desativar o tethering"</string>
+    <string name="upstream_roaming_notification_title" msgid="2870229486619751829">"O ponto de acesso ou tethering está ativado"</string>
+    <string name="upstream_roaming_notification_message" msgid="5229740963392849544">"Pode haver cobranças extras durante o roaming"</string>
 </resources>
diff --git a/Tethering/res/values-mcc310-mnc004-pt-rPT/strings.xml b/Tethering/res/values-mcc310-mnc004-pt-rPT/strings.xml
index bfd45ca..eccabf8 100644
--- a/Tethering/res/values-mcc310-mnc004-pt-rPT/strings.xml
+++ b/Tethering/res/values-mcc310-mnc004-pt-rPT/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="5030042590486713460">"A ligação (à Internet) via telemóvel não tem Internet"</string>
-    <string name="no_upstream_notification_message" msgid="3843613362272973447">"Não é possível ligar os dispositivos"</string>
-    <string name="no_upstream_notification_disable_button" msgid="6385491461813507624">"Desativar ligação (à Internet) via telemóvel"</string>
-    <string name="upstream_roaming_notification_title" msgid="3015912166812283303">"A zona Wi-Fi ou a ligação (à Internet) via telemóvel está ativada"</string>
-    <string name="upstream_roaming_notification_message" msgid="6724434706748439902">"Podem aplicar-se custos adicionais em roaming."</string>
+    <string name="no_upstream_notification_title" msgid="3584617491053416666">"A ligação (à Internet) via telemóvel não tem Internet"</string>
+    <string name="no_upstream_notification_message" msgid="5626323795587558017">"Não é possível ligar os dispositivos"</string>
+    <string name="no_upstream_notification_disable_button" msgid="868677179945695858">"Desativar ligação (à Internet) via telemóvel"</string>
+    <string name="upstream_roaming_notification_title" msgid="2870229486619751829">"A zona Wi-Fi ou a ligação (à Internet) via telemóvel está ativada"</string>
+    <string name="upstream_roaming_notification_message" msgid="5229740963392849544">"Podem aplicar-se custos adicionais em roaming"</string>
 </resources>
diff --git a/Tethering/res/values-mcc310-mnc004-pt/strings.xml b/Tethering/res/values-mcc310-mnc004-pt/strings.xml
index d886617..55767c2 100644
--- a/Tethering/res/values-mcc310-mnc004-pt/strings.xml
+++ b/Tethering/res/values-mcc310-mnc004-pt/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="5030042590486713460">"O tethering não tem Internet"</string>
-    <string name="no_upstream_notification_message" msgid="3843613362272973447">"Não é possível conectar os dispositivos"</string>
-    <string name="no_upstream_notification_disable_button" msgid="6385491461813507624">"Desativar o tethering"</string>
-    <string name="upstream_roaming_notification_title" msgid="3015912166812283303">"Ponto de acesso ou tethering ativado"</string>
-    <string name="upstream_roaming_notification_message" msgid="6724434706748439902">"Pode haver cobranças extras durante o roaming"</string>
+    <string name="no_upstream_notification_title" msgid="3584617491053416666">"O tethering não tem uma conexão de Internet"</string>
+    <string name="no_upstream_notification_message" msgid="5626323795587558017">"Não é possível conectar os dispositivos"</string>
+    <string name="no_upstream_notification_disable_button" msgid="868677179945695858">"Desativar o tethering"</string>
+    <string name="upstream_roaming_notification_title" msgid="2870229486619751829">"O ponto de acesso ou tethering está ativado"</string>
+    <string name="upstream_roaming_notification_message" msgid="5229740963392849544">"Pode haver cobranças extras durante o roaming"</string>
 </resources>
diff --git a/Tethering/res/values-mcc310-mnc004-ro/strings.xml b/Tethering/res/values-mcc310-mnc004-ro/strings.xml
index 8d87a9e..3c3d7bc 100644
--- a/Tethering/res/values-mcc310-mnc004-ro/strings.xml
+++ b/Tethering/res/values-mcc310-mnc004-ro/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="5030042590486713460">"Procesul de tethering nu are internet"</string>
-    <string name="no_upstream_notification_message" msgid="3843613362272973447">"Dispozitivele nu se pot conecta"</string>
-    <string name="no_upstream_notification_disable_button" msgid="6385491461813507624">"Dezactivați procesul de tethering"</string>
-    <string name="upstream_roaming_notification_title" msgid="3015912166812283303">"S-a activat hotspotul sau tethering"</string>
-    <string name="upstream_roaming_notification_message" msgid="6724434706748439902">"Se pot aplica taxe suplimentare pentru roaming"</string>
+    <string name="no_upstream_notification_title" msgid="3584617491053416666">"Procesul de tethering nu are internet"</string>
+    <string name="no_upstream_notification_message" msgid="5626323795587558017">"Dispozitivele nu se pot conecta"</string>
+    <string name="no_upstream_notification_disable_button" msgid="868677179945695858">"Dezactivează tetheringul"</string>
+    <string name="upstream_roaming_notification_title" msgid="2870229486619751829">"S-a activat hotspotul sau tetheringul"</string>
+    <string name="upstream_roaming_notification_message" msgid="5229740963392849544">"Se pot aplica taxe suplimentare pentru roaming"</string>
 </resources>
diff --git a/Tethering/res/values-mcc310-mnc004-ru/strings.xml b/Tethering/res/values-mcc310-mnc004-ru/strings.xml
index dbdb9eb..7667180 100644
--- a/Tethering/res/values-mcc310-mnc004-ru/strings.xml
+++ b/Tethering/res/values-mcc310-mnc004-ru/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="5030042590486713460">"Режим модема используется без доступа к Интернету"</string>
-    <string name="no_upstream_notification_message" msgid="3843613362272973447">"Невозможно подключить устройства."</string>
-    <string name="no_upstream_notification_disable_button" msgid="6385491461813507624">"Отключить режим модема"</string>
-    <string name="upstream_roaming_notification_title" msgid="3015912166812283303">"Включены точка доступа или режим модема"</string>
-    <string name="upstream_roaming_notification_message" msgid="6724434706748439902">"За использование услуг связи в роуминге может взиматься дополнительная плата."</string>
+    <string name="no_upstream_notification_title" msgid="3584617491053416666">"Режим модема используется без доступа к интернету"</string>
+    <string name="no_upstream_notification_message" msgid="5626323795587558017">"Невозможно подключить устройства."</string>
+    <string name="no_upstream_notification_disable_button" msgid="868677179945695858">"Отключить режим модема"</string>
+    <string name="upstream_roaming_notification_title" msgid="2870229486619751829">"Включена точка доступа или режим модема"</string>
+    <string name="upstream_roaming_notification_message" msgid="5229740963392849544">"За использование услуг связи в роуминге может взиматься дополнительная плата."</string>
 </resources>
diff --git a/Tethering/res/values-mcc310-mnc004-si/strings.xml b/Tethering/res/values-mcc310-mnc004-si/strings.xml
index d8301e4..0c88a39 100644
--- a/Tethering/res/values-mcc310-mnc004-si/strings.xml
+++ b/Tethering/res/values-mcc310-mnc004-si/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="5030042590486713460">"ටෙදරින් හට අන්තර්ජාලය නැත"</string>
-    <string name="no_upstream_notification_message" msgid="3843613362272973447">"උපාංගවලට සම්බන්ධ විය නොහැකිය"</string>
-    <string name="no_upstream_notification_disable_button" msgid="6385491461813507624">"ටෙදරින් ක්‍රියාවිරහිත කරන්න"</string>
-    <string name="upstream_roaming_notification_title" msgid="3015912166812283303">"හොට්ස්පොට් හෝ ටෙදරින් ක්‍රියාත්මකයි"</string>
-    <string name="upstream_roaming_notification_message" msgid="6724434706748439902">"රෝමිං අතරතුර අමතර ගාස්තු අදාළ විය හැකිය"</string>
+    <string name="no_upstream_notification_title" msgid="3584617491053416666">"ටෙදරින් හට අන්තර්ජාලය නැත"</string>
+    <string name="no_upstream_notification_message" msgid="5626323795587558017">"උපාංගවලට සම්බන්ධ විය නොහැක"</string>
+    <string name="no_upstream_notification_disable_button" msgid="868677179945695858">"ටෙදරින් ක්‍රියාවිරහිත කරන්න"</string>
+    <string name="upstream_roaming_notification_title" msgid="2870229486619751829">"හොට්ස්පොට් හෝ ටෙදරින් ක්‍රියාත්මකයි"</string>
+    <string name="upstream_roaming_notification_message" msgid="5229740963392849544">"රෝමිං අතරේ අතිරේක ගාස්තු අදාළ විය හැක"</string>
 </resources>
diff --git a/Tethering/res/values-mcc310-mnc004-sk/strings.xml b/Tethering/res/values-mcc310-mnc004-sk/strings.xml
index bef7136..c3b941e 100644
--- a/Tethering/res/values-mcc310-mnc004-sk/strings.xml
+++ b/Tethering/res/values-mcc310-mnc004-sk/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="5030042590486713460">"Tethering nemá internetové pripojenie"</string>
-    <string name="no_upstream_notification_message" msgid="3843613362272973447">"Zariadenia sa nemôžu pripojiť"</string>
-    <string name="no_upstream_notification_disable_button" msgid="6385491461813507624">"Vypnúť tethering"</string>
-    <string name="upstream_roaming_notification_title" msgid="3015912166812283303">"Je zapnutý hotspot alebo tethering"</string>
-    <string name="upstream_roaming_notification_message" msgid="6724434706748439902">"Počas roamingu vám môžu byť účtované ďalšie poplatky"</string>
+    <string name="no_upstream_notification_title" msgid="3584617491053416666">"Tethering nemá internetové pripojenie"</string>
+    <string name="no_upstream_notification_message" msgid="5626323795587558017">"Zariadenia sa nemôžu pripojiť"</string>
+    <string name="no_upstream_notification_disable_button" msgid="868677179945695858">"Vypnúť tethering"</string>
+    <string name="upstream_roaming_notification_title" msgid="2870229486619751829">"Je zapnutý hotspot alebo tethering"</string>
+    <string name="upstream_roaming_notification_message" msgid="5229740963392849544">"Počas roamingu vám môžu byť účtované ďalšie poplatky"</string>
 </resources>
diff --git a/Tethering/res/values-mcc310-mnc004-sl/strings.xml b/Tethering/res/values-mcc310-mnc004-sl/strings.xml
index 3202c62..6573475 100644
--- a/Tethering/res/values-mcc310-mnc004-sl/strings.xml
+++ b/Tethering/res/values-mcc310-mnc004-sl/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="5030042590486713460">"Internetna povezava prek mobilnega telefona ni vzpostavljena"</string>
-    <string name="no_upstream_notification_message" msgid="3843613362272973447">"Napravi se ne moreta povezati"</string>
-    <string name="no_upstream_notification_disable_button" msgid="6385491461813507624">"Izklopi internetno povezavo prek mobilnega telefona"</string>
-    <string name="upstream_roaming_notification_title" msgid="3015912166812283303">"Dostopna točka ali internetna povezava prek mobilnega telefona je vklopljena"</string>
-    <string name="upstream_roaming_notification_message" msgid="6724434706748439902">"Med gostovanjem lahko nastanejo dodatni stroški"</string>
+    <string name="no_upstream_notification_title" msgid="3584617491053416666">"Povezava računalnika z internetom prek mobilnega telefona nima internetne povezave"</string>
+    <string name="no_upstream_notification_message" msgid="5626323795587558017">"Napravi se ne moreta povezati"</string>
+    <string name="no_upstream_notification_disable_button" msgid="868677179945695858">"Izklopi povezavo računalnika z internetom prek mobilnega telefona"</string>
+    <string name="upstream_roaming_notification_title" msgid="2870229486619751829">"Dostopna točka ali povezava računalnika z internetom prek mobilnega telefona je vklopljena"</string>
+    <string name="upstream_roaming_notification_message" msgid="5229740963392849544">"Med gostovanjem lahko nastanejo dodatni stroški."</string>
 </resources>
diff --git a/Tethering/res/values-mcc310-mnc004-sq/strings.xml b/Tethering/res/values-mcc310-mnc004-sq/strings.xml
index 37f6ad2..93ea231 100644
--- a/Tethering/res/values-mcc310-mnc004-sq/strings.xml
+++ b/Tethering/res/values-mcc310-mnc004-sq/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="5030042590486713460">"Ndarja e internetit nuk ka internet"</string>
-    <string name="no_upstream_notification_message" msgid="3843613362272973447">"Pajisjet nuk mund të lidhen"</string>
-    <string name="no_upstream_notification_disable_button" msgid="6385491461813507624">"Çaktivizo ndarjen e internetit"</string>
-    <string name="upstream_roaming_notification_title" msgid="3015912166812283303">"Zona e qasjes për internet ose ndarja e internetit është aktive"</string>
-    <string name="upstream_roaming_notification_message" msgid="6724434706748439902">"Mund të zbatohen tarifime shtesë kur je në roaming"</string>
+    <string name="no_upstream_notification_title" msgid="3584617491053416666">"Ndarja e internetit nuk ka internet"</string>
+    <string name="no_upstream_notification_message" msgid="5626323795587558017">"Pajisjet nuk mund të lidhen"</string>
+    <string name="no_upstream_notification_disable_button" msgid="868677179945695858">"Çaktivizo ndarjen e internetit"</string>
+    <string name="upstream_roaming_notification_title" msgid="2870229486619751829">"Zona e qasjes për internet ose ndarja e internetit është aktive"</string>
+    <string name="upstream_roaming_notification_message" msgid="5229740963392849544">"Mund të zbatohen tarifime shtesë kur je në roaming"</string>
 </resources>
diff --git a/Tethering/res/values-mcc310-mnc004-sr/strings.xml b/Tethering/res/values-mcc310-mnc004-sr/strings.xml
index 5566d03..e8fb478 100644
--- a/Tethering/res/values-mcc310-mnc004-sr/strings.xml
+++ b/Tethering/res/values-mcc310-mnc004-sr/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="5030042590486713460">"Привезивање нема приступ интернету"</string>
-    <string name="no_upstream_notification_message" msgid="3843613362272973447">"Повезивање уређаја није успело"</string>
-    <string name="no_upstream_notification_disable_button" msgid="6385491461813507624">"Искључи привезивање"</string>
-    <string name="upstream_roaming_notification_title" msgid="3015912166812283303">"Укључен је хотспот или привезивање"</string>
-    <string name="upstream_roaming_notification_message" msgid="6724434706748439902">"Можда важе додатни трошкови у ромингу"</string>
+    <string name="no_upstream_notification_title" msgid="3584617491053416666">"Привезивање нема приступ интернету"</string>
+    <string name="no_upstream_notification_message" msgid="5626323795587558017">"Повезивање уређаја није успело"</string>
+    <string name="no_upstream_notification_disable_button" msgid="868677179945695858">"Искључи привезивање"</string>
+    <string name="upstream_roaming_notification_title" msgid="2870229486619751829">"Укључен је хотспот или привезивање"</string>
+    <string name="upstream_roaming_notification_message" msgid="5229740963392849544">"Можда важе додатни трошкови у ромингу"</string>
 </resources>
diff --git a/Tethering/res/values-mcc310-mnc004-sv/strings.xml b/Tethering/res/values-mcc310-mnc004-sv/strings.xml
index 9765acd..6fc1747 100644
--- a/Tethering/res/values-mcc310-mnc004-sv/strings.xml
+++ b/Tethering/res/values-mcc310-mnc004-sv/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="5030042590486713460">"Det finns ingen internetanslutning för internetdelningen"</string>
-    <string name="no_upstream_notification_message" msgid="3843613362272973447">"Enheterna kan inte anslutas"</string>
-    <string name="no_upstream_notification_disable_button" msgid="6385491461813507624">"Inaktivera internetdelning"</string>
-    <string name="upstream_roaming_notification_title" msgid="3015912166812283303">"Surfzon eller internetdelning har aktiverats"</string>
-    <string name="upstream_roaming_notification_message" msgid="6724434706748439902">"Ytterligare avgifter kan tillkomma vid roaming"</string>
+    <string name="no_upstream_notification_title" msgid="3584617491053416666">"Det finns ingen internetanslutning för internetdelningen"</string>
+    <string name="no_upstream_notification_message" msgid="5626323795587558017">"Enheterna kan inte anslutas"</string>
+    <string name="no_upstream_notification_disable_button" msgid="868677179945695858">"Inaktivera internetdelning"</string>
+    <string name="upstream_roaming_notification_title" msgid="2870229486619751829">"Surfzon eller internetdelning har aktiverats"</string>
+    <string name="upstream_roaming_notification_message" msgid="5229740963392849544">"Ytterligare avgifter kan tillkomma vid roaming"</string>
 </resources>
diff --git a/Tethering/res/values-mcc310-mnc004-sw/strings.xml b/Tethering/res/values-mcc310-mnc004-sw/strings.xml
index cf850c9..73a7026 100644
--- a/Tethering/res/values-mcc310-mnc004-sw/strings.xml
+++ b/Tethering/res/values-mcc310-mnc004-sw/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="5030042590486713460">"Kipengele cha kusambaza mtandao hakina intaneti"</string>
-    <string name="no_upstream_notification_message" msgid="3843613362272973447">"Imeshindwa kuunganisha vifaa"</string>
-    <string name="no_upstream_notification_disable_button" msgid="6385491461813507624">"Zima kipengele cha kusambaza mtandao"</string>
-    <string name="upstream_roaming_notification_title" msgid="3015912166812283303">"Umewasha kipengele cha kusambaza mtandao au mtandao pepe"</string>
-    <string name="upstream_roaming_notification_message" msgid="6724434706748439902">"Huenda ukatozwa gharama za ziada ukitumia mitandao ya ng\'ambo"</string>
+    <string name="no_upstream_notification_title" msgid="3584617491053416666">"Huduma ya kusambaza mtandao haina muunganisho wa intaneti"</string>
+    <string name="no_upstream_notification_message" msgid="5626323795587558017">"Imeshindwa kuunganisha vifaa"</string>
+    <string name="no_upstream_notification_disable_button" msgid="868677179945695858">"Zima kipengele cha kusambaza mtandao"</string>
+    <string name="upstream_roaming_notification_title" msgid="2870229486619751829">"Umewasha kipengele cha kusambaza mtandao au mtandao pepe"</string>
+    <string name="upstream_roaming_notification_message" msgid="5229740963392849544">"Huenda ukatozwa gharama za ziada ukitumia mitandao ya ng\'ambo"</string>
 </resources>
diff --git a/Tethering/res/values-mcc310-mnc004-ta/strings.xml b/Tethering/res/values-mcc310-mnc004-ta/strings.xml
index f4b15aa..436f00b 100644
--- a/Tethering/res/values-mcc310-mnc004-ta/strings.xml
+++ b/Tethering/res/values-mcc310-mnc004-ta/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="5030042590486713460">"இணைப்பு முறைக்கு இணைய இணைப்பு இல்லை"</string>
-    <string name="no_upstream_notification_message" msgid="3843613362272973447">"சாதனங்களால் இணைய முடியவில்லை"</string>
-    <string name="no_upstream_notification_disable_button" msgid="6385491461813507624">"இணைப்பு முறையை ஆஃப் செய்"</string>
-    <string name="upstream_roaming_notification_title" msgid="3015912166812283303">"ஹாட்ஸ்பாட் அல்லது இணைப்பு முறை ஆன் செய்யப்பட்டுள்ளது"</string>
-    <string name="upstream_roaming_notification_message" msgid="6724434706748439902">"ரோமிங்கின்போது கூடுதல் கட்டணங்கள் விதிக்கப்படக்கூடும்"</string>
+    <string name="no_upstream_notification_title" msgid="3584617491053416666">"இணைப்பு முறைக்கு இணைய இணைப்பு இல்லை"</string>
+    <string name="no_upstream_notification_message" msgid="5626323795587558017">"சாதனங்களால் இணைய முடியவில்லை"</string>
+    <string name="no_upstream_notification_disable_button" msgid="868677179945695858">"இணைப்பு முறையை முடக்கு"</string>
+    <string name="upstream_roaming_notification_title" msgid="2870229486619751829">"ஹாட்ஸ்பாட் அல்லது இணைப்பு முறை இயக்கப்பட்டுள்ளது"</string>
+    <string name="upstream_roaming_notification_message" msgid="5229740963392849544">"ரோமிங்கின்போது கூடுதல் கட்டணங்கள் விதிக்கப்படலாம்"</string>
 </resources>
diff --git a/Tethering/res/values-mcc310-mnc004-te/strings.xml b/Tethering/res/values-mcc310-mnc004-te/strings.xml
index 937d34d..ba83627 100644
--- a/Tethering/res/values-mcc310-mnc004-te/strings.xml
+++ b/Tethering/res/values-mcc310-mnc004-te/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="5030042590486713460">"టెథరింగ్ చేయడానికి ఇంటర్నెట్ కనెక్షన్ లేదు"</string>
-    <string name="no_upstream_notification_message" msgid="3843613362272973447">"పరికరాలు కనెక్ట్ అవ్వడం లేదు"</string>
-    <string name="no_upstream_notification_disable_button" msgid="6385491461813507624">"టెథరింగ్‌ను ఆఫ్ చేయండి"</string>
-    <string name="upstream_roaming_notification_title" msgid="3015912166812283303">"హాట్‌స్పాట్ లేదా టెథరింగ్ ఆన్‌లో ఉంది"</string>
-    <string name="upstream_roaming_notification_message" msgid="6724434706748439902">"రోమింగ్‌లో ఉన్నప్పుడు అదనపు ఛార్జీలు వర్తించవచ్చు"</string>
+    <string name="no_upstream_notification_title" msgid="3584617491053416666">"టెథరింగ్ చేయడానికి ఇంటర్నెట్ కనెక్షన్ లేదు"</string>
+    <string name="no_upstream_notification_message" msgid="5626323795587558017">"పరికరాలు కనెక్ట్ అవ్వడం లేదు"</string>
+    <string name="no_upstream_notification_disable_button" msgid="868677179945695858">"టెథరింగ్‌ను ఆఫ్ చేయండి"</string>
+    <string name="upstream_roaming_notification_title" msgid="2870229486619751829">"హాట్‌స్పాట్ లేదా టెథరింగ్ ఆన్‌లో ఉంది"</string>
+    <string name="upstream_roaming_notification_message" msgid="5229740963392849544">"రోమింగ్‌లో ఉన్నప్పుడు అదనపు ఛార్జీలు వర్తించవచ్చు"</string>
 </resources>
diff --git a/Tethering/res/values-mcc310-mnc004-th/strings.xml b/Tethering/res/values-mcc310-mnc004-th/strings.xml
index f781fae..e2ea084 100644
--- a/Tethering/res/values-mcc310-mnc004-th/strings.xml
+++ b/Tethering/res/values-mcc310-mnc004-th/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="5030042590486713460">"การเชื่อมต่ออินเทอร์เน็ตผ่านมือถือไม่มีอินเทอร์เน็ต"</string>
-    <string name="no_upstream_notification_message" msgid="3843613362272973447">"อุปกรณ์เชื่อมต่อไม่ได้"</string>
-    <string name="no_upstream_notification_disable_button" msgid="6385491461813507624">"ปิดการเชื่อมต่ออินเทอร์เน็ตผ่านมือถือ"</string>
-    <string name="upstream_roaming_notification_title" msgid="3015912166812283303">"ฮอตสปอตหรือการเชื่อมต่ออินเทอร์เน็ตผ่านมือถือเปิดอยู่"</string>
-    <string name="upstream_roaming_notification_message" msgid="6724434706748439902">"อาจมีค่าใช้จ่ายเพิ่มเติมขณะโรมมิ่ง"</string>
+    <string name="no_upstream_notification_title" msgid="3584617491053416666">"ไม่มีอินเทอร์เน็ตสำหรับการเชื่อมต่ออินเทอร์เน็ตผ่านมือถือ"</string>
+    <string name="no_upstream_notification_message" msgid="5626323795587558017">"อุปกรณ์เชื่อมต่อไม่ได้"</string>
+    <string name="no_upstream_notification_disable_button" msgid="868677179945695858">"ปิดการเชื่อมต่ออินเทอร์เน็ตผ่านมือถือ"</string>
+    <string name="upstream_roaming_notification_title" msgid="2870229486619751829">"ฮอตสปอตหรือการเชื่อมต่ออินเทอร์เน็ตผ่านมือถือเปิดอยู่"</string>
+    <string name="upstream_roaming_notification_message" msgid="5229740963392849544">"อาจมีค่าใช้จ่ายเพิ่มเติมขณะโรมมิ่ง"</string>
 </resources>
diff --git a/Tethering/res/values-mcc310-mnc004-tl/strings.xml b/Tethering/res/values-mcc310-mnc004-tl/strings.xml
index 8d5d465..7b4b71c 100644
--- a/Tethering/res/values-mcc310-mnc004-tl/strings.xml
+++ b/Tethering/res/values-mcc310-mnc004-tl/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="5030042590486713460">"Walang internet ang pag-tether"</string>
-    <string name="no_upstream_notification_message" msgid="3843613362272973447">"Hindi makakonekta ang mga device"</string>
-    <string name="no_upstream_notification_disable_button" msgid="6385491461813507624">"I-off ang pag-tether"</string>
-    <string name="upstream_roaming_notification_title" msgid="3015912166812283303">"Naka-on ang Hotspot o pag-tether"</string>
-    <string name="upstream_roaming_notification_message" msgid="6724434706748439902">"Posibleng magkaroon ng mga karagdagang singil habang nagro-roam"</string>
+    <string name="no_upstream_notification_title" msgid="3584617491053416666">"Walang internet ang pag-tether"</string>
+    <string name="no_upstream_notification_message" msgid="5626323795587558017">"Hindi makakonekta ang mga device"</string>
+    <string name="no_upstream_notification_disable_button" msgid="868677179945695858">"I-off ang pag-tether"</string>
+    <string name="upstream_roaming_notification_title" msgid="2870229486619751829">"Naka-on ang hotspot o pag-tether"</string>
+    <string name="upstream_roaming_notification_message" msgid="5229740963392849544">"Posibleng magkaroon ng mga karagdagang singil habang nagro-roam"</string>
 </resources>
diff --git a/Tethering/res/values-mcc310-mnc004-tr/strings.xml b/Tethering/res/values-mcc310-mnc004-tr/strings.xml
index 80cab33..066e1d7 100644
--- a/Tethering/res/values-mcc310-mnc004-tr/strings.xml
+++ b/Tethering/res/values-mcc310-mnc004-tr/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="5030042590486713460">"Tethering\'in internet bağlantısı yok"</string>
-    <string name="no_upstream_notification_message" msgid="3843613362272973447">"Cihazlar bağlanamıyor"</string>
-    <string name="no_upstream_notification_disable_button" msgid="6385491461813507624">"Tethering\'i kapat"</string>
-    <string name="upstream_roaming_notification_title" msgid="3015912166812283303">"Hotspot veya tethering açık"</string>
-    <string name="upstream_roaming_notification_message" msgid="6724434706748439902">"Dolaşım sırasında ek ücretler uygulanabilir"</string>
+    <string name="no_upstream_notification_title" msgid="3584617491053416666">"Tethering\'in internet bağlantısı yok"</string>
+    <string name="no_upstream_notification_message" msgid="5626323795587558017">"Cihazlar bağlanamıyor"</string>
+    <string name="no_upstream_notification_disable_button" msgid="868677179945695858">"Tethering\'i kapat"</string>
+    <string name="upstream_roaming_notification_title" msgid="2870229486619751829">"Hotspot veya tethering açık"</string>
+    <string name="upstream_roaming_notification_message" msgid="5229740963392849544">"Dolaşım sırasında ek ücretler uygulanabilir"</string>
 </resources>
diff --git a/Tethering/res/values-mcc310-mnc004-uk/strings.xml b/Tethering/res/values-mcc310-mnc004-uk/strings.xml
index c05932a..036746a 100644
--- a/Tethering/res/values-mcc310-mnc004-uk/strings.xml
+++ b/Tethering/res/values-mcc310-mnc004-uk/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="5030042590486713460">"Телефон, який використовується як модем, не підключений до Інтернету"</string>
-    <string name="no_upstream_notification_message" msgid="3843613362272973447">"Не вдається підключити пристрої"</string>
-    <string name="no_upstream_notification_disable_button" msgid="6385491461813507624">"Вимкнути використання телефона як модема"</string>
-    <string name="upstream_roaming_notification_title" msgid="3015912166812283303">"Увімкнено точку доступу або використання телефона як модема"</string>
-    <string name="upstream_roaming_notification_message" msgid="6724434706748439902">"У роумінгу може стягуватися додаткова плата"</string>
+    <string name="no_upstream_notification_title" msgid="3584617491053416666">"Телефон, що використовується як модем, не підключений до Інтернету"</string>
+    <string name="no_upstream_notification_message" msgid="5626323795587558017">"Не вдається підключити пристрої"</string>
+    <string name="no_upstream_notification_disable_button" msgid="868677179945695858">"Вимкнути використання телефона як модема"</string>
+    <string name="upstream_roaming_notification_title" msgid="2870229486619751829">"Увімкнено точку доступу або використання телефона як модема"</string>
+    <string name="upstream_roaming_notification_message" msgid="5229740963392849544">"У роумінгу може стягуватися додаткова плата"</string>
 </resources>
diff --git a/Tethering/res/values-mcc310-mnc004-ur/strings.xml b/Tethering/res/values-mcc310-mnc004-ur/strings.xml
index d820eee..90eadef 100644
--- a/Tethering/res/values-mcc310-mnc004-ur/strings.xml
+++ b/Tethering/res/values-mcc310-mnc004-ur/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="5030042590486713460">"ٹیدرنگ میں انٹرنیٹ نہیں ہے"</string>
-    <string name="no_upstream_notification_message" msgid="3843613362272973447">"آلات منسلک نہیں ہو سکتے"</string>
-    <string name="no_upstream_notification_disable_button" msgid="6385491461813507624">"ٹیدرنگ آف کریں"</string>
-    <string name="upstream_roaming_notification_title" msgid="3015912166812283303">"ہاٹ اسپاٹ یا ٹیدرنگ آن ہے"</string>
-    <string name="upstream_roaming_notification_message" msgid="6724434706748439902">"رومنگ کے دوران اضافی چارجز لاگو ہو سکتے ہیں"</string>
+    <string name="no_upstream_notification_title" msgid="3584617491053416666">"ٹیدرنگ میں انٹرنیٹ نہیں ہے"</string>
+    <string name="no_upstream_notification_message" msgid="5626323795587558017">"آلات منسلک نہیں ہو سکتے"</string>
+    <string name="no_upstream_notification_disable_button" msgid="868677179945695858">"ٹیدرنگ آف کریں"</string>
+    <string name="upstream_roaming_notification_title" msgid="2870229486619751829">"ہاٹ اسپاٹ یا ٹیدرنگ آن ہے"</string>
+    <string name="upstream_roaming_notification_message" msgid="5229740963392849544">"رومنگ کے دوران اضافی چارجز لاگو ہو سکتے ہیں"</string>
 </resources>
diff --git a/Tethering/res/values-mcc310-mnc004-uz/strings.xml b/Tethering/res/values-mcc310-mnc004-uz/strings.xml
index 726148a..f647572 100644
--- a/Tethering/res/values-mcc310-mnc004-uz/strings.xml
+++ b/Tethering/res/values-mcc310-mnc004-uz/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="5030042590486713460">"Modem internetga ulanmagan"</string>
-    <string name="no_upstream_notification_message" msgid="3843613362272973447">"Qurilmalar ulanmadi"</string>
-    <string name="no_upstream_notification_disable_button" msgid="6385491461813507624">"Modem rejimini faolsizlantirish"</string>
-    <string name="upstream_roaming_notification_title" msgid="3015912166812283303">"Hotspot yoki modem rejimi yoniq"</string>
-    <string name="upstream_roaming_notification_message" msgid="6724434706748439902">"Rouming vaqtida qoʻshimcha haq olinishi mumkin"</string>
+    <string name="no_upstream_notification_title" msgid="3584617491053416666">"Modem internetga ulanmagan"</string>
+    <string name="no_upstream_notification_message" msgid="5626323795587558017">"Qurilmalar ulanmadi"</string>
+    <string name="no_upstream_notification_disable_button" msgid="868677179945695858">"Modem rejimini faolsizlantirish"</string>
+    <string name="upstream_roaming_notification_title" msgid="2870229486619751829">"Hotspot yoki modem rejimi yoniq"</string>
+    <string name="upstream_roaming_notification_message" msgid="5229740963392849544">"Rouming vaqtida qoʻshimcha haq olinishi mumkin"</string>
 </resources>
diff --git a/Tethering/res/values-mcc310-mnc004-vi/strings.xml b/Tethering/res/values-mcc310-mnc004-vi/strings.xml
index b7cb045..71db045 100644
--- a/Tethering/res/values-mcc310-mnc004-vi/strings.xml
+++ b/Tethering/res/values-mcc310-mnc004-vi/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="5030042590486713460">"Không có Internet để chia sẻ kết Internet"</string>
-    <string name="no_upstream_notification_message" msgid="3843613362272973447">"Các thiết bị không thể kết nối"</string>
-    <string name="no_upstream_notification_disable_button" msgid="6385491461813507624">"Tắt tính năng chia sẻ Internet"</string>
-    <string name="upstream_roaming_notification_title" msgid="3015912166812283303">"Điểm phát sóng hoặc tính năng chia sẻ Internet đang bật"</string>
-    <string name="upstream_roaming_notification_message" msgid="6724434706748439902">"Bạn có thể mất thêm phí dữ liệu khi chuyển vùng"</string>
+    <string name="no_upstream_notification_title" msgid="3584617491053416666">"Không có Internet để chia sẻ Internet"</string>
+    <string name="no_upstream_notification_message" msgid="5626323795587558017">"Các thiết bị không thể kết nối"</string>
+    <string name="no_upstream_notification_disable_button" msgid="868677179945695858">"Tắt tính năng chia sẻ Internet"</string>
+    <string name="upstream_roaming_notification_title" msgid="2870229486619751829">"Điểm phát sóng hoặc tính năng chia sẻ Internet đang bật"</string>
+    <string name="upstream_roaming_notification_message" msgid="5229740963392849544">"Bạn có thể mất thêm phí dữ liệu khi chuyển vùng"</string>
 </resources>
diff --git a/Tethering/res/values-mcc310-mnc004-zh-rCN/strings.xml b/Tethering/res/values-mcc310-mnc004-zh-rCN/strings.xml
index af91aff..d279fdd 100644
--- a/Tethering/res/values-mcc310-mnc004-zh-rCN/strings.xml
+++ b/Tethering/res/values-mcc310-mnc004-zh-rCN/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="5030042590486713460">"共享网络未连接到互联网"</string>
-    <string name="no_upstream_notification_message" msgid="3843613362272973447">"设备无法连接"</string>
-    <string name="no_upstream_notification_disable_button" msgid="6385491461813507624">"关闭网络共享"</string>
-    <string name="upstream_roaming_notification_title" msgid="3015912166812283303">"热点或网络共享已开启"</string>
-    <string name="upstream_roaming_notification_message" msgid="6724434706748439902">"漫游时可能会产生额外的费用"</string>
+    <string name="no_upstream_notification_title" msgid="3584617491053416666">"共享网络未连接到互联网"</string>
+    <string name="no_upstream_notification_message" msgid="5626323795587558017">"设备无法连接"</string>
+    <string name="no_upstream_notification_disable_button" msgid="868677179945695858">"关闭网络共享"</string>
+    <string name="upstream_roaming_notification_title" msgid="2870229486619751829">"热点或网络共享已开启"</string>
+    <string name="upstream_roaming_notification_message" msgid="5229740963392849544">"漫游时可能会产生额外的费用"</string>
 </resources>
diff --git a/Tethering/res/values-mcc310-mnc004-zh-rHK/strings.xml b/Tethering/res/values-mcc310-mnc004-zh-rHK/strings.xml
index 28e6b80..5bad7e4 100644
--- a/Tethering/res/values-mcc310-mnc004-zh-rHK/strings.xml
+++ b/Tethering/res/values-mcc310-mnc004-zh-rHK/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="5030042590486713460">"無法透過網絡共享連線至互聯網"</string>
-    <string name="no_upstream_notification_message" msgid="3843613362272973447">"裝置無法連接"</string>
-    <string name="no_upstream_notification_disable_button" msgid="6385491461813507624">"關閉網絡共享"</string>
-    <string name="upstream_roaming_notification_title" msgid="3015912166812283303">"熱點或網絡共享已開啟"</string>
-    <string name="upstream_roaming_notification_message" msgid="6724434706748439902">"漫遊時可能需要支付額外費用"</string>
+    <string name="no_upstream_notification_title" msgid="3584617491053416666">"無法透過網絡共享連線至互聯網"</string>
+    <string name="no_upstream_notification_message" msgid="5626323795587558017">"裝置無法連接"</string>
+    <string name="no_upstream_notification_disable_button" msgid="868677179945695858">"關閉網絡共享"</string>
+    <string name="upstream_roaming_notification_title" msgid="2870229486619751829">"熱點或網絡共享已開啟"</string>
+    <string name="upstream_roaming_notification_message" msgid="5229740963392849544">"漫遊時可能需要支付額外費用"</string>
 </resources>
diff --git a/Tethering/res/values-mcc310-mnc004-zh-rTW/strings.xml b/Tethering/res/values-mcc310-mnc004-zh-rTW/strings.xml
index 528a1e5..8991ff4 100644
--- a/Tethering/res/values-mcc310-mnc004-zh-rTW/strings.xml
+++ b/Tethering/res/values-mcc310-mnc004-zh-rTW/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="5030042590486713460">"無法透過網路共用連上網際網路"</string>
-    <string name="no_upstream_notification_message" msgid="3843613362272973447">"裝置無法連線"</string>
-    <string name="no_upstream_notification_disable_button" msgid="6385491461813507624">"關閉網路共用"</string>
-    <string name="upstream_roaming_notification_title" msgid="3015912166812283303">"無線基地台或網路共用已開啟"</string>
-    <string name="upstream_roaming_notification_message" msgid="6724434706748439902">"使用漫遊服務可能須支付額外費用"</string>
+    <string name="no_upstream_notification_title" msgid="3584617491053416666">"無法透過網路共用連上網際網路"</string>
+    <string name="no_upstream_notification_message" msgid="5626323795587558017">"裝置無法連線"</string>
+    <string name="no_upstream_notification_disable_button" msgid="868677179945695858">"關閉網路共用"</string>
+    <string name="upstream_roaming_notification_title" msgid="2870229486619751829">"無線基地台或網路共用已開啟"</string>
+    <string name="upstream_roaming_notification_message" msgid="5229740963392849544">"使用漫遊服務可能須支付額外費用"</string>
 </resources>
diff --git a/Tethering/res/values-mcc310-mnc004-zu/strings.xml b/Tethering/res/values-mcc310-mnc004-zu/strings.xml
index 11eb666..31db66a 100644
--- a/Tethering/res/values-mcc310-mnc004-zu/strings.xml
+++ b/Tethering/res/values-mcc310-mnc004-zu/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="5030042590486713460">"Ukusebenzisa ifoni njengemodemu akunayo i-inthanethi"</string>
-    <string name="no_upstream_notification_message" msgid="3843613362272973447">"Amadivayisi awakwazi ukuxhumeka"</string>
-    <string name="no_upstream_notification_disable_button" msgid="6385491461813507624">"Vala ukusebenzisa ifoni njengemodemu"</string>
-    <string name="upstream_roaming_notification_title" msgid="3015912166812283303">"I-hotspot noma ukusebenzisa ifoni njengemodemu kuvuliwe"</string>
-    <string name="upstream_roaming_notification_message" msgid="6724434706748439902">"Kungaba nezinkokhelo ezengeziwe uma uzula"</string>
+    <string name="no_upstream_notification_title" msgid="3584617491053416666">"Ukusebenzisa ifoni njengemodemu akunayo i-inthanethi"</string>
+    <string name="no_upstream_notification_message" msgid="5626323795587558017">"Amadivayisi awakwazi ukuxhuma"</string>
+    <string name="no_upstream_notification_disable_button" msgid="868677179945695858">"Vala ukusebenzisa ifoni njengemodemu"</string>
+    <string name="upstream_roaming_notification_title" msgid="2870229486619751829">"I-hotspot noma ukusebenzisa ifoni njengemodemu kuvuliwe"</string>
+    <string name="upstream_roaming_notification_message" msgid="5229740963392849544">"Kungaba nezinkokhelo ezengeziwe uma uzula"</string>
 </resources>
diff --git a/Tethering/res/values-mcc311-mnc480-af/strings.xml b/Tethering/res/values-mcc311-mnc480-af/strings.xml
index 9bfa531..cc70b66 100644
--- a/Tethering/res/values-mcc311-mnc480-af/strings.xml
+++ b/Tethering/res/values-mcc311-mnc480-af/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="611650570559011140">"Verbinding het nie internet nie"</string>
-    <string name="no_upstream_notification_message" msgid="6508394877641864863">"Toestelle kan nie koppel nie"</string>
-    <string name="no_upstream_notification_disable_button" msgid="7609346639290990508">"Skakel verbinding af"</string>
-    <string name="upstream_roaming_notification_title" msgid="6032901176124830787">"Warmkol of verbinding is aan"</string>
-    <string name="upstream_roaming_notification_message" msgid="7599056263326217523">"Bykomende heffings kan geld terwyl jy swerf"</string>
+    <string name="no_upstream_notification_title" msgid="5897815458155858594">"Verbinding het nie internet nie"</string>
+    <string name="no_upstream_notification_message" msgid="9037716118606459874">"Toestelle kan nie koppel nie"</string>
+    <string name="no_upstream_notification_disable_button" msgid="5284024068281565456">"Skakel verbinding af"</string>
+    <string name="upstream_roaming_notification_title" msgid="186331286017243006">"Warmkol of verbinding is aan"</string>
+    <string name="upstream_roaming_notification_message" msgid="7692641323940316538">"Bykomende heffings kan geld terwyl jy swerf"</string>
 </resources>
diff --git a/Tethering/res/values-mcc311-mnc480-am/strings.xml b/Tethering/res/values-mcc311-mnc480-am/strings.xml
index 5949dfa..9808534 100644
--- a/Tethering/res/values-mcc311-mnc480-am/strings.xml
+++ b/Tethering/res/values-mcc311-mnc480-am/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="611650570559011140">"ማስተሳሰር ምንም በይነመረብ የለውም"</string>
-    <string name="no_upstream_notification_message" msgid="6508394877641864863">"መሣሪያዎችን ማገናኘት አይቻልም"</string>
-    <string name="no_upstream_notification_disable_button" msgid="7609346639290990508">"ማስተሳሰርን አጥፋ"</string>
-    <string name="upstream_roaming_notification_title" msgid="6032901176124830787">"መገናኛ ነጥብ ወይም ማስተሳሰር በርቷል"</string>
-    <string name="upstream_roaming_notification_message" msgid="7599056263326217523">"በሚያንዣብብበት ጊዜ ተጨማሪ ክፍያዎች ተፈጻሚ ሊሆኑ ይችላሉ"</string>
+    <string name="no_upstream_notification_title" msgid="5897815458155858594">"እንደ ሞደም መሰካት ምንም በይነመረብ የለውም"</string>
+    <string name="no_upstream_notification_message" msgid="9037716118606459874">"መሣሪያዎችን ማገናኘት አልተቻልም"</string>
+    <string name="no_upstream_notification_disable_button" msgid="5284024068281565456">"እንደ ሞደም መሰካትን አጥፋ"</string>
+    <string name="upstream_roaming_notification_title" msgid="186331286017243006">"መገናኛ ነጥብ ወይም እንደ ሞደም መሰካት በርቷል"</string>
+    <string name="upstream_roaming_notification_message" msgid="7692641323940316538">"በሚያንዣብብበት ጊዜ ተጨማሪ ክፍያዎች ተፈጻሚ ሊሆኑ ይችላሉ"</string>
 </resources>
diff --git a/Tethering/res/values-mcc311-mnc480-ar/strings.xml b/Tethering/res/values-mcc311-mnc480-ar/strings.xml
index 8467f9b..ab84c4a 100644
--- a/Tethering/res/values-mcc311-mnc480-ar/strings.xml
+++ b/Tethering/res/values-mcc311-mnc480-ar/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="611650570559011140">"ما مِن اتصال بالإنترنت خلال التوصيل"</string>
-    <string name="no_upstream_notification_message" msgid="6508394877641864863">"تعذّر اتصال الأجهزة"</string>
-    <string name="no_upstream_notification_disable_button" msgid="7609346639290990508">"إيقاف التوصيل"</string>
-    <string name="upstream_roaming_notification_title" msgid="6032901176124830787">"نقطة الاتصال أو التوصيل مفعّلان"</string>
-    <string name="upstream_roaming_notification_message" msgid="7599056263326217523">"قد يتم تطبيق رسوم إضافية أثناء التجوال."</string>
+    <string name="no_upstream_notification_title" msgid="5897815458155858594">"ما مِن اتصال بالإنترنت خلال التوصيل"</string>
+    <string name="no_upstream_notification_message" msgid="9037716118606459874">"يتعذّر اتصال الأجهزة"</string>
+    <string name="no_upstream_notification_disable_button" msgid="5284024068281565456">"إيقاف التوصيل"</string>
+    <string name="upstream_roaming_notification_title" msgid="186331286017243006">"نقطة الاتصال مفعَّلة أو التوصيل مفعَّل"</string>
+    <string name="upstream_roaming_notification_message" msgid="7692641323940316538">"قد يتم تحصيل رسوم إضافية أثناء التجوال."</string>
 </resources>
diff --git a/Tethering/res/values-mcc311-mnc480-as/strings.xml b/Tethering/res/values-mcc311-mnc480-as/strings.xml
index 9776bd8..f7ab7e9 100644
--- a/Tethering/res/values-mcc311-mnc480-as/strings.xml
+++ b/Tethering/res/values-mcc311-mnc480-as/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="611650570559011140">"টে\'ডাৰিঙৰ ইণ্টাৰনেট নাই"</string>
-    <string name="no_upstream_notification_message" msgid="6508394877641864863">"ডিভাইচসমূহ সংযোগ কৰিব নোৱাৰি"</string>
-    <string name="no_upstream_notification_disable_button" msgid="7609346639290990508">"টে\'ডাৰিং অফ কৰক"</string>
-    <string name="upstream_roaming_notification_title" msgid="6032901176124830787">"হটস্পট অথবা টে\'ডাৰিং অন আছে"</string>
-    <string name="upstream_roaming_notification_message" msgid="7599056263326217523">"ৰ\'মিঙত থাকিলে অতিৰিক্ত মাচুল প্ৰযোজ্য হ’ব পাৰে"</string>
+    <string name="no_upstream_notification_title" msgid="5897815458155858594">"টে’ডাৰিঙৰ ইণ্টাৰনেট নাই"</string>
+    <string name="no_upstream_notification_message" msgid="9037716118606459874">"ডিভাইচসমূহ সংযোগ কৰিব নোৱাৰি"</string>
+    <string name="no_upstream_notification_disable_button" msgid="5284024068281565456">"টে’ডাৰিং অফ কৰক"</string>
+    <string name="upstream_roaming_notification_title" msgid="186331286017243006">"হ’টস্প’ট অথবা টে’ডাৰিং অন আছে"</string>
+    <string name="upstream_roaming_notification_message" msgid="7692641323940316538">"ৰ’মিঙত থাকিলে অতিৰিক্ত মাচুল প্ৰযোজ্য হ’ব পাৰে"</string>
 </resources>
diff --git a/Tethering/res/values-mcc311-mnc480-az/strings.xml b/Tethering/res/values-mcc311-mnc480-az/strings.xml
index e6d3eaf..6e36df1 100644
--- a/Tethering/res/values-mcc311-mnc480-az/strings.xml
+++ b/Tethering/res/values-mcc311-mnc480-az/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="611650570559011140">"Modemin internetə girişi yoxdur"</string>
-    <string name="no_upstream_notification_message" msgid="6508394877641864863">"Cihazları qoşmaq mümkün deyil"</string>
-    <string name="no_upstream_notification_disable_button" msgid="7609346639290990508">"Modemi deaktiv edin"</string>
-    <string name="upstream_roaming_notification_title" msgid="6032901176124830787">"Hotspot və ya modem aktivdir"</string>
-    <string name="upstream_roaming_notification_message" msgid="7599056263326217523">"Rouminq zamanı əlavə ödənişlər tətbiq edilə bilər"</string>
+    <string name="no_upstream_notification_title" msgid="5897815458155858594">"Modem rejimi internetə qoşulmayıb"</string>
+    <string name="no_upstream_notification_message" msgid="9037716118606459874">"Cihazları qoşmaq olmur"</string>
+    <string name="no_upstream_notification_disable_button" msgid="5284024068281565456">"Modem rejimini deaktiv edin"</string>
+    <string name="upstream_roaming_notification_title" msgid="186331286017243006">"Hotspot və ya modem rejimi aktivdir"</string>
+    <string name="upstream_roaming_notification_message" msgid="7692641323940316538">"Rouminq zamanı əlavə ödəniş çıxıla bilər"</string>
 </resources>
diff --git a/Tethering/res/values-mcc311-mnc480-b+sr+Latn/strings.xml b/Tethering/res/values-mcc311-mnc480-b+sr+Latn/strings.xml
index 4c8a1df..1730075 100644
--- a/Tethering/res/values-mcc311-mnc480-b+sr+Latn/strings.xml
+++ b/Tethering/res/values-mcc311-mnc480-b+sr+Latn/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="611650570559011140">"Privezivanje nema pristup internetu"</string>
-    <string name="no_upstream_notification_message" msgid="6508394877641864863">"Povezivanje uređaja nije uspelo"</string>
-    <string name="no_upstream_notification_disable_button" msgid="7609346639290990508">"Isključi privezivanje"</string>
-    <string name="upstream_roaming_notification_title" msgid="6032901176124830787">"Uključen je hotspot ili privezivanje"</string>
-    <string name="upstream_roaming_notification_message" msgid="7599056263326217523">"Možda važe dodatni troškovi u romingu"</string>
+    <string name="no_upstream_notification_title" msgid="5897815458155858594">"Privezivanje nema pristup internetu"</string>
+    <string name="no_upstream_notification_message" msgid="9037716118606459874">"Povezivanje uređaja nije uspelo"</string>
+    <string name="no_upstream_notification_disable_button" msgid="5284024068281565456">"Isključi privezivanje"</string>
+    <string name="upstream_roaming_notification_title" msgid="186331286017243006">"Uključen je hotspot ili privezivanje"</string>
+    <string name="upstream_roaming_notification_message" msgid="7692641323940316538">"Možda važe dodatni troškovi u romingu"</string>
 </resources>
diff --git a/Tethering/res/values-mcc311-mnc480-be/strings.xml b/Tethering/res/values-mcc311-mnc480-be/strings.xml
index edfa41e..88577d4 100644
--- a/Tethering/res/values-mcc311-mnc480-be/strings.xml
+++ b/Tethering/res/values-mcc311-mnc480-be/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="611650570559011140">"Рэжым мадэма выкарыстоўваецца без доступу да інтэрнэту"</string>
-    <string name="no_upstream_notification_message" msgid="6508394877641864863">"Не ўдалося падключыць прылады"</string>
-    <string name="no_upstream_notification_disable_button" msgid="7609346639290990508">"Выключыць рэжым мадэма"</string>
-    <string name="upstream_roaming_notification_title" msgid="6032901176124830787">"Хот-спот або рэжым мадэма ўключаны"</string>
-    <string name="upstream_roaming_notification_message" msgid="7599056263326217523">"Пры выкарыстанні роўмінгу можа спаганяцца дадатковая плата"</string>
+    <string name="no_upstream_notification_title" msgid="5897815458155858594">"Рэжым мадэма выкарыстоўваецца без доступу да інтэрнэту"</string>
+    <string name="no_upstream_notification_message" msgid="9037716118606459874">"Не ўдалося падключыць прылады"</string>
+    <string name="no_upstream_notification_disable_button" msgid="5284024068281565456">"Выключыць рэжым мадэма"</string>
+    <string name="upstream_roaming_notification_title" msgid="186331286017243006">"Хот-спот або рэжым мадэма ўключаны"</string>
+    <string name="upstream_roaming_notification_message" msgid="7692641323940316538">"Пры выкарыстанні роўмінгу можа спаганяцца дадатковая плата"</string>
 </resources>
diff --git a/Tethering/res/values-mcc311-mnc480-bg/strings.xml b/Tethering/res/values-mcc311-mnc480-bg/strings.xml
index f563981..d549997 100644
--- a/Tethering/res/values-mcc311-mnc480-bg/strings.xml
+++ b/Tethering/res/values-mcc311-mnc480-bg/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="611650570559011140">"Тетърингът няма връзка с интернет"</string>
-    <string name="no_upstream_notification_message" msgid="6508394877641864863">"Устройствата не могат да установят връзка"</string>
-    <string name="no_upstream_notification_disable_button" msgid="7609346639290990508">"Изключване на тетъринга"</string>
-    <string name="upstream_roaming_notification_title" msgid="6032901176124830787">"Точката за достъп или тетърингът са включени"</string>
-    <string name="upstream_roaming_notification_message" msgid="7599056263326217523">"Възможно е да ви бъдат начислени допълнителни такси при роуминг"</string>
+    <string name="no_upstream_notification_title" msgid="5897815458155858594">"Няма връзка с интернет за тетъринг"</string>
+    <string name="no_upstream_notification_message" msgid="9037716118606459874">"Устройствата не могат да установят връзка"</string>
+    <string name="no_upstream_notification_disable_button" msgid="5284024068281565456">"Изключване на функцията за тетъринг"</string>
+    <string name="upstream_roaming_notification_title" msgid="186331286017243006">"Точката за достъп или функцията за тетъринг са включени"</string>
+    <string name="upstream_roaming_notification_message" msgid="7692641323940316538">"Възможно е да ви бъдат начислени допълнителни такси при роуминг"</string>
 </resources>
diff --git a/Tethering/res/values-mcc311-mnc480-bn/strings.xml b/Tethering/res/values-mcc311-mnc480-bn/strings.xml
index d8ecd2e..93e316a 100644
--- a/Tethering/res/values-mcc311-mnc480-bn/strings.xml
+++ b/Tethering/res/values-mcc311-mnc480-bn/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="611650570559011140">"টিথারিং করার জন্য কোনও ইন্টারনেট কানেকশন নেই"</string>
-    <string name="no_upstream_notification_message" msgid="6508394877641864863">"ডিভাইস কানেক্ট করতে পারছে না"</string>
-    <string name="no_upstream_notification_disable_button" msgid="7609346639290990508">"টিথারিং বন্ধ করুন"</string>
-    <string name="upstream_roaming_notification_title" msgid="6032901176124830787">"হটস্পট বা টিথারিং চালু আছে"</string>
-    <string name="upstream_roaming_notification_message" msgid="7599056263326217523">"রোমিংয়ের সময় অতিরিক্ত চার্জ করা হতে পারে"</string>
+    <string name="no_upstream_notification_title" msgid="5897815458155858594">"টেথারিং করার জন্য কোনও ইন্টারনেট কানেকশন লাগে না"</string>
+    <string name="no_upstream_notification_message" msgid="9037716118606459874">"ডিভাইস কানেক্ট করা যাচ্ছে না"</string>
+    <string name="no_upstream_notification_disable_button" msgid="5284024068281565456">"টেথারিং বন্ধ করুন"</string>
+    <string name="upstream_roaming_notification_title" msgid="186331286017243006">"হটস্পট বা টেথারিং চালু আছে"</string>
+    <string name="upstream_roaming_notification_message" msgid="7692641323940316538">"রোমিংয়ে থাকার সময় অতিরিক্ত চার্জ লাগতে পারে"</string>
 </resources>
diff --git a/Tethering/res/values-mcc311-mnc480-bs/strings.xml b/Tethering/res/values-mcc311-mnc480-bs/strings.xml
index b85fd5e..27777c2 100644
--- a/Tethering/res/values-mcc311-mnc480-bs/strings.xml
+++ b/Tethering/res/values-mcc311-mnc480-bs/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="611650570559011140">"Povezivanje putem mobitela nema internet"</string>
-    <string name="no_upstream_notification_message" msgid="6508394877641864863">"Uređaji se ne mogu povezati"</string>
-    <string name="no_upstream_notification_disable_button" msgid="7609346639290990508">"Isključi povezivanje putem mobitela"</string>
-    <string name="upstream_roaming_notification_title" msgid="6032901176124830787">"Pristupna tačka ili povezivanje putem mobitela je uključeno"</string>
-    <string name="upstream_roaming_notification_message" msgid="7599056263326217523">"Mogu nastati dodatni troškovi u romingu"</string>
+    <string name="no_upstream_notification_title" msgid="5897815458155858594">"Dijeljenje internetske veze nema internet"</string>
+    <string name="no_upstream_notification_message" msgid="9037716118606459874">"Nije moguće povezati uređaje"</string>
+    <string name="no_upstream_notification_disable_button" msgid="5284024068281565456">"Isključi dijeljenje internetske veze"</string>
+    <string name="upstream_roaming_notification_title" msgid="186331286017243006">"Pristupna tačka ili dijeljenje internetske veze su uključeni"</string>
+    <string name="upstream_roaming_notification_message" msgid="7692641323940316538">"Mogu nastati dodatni troškovi u romingu"</string>
 </resources>
diff --git a/Tethering/res/values-mcc311-mnc480-ca/strings.xml b/Tethering/res/values-mcc311-mnc480-ca/strings.xml
index a357215..dad35f8 100644
--- a/Tethering/res/values-mcc311-mnc480-ca/strings.xml
+++ b/Tethering/res/values-mcc311-mnc480-ca/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="611650570559011140">"La compartició de xarxa no té accés a Internet"</string>
-    <string name="no_upstream_notification_message" msgid="6508394877641864863">"No es poden connectar els dispositius"</string>
-    <string name="no_upstream_notification_disable_button" msgid="7609346639290990508">"Desactiva la compartició de xarxa"</string>
-    <string name="upstream_roaming_notification_title" msgid="6032901176124830787">"S\'ha activat el punt d\'accés Wi‑Fi o la compartició de xarxa"</string>
-    <string name="upstream_roaming_notification_message" msgid="7599056263326217523">"És possible que s\'apliquin costos addicionals en itinerància"</string>
+    <string name="no_upstream_notification_title" msgid="5897815458155858594">"La compartició de xarxa no té accés a Internet"</string>
+    <string name="no_upstream_notification_message" msgid="9037716118606459874">"No es poden connectar els dispositius"</string>
+    <string name="no_upstream_notification_disable_button" msgid="5284024068281565456">"Desactiva la compartició de xarxa"</string>
+    <string name="upstream_roaming_notification_title" msgid="186331286017243006">"El punt d\'accés Wi‑Fi o la compartició de xarxa estan activats"</string>
+    <string name="upstream_roaming_notification_message" msgid="7692641323940316538">"És possible que s\'apliquin costos addicionals en itinerància"</string>
 </resources>
diff --git a/Tethering/res/values-mcc311-mnc480-cs/strings.xml b/Tethering/res/values-mcc311-mnc480-cs/strings.xml
index 91196be..cbcabe1 100644
--- a/Tethering/res/values-mcc311-mnc480-cs/strings.xml
+++ b/Tethering/res/values-mcc311-mnc480-cs/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="611650570559011140">"Tethering nemá připojení k internetu"</string>
-    <string name="no_upstream_notification_message" msgid="6508394877641864863">"Zařízení se nemůžou připojit"</string>
-    <string name="no_upstream_notification_disable_button" msgid="7609346639290990508">"Vypnout tethering"</string>
-    <string name="upstream_roaming_notification_title" msgid="6032901176124830787">"Je zapnutý hotspot nebo tethering"</string>
-    <string name="upstream_roaming_notification_message" msgid="7599056263326217523">"Při roamingu mohou být účtovány dodatečné poplatky"</string>
+    <string name="no_upstream_notification_title" msgid="5897815458155858594">"Tethering nemá připojení k internetu"</string>
+    <string name="no_upstream_notification_message" msgid="9037716118606459874">"Zařízení se nemůžou připojit"</string>
+    <string name="no_upstream_notification_disable_button" msgid="5284024068281565456">"Vypnout tethering"</string>
+    <string name="upstream_roaming_notification_title" msgid="186331286017243006">"Je zapnutý hotspot nebo tethering"</string>
+    <string name="upstream_roaming_notification_message" msgid="7692641323940316538">"Při roamingu mohou být účtovány dodatečné poplatky"</string>
 </resources>
diff --git a/Tethering/res/values-mcc311-mnc480-da/strings.xml b/Tethering/res/values-mcc311-mnc480-da/strings.xml
index 1968900..9176709 100644
--- a/Tethering/res/values-mcc311-mnc480-da/strings.xml
+++ b/Tethering/res/values-mcc311-mnc480-da/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="611650570559011140">"Netdeling har ingen internetforbindelse"</string>
-    <string name="no_upstream_notification_message" msgid="6508394877641864863">"Enheder kan ikke oprette forbindelse"</string>
-    <string name="no_upstream_notification_disable_button" msgid="7609346639290990508">"Deaktiver netdeling"</string>
-    <string name="upstream_roaming_notification_title" msgid="6032901176124830787">"Hotspot eller netdeling er aktiveret"</string>
-    <string name="upstream_roaming_notification_message" msgid="7599056263326217523">"Der opkræves muligvis yderligere gebyrer ved roaming"</string>
+    <string name="no_upstream_notification_title" msgid="5897815458155858594">"Netdeling har ingen internetforbindelse"</string>
+    <string name="no_upstream_notification_message" msgid="9037716118606459874">"Enheder kan ikke oprette forbindelse"</string>
+    <string name="no_upstream_notification_disable_button" msgid="5284024068281565456">"Deaktiver netdeling"</string>
+    <string name="upstream_roaming_notification_title" msgid="186331286017243006">"Hotspot eller netdeling er aktiveret"</string>
+    <string name="upstream_roaming_notification_message" msgid="7692641323940316538">"Der opkræves muligvis yderligere gebyrer ved roaming"</string>
 </resources>
diff --git a/Tethering/res/values-mcc311-mnc480-de/strings.xml b/Tethering/res/values-mcc311-mnc480-de/strings.xml
index eb3f8c5..b3bc7d8 100644
--- a/Tethering/res/values-mcc311-mnc480-de/strings.xml
+++ b/Tethering/res/values-mcc311-mnc480-de/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="611650570559011140">"Tethering hat keinen Internetzugriff"</string>
-    <string name="no_upstream_notification_message" msgid="6508394877641864863">"Geräte können sich nicht verbinden"</string>
-    <string name="no_upstream_notification_disable_button" msgid="7609346639290990508">"Tethering deaktivieren"</string>
-    <string name="upstream_roaming_notification_title" msgid="6032901176124830787">"Hotspot oder Tethering ist aktiviert"</string>
-    <string name="upstream_roaming_notification_message" msgid="7599056263326217523">"Für das Roaming können zusätzliche Gebühren anfallen"</string>
+    <string name="no_upstream_notification_title" msgid="5897815458155858594">"Tethering hat keinen Internetzugriff"</string>
+    <string name="no_upstream_notification_message" msgid="9037716118606459874">"Geräte können sich nicht verbinden"</string>
+    <string name="no_upstream_notification_disable_button" msgid="5284024068281565456">"Tethering deaktivieren"</string>
+    <string name="upstream_roaming_notification_title" msgid="186331286017243006">"Hotspot oder Tethering ist aktiviert"</string>
+    <string name="upstream_roaming_notification_message" msgid="7692641323940316538">"Für das Roaming können zusätzliche Gebühren anfallen"</string>
 </resources>
diff --git a/Tethering/res/values-mcc311-mnc480-el/strings.xml b/Tethering/res/values-mcc311-mnc480-el/strings.xml
index 56c3d81..babd62c 100644
--- a/Tethering/res/values-mcc311-mnc480-el/strings.xml
+++ b/Tethering/res/values-mcc311-mnc480-el/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="611650570559011140">"Η σύνδεση δεν έχει πρόσβαση στο διαδίκτυο"</string>
-    <string name="no_upstream_notification_message" msgid="6508394877641864863">"Δεν είναι δυνατή η σύνδεση των συσκευών"</string>
-    <string name="no_upstream_notification_disable_button" msgid="7609346639290990508">"Απενεργοποιήστε τη σύνδεση"</string>
-    <string name="upstream_roaming_notification_title" msgid="6032901176124830787">"Ενεργό σημείο πρόσβασης Wi-Fi ή ενεργή σύνδεση"</string>
-    <string name="upstream_roaming_notification_message" msgid="7599056263326217523">"Ενδέχεται να ισχύουν επιπλέον χρεώσεις κατά την περιαγωγή."</string>
+    <string name="no_upstream_notification_title" msgid="5897815458155858594">"Η σύνδεση δεν έχει πρόσβαση στο διαδίκτυο"</string>
+    <string name="no_upstream_notification_message" msgid="9037716118606459874">"Δεν είναι δυνατή η σύνδεση των συσκευών"</string>
+    <string name="no_upstream_notification_disable_button" msgid="5284024068281565456">"Απενεργοποίηση σύνδεσης"</string>
+    <string name="upstream_roaming_notification_title" msgid="186331286017243006">"Ενεργό σημείο πρόσβασης Wi-Fi ή ενεργή σύνδεση"</string>
+    <string name="upstream_roaming_notification_message" msgid="7692641323940316538">"Ενδέχεται να ισχύουν επιπλέον χρεώσεις κατά την περιαγωγή."</string>
 </resources>
diff --git a/Tethering/res/values-mcc311-mnc480-en-rAU/strings.xml b/Tethering/res/values-mcc311-mnc480-en-rAU/strings.xml
index dd1a197..afa4467 100644
--- a/Tethering/res/values-mcc311-mnc480-en-rAU/strings.xml
+++ b/Tethering/res/values-mcc311-mnc480-en-rAU/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="611650570559011140">"Tethering has no Internet"</string>
-    <string name="no_upstream_notification_message" msgid="6508394877641864863">"Devices can’t connect"</string>
-    <string name="no_upstream_notification_disable_button" msgid="7609346639290990508">"Turn off tethering"</string>
-    <string name="upstream_roaming_notification_title" msgid="6032901176124830787">"Hotspot or tethering is on"</string>
-    <string name="upstream_roaming_notification_message" msgid="7599056263326217523">"Additional charges may apply while roaming"</string>
+    <string name="no_upstream_notification_title" msgid="5897815458155858594">"Tethering has no Internet"</string>
+    <string name="no_upstream_notification_message" msgid="9037716118606459874">"Devices can\'t connect"</string>
+    <string name="no_upstream_notification_disable_button" msgid="5284024068281565456">"Turn off tethering"</string>
+    <string name="upstream_roaming_notification_title" msgid="186331286017243006">"Hotspot or tethering is on"</string>
+    <string name="upstream_roaming_notification_message" msgid="7692641323940316538">"Additional charges may apply while roaming"</string>
 </resources>
diff --git a/Tethering/res/values-mcc311-mnc480-en-rCA/strings.xml b/Tethering/res/values-mcc311-mnc480-en-rCA/strings.xml
index dd1a197..251cad6 100644
--- a/Tethering/res/values-mcc311-mnc480-en-rCA/strings.xml
+++ b/Tethering/res/values-mcc311-mnc480-en-rCA/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="611650570559011140">"Tethering has no Internet"</string>
-    <string name="no_upstream_notification_message" msgid="6508394877641864863">"Devices can’t connect"</string>
-    <string name="no_upstream_notification_disable_button" msgid="7609346639290990508">"Turn off tethering"</string>
-    <string name="upstream_roaming_notification_title" msgid="6032901176124830787">"Hotspot or tethering is on"</string>
-    <string name="upstream_roaming_notification_message" msgid="7599056263326217523">"Additional charges may apply while roaming"</string>
+    <string name="no_upstream_notification_title" msgid="5897815458155858594">"Tethering has no internet"</string>
+    <string name="no_upstream_notification_message" msgid="9037716118606459874">"Devices can’t connect"</string>
+    <string name="no_upstream_notification_disable_button" msgid="5284024068281565456">"Turn off tethering"</string>
+    <string name="upstream_roaming_notification_title" msgid="186331286017243006">"Hotspot or tethering is on"</string>
+    <string name="upstream_roaming_notification_message" msgid="7692641323940316538">"Additional charges may apply while roaming"</string>
 </resources>
diff --git a/Tethering/res/values-mcc311-mnc480-en-rGB/strings.xml b/Tethering/res/values-mcc311-mnc480-en-rGB/strings.xml
index dd1a197..afa4467 100644
--- a/Tethering/res/values-mcc311-mnc480-en-rGB/strings.xml
+++ b/Tethering/res/values-mcc311-mnc480-en-rGB/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="611650570559011140">"Tethering has no Internet"</string>
-    <string name="no_upstream_notification_message" msgid="6508394877641864863">"Devices can’t connect"</string>
-    <string name="no_upstream_notification_disable_button" msgid="7609346639290990508">"Turn off tethering"</string>
-    <string name="upstream_roaming_notification_title" msgid="6032901176124830787">"Hotspot or tethering is on"</string>
-    <string name="upstream_roaming_notification_message" msgid="7599056263326217523">"Additional charges may apply while roaming"</string>
+    <string name="no_upstream_notification_title" msgid="5897815458155858594">"Tethering has no Internet"</string>
+    <string name="no_upstream_notification_message" msgid="9037716118606459874">"Devices can\'t connect"</string>
+    <string name="no_upstream_notification_disable_button" msgid="5284024068281565456">"Turn off tethering"</string>
+    <string name="upstream_roaming_notification_title" msgid="186331286017243006">"Hotspot or tethering is on"</string>
+    <string name="upstream_roaming_notification_message" msgid="7692641323940316538">"Additional charges may apply while roaming"</string>
 </resources>
diff --git a/Tethering/res/values-mcc311-mnc480-en-rIN/strings.xml b/Tethering/res/values-mcc311-mnc480-en-rIN/strings.xml
index dd1a197..afa4467 100644
--- a/Tethering/res/values-mcc311-mnc480-en-rIN/strings.xml
+++ b/Tethering/res/values-mcc311-mnc480-en-rIN/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="611650570559011140">"Tethering has no Internet"</string>
-    <string name="no_upstream_notification_message" msgid="6508394877641864863">"Devices can’t connect"</string>
-    <string name="no_upstream_notification_disable_button" msgid="7609346639290990508">"Turn off tethering"</string>
-    <string name="upstream_roaming_notification_title" msgid="6032901176124830787">"Hotspot or tethering is on"</string>
-    <string name="upstream_roaming_notification_message" msgid="7599056263326217523">"Additional charges may apply while roaming"</string>
+    <string name="no_upstream_notification_title" msgid="5897815458155858594">"Tethering has no Internet"</string>
+    <string name="no_upstream_notification_message" msgid="9037716118606459874">"Devices can\'t connect"</string>
+    <string name="no_upstream_notification_disable_button" msgid="5284024068281565456">"Turn off tethering"</string>
+    <string name="upstream_roaming_notification_title" msgid="186331286017243006">"Hotspot or tethering is on"</string>
+    <string name="upstream_roaming_notification_message" msgid="7692641323940316538">"Additional charges may apply while roaming"</string>
 </resources>
diff --git a/Tethering/res/values-mcc311-mnc480-en-rXC/strings.xml b/Tethering/res/values-mcc311-mnc480-en-rXC/strings.xml
index d3347aa..766a0e8 100644
--- a/Tethering/res/values-mcc311-mnc480-en-rXC/strings.xml
+++ b/Tethering/res/values-mcc311-mnc480-en-rXC/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="611650570559011140">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‎‎‎‎‎‏‎‎‏‏‏‏‎‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‎‏‎‏‏‎‏‏‏‎‎‏‎‏‎‎‎‏‎‎‎Tethering has no internet‎‏‎‎‏‎"</string>
-    <string name="no_upstream_notification_message" msgid="6508394877641864863">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‎‏‎‎‏‎‏‎‎‏‎‎‏‏‏‎‏‏‎‏‎‏‎‏‎‎‎‏‎‎‎‎‎‏‏‏‏‎‏‎‎‎‎‏‎‏‏‎‏‏‎‏‎‎‏‏‏‏‏‎Devices can’t connect‎‏‎‎‏‎"</string>
-    <string name="no_upstream_notification_disable_button" msgid="7609346639290990508">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‎‎‏‏‎‎‏‏‎‎‏‏‏‎‏‎‏‎‎‎‏‏‏‎‎‏‏‏‏‎‎‏‏‏‏‏‏‎‎‎‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‏‏‎‎‎Turn off tethering‎‏‎‎‏‎"</string>
-    <string name="upstream_roaming_notification_title" msgid="6032901176124830787">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‎‏‏‏‎‏‏‏‎‎‏‎‎‏‎‏‎‏‏‏‎‏‎‎‏‏‏‏‏‏‎‏‏‏‏‏‏‏‏‎‏‎‎‏‏‏‏‎‏‎‎‎‏‎‎‎‎‏‏‎Hotspot or tethering is on‎‏‎‎‏‎"</string>
-    <string name="upstream_roaming_notification_message" msgid="7599056263326217523">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‎‎‏‎‏‏‏‎‏‎‏‎‏‎‎‎‏‎‏‎‏‏‎‏‎‎‎‏‏‏‏‎‎‏‏‏‏‎‎‎‏‎‎‎‎‏‏‎‏‎‏‎‎‏‏‎‎‏‏‎Additional charges may apply while roaming‎‏‎‎‏‎"</string>
+    <string name="no_upstream_notification_title" msgid="5897815458155858594">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‎‎‏‏‏‎‏‏‎‎‏‎‎‏‏‏‏‏‏‏‏‏‎‏‎‎‏‎‏‎‎‎‏‏‎‎‎‏‏‎‎‎‎‎‎‎‎‏‎‏‎‏‎‏‎‎‎‏‎‎Tethering has no internet‎‏‎‎‏‎"</string>
+    <string name="no_upstream_notification_message" msgid="9037716118606459874">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‎‏‎‏‏‎‏‏‎‎‎‏‏‎‏‎‏‎‏‏‏‎‏‏‎‏‎‎‎‎‏‏‏‎‎‏‎‏‏‏‎‎‎‎‏‏‎‎‏‏‏‏‏‎‎‎‏‎‎Devices can’t connect‎‏‎‎‏‎"</string>
+    <string name="no_upstream_notification_disable_button" msgid="5284024068281565456">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‎‎‏‎‏‎‏‎‏‎‎‏‎‎‏‏‏‏‏‏‏‏‎‎‏‎‎‏‏‏‎‎‏‏‎‏‎‎‎‏‎‎‏‏‎‎‎‎‎‎‏‎‎‎‏‎‎‎‎‎Turn off tethering‎‏‎‎‏‎"</string>
+    <string name="upstream_roaming_notification_title" msgid="186331286017243006">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‎‏‎‏‎‏‎‎‏‎‏‎‏‏‏‏‏‏‎‏‏‎‏‎‏‎‎‎‏‎‏‎‎‎‎‎‏‏‎‎‏‏‎‎‏‎‏‏‏‎‏‏‏‎‏‏‏‏‏‏‎‎Hotspot or tethering is on‎‏‎‎‏‎"</string>
+    <string name="upstream_roaming_notification_message" msgid="7692641323940316538">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‎‏‎‏‏‎‎‎‎‎‏‏‏‎‎‎‎‎‎‏‎‎‎‎‏‏‎‏‏‎‏‏‎‎‏‏‏‏‏‎‏‎‎‎‎‎‎‎‏‎‏‎‏‏‏‏‎‏‎‎Additional charges may apply while roaming‎‏‎‎‏‎"</string>
 </resources>
diff --git a/Tethering/res/values-mcc311-mnc480-es-rUS/strings.xml b/Tethering/res/values-mcc311-mnc480-es-rUS/strings.xml
index 2f0504f..16c6059 100644
--- a/Tethering/res/values-mcc311-mnc480-es-rUS/strings.xml
+++ b/Tethering/res/values-mcc311-mnc480-es-rUS/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="611650570559011140">"La conexión mediante dispositivo móvil no tiene Internet"</string>
-    <string name="no_upstream_notification_message" msgid="6508394877641864863">"No se pueden conectar los dispositivos"</string>
-    <string name="no_upstream_notification_disable_button" msgid="7609346639290990508">"Desactivar conexión mediante dispositivo móvil"</string>
-    <string name="upstream_roaming_notification_title" msgid="6032901176124830787">"Se activó el hotspot o la conexión mediante dispositivo móvil"</string>
-    <string name="upstream_roaming_notification_message" msgid="7599056263326217523">"Es posible que se apliquen cargos adicionales por roaming"</string>
+    <string name="no_upstream_notification_title" msgid="5897815458155858594">"La conexión mediante dispositivo móvil no tiene Internet"</string>
+    <string name="no_upstream_notification_message" msgid="9037716118606459874">"No se pueden conectar los dispositivos"</string>
+    <string name="no_upstream_notification_disable_button" msgid="5284024068281565456">"Desactivar conexión mediante dispositivo móvil"</string>
+    <string name="upstream_roaming_notification_title" msgid="186331286017243006">"Se activó el hotspot o la conexión mediante dispositivo móvil"</string>
+    <string name="upstream_roaming_notification_message" msgid="7692641323940316538">"Es posible que se apliquen cargos adicionales por roaming"</string>
 </resources>
diff --git a/Tethering/res/values-mcc311-mnc480-es/strings.xml b/Tethering/res/values-mcc311-mnc480-es/strings.xml
index 2d8f882..952e056 100644
--- a/Tethering/res/values-mcc311-mnc480-es/strings.xml
+++ b/Tethering/res/values-mcc311-mnc480-es/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="611650570559011140">"La conexión no se puede compartir, porque no hay acceso a Internet"</string>
-    <string name="no_upstream_notification_message" msgid="6508394877641864863">"Los dispositivos no se pueden conectar"</string>
-    <string name="no_upstream_notification_disable_button" msgid="7609346639290990508">"Desactivar conexión compartida"</string>
-    <string name="upstream_roaming_notification_title" msgid="6032901176124830787">"Punto de acceso o conexión compartida activados"</string>
-    <string name="upstream_roaming_notification_message" msgid="7599056263326217523">"Puede que se apliquen cargos adicionales en itinerancia"</string>
+    <string name="no_upstream_notification_title" msgid="5897815458155858594">"La conexión no se puede compartir, porque no hay acceso a Internet"</string>
+    <string name="no_upstream_notification_message" msgid="9037716118606459874">"Los dispositivos no se pueden conectar"</string>
+    <string name="no_upstream_notification_disable_button" msgid="5284024068281565456">"Desactivar conexión compartida"</string>
+    <string name="upstream_roaming_notification_title" msgid="186331286017243006">"Punto de acceso o conexión compartida activados"</string>
+    <string name="upstream_roaming_notification_message" msgid="7692641323940316538">"Pueden aplicarse cargos adicionales en roaming"</string>
 </resources>
diff --git a/Tethering/res/values-mcc311-mnc480-et/strings.xml b/Tethering/res/values-mcc311-mnc480-et/strings.xml
index 8493c47..c9cae1f 100644
--- a/Tethering/res/values-mcc311-mnc480-et/strings.xml
+++ b/Tethering/res/values-mcc311-mnc480-et/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="611650570559011140">"Jagamisel puudub internetiühendus"</string>
-    <string name="no_upstream_notification_message" msgid="6508394877641864863">"Seadmed ei saa ühendust luua"</string>
-    <string name="no_upstream_notification_disable_button" msgid="7609346639290990508">"Lülita jagamine välja"</string>
-    <string name="upstream_roaming_notification_title" msgid="6032901176124830787">"Kuumkoht või jagamine on sisse lülitatud"</string>
-    <string name="upstream_roaming_notification_message" msgid="7599056263326217523">"Rändluse kasutamisega võivad kaasneda lisatasud"</string>
+    <string name="no_upstream_notification_title" msgid="5897815458155858594">"Jagamisel puudub internetiühendus"</string>
+    <string name="no_upstream_notification_message" msgid="9037716118606459874">"Seadmed ei saa ühendust luua"</string>
+    <string name="no_upstream_notification_disable_button" msgid="5284024068281565456">"Lülita jagamine välja"</string>
+    <string name="upstream_roaming_notification_title" msgid="186331286017243006">"Kuumkoht või jagamine on sisse lülitatud"</string>
+    <string name="upstream_roaming_notification_message" msgid="7692641323940316538">"Rändluse kasutamisega võivad kaasneda lisatasud"</string>
 </resources>
diff --git a/Tethering/res/values-mcc311-mnc480-eu/strings.xml b/Tethering/res/values-mcc311-mnc480-eu/strings.xml
index 33bccab..7abb4b0 100644
--- a/Tethering/res/values-mcc311-mnc480-eu/strings.xml
+++ b/Tethering/res/values-mcc311-mnc480-eu/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="611650570559011140">"Konexioa partekatzeko aukerak ez du Interneteko konexiorik"</string>
-    <string name="no_upstream_notification_message" msgid="6508394877641864863">"Ezin dira konektatu gailuak"</string>
-    <string name="no_upstream_notification_disable_button" msgid="7609346639290990508">"Desaktibatu konexioa partekatzeko aukera"</string>
-    <string name="upstream_roaming_notification_title" msgid="6032901176124830787">"Wifi-gunea edo konexioa partekatzeko aukera aktibatuta dago"</string>
-    <string name="upstream_roaming_notification_message" msgid="7599056263326217523">"Baliteke kostu gehigarriak ordaindu behar izatea ibiltaritzan"</string>
+    <string name="no_upstream_notification_title" msgid="5897815458155858594">"Konexioa partekatzeko aukerak ez du Interneteko konexiorik"</string>
+    <string name="no_upstream_notification_message" msgid="9037716118606459874">"Ezin dira konektatu gailuak"</string>
+    <string name="no_upstream_notification_disable_button" msgid="5284024068281565456">"Desaktibatu konexioa partekatzeko aukera"</string>
+    <string name="upstream_roaming_notification_title" msgid="186331286017243006">"Wifi-gunea edo konexioa partekatzeko aukera aktibatuta dago"</string>
+    <string name="upstream_roaming_notification_message" msgid="7692641323940316538">"Baliteke tarifa gehigarriak ordaindu behar izatea ibiltaritza erabili bitartean"</string>
 </resources>
diff --git a/Tethering/res/values-mcc311-mnc480-fa/strings.xml b/Tethering/res/values-mcc311-mnc480-fa/strings.xml
index cf8a0cc..6bdf387 100644
--- a/Tethering/res/values-mcc311-mnc480-fa/strings.xml
+++ b/Tethering/res/values-mcc311-mnc480-fa/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="611650570559011140">"«اشتراک‌گذاری اینترنت» به اینترنت دسترسی ندارد"</string>
-    <string name="no_upstream_notification_message" msgid="6508394877641864863">"دستگاه‌ها متصل نمی‌شوند"</string>
-    <string name="no_upstream_notification_disable_button" msgid="7609346639290990508">"خاموش کردن «اشتراک‌گذاری اینترنت»"</string>
-    <string name="upstream_roaming_notification_title" msgid="6032901176124830787">"«نقطه اتصال» یا «اشتراک‌گذاری اینترنت» روشن است"</string>
-    <string name="upstream_roaming_notification_message" msgid="7599056263326217523">"ممکن است درحین فراگردی تغییرات دیگر اعمال شود"</string>
+    <string name="no_upstream_notification_title" msgid="5897815458155858594">"«اشتراک‌گذاری اینترنت» به اینترنت دسترسی ندارد"</string>
+    <string name="no_upstream_notification_message" msgid="9037716118606459874">"دستگاه‌ها متصل نمی‌شوند"</string>
+    <string name="no_upstream_notification_disable_button" msgid="5284024068281565456">"خاموش کردن «اشتراک‌گذاری اینترنت»"</string>
+    <string name="upstream_roaming_notification_title" msgid="186331286017243006">"«نقطه اتصال» یا «اشتراک‌گذاری اینترنت» روشن است"</string>
+    <string name="upstream_roaming_notification_message" msgid="7692641323940316538">"هنگام فراگردی ممکن است هزینه‌های اضافی کسر شود"</string>
 </resources>
diff --git a/Tethering/res/values-mcc311-mnc480-fi/strings.xml b/Tethering/res/values-mcc311-mnc480-fi/strings.xml
index 6a3ab80..57f16bb 100644
--- a/Tethering/res/values-mcc311-mnc480-fi/strings.xml
+++ b/Tethering/res/values-mcc311-mnc480-fi/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="611650570559011140">"Ei jaettavaa internetyhteyttä"</string>
-    <string name="no_upstream_notification_message" msgid="6508394877641864863">"Laitteet eivät voi muodostaa yhteyttä"</string>
-    <string name="no_upstream_notification_disable_button" msgid="7609346639290990508">"Laita yhteyden jakaminen pois päältä"</string>
-    <string name="upstream_roaming_notification_title" msgid="6032901176124830787">"Hotspot tai yhteyden jakaminen on päällä"</string>
-    <string name="upstream_roaming_notification_message" msgid="7599056263326217523">"Roaming voi aiheuttaa lisämaksuja"</string>
+    <string name="no_upstream_notification_title" msgid="5897815458155858594">"Puhelinta ei voi käyttää modeemina, koska sillä ei ole internet-yhteyttä"</string>
+    <string name="no_upstream_notification_message" msgid="9037716118606459874">"Laitteet eivät voi muodostaa yhteyttä"</string>
+    <string name="no_upstream_notification_disable_button" msgid="5284024068281565456">"Laita puhelimen käyttäminen modeemina pois päältä"</string>
+    <string name="upstream_roaming_notification_title" msgid="186331286017243006">"Hotspot tai puhelimen käyttäminen modeemina on päällä"</string>
+    <string name="upstream_roaming_notification_message" msgid="7692641323940316538">"Roaming voi aiheuttaa lisämaksuja"</string>
 </resources>
diff --git a/Tethering/res/values-mcc311-mnc480-fr-rCA/strings.xml b/Tethering/res/values-mcc311-mnc480-fr-rCA/strings.xml
index ffb9bf6..bf3d634 100644
--- a/Tethering/res/values-mcc311-mnc480-fr-rCA/strings.xml
+++ b/Tethering/res/values-mcc311-mnc480-fr-rCA/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="611650570559011140">"Le partage de connexion n\'est pas connecté à Internet"</string>
-    <string name="no_upstream_notification_message" msgid="6508394877641864863">"Impossible de connecter les appareils"</string>
-    <string name="no_upstream_notification_disable_button" msgid="7609346639290990508">"Désactiver le partage de connexion"</string>
-    <string name="upstream_roaming_notification_title" msgid="6032901176124830787">"Le point d\'accès ou le partage de connexion est activé"</string>
-    <string name="upstream_roaming_notification_message" msgid="7599056263326217523">"En itinérance, des frais supplémentaires peuvent s\'appliquer"</string>
+    <string name="no_upstream_notification_title" msgid="5897815458155858594">"Le partage de connexion n\'est pas connecté à Internet"</string>
+    <string name="no_upstream_notification_message" msgid="9037716118606459874">"Impossible de connecter les appareils"</string>
+    <string name="no_upstream_notification_disable_button" msgid="5284024068281565456">"Désactiver le partage de connexion"</string>
+    <string name="upstream_roaming_notification_title" msgid="186331286017243006">"Le point d\'accès sans fil ou le partage de connexion est activé"</string>
+    <string name="upstream_roaming_notification_message" msgid="7692641323940316538">"En itinérance, des frais supplémentaires peuvent s\'appliquer"</string>
 </resources>
diff --git a/Tethering/res/values-mcc311-mnc480-fr/strings.xml b/Tethering/res/values-mcc311-mnc480-fr/strings.xml
index 768bce3..6faa61e 100644
--- a/Tethering/res/values-mcc311-mnc480-fr/strings.xml
+++ b/Tethering/res/values-mcc311-mnc480-fr/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="611650570559011140">"Aucune connexion à Internet n\'est disponible pour le partage de connexion"</string>
-    <string name="no_upstream_notification_message" msgid="6508394877641864863">"Impossible de connecter les appareils"</string>
-    <string name="no_upstream_notification_disable_button" msgid="7609346639290990508">"Désactiver le partage de connexion"</string>
-    <string name="upstream_roaming_notification_title" msgid="6032901176124830787">"Le point d\'accès ou le partage de connexion est activé"</string>
-    <string name="upstream_roaming_notification_message" msgid="7599056263326217523">"En itinérance, des frais supplémentaires peuvent s\'appliquer"</string>
+    <string name="no_upstream_notification_title" msgid="5897815458155858594">"Aucune connexion à Internet disponible pour le partage de connexion"</string>
+    <string name="no_upstream_notification_message" msgid="9037716118606459874">"Impossible de connecter les appareils"</string>
+    <string name="no_upstream_notification_disable_button" msgid="5284024068281565456">"Désactiver le partage de connexion"</string>
+    <string name="upstream_roaming_notification_title" msgid="186331286017243006">"Le point d\'accès ou le partage de connexion est activé"</string>
+    <string name="upstream_roaming_notification_message" msgid="7692641323940316538">"En itinérance, des frais supplémentaires peuvent s\'appliquer"</string>
 </resources>
diff --git a/Tethering/res/values-mcc311-mnc480-gl/strings.xml b/Tethering/res/values-mcc311-mnc480-gl/strings.xml
index 0c4195a..446d706 100644
--- a/Tethering/res/values-mcc311-mnc480-gl/strings.xml
+++ b/Tethering/res/values-mcc311-mnc480-gl/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="611650570559011140">"A conexión compartida non ten Internet"</string>
-    <string name="no_upstream_notification_message" msgid="6508394877641864863">"Non se puideron conectar os dispositivos"</string>
-    <string name="no_upstream_notification_disable_button" msgid="7609346639290990508">"Desactivar conexión compartida"</string>
-    <string name="upstream_roaming_notification_title" msgid="6032901176124830787">"Está activada a zona wifi ou a conexión compartida"</string>
-    <string name="upstream_roaming_notification_message" msgid="7599056263326217523">"Pódense aplicar cargos adicionais en itinerancia"</string>
+    <string name="no_upstream_notification_title" msgid="5897815458155858594">"A conexión compartida non ten acceso a Internet"</string>
+    <string name="no_upstream_notification_message" msgid="9037716118606459874">"Non se puideron conectar os dispositivos"</string>
+    <string name="no_upstream_notification_disable_button" msgid="5284024068281565456">"Desactivar conexión compartida"</string>
+    <string name="upstream_roaming_notification_title" msgid="186331286017243006">"Está activada a zona wifi ou a conexión compartida"</string>
+    <string name="upstream_roaming_notification_message" msgid="7692641323940316538">"Pódense aplicar cargos adicionais en itinerancia"</string>
 </resources>
diff --git a/Tethering/res/values-mcc311-mnc480-gu/strings.xml b/Tethering/res/values-mcc311-mnc480-gu/strings.xml
index e9d33a7..577874d 100644
--- a/Tethering/res/values-mcc311-mnc480-gu/strings.xml
+++ b/Tethering/res/values-mcc311-mnc480-gu/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="611650570559011140">"ઇન્ટરનેટ શેર કરવાની સુવિધામાં ઇન્ટરનેટ નથી"</string>
-    <string name="no_upstream_notification_message" msgid="6508394877641864863">"ડિવાઇસ કનેક્ટ કરી શકાતા નથી"</string>
-    <string name="no_upstream_notification_disable_button" msgid="7609346639290990508">"ઇન્ટરનેટ શેર કરવાની સુવિધા બંધ કરો"</string>
-    <string name="upstream_roaming_notification_title" msgid="6032901176124830787">"હૉટસ્પૉટ અથવા ઇન્ટરનેટ શેર કરવાની સુવિધા ચાલુ છે"</string>
-    <string name="upstream_roaming_notification_message" msgid="7599056263326217523">"રોમિંગમાં વધારાના શુલ્ક લાગી શકે છે"</string>
+    <string name="no_upstream_notification_title" msgid="5897815458155858594">"ઇન્ટરનેટ શેર કરવાની સુવિધામાં ઇન્ટરનેટ નથી"</string>
+    <string name="no_upstream_notification_message" msgid="9037716118606459874">"ડિવાઇસ કનેક્ટ કરી શકાતા નથી"</string>
+    <string name="no_upstream_notification_disable_button" msgid="5284024068281565456">"ઇન્ટરનેટ શેર કરવાની સુવિધા બંધ કરો"</string>
+    <string name="upstream_roaming_notification_title" msgid="186331286017243006">"હૉટસ્પૉટ અથવા ઇન્ટરનેટ શેર કરવાની સુવિધા ચાલુ છે"</string>
+    <string name="upstream_roaming_notification_message" msgid="7692641323940316538">"રોમિંગ દરમિયાન વધારાના શુલ્ક લાગુ થઈ શકે છે"</string>
 </resources>
diff --git a/Tethering/res/values-mcc311-mnc480-hi/strings.xml b/Tethering/res/values-mcc311-mnc480-hi/strings.xml
index aa418ac..f2a4773 100644
--- a/Tethering/res/values-mcc311-mnc480-hi/strings.xml
+++ b/Tethering/res/values-mcc311-mnc480-hi/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="611650570559011140">"टेदरिंग से इंटरनेट नहीं चल रहा"</string>
-    <string name="no_upstream_notification_message" msgid="6508394877641864863">"डिवाइस कनेक्ट नहीं हो पा रहे"</string>
-    <string name="no_upstream_notification_disable_button" msgid="7609346639290990508">"टेदरिंग बंद करें"</string>
-    <string name="upstream_roaming_notification_title" msgid="6032901176124830787">"हॉटस्पॉट या टेदरिंग चालू है"</string>
-    <string name="upstream_roaming_notification_message" msgid="7599056263326217523">"रोमिंग के दौरान अतिरिक्त शुल्क लग सकता है"</string>
+    <string name="no_upstream_notification_title" msgid="5897815458155858594">"टेदरिंग से इंटरनेट नहीं चल रहा है"</string>
+    <string name="no_upstream_notification_message" msgid="9037716118606459874">"डिवाइस कनेक्ट नहीं हो पा रहे"</string>
+    <string name="no_upstream_notification_disable_button" msgid="5284024068281565456">"टेदरिंग बंद करें"</string>
+    <string name="upstream_roaming_notification_title" msgid="186331286017243006">"हॉटस्पॉट या टेदरिंग चालू है"</string>
+    <string name="upstream_roaming_notification_message" msgid="7692641323940316538">"रोमिंग के दौरान अतिरिक्त शुल्क काटा जा सकता है"</string>
 </resources>
diff --git a/Tethering/res/values-mcc311-mnc480-hr/strings.xml b/Tethering/res/values-mcc311-mnc480-hr/strings.xml
index 51c524a..a08f822 100644
--- a/Tethering/res/values-mcc311-mnc480-hr/strings.xml
+++ b/Tethering/res/values-mcc311-mnc480-hr/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="611650570559011140">"Modemsko povezivanje nema internet"</string>
-    <string name="no_upstream_notification_message" msgid="6508394877641864863">"Uređaji se ne mogu povezati"</string>
-    <string name="no_upstream_notification_disable_button" msgid="7609346639290990508">"Isključivanje modemskog povezivanja"</string>
-    <string name="upstream_roaming_notification_title" msgid="6032901176124830787">"Uključena je žarišna točka ili modemsko povezivanje"</string>
-    <string name="upstream_roaming_notification_message" msgid="7599056263326217523">"U roamingu su mogući dodatni troškovi"</string>
+    <string name="no_upstream_notification_title" msgid="5897815458155858594">"Modemsko povezivanje nema internet"</string>
+    <string name="no_upstream_notification_message" msgid="9037716118606459874">"Uređaji se ne mogu povezati"</string>
+    <string name="no_upstream_notification_disable_button" msgid="5284024068281565456">"Isključi modemsko povezivanje"</string>
+    <string name="upstream_roaming_notification_title" msgid="186331286017243006">"Uključena je žarišna točka ili modemsko povezivanje"</string>
+    <string name="upstream_roaming_notification_message" msgid="7692641323940316538">"U roamingu su mogući dodatni troškovi"</string>
 </resources>
diff --git a/Tethering/res/values-mcc311-mnc480-hu/strings.xml b/Tethering/res/values-mcc311-mnc480-hu/strings.xml
index 164e45e..61a399a 100644
--- a/Tethering/res/values-mcc311-mnc480-hu/strings.xml
+++ b/Tethering/res/values-mcc311-mnc480-hu/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="611650570559011140">"Nincs internetkapcsolat az internet megosztásához"</string>
-    <string name="no_upstream_notification_message" msgid="6508394877641864863">"Az eszközök nem tudnak csatlakozni"</string>
-    <string name="no_upstream_notification_disable_button" msgid="7609346639290990508">"Internetmegosztás kikapcsolása"</string>
-    <string name="upstream_roaming_notification_title" msgid="6032901176124830787">"A hotspot vagy az internetmegosztás be van kapcsolva"</string>
-    <string name="upstream_roaming_notification_message" msgid="7599056263326217523">"Roaming során további díjak léphetnek fel"</string>
+    <string name="no_upstream_notification_title" msgid="5897815458155858594">"Nincs internetkapcsolat az internet megosztásához"</string>
+    <string name="no_upstream_notification_message" msgid="9037716118606459874">"Az eszközök nem tudnak csatlakozni"</string>
+    <string name="no_upstream_notification_disable_button" msgid="5284024068281565456">"Internetmegosztás kikapcsolása"</string>
+    <string name="upstream_roaming_notification_title" msgid="186331286017243006">"A hotspot vagy az internetmegosztás be van kapcsolva"</string>
+    <string name="upstream_roaming_notification_message" msgid="7692641323940316538">"Roaming során további díjak léphetnek fel"</string>
 </resources>
diff --git a/Tethering/res/values-mcc311-mnc480-hy/strings.xml b/Tethering/res/values-mcc311-mnc480-hy/strings.xml
index e76c0a4..f4d63c9 100644
--- a/Tethering/res/values-mcc311-mnc480-hy/strings.xml
+++ b/Tethering/res/values-mcc311-mnc480-hy/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="611650570559011140">"Մոդեմի ռեժիմի կապը բացակայում է"</string>
-    <string name="no_upstream_notification_message" msgid="6508394877641864863">"Չհաջողվեց միացնել սարքը"</string>
-    <string name="no_upstream_notification_disable_button" msgid="7609346639290990508">"Անջատել մոդեմի ռեժիմը"</string>
-    <string name="upstream_roaming_notification_title" msgid="6032901176124830787">"Թեժ կետը կամ մոդեմի ռեժիմը միացված է"</string>
-    <string name="upstream_roaming_notification_message" msgid="7599056263326217523">"Ռոումինգում կարող են լրացուցիչ վճարներ գանձվել"</string>
+    <string name="no_upstream_notification_title" msgid="5897815458155858594">"Մոդեմի ռեժիմի ինտերնետ կապը բացակայում է"</string>
+    <string name="no_upstream_notification_message" msgid="9037716118606459874">"Չհաջողվեց միացնել սարքերը"</string>
+    <string name="no_upstream_notification_disable_button" msgid="5284024068281565456">"Անջատել մոդեմի ռեժիմը"</string>
+    <string name="upstream_roaming_notification_title" msgid="186331286017243006">"Թեժ կետը կամ մոդեմի ռեժիմը միացված է"</string>
+    <string name="upstream_roaming_notification_message" msgid="7692641323940316538">"Ռոումինգում կարող են լրացուցիչ վճարներ գանձվել"</string>
 </resources>
diff --git a/Tethering/res/values-mcc311-mnc480-in/strings.xml b/Tethering/res/values-mcc311-mnc480-in/strings.xml
index 2b817f8..98c6d71 100644
--- a/Tethering/res/values-mcc311-mnc480-in/strings.xml
+++ b/Tethering/res/values-mcc311-mnc480-in/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="611650570559011140">"Tidak ada koneksi internet di tethering"</string>
-    <string name="no_upstream_notification_message" msgid="6508394877641864863">"Perangkat tidak dapat terhubung"</string>
-    <string name="no_upstream_notification_disable_button" msgid="7609346639290990508">"Nonaktifkan tethering"</string>
-    <string name="upstream_roaming_notification_title" msgid="6032901176124830787">"Hotspot atau tethering aktif"</string>
-    <string name="upstream_roaming_notification_message" msgid="7599056263326217523">"Biaya tambahan mungkin berlaku saat roaming"</string>
+    <string name="no_upstream_notification_title" msgid="5897815458155858594">"Tidak ada koneksi internet di tethering"</string>
+    <string name="no_upstream_notification_message" msgid="9037716118606459874">"Perangkat tidak dapat terhubung"</string>
+    <string name="no_upstream_notification_disable_button" msgid="5284024068281565456">"Nonaktifkan tethering"</string>
+    <string name="upstream_roaming_notification_title" msgid="186331286017243006">"Hotspot atau tethering aktif"</string>
+    <string name="upstream_roaming_notification_message" msgid="7692641323940316538">"Biaya tambahan mungkin berlaku saat roaming"</string>
 </resources>
diff --git a/Tethering/res/values-mcc311-mnc480-is/strings.xml b/Tethering/res/values-mcc311-mnc480-is/strings.xml
index a338d9c..ade1b01 100644
--- a/Tethering/res/values-mcc311-mnc480-is/strings.xml
+++ b/Tethering/res/values-mcc311-mnc480-is/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="611650570559011140">"Tjóðrun er ekki með internettengingu"</string>
-    <string name="no_upstream_notification_message" msgid="6508394877641864863">"Tæki geta ekki tengst"</string>
-    <string name="no_upstream_notification_disable_button" msgid="7609346639290990508">"Slökkva á tjóðrun"</string>
-    <string name="upstream_roaming_notification_title" msgid="6032901176124830787">"Kveikt er á heitum reit eða tjóðrun"</string>
-    <string name="upstream_roaming_notification_message" msgid="7599056263326217523">"Viðbótargjöld kunna að eiga við í reiki"</string>
+    <string name="no_upstream_notification_title" msgid="5897815458155858594">"Tjóðrun er ekki með internettengingu"</string>
+    <string name="no_upstream_notification_message" msgid="9037716118606459874">"Tæki geta ekki tengst"</string>
+    <string name="no_upstream_notification_disable_button" msgid="5284024068281565456">"Slökkva á tjóðrun"</string>
+    <string name="upstream_roaming_notification_title" msgid="186331286017243006">"Kveikt er á heitum reit eða tjóðrun"</string>
+    <string name="upstream_roaming_notification_message" msgid="7692641323940316538">"Viðbótargjöld kunna að eiga við í reiki"</string>
 </resources>
diff --git a/Tethering/res/values-mcc311-mnc480-it/strings.xml b/Tethering/res/values-mcc311-mnc480-it/strings.xml
index 77769c2..07e1526 100644
--- a/Tethering/res/values-mcc311-mnc480-it/strings.xml
+++ b/Tethering/res/values-mcc311-mnc480-it/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="611650570559011140">"Nessuna connessione a Internet per il tethering"</string>
-    <string name="no_upstream_notification_message" msgid="6508394877641864863">"Impossibile connettere i dispositivi"</string>
-    <string name="no_upstream_notification_disable_button" msgid="7609346639290990508">"Disattiva il tethering"</string>
-    <string name="upstream_roaming_notification_title" msgid="6032901176124830787">"Hotspot o tethering attivi"</string>
-    <string name="upstream_roaming_notification_message" msgid="7599056263326217523">"Potrebbero essere applicati costi aggiuntivi durante il roaming"</string>
+    <string name="no_upstream_notification_title" msgid="5897815458155858594">"Nessuna connessione a internet per il tethering"</string>
+    <string name="no_upstream_notification_message" msgid="9037716118606459874">"Impossibile connettere i dispositivi"</string>
+    <string name="no_upstream_notification_disable_button" msgid="5284024068281565456">"Disattiva il tethering"</string>
+    <string name="upstream_roaming_notification_title" msgid="186331286017243006">"Hotspot o tethering attivo"</string>
+    <string name="upstream_roaming_notification_message" msgid="7692641323940316538">"Potrebbero essere applicati costi aggiuntivi durante il roaming"</string>
 </resources>
diff --git a/Tethering/res/values-mcc311-mnc480-iw/strings.xml b/Tethering/res/values-mcc311-mnc480-iw/strings.xml
index 5267b51..ebebae8 100644
--- a/Tethering/res/values-mcc311-mnc480-iw/strings.xml
+++ b/Tethering/res/values-mcc311-mnc480-iw/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="611650570559011140">"אי אפשר להפעיל את תכונת שיתוף האינטרנט בין מכשירים כי אין חיבור לאינטרנט"</string>
-    <string name="no_upstream_notification_message" msgid="6508394877641864863">"למכשירים אין אפשרות להתחבר"</string>
-    <string name="no_upstream_notification_disable_button" msgid="7609346639290990508">"השבתה של שיתוף האינטרנט בין מכשירים"</string>
-    <string name="upstream_roaming_notification_title" msgid="6032901176124830787">"תכונת הנקודה לשיתוף אינטרנט או תכונת שיתוף האינטרנט בין מכשירים פועלת"</string>
-    <string name="upstream_roaming_notification_message" msgid="7599056263326217523">"ייתכנו חיובים נוספים בעת נדידה"</string>
+    <string name="no_upstream_notification_title" msgid="5897815458155858594">"אי אפשר להפעיל את תכונת שיתוף האינטרנט בין מכשירים כי אין חיבור לאינטרנט"</string>
+    <string name="no_upstream_notification_message" msgid="9037716118606459874">"למכשירים אין אפשרות להתחבר"</string>
+    <string name="no_upstream_notification_disable_button" msgid="5284024068281565456">"השבתה של שיתוף האינטרנט בין מכשירים"</string>
+    <string name="upstream_roaming_notification_title" msgid="186331286017243006">"תכונת הנקודה לשיתוף אינטרנט או תכונת שיתוף האינטרנט בין מכשירים פועלת"</string>
+    <string name="upstream_roaming_notification_message" msgid="7692641323940316538">"ייתכנו חיובים נוספים במהלך נדידה"</string>
 </resources>
diff --git a/Tethering/res/values-mcc311-mnc480-ja/strings.xml b/Tethering/res/values-mcc311-mnc480-ja/strings.xml
index 66a9a6d..334d362 100644
--- a/Tethering/res/values-mcc311-mnc480-ja/strings.xml
+++ b/Tethering/res/values-mcc311-mnc480-ja/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="611650570559011140">"テザリングがインターネットに接続されていません"</string>
-    <string name="no_upstream_notification_message" msgid="6508394877641864863">"デバイスを接続できません"</string>
-    <string name="no_upstream_notification_disable_button" msgid="7609346639290990508">"テザリングを OFF にする"</string>
-    <string name="upstream_roaming_notification_title" msgid="6032901176124830787">"アクセス ポイントまたはテザリングが ON です"</string>
-    <string name="upstream_roaming_notification_message" msgid="7599056263326217523">"ローミング時に追加料金が発生することがあります"</string>
+    <string name="no_upstream_notification_title" msgid="5897815458155858594">"テザリングがインターネットに接続されていません"</string>
+    <string name="no_upstream_notification_message" msgid="9037716118606459874">"デバイスを接続できません"</string>
+    <string name="no_upstream_notification_disable_button" msgid="5284024068281565456">"テザリングを OFF にする"</string>
+    <string name="upstream_roaming_notification_title" msgid="186331286017243006">"アクセス ポイントまたはテザリングが ON です"</string>
+    <string name="upstream_roaming_notification_message" msgid="7692641323940316538">"ローミング時に追加料金が発生することがあります"</string>
 </resources>
diff --git a/Tethering/res/values-mcc311-mnc480-ka/strings.xml b/Tethering/res/values-mcc311-mnc480-ka/strings.xml
index d8ad880..d369d20 100644
--- a/Tethering/res/values-mcc311-mnc480-ka/strings.xml
+++ b/Tethering/res/values-mcc311-mnc480-ka/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="611650570559011140">"ტეტერინგს არ აქვს ინტერნეტზე წვდომა"</string>
-    <string name="no_upstream_notification_message" msgid="6508394877641864863">"მოწყობილობები ვერ ახერხებენ დაკავშირებას"</string>
-    <string name="no_upstream_notification_disable_button" msgid="7609346639290990508">"ტეტერინგის გამორთვა"</string>
-    <string name="upstream_roaming_notification_title" msgid="6032901176124830787">"ჩართულია უსადენო ქსელი ან ტეტერინგი"</string>
-    <string name="upstream_roaming_notification_message" msgid="7599056263326217523">"როუმინგის გამოყენებისას შეიძლება ჩამოგეჭრათ დამატებითი საფასური"</string>
+    <string name="no_upstream_notification_title" msgid="5897815458155858594">"ტეტერინგს არ აქვს ინტერნეტზე წვდომა"</string>
+    <string name="no_upstream_notification_message" msgid="9037716118606459874">"მოწყობილობები ვერ ახერხებენ დაკავშირებას"</string>
+    <string name="no_upstream_notification_disable_button" msgid="5284024068281565456">"ტეტერინგის გამორთვა"</string>
+    <string name="upstream_roaming_notification_title" msgid="186331286017243006">"ჩართულია უსადენო ქსელი ან ტეტერინგი"</string>
+    <string name="upstream_roaming_notification_message" msgid="7692641323940316538">"როუმინგის გამოყენებისას შეიძლება ჩამოგეჭრათ დამატებითი საფასური"</string>
 </resources>
diff --git a/Tethering/res/values-mcc311-mnc480-kk/strings.xml b/Tethering/res/values-mcc311-mnc480-kk/strings.xml
index 1ddd6b4..c16c106 100644
--- a/Tethering/res/values-mcc311-mnc480-kk/strings.xml
+++ b/Tethering/res/values-mcc311-mnc480-kk/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="611650570559011140">"Тетеринг режимі интернет байланысынсыз пайдаланылуда"</string>
-    <string name="no_upstream_notification_message" msgid="6508394877641864863">"Құрылғыларды байланыстыру мүмкін емес"</string>
-    <string name="no_upstream_notification_disable_button" msgid="7609346639290990508">"Тетерингіні өшіру"</string>
-    <string name="upstream_roaming_notification_title" msgid="6032901176124830787">"Хотспот немесе тетеринг қосулы"</string>
-    <string name="upstream_roaming_notification_message" msgid="7599056263326217523">"Роуминг кезінде қосымша ақы алынуы мүмкін."</string>
+    <string name="no_upstream_notification_title" msgid="5897815458155858594">"Тетеринг кезінде интернет байланысы жоқ"</string>
+    <string name="no_upstream_notification_message" msgid="9037716118606459874">"Құрылғыларды байланыстыру мүмкін емес"</string>
+    <string name="no_upstream_notification_disable_button" msgid="5284024068281565456">"Тетерингіні өшіру"</string>
+    <string name="upstream_roaming_notification_title" msgid="186331286017243006">"Хотспот немесе тетеринг қосулы"</string>
+    <string name="upstream_roaming_notification_message" msgid="7692641323940316538">"Роуминг кезінде қосымша ақы алынуы мүмкін."</string>
 </resources>
diff --git a/Tethering/res/values-mcc311-mnc480-km/strings.xml b/Tethering/res/values-mcc311-mnc480-km/strings.xml
index cf5a137..8084b87 100644
--- a/Tethering/res/values-mcc311-mnc480-km/strings.xml
+++ b/Tethering/res/values-mcc311-mnc480-km/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="611650570559011140">"ការភ្ជាប់​មិនមានអ៊ីនធឺណិត​ទេ"</string>
-    <string name="no_upstream_notification_message" msgid="6508394877641864863">"មិនអាច​ភ្ជាប់ឧបករណ៍​បានទេ"</string>
-    <string name="no_upstream_notification_disable_button" msgid="7609346639290990508">"បិទការភ្ជាប់"</string>
-    <string name="upstream_roaming_notification_title" msgid="6032901176124830787">"ហតស្ប៉ត ឬការភ្ជាប់​ត្រូវបានបើក"</string>
-    <string name="upstream_roaming_notification_message" msgid="7599056263326217523">"អាចមាន​ការគិតថ្លៃ​បន្ថែម នៅពេល​រ៉ូមីង"</string>
+    <string name="no_upstream_notification_title" msgid="5897815458155858594">"ការភ្ជាប់​មិនមានអ៊ីនធឺណិត​ទេ"</string>
+    <string name="no_upstream_notification_message" msgid="9037716118606459874">"មិនអាច​ភ្ជាប់ឧបករណ៍​បានទេ"</string>
+    <string name="no_upstream_notification_disable_button" msgid="5284024068281565456">"បិទការភ្ជាប់"</string>
+    <string name="upstream_roaming_notification_title" msgid="186331286017243006">"ហតស្ប៉ត ឬការភ្ជាប់​ត្រូវបានបើក"</string>
+    <string name="upstream_roaming_notification_message" msgid="7692641323940316538">"អាចមាន​ការគិតថ្លៃ​បន្ថែម នៅពេល​រ៉ូមីង"</string>
 </resources>
diff --git a/Tethering/res/values-mcc311-mnc480-kn/strings.xml b/Tethering/res/values-mcc311-mnc480-kn/strings.xml
index 68ae68b..528cdbf 100644
--- a/Tethering/res/values-mcc311-mnc480-kn/strings.xml
+++ b/Tethering/res/values-mcc311-mnc480-kn/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="611650570559011140">"ಟೆಥರಿಂಗ್‌ ಯಾವುದೇ ಇಂಟರ್ನೆಟ್ ಕನೆಕ್ಷನ್ ಹೊಂದಿಲ್ಲ"</string>
-    <string name="no_upstream_notification_message" msgid="6508394877641864863">"ಸಾಧನಗಳನ್ನು ಕನೆಕ್ಟ್ ಮಾಡಲು ಸಾಧ್ಯವಿಲ್ಲ"</string>
-    <string name="no_upstream_notification_disable_button" msgid="7609346639290990508">"ಟೆಥರಿಂಗ್‌ ಆಫ್ ಮಾಡಿ"</string>
-    <string name="upstream_roaming_notification_title" msgid="6032901176124830787">"ಹಾಟ್‌ಸ್ಪಾಟ್ ಅಥವಾ ಟೆಥರಿಂಗ್‌ ಆನ್ ಆಗಿದೆ"</string>
-    <string name="upstream_roaming_notification_message" msgid="7599056263326217523">"ರೋಮಿಂಗ್‌ನಲ್ಲಿರುವಾಗ ಹೆಚ್ಚುವರಿ ಶುಲ್ಕಗಳು ಅನ್ವಯವಾಗಬಹುದು"</string>
+    <string name="no_upstream_notification_title" msgid="5897815458155858594">"ಟೆಥರಿಂಗ್‌ ಯಾವುದೇ ಇಂಟರ್ನೆಟ್ ಕನೆಕ್ಷನ್ ಹೊಂದಿಲ್ಲ"</string>
+    <string name="no_upstream_notification_message" msgid="9037716118606459874">"ಸಾಧನಗಳನ್ನು ಕನೆಕ್ಟ್ ಮಾಡಲು ಸಾಧ್ಯವಿಲ್ಲ"</string>
+    <string name="no_upstream_notification_disable_button" msgid="5284024068281565456">"ಟೆಥರಿಂಗ್‌ ಆಫ್ ಮಾಡಿ"</string>
+    <string name="upstream_roaming_notification_title" msgid="186331286017243006">"ಹಾಟ್‌ಸ್ಪಾಟ್ ಅಥವಾ ಟೆಥರಿಂಗ್‌ ಆನ್ ಆಗಿದೆ"</string>
+    <string name="upstream_roaming_notification_message" msgid="7692641323940316538">"ರೋಮಿಂಗ್‌ನಲ್ಲಿರುವಾಗ ಹೆಚ್ಚುವರಿ ಶುಲ್ಕಗಳು ಅನ್ವಯವಾಗಬಹುದು"</string>
 </resources>
diff --git a/Tethering/res/values-mcc311-mnc480-ko/strings.xml b/Tethering/res/values-mcc311-mnc480-ko/strings.xml
index 17185ba..f195c82 100644
--- a/Tethering/res/values-mcc311-mnc480-ko/strings.xml
+++ b/Tethering/res/values-mcc311-mnc480-ko/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="611650570559011140">"테더링으로 인터넷을 사용할 수 없음"</string>
-    <string name="no_upstream_notification_message" msgid="6508394877641864863">"기기에서 연결할 수 없음"</string>
-    <string name="no_upstream_notification_disable_button" msgid="7609346639290990508">"테더링 사용 중지"</string>
-    <string name="upstream_roaming_notification_title" msgid="6032901176124830787">"핫스팟 또는 테더링 켜짐"</string>
-    <string name="upstream_roaming_notification_message" msgid="7599056263326217523">"로밍 중에는 추가 요금이 발생할 수 있습니다."</string>
+    <string name="no_upstream_notification_title" msgid="5897815458155858594">"테더링으로 인터넷을 사용할 수 없음"</string>
+    <string name="no_upstream_notification_message" msgid="9037716118606459874">"기기에서 연결할 수 없음"</string>
+    <string name="no_upstream_notification_disable_button" msgid="5284024068281565456">"테더링 사용 중지"</string>
+    <string name="upstream_roaming_notification_title" msgid="186331286017243006">"핫스팟 또는 테더링이 켜짐"</string>
+    <string name="upstream_roaming_notification_message" msgid="7692641323940316538">"로밍 중에는 추가 요금이 부과될 수 있습니다."</string>
 </resources>
diff --git a/Tethering/res/values-mcc311-mnc480-ky/strings.xml b/Tethering/res/values-mcc311-mnc480-ky/strings.xml
index 6a9fb98..f8ca531 100644
--- a/Tethering/res/values-mcc311-mnc480-ky/strings.xml
+++ b/Tethering/res/values-mcc311-mnc480-ky/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="611650570559011140">"Модем режими Интернети жок колдонулууда"</string>
-    <string name="no_upstream_notification_message" msgid="6508394877641864863">"Түзмөктөр туташпай жатат"</string>
-    <string name="no_upstream_notification_disable_button" msgid="7609346639290990508">"Модем режимин өчүрүү"</string>
-    <string name="upstream_roaming_notification_title" msgid="6032901176124830787">"Байланыш түйүнү же модем режими күйүк"</string>
-    <string name="upstream_roaming_notification_message" msgid="7599056263326217523">"Роумингде кошумча акы алынышы мүмкүн"</string>
+    <string name="no_upstream_notification_title" msgid="5897815458155858594">"Модем режими Интернети жок колдонулууда"</string>
+    <string name="no_upstream_notification_message" msgid="9037716118606459874">"Түзмөктөр туташпай жатат"</string>
+    <string name="no_upstream_notification_disable_button" msgid="5284024068281565456">"Модем режимин өчүрүү"</string>
+    <string name="upstream_roaming_notification_title" msgid="186331286017243006">"Байланыш түйүнү же модем режими күйүк"</string>
+    <string name="upstream_roaming_notification_message" msgid="7692641323940316538">"Роумингде кошумча акы алынышы мүмкүн"</string>
 </resources>
diff --git a/Tethering/res/values-mcc311-mnc480-lo/strings.xml b/Tethering/res/values-mcc311-mnc480-lo/strings.xml
index bcc4b57..6258bc0 100644
--- a/Tethering/res/values-mcc311-mnc480-lo/strings.xml
+++ b/Tethering/res/values-mcc311-mnc480-lo/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="611650570559011140">"ການປ່ອຍສັນຍານບໍ່ມີອິນເຕີເນັດ"</string>
-    <string name="no_upstream_notification_message" msgid="6508394877641864863">"ອຸປະກອນບໍ່ສາມາດເຊື່ອມຕໍ່ໄດ້"</string>
-    <string name="no_upstream_notification_disable_button" msgid="7609346639290990508">"ປິດການປ່ອຍສັນຍານ"</string>
-    <string name="upstream_roaming_notification_title" msgid="6032901176124830787">"ເປີດໃຊ້ຮັອດສະປອດ ຫຼື ການປ່ອຍສັນຍານຢູ່"</string>
-    <string name="upstream_roaming_notification_message" msgid="7599056263326217523">"ອາດມີຄ່າໃຊ້ຈ່າຍເພີ່ມເຕີມໃນລະຫວ່າງການໂຣມມິງ"</string>
+    <string name="no_upstream_notification_title" msgid="5897815458155858594">"ການປ່ອຍສັນຍານບໍ່ມີອິນເຕີເນັດ"</string>
+    <string name="no_upstream_notification_message" msgid="9037716118606459874">"ອຸປະກອນບໍ່ສາມາດເຊື່ອມຕໍ່ໄດ້"</string>
+    <string name="no_upstream_notification_disable_button" msgid="5284024068281565456">"ປິດການປ່ອຍສັນຍານ"</string>
+    <string name="upstream_roaming_notification_title" msgid="186331286017243006">"ຮັອດສະປອດ ຫຼື ການປ່ອຍສັນຍານເປີດຢູ່"</string>
+    <string name="upstream_roaming_notification_message" msgid="7692641323940316538">"ອາດມີຄ່າບໍລິການເພີ່ມເຕີມໃນລະຫວ່າງການໂຣມມິງ"</string>
 </resources>
diff --git a/Tethering/res/values-mcc311-mnc480-lt/strings.xml b/Tethering/res/values-mcc311-mnc480-lt/strings.xml
index 011c2c1..267c7f6 100644
--- a/Tethering/res/values-mcc311-mnc480-lt/strings.xml
+++ b/Tethering/res/values-mcc311-mnc480-lt/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="611650570559011140">"Nėra įrenginio kaip modemo naudojimo interneto ryšio"</string>
-    <string name="no_upstream_notification_message" msgid="6508394877641864863">"Nepavyko susieti įrenginių"</string>
-    <string name="no_upstream_notification_disable_button" msgid="7609346639290990508">"Išjungti įrenginio kaip modemo naudojimą"</string>
-    <string name="upstream_roaming_notification_title" msgid="6032901176124830787">"Įjungtas viešosios interneto prieigos taškas arba įrenginio kaip modemo naudojimas"</string>
-    <string name="upstream_roaming_notification_message" msgid="7599056263326217523">"Veikiant tarptinkliniam ryšiui gali būti taikomi papildomi mokesčiai"</string>
+    <string name="no_upstream_notification_title" msgid="5897815458155858594">"Nėra įrenginio kaip modemo naudojimo interneto ryšio"</string>
+    <string name="no_upstream_notification_message" msgid="9037716118606459874">"Nepavyko susieti įrenginių"</string>
+    <string name="no_upstream_notification_disable_button" msgid="5284024068281565456">"Išjungti įrenginio kaip modemo naudojimą"</string>
+    <string name="upstream_roaming_notification_title" msgid="186331286017243006">"Įjungtas viešosios interneto prieigos taškas arba įrenginio kaip modemo naudojimas"</string>
+    <string name="upstream_roaming_notification_message" msgid="7692641323940316538">"Veikiant tarptinkliniam ryšiui gali būti taikomi papildomi mokesčiai"</string>
 </resources>
diff --git a/Tethering/res/values-mcc311-mnc480-lv/strings.xml b/Tethering/res/values-mcc311-mnc480-lv/strings.xml
index 5cb2f3b..ca54195 100644
--- a/Tethering/res/values-mcc311-mnc480-lv/strings.xml
+++ b/Tethering/res/values-mcc311-mnc480-lv/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="611650570559011140">"Piesaistei nav interneta savienojuma"</string>
-    <string name="no_upstream_notification_message" msgid="6508394877641864863">"Nevar savienot ierīces"</string>
-    <string name="no_upstream_notification_disable_button" msgid="7609346639290990508">"Izslēgt piesaisti"</string>
-    <string name="upstream_roaming_notification_title" msgid="6032901176124830787">"Ir ieslēgts tīklājs vai piesaiste"</string>
-    <string name="upstream_roaming_notification_message" msgid="7599056263326217523">"Viesabonēšanas laikā var tikt piemērota papildu samaksa"</string>
+    <string name="no_upstream_notification_title" msgid="5897815458155858594">"Piesaistei nav interneta savienojuma"</string>
+    <string name="no_upstream_notification_message" msgid="9037716118606459874">"Nevar savienot ierīces"</string>
+    <string name="no_upstream_notification_disable_button" msgid="5284024068281565456">"Izslēgt piesaisti"</string>
+    <string name="upstream_roaming_notification_title" msgid="186331286017243006">"Ir ieslēgts tīklājs vai piesaiste"</string>
+    <string name="upstream_roaming_notification_message" msgid="7692641323940316538">"Viesabonēšanas laikā var tikt piemērota papildu maksa."</string>
 </resources>
diff --git a/Tethering/res/values-mcc311-mnc480-mk/strings.xml b/Tethering/res/values-mcc311-mnc480-mk/strings.xml
index 4cbfd88..8c2b8aa 100644
--- a/Tethering/res/values-mcc311-mnc480-mk/strings.xml
+++ b/Tethering/res/values-mcc311-mnc480-mk/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="611650570559011140">"Нема интернет преку мобилен"</string>
-    <string name="no_upstream_notification_message" msgid="6508394877641864863">"Уредите не може да се поврзат"</string>
-    <string name="no_upstream_notification_disable_button" msgid="7609346639290990508">"Исклучи интернет преку мобилен"</string>
-    <string name="upstream_roaming_notification_title" msgid="6032901176124830787">"Точката на пристап или интернетот преку мобилен е вклучен"</string>
-    <string name="upstream_roaming_notification_message" msgid="7599056263326217523">"При роаминг може да се наплатат дополнителни трошоци"</string>
+    <string name="no_upstream_notification_title" msgid="5897815458155858594">"Нема интернет преку мобилен"</string>
+    <string name="no_upstream_notification_message" msgid="9037716118606459874">"Уредите не може да се поврзат"</string>
+    <string name="no_upstream_notification_disable_button" msgid="5284024068281565456">"Исклучи интернет преку мобилен"</string>
+    <string name="upstream_roaming_notification_title" msgid="186331286017243006">"Вклучено: точка на пристап или интернет преку мобилен"</string>
+    <string name="upstream_roaming_notification_message" msgid="7692641323940316538">"При роаминг може да се наплатат дополнителни трошоци"</string>
 </resources>
diff --git a/Tethering/res/values-mcc311-mnc480-ml/strings.xml b/Tethering/res/values-mcc311-mnc480-ml/strings.xml
index 9cf4eaf..9a722c5 100644
--- a/Tethering/res/values-mcc311-mnc480-ml/strings.xml
+++ b/Tethering/res/values-mcc311-mnc480-ml/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="611650570559011140">"ടെതറിംഗിന് ഇന്റർനെറ്റ് ഇല്ല"</string>
-    <string name="no_upstream_notification_message" msgid="6508394877641864863">"ഉപകരണങ്ങൾ കണക്റ്റ് ചെയ്യാനാവില്ല"</string>
-    <string name="no_upstream_notification_disable_button" msgid="7609346639290990508">"ടെതറിംഗ് ഓഫാക്കുക"</string>
-    <string name="upstream_roaming_notification_title" msgid="6032901176124830787">"ഹോട്ട്‌സ്‌പോട്ട് അല്ലെങ്കിൽ ടെതറിംഗ് ഓണാണ്"</string>
-    <string name="upstream_roaming_notification_message" msgid="7599056263326217523">"റോമിംഗ് ചെയ്യുമ്പോൾ അധിക നിരക്കുകൾ ബാധകമായേക്കാം"</string>
+    <string name="no_upstream_notification_title" msgid="5897815458155858594">"ടെതറിംഗിന് ഇന്റർനെറ്റ് ഇല്ല"</string>
+    <string name="no_upstream_notification_message" msgid="9037716118606459874">"ഉപകരണങ്ങൾ കണക്റ്റ് ചെയ്യാനാവില്ല"</string>
+    <string name="no_upstream_notification_disable_button" msgid="5284024068281565456">"ടെതറിംഗ് ഓഫാക്കുക"</string>
+    <string name="upstream_roaming_notification_title" msgid="186331286017243006">"ഹോട്ട്‌സ്‌പോട്ട് അല്ലെങ്കിൽ ടെതറിംഗ് ഓണാണ്"</string>
+    <string name="upstream_roaming_notification_message" msgid="7692641323940316538">"റോമിംഗ് ചെയ്യുമ്പോൾ അധിക നിരക്കുകൾ ബാധകമായേക്കാം"</string>
 </resources>
diff --git a/Tethering/res/values-mcc311-mnc480-mn/strings.xml b/Tethering/res/values-mcc311-mnc480-mn/strings.xml
index 47c82c1..f6fcf01 100644
--- a/Tethering/res/values-mcc311-mnc480-mn/strings.xml
+++ b/Tethering/res/values-mcc311-mnc480-mn/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="611650570559011140">"Модемд интернэт алга байна"</string>
-    <string name="no_upstream_notification_message" msgid="6508394877641864863">"Төхөөрөмжүүд холбогдох боломжгүй байна"</string>
-    <string name="no_upstream_notification_disable_button" msgid="7609346639290990508">"Модем болгохыг унтраах"</string>
-    <string name="upstream_roaming_notification_title" msgid="6032901176124830787">"Сүлжээний цэг эсвэл модем болгох асаалттай байна"</string>
-    <string name="upstream_roaming_notification_message" msgid="7599056263326217523">"Роумингийн үеэр нэмэлт төлбөр нэхэмжилж болзошгүй"</string>
+    <string name="no_upstream_notification_title" msgid="5897815458155858594">"Модем болгоход ямар ч интернэт байхгүй байна"</string>
+    <string name="no_upstream_notification_message" msgid="9037716118606459874">"Төхөөрөмжүүд холбогдох боломжгүй байна"</string>
+    <string name="no_upstream_notification_disable_button" msgid="5284024068281565456">"Модем болгохыг унтраах"</string>
+    <string name="upstream_roaming_notification_title" msgid="186331286017243006">"Сүлжээний цэг эсвэл модем болгох асаалттай байна"</string>
+    <string name="upstream_roaming_notification_message" msgid="7692641323940316538">"Роумингийн үеэр нэмэлт төлбөр тооцогдож магадгүй"</string>
 </resources>
diff --git a/Tethering/res/values-mcc311-mnc480-mr/strings.xml b/Tethering/res/values-mcc311-mnc480-mr/strings.xml
index ad9e809..2563e15 100644
--- a/Tethering/res/values-mcc311-mnc480-mr/strings.xml
+++ b/Tethering/res/values-mcc311-mnc480-mr/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="611650570559011140">"टेदरिंगला इंटरनेट नाही"</string>
-    <string name="no_upstream_notification_message" msgid="6508394877641864863">"डिव्हाइस कनेक्ट होऊ शकत नाहीत"</string>
-    <string name="no_upstream_notification_disable_button" msgid="7609346639290990508">"टेदरिंग बंद करा"</string>
-    <string name="upstream_roaming_notification_title" msgid="6032901176124830787">"हॉटस्पॉट किंवा टेदरिंग सुरू आहे"</string>
-    <string name="upstream_roaming_notification_message" msgid="7599056263326217523">"रोमिंगदरम्यान अतिरिक्त शुल्क लागू होऊ शकतात"</string>
+    <string name="no_upstream_notification_title" msgid="5897815458155858594">"टेदरिंगसाठी इंटरनेट नाही"</string>
+    <string name="no_upstream_notification_message" msgid="9037716118606459874">"डिव्हाइस कनेक्ट होऊ शकत नाहीत"</string>
+    <string name="no_upstream_notification_disable_button" msgid="5284024068281565456">"टेदरिंग बंद करा"</string>
+    <string name="upstream_roaming_notification_title" msgid="186331286017243006">"हॉटस्पॉट किंवा टेदरिंग सुरू आहे"</string>
+    <string name="upstream_roaming_notification_message" msgid="7692641323940316538">"रोमिंगदरम्यान अतिरिक्त शुल्के लागू होऊ शकतात"</string>
 </resources>
diff --git a/Tethering/res/values-mcc311-mnc480-ms/strings.xml b/Tethering/res/values-mcc311-mnc480-ms/strings.xml
index e708cb8..b2ccbbb 100644
--- a/Tethering/res/values-mcc311-mnc480-ms/strings.xml
+++ b/Tethering/res/values-mcc311-mnc480-ms/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="611650570559011140">"Penambatan tiada Internet"</string>
-    <string name="no_upstream_notification_message" msgid="6508394877641864863">"Peranti tidak dapat disambungkan"</string>
-    <string name="no_upstream_notification_disable_button" msgid="7609346639290990508">"Matikan penambatan"</string>
-    <string name="upstream_roaming_notification_title" msgid="6032901176124830787">"Tempat liputan atau penambatan dihidupkan"</string>
-    <string name="upstream_roaming_notification_message" msgid="7599056263326217523">"Caj tambahan mungkin digunakan semasa perayauan"</string>
+    <string name="no_upstream_notification_title" msgid="5897815458155858594">"Penambatan tiada Internet"</string>
+    <string name="no_upstream_notification_message" msgid="9037716118606459874">"Peranti tidak dapat disambungkan"</string>
+    <string name="no_upstream_notification_disable_button" msgid="5284024068281565456">"Matikan penambatan"</string>
+    <string name="upstream_roaming_notification_title" msgid="186331286017243006">"Tempat liputan atau penambatan dihidupkan"</string>
+    <string name="upstream_roaming_notification_message" msgid="7692641323940316538">"Caj tambahan boleh dikenakan semasa perayauan"</string>
 </resources>
diff --git a/Tethering/res/values-mcc311-mnc480-my/strings.xml b/Tethering/res/values-mcc311-mnc480-my/strings.xml
index ba54622..2281b7b 100644
--- a/Tethering/res/values-mcc311-mnc480-my/strings.xml
+++ b/Tethering/res/values-mcc311-mnc480-my/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="611650570559011140">"မိုဘိုင်းဖုန်းသုံး ချိတ်ဆက်မျှဝေခြင်းတွင် အင်တာနက် မရှိပါ"</string>
-    <string name="no_upstream_notification_message" msgid="6508394877641864863">"စက်များ ချိတ်ဆက်၍ မရပါ"</string>
-    <string name="no_upstream_notification_disable_button" msgid="7609346639290990508">"မိုဘိုင်းဖုန်းသုံး ချိတ်ဆက်မျှဝေခြင်း ပိတ်ရန်"</string>
-    <string name="upstream_roaming_notification_title" msgid="6032901176124830787">"ဟော့စပေါ့ (သို့) မိုဘိုင်းဖုန်းသုံး ချိတ်ဆက်မျှဝေခြင်း ဖွင့်ထားသည်"</string>
-    <string name="upstream_roaming_notification_message" msgid="7599056263326217523">"ပြင်ပကွန်ရက်နှင့် ချိတ်ဆက်သည့်အခါ နောက်ထပ်ကျသင့်မှုများ ရှိနိုင်သည်"</string>
+    <string name="no_upstream_notification_title" msgid="5897815458155858594">"မိုဘိုင်းသုံး၍ ချိတ်ဆက်ခြင်းတွင် အင်တာနက် မရှိပါ"</string>
+    <string name="no_upstream_notification_message" msgid="9037716118606459874">"စက်ပစ္စည်းများကို ချိတ်ဆက်၍မရပါ"</string>
+    <string name="no_upstream_notification_disable_button" msgid="5284024068281565456">"မိုဘိုင်းသုံး၍ ချိတ်ဆက်ခြင်း ပိတ်ရန်"</string>
+    <string name="upstream_roaming_notification_title" msgid="186331286017243006">"ဟော့စပေါ့ (သို့) မိုဘိုင်းသုံး၍ ချိတ်ဆက်ခြင်း ဖွင့်ထားသည်"</string>
+    <string name="upstream_roaming_notification_message" msgid="7692641323940316538">"ပြင်ပကွန်ရက်သုံးနေစဉ် နောက်ထပ်ကျသင့်ငွေ ပေးရနိုင်သည်"</string>
 </resources>
diff --git a/Tethering/res/values-mcc311-mnc480-nb/strings.xml b/Tethering/res/values-mcc311-mnc480-nb/strings.xml
index 57db484..92e6300 100644
--- a/Tethering/res/values-mcc311-mnc480-nb/strings.xml
+++ b/Tethering/res/values-mcc311-mnc480-nb/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="611650570559011140">"Internettdeling har ikke internettilgang"</string>
-    <string name="no_upstream_notification_message" msgid="6508394877641864863">"Enhetene kan ikke koble til"</string>
-    <string name="no_upstream_notification_disable_button" msgid="7609346639290990508">"Slå av internettdeling"</string>
-    <string name="upstream_roaming_notification_title" msgid="6032901176124830787">"Wi-Fi-sone eller internettdeling er på"</string>
-    <string name="upstream_roaming_notification_message" msgid="7599056263326217523">"Ytterligere kostnader kan påløpe under roaming"</string>
+    <string name="no_upstream_notification_title" msgid="5897815458155858594">"Internettdeling har ikke internettilgang"</string>
+    <string name="no_upstream_notification_message" msgid="9037716118606459874">"Enheter kan ikke koble til"</string>
+    <string name="no_upstream_notification_disable_button" msgid="5284024068281565456">"Slå av internettdeling"</string>
+    <string name="upstream_roaming_notification_title" msgid="186331286017243006">"Wifi-sone eller internettdeling er på"</string>
+    <string name="upstream_roaming_notification_message" msgid="7692641323940316538">"Det kan påløpe flere kostnader ved roaming"</string>
 </resources>
diff --git a/Tethering/res/values-mcc311-mnc480-ne/strings.xml b/Tethering/res/values-mcc311-mnc480-ne/strings.xml
index 1503244..bfd6108 100644
--- a/Tethering/res/values-mcc311-mnc480-ne/strings.xml
+++ b/Tethering/res/values-mcc311-mnc480-ne/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="611650570559011140">"टेदरिङमार्फत इन्टरनेट कनेक्सन प्राप्त हुन सकेन"</string>
-    <string name="no_upstream_notification_message" msgid="6508394877641864863">"यन्त्रहरू कनेक्ट गर्न सकिएन"</string>
-    <string name="no_upstream_notification_disable_button" msgid="7609346639290990508">"टेदरिङ निष्क्रिय पार्नुहोस्"</string>
-    <string name="upstream_roaming_notification_title" msgid="6032901176124830787">"हटस्पट वा टेदरिङ सक्रिय छ"</string>
-    <string name="upstream_roaming_notification_message" msgid="7599056263326217523">"रोमिङ सेवा प्रयोग गर्दा अतिरिक्त शुल्क लाग्न सक्छ"</string>
+    <string name="no_upstream_notification_title" msgid="5897815458155858594">"टेदरिङमार्फत इन्टरनेट कनेक्ट गर्न सकिएन"</string>
+    <string name="no_upstream_notification_message" msgid="9037716118606459874">"डिभाइसहरू कनेक्ट गर्न सकिएन"</string>
+    <string name="no_upstream_notification_disable_button" msgid="5284024068281565456">"टेदरिङ अफ गर्नुहोस्"</string>
+    <string name="upstream_roaming_notification_title" msgid="186331286017243006">"हटस्पट वा टेदरिङ अन छ"</string>
+    <string name="upstream_roaming_notification_message" msgid="7692641323940316538">"रोमिङ सेवा प्रयोग गर्दा अतिरिक्त शुल्क लाग्न सक्छ"</string>
 </resources>
diff --git a/Tethering/res/values-mcc311-mnc480-nl/strings.xml b/Tethering/res/values-mcc311-mnc480-nl/strings.xml
index b08133f..7533b6f 100644
--- a/Tethering/res/values-mcc311-mnc480-nl/strings.xml
+++ b/Tethering/res/values-mcc311-mnc480-nl/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="611650570559011140">"Tethering heeft geen internet"</string>
-    <string name="no_upstream_notification_message" msgid="6508394877641864863">"Apparaten kunnen niet worden verbonden"</string>
-    <string name="no_upstream_notification_disable_button" msgid="7609346639290990508">"Tethering uitschakelen"</string>
-    <string name="upstream_roaming_notification_title" msgid="6032901176124830787">"Hotspot of tethering is ingeschakeld"</string>
-    <string name="upstream_roaming_notification_message" msgid="7599056263326217523">"Er kunnen extra kosten voor roaming in rekening worden gebracht."</string>
+    <string name="no_upstream_notification_title" msgid="5897815458155858594">"Tethering heeft geen internet"</string>
+    <string name="no_upstream_notification_message" msgid="9037716118606459874">"Apparaten kunnen geen verbinding maken"</string>
+    <string name="no_upstream_notification_disable_button" msgid="5284024068281565456">"Tethering uitzetten"</string>
+    <string name="upstream_roaming_notification_title" msgid="186331286017243006">"Hotspot of tethering staat aan"</string>
+    <string name="upstream_roaming_notification_message" msgid="7692641323940316538">"Er kunnen extra kosten voor roaming in rekening worden gebracht"</string>
 </resources>
diff --git a/Tethering/res/values-mcc311-mnc480-or/strings.xml b/Tethering/res/values-mcc311-mnc480-or/strings.xml
index 1ad4ca3..7324de1 100644
--- a/Tethering/res/values-mcc311-mnc480-or/strings.xml
+++ b/Tethering/res/values-mcc311-mnc480-or/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="611650570559011140">"ଟିଥରିଂ ପାଇଁ କୌଣସି ଇଣ୍ଟର୍ନେଟ୍ ସଂଯୋଗ ନାହିଁ"</string>
-    <string name="no_upstream_notification_message" msgid="6508394877641864863">"ଡିଭାଇସଗୁଡ଼ିକ ସଂଯୋଗ କରାଯାଇପାରିବ ନାହିଁ"</string>
-    <string name="no_upstream_notification_disable_button" msgid="7609346639290990508">"ଟିଥରିଂ ବନ୍ଦ କରନ୍ତୁ"</string>
-    <string name="upstream_roaming_notification_title" msgid="6032901176124830787">"ହଟସ୍ପଟ୍ କିମ୍ବା ଟିଥରିଂ ଚାଲୁ ଅଛି"</string>
-    <string name="upstream_roaming_notification_message" msgid="7599056263326217523">"ରୋମିଂରେ ଥିବା ସମୟରେ ଅତିରିକ୍ତ ଶୁଳ୍କ ଲାଗୁ ହୋଇପାରେ"</string>
+    <string name="no_upstream_notification_title" msgid="5897815458155858594">"ଟିଥରିଂ ପାଇଁ କୌଣସି ଇଣ୍ଟରନେଟ କନେକ୍ସନ ନାହିଁ"</string>
+    <string name="no_upstream_notification_message" msgid="9037716118606459874">"ଡିଭାଇସଗୁଡ଼ିକୁ କନେକ୍ଟ କରାଯାଇପାରିବ ନାହିଁ"</string>
+    <string name="no_upstream_notification_disable_button" msgid="5284024068281565456">"ଟିଥରିଂକୁ ବନ୍ଦ କରନ୍ତୁ"</string>
+    <string name="upstream_roaming_notification_title" msgid="186331286017243006">"ହଟସ୍ପଟ କିମ୍ବା ଟିଥରିଂ ଚାଲୁ ଅଛି"</string>
+    <string name="upstream_roaming_notification_message" msgid="7692641323940316538">"ରୋମିଂ ସମୟରେ ଅତିରିକ୍ତ ଚାର୍ଜ ଲାଗୁ ହୋଇପାରେ"</string>
 </resources>
diff --git a/Tethering/res/values-mcc311-mnc480-pa/strings.xml b/Tethering/res/values-mcc311-mnc480-pa/strings.xml
index 88def56..0ca0af5 100644
--- a/Tethering/res/values-mcc311-mnc480-pa/strings.xml
+++ b/Tethering/res/values-mcc311-mnc480-pa/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="611650570559011140">"ਟੈਦਰਿੰਗ ਕੋਲ ਇੰਟਰਨੈੱਟ ਪਹੁੰਚ ਨਹੀਂ ਹੈ"</string>
-    <string name="no_upstream_notification_message" msgid="6508394877641864863">"ਡੀਵਾਈਸ ਕਨੈਕਟ ਨਹੀਂ ਕੀਤੇ ਜਾ ਸਕਦੇ"</string>
-    <string name="no_upstream_notification_disable_button" msgid="7609346639290990508">"ਟੈਦਰਿੰਗ ਬੰਦ ਕਰੋ"</string>
-    <string name="upstream_roaming_notification_title" msgid="6032901176124830787">"ਹੌਟਸਪੌਟ ਜਾਂ ਟੈਦਰਿੰਗ ਚਾਲੂ ਹੈ"</string>
-    <string name="upstream_roaming_notification_message" msgid="7599056263326217523">"ਰੋਮਿੰਗ ਦੌਰਾਨ ਵਧੀਕ ਖਰਚੇ ਲਾਗੂ ਹੋ ਸਕਦੇ ਹਨ"</string>
+    <string name="no_upstream_notification_title" msgid="5897815458155858594">"ਟੈਦਰਿੰਗ ਕੋਲ ਇੰਟਰਨੈੱਟ ਪਹੁੰਚ ਨਹੀਂ ਹੈ"</string>
+    <string name="no_upstream_notification_message" msgid="9037716118606459874">"ਡੀਵਾਈਸ ਕਨੈਕਟ ਨਹੀਂ ਕੀਤੇ ਜਾ ਸਕਦੇ"</string>
+    <string name="no_upstream_notification_disable_button" msgid="5284024068281565456">"ਟੈਦਰਿੰਗ ਬੰਦ ਕਰੋ"</string>
+    <string name="upstream_roaming_notification_title" msgid="186331286017243006">"ਹੌਟਸਪੌਟ ਜਾਂ ਟੈਦਰਿੰਗ ਚਾਲੂ ਹੈ"</string>
+    <string name="upstream_roaming_notification_message" msgid="7692641323940316538">"ਰੋਮਿੰਗ ਦੌਰਾਨ ਵਧੀਕ ਖਰਚੇ ਲਾਗੂ ਹੋ ਸਕਦੇ ਹਨ"</string>
 </resources>
diff --git a/Tethering/res/values-mcc311-mnc480-pl/strings.xml b/Tethering/res/values-mcc311-mnc480-pl/strings.xml
index f9890ab..62bb68c 100644
--- a/Tethering/res/values-mcc311-mnc480-pl/strings.xml
+++ b/Tethering/res/values-mcc311-mnc480-pl/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="611650570559011140">"Tethering nie ma internetu"</string>
-    <string name="no_upstream_notification_message" msgid="6508394877641864863">"Urządzenia nie mogą się połączyć"</string>
-    <string name="no_upstream_notification_disable_button" msgid="7609346639290990508">"Wyłącz tethering"</string>
-    <string name="upstream_roaming_notification_title" msgid="6032901176124830787">"Hotspot lub tethering jest włączony"</string>
-    <string name="upstream_roaming_notification_message" msgid="7599056263326217523">"Podczas korzystania z roamingu mogą zostać naliczone dodatkowe opłaty"</string>
+    <string name="no_upstream_notification_title" msgid="5897815458155858594">"Tethering nie ma internetu"</string>
+    <string name="no_upstream_notification_message" msgid="9037716118606459874">"Urządzenia nie mogą się połączyć"</string>
+    <string name="no_upstream_notification_disable_button" msgid="5284024068281565456">"Wyłącz tethering"</string>
+    <string name="upstream_roaming_notification_title" msgid="186331286017243006">"Hotspot lub tethering jest włączony"</string>
+    <string name="upstream_roaming_notification_message" msgid="7692641323940316538">"Podczas korzystania z roamingu mogą zostać naliczone dodatkowe opłaty"</string>
 </resources>
diff --git a/Tethering/res/values-mcc311-mnc480-pt-rBR/strings.xml b/Tethering/res/values-mcc311-mnc480-pt-rBR/strings.xml
index ce3b884..ae033fa 100644
--- a/Tethering/res/values-mcc311-mnc480-pt-rBR/strings.xml
+++ b/Tethering/res/values-mcc311-mnc480-pt-rBR/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="611650570559011140">"O tethering não tem Internet"</string>
-    <string name="no_upstream_notification_message" msgid="6508394877641864863">"Não é possível conectar os dispositivos"</string>
-    <string name="no_upstream_notification_disable_button" msgid="7609346639290990508">"Desativar o tethering"</string>
-    <string name="upstream_roaming_notification_title" msgid="6032901176124830787">"Ponto de acesso ou tethering ativado"</string>
-    <string name="upstream_roaming_notification_message" msgid="7599056263326217523">"Pode haver cobranças extras durante o roaming"</string>
+    <string name="no_upstream_notification_title" msgid="5897815458155858594">"O tethering não tem uma conexão de Internet"</string>
+    <string name="no_upstream_notification_message" msgid="9037716118606459874">"Não é possível conectar os dispositivos"</string>
+    <string name="no_upstream_notification_disable_button" msgid="5284024068281565456">"Desativar o tethering"</string>
+    <string name="upstream_roaming_notification_title" msgid="186331286017243006">"O ponto de acesso ou tethering está ativado"</string>
+    <string name="upstream_roaming_notification_message" msgid="7692641323940316538">"Pode haver cobranças extras durante o roaming"</string>
 </resources>
diff --git a/Tethering/res/values-mcc311-mnc480-pt-rPT/strings.xml b/Tethering/res/values-mcc311-mnc480-pt-rPT/strings.xml
index 7e883ea..c544864 100644
--- a/Tethering/res/values-mcc311-mnc480-pt-rPT/strings.xml
+++ b/Tethering/res/values-mcc311-mnc480-pt-rPT/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="611650570559011140">"A ligação (à Internet) via telemóvel não tem Internet"</string>
-    <string name="no_upstream_notification_message" msgid="6508394877641864863">"Não é possível ligar os dispositivos"</string>
-    <string name="no_upstream_notification_disable_button" msgid="7609346639290990508">"Desativar ligação (à Internet) via telemóvel"</string>
-    <string name="upstream_roaming_notification_title" msgid="6032901176124830787">"A zona Wi-Fi ou a ligação (à Internet) via telemóvel está ativada"</string>
-    <string name="upstream_roaming_notification_message" msgid="7599056263326217523">"Podem aplicar-se custos adicionais em roaming."</string>
+    <string name="no_upstream_notification_title" msgid="5897815458155858594">"A ligação (à Internet) via telemóvel não tem Internet"</string>
+    <string name="no_upstream_notification_message" msgid="9037716118606459874">"Não é possível ligar os dispositivos"</string>
+    <string name="no_upstream_notification_disable_button" msgid="5284024068281565456">"Desativar ligação (à Internet) via telemóvel"</string>
+    <string name="upstream_roaming_notification_title" msgid="186331286017243006">"A zona Wi-Fi ou a ligação (à Internet) via telemóvel está ativada"</string>
+    <string name="upstream_roaming_notification_message" msgid="7692641323940316538">"Podem aplicar-se custos adicionais em roaming"</string>
 </resources>
diff --git a/Tethering/res/values-mcc311-mnc480-pt/strings.xml b/Tethering/res/values-mcc311-mnc480-pt/strings.xml
index ce3b884..ae033fa 100644
--- a/Tethering/res/values-mcc311-mnc480-pt/strings.xml
+++ b/Tethering/res/values-mcc311-mnc480-pt/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="611650570559011140">"O tethering não tem Internet"</string>
-    <string name="no_upstream_notification_message" msgid="6508394877641864863">"Não é possível conectar os dispositivos"</string>
-    <string name="no_upstream_notification_disable_button" msgid="7609346639290990508">"Desativar o tethering"</string>
-    <string name="upstream_roaming_notification_title" msgid="6032901176124830787">"Ponto de acesso ou tethering ativado"</string>
-    <string name="upstream_roaming_notification_message" msgid="7599056263326217523">"Pode haver cobranças extras durante o roaming"</string>
+    <string name="no_upstream_notification_title" msgid="5897815458155858594">"O tethering não tem uma conexão de Internet"</string>
+    <string name="no_upstream_notification_message" msgid="9037716118606459874">"Não é possível conectar os dispositivos"</string>
+    <string name="no_upstream_notification_disable_button" msgid="5284024068281565456">"Desativar o tethering"</string>
+    <string name="upstream_roaming_notification_title" msgid="186331286017243006">"O ponto de acesso ou tethering está ativado"</string>
+    <string name="upstream_roaming_notification_message" msgid="7692641323940316538">"Pode haver cobranças extras durante o roaming"</string>
 </resources>
diff --git a/Tethering/res/values-mcc311-mnc480-ro/strings.xml b/Tethering/res/values-mcc311-mnc480-ro/strings.xml
index 1009417..484b8b3 100644
--- a/Tethering/res/values-mcc311-mnc480-ro/strings.xml
+++ b/Tethering/res/values-mcc311-mnc480-ro/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="611650570559011140">"Procesul de tethering nu are internet"</string>
-    <string name="no_upstream_notification_message" msgid="6508394877641864863">"Dispozitivele nu se pot conecta"</string>
-    <string name="no_upstream_notification_disable_button" msgid="7609346639290990508">"Dezactivați procesul de tethering"</string>
-    <string name="upstream_roaming_notification_title" msgid="6032901176124830787">"S-a activat hotspotul sau tethering"</string>
-    <string name="upstream_roaming_notification_message" msgid="7599056263326217523">"Se pot aplica taxe suplimentare pentru roaming"</string>
+    <string name="no_upstream_notification_title" msgid="5897815458155858594">"Procesul de tethering nu are internet"</string>
+    <string name="no_upstream_notification_message" msgid="9037716118606459874">"Dispozitivele nu se pot conecta"</string>
+    <string name="no_upstream_notification_disable_button" msgid="5284024068281565456">"Dezactivează tetheringul"</string>
+    <string name="upstream_roaming_notification_title" msgid="186331286017243006">"S-a activat hotspotul sau tetheringul"</string>
+    <string name="upstream_roaming_notification_message" msgid="7692641323940316538">"Se pot aplica taxe suplimentare pentru roaming"</string>
 </resources>
diff --git a/Tethering/res/values-mcc311-mnc480-ru/strings.xml b/Tethering/res/values-mcc311-mnc480-ru/strings.xml
index 88683be..ef38703 100644
--- a/Tethering/res/values-mcc311-mnc480-ru/strings.xml
+++ b/Tethering/res/values-mcc311-mnc480-ru/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="611650570559011140">"Режим модема используется без доступа к Интернету"</string>
-    <string name="no_upstream_notification_message" msgid="6508394877641864863">"Невозможно подключить устройства."</string>
-    <string name="no_upstream_notification_disable_button" msgid="7609346639290990508">"Отключить режим модема"</string>
-    <string name="upstream_roaming_notification_title" msgid="6032901176124830787">"Включены точка доступа или режим модема"</string>
-    <string name="upstream_roaming_notification_message" msgid="7599056263326217523">"За использование услуг связи в роуминге может взиматься дополнительная плата."</string>
+    <string name="no_upstream_notification_title" msgid="5897815458155858594">"Режим модема используется без доступа к интернету"</string>
+    <string name="no_upstream_notification_message" msgid="9037716118606459874">"Невозможно подключить устройства."</string>
+    <string name="no_upstream_notification_disable_button" msgid="5284024068281565456">"Отключить режим модема"</string>
+    <string name="upstream_roaming_notification_title" msgid="186331286017243006">"Включена точка доступа или режим модема"</string>
+    <string name="upstream_roaming_notification_message" msgid="7692641323940316538">"За использование услуг связи в роуминге может взиматься дополнительная плата."</string>
 </resources>
diff --git a/Tethering/res/values-mcc311-mnc480-si/strings.xml b/Tethering/res/values-mcc311-mnc480-si/strings.xml
index 176bcdb..3069085 100644
--- a/Tethering/res/values-mcc311-mnc480-si/strings.xml
+++ b/Tethering/res/values-mcc311-mnc480-si/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="611650570559011140">"ටෙදරින් හට අන්තර්ජාලය නැත"</string>
-    <string name="no_upstream_notification_message" msgid="6508394877641864863">"උපාංගවලට සම්බන්ධ විය නොහැකිය"</string>
-    <string name="no_upstream_notification_disable_button" msgid="7609346639290990508">"ටෙදරින් ක්‍රියාවිරහිත කරන්න"</string>
-    <string name="upstream_roaming_notification_title" msgid="6032901176124830787">"හොට්ස්පොට් හෝ ටෙදරින් ක්‍රියාත්මකයි"</string>
-    <string name="upstream_roaming_notification_message" msgid="7599056263326217523">"රෝමිං අතරතුර අමතර ගාස්තු අදාළ විය හැකිය"</string>
+    <string name="no_upstream_notification_title" msgid="5897815458155858594">"ටෙදරින් හට අන්තර්ජාලය නැත"</string>
+    <string name="no_upstream_notification_message" msgid="9037716118606459874">"උපාංගවලට සම්බන්ධ විය නොහැක"</string>
+    <string name="no_upstream_notification_disable_button" msgid="5284024068281565456">"ටෙදරින් ක්‍රියාවිරහිත කරන්න"</string>
+    <string name="upstream_roaming_notification_title" msgid="186331286017243006">"හොට්ස්පොට් හෝ ටෙදරින් ක්‍රියාත්මකයි"</string>
+    <string name="upstream_roaming_notification_message" msgid="7692641323940316538">"රෝමිං අතරේ අතිරේක ගාස්තු අදාළ විය හැක"</string>
 </resources>
diff --git a/Tethering/res/values-mcc311-mnc480-sk/strings.xml b/Tethering/res/values-mcc311-mnc480-sk/strings.xml
index b9e2127..9f70311 100644
--- a/Tethering/res/values-mcc311-mnc480-sk/strings.xml
+++ b/Tethering/res/values-mcc311-mnc480-sk/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="611650570559011140">"Tethering nemá internetové pripojenie"</string>
-    <string name="no_upstream_notification_message" msgid="6508394877641864863">"Zariadenia sa nemôžu pripojiť"</string>
-    <string name="no_upstream_notification_disable_button" msgid="7609346639290990508">"Vypnúť tethering"</string>
-    <string name="upstream_roaming_notification_title" msgid="6032901176124830787">"Je zapnutý hotspot alebo tethering"</string>
-    <string name="upstream_roaming_notification_message" msgid="7599056263326217523">"Počas roamingu vám môžu byť účtované ďalšie poplatky"</string>
+    <string name="no_upstream_notification_title" msgid="5897815458155858594">"Tethering nemá internetové pripojenie"</string>
+    <string name="no_upstream_notification_message" msgid="9037716118606459874">"Zariadenia sa nemôžu pripojiť"</string>
+    <string name="no_upstream_notification_disable_button" msgid="5284024068281565456">"Vypnúť tethering"</string>
+    <string name="upstream_roaming_notification_title" msgid="186331286017243006">"Je zapnutý hotspot alebo tethering"</string>
+    <string name="upstream_roaming_notification_message" msgid="7692641323940316538">"Počas roamingu vám môžu byť účtované ďalšie poplatky"</string>
 </resources>
diff --git a/Tethering/res/values-mcc311-mnc480-sl/strings.xml b/Tethering/res/values-mcc311-mnc480-sl/strings.xml
index e8140e6..613d7a8 100644
--- a/Tethering/res/values-mcc311-mnc480-sl/strings.xml
+++ b/Tethering/res/values-mcc311-mnc480-sl/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="611650570559011140">"Internetna povezava prek mobilnega telefona ni vzpostavljena"</string>
-    <string name="no_upstream_notification_message" msgid="6508394877641864863">"Napravi se ne moreta povezati"</string>
-    <string name="no_upstream_notification_disable_button" msgid="7609346639290990508">"Izklopi internetno povezavo prek mobilnega telefona"</string>
-    <string name="upstream_roaming_notification_title" msgid="6032901176124830787">"Dostopna točka ali internetna povezava prek mobilnega telefona je vklopljena"</string>
-    <string name="upstream_roaming_notification_message" msgid="7599056263326217523">"Med gostovanjem lahko nastanejo dodatni stroški"</string>
+    <string name="no_upstream_notification_title" msgid="5897815458155858594">"Povezava računalnika z internetom prek mobilnega telefona nima internetne povezave"</string>
+    <string name="no_upstream_notification_message" msgid="9037716118606459874">"Napravi se ne moreta povezati"</string>
+    <string name="no_upstream_notification_disable_button" msgid="5284024068281565456">"Izklopi povezavo računalnika z internetom prek mobilnega telefona"</string>
+    <string name="upstream_roaming_notification_title" msgid="186331286017243006">"Dostopna točka ali povezava računalnika z internetom prek mobilnega telefona je vklopljena"</string>
+    <string name="upstream_roaming_notification_message" msgid="7692641323940316538">"Med gostovanjem lahko nastanejo dodatni stroški."</string>
 </resources>
diff --git a/Tethering/res/values-mcc311-mnc480-sq/strings.xml b/Tethering/res/values-mcc311-mnc480-sq/strings.xml
index 61e698d..0472d4d 100644
--- a/Tethering/res/values-mcc311-mnc480-sq/strings.xml
+++ b/Tethering/res/values-mcc311-mnc480-sq/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="611650570559011140">"Ndarja e internetit nuk ka internet"</string>
-    <string name="no_upstream_notification_message" msgid="6508394877641864863">"Pajisjet nuk mund të lidhen"</string>
-    <string name="no_upstream_notification_disable_button" msgid="7609346639290990508">"Çaktivizo ndarjen e internetit"</string>
-    <string name="upstream_roaming_notification_title" msgid="6032901176124830787">"Zona e qasjes për internet ose ndarja e internetit është aktive"</string>
-    <string name="upstream_roaming_notification_message" msgid="7599056263326217523">"Mund të zbatohen tarifime shtesë kur je në roaming"</string>
+    <string name="no_upstream_notification_title" msgid="5897815458155858594">"Ndarja e internetit nuk ka internet"</string>
+    <string name="no_upstream_notification_message" msgid="9037716118606459874">"Pajisjet nuk mund të lidhen"</string>
+    <string name="no_upstream_notification_disable_button" msgid="5284024068281565456">"Çaktivizo ndarjen e internetit"</string>
+    <string name="upstream_roaming_notification_title" msgid="186331286017243006">"Zona e qasjes për internet ose ndarja e internetit është aktive"</string>
+    <string name="upstream_roaming_notification_message" msgid="7692641323940316538">"Mund të zbatohen tarifime shtesë kur je në roaming"</string>
 </resources>
diff --git a/Tethering/res/values-mcc311-mnc480-sr/strings.xml b/Tethering/res/values-mcc311-mnc480-sr/strings.xml
index b4c411c..bc70cf6 100644
--- a/Tethering/res/values-mcc311-mnc480-sr/strings.xml
+++ b/Tethering/res/values-mcc311-mnc480-sr/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="611650570559011140">"Привезивање нема приступ интернету"</string>
-    <string name="no_upstream_notification_message" msgid="6508394877641864863">"Повезивање уређаја није успело"</string>
-    <string name="no_upstream_notification_disable_button" msgid="7609346639290990508">"Искључи привезивање"</string>
-    <string name="upstream_roaming_notification_title" msgid="6032901176124830787">"Укључен је хотспот или привезивање"</string>
-    <string name="upstream_roaming_notification_message" msgid="7599056263326217523">"Можда важе додатни трошкови у ромингу"</string>
+    <string name="no_upstream_notification_title" msgid="5897815458155858594">"Привезивање нема приступ интернету"</string>
+    <string name="no_upstream_notification_message" msgid="9037716118606459874">"Повезивање уређаја није успело"</string>
+    <string name="no_upstream_notification_disable_button" msgid="5284024068281565456">"Искључи привезивање"</string>
+    <string name="upstream_roaming_notification_title" msgid="186331286017243006">"Укључен је хотспот или привезивање"</string>
+    <string name="upstream_roaming_notification_message" msgid="7692641323940316538">"Можда важе додатни трошкови у ромингу"</string>
 </resources>
diff --git a/Tethering/res/values-mcc311-mnc480-sv/strings.xml b/Tethering/res/values-mcc311-mnc480-sv/strings.xml
index 4f543e4..507acc8 100644
--- a/Tethering/res/values-mcc311-mnc480-sv/strings.xml
+++ b/Tethering/res/values-mcc311-mnc480-sv/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="611650570559011140">"Det finns ingen internetanslutning för internetdelningen"</string>
-    <string name="no_upstream_notification_message" msgid="6508394877641864863">"Enheterna kan inte anslutas"</string>
-    <string name="no_upstream_notification_disable_button" msgid="7609346639290990508">"Inaktivera internetdelning"</string>
-    <string name="upstream_roaming_notification_title" msgid="6032901176124830787">"Surfzon eller internetdelning har aktiverats"</string>
-    <string name="upstream_roaming_notification_message" msgid="7599056263326217523">"Ytterligare avgifter kan tillkomma vid roaming"</string>
+    <string name="no_upstream_notification_title" msgid="5897815458155858594">"Det finns ingen internetanslutning för internetdelningen"</string>
+    <string name="no_upstream_notification_message" msgid="9037716118606459874">"Enheterna kan inte anslutas"</string>
+    <string name="no_upstream_notification_disable_button" msgid="5284024068281565456">"Inaktivera internetdelning"</string>
+    <string name="upstream_roaming_notification_title" msgid="186331286017243006">"Surfzon eller internetdelning har aktiverats"</string>
+    <string name="upstream_roaming_notification_message" msgid="7692641323940316538">"Ytterligare avgifter kan tillkomma vid roaming"</string>
 </resources>
diff --git a/Tethering/res/values-mcc311-mnc480-sw/strings.xml b/Tethering/res/values-mcc311-mnc480-sw/strings.xml
index ac347ab..865b0e3 100644
--- a/Tethering/res/values-mcc311-mnc480-sw/strings.xml
+++ b/Tethering/res/values-mcc311-mnc480-sw/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="611650570559011140">"Kipengele cha kusambaza mtandao hakina intaneti"</string>
-    <string name="no_upstream_notification_message" msgid="6508394877641864863">"Imeshindwa kuunganisha vifaa"</string>
-    <string name="no_upstream_notification_disable_button" msgid="7609346639290990508">"Zima kipengele cha kusambaza mtandao"</string>
-    <string name="upstream_roaming_notification_title" msgid="6032901176124830787">"Umewasha kipengele cha kusambaza mtandao au mtandao pepe"</string>
-    <string name="upstream_roaming_notification_message" msgid="7599056263326217523">"Huenda ukatozwa gharama za ziada ukitumia mitandao ya ng\'ambo"</string>
+    <string name="no_upstream_notification_title" msgid="5897815458155858594">"Huduma ya kusambaza mtandao haina muunganisho wa intaneti"</string>
+    <string name="no_upstream_notification_message" msgid="9037716118606459874">"Imeshindwa kuunganisha vifaa"</string>
+    <string name="no_upstream_notification_disable_button" msgid="5284024068281565456">"Zima kipengele cha kusambaza mtandao"</string>
+    <string name="upstream_roaming_notification_title" msgid="186331286017243006">"Umewasha kipengele cha kusambaza mtandao au mtandao pepe"</string>
+    <string name="upstream_roaming_notification_message" msgid="7692641323940316538">"Huenda ukatozwa gharama za ziada ukitumia mitandao ya ng\'ambo"</string>
 </resources>
diff --git a/Tethering/res/values-mcc311-mnc480-ta/strings.xml b/Tethering/res/values-mcc311-mnc480-ta/strings.xml
index 2ea2467..e5f33ee 100644
--- a/Tethering/res/values-mcc311-mnc480-ta/strings.xml
+++ b/Tethering/res/values-mcc311-mnc480-ta/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="611650570559011140">"இணைப்பு முறைக்கு இணைய இணைப்பு இல்லை"</string>
-    <string name="no_upstream_notification_message" msgid="6508394877641864863">"சாதனங்களால் இணைய முடியவில்லை"</string>
-    <string name="no_upstream_notification_disable_button" msgid="7609346639290990508">"இணைப்பு முறையை ஆஃப் செய்"</string>
-    <string name="upstream_roaming_notification_title" msgid="6032901176124830787">"ஹாட்ஸ்பாட் அல்லது இணைப்பு முறை ஆன் செய்யப்பட்டுள்ளது"</string>
-    <string name="upstream_roaming_notification_message" msgid="7599056263326217523">"ரோமிங்கின்போது கூடுதல் கட்டணங்கள் விதிக்கப்படக்கூடும்"</string>
+    <string name="no_upstream_notification_title" msgid="5897815458155858594">"இணைப்பு முறைக்கு இணைய இணைப்பு இல்லை"</string>
+    <string name="no_upstream_notification_message" msgid="9037716118606459874">"சாதனங்களால் இணைய முடியவில்லை"</string>
+    <string name="no_upstream_notification_disable_button" msgid="5284024068281565456">"இணைப்பு முறையை முடக்கு"</string>
+    <string name="upstream_roaming_notification_title" msgid="186331286017243006">"ஹாட்ஸ்பாட் அல்லது இணைப்பு முறை இயக்கப்பட்டுள்ளது"</string>
+    <string name="upstream_roaming_notification_message" msgid="7692641323940316538">"ரோமிங்கின்போது கூடுதல் கட்டணங்கள் விதிக்கப்படலாம்"</string>
 </resources>
diff --git a/Tethering/res/values-mcc311-mnc480-te/strings.xml b/Tethering/res/values-mcc311-mnc480-te/strings.xml
index 9360297..1bb4786 100644
--- a/Tethering/res/values-mcc311-mnc480-te/strings.xml
+++ b/Tethering/res/values-mcc311-mnc480-te/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="611650570559011140">"టెథరింగ్ చేయడానికి ఇంటర్నెట్ కనెక్షన్ లేదు"</string>
-    <string name="no_upstream_notification_message" msgid="6508394877641864863">"పరికరాలు కనెక్ట్ అవ్వడం లేదు"</string>
-    <string name="no_upstream_notification_disable_button" msgid="7609346639290990508">"టెథరింగ్‌ను ఆఫ్ చేయండి"</string>
-    <string name="upstream_roaming_notification_title" msgid="6032901176124830787">"హాట్‌స్పాట్ లేదా టెథరింగ్ ఆన్‌లో ఉంది"</string>
-    <string name="upstream_roaming_notification_message" msgid="7599056263326217523">"రోమింగ్‌లో ఉన్నప్పుడు అదనపు ఛార్జీలు వర్తించవచ్చు"</string>
+    <string name="no_upstream_notification_title" msgid="5897815458155858594">"టెథరింగ్ చేయడానికి ఇంటర్నెట్ కనెక్షన్ లేదు"</string>
+    <string name="no_upstream_notification_message" msgid="9037716118606459874">"పరికరాలు కనెక్ట్ అవ్వడం లేదు"</string>
+    <string name="no_upstream_notification_disable_button" msgid="5284024068281565456">"టెథరింగ్‌ను ఆఫ్ చేయండి"</string>
+    <string name="upstream_roaming_notification_title" msgid="186331286017243006">"హాట్‌స్పాట్ లేదా టెథరింగ్ ఆన్‌లో ఉంది"</string>
+    <string name="upstream_roaming_notification_message" msgid="7692641323940316538">"రోమింగ్‌లో ఉన్నప్పుడు అదనపు ఛార్జీలు వర్తించవచ్చు"</string>
 </resources>
diff --git a/Tethering/res/values-mcc311-mnc480-th/strings.xml b/Tethering/res/values-mcc311-mnc480-th/strings.xml
index 9c4d7e0..e76f735 100644
--- a/Tethering/res/values-mcc311-mnc480-th/strings.xml
+++ b/Tethering/res/values-mcc311-mnc480-th/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="611650570559011140">"การเชื่อมต่ออินเทอร์เน็ตผ่านมือถือไม่มีอินเทอร์เน็ต"</string>
-    <string name="no_upstream_notification_message" msgid="6508394877641864863">"อุปกรณ์เชื่อมต่อไม่ได้"</string>
-    <string name="no_upstream_notification_disable_button" msgid="7609346639290990508">"ปิดการเชื่อมต่ออินเทอร์เน็ตผ่านมือถือ"</string>
-    <string name="upstream_roaming_notification_title" msgid="6032901176124830787">"ฮอตสปอตหรือการเชื่อมต่ออินเทอร์เน็ตผ่านมือถือเปิดอยู่"</string>
-    <string name="upstream_roaming_notification_message" msgid="7599056263326217523">"อาจมีค่าใช้จ่ายเพิ่มเติมขณะโรมมิ่ง"</string>
+    <string name="no_upstream_notification_title" msgid="5897815458155858594">"ไม่มีอินเทอร์เน็ตสำหรับการเชื่อมต่ออินเทอร์เน็ตผ่านมือถือ"</string>
+    <string name="no_upstream_notification_message" msgid="9037716118606459874">"อุปกรณ์เชื่อมต่อไม่ได้"</string>
+    <string name="no_upstream_notification_disable_button" msgid="5284024068281565456">"ปิดการเชื่อมต่ออินเทอร์เน็ตผ่านมือถือ"</string>
+    <string name="upstream_roaming_notification_title" msgid="186331286017243006">"ฮอตสปอตหรือการเชื่อมต่ออินเทอร์เน็ตผ่านมือถือเปิดอยู่"</string>
+    <string name="upstream_roaming_notification_message" msgid="7692641323940316538">"อาจมีค่าใช้จ่ายเพิ่มเติมขณะโรมมิ่ง"</string>
 </resources>
diff --git a/Tethering/res/values-mcc311-mnc480-tl/strings.xml b/Tethering/res/values-mcc311-mnc480-tl/strings.xml
index a7c78a5..cccc8c4 100644
--- a/Tethering/res/values-mcc311-mnc480-tl/strings.xml
+++ b/Tethering/res/values-mcc311-mnc480-tl/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="611650570559011140">"Walang internet ang pag-tether"</string>
-    <string name="no_upstream_notification_message" msgid="6508394877641864863">"Hindi makakonekta ang mga device"</string>
-    <string name="no_upstream_notification_disable_button" msgid="7609346639290990508">"I-off ang pag-tether"</string>
-    <string name="upstream_roaming_notification_title" msgid="6032901176124830787">"Naka-on ang Hotspot o pag-tether"</string>
-    <string name="upstream_roaming_notification_message" msgid="7599056263326217523">"Posibleng magkaroon ng mga karagdagang singil habang nagro-roam"</string>
+    <string name="no_upstream_notification_title" msgid="5897815458155858594">"Walang internet ang pag-tether"</string>
+    <string name="no_upstream_notification_message" msgid="9037716118606459874">"Hindi makakonekta ang mga device"</string>
+    <string name="no_upstream_notification_disable_button" msgid="5284024068281565456">"I-off ang pag-tether"</string>
+    <string name="upstream_roaming_notification_title" msgid="186331286017243006">"Naka-on ang hotspot o pag-tether"</string>
+    <string name="upstream_roaming_notification_message" msgid="7692641323940316538">"Posibleng magkaroon ng mga karagdagang singil habang nagro-roam"</string>
 </resources>
diff --git a/Tethering/res/values-mcc311-mnc480-tr/strings.xml b/Tethering/res/values-mcc311-mnc480-tr/strings.xml
index 93da2c3..93bef12 100644
--- a/Tethering/res/values-mcc311-mnc480-tr/strings.xml
+++ b/Tethering/res/values-mcc311-mnc480-tr/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="611650570559011140">"Tethering\'in internet bağlantısı yok"</string>
-    <string name="no_upstream_notification_message" msgid="6508394877641864863">"Cihazlar bağlanamıyor"</string>
-    <string name="no_upstream_notification_disable_button" msgid="7609346639290990508">"Tethering\'i kapat"</string>
-    <string name="upstream_roaming_notification_title" msgid="6032901176124830787">"Hotspot veya tethering açık"</string>
-    <string name="upstream_roaming_notification_message" msgid="7599056263326217523">"Dolaşım sırasında ek ücretler uygulanabilir"</string>
+    <string name="no_upstream_notification_title" msgid="5897815458155858594">"Tethering\'in internet bağlantısı yok"</string>
+    <string name="no_upstream_notification_message" msgid="9037716118606459874">"Cihazlar bağlanamıyor"</string>
+    <string name="no_upstream_notification_disable_button" msgid="5284024068281565456">"Tethering\'i kapat"</string>
+    <string name="upstream_roaming_notification_title" msgid="186331286017243006">"Hotspot veya tethering açık"</string>
+    <string name="upstream_roaming_notification_message" msgid="7692641323940316538">"Dolaşım sırasında ek ücretler uygulanabilir"</string>
 </resources>
diff --git a/Tethering/res/values-mcc311-mnc480-uk/strings.xml b/Tethering/res/values-mcc311-mnc480-uk/strings.xml
index ee0dcd2..1bc2c06 100644
--- a/Tethering/res/values-mcc311-mnc480-uk/strings.xml
+++ b/Tethering/res/values-mcc311-mnc480-uk/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="611650570559011140">"Телефон, який використовується як модем, не підключений до Інтернету"</string>
-    <string name="no_upstream_notification_message" msgid="6508394877641864863">"Не вдається підключити пристрої"</string>
-    <string name="no_upstream_notification_disable_button" msgid="7609346639290990508">"Вимкнути використання телефона як модема"</string>
-    <string name="upstream_roaming_notification_title" msgid="6032901176124830787">"Увімкнено точку доступу або використання телефона як модема"</string>
-    <string name="upstream_roaming_notification_message" msgid="7599056263326217523">"У роумінгу може стягуватися додаткова плата"</string>
+    <string name="no_upstream_notification_title" msgid="5897815458155858594">"Телефон, що використовується як модем, не підключений до Інтернету"</string>
+    <string name="no_upstream_notification_message" msgid="9037716118606459874">"Не вдається підключити пристрої"</string>
+    <string name="no_upstream_notification_disable_button" msgid="5284024068281565456">"Вимкнути використання телефона як модема"</string>
+    <string name="upstream_roaming_notification_title" msgid="186331286017243006">"Увімкнено точку доступу або використання телефона як модема"</string>
+    <string name="upstream_roaming_notification_message" msgid="7692641323940316538">"У роумінгу може стягуватися додаткова плата"</string>
 </resources>
diff --git a/Tethering/res/values-mcc311-mnc480-ur/strings.xml b/Tethering/res/values-mcc311-mnc480-ur/strings.xml
index 41cd28e..63d8e1b 100644
--- a/Tethering/res/values-mcc311-mnc480-ur/strings.xml
+++ b/Tethering/res/values-mcc311-mnc480-ur/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="611650570559011140">"ٹیدرنگ میں انٹرنیٹ نہیں ہے"</string>
-    <string name="no_upstream_notification_message" msgid="6508394877641864863">"آلات منسلک نہیں ہو سکتے"</string>
-    <string name="no_upstream_notification_disable_button" msgid="7609346639290990508">"ٹیدرنگ آف کریں"</string>
-    <string name="upstream_roaming_notification_title" msgid="6032901176124830787">"ہاٹ اسپاٹ یا ٹیدرنگ آن ہے"</string>
-    <string name="upstream_roaming_notification_message" msgid="7599056263326217523">"رومنگ کے دوران اضافی چارجز لاگو ہو سکتے ہیں"</string>
+    <string name="no_upstream_notification_title" msgid="5897815458155858594">"ٹیدرنگ میں انٹرنیٹ نہیں ہے"</string>
+    <string name="no_upstream_notification_message" msgid="9037716118606459874">"آلات منسلک نہیں ہو سکتے"</string>
+    <string name="no_upstream_notification_disable_button" msgid="5284024068281565456">"ٹیدرنگ آف کریں"</string>
+    <string name="upstream_roaming_notification_title" msgid="186331286017243006">"ہاٹ اسپاٹ یا ٹیدرنگ آن ہے"</string>
+    <string name="upstream_roaming_notification_message" msgid="7692641323940316538">"رومنگ کے دوران اضافی چارجز لاگو ہو سکتے ہیں"</string>
 </resources>
diff --git a/Tethering/res/values-mcc311-mnc480-uz/strings.xml b/Tethering/res/values-mcc311-mnc480-uz/strings.xml
index c847bc9..4d655d9 100644
--- a/Tethering/res/values-mcc311-mnc480-uz/strings.xml
+++ b/Tethering/res/values-mcc311-mnc480-uz/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="611650570559011140">"Modem internetga ulanmagan"</string>
-    <string name="no_upstream_notification_message" msgid="6508394877641864863">"Qurilmalar ulanmadi"</string>
-    <string name="no_upstream_notification_disable_button" msgid="7609346639290990508">"Modem rejimini faolsizlantirish"</string>
-    <string name="upstream_roaming_notification_title" msgid="6032901176124830787">"Hotspot yoki modem rejimi yoniq"</string>
-    <string name="upstream_roaming_notification_message" msgid="7599056263326217523">"Rouming vaqtida qoʻshimcha haq olinishi mumkin"</string>
+    <string name="no_upstream_notification_title" msgid="5897815458155858594">"Modem internetga ulanmagan"</string>
+    <string name="no_upstream_notification_message" msgid="9037716118606459874">"Qurilmalar ulanmadi"</string>
+    <string name="no_upstream_notification_disable_button" msgid="5284024068281565456">"Modem rejimini faolsizlantirish"</string>
+    <string name="upstream_roaming_notification_title" msgid="186331286017243006">"Hotspot yoki modem rejimi yoniq"</string>
+    <string name="upstream_roaming_notification_message" msgid="7692641323940316538">"Rouming vaqtida qoʻshimcha haq olinishi mumkin"</string>
 </resources>
diff --git a/Tethering/res/values-mcc311-mnc480-vi/strings.xml b/Tethering/res/values-mcc311-mnc480-vi/strings.xml
index a74326f..15e7a01 100644
--- a/Tethering/res/values-mcc311-mnc480-vi/strings.xml
+++ b/Tethering/res/values-mcc311-mnc480-vi/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="611650570559011140">"Không có Internet để chia sẻ kết Internet"</string>
-    <string name="no_upstream_notification_message" msgid="6508394877641864863">"Các thiết bị không thể kết nối"</string>
-    <string name="no_upstream_notification_disable_button" msgid="7609346639290990508">"Tắt tính năng chia sẻ Internet"</string>
-    <string name="upstream_roaming_notification_title" msgid="6032901176124830787">"Điểm phát sóng hoặc tính năng chia sẻ Internet đang bật"</string>
-    <string name="upstream_roaming_notification_message" msgid="7599056263326217523">"Bạn có thể mất thêm phí dữ liệu khi chuyển vùng"</string>
+    <string name="no_upstream_notification_title" msgid="5897815458155858594">"Không có Internet để chia sẻ Internet"</string>
+    <string name="no_upstream_notification_message" msgid="9037716118606459874">"Các thiết bị không thể kết nối"</string>
+    <string name="no_upstream_notification_disable_button" msgid="5284024068281565456">"Tắt tính năng chia sẻ Internet"</string>
+    <string name="upstream_roaming_notification_title" msgid="186331286017243006">"Điểm phát sóng hoặc tính năng chia sẻ Internet đang bật"</string>
+    <string name="upstream_roaming_notification_message" msgid="7692641323940316538">"Bạn có thể mất thêm phí dữ liệu khi chuyển vùng"</string>
 </resources>
diff --git a/Tethering/res/values-mcc311-mnc480-zh-rCN/strings.xml b/Tethering/res/values-mcc311-mnc480-zh-rCN/strings.xml
index d737003..8a200aa 100644
--- a/Tethering/res/values-mcc311-mnc480-zh-rCN/strings.xml
+++ b/Tethering/res/values-mcc311-mnc480-zh-rCN/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="611650570559011140">"共享网络未连接到互联网"</string>
-    <string name="no_upstream_notification_message" msgid="6508394877641864863">"设备无法连接"</string>
-    <string name="no_upstream_notification_disable_button" msgid="7609346639290990508">"关闭网络共享"</string>
-    <string name="upstream_roaming_notification_title" msgid="6032901176124830787">"热点或网络共享已开启"</string>
-    <string name="upstream_roaming_notification_message" msgid="7599056263326217523">"漫游时可能会产生额外的费用"</string>
+    <string name="no_upstream_notification_title" msgid="5897815458155858594">"共享网络未连接到互联网"</string>
+    <string name="no_upstream_notification_message" msgid="9037716118606459874">"设备无法连接"</string>
+    <string name="no_upstream_notification_disable_button" msgid="5284024068281565456">"关闭网络共享"</string>
+    <string name="upstream_roaming_notification_title" msgid="186331286017243006">"热点或网络共享已开启"</string>
+    <string name="upstream_roaming_notification_message" msgid="7692641323940316538">"漫游时可能会产生额外的费用"</string>
 </resources>
diff --git a/Tethering/res/values-mcc311-mnc480-zh-rHK/strings.xml b/Tethering/res/values-mcc311-mnc480-zh-rHK/strings.xml
index f378a9d..b2e64d1 100644
--- a/Tethering/res/values-mcc311-mnc480-zh-rHK/strings.xml
+++ b/Tethering/res/values-mcc311-mnc480-zh-rHK/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="611650570559011140">"無法透過網絡共享連線至互聯網"</string>
-    <string name="no_upstream_notification_message" msgid="6508394877641864863">"裝置無法連接"</string>
-    <string name="no_upstream_notification_disable_button" msgid="7609346639290990508">"關閉網絡共享"</string>
-    <string name="upstream_roaming_notification_title" msgid="6032901176124830787">"熱點或網絡共享已開啟"</string>
-    <string name="upstream_roaming_notification_message" msgid="7599056263326217523">"漫遊時可能需要支付額外費用"</string>
+    <string name="no_upstream_notification_title" msgid="5897815458155858594">"無法透過網絡共享連線至互聯網"</string>
+    <string name="no_upstream_notification_message" msgid="9037716118606459874">"裝置無法連接"</string>
+    <string name="no_upstream_notification_disable_button" msgid="5284024068281565456">"關閉網絡共享"</string>
+    <string name="upstream_roaming_notification_title" msgid="186331286017243006">"熱點或網絡共享已開啟"</string>
+    <string name="upstream_roaming_notification_message" msgid="7692641323940316538">"漫遊時可能需要支付額外費用"</string>
 </resources>
diff --git a/Tethering/res/values-mcc311-mnc480-zh-rTW/strings.xml b/Tethering/res/values-mcc311-mnc480-zh-rTW/strings.xml
index cd653df..0d7ddf2 100644
--- a/Tethering/res/values-mcc311-mnc480-zh-rTW/strings.xml
+++ b/Tethering/res/values-mcc311-mnc480-zh-rTW/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="611650570559011140">"無法透過網路共用連上網際網路"</string>
-    <string name="no_upstream_notification_message" msgid="6508394877641864863">"裝置無法連線"</string>
-    <string name="no_upstream_notification_disable_button" msgid="7609346639290990508">"關閉網路共用"</string>
-    <string name="upstream_roaming_notification_title" msgid="6032901176124830787">"無線基地台或網路共用已開啟"</string>
-    <string name="upstream_roaming_notification_message" msgid="7599056263326217523">"使用漫遊服務可能須支付額外費用"</string>
+    <string name="no_upstream_notification_title" msgid="5897815458155858594">"無法透過網路共用連上網際網路"</string>
+    <string name="no_upstream_notification_message" msgid="9037716118606459874">"裝置無法連線"</string>
+    <string name="no_upstream_notification_disable_button" msgid="5284024068281565456">"關閉網路共用"</string>
+    <string name="upstream_roaming_notification_title" msgid="186331286017243006">"無線基地台或網路共用已開啟"</string>
+    <string name="upstream_roaming_notification_message" msgid="7692641323940316538">"使用漫遊服務可能須支付額外費用"</string>
 </resources>
diff --git a/Tethering/res/values-mcc311-mnc480-zu/strings.xml b/Tethering/res/values-mcc311-mnc480-zu/strings.xml
index 32f6df5..d18f079 100644
--- a/Tethering/res/values-mcc311-mnc480-zu/strings.xml
+++ b/Tethering/res/values-mcc311-mnc480-zu/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="no_upstream_notification_title" msgid="611650570559011140">"Ukusebenzisa ifoni njengemodemu akunayo i-inthanethi"</string>
-    <string name="no_upstream_notification_message" msgid="6508394877641864863">"Amadivayisi awakwazi ukuxhumeka"</string>
-    <string name="no_upstream_notification_disable_button" msgid="7609346639290990508">"Vala ukusebenzisa ifoni njengemodemu"</string>
-    <string name="upstream_roaming_notification_title" msgid="6032901176124830787">"I-hotspot noma ukusebenzisa ifoni njengemodemu kuvuliwe"</string>
-    <string name="upstream_roaming_notification_message" msgid="7599056263326217523">"Kungaba nezinkokhelo ezengeziwe uma uzula"</string>
+    <string name="no_upstream_notification_title" msgid="5897815458155858594">"Ukusebenzisa ifoni njengemodemu akunayo i-inthanethi"</string>
+    <string name="no_upstream_notification_message" msgid="9037716118606459874">"Amadivayisi awakwazi ukuxhuma"</string>
+    <string name="no_upstream_notification_disable_button" msgid="5284024068281565456">"Vala ukusebenzisa ifoni njengemodemu"</string>
+    <string name="upstream_roaming_notification_title" msgid="186331286017243006">"I-hotspot noma ukusebenzisa ifoni njengemodemu kuvuliwe"</string>
+    <string name="upstream_roaming_notification_message" msgid="7692641323940316538">"Kungaba nezinkokhelo ezengeziwe uma uzula"</string>
 </resources>
diff --git a/Tethering/res/values-mk/strings.xml b/Tethering/res/values-mk/strings.xml
index 9ad9b9a..f1b15e6 100644
--- a/Tethering/res/values-mk/strings.xml
+++ b/Tethering/res/values-mk/strings.xml
@@ -16,14 +16,14 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="tethered_notification_title" msgid="6426563586025792944">"Активно е врзување или точка на пристап"</string>
-    <string name="tethered_notification_message" msgid="64800879503420696">"Допрете за поставување."</string>
-    <string name="disable_tether_notification_title" msgid="3004509127903564191">"Врзувањето е оневозможено"</string>
-    <string name="disable_tether_notification_message" msgid="6717523799293901476">"Контактирајте со администраторот за детали"</string>
-    <string name="notification_channel_tethering_status" msgid="2663463891530932727">"Статус на точката на пристап и врзувањето"</string>
-    <string name="no_upstream_notification_title" msgid="1204601824631788482"></string>
-    <string name="no_upstream_notification_message" msgid="8586582938243032621"></string>
-    <string name="no_upstream_notification_disable_button" msgid="8800919436924640822"></string>
-    <string name="upstream_roaming_notification_title" msgid="4772373823198997030"></string>
-    <string name="upstream_roaming_notification_message" msgid="3985577843181551650"></string>
+    <string name="tethered_notification_title" msgid="5350162111436634622">"Активно: интернет преку мобилен или точка на пристап"</string>
+    <string name="tethered_notification_message" msgid="2338023450330652098">"Допрете за поставување."</string>
+    <string name="disable_tether_notification_title" msgid="3183576627492925522">"Интернетот преку мобилен е оневозможен"</string>
+    <string name="disable_tether_notification_message" msgid="6655882039707534929">"За детали, контактирајте со администраторот"</string>
+    <string name="notification_channel_tethering_status" msgid="7030733422705019001">"Статус на точка на пристап и интернет преку мобилен"</string>
+    <string name="no_upstream_notification_title" msgid="2052743091868702475"></string>
+    <string name="no_upstream_notification_message" msgid="6932020551635470134"></string>
+    <string name="no_upstream_notification_disable_button" msgid="8836277213343697023"></string>
+    <string name="upstream_roaming_notification_title" msgid="8614262557406849762"></string>
+    <string name="upstream_roaming_notification_message" msgid="5999740876323106599"></string>
 </resources>
diff --git a/Tethering/res/values-ml/strings.xml b/Tethering/res/values-ml/strings.xml
index 9db79ce..8182b11 100644
--- a/Tethering/res/values-ml/strings.xml
+++ b/Tethering/res/values-ml/strings.xml
@@ -16,14 +16,14 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="tethered_notification_title" msgid="6426563586025792944">"ടെതറിംഗ് അല്ലെങ്കിൽ ഹോട്ട്സ്‌പോട്ട് സജീവമാണ്"</string>
-    <string name="tethered_notification_message" msgid="64800879503420696">"സജ്ജീകരിക്കാൻ ടാപ്പ് ചെയ്യുക."</string>
-    <string name="disable_tether_notification_title" msgid="3004509127903564191">"ടെതറിംഗ് പ്രവർത്തനരഹിതമാക്കിയിരിക്കുന്നു"</string>
-    <string name="disable_tether_notification_message" msgid="6717523799293901476">"വിശദാംശങ്ങൾക്ക് നിങ്ങളുടെ അഡ്മിനെ ബന്ധപ്പെടുക"</string>
-    <string name="notification_channel_tethering_status" msgid="2663463891530932727">"ഹോട്ട്‌സ്പോട്ടിന്റെയും ടെതറിംഗിന്റെയും നില"</string>
-    <string name="no_upstream_notification_title" msgid="1204601824631788482"></string>
-    <string name="no_upstream_notification_message" msgid="8586582938243032621"></string>
-    <string name="no_upstream_notification_disable_button" msgid="8800919436924640822"></string>
-    <string name="upstream_roaming_notification_title" msgid="4772373823198997030"></string>
-    <string name="upstream_roaming_notification_message" msgid="3985577843181551650"></string>
+    <string name="tethered_notification_title" msgid="5350162111436634622">"ടെതറിംഗ് അല്ലെങ്കിൽ ഹോട്ട്സ്‌പോട്ട് സജീവമാണ്"</string>
+    <string name="tethered_notification_message" msgid="2338023450330652098">"സജ്ജീകരിക്കാൻ ടാപ്പ് ചെയ്യുക."</string>
+    <string name="disable_tether_notification_title" msgid="3183576627492925522">"ടെതറിംഗ് പ്രവർത്തനരഹിതമാക്കിയിരിക്കുന്നു"</string>
+    <string name="disable_tether_notification_message" msgid="6655882039707534929">"വിശദാംശങ്ങൾക്ക് നിങ്ങളുടെ അഡ്മിനെ ബന്ധപ്പെടുക"</string>
+    <string name="notification_channel_tethering_status" msgid="7030733422705019001">"ഹോട്ട്‌സ്പോട്ടിന്റെയും ടെതറിംഗിന്റെയും നില"</string>
+    <string name="no_upstream_notification_title" msgid="2052743091868702475"></string>
+    <string name="no_upstream_notification_message" msgid="6932020551635470134"></string>
+    <string name="no_upstream_notification_disable_button" msgid="8836277213343697023"></string>
+    <string name="upstream_roaming_notification_title" msgid="8614262557406849762"></string>
+    <string name="upstream_roaming_notification_message" msgid="5999740876323106599"></string>
 </resources>
diff --git a/Tethering/res/values-mn/strings.xml b/Tethering/res/values-mn/strings.xml
index 42d1edb..a9aef5c 100644
--- a/Tethering/res/values-mn/strings.xml
+++ b/Tethering/res/values-mn/strings.xml
@@ -16,14 +16,14 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="tethered_notification_title" msgid="6426563586025792944">"Модем болгох эсвэл сүлжээний цэг идэвхтэй байна"</string>
-    <string name="tethered_notification_message" msgid="64800879503420696">"Тохируулахын тулд товшино уу."</string>
-    <string name="disable_tether_notification_title" msgid="3004509127903564191">"Модем болгохыг идэвхгүй болгосон"</string>
-    <string name="disable_tether_notification_message" msgid="6717523799293901476">"Дэлгэрэнгүй мэдээлэл авахын тулд админтайгаа холбогдоно уу"</string>
-    <string name="notification_channel_tethering_status" msgid="2663463891530932727">"Сүлжээний цэг болон модем болгох төлөв"</string>
-    <string name="no_upstream_notification_title" msgid="1204601824631788482"></string>
-    <string name="no_upstream_notification_message" msgid="8586582938243032621"></string>
-    <string name="no_upstream_notification_disable_button" msgid="8800919436924640822"></string>
-    <string name="upstream_roaming_notification_title" msgid="4772373823198997030"></string>
-    <string name="upstream_roaming_notification_message" msgid="3985577843181551650"></string>
+    <string name="tethered_notification_title" msgid="5350162111436634622">"Модем болгох эсвэл сүлжээний цэг идэвхтэй байна"</string>
+    <string name="tethered_notification_message" msgid="2338023450330652098">"Тохируулахын тулд товшино уу."</string>
+    <string name="disable_tether_notification_title" msgid="3183576627492925522">"Модем болгохыг идэвхгүй болгосон"</string>
+    <string name="disable_tether_notification_message" msgid="6655882039707534929">"Дэлгэрэнгүй мэдээлэл авах бол админтайгаа холбогдоно уу"</string>
+    <string name="notification_channel_tethering_status" msgid="7030733422705019001">"Сүлжээний цэг болон модем болгохын төлөв"</string>
+    <string name="no_upstream_notification_title" msgid="2052743091868702475"></string>
+    <string name="no_upstream_notification_message" msgid="6932020551635470134"></string>
+    <string name="no_upstream_notification_disable_button" msgid="8836277213343697023"></string>
+    <string name="upstream_roaming_notification_title" msgid="8614262557406849762"></string>
+    <string name="upstream_roaming_notification_message" msgid="5999740876323106599"></string>
 </resources>
diff --git a/Tethering/res/values-mr/strings.xml b/Tethering/res/values-mr/strings.xml
index 13995b6..d49cc61 100644
--- a/Tethering/res/values-mr/strings.xml
+++ b/Tethering/res/values-mr/strings.xml
@@ -16,14 +16,14 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="tethered_notification_title" msgid="6426563586025792944">"टेदरिंग किंवा हॉटस्पॉट अ‍ॅक्टिव्ह आहे"</string>
-    <string name="tethered_notification_message" msgid="64800879503420696">"सेट करण्यासाठी टॅप करा."</string>
-    <string name="disable_tether_notification_title" msgid="3004509127903564191">"टेदरिंग बंद केले आहे"</string>
-    <string name="disable_tether_notification_message" msgid="6717523799293901476">"तपशीलांसाठी तुमच्या ॲडमिनशी संपर्क साधा"</string>
-    <string name="notification_channel_tethering_status" msgid="2663463891530932727">"हॉटस्पॉट आणि टेदरिंगची स्थिती"</string>
-    <string name="no_upstream_notification_title" msgid="1204601824631788482"></string>
-    <string name="no_upstream_notification_message" msgid="8586582938243032621"></string>
-    <string name="no_upstream_notification_disable_button" msgid="8800919436924640822"></string>
-    <string name="upstream_roaming_notification_title" msgid="4772373823198997030"></string>
-    <string name="upstream_roaming_notification_message" msgid="3985577843181551650"></string>
+    <string name="tethered_notification_title" msgid="5350162111436634622">"टेदरिंग किंवा हॉटस्पॉट अ‍ॅक्टिव्ह आहे"</string>
+    <string name="tethered_notification_message" msgid="2338023450330652098">"सेट करण्यासाठी टॅप करा."</string>
+    <string name="disable_tether_notification_title" msgid="3183576627492925522">"टेदरिंग बंद केले आहे"</string>
+    <string name="disable_tether_notification_message" msgid="6655882039707534929">"तपशिलांसाठी तुमच्या ॲडमिनशी संपर्क साधा"</string>
+    <string name="notification_channel_tethering_status" msgid="7030733422705019001">"हॉटस्पॉट &amp; टेदरिंग स्टेटस"</string>
+    <string name="no_upstream_notification_title" msgid="2052743091868702475"></string>
+    <string name="no_upstream_notification_message" msgid="6932020551635470134"></string>
+    <string name="no_upstream_notification_disable_button" msgid="8836277213343697023"></string>
+    <string name="upstream_roaming_notification_title" msgid="8614262557406849762"></string>
+    <string name="upstream_roaming_notification_message" msgid="5999740876323106599"></string>
 </resources>
diff --git a/Tethering/res/values-ms/strings.xml b/Tethering/res/values-ms/strings.xml
index d6a67f3..bc7aab3 100644
--- a/Tethering/res/values-ms/strings.xml
+++ b/Tethering/res/values-ms/strings.xml
@@ -16,14 +16,14 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="tethered_notification_title" msgid="6426563586025792944">"Penambatan atau tempat liputan aktif"</string>
-    <string name="tethered_notification_message" msgid="64800879503420696">"Ketik untuk membuat persediaan."</string>
-    <string name="disable_tether_notification_title" msgid="3004509127903564191">"Penambatan dilumpuhkan"</string>
-    <string name="disable_tether_notification_message" msgid="6717523799293901476">"Hubungi pentadbir anda untuk mendapatkan maklumat lanjut"</string>
-    <string name="notification_channel_tethering_status" msgid="2663463891530932727">"Status tempat liputan &amp; penambatan"</string>
-    <string name="no_upstream_notification_title" msgid="1204601824631788482"></string>
-    <string name="no_upstream_notification_message" msgid="8586582938243032621"></string>
-    <string name="no_upstream_notification_disable_button" msgid="8800919436924640822"></string>
-    <string name="upstream_roaming_notification_title" msgid="4772373823198997030"></string>
-    <string name="upstream_roaming_notification_message" msgid="3985577843181551650"></string>
+    <string name="tethered_notification_title" msgid="5350162111436634622">"Penambatan atau tempat liputan aktif"</string>
+    <string name="tethered_notification_message" msgid="2338023450330652098">"Ketik untuk membuat persediaan."</string>
+    <string name="disable_tether_notification_title" msgid="3183576627492925522">"Penambatan dilumpuhkan"</string>
+    <string name="disable_tether_notification_message" msgid="6655882039707534929">"Hubungi pentadbir anda untuk mendapatkan maklumat lanjut"</string>
+    <string name="notification_channel_tethering_status" msgid="7030733422705019001">"Status tempat liputan &amp; penambatan"</string>
+    <string name="no_upstream_notification_title" msgid="2052743091868702475"></string>
+    <string name="no_upstream_notification_message" msgid="6932020551635470134"></string>
+    <string name="no_upstream_notification_disable_button" msgid="8836277213343697023"></string>
+    <string name="upstream_roaming_notification_title" msgid="8614262557406849762"></string>
+    <string name="upstream_roaming_notification_message" msgid="5999740876323106599"></string>
 </resources>
diff --git a/Tethering/res/values-my/strings.xml b/Tethering/res/values-my/strings.xml
index 49f6b88..4f40423 100644
--- a/Tethering/res/values-my/strings.xml
+++ b/Tethering/res/values-my/strings.xml
@@ -16,14 +16,14 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="tethered_notification_title" msgid="6426563586025792944">"မိုဘိုင်းဖုန်းသုံး ချိတ်ဆက်မျှဝေခြင်း သို့မဟုတ် ဟော့စပေါ့ ဖွင့်ထားသည်"</string>
-    <string name="tethered_notification_message" msgid="64800879503420696">"စနစ်ထည့်သွင်းရန် တို့ပါ။"</string>
-    <string name="disable_tether_notification_title" msgid="3004509127903564191">"မိုဘိုင်းဖုန်းသုံး ချိတ်ဆက်မျှဝေခြင်းကို ပိတ်ထားသည်"</string>
-    <string name="disable_tether_notification_message" msgid="6717523799293901476">"အသေးစိတ်အတွက် သင့်စီမံခန့်ခွဲသူကို ဆက်သွယ်ပါ"</string>
-    <string name="notification_channel_tethering_status" msgid="2663463891530932727">"ဟော့စပေါ့နှင့် မိုဘိုင်းဖုန်းသုံး ချိတ်ဆက်မျှဝေခြင်း အခြေအနေ"</string>
-    <string name="no_upstream_notification_title" msgid="1204601824631788482"></string>
-    <string name="no_upstream_notification_message" msgid="8586582938243032621"></string>
-    <string name="no_upstream_notification_disable_button" msgid="8800919436924640822"></string>
-    <string name="upstream_roaming_notification_title" msgid="4772373823198997030"></string>
-    <string name="upstream_roaming_notification_message" msgid="3985577843181551650"></string>
+    <string name="tethered_notification_title" msgid="5350162111436634622">"မိုဘိုင်းသုံး၍ ချိတ်ဆက်ခြင်း (သို့) ဟော့စပေါ့ ဖွင့်ထားသည်"</string>
+    <string name="tethered_notification_message" msgid="2338023450330652098">"စနစ်ထည့်သွင်းရန် တို့ပါ။"</string>
+    <string name="disable_tether_notification_title" msgid="3183576627492925522">"မိုဘိုင်းသုံး၍ ချိတ်ဆက်ခြင်း ပိတ်ထားသည်"</string>
+    <string name="disable_tether_notification_message" msgid="6655882039707534929">"အသေးစိတ်သိရန် သင့်စီမံခန့်ခွဲသူထံ ဆက်သွယ်ပါ"</string>
+    <string name="notification_channel_tethering_status" msgid="7030733422705019001">"ဟော့စပေါ့နှင့် မိုဘိုင်းသုံး၍ ချိတ်ဆက်ခြင်း အခြေအနေ"</string>
+    <string name="no_upstream_notification_title" msgid="2052743091868702475"></string>
+    <string name="no_upstream_notification_message" msgid="6932020551635470134"></string>
+    <string name="no_upstream_notification_disable_button" msgid="8836277213343697023"></string>
+    <string name="upstream_roaming_notification_title" msgid="8614262557406849762"></string>
+    <string name="upstream_roaming_notification_message" msgid="5999740876323106599"></string>
 </resources>
diff --git a/Tethering/res/values-nb/strings.xml b/Tethering/res/values-nb/strings.xml
index 9594e0a..e9024c0 100644
--- a/Tethering/res/values-nb/strings.xml
+++ b/Tethering/res/values-nb/strings.xml
@@ -16,14 +16,14 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="tethered_notification_title" msgid="6426563586025792944">"Internettdeling eller Wi-Fi-sone er aktiv"</string>
-    <string name="tethered_notification_message" msgid="64800879503420696">"Trykk for å konfigurere."</string>
-    <string name="disable_tether_notification_title" msgid="3004509127903564191">"Internettdeling er slått av"</string>
-    <string name="disable_tether_notification_message" msgid="6717523799293901476">"Ta kontakt med administratoren din for å få mer informasjon"</string>
-    <string name="notification_channel_tethering_status" msgid="2663463891530932727">"Status for Wi-Fi-sone og internettdeling"</string>
-    <string name="no_upstream_notification_title" msgid="1204601824631788482"></string>
-    <string name="no_upstream_notification_message" msgid="8586582938243032621"></string>
-    <string name="no_upstream_notification_disable_button" msgid="8800919436924640822"></string>
-    <string name="upstream_roaming_notification_title" msgid="4772373823198997030"></string>
-    <string name="upstream_roaming_notification_message" msgid="3985577843181551650"></string>
+    <string name="tethered_notification_title" msgid="5350162111436634622">"Internettdeling eller wifi-sone er aktiv"</string>
+    <string name="tethered_notification_message" msgid="2338023450330652098">"Trykk for å konfigurere."</string>
+    <string name="disable_tether_notification_title" msgid="3183576627492925522">"Internettdeling er slått av"</string>
+    <string name="disable_tether_notification_message" msgid="6655882039707534929">"Kontakt administratoren din for å få mer informasjon"</string>
+    <string name="notification_channel_tethering_status" msgid="7030733422705019001">"Status for wifi-sone og internettdeling"</string>
+    <string name="no_upstream_notification_title" msgid="2052743091868702475"></string>
+    <string name="no_upstream_notification_message" msgid="6932020551635470134"></string>
+    <string name="no_upstream_notification_disable_button" msgid="8836277213343697023"></string>
+    <string name="upstream_roaming_notification_title" msgid="8614262557406849762"></string>
+    <string name="upstream_roaming_notification_message" msgid="5999740876323106599"></string>
 </resources>
diff --git a/Tethering/res/values-ne/strings.xml b/Tethering/res/values-ne/strings.xml
index 72ae3a8..988d5c2 100644
--- a/Tethering/res/values-ne/strings.xml
+++ b/Tethering/res/values-ne/strings.xml
@@ -16,14 +16,14 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="tethered_notification_title" msgid="6426563586025792944">"टेदरिङ वा हटस्पट सक्रिय छ"</string>
-    <string name="tethered_notification_message" msgid="64800879503420696">"सेटअप गर्न ट्याप गर्नुहोस्।"</string>
-    <string name="disable_tether_notification_title" msgid="3004509127903564191">"टेदरिङ सुविधा असक्षम पारिएको छ"</string>
-    <string name="disable_tether_notification_message" msgid="6717523799293901476">"विवरणहरूका लागि आफ्ना प्रशासकलाई सम्पर्क गर्नुहोस्"</string>
-    <string name="notification_channel_tethering_status" msgid="2663463891530932727">"हटस्पट तथा टेदरिङको स्थिति"</string>
-    <string name="no_upstream_notification_title" msgid="1204601824631788482"></string>
-    <string name="no_upstream_notification_message" msgid="8586582938243032621"></string>
-    <string name="no_upstream_notification_disable_button" msgid="8800919436924640822"></string>
-    <string name="upstream_roaming_notification_title" msgid="4772373823198997030"></string>
-    <string name="upstream_roaming_notification_message" msgid="3985577843181551650"></string>
+    <string name="tethered_notification_title" msgid="5350162111436634622">"टेदरिङ वा हटस्पट अन छ"</string>
+    <string name="tethered_notification_message" msgid="2338023450330652098">"सेटअप गर्न ट्याप गर्नुहोस्।"</string>
+    <string name="disable_tether_notification_title" msgid="3183576627492925522">"टेदरिङ सुविधा अफ गरिएको छ"</string>
+    <string name="disable_tether_notification_message" msgid="6655882039707534929">"विस्तृत जानकारीका लागि एड्मिनलाई सम्पर्क गर्नुहोस्"</string>
+    <string name="notification_channel_tethering_status" msgid="7030733422705019001">"हटस्पट तथा टेदरिङको स्थिति"</string>
+    <string name="no_upstream_notification_title" msgid="2052743091868702475"></string>
+    <string name="no_upstream_notification_message" msgid="6932020551635470134"></string>
+    <string name="no_upstream_notification_disable_button" msgid="8836277213343697023"></string>
+    <string name="upstream_roaming_notification_title" msgid="8614262557406849762"></string>
+    <string name="upstream_roaming_notification_message" msgid="5999740876323106599"></string>
 </resources>
diff --git a/Tethering/res/values-nl/strings.xml b/Tethering/res/values-nl/strings.xml
index 18b2bbf..d6a0a1a 100644
--- a/Tethering/res/values-nl/strings.xml
+++ b/Tethering/res/values-nl/strings.xml
@@ -16,14 +16,14 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="tethered_notification_title" msgid="6426563586025792944">"Tethering of hotspot actief"</string>
-    <string name="tethered_notification_message" msgid="64800879503420696">"Tik om in te stellen."</string>
-    <string name="disable_tether_notification_title" msgid="3004509127903564191">"Tethering is uitgeschakeld"</string>
-    <string name="disable_tether_notification_message" msgid="6717523799293901476">"Neem contact op met je beheerder voor meer informatie"</string>
-    <string name="notification_channel_tethering_status" msgid="2663463891530932727">"Status van hotspot en tethering"</string>
-    <string name="no_upstream_notification_title" msgid="1204601824631788482"></string>
-    <string name="no_upstream_notification_message" msgid="8586582938243032621"></string>
-    <string name="no_upstream_notification_disable_button" msgid="8800919436924640822"></string>
-    <string name="upstream_roaming_notification_title" msgid="4772373823198997030"></string>
-    <string name="upstream_roaming_notification_message" msgid="3985577843181551650"></string>
+    <string name="tethered_notification_title" msgid="5350162111436634622">"Tethering of hotspot actief"</string>
+    <string name="tethered_notification_message" msgid="2338023450330652098">"Tik om in te stellen."</string>
+    <string name="disable_tether_notification_title" msgid="3183576627492925522">"Tethering staat uit"</string>
+    <string name="disable_tether_notification_message" msgid="6655882039707534929">"Neem contact op met je beheerder voor meer informatie"</string>
+    <string name="notification_channel_tethering_status" msgid="7030733422705019001">"Status van hotspot en tethering"</string>
+    <string name="no_upstream_notification_title" msgid="2052743091868702475"></string>
+    <string name="no_upstream_notification_message" msgid="6932020551635470134"></string>
+    <string name="no_upstream_notification_disable_button" msgid="8836277213343697023"></string>
+    <string name="upstream_roaming_notification_title" msgid="8614262557406849762"></string>
+    <string name="upstream_roaming_notification_message" msgid="5999740876323106599"></string>
 </resources>
diff --git a/Tethering/res/values-or/strings.xml b/Tethering/res/values-or/strings.xml
index a15a6db..9abca6c 100644
--- a/Tethering/res/values-or/strings.xml
+++ b/Tethering/res/values-or/strings.xml
@@ -16,14 +16,14 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="tethered_notification_title" msgid="6426563586025792944">"ଟିଥେରିଂ କିମ୍ୱା ହଟସ୍ପଟ୍ ସକ୍ରିୟ ଅଛି"</string>
-    <string name="tethered_notification_message" msgid="64800879503420696">"ସେଟ୍ ଅପ୍ କରିବାକୁ ଟାପ୍ କରନ୍ତୁ।"</string>
-    <string name="disable_tether_notification_title" msgid="3004509127903564191">"ଟିଥେରିଂ ଅକ୍ଷମ କରାଯାଇଛି"</string>
-    <string name="disable_tether_notification_message" msgid="6717523799293901476">"ବିବରଣୀଗୁଡ଼ିକ ପାଇଁ ଆପଣଙ୍କ ଆଡମିନଙ୍କ ସହ ଯୋଗାଯୋଗ କରନ୍ତୁ"</string>
-    <string name="notification_channel_tethering_status" msgid="2663463891530932727">"ହଟସ୍ପଟ୍ ଓ ଟିଥେରିଂ ସ୍ଥିତି"</string>
-    <string name="no_upstream_notification_title" msgid="1204601824631788482"></string>
-    <string name="no_upstream_notification_message" msgid="8586582938243032621"></string>
-    <string name="no_upstream_notification_disable_button" msgid="8800919436924640822"></string>
-    <string name="upstream_roaming_notification_title" msgid="4772373823198997030"></string>
-    <string name="upstream_roaming_notification_message" msgid="3985577843181551650"></string>
+    <string name="tethered_notification_title" msgid="5350162111436634622">"ଟିଥରିଂ କିମ୍ବା ହଟସ୍ପଟ ସକ୍ରିୟ ଅଛି"</string>
+    <string name="tethered_notification_message" msgid="2338023450330652098">"ସେଟ ଅପ କରିବାକୁ ଟାପ କରନ୍ତୁ।"</string>
+    <string name="disable_tether_notification_title" msgid="3183576627492925522">"ଟିଥରିଂକୁ ଅକ୍ଷମ କରାଯାଇଛି"</string>
+    <string name="disable_tether_notification_message" msgid="6655882039707534929">"ବିବରଣୀ ପାଇଁ ଆପଣଙ୍କ ଆଡମିନଙ୍କ ସହ କଣ୍ଟାକ୍ଟ କରନ୍ତୁ"</string>
+    <string name="notification_channel_tethering_status" msgid="7030733422705019001">"ହଟସ୍ପଟ ଏବଂ ଟିଥରିଂ ସ୍ଥିତି"</string>
+    <string name="no_upstream_notification_title" msgid="2052743091868702475"></string>
+    <string name="no_upstream_notification_message" msgid="6932020551635470134"></string>
+    <string name="no_upstream_notification_disable_button" msgid="8836277213343697023"></string>
+    <string name="upstream_roaming_notification_title" msgid="8614262557406849762"></string>
+    <string name="upstream_roaming_notification_message" msgid="5999740876323106599"></string>
 </resources>
diff --git a/Tethering/res/values-pa/strings.xml b/Tethering/res/values-pa/strings.xml
index a8235e4..bcd1c14 100644
--- a/Tethering/res/values-pa/strings.xml
+++ b/Tethering/res/values-pa/strings.xml
@@ -16,14 +16,14 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="tethered_notification_title" msgid="6426563586025792944">"ਟੈਦਰਿੰਗ ਜਾਂ ਹੌਟਸਪੌਟ ਕਿਰਿਆਸ਼ੀਲ"</string>
-    <string name="tethered_notification_message" msgid="64800879503420696">"ਸੈੱਟਅੱਪ ਕਰਨ ਲਈ ਟੈਪ ਕਰੋ।"</string>
-    <string name="disable_tether_notification_title" msgid="3004509127903564191">"ਟੈਦਰਿੰਗ ਨੂੰ ਬੰਦ ਕੀਤਾ ਗਿਆ ਹੈ"</string>
-    <string name="disable_tether_notification_message" msgid="6717523799293901476">"ਵੇਰਵਿਆਂ ਲਈ ਆਪਣੇ ਪ੍ਰਸ਼ਾਸਕ ਨਾਲ ਸੰਪਰਕ ਕਰੋ"</string>
-    <string name="notification_channel_tethering_status" msgid="2663463891530932727">"ਹੌਟਸਪੌਟ ਅਤੇ ਟੈਦਰਿੰਗ ਦੀ ਸਥਿਤੀ"</string>
-    <string name="no_upstream_notification_title" msgid="1204601824631788482"></string>
-    <string name="no_upstream_notification_message" msgid="8586582938243032621"></string>
-    <string name="no_upstream_notification_disable_button" msgid="8800919436924640822"></string>
-    <string name="upstream_roaming_notification_title" msgid="4772373823198997030"></string>
-    <string name="upstream_roaming_notification_message" msgid="3985577843181551650"></string>
+    <string name="tethered_notification_title" msgid="5350162111436634622">"ਟੈਦਰਿੰਗ ਜਾਂ ਹੌਟਸਪੌਟ ਕਿਰਿਆਸ਼ੀਲ"</string>
+    <string name="tethered_notification_message" msgid="2338023450330652098">"ਸੈੱਟਅੱਪ ਕਰਨ ਲਈ ਟੈਪ ਕਰੋ।"</string>
+    <string name="disable_tether_notification_title" msgid="3183576627492925522">"ਟੈਦਰਿੰਗ ਨੂੰ ਬੰਦ ਕੀਤਾ ਗਿਆ ਹੈ"</string>
+    <string name="disable_tether_notification_message" msgid="6655882039707534929">"ਵੇਰਵਿਆਂ ਲਈ ਆਪਣੇ ਪ੍ਰਸ਼ਾਸਕ ਨਾਲ ਸੰਪਰਕ ਕਰੋ"</string>
+    <string name="notification_channel_tethering_status" msgid="7030733422705019001">"ਹੌਟਸਪੌਟ ਅਤੇ ਟੈਦਰਿੰਗ ਦੀ ਸਥਿਤੀ"</string>
+    <string name="no_upstream_notification_title" msgid="2052743091868702475"></string>
+    <string name="no_upstream_notification_message" msgid="6932020551635470134"></string>
+    <string name="no_upstream_notification_disable_button" msgid="8836277213343697023"></string>
+    <string name="upstream_roaming_notification_title" msgid="8614262557406849762"></string>
+    <string name="upstream_roaming_notification_message" msgid="5999740876323106599"></string>
 </resources>
diff --git a/Tethering/res/values-pl/strings.xml b/Tethering/res/values-pl/strings.xml
index ccb017d..855afb4 100644
--- a/Tethering/res/values-pl/strings.xml
+++ b/Tethering/res/values-pl/strings.xml
@@ -16,14 +16,14 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="tethered_notification_title" msgid="6426563586025792944">"Aktywny tethering lub punkt dostępu"</string>
-    <string name="tethered_notification_message" msgid="64800879503420696">"Kliknij, by skonfigurować"</string>
-    <string name="disable_tether_notification_title" msgid="3004509127903564191">"Tethering został wyłączony"</string>
-    <string name="disable_tether_notification_message" msgid="6717523799293901476">"Aby uzyskać szczegółowe informacje, skontaktuj się z administratorem"</string>
-    <string name="notification_channel_tethering_status" msgid="2663463891530932727">"Hotspot i tethering – stan"</string>
-    <string name="no_upstream_notification_title" msgid="1204601824631788482"></string>
-    <string name="no_upstream_notification_message" msgid="8586582938243032621"></string>
-    <string name="no_upstream_notification_disable_button" msgid="8800919436924640822"></string>
-    <string name="upstream_roaming_notification_title" msgid="4772373823198997030"></string>
-    <string name="upstream_roaming_notification_message" msgid="3985577843181551650"></string>
+    <string name="tethered_notification_title" msgid="5350162111436634622">"Aktywny tethering lub hotspot"</string>
+    <string name="tethered_notification_message" msgid="2338023450330652098">"Kliknij, aby skonfigurować."</string>
+    <string name="disable_tether_notification_title" msgid="3183576627492925522">"Tethering jest wyłączony"</string>
+    <string name="disable_tether_notification_message" msgid="6655882039707534929">"Aby uzyskać szczegółowe informacje, skontaktuj się z administratorem"</string>
+    <string name="notification_channel_tethering_status" msgid="7030733422705019001">"Stan hotspotu i tetheringu"</string>
+    <string name="no_upstream_notification_title" msgid="2052743091868702475"></string>
+    <string name="no_upstream_notification_message" msgid="6932020551635470134"></string>
+    <string name="no_upstream_notification_disable_button" msgid="8836277213343697023"></string>
+    <string name="upstream_roaming_notification_title" msgid="8614262557406849762"></string>
+    <string name="upstream_roaming_notification_message" msgid="5999740876323106599"></string>
 </resources>
diff --git a/Tethering/res/values-pt-rBR/strings.xml b/Tethering/res/values-pt-rBR/strings.xml
index a0a4745..7e19f0e 100644
--- a/Tethering/res/values-pt-rBR/strings.xml
+++ b/Tethering/res/values-pt-rBR/strings.xml
@@ -16,14 +16,14 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="tethered_notification_title" msgid="6426563586025792944">"Ponto de acesso ou tethering ativo"</string>
-    <string name="tethered_notification_message" msgid="64800879503420696">"Toque para configurar."</string>
-    <string name="disable_tether_notification_title" msgid="3004509127903564191">"Tethering desativado"</string>
-    <string name="disable_tether_notification_message" msgid="6717523799293901476">"Fale com seu administrador para saber detalhes"</string>
-    <string name="notification_channel_tethering_status" msgid="2663463891530932727">"Status de ponto de acesso e tethering"</string>
-    <string name="no_upstream_notification_title" msgid="1204601824631788482"></string>
-    <string name="no_upstream_notification_message" msgid="8586582938243032621"></string>
-    <string name="no_upstream_notification_disable_button" msgid="8800919436924640822"></string>
-    <string name="upstream_roaming_notification_title" msgid="4772373823198997030"></string>
-    <string name="upstream_roaming_notification_message" msgid="3985577843181551650"></string>
+    <string name="tethered_notification_title" msgid="5350162111436634622">"Ponto de acesso ou tethering ativo"</string>
+    <string name="tethered_notification_message" msgid="2338023450330652098">"Toque para configurar."</string>
+    <string name="disable_tether_notification_title" msgid="3183576627492925522">"O tethering está desativado"</string>
+    <string name="disable_tether_notification_message" msgid="6655882039707534929">"Entre em contato com seu administrador para saber detalhes"</string>
+    <string name="notification_channel_tethering_status" msgid="7030733422705019001">"Status do ponto de acesso e do tethering"</string>
+    <string name="no_upstream_notification_title" msgid="2052743091868702475"></string>
+    <string name="no_upstream_notification_message" msgid="6932020551635470134"></string>
+    <string name="no_upstream_notification_disable_button" msgid="8836277213343697023"></string>
+    <string name="upstream_roaming_notification_title" msgid="8614262557406849762"></string>
+    <string name="upstream_roaming_notification_message" msgid="5999740876323106599"></string>
 </resources>
diff --git a/Tethering/res/values-pt-rPT/strings.xml b/Tethering/res/values-pt-rPT/strings.xml
index e3f03fc..ac8ea5c 100644
--- a/Tethering/res/values-pt-rPT/strings.xml
+++ b/Tethering/res/values-pt-rPT/strings.xml
@@ -16,14 +16,14 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="tethered_notification_title" msgid="6426563586025792944">"Ligação (à Internet) via telemóvel ou zona Wi-Fi ativas"</string>
-    <string name="tethered_notification_message" msgid="64800879503420696">"Toque para configurar."</string>
-    <string name="disable_tether_notification_title" msgid="3004509127903564191">"A ligação (à Internet) via telemóvel está desativada."</string>
-    <string name="disable_tether_notification_message" msgid="6717523799293901476">"Contacte o administrador para obter detalhes."</string>
-    <string name="notification_channel_tethering_status" msgid="2663463891530932727">"Estado da zona Wi-Fi e da ligação (à Internet) via telemóvel"</string>
-    <string name="no_upstream_notification_title" msgid="1204601824631788482"></string>
-    <string name="no_upstream_notification_message" msgid="8586582938243032621"></string>
-    <string name="no_upstream_notification_disable_button" msgid="8800919436924640822"></string>
-    <string name="upstream_roaming_notification_title" msgid="4772373823198997030"></string>
-    <string name="upstream_roaming_notification_message" msgid="3985577843181551650"></string>
+    <string name="tethered_notification_title" msgid="5350162111436634622">"Ligação (à Internet) via telemóvel ou zona Wi-Fi ativa"</string>
+    <string name="tethered_notification_message" msgid="2338023450330652098">"Toque para configurar."</string>
+    <string name="disable_tether_notification_title" msgid="3183576627492925522">"A ligação (à Internet) via telemóvel está desativada"</string>
+    <string name="disable_tether_notification_message" msgid="6655882039707534929">"Contacte o administrador para obter detalhes"</string>
+    <string name="notification_channel_tethering_status" msgid="7030733422705019001">"Estado da zona Wi-Fi e da ligação (à Internet) via telemóvel"</string>
+    <string name="no_upstream_notification_title" msgid="2052743091868702475"></string>
+    <string name="no_upstream_notification_message" msgid="6932020551635470134"></string>
+    <string name="no_upstream_notification_disable_button" msgid="8836277213343697023"></string>
+    <string name="upstream_roaming_notification_title" msgid="8614262557406849762"></string>
+    <string name="upstream_roaming_notification_message" msgid="5999740876323106599"></string>
 </resources>
diff --git a/Tethering/res/values-pt/strings.xml b/Tethering/res/values-pt/strings.xml
index a0a4745..7e19f0e 100644
--- a/Tethering/res/values-pt/strings.xml
+++ b/Tethering/res/values-pt/strings.xml
@@ -16,14 +16,14 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="tethered_notification_title" msgid="6426563586025792944">"Ponto de acesso ou tethering ativo"</string>
-    <string name="tethered_notification_message" msgid="64800879503420696">"Toque para configurar."</string>
-    <string name="disable_tether_notification_title" msgid="3004509127903564191">"Tethering desativado"</string>
-    <string name="disable_tether_notification_message" msgid="6717523799293901476">"Fale com seu administrador para saber detalhes"</string>
-    <string name="notification_channel_tethering_status" msgid="2663463891530932727">"Status de ponto de acesso e tethering"</string>
-    <string name="no_upstream_notification_title" msgid="1204601824631788482"></string>
-    <string name="no_upstream_notification_message" msgid="8586582938243032621"></string>
-    <string name="no_upstream_notification_disable_button" msgid="8800919436924640822"></string>
-    <string name="upstream_roaming_notification_title" msgid="4772373823198997030"></string>
-    <string name="upstream_roaming_notification_message" msgid="3985577843181551650"></string>
+    <string name="tethered_notification_title" msgid="5350162111436634622">"Ponto de acesso ou tethering ativo"</string>
+    <string name="tethered_notification_message" msgid="2338023450330652098">"Toque para configurar."</string>
+    <string name="disable_tether_notification_title" msgid="3183576627492925522">"O tethering está desativado"</string>
+    <string name="disable_tether_notification_message" msgid="6655882039707534929">"Entre em contato com seu administrador para saber detalhes"</string>
+    <string name="notification_channel_tethering_status" msgid="7030733422705019001">"Status do ponto de acesso e do tethering"</string>
+    <string name="no_upstream_notification_title" msgid="2052743091868702475"></string>
+    <string name="no_upstream_notification_message" msgid="6932020551635470134"></string>
+    <string name="no_upstream_notification_disable_button" msgid="8836277213343697023"></string>
+    <string name="upstream_roaming_notification_title" msgid="8614262557406849762"></string>
+    <string name="upstream_roaming_notification_message" msgid="5999740876323106599"></string>
 </resources>
diff --git a/Tethering/res/values-ro/strings.xml b/Tethering/res/values-ro/strings.xml
index 5706a4a..e022504 100644
--- a/Tethering/res/values-ro/strings.xml
+++ b/Tethering/res/values-ro/strings.xml
@@ -16,14 +16,14 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="tethered_notification_title" msgid="6426563586025792944">"Tethering sau hotspot activ"</string>
-    <string name="tethered_notification_message" msgid="64800879503420696">"Atingeți ca să configurați."</string>
-    <string name="disable_tether_notification_title" msgid="3004509127903564191">"Tetheringul este dezactivat"</string>
-    <string name="disable_tether_notification_message" msgid="6717523799293901476">"Contactați administratorul pentru detalii"</string>
-    <string name="notification_channel_tethering_status" msgid="2663463891530932727">"Starea hotspotului și a tetheringului"</string>
-    <string name="no_upstream_notification_title" msgid="1204601824631788482"></string>
-    <string name="no_upstream_notification_message" msgid="8586582938243032621"></string>
-    <string name="no_upstream_notification_disable_button" msgid="8800919436924640822"></string>
-    <string name="upstream_roaming_notification_title" msgid="4772373823198997030"></string>
-    <string name="upstream_roaming_notification_message" msgid="3985577843181551650"></string>
+    <string name="tethered_notification_title" msgid="5350162111436634622">"Tethering sau hotspot activ"</string>
+    <string name="tethered_notification_message" msgid="2338023450330652098">"Atinge pentru a configura."</string>
+    <string name="disable_tether_notification_title" msgid="3183576627492925522">"Tetheringul este dezactivat"</string>
+    <string name="disable_tether_notification_message" msgid="6655882039707534929">"Contactează administratorul pentru detalii"</string>
+    <string name="notification_channel_tethering_status" msgid="7030733422705019001">"Starea hotspotului și a tetheringului"</string>
+    <string name="no_upstream_notification_title" msgid="2052743091868702475"></string>
+    <string name="no_upstream_notification_message" msgid="6932020551635470134"></string>
+    <string name="no_upstream_notification_disable_button" msgid="8836277213343697023"></string>
+    <string name="upstream_roaming_notification_title" msgid="8614262557406849762"></string>
+    <string name="upstream_roaming_notification_message" msgid="5999740876323106599"></string>
 </resources>
diff --git a/Tethering/res/values-ru/strings.xml b/Tethering/res/values-ru/strings.xml
index 7cb6f7d..4361d70 100644
--- a/Tethering/res/values-ru/strings.xml
+++ b/Tethering/res/values-ru/strings.xml
@@ -16,14 +16,14 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="tethered_notification_title" msgid="6426563586025792944">"Включен режим модема или точка доступа"</string>
-    <string name="tethered_notification_message" msgid="64800879503420696">"Нажмите, чтобы настроить."</string>
-    <string name="disable_tether_notification_title" msgid="3004509127903564191">"Использование телефона в качестве модема запрещено"</string>
-    <string name="disable_tether_notification_message" msgid="6717523799293901476">"Чтобы узнать подробности, обратитесь к администратору."</string>
-    <string name="notification_channel_tethering_status" msgid="2663463891530932727">"Статус хот-спота и режима модема"</string>
-    <string name="no_upstream_notification_title" msgid="1204601824631788482"></string>
-    <string name="no_upstream_notification_message" msgid="8586582938243032621"></string>
-    <string name="no_upstream_notification_disable_button" msgid="8800919436924640822"></string>
-    <string name="upstream_roaming_notification_title" msgid="4772373823198997030"></string>
-    <string name="upstream_roaming_notification_message" msgid="3985577843181551650"></string>
+    <string name="tethered_notification_title" msgid="5350162111436634622">"Включен режим модема или точка доступа"</string>
+    <string name="tethered_notification_message" msgid="2338023450330652098">"Нажмите, чтобы настроить."</string>
+    <string name="disable_tether_notification_title" msgid="3183576627492925522">"Включить режим модема нельзя"</string>
+    <string name="disable_tether_notification_message" msgid="6655882039707534929">"Чтобы узнать больше, обратитесь к администратору."</string>
+    <string name="notification_channel_tethering_status" msgid="7030733422705019001">"Статус точки доступа и режима модема"</string>
+    <string name="no_upstream_notification_title" msgid="2052743091868702475"></string>
+    <string name="no_upstream_notification_message" msgid="6932020551635470134"></string>
+    <string name="no_upstream_notification_disable_button" msgid="8836277213343697023"></string>
+    <string name="upstream_roaming_notification_title" msgid="8614262557406849762"></string>
+    <string name="upstream_roaming_notification_message" msgid="5999740876323106599"></string>
 </resources>
diff --git a/Tethering/res/values-si/strings.xml b/Tethering/res/values-si/strings.xml
index ec34c22..14f30e9 100644
--- a/Tethering/res/values-si/strings.xml
+++ b/Tethering/res/values-si/strings.xml
@@ -16,14 +16,14 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="tethered_notification_title" msgid="6426563586025792944">"ටෙදරින් හෝ හොට්ස්පොට් සක්‍රීයයි"</string>
-    <string name="tethered_notification_message" msgid="64800879503420696">"පිහිටුවීමට තට්ටු කරන්න."</string>
-    <string name="disable_tether_notification_title" msgid="3004509127903564191">"ටෙදරින් අබල කර ඇත"</string>
-    <string name="disable_tether_notification_message" msgid="6717523799293901476">"විස්තර සඳහා ඔබගේ පරිපාලක අමතන්න"</string>
-    <string name="notification_channel_tethering_status" msgid="2663463891530932727">"හොට්ස්පොට් &amp; ටෙදරින් තත්ත්වය"</string>
-    <string name="no_upstream_notification_title" msgid="1204601824631788482"></string>
-    <string name="no_upstream_notification_message" msgid="8586582938243032621"></string>
-    <string name="no_upstream_notification_disable_button" msgid="8800919436924640822"></string>
-    <string name="upstream_roaming_notification_title" msgid="4772373823198997030"></string>
-    <string name="upstream_roaming_notification_message" msgid="3985577843181551650"></string>
+    <string name="tethered_notification_title" msgid="5350162111436634622">"ටෙදරින් හෝ හොට්ස්පොට් සක්‍රියයි"</string>
+    <string name="tethered_notification_message" msgid="2338023450330652098">"පිහිටුවීමට තට්ටු කරන්න."</string>
+    <string name="disable_tether_notification_title" msgid="3183576627492925522">"ටෙදරින් අබල කර ඇත"</string>
+    <string name="disable_tether_notification_message" msgid="6655882039707534929">"විස්තර සඳහා ඔබේ පරිපාලක අමතන්න"</string>
+    <string name="notification_channel_tethering_status" msgid="7030733422705019001">"හොට්ස්පොට් සහ ටෙදරින් තත්ත්වය"</string>
+    <string name="no_upstream_notification_title" msgid="2052743091868702475"></string>
+    <string name="no_upstream_notification_message" msgid="6932020551635470134"></string>
+    <string name="no_upstream_notification_disable_button" msgid="8836277213343697023"></string>
+    <string name="upstream_roaming_notification_title" msgid="8614262557406849762"></string>
+    <string name="upstream_roaming_notification_message" msgid="5999740876323106599"></string>
 </resources>
diff --git a/Tethering/res/values-sk/strings.xml b/Tethering/res/values-sk/strings.xml
index 43e787c..15845e7 100644
--- a/Tethering/res/values-sk/strings.xml
+++ b/Tethering/res/values-sk/strings.xml
@@ -16,14 +16,14 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="tethered_notification_title" msgid="6426563586025792944">"Tethering alebo prístupový bod je aktívny"</string>
-    <string name="tethered_notification_message" msgid="64800879503420696">"Klepnutím prejdete na nastavenie."</string>
-    <string name="disable_tether_notification_title" msgid="3004509127903564191">"Tethering je deaktivovaný"</string>
-    <string name="disable_tether_notification_message" msgid="6717523799293901476">"O podrobnosti požiadajte svojho správcu"</string>
-    <string name="notification_channel_tethering_status" msgid="2663463891530932727">"Stav hotspotu a tetheringu"</string>
-    <string name="no_upstream_notification_title" msgid="1204601824631788482"></string>
-    <string name="no_upstream_notification_message" msgid="8586582938243032621"></string>
-    <string name="no_upstream_notification_disable_button" msgid="8800919436924640822"></string>
-    <string name="upstream_roaming_notification_title" msgid="4772373823198997030"></string>
-    <string name="upstream_roaming_notification_message" msgid="3985577843181551650"></string>
+    <string name="tethered_notification_title" msgid="5350162111436634622">"Tethering alebo hotspot je aktívny"</string>
+    <string name="tethered_notification_message" msgid="2338023450330652098">"Nastavíte ho klepnutím."</string>
+    <string name="disable_tether_notification_title" msgid="3183576627492925522">"Tethering je deaktivovaný"</string>
+    <string name="disable_tether_notification_message" msgid="6655882039707534929">"O podrobnosti požiadajte svojho správcu"</string>
+    <string name="notification_channel_tethering_status" msgid="7030733422705019001">"Stav hotspotu a tetheringu"</string>
+    <string name="no_upstream_notification_title" msgid="2052743091868702475"></string>
+    <string name="no_upstream_notification_message" msgid="6932020551635470134"></string>
+    <string name="no_upstream_notification_disable_button" msgid="8836277213343697023"></string>
+    <string name="upstream_roaming_notification_title" msgid="8614262557406849762"></string>
+    <string name="upstream_roaming_notification_message" msgid="5999740876323106599"></string>
 </resources>
diff --git a/Tethering/res/values-sl/strings.xml b/Tethering/res/values-sl/strings.xml
index 5943362..4c9bd3c 100644
--- a/Tethering/res/values-sl/strings.xml
+++ b/Tethering/res/values-sl/strings.xml
@@ -16,14 +16,14 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="tethered_notification_title" msgid="6426563586025792944">"Povezava z internetom prek mobilnega telefona ali dostopna točka je aktivna"</string>
-    <string name="tethered_notification_message" msgid="64800879503420696">"Dotaknite se, če želite nastaviti."</string>
-    <string name="disable_tether_notification_title" msgid="3004509127903564191">"Povezava z internetom prek mobilnega telefona je onemogočena"</string>
-    <string name="disable_tether_notification_message" msgid="6717523799293901476">"Za podrobnosti se obrnite na skrbnika"</string>
-    <string name="notification_channel_tethering_status" msgid="2663463891530932727">"Stanje dostopne točke in povezave z internetom prek mobilnega telefona"</string>
-    <string name="no_upstream_notification_title" msgid="1204601824631788482"></string>
-    <string name="no_upstream_notification_message" msgid="8586582938243032621"></string>
-    <string name="no_upstream_notification_disable_button" msgid="8800919436924640822"></string>
-    <string name="upstream_roaming_notification_title" msgid="4772373823198997030"></string>
-    <string name="upstream_roaming_notification_message" msgid="3985577843181551650"></string>
+    <string name="tethered_notification_title" msgid="5350162111436634622">"Povezava računalnika z internetom prek mobilnega telefona ali dostopna točka je aktivna."</string>
+    <string name="tethered_notification_message" msgid="2338023450330652098">"Dotaknite se za nastavitev."</string>
+    <string name="disable_tether_notification_title" msgid="3183576627492925522">"Povezava računalnika z internetom prek mobilnega telefona je onemogočena."</string>
+    <string name="disable_tether_notification_message" msgid="6655882039707534929">"Za podrobnosti se obrnite na skrbnika."</string>
+    <string name="notification_channel_tethering_status" msgid="7030733422705019001">"Stanje dostopne točke in povezave računalnika z internetom prek mobilnega telefona"</string>
+    <string name="no_upstream_notification_title" msgid="2052743091868702475"></string>
+    <string name="no_upstream_notification_message" msgid="6932020551635470134"></string>
+    <string name="no_upstream_notification_disable_button" msgid="8836277213343697023"></string>
+    <string name="upstream_roaming_notification_title" msgid="8614262557406849762"></string>
+    <string name="upstream_roaming_notification_message" msgid="5999740876323106599"></string>
 </resources>
diff --git a/Tethering/res/values-sq/strings.xml b/Tethering/res/values-sq/strings.xml
index 21e1155..e39e98d 100644
--- a/Tethering/res/values-sq/strings.xml
+++ b/Tethering/res/values-sq/strings.xml
@@ -16,14 +16,14 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="tethered_notification_title" msgid="6426563586025792944">"Ndarja e internetit ose zona e qasjes së internetit është aktive"</string>
-    <string name="tethered_notification_message" msgid="64800879503420696">"Trokit për ta konfiguruar."</string>
-    <string name="disable_tether_notification_title" msgid="3004509127903564191">"Ndarja e internetit është çaktivizuar"</string>
-    <string name="disable_tether_notification_message" msgid="6717523799293901476">"Kontakto me administratorin për detaje"</string>
-    <string name="notification_channel_tethering_status" msgid="2663463891530932727">"Statusi i zonës së qasjes dhe ndarjes së internetit"</string>
-    <string name="no_upstream_notification_title" msgid="1204601824631788482"></string>
-    <string name="no_upstream_notification_message" msgid="8586582938243032621"></string>
-    <string name="no_upstream_notification_disable_button" msgid="8800919436924640822"></string>
-    <string name="upstream_roaming_notification_title" msgid="4772373823198997030"></string>
-    <string name="upstream_roaming_notification_message" msgid="3985577843181551650"></string>
+    <string name="tethered_notification_title" msgid="5350162111436634622">"Lidhja e çiftimit ose ajo e qasjes në zona publike interneti është aktive"</string>
+    <string name="tethered_notification_message" msgid="2338023450330652098">"Trokit për ta konfiguruar."</string>
+    <string name="disable_tether_notification_title" msgid="3183576627492925522">"Ndarja e internetit është çaktivizuar"</string>
+    <string name="disable_tether_notification_message" msgid="6655882039707534929">"Kontakto me administratorin për detaje"</string>
+    <string name="notification_channel_tethering_status" msgid="7030733422705019001">"Statusi i zonës së qasjes dhe ndarjes së internetit"</string>
+    <string name="no_upstream_notification_title" msgid="2052743091868702475"></string>
+    <string name="no_upstream_notification_message" msgid="6932020551635470134"></string>
+    <string name="no_upstream_notification_disable_button" msgid="8836277213343697023"></string>
+    <string name="upstream_roaming_notification_title" msgid="8614262557406849762"></string>
+    <string name="upstream_roaming_notification_message" msgid="5999740876323106599"></string>
 </resources>
diff --git a/Tethering/res/values-sr/strings.xml b/Tethering/res/values-sr/strings.xml
index e2e4dc6..ca3ba59 100644
--- a/Tethering/res/values-sr/strings.xml
+++ b/Tethering/res/values-sr/strings.xml
@@ -16,14 +16,14 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="tethered_notification_title" msgid="6426563586025792944">"Привезивање или хотспот је активан"</string>
-    <string name="tethered_notification_message" msgid="64800879503420696">"Додирните да бисте подесили."</string>
-    <string name="disable_tether_notification_title" msgid="3004509127903564191">"Привезивање је онемогућено"</string>
-    <string name="disable_tether_notification_message" msgid="6717523799293901476">"Потражите детаље од администратора"</string>
-    <string name="notification_channel_tethering_status" msgid="2663463891530932727">"Статус хотспота и привезивања"</string>
-    <string name="no_upstream_notification_title" msgid="1204601824631788482"></string>
-    <string name="no_upstream_notification_message" msgid="8586582938243032621"></string>
-    <string name="no_upstream_notification_disable_button" msgid="8800919436924640822"></string>
-    <string name="upstream_roaming_notification_title" msgid="4772373823198997030"></string>
-    <string name="upstream_roaming_notification_message" msgid="3985577843181551650"></string>
+    <string name="tethered_notification_title" msgid="5350162111436634622">"Активно је привезивање или хотспот"</string>
+    <string name="tethered_notification_message" msgid="2338023450330652098">"Додирните да бисте подесили."</string>
+    <string name="disable_tether_notification_title" msgid="3183576627492925522">"Привезивање је онемогућено"</string>
+    <string name="disable_tether_notification_message" msgid="6655882039707534929">"Потражите детаље од администратора"</string>
+    <string name="notification_channel_tethering_status" msgid="7030733422705019001">"Статус хотспота и привезивања"</string>
+    <string name="no_upstream_notification_title" msgid="2052743091868702475"></string>
+    <string name="no_upstream_notification_message" msgid="6932020551635470134"></string>
+    <string name="no_upstream_notification_disable_button" msgid="8836277213343697023"></string>
+    <string name="upstream_roaming_notification_title" msgid="8614262557406849762"></string>
+    <string name="upstream_roaming_notification_message" msgid="5999740876323106599"></string>
 </resources>
diff --git a/Tethering/res/values-sv/strings.xml b/Tethering/res/values-sv/strings.xml
index 72702c2..da5e104 100644
--- a/Tethering/res/values-sv/strings.xml
+++ b/Tethering/res/values-sv/strings.xml
@@ -16,14 +16,14 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="tethered_notification_title" msgid="6426563586025792944">"Internetdelning eller surfzon har aktiverats"</string>
-    <string name="tethered_notification_message" msgid="64800879503420696">"Tryck om du vill konfigurera."</string>
-    <string name="disable_tether_notification_title" msgid="3004509127903564191">"Internetdelning har inaktiverats"</string>
-    <string name="disable_tether_notification_message" msgid="6717523799293901476">"Kontakta administratören om du vill veta mer"</string>
-    <string name="notification_channel_tethering_status" msgid="2663463891530932727">"Trådlös surfzon och internetdelning har inaktiverats"</string>
-    <string name="no_upstream_notification_title" msgid="1204601824631788482"></string>
-    <string name="no_upstream_notification_message" msgid="8586582938243032621"></string>
-    <string name="no_upstream_notification_disable_button" msgid="8800919436924640822"></string>
-    <string name="upstream_roaming_notification_title" msgid="4772373823198997030"></string>
-    <string name="upstream_roaming_notification_message" msgid="3985577843181551650"></string>
+    <string name="tethered_notification_title" msgid="5350162111436634622">"Internetdelning eller surfzon är aktiv"</string>
+    <string name="tethered_notification_message" msgid="2338023450330652098">"Tryck om du vill konfigurera."</string>
+    <string name="disable_tether_notification_title" msgid="3183576627492925522">"Internetdelning har inaktiverats"</string>
+    <string name="disable_tether_notification_message" msgid="6655882039707534929">"Kontakta administratören om du vill veta mer"</string>
+    <string name="notification_channel_tethering_status" msgid="7030733422705019001">"Status för surfzon och internetdelning"</string>
+    <string name="no_upstream_notification_title" msgid="2052743091868702475"></string>
+    <string name="no_upstream_notification_message" msgid="6932020551635470134"></string>
+    <string name="no_upstream_notification_disable_button" msgid="8836277213343697023"></string>
+    <string name="upstream_roaming_notification_title" msgid="8614262557406849762"></string>
+    <string name="upstream_roaming_notification_message" msgid="5999740876323106599"></string>
 </resources>
diff --git a/Tethering/res/values-sw/strings.xml b/Tethering/res/values-sw/strings.xml
index 65e4aa8..3e58667 100644
--- a/Tethering/res/values-sw/strings.xml
+++ b/Tethering/res/values-sw/strings.xml
@@ -16,14 +16,14 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="tethered_notification_title" msgid="6426563586025792944">"Kusambaza mtandao au mtandaopepe umewashwa"</string>
-    <string name="tethered_notification_message" msgid="64800879503420696">"Gusa ili uweke mipangilio."</string>
-    <string name="disable_tether_notification_title" msgid="3004509127903564191">"Umezima kipengele cha kusambaza mtandao"</string>
-    <string name="disable_tether_notification_message" msgid="6717523799293901476">"Wasiliana na msimamizi wako ili upate maelezo zaidi"</string>
-    <string name="notification_channel_tethering_status" msgid="2663463891530932727">"Mtandaopepe na hali ya kusambaza mtandao"</string>
-    <string name="no_upstream_notification_title" msgid="1204601824631788482"></string>
-    <string name="no_upstream_notification_message" msgid="8586582938243032621"></string>
-    <string name="no_upstream_notification_disable_button" msgid="8800919436924640822"></string>
-    <string name="upstream_roaming_notification_title" msgid="4772373823198997030"></string>
-    <string name="upstream_roaming_notification_message" msgid="3985577843181551650"></string>
+    <string name="tethered_notification_title" msgid="5350162111436634622">"Umewasha kipengele cha kusambaza mtandao au mtandao pepe"</string>
+    <string name="tethered_notification_message" msgid="2338023450330652098">"Gusa ili uweke mipangilio."</string>
+    <string name="disable_tether_notification_title" msgid="3183576627492925522">"Umezima kipengele cha kusambaza mtandao"</string>
+    <string name="disable_tether_notification_message" msgid="6655882039707534929">"Wasiliana na msimamizi wako ili upate maelezo zaidi"</string>
+    <string name="notification_channel_tethering_status" msgid="7030733422705019001">"Mtandaopepe na hali ya kusambaza mtandao"</string>
+    <string name="no_upstream_notification_title" msgid="2052743091868702475"></string>
+    <string name="no_upstream_notification_message" msgid="6932020551635470134"></string>
+    <string name="no_upstream_notification_disable_button" msgid="8836277213343697023"></string>
+    <string name="upstream_roaming_notification_title" msgid="8614262557406849762"></string>
+    <string name="upstream_roaming_notification_message" msgid="5999740876323106599"></string>
 </resources>
diff --git a/Tethering/res/values-ta/strings.xml b/Tethering/res/values-ta/strings.xml
index 4aba62d..a811e67 100644
--- a/Tethering/res/values-ta/strings.xml
+++ b/Tethering/res/values-ta/strings.xml
@@ -16,14 +16,14 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="tethered_notification_title" msgid="6426563586025792944">"டெதெரிங் அல்லது ஹாட்ஸ்பாட் இயங்குகிறது"</string>
-    <string name="tethered_notification_message" msgid="64800879503420696">"அமைக்க, தட்டவும்."</string>
-    <string name="disable_tether_notification_title" msgid="3004509127903564191">"டெதெரிங் முடக்கப்பட்டுள்ளது"</string>
-    <string name="disable_tether_notification_message" msgid="6717523799293901476">"விவரங்களுக்கு உங்கள் நிர்வாகியைத் தொடர்புகொள்ளவும்"</string>
-    <string name="notification_channel_tethering_status" msgid="2663463891530932727">"ஹாட்ஸ்பாட் &amp; டெதெரிங் நிலை"</string>
-    <string name="no_upstream_notification_title" msgid="1204601824631788482"></string>
-    <string name="no_upstream_notification_message" msgid="8586582938243032621"></string>
-    <string name="no_upstream_notification_disable_button" msgid="8800919436924640822"></string>
-    <string name="upstream_roaming_notification_title" msgid="4772373823198997030"></string>
-    <string name="upstream_roaming_notification_message" msgid="3985577843181551650"></string>
+    <string name="tethered_notification_title" msgid="5350162111436634622">"இணைப்பு முறை அல்லது ஹாட்ஸ்பாட் செயல்பாட்டில் உள்ளது"</string>
+    <string name="tethered_notification_message" msgid="2338023450330652098">"அமைக்க தட்டவும்."</string>
+    <string name="disable_tether_notification_title" msgid="3183576627492925522">"இணைப்பு முறை முடக்கப்பட்டுள்ளது"</string>
+    <string name="disable_tether_notification_message" msgid="6655882039707534929">"விவரங்களுக்கு உங்கள் நிர்வாகியைத் தொடர்புகொள்ளவும்"</string>
+    <string name="notification_channel_tethering_status" msgid="7030733422705019001">"ஹாட்ஸ்பாட் &amp; இணைப்பு முறை நிலை"</string>
+    <string name="no_upstream_notification_title" msgid="2052743091868702475"></string>
+    <string name="no_upstream_notification_message" msgid="6932020551635470134"></string>
+    <string name="no_upstream_notification_disable_button" msgid="8836277213343697023"></string>
+    <string name="upstream_roaming_notification_title" msgid="8614262557406849762"></string>
+    <string name="upstream_roaming_notification_message" msgid="5999740876323106599"></string>
 </resources>
diff --git a/Tethering/res/values-te/strings.xml b/Tethering/res/values-te/strings.xml
index 1f91791..a92208d 100644
--- a/Tethering/res/values-te/strings.xml
+++ b/Tethering/res/values-te/strings.xml
@@ -16,14 +16,14 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="tethered_notification_title" msgid="6426563586025792944">"టెథరింగ్ లేదా హాట్‌స్పాట్ యాక్టివ్‌గా ఉంది"</string>
-    <string name="tethered_notification_message" msgid="64800879503420696">"సెటప్ చేయడానికి ట్యాప్ చేయండి."</string>
-    <string name="disable_tether_notification_title" msgid="3004509127903564191">"టెథరింగ్ డిజేబుల్ చేయబడింది"</string>
-    <string name="disable_tether_notification_message" msgid="6717523799293901476">"వివరాల కోసం మీ అడ్మిన్‌ని సంప్రదించండి"</string>
-    <string name="notification_channel_tethering_status" msgid="2663463891530932727">"హాట్‌స్పాట్ &amp; టెథరింగ్ స్థితి"</string>
-    <string name="no_upstream_notification_title" msgid="1204601824631788482"></string>
-    <string name="no_upstream_notification_message" msgid="8586582938243032621"></string>
-    <string name="no_upstream_notification_disable_button" msgid="8800919436924640822"></string>
-    <string name="upstream_roaming_notification_title" msgid="4772373823198997030"></string>
-    <string name="upstream_roaming_notification_message" msgid="3985577843181551650"></string>
+    <string name="tethered_notification_title" msgid="5350162111436634622">"టెథరింగ్ లేదా హాట్‌స్పాట్ యాక్టివ్‌గా ఉంది"</string>
+    <string name="tethered_notification_message" msgid="2338023450330652098">"సెటప్ చేయడానికి ట్యాప్ చేయండి."</string>
+    <string name="disable_tether_notification_title" msgid="3183576627492925522">"టెథరింగ్ డిజేబుల్ చేయబడింది"</string>
+    <string name="disable_tether_notification_message" msgid="6655882039707534929">"వివరాల కోసం మీ అడ్మిన్‌ను కాంటాక్ట్ చేయండి"</string>
+    <string name="notification_channel_tethering_status" msgid="7030733422705019001">"హాట్‌స్పాట్ &amp; టెథరింగ్ స్టేటస్"</string>
+    <string name="no_upstream_notification_title" msgid="2052743091868702475"></string>
+    <string name="no_upstream_notification_message" msgid="6932020551635470134"></string>
+    <string name="no_upstream_notification_disable_button" msgid="8836277213343697023"></string>
+    <string name="upstream_roaming_notification_title" msgid="8614262557406849762"></string>
+    <string name="upstream_roaming_notification_message" msgid="5999740876323106599"></string>
 </resources>
diff --git a/Tethering/res/values-th/strings.xml b/Tethering/res/values-th/strings.xml
index 44171c0..5ebbc80 100644
--- a/Tethering/res/values-th/strings.xml
+++ b/Tethering/res/values-th/strings.xml
@@ -16,14 +16,14 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="tethered_notification_title" msgid="6426563586025792944">"การเชื่อมต่ออินเทอร์เน็ตผ่านมือถือหรือฮอตสปอตทำงานอยู่"</string>
-    <string name="tethered_notification_message" msgid="64800879503420696">"แตะเพื่อตั้งค่า"</string>
-    <string name="disable_tether_notification_title" msgid="3004509127903564191">"ปิดใช้การเชื่อมต่ออินเทอร์เน็ตผ่านมือถือแล้ว"</string>
-    <string name="disable_tether_notification_message" msgid="6717523799293901476">"ติดต่อผู้ดูแลระบบเพื่อขอรายละเอียด"</string>
-    <string name="notification_channel_tethering_status" msgid="2663463891530932727">"สถานะฮอตสปอตและการเชื่อมต่ออินเทอร์เน็ตผ่านมือถือ"</string>
-    <string name="no_upstream_notification_title" msgid="1204601824631788482"></string>
-    <string name="no_upstream_notification_message" msgid="8586582938243032621"></string>
-    <string name="no_upstream_notification_disable_button" msgid="8800919436924640822"></string>
-    <string name="upstream_roaming_notification_title" msgid="4772373823198997030"></string>
-    <string name="upstream_roaming_notification_message" msgid="3985577843181551650"></string>
+    <string name="tethered_notification_title" msgid="5350162111436634622">"การเชื่อมต่ออินเทอร์เน็ตผ่านมือถือหรือฮอตสปอตทำงานอยู่"</string>
+    <string name="tethered_notification_message" msgid="2338023450330652098">"แตะเพื่อตั้งค่า"</string>
+    <string name="disable_tether_notification_title" msgid="3183576627492925522">"การเชื่อมต่ออินเทอร์เน็ตผ่านมือถือปิดอยู่"</string>
+    <string name="disable_tether_notification_message" msgid="6655882039707534929">"ติดต่อผู้ดูแลระบบเพื่อขอรายละเอียด"</string>
+    <string name="notification_channel_tethering_status" msgid="7030733422705019001">"สถานะฮอตสปอตและการเชื่อมต่ออินเทอร์เน็ตผ่านมือถือ"</string>
+    <string name="no_upstream_notification_title" msgid="2052743091868702475"></string>
+    <string name="no_upstream_notification_message" msgid="6932020551635470134"></string>
+    <string name="no_upstream_notification_disable_button" msgid="8836277213343697023"></string>
+    <string name="upstream_roaming_notification_title" msgid="8614262557406849762"></string>
+    <string name="upstream_roaming_notification_message" msgid="5999740876323106599"></string>
 </resources>
diff --git a/Tethering/res/values-tl/strings.xml b/Tethering/res/values-tl/strings.xml
index 7347dd3..3364e52 100644
--- a/Tethering/res/values-tl/strings.xml
+++ b/Tethering/res/values-tl/strings.xml
@@ -16,14 +16,14 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="tethered_notification_title" msgid="6426563586025792944">"Aktibo ang pag-tether o hotspot"</string>
-    <string name="tethered_notification_message" msgid="64800879503420696">"I-tap para i-set up."</string>
-    <string name="disable_tether_notification_title" msgid="3004509127903564191">"Naka-disable ang pag-tether"</string>
-    <string name="disable_tether_notification_message" msgid="6717523799293901476">"Makipag-ugnayan sa iyong admin para sa mga detalye"</string>
-    <string name="notification_channel_tethering_status" msgid="2663463891530932727">"Status ng hotspot at pag-tether"</string>
-    <string name="no_upstream_notification_title" msgid="1204601824631788482"></string>
-    <string name="no_upstream_notification_message" msgid="8586582938243032621"></string>
-    <string name="no_upstream_notification_disable_button" msgid="8800919436924640822"></string>
-    <string name="upstream_roaming_notification_title" msgid="4772373823198997030"></string>
-    <string name="upstream_roaming_notification_message" msgid="3985577843181551650"></string>
+    <string name="tethered_notification_title" msgid="5350162111436634622">"Aktibo ang pag-tether o hotspot"</string>
+    <string name="tethered_notification_message" msgid="2338023450330652098">"I-tap para i-set up."</string>
+    <string name="disable_tether_notification_title" msgid="3183576627492925522">"Naka-disable ang pag-tether"</string>
+    <string name="disable_tether_notification_message" msgid="6655882039707534929">"Makipag-ugnayan sa iyong admin para sa mga detalye"</string>
+    <string name="notification_channel_tethering_status" msgid="7030733422705019001">"Status ng hotspot at pag-tether"</string>
+    <string name="no_upstream_notification_title" msgid="2052743091868702475"></string>
+    <string name="no_upstream_notification_message" msgid="6932020551635470134"></string>
+    <string name="no_upstream_notification_disable_button" msgid="8836277213343697023"></string>
+    <string name="upstream_roaming_notification_title" msgid="8614262557406849762"></string>
+    <string name="upstream_roaming_notification_message" msgid="5999740876323106599"></string>
 </resources>
diff --git a/Tethering/res/values-tr/strings.xml b/Tethering/res/values-tr/strings.xml
index 32030f1..0bb273c 100644
--- a/Tethering/res/values-tr/strings.xml
+++ b/Tethering/res/values-tr/strings.xml
@@ -16,14 +16,14 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="tethered_notification_title" msgid="6426563586025792944">"Tethering veya hotspot etkin"</string>
-    <string name="tethered_notification_message" msgid="64800879503420696">"Ayarlamak için dokunun."</string>
-    <string name="disable_tether_notification_title" msgid="3004509127903564191">"Tethering devre dışı bırakıldı"</string>
-    <string name="disable_tether_notification_message" msgid="6717523799293901476">"Ayrıntılı bilgi için yöneticinize başvurun"</string>
-    <string name="notification_channel_tethering_status" msgid="2663463891530932727">"Hotspot ve tethering durumu"</string>
-    <string name="no_upstream_notification_title" msgid="1204601824631788482"></string>
-    <string name="no_upstream_notification_message" msgid="8586582938243032621"></string>
-    <string name="no_upstream_notification_disable_button" msgid="8800919436924640822"></string>
-    <string name="upstream_roaming_notification_title" msgid="4772373823198997030"></string>
-    <string name="upstream_roaming_notification_message" msgid="3985577843181551650"></string>
+    <string name="tethered_notification_title" msgid="5350162111436634622">"Tethering veya hotspot etkin"</string>
+    <string name="tethered_notification_message" msgid="2338023450330652098">"Ayarlamak için dokunun."</string>
+    <string name="disable_tether_notification_title" msgid="3183576627492925522">"Tethering devre dışı bırakıldı"</string>
+    <string name="disable_tether_notification_message" msgid="6655882039707534929">"Ayrıntılı bilgi için yöneticinize başvurun"</string>
+    <string name="notification_channel_tethering_status" msgid="7030733422705019001">"Hotspot ve tethering durumu"</string>
+    <string name="no_upstream_notification_title" msgid="2052743091868702475"></string>
+    <string name="no_upstream_notification_message" msgid="6932020551635470134"></string>
+    <string name="no_upstream_notification_disable_button" msgid="8836277213343697023"></string>
+    <string name="upstream_roaming_notification_title" msgid="8614262557406849762"></string>
+    <string name="upstream_roaming_notification_message" msgid="5999740876323106599"></string>
 </resources>
diff --git a/Tethering/res/values-uk/strings.xml b/Tethering/res/values-uk/strings.xml
index 1ca89b3..11962e5 100644
--- a/Tethering/res/values-uk/strings.xml
+++ b/Tethering/res/values-uk/strings.xml
@@ -16,14 +16,14 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="tethered_notification_title" msgid="6426563586025792944">"Модем чи точка доступу активні"</string>
-    <string name="tethered_notification_message" msgid="64800879503420696">"Натисніть, щоб налаштувати."</string>
-    <string name="disable_tether_notification_title" msgid="3004509127903564191">"Використання телефона як модема вимкнено"</string>
-    <string name="disable_tether_notification_message" msgid="6717523799293901476">"Щоб дізнатися більше, зв\'яжіться з адміністратором"</string>
-    <string name="notification_channel_tethering_status" msgid="2663463891530932727">"Статус точки доступу та модема"</string>
-    <string name="no_upstream_notification_title" msgid="1204601824631788482"></string>
-    <string name="no_upstream_notification_message" msgid="8586582938243032621"></string>
-    <string name="no_upstream_notification_disable_button" msgid="8800919436924640822"></string>
-    <string name="upstream_roaming_notification_title" msgid="4772373823198997030"></string>
-    <string name="upstream_roaming_notification_message" msgid="3985577843181551650"></string>
+    <string name="tethered_notification_title" msgid="5350162111436634622">"Модем чи точка доступу активні"</string>
+    <string name="tethered_notification_message" msgid="2338023450330652098">"Натисніть, щоб налаштувати."</string>
+    <string name="disable_tether_notification_title" msgid="3183576627492925522">"Використання телефона як модема вимкнено"</string>
+    <string name="disable_tether_notification_message" msgid="6655882039707534929">"Щоб дізнатися більше, зверніться до адміністратора"</string>
+    <string name="notification_channel_tethering_status" msgid="7030733422705019001">"Статус точки доступу й модема"</string>
+    <string name="no_upstream_notification_title" msgid="2052743091868702475"></string>
+    <string name="no_upstream_notification_message" msgid="6932020551635470134"></string>
+    <string name="no_upstream_notification_disable_button" msgid="8836277213343697023"></string>
+    <string name="upstream_roaming_notification_title" msgid="8614262557406849762"></string>
+    <string name="upstream_roaming_notification_message" msgid="5999740876323106599"></string>
 </resources>
diff --git a/Tethering/res/values-ur/strings.xml b/Tethering/res/values-ur/strings.xml
index d72c7d4..c70e44f 100644
--- a/Tethering/res/values-ur/strings.xml
+++ b/Tethering/res/values-ur/strings.xml
@@ -16,14 +16,14 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="tethered_notification_title" msgid="6426563586025792944">"ٹیدرنگ یا ہاٹ اسپاٹ فعال"</string>
-    <string name="tethered_notification_message" msgid="64800879503420696">"سیٹ اپ کرنے کیلئے تھپتھپائیں۔"</string>
-    <string name="disable_tether_notification_title" msgid="3004509127903564191">"ٹیدرنگ غیر فعال ہے"</string>
-    <string name="disable_tether_notification_message" msgid="6717523799293901476">"تفصیلات کے لئے اپنے منتظم سے رابطہ کریں"</string>
-    <string name="notification_channel_tethering_status" msgid="2663463891530932727">"ہاٹ اسپاٹ اور ٹیتھرنگ کا اسٹیٹس"</string>
-    <string name="no_upstream_notification_title" msgid="1204601824631788482"></string>
-    <string name="no_upstream_notification_message" msgid="8586582938243032621"></string>
-    <string name="no_upstream_notification_disable_button" msgid="8800919436924640822"></string>
-    <string name="upstream_roaming_notification_title" msgid="4772373823198997030"></string>
-    <string name="upstream_roaming_notification_message" msgid="3985577843181551650"></string>
+    <string name="tethered_notification_title" msgid="5350162111436634622">"ٹیدرنگ یا ہاٹ اسپاٹ فعال ہے"</string>
+    <string name="tethered_notification_message" msgid="2338023450330652098">"سیٹ اپ کرنے کیلئے تھپتھپائیں۔"</string>
+    <string name="disable_tether_notification_title" msgid="3183576627492925522">"ٹیدرنگ غیر فعال ہے"</string>
+    <string name="disable_tether_notification_message" msgid="6655882039707534929">"تفصیلات کیلئے اپنے منتظم سے رابطہ کریں"</string>
+    <string name="notification_channel_tethering_status" msgid="7030733422705019001">"ہاٹ اسپاٹ اور ٹیتھرنگ کا اسٹیٹس"</string>
+    <string name="no_upstream_notification_title" msgid="2052743091868702475"></string>
+    <string name="no_upstream_notification_message" msgid="6932020551635470134"></string>
+    <string name="no_upstream_notification_disable_button" msgid="8836277213343697023"></string>
+    <string name="upstream_roaming_notification_title" msgid="8614262557406849762"></string>
+    <string name="upstream_roaming_notification_message" msgid="5999740876323106599"></string>
 </resources>
diff --git a/Tethering/res/values-uz/strings.xml b/Tethering/res/values-uz/strings.xml
index af3b2eb..b315901 100644
--- a/Tethering/res/values-uz/strings.xml
+++ b/Tethering/res/values-uz/strings.xml
@@ -16,14 +16,14 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="tethered_notification_title" msgid="6426563586025792944">"Modem rejimi yoki hotspot yoniq"</string>
-    <string name="tethered_notification_message" msgid="64800879503420696">"Sozlash uchun bosing."</string>
-    <string name="disable_tether_notification_title" msgid="3004509127903564191">"Modem rejimi faolsizlantirildi"</string>
-    <string name="disable_tether_notification_message" msgid="6717523799293901476">"Tafsilotlari uchun administratoringizga murojaat qiling"</string>
-    <string name="notification_channel_tethering_status" msgid="2663463891530932727">"Hotspot va modem rejimi holati"</string>
-    <string name="no_upstream_notification_title" msgid="1204601824631788482"></string>
-    <string name="no_upstream_notification_message" msgid="8586582938243032621"></string>
-    <string name="no_upstream_notification_disable_button" msgid="8800919436924640822"></string>
-    <string name="upstream_roaming_notification_title" msgid="4772373823198997030"></string>
-    <string name="upstream_roaming_notification_message" msgid="3985577843181551650"></string>
+    <string name="tethered_notification_title" msgid="5350162111436634622">"Modem rejimi yoki hotspot yoniq"</string>
+    <string name="tethered_notification_message" msgid="2338023450330652098">"Sozlash uchun bosing."</string>
+    <string name="disable_tether_notification_title" msgid="3183576627492925522">"Modem rejimi faolsizlantirildi"</string>
+    <string name="disable_tether_notification_message" msgid="6655882039707534929">"Tafsilotlari uchun administratoringizga murojaat qiling"</string>
+    <string name="notification_channel_tethering_status" msgid="7030733422705019001">"Hotspot va modem rejimi holati"</string>
+    <string name="no_upstream_notification_title" msgid="2052743091868702475"></string>
+    <string name="no_upstream_notification_message" msgid="6932020551635470134"></string>
+    <string name="no_upstream_notification_disable_button" msgid="8836277213343697023"></string>
+    <string name="upstream_roaming_notification_title" msgid="8614262557406849762"></string>
+    <string name="upstream_roaming_notification_message" msgid="5999740876323106599"></string>
 </resources>
diff --git a/Tethering/res/values-vi/strings.xml b/Tethering/res/values-vi/strings.xml
index 21a0735..8e1b91e 100644
--- a/Tethering/res/values-vi/strings.xml
+++ b/Tethering/res/values-vi/strings.xml
@@ -16,14 +16,14 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="tethered_notification_title" msgid="6426563586025792944">"Tính năng chia sẻ Internet hoặc điểm phát sóng đang hoạt động"</string>
-    <string name="tethered_notification_message" msgid="64800879503420696">"Hãy nhấn để thiết lập."</string>
-    <string name="disable_tether_notification_title" msgid="3004509127903564191">"Đã tắt tính năng chia sẻ Internet"</string>
-    <string name="disable_tether_notification_message" msgid="6717523799293901476">"Hãy liên hệ với quản trị viên của bạn để biết chi tiết"</string>
-    <string name="notification_channel_tethering_status" msgid="2663463891530932727">"Trạng thái điểm phát sóng và chia sẻ Internet"</string>
-    <string name="no_upstream_notification_title" msgid="1204601824631788482"></string>
-    <string name="no_upstream_notification_message" msgid="8586582938243032621"></string>
-    <string name="no_upstream_notification_disable_button" msgid="8800919436924640822"></string>
-    <string name="upstream_roaming_notification_title" msgid="4772373823198997030"></string>
-    <string name="upstream_roaming_notification_message" msgid="3985577843181551650"></string>
+    <string name="tethered_notification_title" msgid="5350162111436634622">"Tính năng chia sẻ Internet hoặc điểm phát sóng đang hoạt động"</string>
+    <string name="tethered_notification_message" msgid="2338023450330652098">"Hãy nhấn để thiết lập."</string>
+    <string name="disable_tether_notification_title" msgid="3183576627492925522">"Tính năng chia sẻ Internet đã bị tắt"</string>
+    <string name="disable_tether_notification_message" msgid="6655882039707534929">"Hãy liên hệ với quản trị viên của bạn để biết thông tin chi tiết"</string>
+    <string name="notification_channel_tethering_status" msgid="7030733422705019001">"Trạng thái của chế độ cài đặt \"Điểm phát sóng và chia sẻ Internet\""</string>
+    <string name="no_upstream_notification_title" msgid="2052743091868702475"></string>
+    <string name="no_upstream_notification_message" msgid="6932020551635470134"></string>
+    <string name="no_upstream_notification_disable_button" msgid="8836277213343697023"></string>
+    <string name="upstream_roaming_notification_title" msgid="8614262557406849762"></string>
+    <string name="upstream_roaming_notification_message" msgid="5999740876323106599"></string>
 </resources>
diff --git a/Tethering/res/values-zh-rCN/strings.xml b/Tethering/res/values-zh-rCN/strings.xml
index 98e3b4b..054344e 100644
--- a/Tethering/res/values-zh-rCN/strings.xml
+++ b/Tethering/res/values-zh-rCN/strings.xml
@@ -16,14 +16,14 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="tethered_notification_title" msgid="6426563586025792944">"网络共享或热点已启用"</string>
-    <string name="tethered_notification_message" msgid="64800879503420696">"点按即可设置。"</string>
-    <string name="disable_tether_notification_title" msgid="3004509127903564191">"网络共享已停用"</string>
-    <string name="disable_tether_notification_message" msgid="6717523799293901476">"如需了解详情,请与您的管理员联系"</string>
-    <string name="notification_channel_tethering_status" msgid="2663463891530932727">"热点和网络共享状态"</string>
-    <string name="no_upstream_notification_title" msgid="1204601824631788482"></string>
-    <string name="no_upstream_notification_message" msgid="8586582938243032621"></string>
-    <string name="no_upstream_notification_disable_button" msgid="8800919436924640822"></string>
-    <string name="upstream_roaming_notification_title" msgid="4772373823198997030"></string>
-    <string name="upstream_roaming_notification_message" msgid="3985577843181551650"></string>
+    <string name="tethered_notification_title" msgid="5350162111436634622">"网络共享或热点已启用"</string>
+    <string name="tethered_notification_message" msgid="2338023450330652098">"点按即可设置。"</string>
+    <string name="disable_tether_notification_title" msgid="3183576627492925522">"网络共享已停用"</string>
+    <string name="disable_tether_notification_message" msgid="6655882039707534929">"如需了解详情,请与您的管理员联系"</string>
+    <string name="notification_channel_tethering_status" msgid="7030733422705019001">"热点和网络共享状态"</string>
+    <string name="no_upstream_notification_title" msgid="2052743091868702475"></string>
+    <string name="no_upstream_notification_message" msgid="6932020551635470134"></string>
+    <string name="no_upstream_notification_disable_button" msgid="8836277213343697023"></string>
+    <string name="upstream_roaming_notification_title" msgid="8614262557406849762"></string>
+    <string name="upstream_roaming_notification_message" msgid="5999740876323106599"></string>
 </resources>
diff --git a/Tethering/res/values-zh-rHK/strings.xml b/Tethering/res/values-zh-rHK/strings.xml
index 9cafd42..790d40a 100644
--- a/Tethering/res/values-zh-rHK/strings.xml
+++ b/Tethering/res/values-zh-rHK/strings.xml
@@ -16,14 +16,14 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="tethered_notification_title" msgid="6426563586025792944">"網絡共享或熱點已啟用"</string>
-    <string name="tethered_notification_message" msgid="64800879503420696">"輕按即可設定。"</string>
-    <string name="disable_tether_notification_title" msgid="3004509127903564191">"網絡共享已停用"</string>
-    <string name="disable_tether_notification_message" msgid="6717523799293901476">"請聯絡您的管理員以瞭解詳情"</string>
-    <string name="notification_channel_tethering_status" msgid="2663463891530932727">"熱點和網絡共享狀態"</string>
-    <string name="no_upstream_notification_title" msgid="1204601824631788482"></string>
-    <string name="no_upstream_notification_message" msgid="8586582938243032621"></string>
-    <string name="no_upstream_notification_disable_button" msgid="8800919436924640822"></string>
-    <string name="upstream_roaming_notification_title" msgid="4772373823198997030"></string>
-    <string name="upstream_roaming_notification_message" msgid="3985577843181551650"></string>
+    <string name="tethered_notification_title" msgid="5350162111436634622">"網絡共享或熱點已啟用"</string>
+    <string name="tethered_notification_message" msgid="2338023450330652098">"輕按即可設定。"</string>
+    <string name="disable_tether_notification_title" msgid="3183576627492925522">"網絡共享已停用"</string>
+    <string name="disable_tether_notification_message" msgid="6655882039707534929">"請聯絡你的管理員以瞭解詳情"</string>
+    <string name="notification_channel_tethering_status" msgid="7030733422705019001">"熱點和網絡共享狀態"</string>
+    <string name="no_upstream_notification_title" msgid="2052743091868702475"></string>
+    <string name="no_upstream_notification_message" msgid="6932020551635470134"></string>
+    <string name="no_upstream_notification_disable_button" msgid="8836277213343697023"></string>
+    <string name="upstream_roaming_notification_title" msgid="8614262557406849762"></string>
+    <string name="upstream_roaming_notification_message" msgid="5999740876323106599"></string>
 </resources>
diff --git a/Tethering/res/values-zh-rTW/strings.xml b/Tethering/res/values-zh-rTW/strings.xml
index 50a50bf..65a689e 100644
--- a/Tethering/res/values-zh-rTW/strings.xml
+++ b/Tethering/res/values-zh-rTW/strings.xml
@@ -16,14 +16,14 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="tethered_notification_title" msgid="6426563586025792944">"網路共用或無線基地台已啟用"</string>
-    <string name="tethered_notification_message" msgid="64800879503420696">"輕觸即可進行設定。"</string>
-    <string name="disable_tether_notification_title" msgid="3004509127903564191">"網路共用已停用"</string>
-    <string name="disable_tether_notification_message" msgid="6717523799293901476">"詳情請洽你的管理員"</string>
-    <string name="notification_channel_tethering_status" msgid="2663463891530932727">"無線基地台與網路共用狀態"</string>
-    <string name="no_upstream_notification_title" msgid="1204601824631788482"></string>
-    <string name="no_upstream_notification_message" msgid="8586582938243032621"></string>
-    <string name="no_upstream_notification_disable_button" msgid="8800919436924640822"></string>
-    <string name="upstream_roaming_notification_title" msgid="4772373823198997030"></string>
-    <string name="upstream_roaming_notification_message" msgid="3985577843181551650"></string>
+    <string name="tethered_notification_title" msgid="5350162111436634622">"網路共用或無線基地台已啟用"</string>
+    <string name="tethered_notification_message" msgid="2338023450330652098">"輕觸即可設定。"</string>
+    <string name="disable_tether_notification_title" msgid="3183576627492925522">"網路共用已停用"</string>
+    <string name="disable_tether_notification_message" msgid="6655882039707534929">"詳情請洽你的管理員"</string>
+    <string name="notification_channel_tethering_status" msgid="7030733422705019001">"無線基地台與網路共用狀態"</string>
+    <string name="no_upstream_notification_title" msgid="2052743091868702475"></string>
+    <string name="no_upstream_notification_message" msgid="6932020551635470134"></string>
+    <string name="no_upstream_notification_disable_button" msgid="8836277213343697023"></string>
+    <string name="upstream_roaming_notification_title" msgid="8614262557406849762"></string>
+    <string name="upstream_roaming_notification_message" msgid="5999740876323106599"></string>
 </resources>
diff --git a/Tethering/res/values-zu/strings.xml b/Tethering/res/values-zu/strings.xml
index f210f87..e9651dd 100644
--- a/Tethering/res/values-zu/strings.xml
+++ b/Tethering/res/values-zu/strings.xml
@@ -16,14 +16,14 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="tethered_notification_title" msgid="6426563586025792944">"Ukusebenzisa njengemodemu noma i-hotspot ephathekayo kuvuliwe"</string>
-    <string name="tethered_notification_message" msgid="64800879503420696">"Thepha ukuze usethe."</string>
-    <string name="disable_tether_notification_title" msgid="3004509127903564191">"Ukusebenzisa ifoni njengemodemu kukhutshaziwe"</string>
-    <string name="disable_tether_notification_message" msgid="6717523799293901476">"Xhumana nomphathi wakho ukuze uthole imininingwane"</string>
-    <string name="notification_channel_tethering_status" msgid="2663463891530932727">"I-Hotspot nesimo sokusebenzisa ifoni njengemodemu"</string>
-    <string name="no_upstream_notification_title" msgid="1204601824631788482"></string>
-    <string name="no_upstream_notification_message" msgid="8586582938243032621"></string>
-    <string name="no_upstream_notification_disable_button" msgid="8800919436924640822"></string>
-    <string name="upstream_roaming_notification_title" msgid="4772373823198997030"></string>
-    <string name="upstream_roaming_notification_message" msgid="3985577843181551650"></string>
+    <string name="tethered_notification_title" msgid="5350162111436634622">"Ukusebenzisa njengemodemu noma i-hotspot ephathekayo kuvuliwe"</string>
+    <string name="tethered_notification_message" msgid="2338023450330652098">"Thepha ukuze usethe."</string>
+    <string name="disable_tether_notification_title" msgid="3183576627492925522">"Ukusebenzisa ifoni njengemodemu kukhutshaziwe"</string>
+    <string name="disable_tether_notification_message" msgid="6655882039707534929">"Xhumana nomphathi wakho ukuze uthole imininingwane"</string>
+    <string name="notification_channel_tethering_status" msgid="7030733422705019001">"I-Hotspot nesimo sokusebenzisa ifoni njengemodemu"</string>
+    <string name="no_upstream_notification_title" msgid="2052743091868702475"></string>
+    <string name="no_upstream_notification_message" msgid="6932020551635470134"></string>
+    <string name="no_upstream_notification_disable_button" msgid="8836277213343697023"></string>
+    <string name="upstream_roaming_notification_title" msgid="8614262557406849762"></string>
+    <string name="upstream_roaming_notification_message" msgid="5999740876323106599"></string>
 </resources>
diff --git a/Tethering/src/android/net/ip/IpServer.java b/Tethering/src/android/net/ip/IpServer.java
index 5ec4d15..544ba01 100644
--- a/Tethering/src/android/net/ip/IpServer.java
+++ b/Tethering/src/android/net/ip/IpServer.java
@@ -33,6 +33,7 @@
 
 import static com.android.net.module.util.Inet4AddressUtils.intToInet4AddressHTH;
 import static com.android.net.module.util.NetworkStackConstants.RFC7421_PREFIX_LENGTH;
+import static com.android.networkstack.tethering.TetheringConfiguration.USE_SYNC_SM;
 import static com.android.networkstack.tethering.UpstreamNetworkState.isVcnInterface;
 import static com.android.networkstack.tethering.util.PrefixUtils.asIpPrefix;
 import static com.android.networkstack.tethering.util.TetheringMessageBase.BASE_IPSERVER;
@@ -316,7 +317,6 @@
 
     private final TetheringMetrics mTetheringMetrics;
     private final Handler mHandler;
-    private final boolean mIsSyncSM;
 
     // TODO: Add a dependency object to pass the data members or variables from the tethering
     // object. It helps to reduce the arguments of the constructor.
@@ -326,7 +326,7 @@
             @Nullable LateSdk<RoutingCoordinatorManager> routingCoordinator, Callback callback,
             TetheringConfiguration config, PrivateAddressCoordinator addressCoordinator,
             TetheringMetrics tetheringMetrics, Dependencies deps) {
-        super(ifaceName, config.isSyncSM() ? null : handler.getLooper());
+        super(ifaceName, USE_SYNC_SM ? null : handler.getLooper());
         mHandler = handler;
         mLog = log.forSubComponent(ifaceName);
         mNetd = netd;
@@ -339,7 +339,6 @@
         mLinkProperties = new LinkProperties();
         mUsingLegacyDhcp = config.useLegacyDhcpServer();
         mP2pLeasesSubnetPrefixLength = config.getP2pLeasesSubnetPrefixLength();
-        mIsSyncSM = config.isSyncSM();
         mPrivateAddressCoordinator = addressCoordinator;
         mDeps = deps;
         mTetheringMetrics = tetheringMetrics;
@@ -517,7 +516,7 @@
 
         private void handleError() {
             mLastError = TETHER_ERROR_DHCPSERVER_ERROR;
-            if (mIsSyncSM) {
+            if (USE_SYNC_SM) {
                 sendMessage(CMD_SERVICE_FAILED_TO_START, TETHER_ERROR_DHCPSERVER_ERROR);
             } else {
                 sendMessageAtFrontOfQueueToAsyncSM(CMD_SERVICE_FAILED_TO_START,
@@ -1172,7 +1171,7 @@
                 // in previous versions of the mainline module.
                 // TODO : remove sendMessageAtFrontOfQueueToAsyncSM after migrating to the Sync
                 // StateMachine.
-                if (mIsSyncSM) {
+                if (USE_SYNC_SM) {
                     sendSelfMessageToSyncSM(CMD_SERVICE_FAILED_TO_START, mLastError);
                 } else {
                     sendMessageAtFrontOfQueueToAsyncSM(CMD_SERVICE_FAILED_TO_START, mLastError);
diff --git a/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java b/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java
index 9f542f4..81e18ab 100644
--- a/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java
+++ b/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java
@@ -379,7 +379,7 @@
             if (!isAtLeastS()) return null;
             try {
                 return new BpfMap<>(TETHER_DOWNSTREAM4_MAP_PATH,
-                    BpfMap.BPF_F_RDWR, Tether4Key.class, Tether4Value.class);
+                    Tether4Key.class, Tether4Value.class);
             } catch (ErrnoException e) {
                 Log.e(TAG, "Cannot create downstream4 map: " + e);
                 return null;
@@ -391,7 +391,7 @@
             if (!isAtLeastS()) return null;
             try {
                 return new BpfMap<>(TETHER_UPSTREAM4_MAP_PATH,
-                    BpfMap.BPF_F_RDWR, Tether4Key.class, Tether4Value.class);
+                    Tether4Key.class, Tether4Value.class);
             } catch (ErrnoException e) {
                 Log.e(TAG, "Cannot create upstream4 map: " + e);
                 return null;
@@ -403,7 +403,7 @@
             if (!isAtLeastS()) return null;
             try {
                 return new BpfMap<>(TETHER_DOWNSTREAM6_FS_PATH,
-                    BpfMap.BPF_F_RDWR, TetherDownstream6Key.class, Tether6Value.class);
+                    TetherDownstream6Key.class, Tether6Value.class);
             } catch (ErrnoException e) {
                 Log.e(TAG, "Cannot create downstream6 map: " + e);
                 return null;
@@ -414,7 +414,7 @@
         @Nullable public IBpfMap<TetherUpstream6Key, Tether6Value> getBpfUpstream6Map() {
             if (!isAtLeastS()) return null;
             try {
-                return new BpfMap<>(TETHER_UPSTREAM6_FS_PATH, BpfMap.BPF_F_RDWR,
+                return new BpfMap<>(TETHER_UPSTREAM6_FS_PATH,
                         TetherUpstream6Key.class, Tether6Value.class);
             } catch (ErrnoException e) {
                 Log.e(TAG, "Cannot create upstream6 map: " + e);
@@ -427,7 +427,7 @@
             if (!isAtLeastS()) return null;
             try {
                 return new BpfMap<>(TETHER_STATS_MAP_PATH,
-                    BpfMap.BPF_F_RDWR, TetherStatsKey.class, TetherStatsValue.class);
+                    TetherStatsKey.class, TetherStatsValue.class);
             } catch (ErrnoException e) {
                 Log.e(TAG, "Cannot create stats map: " + e);
                 return null;
@@ -439,7 +439,7 @@
             if (!isAtLeastS()) return null;
             try {
                 return new BpfMap<>(TETHER_LIMIT_MAP_PATH,
-                    BpfMap.BPF_F_RDWR, TetherLimitKey.class, TetherLimitValue.class);
+                    TetherLimitKey.class, TetherLimitValue.class);
             } catch (ErrnoException e) {
                 Log.e(TAG, "Cannot create limit map: " + e);
                 return null;
@@ -451,7 +451,7 @@
             if (!isAtLeastS()) return null;
             try {
                 return new BpfMap<>(TETHER_DEV_MAP_PATH,
-                    BpfMap.BPF_F_RDWR, TetherDevKey.class, TetherDevValue.class);
+                    TetherDevKey.class, TetherDevValue.class);
             } catch (ErrnoException e) {
                 Log.e(TAG, "Cannot create dev map: " + e);
                 return null;
diff --git a/Tethering/src/com/android/networkstack/tethering/OffloadHardwareInterface.java b/Tethering/src/com/android/networkstack/tethering/OffloadHardwareInterface.java
index 53c80ae..13a7a22 100644
--- a/Tethering/src/com/android/networkstack/tethering/OffloadHardwareInterface.java
+++ b/Tethering/src/com/android/networkstack/tethering/OffloadHardwareInterface.java
@@ -16,6 +16,7 @@
 
 package com.android.networkstack.tethering;
 
+import static com.android.net.module.util.netlink.NetlinkUtils.SOCKET_RECV_BUFSIZE;
 import static com.android.net.module.util.netlink.StructNlMsgHdr.NLM_F_DUMP;
 import static com.android.net.module.util.netlink.StructNlMsgHdr.NLM_F_REQUEST;
 
@@ -198,7 +199,8 @@
         public NativeHandle createConntrackSocket(final int groups) {
             final FileDescriptor fd;
             try {
-                fd = NetlinkUtils.netlinkSocketForProto(OsConstants.NETLINK_NETFILTER);
+                fd = NetlinkUtils.netlinkSocketForProto(OsConstants.NETLINK_NETFILTER,
+                        SOCKET_RECV_BUFSIZE);
             } catch (ErrnoException e) {
                 mLog.e("Unable to create conntrack socket " + e);
                 return null;
diff --git a/Tethering/src/com/android/networkstack/tethering/Tethering.java b/Tethering/src/com/android/networkstack/tethering/Tethering.java
index 552b105..873961a 100644
--- a/Tethering/src/com/android/networkstack/tethering/Tethering.java
+++ b/Tethering/src/com/android/networkstack/tethering/Tethering.java
@@ -368,6 +368,7 @@
 
         // Load tethering configuration.
         updateConfiguration();
+        mConfig.readEnableSyncSM(mContext);
         // It is OK for the configuration to be passed to the PrivateAddressCoordinator at
         // construction time because the only part of the configuration it uses is
         // shouldEnableWifiP2pDedicatedIp(), and currently do not support changing that.
diff --git a/Tethering/src/com/android/networkstack/tethering/TetheringConfiguration.java b/Tethering/src/com/android/networkstack/tethering/TetheringConfiguration.java
index d09183a..298940e 100644
--- a/Tethering/src/com/android/networkstack/tethering/TetheringConfiguration.java
+++ b/Tethering/src/com/android/networkstack/tethering/TetheringConfiguration.java
@@ -141,6 +141,9 @@
      */
     public static final int DEFAULT_TETHER_OFFLOAD_POLL_INTERVAL_MS = 5000;
 
+    /** A flag for using synchronous or asynchronous state machine. */
+    public static boolean USE_SYNC_SM = false;
+
     public final String[] tetherableUsbRegexs;
     public final String[] tetherableWifiRegexs;
     public final String[] tetherableWigigRegexs;
@@ -174,7 +177,6 @@
 
     private final boolean mEnableWearTethering;
     private final boolean mRandomPrefixBase;
-    private final boolean mEnableSyncSm;
 
     private final int mUsbTetheringFunction;
     protected final ContentResolver mContentResolver;
@@ -293,7 +295,6 @@
         mEnableWearTethering = shouldEnableWearTethering(ctx);
 
         mRandomPrefixBase = mDeps.isFeatureEnabled(ctx, TETHER_FORCE_RANDOM_PREFIX_BASE_SELECTION);
-        mEnableSyncSm = mDeps.isFeatureEnabled(ctx, TETHER_ENABLE_SYNC_SM);
 
         configLog.log(toString());
     }
@@ -387,8 +388,14 @@
         return mRandomPrefixBase;
     }
 
-    public boolean isSyncSM() {
-        return mEnableSyncSm;
+    /**
+     * Check whether sync SM is enabled then set it to USE_SYNC_SM. This should be called once
+     * when tethering is created. Otherwise if the flag is pushed while tethering is enabled,
+     * then it's possible for some IpServer(s) running the new sync state machine while others
+     * use the async state machine.
+     */
+    public void readEnableSyncSM(final Context ctx) {
+        USE_SYNC_SM = mDeps.isFeatureEnabled(ctx, TETHER_ENABLE_SYNC_SM);
     }
 
     /** Does the dumping.*/
@@ -445,8 +452,8 @@
         pw.print("mRandomPrefixBase: ");
         pw.println(mRandomPrefixBase);
 
-        pw.print("mEnableSyncSm: ");
-        pw.println(mEnableSyncSm);
+        pw.print("USE_SYNC_SM: ");
+        pw.println(USE_SYNC_SM);
     }
 
     /** Returns the string representation of this object.*/
diff --git a/Tethering/tests/Android.bp b/Tethering/tests/Android.bp
index 72ca666..22cf3c5 100644
--- a/Tethering/tests/Android.bp
+++ b/Tethering/tests/Android.bp
@@ -15,6 +15,7 @@
 //
 
 package {
+    default_team: "trendy_team_fwk_core_networking",
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
@@ -24,5 +25,5 @@
     visibility: [
         "//packages/modules/Connectivity/tests:__subpackages__",
         "//packages/modules/Connectivity/Tethering/tests:__subpackages__",
-    ]
+    ],
 }
diff --git a/Tethering/tests/integration/Android.bp b/Tethering/tests/integration/Android.bp
index 2594a5e..07fa733 100644
--- a/Tethering/tests/integration/Android.bp
+++ b/Tethering/tests/integration/Android.bp
@@ -14,6 +14,7 @@
 // limitations under the License.
 //
 package {
+    default_team: "trendy_team_fwk_core_networking",
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
@@ -45,12 +46,11 @@
 android_library {
     name: "TetheringIntegrationTestsBaseLib",
     target_sdk_version: "current",
-    platform_apis: true,
     defaults: ["TetheringIntegrationTestsDefaults"],
     visibility: [
         "//packages/modules/Connectivity/Tethering/tests/mts",
         "//packages/modules/Connectivity/tests/cts/net",
-    ]
+    ],
 }
 
 // Library including tethering integration tests targeting the latest stable SDK.
@@ -58,7 +58,6 @@
 android_library {
     name: "TetheringIntegrationTestsLatestSdkLib",
     target_sdk_version: "33",
-    platform_apis: true,
     defaults: ["TetheringIntegrationTestsDefaults"],
     srcs: [
         "src/**/*.java",
@@ -67,7 +66,7 @@
         "//packages/modules/Connectivity/tests/cts/tethering",
         "//packages/modules/Connectivity/tests:__subpackages__",
         "//packages/modules/Connectivity/Tethering/tests:__subpackages__",
-    ]
+    ],
 }
 
 // Library including tethering integration tests targeting current development SDK.
@@ -75,7 +74,6 @@
 android_library {
     name: "TetheringIntegrationTestsLib",
     target_sdk_version: "current",
-    platform_apis: true,
     defaults: ["TetheringIntegrationTestsDefaults"],
     srcs: [
         "src/**/*.java",
@@ -83,7 +81,7 @@
     visibility: [
         "//packages/modules/Connectivity/tests/cts/tethering",
         "//packages/modules/Connectivity/Tethering/tests/mts",
-    ]
+    ],
 }
 
 // TODO: remove because TetheringIntegrationTests has been covered by ConnectivityCoverageTests.
diff --git a/Tethering/tests/integration/base/android/net/EthernetTetheringTestBase.java b/Tethering/tests/integration/base/android/net/EthernetTetheringTestBase.java
index c232697..2933a44 100644
--- a/Tethering/tests/integration/base/android/net/EthernetTetheringTestBase.java
+++ b/Tethering/tests/integration/base/android/net/EthernetTetheringTestBase.java
@@ -865,12 +865,12 @@
         // trigger the reselection, the total test time may over test suite 1 minmute timeout.
         // Probably need to disable/restore all internet networks in a common place of test
         // process. Currently, EthernetTetheringTest is part of CTS test which needs wifi network
-        // connection if device has wifi feature. CtsNetUtils#toggleWifi() checks wifi connection
-        // during the toggling process.
-        // See Tethering#chooseUpstreamType, CtsNetUtils#toggleWifi.
+        // connection if device has wifi feature.
+        // See Tethering#chooseUpstreamType
         // TODO: toggle cellular network if the device has no WIFI feature.
         Log.d(TAG, "Toggle WIFI to retry upstream selection");
-        sCtsNetUtils.toggleWifi();
+        sCtsNetUtils.disableWifi();
+        sCtsNetUtils.ensureWifiConnected();
 
         // Wait for expected upstream.
         final CompletableFuture<Network> future = new CompletableFuture<>();
diff --git a/Tethering/tests/mts/Android.bp b/Tethering/tests/mts/Android.bp
index 4f4b03c..a80e49e 100644
--- a/Tethering/tests/mts/Android.bp
+++ b/Tethering/tests/mts/Android.bp
@@ -13,6 +13,7 @@
 // limitations under the License.
 
 package {
+    default_team: "trendy_team_fwk_core_networking",
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
diff --git a/Tethering/tests/privileged/Android.bp b/Tethering/tests/privileged/Android.bp
index c890197..ba6be66 100644
--- a/Tethering/tests/privileged/Android.bp
+++ b/Tethering/tests/privileged/Android.bp
@@ -15,6 +15,7 @@
 //
 
 package {
+    default_team: "trendy_team_fwk_core_networking",
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
diff --git a/Tethering/tests/privileged/src/com/android/networkstack/tethering/BpfMapTest.java b/Tethering/tests/privileged/src/com/android/networkstack/tethering/BpfMapTest.java
index 0e8b044..d5d71bc 100644
--- a/Tethering/tests/privileged/src/com/android/networkstack/tethering/BpfMapTest.java
+++ b/Tethering/tests/privileged/src/com/android/networkstack/tethering/BpfMapTest.java
@@ -84,7 +84,7 @@
 
     private void initTestMap() throws Exception {
         mTestMap = new BpfMap<>(
-                TETHER_DOWNSTREAM6_FS_PATH, BpfMap.BPF_F_RDWR,
+                TETHER_DOWNSTREAM6_FS_PATH,
                 TetherDownstream6Key.class, Tether6Value.class);
 
         mTestMap.forEach((key, value) -> {
@@ -135,7 +135,7 @@
                 assertEquals(OsConstants.EPERM, expected.errno);
             }
         }
-        try (BpfMap readWriteMap = new BpfMap<>(TETHER_DOWNSTREAM6_FS_PATH, BpfMap.BPF_F_RDWR,
+        try (BpfMap readWriteMap = new BpfMap<>(TETHER_DOWNSTREAM6_FS_PATH,
                 TetherDownstream6Key.class, Tether6Value.class)) {
             assertNotNull(readWriteMap);
         }
@@ -389,7 +389,7 @@
     public void testOpenNonexistentMap() throws Exception {
         try {
             final BpfMap<TetherDownstream6Key, Tether6Value> nonexistentMap = new BpfMap<>(
-                    "/sys/fs/bpf/tethering/nonexistent", BpfMap.BPF_F_RDWR,
+                    "/sys/fs/bpf/tethering/nonexistent",
                     TetherDownstream6Key.class, Tether6Value.class);
         } catch (ErrnoException expected) {
             assertEquals(OsConstants.ENOENT, expected.errno);
@@ -409,8 +409,8 @@
         final int before = getNumOpenFds();
         for (int i = 0; i < iterations; i++) {
             try (BpfMap<TetherDownstream6Key, Tether6Value> map = new BpfMap<>(
-                TETHER_DOWNSTREAM6_FS_PATH, BpfMap.BPF_F_RDWR,
-                TetherDownstream6Key.class, Tether6Value.class)) {
+                    TETHER_DOWNSTREAM6_FS_PATH,
+                    TetherDownstream6Key.class, Tether6Value.class)) {
                 // do nothing
             }
         }
diff --git a/Tethering/tests/unit/Android.bp b/Tethering/tests/unit/Android.bp
index 36d9a63..24407ca 100644
--- a/Tethering/tests/unit/Android.bp
+++ b/Tethering/tests/unit/Android.bp
@@ -16,6 +16,7 @@
 
 // Tests in this folder are included both in unit tests and CTS.
 package {
+    default_team: "trendy_team_fwk_core_networking",
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
@@ -23,7 +24,7 @@
     name: "TetheringCommonTests",
     srcs: [
         "common/**/*.java",
-        "common/**/*.kt"
+        "common/**/*.kt",
     ],
     static_libs: [
         "androidx.test.rules",
@@ -95,7 +96,7 @@
     visibility: [
         "//packages/modules/Connectivity/tests:__subpackages__",
         "//packages/modules/Connectivity/Tethering/tests:__subpackages__",
-    ]
+    ],
 }
 
 android_test {
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringConfigurationTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringConfigurationTest.java
index 19c6e5a..dd51c7a 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringConfigurationTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringConfigurationTest.java
@@ -757,23 +757,24 @@
 
     private void setTetherEnableSyncSMFlagEnabled(Boolean enabled) {
         mDeps.setFeatureEnabled(TetheringConfiguration.TETHER_ENABLE_SYNC_SM, enabled);
+        new TetheringConfiguration(
+                mMockContext, mLog, INVALID_SUBSCRIPTION_ID, mDeps).readEnableSyncSM(mMockContext);
     }
 
-    private void assertEnableSyncSMIs(boolean value) {
-        assertEquals(value, new TetheringConfiguration(
-                mMockContext, mLog, INVALID_SUBSCRIPTION_ID, mDeps).isSyncSM());
+    private void assertEnableSyncSM(boolean value) {
+        assertEquals(value, TetheringConfiguration.USE_SYNC_SM);
     }
 
     @Test
     public void testEnableSyncSMFlag() throws Exception {
         // Test default disabled
         setTetherEnableSyncSMFlagEnabled(null);
-        assertEnableSyncSMIs(false);
+        assertEnableSyncSM(false);
 
         setTetherEnableSyncSMFlagEnabled(true);
-        assertEnableSyncSMIs(true);
+        assertEnableSyncSM(true);
 
         setTetherEnableSyncSMFlagEnabled(false);
-        assertEnableSyncSMIs(false);
+        assertEnableSyncSM(false);
     }
 }
diff --git a/bpf_progs/Android.bp b/bpf_progs/Android.bp
index cdf47e7..674cd98 100644
--- a/bpf_progs/Android.bp
+++ b/bpf_progs/Android.bp
@@ -18,6 +18,7 @@
 // struct definitions shared with JNI
 //
 package {
+    default_team: "trendy_team_fwk_core_networking",
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
diff --git a/bpf_progs/netd.c b/bpf_progs/netd.c
index f223dd1..5e401aa 100644
--- a/bpf_progs/netd.c
+++ b/bpf_progs/netd.c
@@ -446,8 +446,18 @@
                                                       const struct egress_bool egress,
                                                       const bool enable_tracing,
                                                       const struct kver_uint kver) {
+    // sock_uid will be 'overflowuid' if !sk_fullsock(sk_to_full_sk(skb->sk))
     uint32_t sock_uid = bpf_get_socket_uid(skb);
-    uint64_t cookie = bpf_get_socket_cookie(skb);
+
+    // kernel's DEFAULT_OVERFLOWUID is 65534, this is the overflow 'nobody' uid,
+    // usually this being returned means that skb->sk is NULL during RX
+    // (early decap socket lookup failure), which commonly happens for incoming
+    // packets to an unconnected udp socket.
+    // But it can also happen for egress from a timewait socket.
+    // Let's treat such cases as 'root' which is_system_uid()
+    if (sock_uid == 65534) sock_uid = 0;
+
+    uint64_t cookie = bpf_get_socket_cookie(skb);  // 0 iff !skb->sk
     UidTagValue* utag = bpf_cookie_tag_map_lookup_elem(&cookie);
     uint32_t uid, tag;
     if (utag) {
@@ -540,7 +550,7 @@
                     bpf_cgroup_egress_trace_user, KVER_5_8, KVER_INF,
                     BPFLOADER_IGNORED_ON_VERSION, BPFLOADER_MAX_VER, OPTIONAL,
                     "fs_bpf_netd_readonly", "",
-                    LOAD_ON_ENG, IGNORE_ON_USER, LOAD_ON_USERDEBUG)
+                    IGNORE_ON_ENG, LOAD_ON_USER, IGNORE_ON_USERDEBUG)
 (struct __sk_buff* skb) {
     return bpf_traffic_account(skb, EGRESS, TRACE_ON, KVER_5_8);
 }
@@ -616,12 +626,13 @@
     uint32_t sock_uid = bpf_get_socket_uid(skb);
     if (is_system_uid(sock_uid)) return BPF_MATCH;
 
-    // 65534 is the overflow 'nobody' uid, usually this being returned means
-    // that skb->sk is NULL during RX (early decap socket lookup failure),
-    // which commonly happens for incoming packets to an unconnected udp socket.
-    // Additionally bpf_get_socket_cookie() returns 0 if skb->sk is NULL
-    if ((sock_uid == 65534) && !bpf_get_socket_cookie(skb) && is_received_skb(skb))
-        return BPF_MATCH;
+    // kernel's DEFAULT_OVERFLOWUID is 65534, this is the overflow 'nobody' uid,
+    // usually this being returned means that skb->sk is NULL during RX
+    // (early decap socket lookup failure), which commonly happens for incoming
+    // packets to an unconnected udp socket.
+    // But it can also happen for egress from a timewait socket.
+    // Let's treat such cases as 'root' which is_system_uid()
+    if (sock_uid == 65534) return BPF_MATCH;
 
     UidOwnerValue* allowlistMatch = bpf_uid_owner_map_lookup_elem(&sock_uid);
     if (allowlistMatch) return allowlistMatch->rule & HAPPY_BOX_MATCH ? BPF_MATCH : BPF_NOMATCH;
diff --git a/bpf_progs/netd.h b/bpf_progs/netd.h
index 64ed633..098147f 100644
--- a/bpf_progs/netd.h
+++ b/bpf_progs/netd.h
@@ -178,7 +178,7 @@
 #endif // __cplusplus
 
 // LINT.IfChange(match_type)
-enum UidOwnerMatchType {
+enum UidOwnerMatchType : uint32_t {
     NO_MATCH = 0,
     HAPPY_BOX_MATCH = (1 << 0),
     PENALTY_BOX_MATCH = (1 << 1),
@@ -196,14 +196,14 @@
 };
 // LINT.ThenChange(../framework/src/android/net/BpfNetMapsConstants.java)
 
-enum BpfPermissionMatch {
+enum BpfPermissionMatch : uint8_t {
     BPF_PERMISSION_INTERNET = 1 << 2,
     BPF_PERMISSION_UPDATE_DEVICE_STATS = 1 << 3,
 };
 // In production we use two identical stats maps to record per uid stats and
 // do swap and clean based on the configuration specified here. The statsMapType
 // value in configuration map specified which map is currently in use.
-enum StatsMapType {
+enum StatsMapType : uint32_t {
     SELECT_MAP_A,
     SELECT_MAP_B,
 };
diff --git a/clatd/Android.bp b/clatd/Android.bp
index 595c6b9..43eb2d8 100644
--- a/clatd/Android.bp
+++ b/clatd/Android.bp
@@ -1,4 +1,5 @@
 package {
+    default_team: "trendy_team_fwk_core_networking",
     default_applicable_licenses: ["external_android-clat_license"],
 }
 
@@ -54,7 +55,7 @@
     defaults: ["clatd_defaults"],
     srcs: [
         ":clatd_common",
-        "main.c"
+        "main.c",
     ],
     static_libs: [
         "libip_checksum",
@@ -101,7 +102,7 @@
     defaults: ["clatd_defaults"],
     srcs: [
         ":clatd_common",
-        "clatd_test.cpp"
+        "clatd_test.cpp",
     ],
     static_libs: [
         "libbase",
diff --git a/common/Android.bp b/common/Android.bp
index f2f3929..0048a0a 100644
--- a/common/Android.bp
+++ b/common/Android.bp
@@ -15,10 +15,17 @@
 //
 
 package {
+    default_team: "trendy_team_fwk_core_networking",
     // See: http://go/android-license-faq
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
+build = ["FlaggedApi.bp"]
+
+// This is a placeholder comment to avoid merge conflicts
+// as the above target may not exist
+// depending on the branch
+
 // The library requires the final artifact to contain net-utils-device-common-struct.
 java_library {
     name: "connectivity-net-module-utils-bpf",
@@ -43,5 +50,7 @@
     apex_available: [
         "com.android.tethering",
     ],
-    lint: { strict_updatability_linting: true },
+    lint: {
+        strict_updatability_linting: true,
+    },
 }
diff --git a/common/FlaggedApi.bp b/common/FlaggedApi.bp
new file mode 100644
index 0000000..56625c5
--- /dev/null
+++ b/common/FlaggedApi.bp
@@ -0,0 +1,39 @@
+//
+// 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.
+//
+
+aconfig_declarations {
+    name: "com.android.net.flags-aconfig",
+    package: "com.android.net.flags",
+    container: "system",
+    srcs: ["flags.aconfig"],
+    visibility: ["//packages/modules/Connectivity:__subpackages__"],
+}
+
+aconfig_declarations {
+    name: "com.android.net.thread.flags-aconfig",
+    package: "com.android.net.thread.flags",
+    container: "system",
+    srcs: ["thread_flags.aconfig"],
+    visibility: ["//packages/modules/Connectivity:__subpackages__"],
+}
+
+aconfig_declarations {
+    name: "nearby_flags",
+    package: "com.android.nearby.flags",
+    container: "system",
+    srcs: ["nearby_flags.aconfig"],
+    visibility: ["//packages/modules/Connectivity:__subpackages__"],
+}
diff --git a/common/OWNERS b/common/OWNERS
new file mode 100644
index 0000000..e7f5d11
--- /dev/null
+++ b/common/OWNERS
@@ -0,0 +1 @@
+per-file thread_flags.aconfig = file:platform/packages/modules/Connectivity:main:/thread/OWNERS
diff --git a/common/flags.aconfig b/common/flags.aconfig
new file mode 100644
index 0000000..19b522c
--- /dev/null
+++ b/common/flags.aconfig
@@ -0,0 +1,75 @@
+package: "com.android.net.flags"
+container: "system"
+
+# This file contains aconfig flags for FlaggedAPI annotations
+# Flags used from platform code must be in under frameworks
+
+flag {
+  name: "set_data_saver_via_cm"
+  namespace: "android_core_networking"
+  description: "Set data saver through ConnectivityManager API"
+  bug: "297836825"
+}
+
+flag {
+  name: "support_is_uid_networking_blocked"
+  namespace: "android_core_networking"
+  description: "This flag controls whether isUidNetworkingBlocked is supported"
+  bug: "297836825"
+}
+
+flag {
+  name: "basic_background_restrictions_enabled"
+  namespace: "android_core_networking"
+  description: "Block network access for apps in a low importance background state"
+  bug: "304347838"
+}
+
+flag {
+  name: "ipsec_transform_state"
+  namespace: "android_core_networking_ipsec"
+  description: "The flag controls the access for getIpSecTransformState and IpSecTransformState"
+  bug: "308011229"
+}
+
+flag {
+  name: "tethering_request_with_soft_ap_config"
+  namespace: "android_core_networking"
+  description: "The flag controls the access for the parcelable TetheringRequest with getSoftApConfiguration/setSoftApConfiguration API"
+  bug: "216524590"
+}
+
+flag {
+  name: "request_restricted_wifi"
+  namespace: "android_core_networking"
+  description: "Flag for API to support requesting restricted wifi"
+  bug: "315835605"
+}
+
+flag {
+  name: "net_capability_local_network"
+  namespace: "android_core_networking"
+  description: "Flag for local network capability API"
+  bug: "313000440"
+}
+
+flag {
+  name: "support_transport_satellite"
+  namespace: "android_core_networking"
+  description: "Flag for satellite transport API"
+  bug: "320514105"
+}
+
+flag {
+  name: "nsd_subtypes_support_enabled"
+  namespace: "android_core_networking"
+  description: "Flag for API to support nsd subtypes"
+  bug: "265095929"
+}
+
+flag {
+  name: "register_nsd_offload_engine_api"
+  namespace: "android_core_networking"
+  description: "Flag for API to register nsd offload engine"
+  bug: "301713539"
+}
diff --git a/common/nearby_flags.aconfig b/common/nearby_flags.aconfig
new file mode 100644
index 0000000..b957d33
--- /dev/null
+++ b/common/nearby_flags.aconfig
@@ -0,0 +1,9 @@
+package: "com.android.nearby.flags"
+container: "system"
+
+flag {
+    name: "powered_off_finding"
+    namespace: "nearby"
+    description: "Controls whether the Powered Off Finding feature is enabled"
+    bug: "307898240"
+}
diff --git a/common/thread_flags.aconfig b/common/thread_flags.aconfig
new file mode 100644
index 0000000..09595a6
--- /dev/null
+++ b/common/thread_flags.aconfig
@@ -0,0 +1,9 @@
+package: "com.android.net.thread.flags"
+container: "system"
+
+flag {
+    name: "thread_enabled"
+    namespace: "thread_network"
+    description: "Controls whether the Android Thread feature is enabled"
+    bug: "301473012"
+}
diff --git a/framework-t/Android.bp b/framework-t/Android.bp
index c31dcf5..bc919ac 100644
--- a/framework-t/Android.bp
+++ b/framework-t/Android.bp
@@ -15,6 +15,7 @@
 //
 
 package {
+    default_team: "trendy_team_fwk_core_networking",
     // See: http://go/android-license-faq
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
@@ -34,6 +35,7 @@
     name: "enable-framework-connectivity-t-targets",
     enabled: true,
 }
+
 // The above defaults can be used to disable framework-connectivity t
 // targets while minimizing merge conflicts in the build rules.
 
@@ -116,6 +118,7 @@
         "framework-bluetooth",
         "framework-wifi",
         "framework-connectivity-pre-jarjar",
+        "framework-location.stubs.module_lib",
     ],
     visibility: ["//packages/modules/Connectivity:__subpackages__"],
 }
@@ -138,6 +141,7 @@
         "sdk_module-lib_current_framework-connectivity",
     ],
     libs: [
+        "framework-location.stubs.module_lib",
         "sdk_module-lib_current_framework-connectivity",
     ],
     permitted_packages: [
@@ -178,6 +182,7 @@
         "//frameworks/base/core/tests/bandwidthtests",
         "//frameworks/base/core/tests/benchmarks",
         "//frameworks/base/core/tests/utillib",
+        "//frameworks/base/services/tests/VpnTests",
         "//frameworks/base/tests/vcn",
         "//frameworks/opt/net/ethernet/tests:__subpackages__",
         "//frameworks/opt/telephony/tests/telephonytests",
@@ -191,6 +196,11 @@
         "//packages/modules/NetworkStack/tests:__subpackages__",
         "//packages/modules/Wifi/service/tests/wifitests",
     ],
+    aconfig_declarations: [
+        "com.android.net.flags-aconfig",
+        "com.android.net.thread.flags-aconfig",
+        "nearby_flags",
+    ],
 }
 
 // This rule is not used anymore(b/268440216).
diff --git a/framework-t/api/current.txt b/framework-t/api/current.txt
index 60a88c0..9ae0cf7 100644
--- a/framework-t/api/current.txt
+++ b/framework-t/api/current.txt
@@ -127,7 +127,7 @@
 
   public final class IpSecTransform implements java.lang.AutoCloseable {
     method public void close();
-    method @FlaggedApi("com.android.net.flags.ipsec_transform_state") public void getIpSecTransformState(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.net.IpSecTransformState,java.lang.RuntimeException>);
+    method @FlaggedApi("com.android.net.flags.ipsec_transform_state") public void requestIpSecTransformState(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.net.IpSecTransformState,java.lang.RuntimeException>);
   }
 
   public static class IpSecTransform.Builder {
@@ -145,7 +145,7 @@
     method public long getPacketCount();
     method @NonNull public byte[] getReplayBitmap();
     method public long getRxHighestSequenceNumber();
-    method public long getTimestamp();
+    method public long getTimestampMillis();
     method public long getTxHighestSequenceNumber();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.net.IpSecTransformState> CREATOR;
@@ -158,7 +158,7 @@
     method @NonNull public android.net.IpSecTransformState.Builder setPacketCount(long);
     method @NonNull public android.net.IpSecTransformState.Builder setReplayBitmap(@NonNull byte[]);
     method @NonNull public android.net.IpSecTransformState.Builder setRxHighestSequenceNumber(long);
-    method @NonNull public android.net.IpSecTransformState.Builder setTimestamp(long);
+    method @NonNull public android.net.IpSecTransformState.Builder setTimestampMillis(long);
     method @NonNull public android.net.IpSecTransformState.Builder setTxHighestSequenceNumber(long);
   }
 
@@ -210,9 +210,26 @@
 
 package android.net.nsd {
 
+  @FlaggedApi("com.android.net.flags.nsd_subtypes_support_enabled") public final class DiscoveryRequest implements android.os.Parcelable {
+    method public int describeContents();
+    method @Nullable public android.net.Network getNetwork();
+    method @NonNull public String getServiceType();
+    method @Nullable public String getSubtype();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.net.nsd.DiscoveryRequest> CREATOR;
+  }
+
+  public static final class DiscoveryRequest.Builder {
+    ctor public DiscoveryRequest.Builder(@NonNull String);
+    method @NonNull public android.net.nsd.DiscoveryRequest build();
+    method @NonNull public android.net.nsd.DiscoveryRequest.Builder setNetwork(@Nullable android.net.Network);
+    method @NonNull public android.net.nsd.DiscoveryRequest.Builder setSubtype(@Nullable String);
+  }
+
   public final class NsdManager {
     method public void discoverServices(String, int, android.net.nsd.NsdManager.DiscoveryListener);
     method public void discoverServices(@NonNull String, int, @Nullable android.net.Network, @NonNull java.util.concurrent.Executor, @NonNull android.net.nsd.NsdManager.DiscoveryListener);
+    method @FlaggedApi("com.android.net.flags.nsd_subtypes_support_enabled") public void discoverServices(@NonNull android.net.nsd.DiscoveryRequest, @NonNull java.util.concurrent.Executor, @NonNull android.net.nsd.NsdManager.DiscoveryListener);
     method @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public void discoverServices(@NonNull String, int, @NonNull android.net.NetworkRequest, @NonNull java.util.concurrent.Executor, @NonNull android.net.nsd.NsdManager.DiscoveryListener);
     method public void registerService(android.net.nsd.NsdServiceInfo, int, android.net.nsd.NsdManager.RegistrationListener);
     method public void registerService(@NonNull android.net.nsd.NsdServiceInfo, int, @NonNull java.util.concurrent.Executor, @NonNull android.net.nsd.NsdManager.RegistrationListener);
diff --git a/framework-t/api/system-current.txt b/framework-t/api/system-current.txt
index 3513573..1f1953c 100644
--- a/framework-t/api/system-current.txt
+++ b/framework-t/api/system-current.txt
@@ -59,11 +59,17 @@
   }
 
   public class NearbyManager {
+    method @FlaggedApi("com.android.nearby.flags.powered_off_finding") @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public int getPoweredOffFindingMode();
     method public void queryOffloadCapability(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.nearby.OffloadCapability>);
+    method @FlaggedApi("com.android.nearby.flags.powered_off_finding") @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public void setPoweredOffFindingEphemeralIds(@NonNull java.util.List<byte[]>);
+    method @FlaggedApi("com.android.nearby.flags.powered_off_finding") @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public void setPoweredOffFindingMode(int);
     method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_ADVERTISE, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public void startBroadcast(@NonNull android.nearby.BroadcastRequest, @NonNull java.util.concurrent.Executor, @NonNull android.nearby.BroadcastCallback);
     method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_SCAN, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public int startScan(@NonNull android.nearby.ScanRequest, @NonNull java.util.concurrent.Executor, @NonNull android.nearby.ScanCallback);
     method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_ADVERTISE, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public void stopBroadcast(@NonNull android.nearby.BroadcastCallback);
     method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_SCAN, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public void stopScan(@NonNull android.nearby.ScanCallback);
+    field @FlaggedApi("com.android.nearby.flags.powered_off_finding") public static final int POWERED_OFF_FINDING_MODE_DISABLED = 1; // 0x1
+    field @FlaggedApi("com.android.nearby.flags.powered_off_finding") public static final int POWERED_OFF_FINDING_MODE_ENABLED = 2; // 0x2
+    field @FlaggedApi("com.android.nearby.flags.powered_off_finding") public static final int POWERED_OFF_FINDING_MODE_UNSUPPORTED = 0; // 0x0
   }
 
   public final class OffloadCapability implements android.os.Parcelable {
@@ -500,6 +506,7 @@
     method @RequiresPermission(allOf={android.Manifest.permission.ACCESS_NETWORK_STATE, "android.permission.THREAD_NETWORK_PRIVILEGED"}) public void registerOperationalDatasetCallback(@NonNull java.util.concurrent.Executor, @NonNull android.net.thread.ThreadNetworkController.OperationalDatasetCallback);
     method @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public void registerStateCallback(@NonNull java.util.concurrent.Executor, @NonNull android.net.thread.ThreadNetworkController.StateCallback);
     method @RequiresPermission("android.permission.THREAD_NETWORK_PRIVILEGED") public void scheduleMigration(@NonNull android.net.thread.PendingOperationalDataset, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.net.thread.ThreadNetworkException>);
+    method @RequiresPermission("android.permission.THREAD_NETWORK_PRIVILEGED") public void setEnabled(boolean, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.net.thread.ThreadNetworkException>);
     method @RequiresPermission(allOf={android.Manifest.permission.ACCESS_NETWORK_STATE, "android.permission.THREAD_NETWORK_PRIVILEGED"}) public void unregisterOperationalDatasetCallback(@NonNull android.net.thread.ThreadNetworkController.OperationalDatasetCallback);
     method @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public void unregisterStateCallback(@NonNull android.net.thread.ThreadNetworkController.StateCallback);
     field public static final int DEVICE_ROLE_CHILD = 2; // 0x2
@@ -507,6 +514,9 @@
     field public static final int DEVICE_ROLE_LEADER = 4; // 0x4
     field public static final int DEVICE_ROLE_ROUTER = 3; // 0x3
     field public static final int DEVICE_ROLE_STOPPED = 0; // 0x0
+    field public static final int STATE_DISABLED = 0; // 0x0
+    field public static final int STATE_DISABLING = 2; // 0x2
+    field public static final int STATE_ENABLED = 1; // 0x1
     field public static final int THREAD_VERSION_1_3 = 4; // 0x4
   }
 
@@ -518,6 +528,7 @@
   public static interface ThreadNetworkController.StateCallback {
     method public void onDeviceRoleChanged(int);
     method public default void onPartitionIdChanged(long);
+    method public default void onThreadEnableStateChanged(int);
   }
 
   @FlaggedApi("com.android.net.thread.flags.thread_enabled") public class ThreadNetworkException extends java.lang.Exception {
@@ -530,8 +541,10 @@
     field public static final int ERROR_REJECTED_BY_PEER = 8; // 0x8
     field public static final int ERROR_RESOURCE_EXHAUSTED = 10; // 0xa
     field public static final int ERROR_RESPONSE_BAD_FORMAT = 9; // 0x9
+    field public static final int ERROR_THREAD_DISABLED = 12; // 0xc
     field public static final int ERROR_TIMEOUT = 3; // 0x3
     field public static final int ERROR_UNAVAILABLE = 4; // 0x4
+    field public static final int ERROR_UNKNOWN = 11; // 0xb
     field public static final int ERROR_UNSUPPORTED_CHANNEL = 7; // 0x7
   }
 
diff --git a/framework-t/src/android/app/usage/NetworkStatsManager.java b/framework-t/src/android/app/usage/NetworkStatsManager.java
index d139544..7fa0661 100644
--- a/framework-t/src/android/app/usage/NetworkStatsManager.java
+++ b/framework-t/src/android/app/usage/NetworkStatsManager.java
@@ -510,6 +510,27 @@
      * Query network usage statistics details for a given uid.
      * This may take a long time, and apps should avoid calling this on their main thread.
      *
+     * @param networkType As defined in {@link ConnectivityManager}, e.g.
+     *            {@link ConnectivityManager#TYPE_MOBILE}, {@link ConnectivityManager#TYPE_WIFI}
+     *            etc.
+     * @param subscriberId If applicable, the subscriber id of the network interface.
+     *                     <p>Starting with API level 29, the {@code subscriberId} is guarded by
+     *                     additional restrictions. Calling apps that do not meet the new
+     *                     requirements to access the {@code subscriberId} can provide a {@code
+     *                     null} value when querying for the mobile network type to receive usage
+     *                     for all mobile networks. For additional details see {@link
+     *                     TelephonyManager#getSubscriberId()}.
+     *                     <p>Starting with API level 31, calling apps can provide a
+     *                     {@code subscriberId} with wifi network type to receive usage for
+     *                     wifi networks which is under the given subscription if applicable.
+     *                     Otherwise, pass {@code null} when querying all wifi networks.
+     * @param startTime Start of period. Defined in terms of "Unix time", see
+     *            {@link java.lang.System#currentTimeMillis}.
+     * @param endTime End of period. Defined in terms of "Unix time", see
+     *            {@link java.lang.System#currentTimeMillis}.
+     * @param uid UID of app
+     * @return Statistics which is described above.
+     * @throws SecurityException if permissions are insufficient to read network statistics.
      * @see #queryDetailsForUidTagState(int, String, long, long, int, int, int)
      */
     @NonNull
diff --git a/framework-t/src/android/net/IpSecTransform.java b/framework-t/src/android/net/IpSecTransform.java
index 246a2dd..4e10a96 100644
--- a/framework-t/src/android/net/IpSecTransform.java
+++ b/framework-t/src/android/net/IpSecTransform.java
@@ -215,7 +215,7 @@
      * @see IpSecTransformState
      */
     @FlaggedApi(IPSEC_TRANSFORM_STATE)
-    public void getIpSecTransformState(
+    public void requestIpSecTransformState(
             @CallbackExecutor @NonNull Executor executor,
             @NonNull OutcomeReceiver<IpSecTransformState, RuntimeException> callback) {
         Objects.requireNonNull(executor);
diff --git a/framework-t/src/android/net/IpSecTransformState.java b/framework-t/src/android/net/IpSecTransformState.java
index b575dd5..5b80ae2 100644
--- a/framework-t/src/android/net/IpSecTransformState.java
+++ b/framework-t/src/android/net/IpSecTransformState.java
@@ -23,6 +23,7 @@
 import android.annotation.NonNull;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.os.SystemClock;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.net.module.util.HexDump;
@@ -40,7 +41,7 @@
  */
 @FlaggedApi(IPSEC_TRANSFORM_STATE)
 public final class IpSecTransformState implements Parcelable {
-    private final long mTimeStamp;
+    private final long mTimestamp;
     private final long mTxHighestSequenceNumber;
     private final long mRxHighestSequenceNumber;
     private final long mPacketCount;
@@ -54,7 +55,7 @@
             long packetCount,
             long byteCount,
             byte[] replayBitmap) {
-        mTimeStamp = timestamp;
+        mTimestamp = timestamp;
         mTxHighestSequenceNumber = txHighestSequenceNumber;
         mRxHighestSequenceNumber = rxHighestSequenceNumber;
         mPacketCount = packetCount;
@@ -78,7 +79,7 @@
     @VisibleForTesting(visibility = Visibility.PRIVATE)
     public IpSecTransformState(@NonNull Parcel in) {
         Objects.requireNonNull(in, "The input PersistableBundle is null");
-        mTimeStamp = in.readLong();
+        mTimestamp = in.readLong();
         mTxHighestSequenceNumber = in.readLong();
         mRxHighestSequenceNumber = in.readLong();
         mPacketCount = in.readLong();
@@ -97,7 +98,7 @@
 
     @Override
     public void writeToParcel(@NonNull Parcel out, int flags) {
-        out.writeLong(mTimeStamp);
+        out.writeLong(mTimestamp);
         out.writeLong(mTxHighestSequenceNumber);
         out.writeLong(mRxHighestSequenceNumber);
         out.writeLong(mPacketCount);
@@ -120,16 +121,17 @@
             };
 
     /**
-     * Retrieve the epoch timestamp (milliseconds) for when this state was created
+     * Retrieve the timestamp (milliseconds) when this state was created, as per {@link
+     * SystemClock#elapsedRealtime}
      *
-     * @see Builder#setTimestamp(long)
+     * @see Builder#setTimestampMillis(long)
      */
-    public long getTimestamp() {
-        return mTimeStamp;
+    public long getTimestampMillis() {
+        return mTimestamp;
     }
 
     /**
-     * Retrieve the highest sequence number sent so far
+     * Retrieve the highest sequence number sent so far as an unsigned long
      *
      * @see Builder#setTxHighestSequenceNumber(long)
      */
@@ -138,7 +140,7 @@
     }
 
     /**
-     * Retrieve the highest sequence number received so far
+     * Retrieve the highest sequence number received so far as an unsigned long
      *
      * @see Builder#setRxHighestSequenceNumber(long)
      */
@@ -147,7 +149,10 @@
     }
 
     /**
-     * Retrieve the number of packets received AND sent so far
+     * Retrieve the number of packets processed so far as an unsigned long.
+     *
+     * <p>The packet count direction (inbound or outbound) aligns with the direction in which the
+     * IpSecTransform is applied to.
      *
      * @see Builder#setPacketCount(long)
      */
@@ -156,7 +161,10 @@
     }
 
     /**
-     * Retrieve the number of bytes received AND sent so far
+     * Retrieve the number of bytes processed so far as an unsigned long
+     *
+     * <p>The byte count direction (inbound or outbound) aligns with the direction in which the
+     * IpSecTransform is applied to.
      *
      * @see Builder#setByteCount(long)
      */
@@ -183,10 +191,15 @@
         return mReplayBitmap.clone();
     }
 
-    /** Builder class for testing purposes */
+    /**
+     * Builder class for testing purposes
+     *
+     * <p>Except for testing, IPsec callers normally do not instantiate {@link IpSecTransformState}
+     * themselves but instead get a reference via {@link IpSecTransformState}
+     */
     @FlaggedApi(IPSEC_TRANSFORM_STATE)
     public static final class Builder {
-        private long mTimeStamp;
+        private long mTimestamp;
         private long mTxHighestSequenceNumber;
         private long mRxHighestSequenceNumber;
         private long mPacketCount;
@@ -194,22 +207,22 @@
         private byte[] mReplayBitmap;
 
         public Builder() {
-            mTimeStamp = System.currentTimeMillis();
+            mTimestamp = SystemClock.elapsedRealtime();
         }
 
         /**
-         * Set the epoch timestamp (milliseconds) for when this state was created
+         * Set the timestamp (milliseconds) when this state was created
          *
-         * @see IpSecTransformState#getTimestamp()
+         * @see IpSecTransformState#getTimestampMillis()
          */
         @NonNull
-        public Builder setTimestamp(long timeStamp) {
-            mTimeStamp = timeStamp;
+        public Builder setTimestampMillis(long timestamp) {
+            mTimestamp = timestamp;
             return this;
         }
 
         /**
-         * Set the highest sequence number sent so far
+         * Set the highest sequence number sent so far as an unsigned long
          *
          * @see IpSecTransformState#getTxHighestSequenceNumber()
          */
@@ -220,7 +233,7 @@
         }
 
         /**
-         * Set the highest sequence number received so far
+         * Set the highest sequence number received so far as an unsigned long
          *
          * @see IpSecTransformState#getRxHighestSequenceNumber()
          */
@@ -231,7 +244,7 @@
         }
 
         /**
-         * Set the number of packets received AND sent so far
+         * Set the number of packets processed so far as an unsigned long
          *
          * @see IpSecTransformState#getPacketCount()
          */
@@ -242,7 +255,7 @@
         }
 
         /**
-         * Set the number of bytes received AND sent so far
+         * Set the number of bytes processed so far as an unsigned long
          *
          * @see IpSecTransformState#getByteCount()
          */
@@ -271,7 +284,7 @@
         @NonNull
         public IpSecTransformState build() {
             return new IpSecTransformState(
-                    mTimeStamp,
+                    mTimestamp,
                     mTxHighestSequenceNumber,
                     mRxHighestSequenceNumber,
                     mPacketCount,
diff --git a/framework-t/src/android/net/NetworkStatsAccess.java b/framework-t/src/android/net/NetworkStatsAccess.java
index 7fe499b..7c9b3ec 100644
--- a/framework-t/src/android/net/NetworkStatsAccess.java
+++ b/framework-t/src/android/net/NetworkStatsAccess.java
@@ -128,7 +128,7 @@
 
         final int appId = UserHandle.getAppId(callingUid);
 
-        final boolean isNetworkStack = PermissionUtils.checkAnyPermissionOf(
+        final boolean isNetworkStack = PermissionUtils.hasAnyPermissionOf(
                 context, callingPid, callingUid, android.Manifest.permission.NETWORK_STACK,
                 NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK);
 
diff --git a/framework-t/src/android/net/nsd/AdvertisingRequest.java b/framework-t/src/android/net/nsd/AdvertisingRequest.java
index b1ef98f..2895b0c 100644
--- a/framework-t/src/android/net/nsd/AdvertisingRequest.java
+++ b/framework-t/src/android/net/nsd/AdvertisingRequest.java
@@ -17,11 +17,13 @@
 
 import android.annotation.LongDef;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.os.Parcel;
 import android.os.Parcelable;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.time.Duration;
 import java.util.Objects;
 
 /**
@@ -34,7 +36,7 @@
     /**
      * Only update the registration without sending exit and re-announcement.
      */
-    public static final int NSD_ADVERTISING_UPDATE_ONLY = 1;
+    public static final long NSD_ADVERTISING_UPDATE_ONLY = 1;
 
 
     @NonNull
@@ -46,7 +48,9 @@
                             NsdServiceInfo.class.getClassLoader(), NsdServiceInfo.class);
                     final int protocolType = in.readInt();
                     final long advertiseConfig = in.readLong();
-                    return new AdvertisingRequest(serviceInfo, protocolType, advertiseConfig);
+                    final long ttlSeconds = in.readLong();
+                    final Duration ttl = ttlSeconds < 0 ? null : Duration.ofSeconds(ttlSeconds);
+                    return new AdvertisingRequest(serviceInfo, protocolType, advertiseConfig, ttl);
                 }
 
                 @Override
@@ -60,6 +64,9 @@
     // Bitmask of @AdvertisingConfig flags. Uses a long to allow 64 possible flags in the future.
     private final long mAdvertisingConfig;
 
+    @Nullable
+    private final Duration mTtl;
+
     /**
      * @hide
      */
@@ -73,10 +80,11 @@
      * The constructor for the advertiseRequest
      */
     private AdvertisingRequest(@NonNull NsdServiceInfo serviceInfo, int protocolType,
-            long advertisingConfig) {
+            long advertisingConfig, @NonNull Duration ttl) {
         mServiceInfo = serviceInfo;
         mProtocolType = protocolType;
         mAdvertisingConfig = advertisingConfig;
+        mTtl = ttl;
     }
 
     /**
@@ -101,12 +109,25 @@
         return mAdvertisingConfig;
     }
 
+    /**
+     * Returns the time interval that the resource records may be cached on a DNS resolver or
+     * {@code null} if not specified.
+     *
+     * @hide
+     */
+    // @FlaggedApi(NsdManager.Flags.NSD_CUSTOM_TTL_ENABLED)
+    @Nullable
+    public Duration getTtl() {
+        return mTtl;
+    }
+
     @Override
     public String toString() {
         StringBuilder sb = new StringBuilder();
         sb.append("serviceInfo: ").append(mServiceInfo)
                 .append(", protocolType: ").append(mProtocolType)
-                .append(", advertisingConfig: ").append(mAdvertisingConfig);
+                .append(", advertisingConfig: ").append(mAdvertisingConfig)
+                .append(", ttl: ").append(mTtl);
         return sb.toString();
     }
 
@@ -120,13 +141,14 @@
             final AdvertisingRequest otherRequest = (AdvertisingRequest) other;
             return mServiceInfo.equals(otherRequest.mServiceInfo)
                     && mProtocolType == otherRequest.mProtocolType
-                    && mAdvertisingConfig == otherRequest.mAdvertisingConfig;
+                    && mAdvertisingConfig == otherRequest.mAdvertisingConfig
+                    && Objects.equals(mTtl, otherRequest.mTtl);
         }
     }
 
     @Override
     public int hashCode() {
-        return Objects.hash(mServiceInfo, mProtocolType, mAdvertisingConfig);
+        return Objects.hash(mServiceInfo, mProtocolType, mAdvertisingConfig, mTtl);
     }
 
     @Override
@@ -139,6 +161,7 @@
         dest.writeParcelable(mServiceInfo, flags);
         dest.writeInt(mProtocolType);
         dest.writeLong(mAdvertisingConfig);
+        dest.writeLong(mTtl == null ? -1 : mTtl.getSeconds());
     }
 
 //    @FlaggedApi(NsdManager.Flags.ADVERTISE_REQUEST_API)
@@ -151,6 +174,8 @@
         private final NsdServiceInfo mServiceInfo;
         private final int mProtocolType;
         private long mAdvertisingConfig;
+        @Nullable
+        private Duration mTtl;
         /**
          * Creates a new {@link Builder} object.
          */
@@ -170,11 +195,44 @@
             return this;
         }
 
+        /**
+         * Sets the time interval that the resource records may be cached on a DNS resolver.
+         *
+         * If this method is not called or {@code ttl} is {@code null}, default TTL values
+         * will be used for the service when it's registered. Otherwise, the {@code ttl}
+         * will be used for all resource records of this service.
+         *
+         * When registering a service, {@link NsdManager#FAILURE_BAD_PARAMETERS} will be returned
+         * if {@code ttl} is smaller than 30 seconds.
+         *
+         * Note: only number of seconds of {@code ttl} is used.
+         *
+         * @param ttl the maximum duration that the DNS resource records will be cached
+         *
+         * @see AdvertisingRequest#getTtl
+         * @hide
+         */
+        // @FlaggedApi(NsdManager.Flags.NSD_CUSTOM_TTL_ENABLED)
+        @NonNull
+        public Builder setTtl(@Nullable Duration ttl) {
+            if (ttl == null) {
+                mTtl = null;
+                return this;
+            }
+            final long ttlSeconds = ttl.getSeconds();
+            if (ttlSeconds < 0 || ttlSeconds > 0xffffffffL) {
+                throw new IllegalArgumentException(
+                        "ttlSeconds exceeds the allowed range (value = " + ttlSeconds
+                                + ", allowedRanged = [0, 0xffffffffL])");
+            }
+            mTtl = Duration.ofSeconds(ttlSeconds);
+            return this;
+        }
 
         /** Creates a new {@link AdvertisingRequest} object. */
         @NonNull
         public AdvertisingRequest build() {
-            return new AdvertisingRequest(mServiceInfo, mProtocolType, mAdvertisingConfig);
+            return new AdvertisingRequest(mServiceInfo, mProtocolType, mAdvertisingConfig, mTtl);
         }
     }
 }
diff --git a/framework-t/src/android/net/nsd/DiscoveryRequest.java b/framework-t/src/android/net/nsd/DiscoveryRequest.java
new file mode 100644
index 0000000..b0b71ea
--- /dev/null
+++ b/framework-t/src/android/net/nsd/DiscoveryRequest.java
@@ -0,0 +1,247 @@
+/*
+ * Copyright (C) 2023 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 android.net.nsd;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.net.Network;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+
+import java.util.Objects;
+
+/**
+ * Encapsulates parameters for {@link NsdManager#discoverServices}.
+ */
+@FlaggedApi(NsdManager.Flags.NSD_SUBTYPES_SUPPORT_ENABLED)
+public final class DiscoveryRequest implements Parcelable {
+    private final int mProtocolType;
+
+    @NonNull
+    private final String mServiceType;
+
+    @Nullable
+    private final String mSubtype;
+
+    @Nullable
+    private final Network mNetwork;
+
+    // TODO: add mDiscoveryConfig for more fine-grained discovery behavior control
+
+    @NonNull
+    public static final Creator<DiscoveryRequest> CREATOR =
+            new Creator<>() {
+                @Override
+                public DiscoveryRequest createFromParcel(Parcel in) {
+                    int protocolType = in.readInt();
+                    String serviceType = in.readString();
+                    String subtype = in.readString();
+                    Network network =
+                            in.readParcelable(Network.class.getClassLoader(), Network.class);
+                    return new DiscoveryRequest(protocolType, serviceType, subtype, network);
+                }
+
+                @Override
+                public DiscoveryRequest[] newArray(int size) {
+                    return new DiscoveryRequest[size];
+                }
+            };
+
+    private DiscoveryRequest(int protocolType, @NonNull String serviceType,
+            @Nullable String subtype, @Nullable Network network) {
+        mProtocolType = protocolType;
+        mServiceType = serviceType;
+        mSubtype = subtype;
+        mNetwork = network;
+    }
+
+    /**
+     * Returns the service type in format of dot-joint string of two labels.
+     *
+     * For example, "_ipp._tcp" for internet printer and "_matter._tcp" for <a
+     * href="https://csa-iot.org/all-solutions/matter">Matter</a> operational device.
+     */
+    @NonNull
+    public String getServiceType() {
+        return mServiceType;
+    }
+
+    /**
+     * Returns the subtype without the trailing "._sub" label or {@code null} if no subtype is
+     * specified.
+     *
+     * For example, the return value will be "_printer" for subtype "_printer._sub".
+     */
+    @Nullable
+    public String getSubtype() {
+        return mSubtype;
+    }
+
+    /**
+     * Returns the service discovery protocol.
+     *
+     * @hide
+     */
+    public int getProtocolType() {
+        return mProtocolType;
+    }
+
+    /**
+     * Returns the {@link Network} on which the query should be sent or {@code null} if no
+     * network is specified.
+     */
+    @Nullable
+    public Network getNetwork() {
+        return mNetwork;
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+        sb.append(", protocolType: ").append(mProtocolType)
+            .append(", serviceType: ").append(mServiceType)
+            .append(", subtype: ").append(mSubtype)
+            .append(", network: ").append(mNetwork);
+        return sb.toString();
+    }
+
+    @Override
+    public boolean equals(Object other) {
+        if (this == other) {
+            return true;
+        } else if (!(other instanceof DiscoveryRequest)) {
+            return false;
+        } else {
+            DiscoveryRequest otherRequest = (DiscoveryRequest) other;
+            return mProtocolType == otherRequest.mProtocolType
+                    && Objects.equals(mServiceType, otherRequest.mServiceType)
+                    && Objects.equals(mSubtype, otherRequest.mSubtype)
+                    && Objects.equals(mNetwork, otherRequest.mNetwork);
+        }
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mProtocolType, mServiceType, mSubtype, mNetwork);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeInt(mProtocolType);
+        dest.writeString(mServiceType);
+        dest.writeString(mSubtype);
+        dest.writeParcelable(mNetwork, flags);
+    }
+
+    /** The builder for creating new {@link DiscoveryRequest} objects. */
+    public static final class Builder {
+        private final int mProtocolType;
+
+        @NonNull
+        private String mServiceType;
+
+        @Nullable
+        private String mSubtype;
+
+        @Nullable
+        private Network mNetwork;
+
+        /**
+         * Creates a new default {@link Builder} object with given service type.
+         *
+         * @throws IllegalArgumentException if {@code serviceType} is {@code null} or an empty
+         * string
+         */
+        public Builder(@NonNull String serviceType) {
+            this(NsdManager.PROTOCOL_DNS_SD, serviceType);
+        }
+
+        /** @hide */
+        public Builder(int protocolType, @NonNull String serviceType) {
+            NsdManager.checkProtocol(protocolType);
+            mProtocolType = protocolType;
+            setServiceType(serviceType);
+        }
+
+        /**
+         * Sets the service type to be discovered or {@code null} if no services should be queried.
+         *
+         * The {@code serviceType} must be a dot-joint string of two labels. For example,
+         * "_ipp._tcp" for internet printer. Additionally, the first label must start with
+         * underscore ('_') and the second label must be either "_udp" or "_tcp". Otherwise, {@link
+         * NsdManager#discoverServices} will fail with {@link NsdManager#FAILURE_BAD_PARAMETER}.
+         *
+         * @throws IllegalArgumentException if {@code serviceType} is {@code null} or an empty
+         * string
+         *
+         * @hide
+         */
+        @NonNull
+        public Builder setServiceType(@NonNull String serviceType) {
+            if (TextUtils.isEmpty(serviceType)) {
+                throw new IllegalArgumentException("Service type cannot be empty");
+            }
+            mServiceType = serviceType;
+            return this;
+        }
+
+        /**
+         * Sets the optional subtype of the services to be discovered.
+         *
+         * If a non-empty {@code subtype} is specified, it must start with underscore ('_') and
+         * have the trailing "._sub" removed. Otherwise, {@link NsdManager#discoverServices} will
+         * fail with {@link NsdManager#FAILURE_BAD_PARAMETER}. For example, {@code subtype} should
+         * be "_printer" for DNS name "_printer._sub._http._tcp". In this case, only services with
+         * this {@code subtype} will be queried, rather than all services of the base service type.
+         *
+         * Note that a non-empty service type must be specified with {@link #setServiceType} if a
+         * non-empty subtype is specified by this method.
+         */
+        @NonNull
+        public Builder setSubtype(@Nullable String subtype) {
+            mSubtype = subtype;
+            return this;
+        }
+
+        /**
+         * Sets the {@link Network} on which the discovery queries should be sent.
+         *
+         * @param network the discovery network or {@code null} if the query should be sent on
+         * all supported networks
+         */
+        @NonNull
+        public Builder setNetwork(@Nullable Network network) {
+            mNetwork = network;
+            return this;
+        }
+
+        /**
+         * Creates a new {@link DiscoveryRequest} object.
+         */
+        @NonNull
+        public DiscoveryRequest build() {
+            return new DiscoveryRequest(mProtocolType, mServiceType, mSubtype, mNetwork);
+        }
+    }
+}
diff --git a/framework-t/src/android/net/nsd/INsdManagerCallback.aidl b/framework-t/src/android/net/nsd/INsdManagerCallback.aidl
index d89bfa9..55820ec 100644
--- a/framework-t/src/android/net/nsd/INsdManagerCallback.aidl
+++ b/framework-t/src/android/net/nsd/INsdManagerCallback.aidl
@@ -17,6 +17,7 @@
 package android.net.nsd;
 
 import android.os.Messenger;
+import android.net.nsd.DiscoveryRequest;
 import android.net.nsd.NsdServiceInfo;
 
 /**
@@ -24,7 +25,7 @@
  * @hide
  */
 oneway interface INsdManagerCallback {
-    void onDiscoverServicesStarted(int listenerKey, in NsdServiceInfo info);
+    void onDiscoverServicesStarted(int listenerKey, in DiscoveryRequest discoveryRequest);
     void onDiscoverServicesFailed(int listenerKey, int error);
     void onServiceFound(int listenerKey, in NsdServiceInfo info);
     void onServiceLost(int listenerKey, in NsdServiceInfo info);
diff --git a/framework-t/src/android/net/nsd/INsdServiceConnector.aidl b/framework-t/src/android/net/nsd/INsdServiceConnector.aidl
index b03eb29..9a31278 100644
--- a/framework-t/src/android/net/nsd/INsdServiceConnector.aidl
+++ b/framework-t/src/android/net/nsd/INsdServiceConnector.aidl
@@ -17,6 +17,7 @@
 package android.net.nsd;
 
 import android.net.nsd.AdvertisingRequest;
+import android.net.nsd.DiscoveryRequest;
 import android.net.nsd.INsdManagerCallback;
 import android.net.nsd.IOffloadEngine;
 import android.net.nsd.NsdServiceInfo;
@@ -30,7 +31,7 @@
 interface INsdServiceConnector {
     void registerService(int listenerKey, in AdvertisingRequest advertisingRequest);
     void unregisterService(int listenerKey);
-    void discoverServices(int listenerKey, in NsdServiceInfo serviceInfo);
+    void discoverServices(int listenerKey, in DiscoveryRequest discoveryRequest);
     void stopDiscovery(int listenerKey);
     void resolveService(int listenerKey, in NsdServiceInfo serviceInfo);
     void startDaemon();
@@ -39,4 +40,4 @@
     void unregisterServiceInfoCallback(int listenerKey);
     void registerOffloadEngine(String ifaceName, in IOffloadEngine cb, long offloadCapabilities, long offloadType);
     void unregisterOffloadEngine(in IOffloadEngine cb);
-}
\ No newline at end of file
+}
diff --git a/framework-t/src/android/net/nsd/NsdManager.java b/framework-t/src/android/net/nsd/NsdManager.java
index b4f2be9..1001423 100644
--- a/framework-t/src/android/net/nsd/NsdManager.java
+++ b/framework-t/src/android/net/nsd/NsdManager.java
@@ -158,13 +158,38 @@
                 "com.android.net.flags.nsd_subtypes_support_enabled";
         static final String ADVERTISE_REQUEST_API =
                 "com.android.net.flags.advertise_request_api";
+        static final String NSD_CUSTOM_HOSTNAME_ENABLED =
+                "com.android.net.flags.nsd_custom_hostname_enabled";
+        static final String NSD_CUSTOM_TTL_ENABLED =
+                "com.android.net.flags.nsd_custom_ttl_enabled";
     }
 
     /**
      * A regex for the acceptable format of a type or subtype label.
      * @hide
      */
-    public static final String TYPE_SUBTYPE_LABEL_REGEX = "_[a-zA-Z0-9-_]{1,61}[a-zA-Z0-9]";
+    public static final String TYPE_LABEL_REGEX = "_[a-zA-Z0-9-_]{1,61}[a-zA-Z0-9]";
+
+    /**
+     * A regex for the acceptable format of a subtype label.
+     *
+     * As per RFC 6763 7.1, "Subtype strings are not required to begin with an underscore, though
+     * they often do.", and "Subtype strings [...] may be constructed using arbitrary 8-bit data
+     * values.  In many cases these data values may be UTF-8 [RFC3629] representations of text, or
+     * even (as in the example above) plain ASCII [RFC20], but they do not have to be.".
+     *
+     * This regex is overly conservative as it mandates the underscore and only allows printable
+     * ASCII characters (codes 0x20 to 0x7e, space to tilde), except for comma (0x2c) and dot
+     * (0x2e); so the NsdManager API does not allow everything the RFC allows. This may be revisited
+     * in the future, but using arbitrary bytes makes logging and testing harder, and using other
+     * characters would probably be a bad idea for interoperability for apps.
+     * @hide
+     */
+    public static final String SUBTYPE_LABEL_REGEX = "_["
+            + "\\x20-\\x2b"
+            + "\\x2d"
+            + "\\x2f-\\x7e"
+            + "]{1,62}";
 
     /**
      * A regex for the acceptable format of a service type specification.
@@ -177,14 +202,14 @@
     public static final String TYPE_REGEX =
             // Optional leading subtype (_subtype._type._tcp)
             // (?: xxx) is a non-capturing parenthesis, don't capture the dot
-            "^(?:(" + TYPE_SUBTYPE_LABEL_REGEX + ")\\.)?"
+            "^(?:(" + SUBTYPE_LABEL_REGEX + ")\\.)?"
                     // Actual type (_type._tcp.local)
-                    + "(" + TYPE_SUBTYPE_LABEL_REGEX + "\\._(?:tcp|udp))"
+                    + "(" + TYPE_LABEL_REGEX + "\\._(?:tcp|udp))"
                     // Drop '.' at the end of service type that is compatible with old backend.
                     // e.g. allow "_type._tcp.local."
                     + "\\.?"
                     // Optional subtype after comma, for "_type._tcp,_subtype1,_subtype2" format
-                    + "((?:," + TYPE_SUBTYPE_LABEL_REGEX + ")*)"
+                    + "((?:," + SUBTYPE_LABEL_REGEX + ")*)"
                     + "$";
 
     /**
@@ -304,6 +329,20 @@
     /** Dns based service discovery protocol */
     public static final int PROTOCOL_DNS_SD = 0x0001;
 
+    /**
+     * The minimum TTL seconds which is allowed for a service registration.
+     *
+     * @hide
+     */
+    public static final long TTL_SECONDS_MIN = 30L;
+
+    /**
+     * The maximum TTL seconds which is allowed for a service registration.
+     *
+     * @hide
+     */
+    public static final long TTL_SECONDS_MAX = 10 * 3600L;
+
     private static final SparseArray<String> EVENT_NAMES = new SparseArray<>();
     static {
         EVENT_NAMES.put(DISCOVER_SERVICES, "DISCOVER_SERVICES");
@@ -360,6 +399,8 @@
     @GuardedBy("mMapLock")
     private final SparseArray<NsdServiceInfo> mServiceMap = new SparseArray<>();
     @GuardedBy("mMapLock")
+    private final SparseArray<DiscoveryRequest> mDiscoveryMap = new SparseArray<>();
+    @GuardedBy("mMapLock")
     private final SparseArray<Executor> mExecutorMap = new SparseArray<>();
     private final Object mMapLock = new Object();
     // Map of listener key sent by client -> per-network discovery tracker
@@ -715,6 +756,12 @@
             mServHandler.sendMessage(mServHandler.obtainMessage(message, 0, listenerKey, info));
         }
 
+        private void sendDiscoveryRequest(
+                int message, int listenerKey, DiscoveryRequest discoveryRequest) {
+            mServHandler.sendMessage(
+                    mServHandler.obtainMessage(message, 0, listenerKey, discoveryRequest));
+        }
+
         private void sendError(int message, int listenerKey, int error) {
             mServHandler.sendMessage(mServHandler.obtainMessage(message, error, listenerKey));
         }
@@ -724,8 +771,8 @@
         }
 
         @Override
-        public void onDiscoverServicesStarted(int listenerKey, NsdServiceInfo info) {
-            sendInfo(DISCOVER_SERVICES_STARTED, listenerKey, info);
+        public void onDiscoverServicesStarted(int listenerKey, DiscoveryRequest discoveryRequest) {
+            sendDiscoveryRequest(DISCOVER_SERVICES_STARTED, listenerKey, discoveryRequest);
         }
 
         @Override
@@ -1003,10 +1050,12 @@
             final Object obj = message.obj;
             final Object listener;
             final NsdServiceInfo ns;
+            final DiscoveryRequest discoveryRequest;
             final Executor executor;
             synchronized (mMapLock) {
                 listener = mListenerMap.get(key);
                 ns = mServiceMap.get(key);
+                discoveryRequest = mDiscoveryMap.get(key);
                 executor = mExecutorMap.get(key);
             }
             if (listener == null) {
@@ -1014,17 +1063,22 @@
                 return;
             }
             if (DBG) {
-                Log.d(TAG, "received " + nameOf(what) + " for key " + key + ", service " + ns);
+                if (discoveryRequest != null) {
+                    Log.d(TAG, "received " + nameOf(what) + " for key " + key + ", discovery "
+                            + discoveryRequest);
+                } else {
+                    Log.d(TAG, "received " + nameOf(what) + " for key " + key + ", service " + ns);
+                }
             }
             switch (what) {
                 case DISCOVER_SERVICES_STARTED:
-                    final String s = getNsdServiceInfoType((NsdServiceInfo) obj);
+                    final String s = getNsdServiceInfoType((DiscoveryRequest) obj);
                     executor.execute(() -> ((DiscoveryListener) listener).onDiscoveryStarted(s));
                     break;
                 case DISCOVER_SERVICES_FAILED:
                     removeListener(key);
                     executor.execute(() -> ((DiscoveryListener) listener).onStartDiscoveryFailed(
-                            getNsdServiceInfoType(ns), errorCode));
+                            getNsdServiceInfoType(discoveryRequest), errorCode));
                     break;
                 case SERVICE_FOUND:
                     executor.execute(() -> ((DiscoveryListener) listener).onServiceFound(
@@ -1039,12 +1093,12 @@
                     // the effect for the client is indistinguishable from STOP_DISCOVERY_SUCCEEDED
                     removeListener(key);
                     executor.execute(() -> ((DiscoveryListener) listener).onStopDiscoveryFailed(
-                            getNsdServiceInfoType(ns), errorCode));
+                            getNsdServiceInfoType(discoveryRequest), errorCode));
                     break;
                 case STOP_DISCOVERY_SUCCEEDED:
                     removeListener(key);
                     executor.execute(() -> ((DiscoveryListener) listener).onDiscoveryStopped(
-                            getNsdServiceInfoType(ns)));
+                            getNsdServiceInfoType(discoveryRequest)));
                     break;
                 case REGISTER_SERVICE_FAILED:
                     removeListener(key);
@@ -1117,21 +1171,33 @@
         return mListenerKey;
     }
 
-    // Assert that the listener is not in the map, then add it and returns its key
-    private int putListener(Object listener, Executor e, NsdServiceInfo s) {
-        checkListener(listener);
-        final int key;
+    private int putListener(Object listener, Executor e, NsdServiceInfo serviceInfo) {
         synchronized (mMapLock) {
-            int valueIndex = mListenerMap.indexOfValue(listener);
+            return putListener(listener, e, mServiceMap, serviceInfo);
+        }
+    }
+
+    private int putListener(Object listener, Executor e, DiscoveryRequest discoveryRequest) {
+        synchronized (mMapLock) {
+            return putListener(listener, e, mDiscoveryMap, discoveryRequest);
+        }
+    }
+
+    // Assert that the listener is not in the map, then add it and returns its key
+    private <T> int putListener(Object listener, Executor e, SparseArray<T> map, T value) {
+        synchronized (mMapLock) {
+            checkListener(listener);
+            final int key;
+            final int valueIndex = mListenerMap.indexOfValue(listener);
             if (valueIndex != -1) {
                 throw new IllegalArgumentException("listener already in use");
             }
             key = nextListenerKey();
             mListenerMap.put(key, listener);
-            mServiceMap.put(key, s);
+            map.put(key, value);
             mExecutorMap.put(key, e);
+            return key;
         }
-        return key;
     }
 
     private int updateRegisteredListener(Object listener, Executor e, NsdServiceInfo s) {
@@ -1148,6 +1214,7 @@
         synchronized (mMapLock) {
             mListenerMap.remove(key);
             mServiceMap.remove(key);
+            mDiscoveryMap.remove(key);
             mExecutorMap.remove(key);
         }
     }
@@ -1163,9 +1230,9 @@
         }
     }
 
-    private static String getNsdServiceInfoType(NsdServiceInfo s) {
-        if (s == null) return "?";
-        return s.getServiceType();
+    private static String getNsdServiceInfoType(DiscoveryRequest r) {
+        if (r == null) return "?";
+        return r.getServiceType();
     }
 
     /**
@@ -1208,7 +1275,7 @@
      */
     public void registerService(@NonNull NsdServiceInfo serviceInfo, int protocolType,
             @NonNull Executor executor, @NonNull RegistrationListener listener) {
-        checkServiceInfo(serviceInfo);
+        checkServiceInfoForRegistration(serviceInfo);
         checkProtocol(protocolType);
         final AdvertisingRequest.Builder builder = new AdvertisingRequest.Builder(serviceInfo,
                 protocolType);
@@ -1267,7 +1334,10 @@
      * @return Type and comma-separated list of subtypes, or null if invalid format.
      */
     @Nullable
-    private static Pair<String, String> getTypeAndSubtypes(@NonNull String typeWithSubtype) {
+    private static Pair<String, String> getTypeAndSubtypes(@Nullable String typeWithSubtype) {
+        if (typeWithSubtype == null) {
+            return null;
+        }
         final Matcher matcher = Pattern.compile(TYPE_REGEX).matcher(typeWithSubtype);
         if (!matcher.matches()) return null;
         // Reject specifications using leading subtypes with a dot
@@ -1298,10 +1368,7 @@
             @NonNull RegistrationListener listener) {
         final NsdServiceInfo serviceInfo = advertisingRequest.getServiceInfo();
         final int protocolType = advertisingRequest.getProtocolType();
-        if (serviceInfo.getPort() <= 0) {
-            throw new IllegalArgumentException("Invalid port number");
-        }
-        checkServiceInfo(serviceInfo);
+        checkServiceInfoForRegistration(serviceInfo);
         checkProtocol(protocolType);
         final int key;
         // For update only request, the old listener has to be reused
@@ -1406,15 +1473,44 @@
         if (TextUtils.isEmpty(serviceType)) {
             throw new IllegalArgumentException("Service type cannot be empty");
         }
-        checkProtocol(protocolType);
+        DiscoveryRequest request = new DiscoveryRequest.Builder(protocolType, serviceType)
+                .setNetwork(network).build();
+        discoverServices(request, executor, listener);
+    }
 
-        NsdServiceInfo s = new NsdServiceInfo();
-        s.setServiceType(serviceType);
-        s.setNetwork(network);
-
-        int key = putListener(listener, executor, s);
+    /**
+     * Initiates service discovery to browse for instances of a service type. Service discovery
+     * consumes network bandwidth and will continue until the application calls
+     * {@link #stopServiceDiscovery}.
+     *
+     * <p> The function call immediately returns after sending a request to start service
+     * discovery to the framework. The application is notified of a success to initiate
+     * discovery through the callback {@link DiscoveryListener#onDiscoveryStarted} or a failure
+     * through {@link DiscoveryListener#onStartDiscoveryFailed}.
+     *
+     * <p> Upon successful start, application is notified when a service is found with
+     * {@link DiscoveryListener#onServiceFound} or when a service is lost with
+     * {@link DiscoveryListener#onServiceLost}.
+     *
+     * <p> Upon failure to start, service discovery is not active and application does
+     * not need to invoke {@link #stopServiceDiscovery}
+     *
+     * <p> The application should call {@link #stopServiceDiscovery} when discovery of this
+     * service type is no longer required, and/or whenever the application is paused or
+     * stopped.
+     *
+     * @param discoveryRequest the {@link DiscoveryRequest} object which specifies the discovery
+     * parameters such as service type, subtype and network
+     * @param executor Executor to run listener callbacks with
+     * @param listener  The listener notifies of a successful discovery and is used
+     * to stop discovery on this serviceType through a call on {@link #stopServiceDiscovery}.
+     */
+    @FlaggedApi(Flags.NSD_SUBTYPES_SUPPORT_ENABLED)
+    public void discoverServices(@NonNull DiscoveryRequest discoveryRequest,
+            @NonNull Executor executor, @NonNull DiscoveryListener listener) {
+        int key = putListener(listener, executor, discoveryRequest);
         try {
-            mService.discoverServices(key, s);
+            mService.discoverServices(key, discoveryRequest);
         } catch (RemoteException e) {
             e.rethrowFromSystemServer();
         }
@@ -1465,12 +1561,10 @@
             throw new IllegalArgumentException("Service type cannot be empty");
         }
         Objects.requireNonNull(networkRequest, "NetworkRequest cannot be null");
-        checkProtocol(protocolType);
+        DiscoveryRequest discoveryRequest =
+                new DiscoveryRequest.Builder(protocolType, serviceType).build();
 
-        NsdServiceInfo s = new NsdServiceInfo();
-        s.setServiceType(serviceType);
-
-        final int baseListenerKey = putListener(listener, executor, s);
+        final int baseListenerKey = putListener(listener, executor, discoveryRequest);
 
         final PerNetworkDiscoveryTracker discoveryInfo = new PerNetworkDiscoveryTracker(
                 serviceType, protocolType, executor, listener);
@@ -1551,7 +1645,7 @@
     @Deprecated
     public void resolveService(@NonNull NsdServiceInfo serviceInfo,
             @NonNull Executor executor, @NonNull ResolveListener listener) {
-        checkServiceInfo(serviceInfo);
+        checkServiceInfoForResolution(serviceInfo);
         int key = putListener(listener, executor, serviceInfo);
         try {
             mService.resolveService(key, serviceInfo);
@@ -1602,9 +1696,10 @@
      * @param executor Executor to run callbacks with
      * @param listener to receive callback upon service update
      */
+    // TODO: use {@link DiscoveryRequest} to specify the service to be subscribed
     public void registerServiceInfoCallback(@NonNull NsdServiceInfo serviceInfo,
             @NonNull Executor executor, @NonNull ServiceInfoCallback listener) {
-        checkServiceInfo(serviceInfo);
+        checkServiceInfoForResolution(serviceInfo);
         int key = putListener(listener, executor, serviceInfo);
         try {
             mService.registerServiceInfoCallback(key, serviceInfo);
@@ -1643,13 +1738,13 @@
         Objects.requireNonNull(listener, "listener cannot be null");
     }
 
-    private static void checkProtocol(int protocolType) {
+    static void checkProtocol(int protocolType) {
         if (protocolType != PROTOCOL_DNS_SD) {
             throw new IllegalArgumentException("Unsupported protocol");
         }
     }
 
-    private static void checkServiceInfo(NsdServiceInfo serviceInfo) {
+    private static void checkServiceInfoForResolution(NsdServiceInfo serviceInfo) {
         Objects.requireNonNull(serviceInfo, "NsdServiceInfo cannot be null");
         if (TextUtils.isEmpty(serviceInfo.getServiceName())) {
             throw new IllegalArgumentException("Service name cannot be empty");
@@ -1658,4 +1753,46 @@
             throw new IllegalArgumentException("Service type cannot be empty");
         }
     }
+
+    /**
+     * Check if the {@link NsdServiceInfo} is valid for registration.
+     *
+     * The following can be registered:
+     * - A service with an optional host.
+     * - A hostname with addresses.
+     *
+     * Note that:
+     * - When registering a service, the service name, service type and port must be specified. If
+     *   hostname is specified, the host addresses can optionally be specified.
+     * - When registering a host without a service, the addresses must be specified.
+     *
+     * @hide
+     */
+    public static void checkServiceInfoForRegistration(NsdServiceInfo serviceInfo) {
+        Objects.requireNonNull(serviceInfo, "NsdServiceInfo cannot be null");
+        boolean hasServiceName = !TextUtils.isEmpty(serviceInfo.getServiceName());
+        boolean hasServiceType = !TextUtils.isEmpty(serviceInfo.getServiceType());
+        boolean hasHostname = !TextUtils.isEmpty(serviceInfo.getHostname());
+        boolean hasHostAddresses = !CollectionUtils.isEmpty(serviceInfo.getHostAddresses());
+
+        if (serviceInfo.getPort() < 0) {
+            throw new IllegalArgumentException("Invalid port");
+        }
+
+        if (hasServiceType || hasServiceName || (serviceInfo.getPort() > 0)) {
+            if (!(hasServiceType && hasServiceName && (serviceInfo.getPort() > 0))) {
+                throw new IllegalArgumentException(
+                        "The service type, service name or port is missing");
+            }
+        }
+
+        if (!hasServiceType && !hasHostname) {
+            throw new IllegalArgumentException("No service or host specified in NsdServiceInfo");
+        }
+
+        if (!hasServiceType && hasHostname && !hasHostAddresses) {
+            // TODO: b/317946010 - This may be allowed when it supports registering KEY RR.
+            throw new IllegalArgumentException("No host addresses specified in NsdServiceInfo");
+        }
+    }
 }
diff --git a/framework-t/src/android/net/nsd/NsdServiceInfo.java b/framework-t/src/android/net/nsd/NsdServiceInfo.java
index ac4ea23..f4cc2ac 100644
--- a/framework-t/src/android/net/nsd/NsdServiceInfo.java
+++ b/framework-t/src/android/net/nsd/NsdServiceInfo.java
@@ -16,8 +16,6 @@
 
 package android.net.nsd;
 
-import static java.nio.charset.StandardCharsets.UTF_8;
-
 import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -35,6 +33,7 @@
 import java.io.UnsupportedEncodingException;
 import java.net.InetAddress;
 import java.nio.charset.StandardCharsets;
+import java.time.Instant;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
@@ -49,8 +48,10 @@
 
     private static final String TAG = "NsdServiceInfo";
 
+    @Nullable
     private String mServiceName;
 
+    @Nullable
     private String mServiceType;
 
     private final Set<String> mSubtypes;
@@ -59,6 +60,9 @@
 
     private final List<InetAddress> mHostAddresses;
 
+    @Nullable
+    private String mHostname;
+
     private int mPort;
 
     @Nullable
@@ -66,6 +70,10 @@
 
     private int mInterfaceIndex;
 
+    // The timestamp that all resource records associated with this service are considered invalid.
+    @Nullable
+    private Instant mExpirationTime;
+
     public NsdServiceInfo() {
         mSubtypes = new ArraySet<>();
         mTxtRecord = new ArrayMap<>();
@@ -90,9 +98,11 @@
         mSubtypes = new ArraySet<>(other.getSubtypes());
         mTxtRecord = new ArrayMap<>(other.mTxtRecord);
         mHostAddresses = new ArrayList<>(other.getHostAddresses());
+        mHostname = other.getHostname();
         mPort = other.getPort();
         mNetwork = other.getNetwork();
         mInterfaceIndex = other.getInterfaceIndex();
+        mExpirationTime = other.getExpirationTime();
     }
 
     /** Get the service name */
@@ -169,6 +179,43 @@
     }
 
     /**
+     * Get the hostname.
+     *
+     * <p>When a service is resolved, it returns the hostname of the resolved service . The top
+     * level domain ".local." is omitted.
+     *
+     * <p>For example, it returns "MyHost" when the service's hostname is "MyHost.local.".
+     *
+     * @hide
+     */
+//    @FlaggedApi(NsdManager.Flags.NSD_CUSTOM_HOSTNAME_ENABLED)
+    @Nullable
+    public String getHostname() {
+        return mHostname;
+    }
+
+    /**
+     * Set a custom hostname for this service instance for registration.
+     *
+     * <p>A hostname must be in ".local." domain. The ".local." must be omitted when calling this
+     * method.
+     *
+     * <p>For example, you should call setHostname("MyHost") to use the hostname "MyHost.local.".
+     *
+     * <p>If a hostname is set with this method, the addresses set with {@link #setHostAddresses}
+     * will be registered with the hostname.
+     *
+     * <p>If the hostname is null (which is the default for a new {@link NsdServiceInfo}), a random
+     * hostname is used and the addresses of this device will be registered.
+     *
+     * @hide
+     */
+//    @FlaggedApi(NsdManager.Flags.NSD_CUSTOM_HOSTNAME_ENABLED)
+    public void setHostname(@Nullable String hostname) {
+        mHostname = hostname;
+    }
+
+    /**
      * Unpack txt information from a base-64 encoded byte array.
      *
      * @param txtRecordsRawBytes The raw base64 encoded byte array.
@@ -447,6 +494,38 @@
         return Collections.unmodifiableSet(mSubtypes);
     }
 
+    /**
+     * Sets the timestamp after when this service is expired.
+     *
+     * Note: only number of seconds of {@code expirationTime} is used.
+     *
+     * @hide
+     */
+    public void setExpirationTime(@Nullable Instant expirationTime) {
+        if (expirationTime == null) {
+            mExpirationTime = null;
+        } else {
+            mExpirationTime = Instant.ofEpochSecond(expirationTime.getEpochSecond());
+        }
+    }
+
+    /**
+     * Returns the timestamp after when this service is expired or {@code null} if it's unknown.
+     *
+     * A service is considered expired if any of its DNS record is expired.
+     *
+     * Clients that are depending on the refreshness of the service information should not continue
+     * use this service after the returned timestamp. Instead, clients may re-send queries for the
+     * service to get updated the service information.
+     *
+     * @hide
+     */
+    // @FlaggedApi(NsdManager.Flags.NSD_CUSTOM_TTL_ENABLED)
+    @Nullable
+    public Instant getExpirationTime() {
+        return mExpirationTime;
+    }
+
     @Override
     public String toString() {
         StringBuilder sb = new StringBuilder();
@@ -454,8 +533,10 @@
                 .append(", type: ").append(mServiceType)
                 .append(", subtypes: ").append(TextUtils.join(", ", mSubtypes))
                 .append(", hostAddresses: ").append(TextUtils.join(", ", mHostAddresses))
+                .append(", hostname: ").append(mHostname)
                 .append(", port: ").append(mPort)
-                .append(", network: ").append(mNetwork);
+                .append(", network: ").append(mNetwork)
+                .append(", expirationTime: ").append(mExpirationTime);
 
         byte[] txtRecord = getTxtRecord();
         sb.append(", txtRecord: ").append(new String(txtRecord, StandardCharsets.UTF_8));
@@ -494,6 +575,8 @@
         for (InetAddress address : mHostAddresses) {
             InetAddressUtils.parcelInetAddress(dest, address, flags);
         }
+        dest.writeString(mHostname);
+        dest.writeLong(mExpirationTime != null ? mExpirationTime.getEpochSecond() : -1);
     }
 
     /** Implement the Parcelable interface */
@@ -523,6 +606,9 @@
                 for (int i = 0; i < size; i++) {
                     info.mHostAddresses.add(InetAddressUtils.unparcelInetAddress(in));
                 }
+                info.mHostname = in.readString();
+                final long seconds = in.readLong();
+                info.setExpirationTime(seconds < 0 ? null : Instant.ofEpochSecond(seconds));
                 return info;
             }
 
diff --git a/framework/Android.bp b/framework/Android.bp
index 432cabf..aef0f74 100644
--- a/framework/Android.bp
+++ b/framework/Android.bp
@@ -15,6 +15,7 @@
 //
 
 package {
+    default_team: "trendy_team_fwk_core_networking",
     // See: http://go/android-license-faq
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
@@ -95,7 +96,6 @@
     ],
     impl_only_static_libs: [
         "net-utils-device-common-bpf",
-        "net-utils-device-common-struct",
     ],
     libs: [
         "androidx.annotation_annotation",
@@ -106,9 +106,6 @@
     apex_available: [
         "com.android.tethering",
     ],
-    lint: {
-        strict_updatability_linting: true,
-    },
 }
 
 java_library {
@@ -119,7 +116,6 @@
     static_libs: [
         "httpclient_api",
         "httpclient_impl",
-        "http_client_logging",
         // Framework-connectivity-pre-jarjar is identical to framework-connectivity
         // implementation, but without the jarjar rules. However, framework-connectivity
         // is not based on framework-connectivity-pre-jarjar, it's rebuilt from source
@@ -127,7 +123,6 @@
         // Even if the library is included in "impl_only_static_libs" of defaults. This is still
         // needed because java_library which doesn't understand "impl_only_static_libs".
         "net-utils-device-common-bpf",
-        "net-utils-device-common-struct",
     ],
     libs: [
         // This cannot be in the defaults clause above because if it were, it would be used
@@ -139,9 +134,6 @@
         "framework-wifi.stubs.module_lib",
     ],
     visibility: ["//packages/modules/Connectivity:__subpackages__"],
-    lint: {
-        baseline_filename: "lint-baseline.xml",
-    },
 }
 
 java_defaults {
@@ -152,7 +144,6 @@
     ],
     impl_only_static_libs: [
         "httpclient_impl",
-        "http_client_logging",
     ],
 }
 
@@ -172,17 +163,16 @@
         "//packages/modules/Connectivity/framework-t",
         "//packages/modules/Connectivity/service",
         "//packages/modules/Connectivity/service-t",
-        "//frameworks/base/packages/Connectivity/service",
         "//frameworks/base",
 
         // Tests using hidden APIs
         "//cts/tests/netlegacy22.api",
         "//cts/tests/tests/app.usage", // NetworkUsageStatsTest
         "//external/sl4a:__subpackages__",
-        "//frameworks/base/packages/Connectivity/tests:__subpackages__",
         "//frameworks/base/core/tests/bandwidthtests",
         "//frameworks/base/core/tests/benchmarks",
         "//frameworks/base/core/tests/utillib",
+        "//frameworks/base/services/tests/VpnTests",
         "//frameworks/base/tests/vcn",
         "//frameworks/opt/net/ethernet/tests:__subpackages__",
         "//frameworks/opt/telephony/tests/telephonytests",
@@ -200,6 +190,9 @@
     lint: {
         baseline_filename: "lint-baseline.xml",
     },
+    aconfig_declarations: [
+        "com.android.net.flags-aconfig",
+    ],
 }
 
 platform_compat_config {
@@ -259,9 +252,6 @@
     apex_available: [
         "com.android.tethering",
     ],
-    lint: {
-        baseline_filename: "lint-baseline.xml",
-    },
 }
 
 java_genrule {
@@ -322,9 +312,6 @@
 java_library {
     name: "framework-connectivity-module-api-stubs-including-flagged",
     srcs: [":framework-connectivity-module-api-stubs-including-flagged-droidstubs"],
-    lint: {
-        baseline_filename: "lint-baseline.xml",
-    },
 }
 
 // Library providing limited APIs within the connectivity module, so that R+ components like
@@ -349,7 +336,4 @@
     visibility: [
         "//packages/modules/Connectivity/Tethering:__subpackages__",
     ],
-    lint: {
-        baseline_filename: "lint-baseline.xml",
-    },
 }
diff --git a/framework/aidl-export/android/net/TetheringManager.aidl b/framework/aidl-export/android/net/TetheringManager.aidl
new file mode 100644
index 0000000..1235722
--- /dev/null
+++ b/framework/aidl-export/android/net/TetheringManager.aidl
@@ -0,0 +1,20 @@
+/**
+ *
+ * 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.
+ */
+
+package android.net;
+
+parcelable TetheringManager.TetheringRequest;
diff --git a/framework/aidl-export/android/net/nsd/DiscoveryRequest.aidl b/framework/aidl-export/android/net/nsd/DiscoveryRequest.aidl
new file mode 100644
index 0000000..481a066
--- /dev/null
+++ b/framework/aidl-export/android/net/nsd/DiscoveryRequest.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2023 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 android.net.nsd;
+
+@JavaOnlyStableParcelable parcelable DiscoveryRequest;
diff --git a/framework/api/current.txt b/framework/api/current.txt
index 6860c3c..ef8415c 100644
--- a/framework/api/current.txt
+++ b/framework/api/current.txt
@@ -315,6 +315,7 @@
     method @Nullable public android.net.NetworkSpecifier getNetworkSpecifier();
     method public int getOwnerUid();
     method public int getSignalStrength();
+    method @FlaggedApi("com.android.net.flags.request_restricted_wifi") @NonNull public java.util.Set<java.lang.Integer> getSubscriptionIds();
     method @Nullable public android.net.TransportInfo getTransportInfo();
     method public boolean hasCapability(int);
     method public boolean hasEnterpriseId(int);
@@ -332,6 +333,7 @@
     field public static final int NET_CAPABILITY_IA = 7; // 0x7
     field public static final int NET_CAPABILITY_IMS = 4; // 0x4
     field public static final int NET_CAPABILITY_INTERNET = 12; // 0xc
+    field @FlaggedApi("com.android.net.flags.net_capability_local_network") public static final int NET_CAPABILITY_LOCAL_NETWORK = 36; // 0x24
     field public static final int NET_CAPABILITY_MCX = 23; // 0x17
     field public static final int NET_CAPABILITY_MMS = 0; // 0x0
     field public static final int NET_CAPABILITY_MMTEL = 33; // 0x21
@@ -360,6 +362,7 @@
     field public static final int TRANSPORT_CELLULAR = 0; // 0x0
     field public static final int TRANSPORT_ETHERNET = 3; // 0x3
     field public static final int TRANSPORT_LOWPAN = 6; // 0x6
+    field @FlaggedApi("com.android.net.flags.support_transport_satellite") public static final int TRANSPORT_SATELLITE = 10; // 0xa
     field public static final int TRANSPORT_THREAD = 9; // 0x9
     field public static final int TRANSPORT_USB = 8; // 0x8
     field public static final int TRANSPORT_VPN = 4; // 0x4
@@ -418,6 +421,7 @@
     method public int describeContents();
     method @NonNull public int[] getCapabilities();
     method @Nullable public android.net.NetworkSpecifier getNetworkSpecifier();
+    method @FlaggedApi("com.android.net.flags.request_restricted_wifi") @NonNull public java.util.Set<java.lang.Integer> getSubscriptionIds();
     method @NonNull public int[] getTransportTypes();
     method public boolean hasCapability(int);
     method public boolean hasTransport(int);
@@ -437,6 +441,7 @@
     method @NonNull public android.net.NetworkRequest.Builder setIncludeOtherUidNetworks(boolean);
     method @Deprecated public android.net.NetworkRequest.Builder setNetworkSpecifier(String);
     method public android.net.NetworkRequest.Builder setNetworkSpecifier(android.net.NetworkSpecifier);
+    method @FlaggedApi("com.android.net.flags.request_restricted_wifi") @NonNull public android.net.NetworkRequest.Builder setSubscriptionIds(@NonNull java.util.Set<java.lang.Integer>);
   }
 
   public class ParseException extends java.lang.RuntimeException {
diff --git a/framework/api/system-current.txt b/framework/api/system-current.txt
index e812024..bef29a4 100644
--- a/framework/api/system-current.txt
+++ b/framework/api/system-current.txt
@@ -307,7 +307,6 @@
     method @NonNull public int[] getAdministratorUids();
     method @Nullable public static String getCapabilityCarrierName(int);
     method @Nullable public String getSsid();
-    method @NonNull public java.util.Set<java.lang.Integer> getSubscriptionIds();
     method @NonNull public int[] getTransportTypes();
     method @Nullable public java.util.List<android.net.Network> getUnderlyingNetworks();
     method public boolean isPrivateDnsBroken();
@@ -373,7 +372,6 @@
 
   public static class NetworkRequest.Builder {
     method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_SIGNAL_STRENGTH_WAKEUP) public android.net.NetworkRequest.Builder setSignalStrength(int);
-    method @NonNull public android.net.NetworkRequest.Builder setSubscriptionIds(@NonNull java.util.Set<java.lang.Integer>);
   }
 
   public final class NetworkScore implements android.os.Parcelable {
diff --git a/framework/jni/android_net_NetworkUtils.cpp b/framework/jni/android_net_NetworkUtils.cpp
index 51eaf1c..3779a00 100644
--- a/framework/jni/android_net_NetworkUtils.cpp
+++ b/framework/jni/android_net_NetworkUtils.cpp
@@ -255,6 +255,10 @@
     return bpf::isKernel64Bit();
 }
 
+static jboolean android_net_utils_isKernelX86(JNIEnv *env, jclass clazz) {
+    return bpf::isX86();
+}
+
 // ----------------------------------------------------------------------------
 
 /*
@@ -278,6 +282,7 @@
     { "setsockoptBytes", "(Ljava/io/FileDescriptor;II[B)V",
     (void*) android_net_utils_setsockoptBytes},
     { "isKernel64Bit", "()Z", (void*) android_net_utils_isKernel64Bit },
+    { "isKernelX86", "()Z", (void*) android_net_utils_isKernelX86 },
 };
 // clang-format on
 
diff --git a/framework/lint-baseline.xml b/framework/lint-baseline.xml
index f68aad7..2c0b15f 100644
--- a/framework/lint-baseline.xml
+++ b/framework/lint-baseline.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 8.0.0-dev" type="baseline" dependencies="true" variant="all" version="8.0.0-dev">
+<issues format="6" by="lint 8.4.0-alpha01" type="baseline" client="" dependencies="true" name="" variant="all" version="8.4.0-alpha01">
 
     <issue
         id="NewApi"
@@ -8,78 +8,177 @@
         errorLine2="                                                                      ~~~~~~~~~~~~~">
         <location
             file="packages/modules/Connectivity/framework/src/android/net/ConnectivityManager.java"
-            line="2456"
+            line="2490"
             column="71"/>
     </issue>
 
     <issue
         id="NewApi"
-        message="Call requires API level 31 (current min is 30): `android.net.Proxy#setHttpProxyConfiguration`"
-        errorLine1="                Proxy.setHttpProxyConfiguration(getInstance().getDefaultProxy());"
-        errorLine2="                      ~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="packages/modules/Connectivity/framework/src/android/net/ConnectivityManager.java"
-            line="5323"
-            column="23"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 31 (current min is 30): `android.os.Build#isDebuggable`"
-        errorLine1="            if (!Build.isDebuggable()) {"
-        errorLine2="                       ~~~~~~~~~~~~">
-        <location
-            file="packages/modules/Connectivity/framework/src/android/net/ConnectivitySettingsManager.java"
-            line="1072"
-            column="24"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 31 (current min is 30): `android.os.UserHandle#getUid`"
-        errorLine1="        final int end = nextUser.getUid(0 /* appId */) - 1;"
-        errorLine2="                                 ~~~~~~">
-        <location
-            file="packages/modules/Connectivity/framework/src/android/net/UidRange.java"
-            line="50"
-            column="34"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 31 (current min is 30): `android.os.UserHandle#getUid`"
-        errorLine1="        final int start = user.getUid(0 /* appId */);"
-        errorLine2="                               ~~~~~~">
-        <location
-            file="packages/modules/Connectivity/framework/src/android/net/UidRange.java"
-            line="49"
-            column="32"/>
-    </issue>
-
-    <issue
-        id="NewApi"
         message="Call requires API level 31 (current min is 30): `android.provider.Settings#checkAndNoteWriteSettingsOperation`"
         errorLine1="        return Settings.checkAndNoteWriteSettingsOperation(context, uid, callingPackage,"
         errorLine2="                        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
         <location
             file="packages/modules/Connectivity/framework/src/android/net/ConnectivityManager.java"
-            line="2799"
+            line="2853"
             column="25"/>
     </issue>
 
     <issue
         id="NewApi"
+        message="Call requires API level 31 (current min is 30): `android.net.Proxy#setHttpProxyConfiguration`"
+        errorLine1="                Proxy.setHttpProxyConfiguration(getInstance().getDefaultProxy());"
+        errorLine2="                      ~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="packages/modules/Connectivity/framework/src/android/net/ConnectivityManager.java"
+            line="5422"
+            column="23"/>
+    </issue>
+
+    <issue
+        id="NewApi"
         message="Call requires API level 31 (current min is 30): `java.net.InetAddress#clearDnsCache`"
         errorLine1="            InetAddress.clearDnsCache();"
         errorLine2="                        ~~~~~~~~~~~~~">
         <location
             file="packages/modules/Connectivity/framework/src/android/net/ConnectivityManager.java"
-            line="5329"
+            line="5428"
             column="25"/>
     </issue>
 
     <issue
         id="NewApi"
+        message="Call requires API level 31 (current min is 30): `libcore.net.event.NetworkEventDispatcher#dispatchNetworkConfigurationChange`"
+        errorLine1="            NetworkEventDispatcher.getInstance().dispatchNetworkConfigurationChange();"
+        errorLine2="                                                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="packages/modules/Connectivity/framework/src/android/net/ConnectivityManager.java"
+            line="5431"
+            column="50"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 31 (current min is 30): `libcore.net.event.NetworkEventDispatcher#getInstance`"
+        errorLine1="            NetworkEventDispatcher.getInstance().dispatchNetworkConfigurationChange();"
+        errorLine2="                                   ~~~~~~~~~~~">
+        <location
+            file="packages/modules/Connectivity/framework/src/android/net/ConnectivityManager.java"
+            line="5431"
+            column="36"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 31 (current min is 30): `android.os.Build#isDebuggable`"
+        errorLine1="            if (!Build.isDebuggable()) {"
+        errorLine2="                       ~~~~~~~~~~~~">
+        <location
+            file="packages/modules/Connectivity/framework/src/android/net/ConnectivitySettingsManager.java"
+            line="1095"
+            column="24"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Field requires API level 31 (current min is 30): `android.system.OsConstants#ENONET`"
+        errorLine1='                    new DnsException(ERROR_SYSTEM, new ErrnoException("resNetworkQuery", ENONET))));'
+        errorLine2="                                                                                         ~~~~~~">
+        <location
+            file="packages/modules/Connectivity/framework/src/android/net/DnsResolver.java"
+            line="367"
+            column="90"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 31 (current min is 30): `libcore.io.IoUtils#closeQuietly`"
+        errorLine1="            IoUtils.closeQuietly(socket);"
+        errorLine2="                    ~~~~~~~~~~~~">
+        <location
+            file="packages/modules/Connectivity/framework/src/android/net/util/DnsUtils.java"
+            line="181"
+            column="21"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 31 (current min is 30): `libcore.io.IoUtils#closeQuietly`"
+        errorLine1="            IoUtils.closeQuietly(socket);"
+        errorLine2="                    ~~~~~~~~~~~~">
+        <location
+            file="packages/modules/Connectivity/framework/src/android/net/util/DnsUtils.java"
+            line="373"
+            column="21"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 31 (current min is 30): `libcore.io.IoUtils#closeQuietly`"
+        errorLine1="                        IoUtils.closeQuietly(is);"
+        errorLine2="                                ~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/core/java/com/android/internal/util/FileRotator.java"
+            line="171"
+            column="33"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 31 (current min is 30): `libcore.io.IoUtils#closeQuietly`"
+        errorLine1="            IoUtils.closeQuietly(zos);"
+        errorLine2="                    ~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/core/java/com/android/internal/util/FileRotator.java"
+            line="178"
+            column="21"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 31 (current min is 30): `libcore.io.IoUtils#closeQuietly`"
+        errorLine1="            IoUtils.closeQuietly(bis);"
+        errorLine2="                    ~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/core/java/com/android/internal/util/FileRotator.java"
+            line="401"
+            column="21"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 31 (current min is 30): `libcore.io.IoUtils#closeQuietly`"
+        errorLine1="            IoUtils.closeQuietly(bos);"
+        errorLine2="                    ~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/core/java/com/android/internal/util/FileRotator.java"
+            line="416"
+            column="21"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 31 (current min is 30): `libcore.net.InetAddressUtils#isNumericAddress`"
+        errorLine1="        return InetAddressUtils.isNumericAddress(address);"
+        errorLine2="                                ~~~~~~~~~~~~~~~~">
+        <location
+            file="packages/modules/Connectivity/framework/src/android/net/InetAddresses.java"
+            line="46"
+            column="33"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 31 (current min is 30): `libcore.net.InetAddressUtils#parseNumericAddress`"
+        errorLine1="        return InetAddressUtils.parseNumericAddress(address);"
+        errorLine2="                                ~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="packages/modules/Connectivity/framework/src/android/net/InetAddresses.java"
+            line="63"
+            column="33"/>
+    </issue>
+
+    <issue
+        id="NewApi"
         message="Call requires API level 31 (current min is 30): `java.net.InetAddress#getAllByNameOnNet`"
         errorLine1="        return InetAddress.getAllByNameOnNet(host, getNetIdForResolv());"
         errorLine2="                           ~~~~~~~~~~~~~~~~~">
@@ -103,17 +202,6 @@
     <issue
         id="NewApi"
         message="Call requires API level 31 (current min is 30): `libcore.io.IoUtils#closeQuietly`"
-        errorLine1="                        IoUtils.closeQuietly(is);"
-        errorLine2="                                ~~~~~~~~~~~~">
-        <location
-            file="frameworks/base/core/java/com/android/internal/util/FileRotator.java"
-            line="168"
-            column="33"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 31 (current min is 30): `libcore.io.IoUtils#closeQuietly`"
         errorLine1="                        if (failed) IoUtils.closeQuietly(socket);"
         errorLine2="                                            ~~~~~~~~~~~~">
         <location
@@ -157,105 +245,6 @@
 
     <issue
         id="NewApi"
-        message="Call requires API level 31 (current min is 30): `libcore.io.IoUtils#closeQuietly`"
-        errorLine1="            IoUtils.closeQuietly(bis);"
-        errorLine2="                    ~~~~~~~~~~~~">
-        <location
-            file="frameworks/base/core/java/com/android/internal/util/FileRotator.java"
-            line="391"
-            column="21"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 31 (current min is 30): `libcore.io.IoUtils#closeQuietly`"
-        errorLine1="            IoUtils.closeQuietly(bos);"
-        errorLine2="                    ~~~~~~~~~~~~">
-        <location
-            file="frameworks/base/core/java/com/android/internal/util/FileRotator.java"
-            line="406"
-            column="21"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 31 (current min is 30): `libcore.io.IoUtils#closeQuietly`"
-        errorLine1="            IoUtils.closeQuietly(socket);"
-        errorLine2="                    ~~~~~~~~~~~~">
-        <location
-            file="packages/modules/Connectivity/framework/src/android/net/util/DnsUtils.java"
-            line="181"
-            column="21"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 31 (current min is 30): `libcore.io.IoUtils#closeQuietly`"
-        errorLine1="            IoUtils.closeQuietly(socket);"
-        errorLine2="                    ~~~~~~~~~~~~">
-        <location
-            file="packages/modules/Connectivity/framework/src/android/net/util/DnsUtils.java"
-            line="373"
-            column="21"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 31 (current min is 30): `libcore.io.IoUtils#closeQuietly`"
-        errorLine1="            IoUtils.closeQuietly(zos);"
-        errorLine2="                    ~~~~~~~~~~~~">
-        <location
-            file="frameworks/base/core/java/com/android/internal/util/FileRotator.java"
-            line="175"
-            column="21"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 31 (current min is 30): `libcore.net.InetAddressUtils#isNumericAddress`"
-        errorLine1="        return InetAddressUtils.isNumericAddress(address);"
-        errorLine2="                                ~~~~~~~~~~~~~~~~">
-        <location
-            file="packages/modules/Connectivity/framework/src/android/net/InetAddresses.java"
-            line="46"
-            column="33"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 31 (current min is 30): `libcore.net.InetAddressUtils#parseNumericAddress`"
-        errorLine1="        return InetAddressUtils.parseNumericAddress(address);"
-        errorLine2="                                ~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="packages/modules/Connectivity/framework/src/android/net/InetAddresses.java"
-            line="63"
-            column="33"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 31 (current min is 30): `libcore.net.event.NetworkEventDispatcher#dispatchNetworkConfigurationChange`"
-        errorLine1="            NetworkEventDispatcher.getInstance().dispatchNetworkConfigurationChange();"
-        errorLine2="                                                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="packages/modules/Connectivity/framework/src/android/net/ConnectivityManager.java"
-            line="5332"
-            column="50"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 31 (current min is 30): `libcore.net.event.NetworkEventDispatcher#getInstance`"
-        errorLine1="            NetworkEventDispatcher.getInstance().dispatchNetworkConfigurationChange();"
-        errorLine2="                                   ~~~~~~~~~~~">
-        <location
-            file="packages/modules/Connectivity/framework/src/android/net/ConnectivityManager.java"
-            line="5332"
-            column="36"/>
-    </issue>
-
-    <issue
-        id="NewApi"
         message="Call requires API level 31 (current min is 30): `libcore.net.http.HttpURLConnectionFactory#createInstance`"
         errorLine1="        HttpURLConnectionFactory urlConnectionFactory = HttpURLConnectionFactory.createInstance();"
         errorLine2="                                                                                 ~~~~~~~~~~~~~~">
@@ -267,17 +256,6 @@
 
     <issue
         id="NewApi"
-        message="Call requires API level 31 (current min is 30): `libcore.net.http.HttpURLConnectionFactory#openConnection`"
-        errorLine1="        return urlConnectionFactory.openConnection(url, socketFactory, proxy);"
-        errorLine2="                                    ~~~~~~~~~~~~~~">
-        <location
-            file="packages/modules/Connectivity/framework/src/android/net/Network.java"
-            line="372"
-            column="37"/>
-    </issue>
-
-    <issue
-        id="NewApi"
         message="Call requires API level 31 (current min is 30): `libcore.net.http.HttpURLConnectionFactory#setDns`"
         errorLine1="        urlConnectionFactory.setDns(dnsLookup); // Let traffic go via dnsLookup"
         errorLine2="                             ~~~~~~">
@@ -300,35 +278,13 @@
 
     <issue
         id="NewApi"
-        message="Call requires API level 31 (current min is 30): `new android.net.EthernetNetworkSpecifier`"
-        errorLine1="                    return setNetworkSpecifier(new EthernetNetworkSpecifier(networkSpecifier));"
-        errorLine2="                                               ~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        message="Call requires API level 31 (current min is 30): `libcore.net.http.HttpURLConnectionFactory#openConnection`"
+        errorLine1="        return urlConnectionFactory.openConnection(url, socketFactory, proxy);"
+        errorLine2="                                    ~~~~~~~~~~~~~~">
         <location
-            file="packages/modules/Connectivity/framework/src/android/net/NetworkRequest.java"
-            line="525"
-            column="48"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Cast from `EthernetNetworkSpecifier` to `NetworkSpecifier` requires API level 31 (current min is 30)"
-        errorLine1="                    return setNetworkSpecifier(new EthernetNetworkSpecifier(networkSpecifier));"
-        errorLine2="                                               ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="packages/modules/Connectivity/framework/src/android/net/NetworkRequest.java"
-            line="525"
-            column="48"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Class requires API level 31 (current min is 30): `android.telephony.data.EpsBearerQosSessionAttributes`"
-        errorLine1="                    (EpsBearerQosSessionAttributes)attributes));"
-        errorLine2="                     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="packages/modules/Connectivity/framework/src/android/net/NetworkAgent.java"
-            line="1421"
-            column="22"/>
+            file="packages/modules/Connectivity/framework/src/android/net/Network.java"
+            line="372"
+            column="37"/>
     </issue>
 
     <issue
@@ -338,18 +294,18 @@
         errorLine2="                                  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
         <location
             file="packages/modules/Connectivity/framework/src/android/net/NetworkAgent.java"
-            line="1418"
+            line="1462"
             column="35"/>
     </issue>
 
     <issue
         id="NewApi"
-        message="Class requires API level 31 (current min is 30): `android.telephony.data.NrQosSessionAttributes`"
-        errorLine1="                    (NrQosSessionAttributes)attributes));"
-        errorLine2="                     ~~~~~~~~~~~~~~~~~~~~~~">
+        message="Class requires API level 31 (current min is 30): `android.telephony.data.EpsBearerQosSessionAttributes`"
+        errorLine1="                    (EpsBearerQosSessionAttributes)attributes));"
+        errorLine2="                     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
         <location
             file="packages/modules/Connectivity/framework/src/android/net/NetworkAgent.java"
-            line="1425"
+            line="1465"
             column="22"/>
     </issue>
 
@@ -360,8 +316,63 @@
         errorLine2="                                         ~~~~~~~~~~~~~~~~~~~~~~">
         <location
             file="packages/modules/Connectivity/framework/src/android/net/NetworkAgent.java"
-            line="1422"
+            line="1466"
             column="42"/>
     </issue>
 
+    <issue
+        id="NewApi"
+        message="Class requires API level 31 (current min is 30): `android.telephony.data.NrQosSessionAttributes`"
+        errorLine1="                    (NrQosSessionAttributes)attributes));"
+        errorLine2="                     ~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="packages/modules/Connectivity/framework/src/android/net/NetworkAgent.java"
+            line="1469"
+            column="22"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 31 (current min is 30): `new android.net.EthernetNetworkSpecifier`"
+        errorLine1="                    return setNetworkSpecifier(new EthernetNetworkSpecifier(networkSpecifier));"
+        errorLine2="                                               ~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="packages/modules/Connectivity/framework/src/android/net/NetworkRequest.java"
+            line="553"
+            column="48"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Cast from `EthernetNetworkSpecifier` to `NetworkSpecifier` requires API level 31 (current min is 30)"
+        errorLine1="                    return setNetworkSpecifier(new EthernetNetworkSpecifier(networkSpecifier));"
+        errorLine2="                                               ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="packages/modules/Connectivity/framework/src/android/net/NetworkRequest.java"
+            line="553"
+            column="48"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 31 (current min is 30): `android.os.UserHandle#getUid`"
+        errorLine1="        final int start = user.getUid(0 /* appId */);"
+        errorLine2="                               ~~~~~~">
+        <location
+            file="packages/modules/Connectivity/framework/src/android/net/UidRange.java"
+            line="49"
+            column="32"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 31 (current min is 30): `android.os.UserHandle#getUid`"
+        errorLine1="        final int end = nextUser.getUid(0 /* appId */) - 1;"
+        errorLine2="                                 ~~~~~~">
+        <location
+            file="packages/modules/Connectivity/framework/src/android/net/UidRange.java"
+            line="50"
+            column="34"/>
+    </issue>
+
 </issues>
\ No newline at end of file
diff --git a/framework/src/android/net/BpfNetMapsUtils.java b/framework/src/android/net/BpfNetMapsUtils.java
index 11d610c..0be30bb 100644
--- a/framework/src/android/net/BpfNetMapsUtils.java
+++ b/framework/src/android/net/BpfNetMapsUtils.java
@@ -125,13 +125,11 @@
         return sj.toString();
     }
 
-    public static final boolean PRE_T = !SdkLevel.isAtLeastT();
-
     /**
      * Throw UnsupportedOperationException if SdkLevel is before T.
      */
     public static void throwIfPreT(final String msg) {
-        if (PRE_T) {
+        if (!SdkLevel.isAtLeastT()) {
             throw new UnsupportedOperationException(msg);
         }
     }
diff --git a/framework/src/android/net/ConnectivityManager.java b/framework/src/android/net/ConnectivityManager.java
index 1ea1815..915ec52 100644
--- a/framework/src/android/net/ConnectivityManager.java
+++ b/framework/src/android/net/ConnectivityManager.java
@@ -74,6 +74,7 @@
 import android.util.SparseIntArray;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.modules.utils.build.SdkLevel;
 
 import libcore.net.event.NetworkEventDispatcher;
 
@@ -6278,9 +6279,13 @@
     // Only the system server process and the network stack have access.
     @FlaggedApi(Flags.SUPPORT_IS_UID_NETWORKING_BLOCKED)
     @SystemApi(client = MODULE_LIBRARIES)
-    @RequiresApi(Build.VERSION_CODES.TIRAMISU)  // BPF maps were only mainlined in T
+    // Note b/326143935 kernel bug can trigger crash on some T device.
+    @RequiresApi(VERSION_CODES.UPSIDE_DOWN_CAKE)
     @RequiresPermission(NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK)
     public boolean isUidNetworkingBlocked(int uid, boolean isNetworkMetered) {
+        if (!SdkLevel.isAtLeastU()) {
+            Log.wtf(TAG, "isUidNetworkingBlocked is not supported on pre-U devices");
+        }
         final BpfNetMapsReader reader = BpfNetMapsReader.getInstance();
         // Note that before V, the data saver status in bpf is written by ConnectivityService
         // when receiving {@link #ACTION_RESTRICT_BACKGROUND_CHANGED}. Thus,
diff --git a/framework/src/android/net/NetworkCapabilities.java b/framework/src/android/net/NetworkCapabilities.java
index efae754..84a0d29 100644
--- a/framework/src/android/net/NetworkCapabilities.java
+++ b/framework/src/android/net/NetworkCapabilities.java
@@ -20,6 +20,7 @@
 import static com.android.net.module.util.BitUtils.appendStringRepresentationOfBitMaskToStringBuilder;
 import static com.android.net.module.util.BitUtils.describeDifferences;
 
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.LongDef;
 import android.annotation.NonNull;
@@ -29,9 +30,6 @@
 import android.annotation.SystemApi;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.net.ConnectivityManager.NetworkCallback;
-// Can't be imported because aconfig tooling doesn't exist on udc-mainline-prod yet
-// See inner class Flags which mimics this for the time being
-// import android.net.flags.Flags;
 import android.os.Build;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -130,6 +128,12 @@
     public static class Flags {
         static final String FLAG_FORBIDDEN_CAPABILITY =
                 "com.android.net.flags.forbidden_capability";
+        static final String FLAG_NET_CAPABILITY_LOCAL_NETWORK =
+                "com.android.net.flags.net_capability_local_network";
+        static final String REQUEST_RESTRICTED_WIFI =
+                "com.android.net.flags.request_restricted_wifi";
+        static final String SUPPORT_TRANSPORT_SATELLITE =
+                "com.android.net.flags.support_transport_satellite";
     }
 
     /**
@@ -716,17 +720,24 @@
     public static final int NET_CAPABILITY_PRIORITIZE_BANDWIDTH = 35;
 
     /**
-     * This is a local network, e.g. a tethering downstream or a P2P direct network.
+     * Indicates that this network is a local network.
      *
-     * <p>
-     * Note that local networks are not sent to callbacks by default. To receive callbacks about
-     * them, the {@link NetworkRequest} instance must be prepared to see them, either by
-     * adding the capability with {@link NetworkRequest.Builder#addCapability}, by removing
-     * this forbidden capability with {@link NetworkRequest.Builder#removeForbiddenCapability},
-     * or by clearing all capabilites with {@link NetworkRequest.Builder#clearCapabilities()}.
-     * </p>
-     * @hide
+     * Local networks are networks where the device is not obtaining IP addresses from the
+     * network, but advertising IP addresses itself. Examples of local networks are:
+     * <ul>
+     * <li>USB tethering or Wi-Fi hotspot networks to which the device is sharing its Internet
+     * connectivity.
+     * <li>Thread networks where the current device is the Thread Border Router.
+     * <li>Wi-Fi P2P networks where the current device is the Group Owner.
+     * </ul>
+     *
+     * Networks used to obtain Internet access are never local networks.
+     *
+     * Apps that target an SDK before {@link Build.VERSION_CODES.VANILLA_ICE_CREAM} will not see
+     * networks with this capability unless they explicitly set the NET_CAPABILITY_LOCAL_NETWORK
+     * in their NetworkRequests.
      */
+    @FlaggedApi(Flags.FLAG_NET_CAPABILITY_LOCAL_NETWORK)
     public static final int NET_CAPABILITY_LOCAL_NETWORK = 36;
 
     private static final int MAX_NET_CAPABILITY = NET_CAPABILITY_LOCAL_NETWORK;
@@ -1257,6 +1268,7 @@
             TRANSPORT_TEST,
             TRANSPORT_USB,
             TRANSPORT_THREAD,
+            TRANSPORT_SATELLITE,
     })
     public @interface Transport { }
 
@@ -1313,10 +1325,16 @@
      */
     public static final int TRANSPORT_THREAD = 9;
 
+    /**
+     * Indicates this network uses a Satellite transport.
+     */
+    @FlaggedApi(Flags.SUPPORT_TRANSPORT_SATELLITE)
+    public static final int TRANSPORT_SATELLITE = 10;
+
     /** @hide */
     public static final int MIN_TRANSPORT = TRANSPORT_CELLULAR;
     /** @hide */
-    public static final int MAX_TRANSPORT = TRANSPORT_THREAD;
+    public static final int MAX_TRANSPORT = TRANSPORT_SATELLITE;
 
     private static final int ALL_VALID_TRANSPORTS;
     static {
@@ -1343,6 +1361,7 @@
         "TEST",
         "USB",
         "THREAD",
+        "SATELLITE",
     };
 
     /**
@@ -1751,9 +1770,13 @@
     public @NonNull NetworkCapabilities setNetworkSpecifier(
             @NonNull NetworkSpecifier networkSpecifier) {
         if (networkSpecifier != null
-                // Transport can be test, or test + a single other transport
+                // Transport can be test, or test + a single other transport or cellular + satellite
+                // transport. Note: cellular + satellite combination is allowed since both transport
+                // use the same specifier, TelephonyNetworkSpecifier.
                 && mTransportTypes != (1L << TRANSPORT_TEST)
-                && Long.bitCount(mTransportTypes & ~(1L << TRANSPORT_TEST)) != 1) {
+                && Long.bitCount(mTransportTypes & ~(1L << TRANSPORT_TEST)) != 1
+                && (mTransportTypes & ~(1L << TRANSPORT_TEST))
+                != (1 << TRANSPORT_CELLULAR | 1 << TRANSPORT_SATELLITE)) {
             throw new IllegalStateException("Must have a single non-test transport specified to "
                     + "use setNetworkSpecifier");
         }
@@ -2794,10 +2817,9 @@
      * receiver holds the NETWORK_FACTORY permission. In all other cases, it will be the empty set.
      *
      * @return
-     * @hide
      */
     @NonNull
-    @SystemApi
+    @FlaggedApi(Flags.REQUEST_RESTRICTED_WIFI)
     public Set<Integer> getSubscriptionIds() {
         return new ArraySet<>(mSubIds);
     }
diff --git a/framework/src/android/net/NetworkRequest.java b/framework/src/android/net/NetworkRequest.java
index 653e41d..4de02ac 100644
--- a/framework/src/android/net/NetworkRequest.java
+++ b/framework/src/android/net/NetworkRequest.java
@@ -34,6 +34,7 @@
 import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED;
 import static android.net.NetworkCapabilities.TRANSPORT_TEST;
 
+import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
@@ -145,6 +146,12 @@
  * Look up the specific capability to learn whether its usage requires this self-certification.
  */
 public class NetworkRequest implements Parcelable {
+
+    /** @hide */
+    public static class Flags {
+        static final String REQUEST_RESTRICTED_WIFI =
+                "com.android.net.flags.request_restricted_wifi";
+    }
     /**
      * The first requestId value that will be allocated.
      * @hide only used by ConnectivityService.
@@ -630,10 +637,9 @@
          * NETWORK_FACTORY permission.
          *
          * @param subIds A {@code Set} that represents subscription IDs.
-         * @hide
          */
         @NonNull
-        @SystemApi
+        @FlaggedApi(Flags.REQUEST_RESTRICTED_WIFI)
         public Builder setSubscriptionIds(@NonNull Set<Integer> subIds) {
             mNetworkCapabilities.setSubscriptionIds(subIds);
             return this;
@@ -890,4 +896,17 @@
         // a new array.
         return networkCapabilities.getTransportTypes();
     }
+
+    /**
+     * Gets all the subscription ids set on this {@code NetworkRequest} instance.
+     *
+     * @return Set of Integer values for this instance.
+     */
+    @NonNull
+    @FlaggedApi(Flags.REQUEST_RESTRICTED_WIFI)
+    public Set<Integer> getSubscriptionIds() {
+        // No need to make a defensive copy here as NC#getSubscriptionIds() already returns
+        // a new set.
+        return networkCapabilities.getSubscriptionIds();
+    }
 }
diff --git a/framework/src/android/net/NetworkUtils.java b/framework/src/android/net/NetworkUtils.java
index 785c029..18feb84 100644
--- a/framework/src/android/net/NetworkUtils.java
+++ b/framework/src/android/net/NetworkUtils.java
@@ -440,4 +440,7 @@
 
     /** Returns whether the Linux Kernel is 64 bit */
     public static native boolean isKernel64Bit();
+
+    /** Returns whether the Linux Kernel is x86 */
+    public static native boolean isKernelX86();
 }
diff --git a/nearby/apex/Android.bp b/nearby/apex/Android.bp
index d7f063a..5fdf5c9 100644
--- a/nearby/apex/Android.bp
+++ b/nearby/apex/Android.bp
@@ -12,6 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 package {
+    default_team: "trendy_team_fwk_core_networking",
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
diff --git a/nearby/framework/Android.bp b/nearby/framework/Android.bp
index 4bb9efd..4be102c 100644
--- a/nearby/framework/Android.bp
+++ b/nearby/framework/Android.bp
@@ -13,6 +13,7 @@
 // limitations under the License.
 
 package {
+    default_team: "trendy_team_fwk_core_networking",
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
@@ -49,6 +50,7 @@
         "androidx.annotation_annotation",
         "framework-annotations-lib",
         "framework-bluetooth",
+        "framework-location.stubs.module_lib",
     ],
     static_libs: [
         "modules-utils-preconditions",
diff --git a/nearby/framework/java/android/nearby/INearbyManager.aidl b/nearby/framework/java/android/nearby/INearbyManager.aidl
index 7af271e..21ae0ac 100644
--- a/nearby/framework/java/android/nearby/INearbyManager.aidl
+++ b/nearby/framework/java/android/nearby/INearbyManager.aidl
@@ -20,6 +20,7 @@
 import android.nearby.IScanListener;
 import android.nearby.BroadcastRequestParcelable;
 import android.nearby.ScanRequest;
+import android.nearby.PoweredOffFindingEphemeralId;
 import android.nearby.aidl.IOffloadCallback;
 
 /**
@@ -40,4 +41,10 @@
     void stopBroadcast(in IBroadcastListener callback, String packageName, @nullable String attributionTag);
 
     void queryOffloadCapability(in IOffloadCallback callback) ;
-}
\ No newline at end of file
+
+    void setPoweredOffFindingEphemeralIds(in List<PoweredOffFindingEphemeralId> eids);
+
+    void setPoweredOffModeEnabled(boolean enabled);
+
+    boolean getPoweredOffModeEnabled();
+}
diff --git a/nearby/framework/java/android/nearby/NearbyManager.java b/nearby/framework/java/android/nearby/NearbyManager.java
index 00f1c38..cae653d 100644
--- a/nearby/framework/java/android/nearby/NearbyManager.java
+++ b/nearby/framework/java/android/nearby/NearbyManager.java
@@ -18,6 +18,7 @@
 
 import android.Manifest;
 import android.annotation.CallbackExecutor;
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -25,9 +26,12 @@
 import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
 import android.annotation.SystemService;
+import android.bluetooth.BluetoothManager;
 import android.content.Context;
+import android.location.LocationManager;
 import android.nearby.aidl.IOffloadCallback;
 import android.os.RemoteException;
+import android.os.SystemProperties;
 import android.provider.Settings;
 import android.util.Log;
 
@@ -37,6 +41,7 @@
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.lang.ref.WeakReference;
+import java.util.List;
 import java.util.Objects;
 import java.util.WeakHashMap;
 import java.util.concurrent.Executor;
@@ -75,8 +80,51 @@
         int ERROR = 2;
     }
 
+    /**
+     * Return value of {@link #getPoweredOffFindingMode()} when this powered off finding is not
+     * supported the device.
+     */
+    @FlaggedApi("com.android.nearby.flags.powered_off_finding")
+    public static final int POWERED_OFF_FINDING_MODE_UNSUPPORTED = 0;
+
+    /**
+     * Return value of {@link #getPoweredOffFindingMode()} and argument of {@link
+     * #setPoweredOffFindingMode(int)} when powered off finding is supported but disabled. The
+     * device will not start to advertise when powered off.
+     */
+    @FlaggedApi("com.android.nearby.flags.powered_off_finding")
+    public static final int POWERED_OFF_FINDING_MODE_DISABLED = 1;
+
+    /**
+     * Return value of {@link #getPoweredOffFindingMode()} and argument of {@link
+     * #setPoweredOffFindingMode(int)} when powered off finding is enabled. The device will start to
+     * advertise when powered off.
+     */
+    @FlaggedApi("com.android.nearby.flags.powered_off_finding")
+    public static final int POWERED_OFF_FINDING_MODE_ENABLED = 2;
+
+    /**
+     * Powered off finding modes.
+     *
+     * @hide
+     */
+    @IntDef(
+            prefix = {"POWERED_OFF_FINDING_MODE"},
+            value = {
+                    POWERED_OFF_FINDING_MODE_UNSUPPORTED,
+                    POWERED_OFF_FINDING_MODE_DISABLED,
+                    POWERED_OFF_FINDING_MODE_ENABLED,
+            })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface PoweredOffFindingMode {}
+
     private static final String TAG = "NearbyManager";
 
+    private static final int POWERED_OFF_FINDING_EID_LENGTH = 20;
+
+    private static final String POWER_OFF_FINDING_SUPPORTED_PROPERTY =
+            "ro.bluetooth.finder.supported";
+
     /**
      * TODO(b/286137024): Remove this when CTS R5 is rolled out.
      * Whether allows Fast Pair to scan.
@@ -456,4 +504,124 @@
                 "successfully %s Fast Pair scan", enable ? "enables" : "disables"));
     }
 
+    /**
+     * Sets the precomputed EIDs for advertising when the phone is powered off. The Bluetooth
+     * controller will store these EIDs in its memory, and will start advertising them in Find My
+     * Device network EID frames when powered off, only if the powered off finding mode was
+     * previously enabled by calling {@link #setPoweredOffFindingMode(int)}.
+     *
+     * <p>The EIDs are cryptographic ephemeral identifiers that change periodically, based on the
+     * Android clock at the time of the shutdown. They are used as the public part of asymmetric key
+     * pairs. Members of the Find My Device network can use them to encrypt the location of where
+     * they sight the advertising device. Only someone in possession of the private key (the device
+     * owner or someone that the device owner shared the key with) can decrypt this encrypted
+     * location.
+     *
+     * <p>Android will typically call this method during the shutdown process. Even after the
+     * method was called, it is still possible to call {#link setPoweredOffFindingMode() to disable
+     * the advertisement, for example to temporarily disable it for a single shutdown.
+     *
+     * <p>If called more than once, the EIDs of the most recent call overrides the EIDs from any
+     * previous call.
+     *
+     * @throws IllegalArgumentException if the length of one of the EIDs is not 20 bytes
+     */
+    @FlaggedApi("com.android.nearby.flags.powered_off_finding")
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
+    public void setPoweredOffFindingEphemeralIds(@NonNull List<byte[]> eids) {
+        Objects.requireNonNull(eids);
+        if (!isPoweredOffFindingSupported()) {
+            throw new UnsupportedOperationException(
+                    "Powered off finding is not supported on this device");
+        }
+        List<PoweredOffFindingEphemeralId> ephemeralIdList = eids.stream().map(
+                eid -> {
+                    Preconditions.checkArgument(eid.length == POWERED_OFF_FINDING_EID_LENGTH);
+                    PoweredOffFindingEphemeralId ephemeralId = new PoweredOffFindingEphemeralId();
+                    ephemeralId.bytes = eid;
+                    return ephemeralId;
+                }).toList();
+        try {
+            mService.setPoweredOffFindingEphemeralIds(ephemeralIdList);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+
+    }
+
+    /**
+     * Turns the powered off finding on or off. Power off finding will operate only if this method
+     * was called at least once since boot, and the value of the argument {@code
+     * poweredOffFindinMode} was {@link #POWERED_OFF_FINDING_MODE_ENABLED} the last time the method
+     * was called.
+     *
+     * <p>When an Android device with the powered off finding feature is turned off (either as part
+     * of a normal shutdown or due to dead battery), its Bluetooth chip starts to advertise Find My
+     * Device network EID frames with the EID payload that were provided by the last call to {@link
+     * #setPoweredOffFindingEphemeralIds(List)}. These EIDs can be sighted by other Android devices
+     * in BLE range that are part of the Find My Device network. The Android sighters use the EID to
+     * encrypt the location of the Android device and upload it to the server, in a way that only
+     * the owner of the advertising device, or people that the owner shared their encryption key
+     * with, can decrypt the location.
+     *
+     * @param poweredOffFindingMode {@link #POWERED_OFF_FINDING_MODE_ENABLED} or {@link
+     * #POWERED_OFF_FINDING_MODE_DISABLED}
+     *
+     * @throws IllegalStateException if called with {@link #POWERED_OFF_FINDING_MODE_ENABLED} when
+     * Bluetooth or location services are disabled
+     */
+    @FlaggedApi("com.android.nearby.flags.powered_off_finding")
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
+    public void setPoweredOffFindingMode(@PoweredOffFindingMode int poweredOffFindingMode) {
+        Preconditions.checkArgument(
+                poweredOffFindingMode == POWERED_OFF_FINDING_MODE_ENABLED
+                        || poweredOffFindingMode == POWERED_OFF_FINDING_MODE_DISABLED,
+                "invalid poweredOffFindingMode");
+        if (!isPoweredOffFindingSupported()) {
+            throw new UnsupportedOperationException(
+                    "Powered off finding is not supported on this device");
+        }
+        if (poweredOffFindingMode == POWERED_OFF_FINDING_MODE_ENABLED) {
+            Preconditions.checkState(areLocationAndBluetoothEnabled(),
+                    "Location services and Bluetooth must be on");
+        }
+        try {
+            mService.setPoweredOffModeEnabled(
+                    poweredOffFindingMode == POWERED_OFF_FINDING_MODE_ENABLED);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Returns the state of the powered off finding feature.
+     *
+     * <p>{@link #POWERED_OFF_FINDING_MODE_UNSUPPORTED} if the feature is not supported by the
+     * device, {@link #POWERED_OFF_FINDING_MODE_DISABLED} if this was the last value set by {@link
+     * #setPoweredOffFindingMode(int)} or if no value was set since boot, {@link
+     * #POWERED_OFF_FINDING_MODE_ENABLED} if this was the last value set by {@link
+     * #setPoweredOffFindingMode(int)}
+     */
+    @FlaggedApi("com.android.nearby.flags.powered_off_finding")
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
+    public @PoweredOffFindingMode int getPoweredOffFindingMode() {
+        if (!isPoweredOffFindingSupported()) {
+            return POWERED_OFF_FINDING_MODE_UNSUPPORTED;
+        }
+        try {
+            return mService.getPoweredOffModeEnabled()
+                    ? POWERED_OFF_FINDING_MODE_ENABLED : POWERED_OFF_FINDING_MODE_DISABLED;
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    private boolean isPoweredOffFindingSupported() {
+        return Boolean.parseBoolean(SystemProperties.get(POWER_OFF_FINDING_SUPPORTED_PROPERTY));
+    }
+
+    private boolean areLocationAndBluetoothEnabled() {
+        return mContext.getSystemService(BluetoothManager.class).getAdapter().isEnabled()
+                && mContext.getSystemService(LocationManager.class).isLocationEnabled();
+    }
 }
diff --git a/nearby/framework/java/android/nearby/PoweredOffFindingEphemeralId.aidl b/nearby/framework/java/android/nearby/PoweredOffFindingEphemeralId.aidl
new file mode 100644
index 0000000..9f4bfef
--- /dev/null
+++ b/nearby/framework/java/android/nearby/PoweredOffFindingEphemeralId.aidl
@@ -0,0 +1,26 @@
+/*
+ * 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.
+ */
+
+package android.nearby;
+
+/**
+ * Find My Device network ephemeral ID for powered off finding.
+ *
+ * @hide
+ */
+parcelable PoweredOffFindingEphemeralId {
+    byte[20] bytes;
+}
diff --git a/nearby/service/Android.bp b/nearby/service/Android.bp
index 17b80b0..749113d 100644
--- a/nearby/service/Android.bp
+++ b/nearby/service/Android.bp
@@ -13,6 +13,7 @@
 // limitations under the License.
 
 package {
+    default_team: "trendy_team_fwk_core_networking",
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
@@ -42,6 +43,7 @@
     ],
     static_libs: [
         "androidx.core_core",
+        "android.hardware.bluetooth.finder-V1-java",
         "guava",
         "libprotobuf-java-lite",
         "modules-utils-build",
diff --git a/nearby/service/java/com/android/server/nearby/NearbyService.java b/nearby/service/java/com/android/server/nearby/NearbyService.java
index 3c183ec..1575f07 100644
--- a/nearby/service/java/com/android/server/nearby/NearbyService.java
+++ b/nearby/service/java/com/android/server/nearby/NearbyService.java
@@ -35,12 +35,14 @@
 import android.nearby.INearbyManager;
 import android.nearby.IScanListener;
 import android.nearby.NearbyManager;
+import android.nearby.PoweredOffFindingEphemeralId;
 import android.nearby.ScanRequest;
 import android.nearby.aidl.IOffloadCallback;
 import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.nearby.injector.Injector;
+import com.android.server.nearby.managers.BluetoothFinderManager;
 import com.android.server.nearby.managers.BroadcastProviderManager;
 import com.android.server.nearby.managers.DiscoveryManager;
 import com.android.server.nearby.managers.DiscoveryProviderManager;
@@ -50,6 +52,8 @@
 import com.android.server.nearby.util.permissions.BroadcastPermissions;
 import com.android.server.nearby.util.permissions.DiscoveryPermissions;
 
+import java.util.List;
+
 /** Service implementing nearby functionality. */
 public class NearbyService extends INearbyManager.Stub {
     public static final String TAG = "NearbyService";
@@ -79,6 +83,7 @@
             };
     private final DiscoveryManager mDiscoveryProviderManager;
     private final BroadcastProviderManager mBroadcastProviderManager;
+    private final BluetoothFinderManager mBluetoothFinderManager;
 
     public NearbyService(Context context) {
         mContext = context;
@@ -90,6 +95,7 @@
                 mNearbyConfiguration.refactorDiscoveryManager()
                         ? new DiscoveryProviderManager(context, mInjector)
                         : new DiscoveryProviderManagerLegacy(context, mInjector);
+        mBluetoothFinderManager = new BluetoothFinderManager();
     }
 
     @VisibleForTesting
@@ -148,6 +154,30 @@
         mDiscoveryProviderManager.queryOffloadCapability(callback);
     }
 
+    @Override
+    public void setPoweredOffFindingEphemeralIds(List<PoweredOffFindingEphemeralId> eids) {
+        // Permissions check
+        enforceBluetoothPrivilegedPermission(mContext);
+
+        mBluetoothFinderManager.sendEids(eids);
+    }
+
+    @Override
+    public void setPoweredOffModeEnabled(boolean enabled) {
+        // Permissions check
+        enforceBluetoothPrivilegedPermission(mContext);
+
+        mBluetoothFinderManager.setPoweredOffFinderMode(enabled);
+    }
+
+    @Override
+    public boolean getPoweredOffModeEnabled() {
+        // Permissions check
+        enforceBluetoothPrivilegedPermission(mContext);
+
+        return mBluetoothFinderManager.getPoweredOffFinderMode();
+    }
+
     /**
      * Called by the service initializer.
      *
diff --git a/nearby/service/java/com/android/server/nearby/managers/BluetoothFinderManager.java b/nearby/service/java/com/android/server/nearby/managers/BluetoothFinderManager.java
new file mode 100644
index 0000000..365b099
--- /dev/null
+++ b/nearby/service/java/com/android/server/nearby/managers/BluetoothFinderManager.java
@@ -0,0 +1,166 @@
+/*
+ * 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.
+ */
+
+package com.android.server.nearby.managers;
+
+import static com.android.server.nearby.NearbyService.TAG;
+
+import android.annotation.TargetApi;
+import android.hardware.bluetooth.finder.Eid;
+import android.hardware.bluetooth.finder.IBluetoothFinder;
+import android.nearby.PoweredOffFindingEphemeralId;
+import android.os.Build;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.ServiceSpecificException;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.modules.utils.build.SdkLevel;
+
+import java.util.List;
+
+/** Connects to {@link IBluetoothFinder} HAL and invokes its API. */
+@TargetApi(Build.VERSION_CODES.TIRAMISU)
+public class BluetoothFinderManager {
+
+    private static final String HAL_INSTANCE_NAME = IBluetoothFinder.DESCRIPTOR + "/default";
+
+    private IBluetoothFinder mBluetoothFinder;
+    private IBinder.DeathRecipient mServiceDeathRecipient;
+    private final Object mLock = new Object();
+
+    private boolean initBluetoothFinderHal() {
+        final String methodStr = "initBluetoothFinderHal";
+        if (!SdkLevel.isAtLeastV()) return false;
+        synchronized (mLock) {
+            if (mBluetoothFinder != null) {
+                Log.i(TAG, "Bluetooth Finder HAL is already initialized");
+                return true;
+            }
+            try {
+                mBluetoothFinder = getServiceMockable();
+                if (mBluetoothFinder == null) {
+                    Log.e(TAG, "Unable to obtain IBluetoothFinder");
+                    return false;
+                }
+                Log.i(TAG, "Obtained IBluetoothFinder. Local ver: " + IBluetoothFinder.VERSION
+                        + ", Remote ver: " + mBluetoothFinder.getInterfaceVersion());
+
+                IBinder serviceBinder = getServiceBinderMockable();
+                if (serviceBinder == null) {
+                    Log.e(TAG, "Unable to obtain the service binder for IBluetoothFinder");
+                    return false;
+                }
+                mServiceDeathRecipient = new BluetoothFinderDeathRecipient();
+                serviceBinder.linkToDeath(mServiceDeathRecipient, /* flags= */ 0);
+
+                Log.i(TAG, "Bluetooth Finder HAL initialization was successful");
+                return true;
+            } catch (RemoteException e) {
+                handleRemoteException(e, methodStr);
+            } catch (Exception e) {
+                Log.e(TAG, methodStr + " encountered an exception: "  + e);
+            }
+            return false;
+        }
+    }
+
+    @VisibleForTesting
+    protected IBluetoothFinder getServiceMockable() {
+        return IBluetoothFinder.Stub.asInterface(
+                ServiceManager.waitForDeclaredService(HAL_INSTANCE_NAME));
+    }
+
+    @VisibleForTesting
+    protected IBinder getServiceBinderMockable() {
+        return mBluetoothFinder.asBinder();
+    }
+
+    private class BluetoothFinderDeathRecipient implements IBinder.DeathRecipient {
+        @Override
+        public void binderDied() {
+            Log.e(TAG, "BluetoothFinder service died.");
+            synchronized (mLock) {
+                mBluetoothFinder = null;
+            }
+        }
+    }
+
+    /** See comments for {@link IBluetoothFinder#sendEids(Eid[])} */
+    public void sendEids(List<PoweredOffFindingEphemeralId> eids) {
+        final String methodStr = "sendEids";
+        if (!checkHalAndLogFailure(methodStr)) return;
+        Eid[] eidArray = eids.stream().map(
+                ephmeralId -> {
+                    Eid eid = new Eid();
+                    eid.bytes = ephmeralId.bytes;
+                    return eid;
+                }).toArray(Eid[]::new);
+        try {
+            mBluetoothFinder.sendEids(eidArray);
+        } catch (RemoteException e) {
+            handleRemoteException(e, methodStr);
+        } catch (ServiceSpecificException e) {
+            handleServiceSpecificException(e, methodStr);
+        }
+    }
+
+    /** See comments for {@link IBluetoothFinder#setPoweredOffFinderMode(boolean)} */
+    public void setPoweredOffFinderMode(boolean enable) {
+        final String methodStr = "setPoweredOffMode";
+        if (!checkHalAndLogFailure(methodStr)) return;
+        try {
+            mBluetoothFinder.setPoweredOffFinderMode(enable);
+        } catch (RemoteException e) {
+            handleRemoteException(e, methodStr);
+        } catch (ServiceSpecificException e) {
+            handleServiceSpecificException(e, methodStr);
+        }
+    }
+
+    /** See comments for {@link IBluetoothFinder#getPoweredOffFinderMode()} */
+    public boolean getPoweredOffFinderMode() {
+        final String methodStr = "getPoweredOffMode";
+        if (!checkHalAndLogFailure(methodStr)) return false;
+        try {
+            return mBluetoothFinder.getPoweredOffFinderMode();
+        } catch (RemoteException e) {
+            handleRemoteException(e, methodStr);
+        } catch (ServiceSpecificException e) {
+            handleServiceSpecificException(e, methodStr);
+        }
+        return false;
+    }
+
+    private boolean checkHalAndLogFailure(String methodStr) {
+        if ((mBluetoothFinder == null) && !initBluetoothFinderHal()) {
+            Log.e(TAG, "Unable to call " + methodStr + " because IBluetoothFinder is null.");
+            return false;
+        }
+        return true;
+    }
+
+    private void handleRemoteException(RemoteException e, String methodStr) {
+        mBluetoothFinder = null;
+        Log.e(TAG, methodStr + " failed with remote exception: " + e);
+    }
+
+    private void handleServiceSpecificException(ServiceSpecificException e, String methodStr) {
+        Log.e(TAG, methodStr + " failed with service-specific exception: " + e);
+    }
+}
diff --git a/nearby/service/lint-baseline.xml b/nearby/service/lint-baseline.xml
index a4761ab..3477594 100644
--- a/nearby/service/lint-baseline.xml
+++ b/nearby/service/lint-baseline.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 8.0.0-dev" type="baseline" dependencies="true" variant="all" version="8.0.0-dev">
+<issues format="6" by="lint 8.4.0-alpha01" type="baseline" client="" dependencies="true" name="" variant="all" version="8.4.0-alpha01">
 
     <issue
         id="NewApi"
@@ -8,7 +8,7 @@
         errorLine2="                                                     ~~~~~~~~~~~~">
         <location
             file="packages/modules/Connectivity/nearby/service/java/com/android/server/nearby/provider/ChreCommunication.java"
-            line="263"
+            line="289"
             column="54"/>
     </issue>
 
diff --git a/nearby/service/proto/Android.bp b/nearby/service/proto/Android.bp
index 1b00cf6..be5a0b3 100644
--- a/nearby/service/proto/Android.bp
+++ b/nearby/service/proto/Android.bp
@@ -12,6 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 package {
+    default_team: "trendy_team_fwk_core_networking",
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
@@ -41,4 +42,4 @@
     apex_available: [
         "com.android.tethering",
     ],
-}
\ No newline at end of file
+}
diff --git a/nearby/tests/cts/fastpair/Android.bp b/nearby/tests/cts/fastpair/Android.bp
index 4309d7e..8009303 100644
--- a/nearby/tests/cts/fastpair/Android.bp
+++ b/nearby/tests/cts/fastpair/Android.bp
@@ -13,6 +13,7 @@
 // limitations under the License.
 
 package {
+    default_team: "trendy_team_fwk_core_networking",
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
@@ -33,6 +34,7 @@
         "framework-bluetooth.stubs.module_lib",
         "framework-configinfrastructure",
         "framework-connectivity-t.impl",
+        "framework-location.stubs.module_lib",
     ],
     srcs: ["src/**/*.java"],
     test_suites: [
diff --git a/nearby/tests/cts/fastpair/src/android/nearby/cts/NearbyManagerTest.java b/nearby/tests/cts/fastpair/src/android/nearby/cts/NearbyManagerTest.java
index bc9691d..832ac03 100644
--- a/nearby/tests/cts/fastpair/src/android/nearby/cts/NearbyManagerTest.java
+++ b/nearby/tests/cts/fastpair/src/android/nearby/cts/NearbyManagerTest.java
@@ -25,12 +25,14 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.junit.Assert.assertThrows;
+import static org.junit.Assume.assumeTrue;
 
 import android.app.UiAutomation;
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothManager;
 import android.bluetooth.cts.BTAdapterUtils;
 import android.content.Context;
+import android.location.LocationManager;
 import android.nearby.BroadcastCallback;
 import android.nearby.BroadcastRequest;
 import android.nearby.NearbyDevice;
@@ -42,6 +44,8 @@
 import android.nearby.ScanCallback;
 import android.nearby.ScanRequest;
 import android.os.Build;
+import android.os.Process;
+import android.os.UserHandle;
 import android.provider.DeviceConfig;
 
 import androidx.annotation.NonNull;
@@ -50,6 +54,7 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SdkSuppress;
 
+import com.android.compatibility.common.util.SystemUtil;
 import com.android.modules.utils.build.SdkLevel;
 
 import org.junit.Before;
@@ -57,6 +62,7 @@
 import org.junit.runner.RunWith;
 
 import java.util.Collections;
+import java.util.List;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.Executor;
 import java.util.concurrent.Executors;
@@ -189,6 +195,92 @@
         mScanCallback.onError(ERROR_UNSUPPORTED);
     }
 
+    @Test
+    public void testsetPoweredOffFindingEphemeralIds() {
+        // Replace with minSdkVersion when Build.VERSION_CODES.VANILLA_ICE_CREAM can be used.
+        assumeTrue(SdkLevel.isAtLeastV());
+        // Only test supporting devices.
+        if (mNearbyManager.getPoweredOffFindingMode()
+                == NearbyManager.POWERED_OFF_FINDING_MODE_UNSUPPORTED) return;
+
+        mNearbyManager.setPoweredOffFindingEphemeralIds(List.of(new byte[20], new byte[20]));
+    }
+
+    @Test
+    public void testsetPoweredOffFindingEphemeralIds_noPrivilegedPermission() {
+        // Replace with minSdkVersion when Build.VERSION_CODES.VANILLA_ICE_CREAM can be used.
+        assumeTrue(SdkLevel.isAtLeastV());
+        // Only test supporting devices.
+        if (mNearbyManager.getPoweredOffFindingMode()
+                == NearbyManager.POWERED_OFF_FINDING_MODE_UNSUPPORTED) return;
+
+        mUiAutomation.dropShellPermissionIdentity();
+
+        assertThrows(SecurityException.class,
+                () -> mNearbyManager.setPoweredOffFindingEphemeralIds(List.of(new byte[20])));
+    }
+
+
+    @Test
+    public void testSetAndGetPoweredOffFindingMode_enabled() {
+        // Replace with minSdkVersion when Build.VERSION_CODES.VANILLA_ICE_CREAM can be used.
+        assumeTrue(SdkLevel.isAtLeastV());
+        // Only test supporting devices.
+        if (mNearbyManager.getPoweredOffFindingMode()
+                == NearbyManager.POWERED_OFF_FINDING_MODE_UNSUPPORTED) return;
+
+        enableLocation();
+        // enableLocation() has dropped shell permission identity.
+        mUiAutomation.adoptShellPermissionIdentity(BLUETOOTH_PRIVILEGED);
+
+        mNearbyManager.setPoweredOffFindingMode(
+                NearbyManager.POWERED_OFF_FINDING_MODE_ENABLED);
+        assertThat(mNearbyManager.getPoweredOffFindingMode())
+                .isEqualTo(NearbyManager.POWERED_OFF_FINDING_MODE_ENABLED);
+    }
+
+    @Test
+    public void testSetAndGetPoweredOffFindingMode_disabled() {
+        // Replace with minSdkVersion when Build.VERSION_CODES.VANILLA_ICE_CREAM can be used.
+        assumeTrue(SdkLevel.isAtLeastV());
+        // Only test supporting devices.
+        if (mNearbyManager.getPoweredOffFindingMode()
+                == NearbyManager.POWERED_OFF_FINDING_MODE_UNSUPPORTED) return;
+
+        mNearbyManager.setPoweredOffFindingMode(
+                NearbyManager.POWERED_OFF_FINDING_MODE_DISABLED);
+        assertThat(mNearbyManager.getPoweredOffFindingMode())
+                .isEqualTo(NearbyManager.POWERED_OFF_FINDING_MODE_DISABLED);
+    }
+
+    @Test
+    public void testSetPoweredOffFindingMode_noPrivilegedPermission() {
+        // Replace with minSdkVersion when Build.VERSION_CODES.VANILLA_ICE_CREAM can be used.
+        assumeTrue(SdkLevel.isAtLeastV());
+        // Only test supporting devices.
+        if (mNearbyManager.getPoweredOffFindingMode()
+                == NearbyManager.POWERED_OFF_FINDING_MODE_UNSUPPORTED) return;
+
+        enableLocation();
+        mUiAutomation.dropShellPermissionIdentity();
+
+        assertThrows(SecurityException.class, () -> mNearbyManager
+                .setPoweredOffFindingMode(NearbyManager.POWERED_OFF_FINDING_MODE_ENABLED));
+    }
+
+    @Test
+    public void testGetPoweredOffFindingMode_noPrivilegedPermission() {
+        // Replace with minSdkVersion when Build.VERSION_CODES.VANILLA_ICE_CREAM can be used.
+        assumeTrue(SdkLevel.isAtLeastV());
+        // Only test supporting devices.
+        if (mNearbyManager.getPoweredOffFindingMode()
+                == NearbyManager.POWERED_OFF_FINDING_MODE_UNSUPPORTED) return;
+
+        mUiAutomation.dropShellPermissionIdentity();
+
+        assertThrows(SecurityException.class, () -> mNearbyManager.getPoweredOffFindingMode());
+    }
+
     private void enableBluetooth() {
         BluetoothManager manager = mContext.getSystemService(BluetoothManager.class);
         BluetoothAdapter bluetoothAdapter = manager.getAdapter();
@@ -197,6 +289,13 @@
         }
     }
 
+    private void enableLocation() {
+        LocationManager locationManager = mContext.getSystemService(LocationManager.class);
+        UserHandle user = Process.myUserHandle();
+        SystemUtil.runWithShellPermissionIdentity(
+                mUiAutomation, () -> locationManager.setLocationEnabledForUser(true, user));
+    }
+
     private static class OffloadCallback implements Consumer<OffloadCapability> {
         @Override
         public void accept(OffloadCapability aBoolean) {
diff --git a/nearby/tests/integration/privileged/Android.bp b/nearby/tests/integration/privileged/Android.bp
index 9b6e488..5e64009 100644
--- a/nearby/tests/integration/privileged/Android.bp
+++ b/nearby/tests/integration/privileged/Android.bp
@@ -13,6 +13,7 @@
 // limitations under the License.
 
 package {
+    default_team: "trendy_team_fwk_core_networking",
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
diff --git a/nearby/tests/integration/privileged/src/android/nearby/integration/privileged/NearbyManagerTest.kt b/nearby/tests/integration/privileged/src/android/nearby/integration/privileged/NearbyManagerTest.kt
index 506b4e2..b949720 100644
--- a/nearby/tests/integration/privileged/src/android/nearby/integration/privileged/NearbyManagerTest.kt
+++ b/nearby/tests/integration/privileged/src/android/nearby/integration/privileged/NearbyManagerTest.kt
@@ -29,6 +29,7 @@
 import androidx.test.core.app.ApplicationProvider
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import com.google.common.truth.Truth.assertThat
+import org.junit.Assert.assertThrows
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -96,4 +97,49 @@
         )
         nearbyManager.stopBroadcast(broadcastCallback)
     }
+
+    /** Verify privileged app can set powered off finding ephemeral IDs without exception. */
+    @Test
+    fun testNearbyManagerSetPoweredOffFindingEphemeralIds_fromPrivilegedApp_succeed() {
+        val nearbyManager = appContext.getSystemService(Context.NEARBY_SERVICE) as NearbyManager
+        // Only test supporting devices.
+        if (nearbyManager.getPoweredOffFindingMode()
+                == NearbyManager.POWERED_OFF_FINDING_MODE_UNSUPPORTED) return
+
+        val eid = ByteArray(20)
+
+        nearbyManager.setPoweredOffFindingEphemeralIds(listOf(eid))
+    }
+
+    /**
+     * Verifies that [NearbyManager.setPoweredOffFindingEphemeralIds] checkes the ephemeral ID
+     * length.
+     */
+    @Test
+    fun testNearbyManagerSetPoweredOffFindingEphemeralIds_wrongSize_throwsException() {
+        val nearbyManager = appContext.getSystemService(Context.NEARBY_SERVICE) as NearbyManager
+        // Only test supporting devices.
+        if (nearbyManager.getPoweredOffFindingMode()
+                == NearbyManager.POWERED_OFF_FINDING_MODE_UNSUPPORTED) return
+
+        assertThrows(IllegalArgumentException::class.java) {
+            nearbyManager.setPoweredOffFindingEphemeralIds(listOf(ByteArray(21)))
+        }
+        assertThrows(IllegalArgumentException::class.java) {
+            nearbyManager.setPoweredOffFindingEphemeralIds(listOf(ByteArray(19)))
+        }
+    }
+
+    /** Verify privileged app can set and get powered off finding mode without exception. */
+    @Test
+    fun testNearbyManagerSetGetPoweredOffMode_fromPrivilegedApp_succeed() {
+        val nearbyManager = appContext.getSystemService(Context.NEARBY_SERVICE) as NearbyManager
+        // Only test supporting devices.
+        if (nearbyManager.getPoweredOffFindingMode()
+                == NearbyManager.POWERED_OFF_FINDING_MODE_UNSUPPORTED) return
+
+        nearbyManager.setPoweredOffFindingMode(NearbyManager.POWERED_OFF_FINDING_MODE_DISABLED)
+        assertThat(nearbyManager.getPoweredOffFindingMode())
+                .isEqualTo(NearbyManager.POWERED_OFF_FINDING_MODE_DISABLED)
+    }
 }
diff --git a/nearby/tests/integration/untrusted/Android.bp b/nearby/tests/integration/untrusted/Android.bp
index 75f765b..e6259c5 100644
--- a/nearby/tests/integration/untrusted/Android.bp
+++ b/nearby/tests/integration/untrusted/Android.bp
@@ -13,6 +13,7 @@
 // limitations under the License.
 
 package {
+    default_team: "trendy_team_fwk_core_networking",
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
diff --git a/nearby/tests/integration/untrusted/src/android/nearby/integration/untrusted/NearbyManagerTest.kt b/nearby/tests/integration/untrusted/src/android/nearby/integration/untrusted/NearbyManagerTest.kt
index 7bf9f63..015d022 100644
--- a/nearby/tests/integration/untrusted/src/android/nearby/integration/untrusted/NearbyManagerTest.kt
+++ b/nearby/tests/integration/untrusted/src/android/nearby/integration/untrusted/NearbyManagerTest.kt
@@ -30,12 +30,12 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.uiautomator.LogcatWaitMixin
 import com.google.common.truth.Truth.assertThat
+import java.time.Duration
+import java.util.Calendar
 import org.junit.Assert.assertThrows
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
-import java.time.Duration
-import java.util.Calendar
 
 @RunWith(AndroidJUnit4::class)
 class NearbyManagerTest {
@@ -151,6 +151,46 @@
         ).isTrue()
     }
 
+    /**
+     * Verify untrusted app can't set powered off finding ephemeral IDs because it needs
+     * BLUETOOTH_PRIVILEGED permission which is not for use by third-party applications.
+     */
+    @Test
+    fun testNearbyManagerSetPoweredOffFindingEphemeralIds_fromUnTrustedApp_throwsException() {
+        val nearbyManager = appContext.getSystemService(Context.NEARBY_SERVICE) as NearbyManager
+        val eid = ByteArray(20)
+
+        assertThrows(SecurityException::class.java) {
+            nearbyManager.setPoweredOffFindingEphemeralIds(listOf(eid))
+        }
+    }
+
+    /**
+     * Verify untrusted app can't set powered off finding mode because it needs BLUETOOTH_PRIVILEGED
+     * permission which is not for use by third-party applications.
+     */
+    @Test
+    fun testNearbyManagerSetPoweredOffFindingMode_fromUnTrustedApp_throwsException() {
+        val nearbyManager = appContext.getSystemService(Context.NEARBY_SERVICE) as NearbyManager
+
+        assertThrows(SecurityException::class.java) {
+            nearbyManager.setPoweredOffFindingMode(NearbyManager.POWERED_OFF_FINDING_MODE_ENABLED)
+        }
+    }
+
+    /**
+     * Verify untrusted app can't get powered off finding mode because it needs BLUETOOTH_PRIVILEGED
+     * permission which is not for use by third-party applications.
+     */
+    @Test
+    fun testNearbyManagerGetPoweredOffFindingMode_fromUnTrustedApp_throwsException() {
+        val nearbyManager = appContext.getSystemService(Context.NEARBY_SERVICE) as NearbyManager
+
+        assertThrows(SecurityException::class.java) {
+            nearbyManager.getPoweredOffFindingMode()
+        }
+    }
+
     companion object {
         private val WAIT_INVALID_OPERATIONS_LOGS_TIMEOUT = Duration.ofSeconds(5)
     }
diff --git a/nearby/tests/unit/Android.bp b/nearby/tests/unit/Android.bp
index bbf42c7..2950568 100644
--- a/nearby/tests/unit/Android.bp
+++ b/nearby/tests/unit/Android.bp
@@ -13,6 +13,7 @@
 // limitations under the License.
 
 package {
+    default_team: "trendy_team_fwk_core_networking",
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
diff --git a/nearby/tests/unit/src/com/android/server/nearby/managers/BluetoothFinderManagerTest.java b/nearby/tests/unit/src/com/android/server/nearby/managers/BluetoothFinderManagerTest.java
new file mode 100644
index 0000000..32286e1
--- /dev/null
+++ b/nearby/tests/unit/src/com/android/server/nearby/managers/BluetoothFinderManagerTest.java
@@ -0,0 +1,191 @@
+/*
+ * 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.
+ */
+
+package com.android.server.nearby.managers;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assume.assumeTrue;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyBoolean;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.hardware.bluetooth.finder.Eid;
+import android.hardware.bluetooth.finder.IBluetoothFinder;
+import android.nearby.PoweredOffFindingEphemeralId;
+import android.os.IBinder;
+import android.os.IBinder.DeathRecipient;
+import android.os.RemoteException;
+import android.os.ServiceSpecificException;
+
+import com.android.modules.utils.build.SdkLevel;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.List;
+
+public class BluetoothFinderManagerTest {
+    private BluetoothFinderManager mBluetoothFinderManager;
+    private boolean mGetServiceCalled = false;
+
+    @Mock private IBluetoothFinder mIBluetoothFinderMock;
+    @Mock private IBinder mServiceBinderMock;
+
+    private ArgumentCaptor<DeathRecipient> mDeathRecipientCaptor =
+            ArgumentCaptor.forClass(DeathRecipient.class);
+
+    private ArgumentCaptor<Eid[]> mEidArrayCaptor = ArgumentCaptor.forClass(Eid[].class);
+
+    private class BluetoothFinderManagerSpy extends BluetoothFinderManager {
+        @Override
+        protected IBluetoothFinder getServiceMockable() {
+            mGetServiceCalled = true;
+            return mIBluetoothFinderMock;
+        }
+
+        @Override
+        protected IBinder getServiceBinderMockable() {
+            return mServiceBinderMock;
+        }
+    }
+
+    @Before
+    public void setup() {
+        // Replace with minSdkVersion when Build.VERSION_CODES.VANILLA_ICE_CREAM can be used.
+        assumeTrue(SdkLevel.isAtLeastV());
+        MockitoAnnotations.initMocks(this);
+        mBluetoothFinderManager = new BluetoothFinderManagerSpy();
+    }
+
+    @Test
+    public void testSendEids() throws Exception {
+        byte[] eidBytes1 = {
+                (byte) 0xe1, (byte) 0xde, (byte) 0x1d, (byte) 0xe1, (byte) 0xde, (byte) 0x1d,
+                (byte) 0xe1, (byte) 0xde, (byte) 0x1d, (byte) 0xe1, (byte) 0xde, (byte) 0x1d,
+                (byte) 0xe1, (byte) 0xde, (byte) 0x1d, (byte) 0xe1, (byte) 0xde, (byte) 0x1d,
+                (byte) 0xe1, (byte) 0xde
+        };
+        byte[] eidBytes2 = {
+                (byte) 0xf2, (byte) 0xef, (byte) 0x2e, (byte) 0xf2, (byte) 0xef, (byte) 0x2e,
+                (byte) 0xf2, (byte) 0xef, (byte) 0x2e, (byte) 0xf2, (byte) 0xef, (byte) 0x2e,
+                (byte) 0xf2, (byte) 0xef, (byte) 0x2e, (byte) 0xf2, (byte) 0xef, (byte) 0x2e,
+                (byte) 0xf2, (byte) 0xef
+        };
+        PoweredOffFindingEphemeralId ephemeralId1 = new PoweredOffFindingEphemeralId();
+        PoweredOffFindingEphemeralId ephemeralId2 = new PoweredOffFindingEphemeralId();
+        ephemeralId1.bytes = eidBytes1;
+        ephemeralId2.bytes = eidBytes2;
+
+        mBluetoothFinderManager.sendEids(List.of(ephemeralId1, ephemeralId2));
+
+        verify(mIBluetoothFinderMock).sendEids(mEidArrayCaptor.capture());
+        assertThat(mEidArrayCaptor.getValue()[0].bytes).isEqualTo(eidBytes1);
+        assertThat(mEidArrayCaptor.getValue()[1].bytes).isEqualTo(eidBytes2);
+    }
+
+    @Test
+    public void testSendEids_remoteException() throws Exception {
+        doThrow(new RemoteException())
+                .when(mIBluetoothFinderMock).sendEids(any());
+        mBluetoothFinderManager.sendEids(List.of());
+
+        // Verify that we get the service again following a RemoteException.
+        mGetServiceCalled = false;
+        mBluetoothFinderManager.sendEids(List.of());
+        assertThat(mGetServiceCalled).isTrue();
+    }
+
+    @Test
+    public void testSendEids_serviceSpecificException() throws Exception {
+        doThrow(new ServiceSpecificException(1))
+                .when(mIBluetoothFinderMock).sendEids(any());
+        mBluetoothFinderManager.sendEids(List.of());
+    }
+
+    @Test
+    public void testSetPoweredOffFinderMode() throws Exception {
+        mBluetoothFinderManager.setPoweredOffFinderMode(true);
+        verify(mIBluetoothFinderMock).setPoweredOffFinderMode(true);
+
+        mBluetoothFinderManager.setPoweredOffFinderMode(false);
+        verify(mIBluetoothFinderMock).setPoweredOffFinderMode(false);
+    }
+
+    @Test
+    public void testSetPoweredOffFinderMode_remoteException() throws Exception {
+        doThrow(new RemoteException())
+                .when(mIBluetoothFinderMock).setPoweredOffFinderMode(anyBoolean());
+        mBluetoothFinderManager.setPoweredOffFinderMode(true);
+
+        // Verify that we get the service again following a RemoteException.
+        mGetServiceCalled = false;
+        mBluetoothFinderManager.setPoweredOffFinderMode(true);
+        assertThat(mGetServiceCalled).isTrue();
+    }
+
+    @Test
+    public void testSetPoweredOffFinderMode_serviceSpecificException() throws Exception {
+        doThrow(new ServiceSpecificException(1))
+                .when(mIBluetoothFinderMock).setPoweredOffFinderMode(anyBoolean());
+        mBluetoothFinderManager.setPoweredOffFinderMode(true);
+    }
+
+    @Test
+    public void testGetPoweredOffFinderMode() throws Exception {
+        when(mIBluetoothFinderMock.getPoweredOffFinderMode()).thenReturn(true);
+        assertThat(mBluetoothFinderManager.getPoweredOffFinderMode()).isTrue();
+
+        when(mIBluetoothFinderMock.getPoweredOffFinderMode()).thenReturn(false);
+        assertThat(mBluetoothFinderManager.getPoweredOffFinderMode()).isFalse();
+    }
+
+    @Test
+    public void testGetPoweredOffFinderMode_remoteException() throws Exception {
+        when(mIBluetoothFinderMock.getPoweredOffFinderMode()).thenThrow(new RemoteException());
+        assertThat(mBluetoothFinderManager.getPoweredOffFinderMode()).isFalse();
+
+        // Verify that we get the service again following a RemoteException.
+        mGetServiceCalled = false;
+        assertThat(mBluetoothFinderManager.getPoweredOffFinderMode()).isFalse();
+        assertThat(mGetServiceCalled).isTrue();
+    }
+
+    @Test
+    public void testGetPoweredOffFinderMode_serviceSpecificException() throws Exception {
+        when(mIBluetoothFinderMock.getPoweredOffFinderMode())
+                .thenThrow(new ServiceSpecificException(1));
+        assertThat(mBluetoothFinderManager.getPoweredOffFinderMode()).isFalse();
+    }
+
+    @Test
+    public void testDeathRecipient() throws Exception {
+        mBluetoothFinderManager.setPoweredOffFinderMode(true);
+        verify(mServiceBinderMock).linkToDeath(mDeathRecipientCaptor.capture(), anyInt());
+        mDeathRecipientCaptor.getValue().binderDied();
+
+        // Verify that we get the service again following a binder death.
+        mGetServiceCalled = false;
+        mBluetoothFinderManager.setPoweredOffFinderMode(true);
+        assertThat(mGetServiceCalled).isTrue();
+    }
+}
diff --git a/netbpfload/Android.bp b/netbpfload/Android.bp
index 1f92374..2b603fc 100644
--- a/netbpfload/Android.bp
+++ b/netbpfload/Android.bp
@@ -14,6 +14,34 @@
 // limitations under the License.
 //
 
+package {
+    default_team: "trendy_team_fwk_core_networking",
+}
+
+install_symlink {
+    name: "platform_ethtool_symlink",
+    symlink_target: "/apex/com.android.tethering/bin/ethtool",
+    // installed_location is relative to /system because that's the default partition for soong
+    // modules, unless we add something like `system_ext_specific: true` like in hwservicemanager.
+    installed_location: "bin/ethtool",
+}
+
+install_symlink {
+    name: "platform_netbpfload_symlink",
+    symlink_target: "/apex/com.android.tethering/bin/netbpfload",
+    installed_location: "bin/netbpfload",
+    init_rc: ["netbpfload.rc"],
+}
+
+phony {
+    name: "mainline_tethering_platform_components",
+    required: [
+        "bpfloader",
+        "platform_ethtool_symlink",
+        "platform_netbpfload_symlink",
+    ],
+}
+
 cc_binary {
     name: "netbpfload",
 
@@ -40,12 +68,22 @@
         "com.android.tethering",
         "//apex_available:platform",
     ],
-    // really should be Android 14/U (34), but we cannot include binaries built
+    // really should be Android 13/T (33), but we cannot include binaries built
     // against newer sdk in the apex, which still targets 30(R):
     // module "netbpfload" variant "android_x86_apex30": should support
     // min_sdk_version(30) for "com.android.tethering": newer SDK(34).
     min_sdk_version: "30",
+    installable: false,
+}
 
-    init_rc: ["netbpfload.rc"],
-    required: ["bpfloader"],
+// Versioned netbpfload init rc: init system will process it only on api T/33+ devices
+// Note: R[30] S[31] Sv2[32] T[33] U[34] V[35])
+//
+// For details of versioned rc files see:
+// https://android.googlesource.com/platform/system/core/+/HEAD/init/README.md#versioned-rc-files-within-apexs
+prebuilt_etc {
+    name: "netbpfload.mainline.rc",
+    src: "netbpfload.mainline.rc",
+    filename: "netbpfload.33rc",
+    installable: false,
 }
diff --git a/netbpfload/NetBpfLoad.cpp b/netbpfload/NetBpfLoad.cpp
index 6152287..ed7d048 100644
--- a/netbpfload/NetBpfLoad.cpp
+++ b/netbpfload/NetBpfLoad.cpp
@@ -169,15 +169,142 @@
     return 0;
 }
 
+#define APEX_MOUNT_POINT "/apex/com.android.tethering"
+const char * const platformBpfLoader = "/system/bin/bpfloader";
+const char * const platformNetBpfLoad = "/system/bin/netbpfload";
+const char * const apexNetBpfLoad = APEX_MOUNT_POINT "/bin/netbpfload";
+
+int logTetheringApexVersion(void) {
+    char * found_blockdev = NULL;
+    FILE * f = NULL;
+    char buf[4096];
+
+    f = fopen("/proc/mounts", "re");
+    if (!f) return 1;
+
+    // /proc/mounts format: block_device [space] mount_point [space] other stuff... newline
+    while (fgets(buf, sizeof(buf), f)) {
+        char * blockdev = buf;
+        char * space = strchr(blockdev, ' ');
+        if (!space) continue;
+        *space = '\0';
+        char * mntpath = space + 1;
+        space = strchr(mntpath, ' ');
+        if (!space) continue;
+        *space = '\0';
+        if (strcmp(mntpath, APEX_MOUNT_POINT)) continue;
+        found_blockdev = strdup(blockdev);
+        break;
+    }
+    fclose(f);
+    f = NULL;
+
+    if (!found_blockdev) return 2;
+    ALOGD("Found Tethering Apex mounted from blockdev %s", found_blockdev);
+
+    f = fopen("/proc/mounts", "re");
+    if (!f) { free(found_blockdev); return 3; }
+
+    while (fgets(buf, sizeof(buf), f)) {
+        char * blockdev = buf;
+        char * space = strchr(blockdev, ' ');
+        if (!space) continue;
+        *space = '\0';
+        char * mntpath = space + 1;
+        space = strchr(mntpath, ' ');
+        if (!space) continue;
+        *space = '\0';
+        if (strcmp(blockdev, found_blockdev)) continue;
+        if (strncmp(mntpath, APEX_MOUNT_POINT "@", strlen(APEX_MOUNT_POINT "@"))) continue;
+        char * at = strchr(mntpath, '@');
+        if (!at) continue;
+        char * ver = at + 1;
+        ALOGI("Tethering APEX version %s", ver);
+    }
+    fclose(f);
+    free(found_blockdev);
+    return 0;
+}
+
 int main(int argc, char** argv, char * const envp[]) {
     (void)argc;
     android::base::InitLogging(argv, &android::base::KernelLogger);
 
-    const int device_api_level = android_get_device_api_level();
-    const bool isAtLeastU = (device_api_level >= __ANDROID_API_U__);
+    ALOGI("NetBpfLoad '%s' starting...", argv[0]);
 
-    if (!android::bpf::isAtLeastKernelVersion(4, 19, 0)) {
-        ALOGE("Android U QPR2 requires kernel 4.19.");
+    // true iff we are running from the module
+    const bool is_mainline = !strcmp(argv[0], apexNetBpfLoad);
+
+    // true iff we are running from the platform
+    const bool is_platform = !strcmp(argv[0], platformNetBpfLoad);
+
+    const int device_api_level = android_get_device_api_level();
+    const bool isAtLeastT = (device_api_level >= __ANDROID_API_T__);
+    const bool isAtLeastU = (device_api_level >= __ANDROID_API_U__);
+    const bool isAtLeastV = (device_api_level >= __ANDROID_API_V__);
+
+    // last in U QPR2 beta1
+    const bool has_platform_bpfloader_rc = exists("/system/etc/init/bpfloader.rc");
+    // first in U QPR2 beta~2
+    const bool has_platform_netbpfload_rc = exists("/system/etc/init/netbpfload.rc");
+
+    ALOGI("NetBpfLoad api:%d/%d kver:%07x platform:%d mainline:%d rc:%d%d",
+          android_get_application_target_sdk_version(), device_api_level,
+          android::bpf::kernelVersion(), is_platform, is_mainline,
+          has_platform_bpfloader_rc, has_platform_netbpfload_rc);
+
+    if (!is_platform && !is_mainline) {
+        ALOGE("Unable to determine if we're platform or mainline netbpfload.");
+        return 1;
+    }
+
+    if (is_platform) {
+        ALOGI("Executing apex netbpfload...");
+        const char * args[] = { apexNetBpfLoad, NULL, };
+        execve(args[0], (char**)args, envp);
+        ALOGE("exec '%s' fail: %d[%s]", apexNetBpfLoad, errno, strerror(errno));
+        return 1;
+    }
+
+    if (!has_platform_bpfloader_rc && !has_platform_netbpfload_rc) {
+        ALOGE("Unable to find platform's bpfloader & netbpfload init scripts.");
+        return 1;
+    }
+
+    if (has_platform_bpfloader_rc && has_platform_netbpfload_rc) {
+        ALOGE("Platform has *both* bpfloader & netbpfload init scripts.");
+        return 1;
+    }
+
+    logTetheringApexVersion();
+
+    if (is_mainline && has_platform_bpfloader_rc && !has_platform_netbpfload_rc) {
+        // Tethering apex shipped initrc file causes us to reach here
+        // but we're not ready to correctly handle anything before U QPR2
+        // in which the 'bpfloader' vs 'netbpfload' split happened
+        const char * args[] = { platformBpfLoader, NULL, };
+        execve(args[0], (char**)args, envp);
+        ALOGE("exec '%s' fail: %d[%s]", platformBpfLoader, errno, strerror(errno));
+        return 1;
+    }
+
+    if (isAtLeastT && !android::bpf::isAtLeastKernelVersion(4, 9, 0)) {
+        ALOGE("Android T requires kernel 4.9.");
+        return 1;
+    }
+
+    if (isAtLeastU && !android::bpf::isAtLeastKernelVersion(4, 14, 0)) {
+        ALOGE("Android U requires kernel 4.14.");
+        return 1;
+    }
+
+    if (isAtLeastV && !android::bpf::isAtLeastKernelVersion(4, 19, 0)) {
+        ALOGE("Android V requires kernel 4.19.");
+        return 1;
+    }
+
+    if (isAtLeastV && android::bpf::isX86() && !android::bpf::isKernel64Bit()) {
+        ALOGE("Android V requires X86 kernel to be 64-bit.");
         return 1;
     }
 
@@ -242,6 +369,13 @@
         if (createSysFsBpfSubDir(location.prefix)) return 1;
     }
 
+    // Note: there's no actual src dir for fs_bpf_loader .o's,
+    // so it is not listed in 'locations[].prefix'.
+    // This is because this is primarily meant for triggering genfscon rules,
+    // and as such this will likely always be the case.
+    // Thus we need to manually create the /sys/fs/bpf/loader subdirectory.
+    if (createSysFsBpfSubDir("loader")) return 1;
+
     // Load all ELF objects, create programs and maps, and pin them
     for (const auto& location : locations) {
         if (loadAllElfObjects(location) != 0) {
@@ -266,10 +400,8 @@
 
     ALOGI("done, transferring control to platform bpfloader.");
 
-    const char * args[] = { "/system/bin/bpfloader", NULL, };
-    if (execve(args[0], (char**)args, envp)) {
-        ALOGE("FATAL: execve('/system/bin/bpfloader'): %d[%s]", errno, strerror(errno));
-    }
-
+    const char * args[] = { platformBpfLoader, NULL, };
+    execve(args[0], (char**)args, envp);
+    ALOGE("FATAL: execve('%s'): %d[%s]", platformBpfLoader, errno, strerror(errno));
     return 1;
 }
diff --git a/netbpfload/netbpfload.mainline.rc b/netbpfload/netbpfload.mainline.rc
new file mode 100644
index 0000000..0ac5de8
--- /dev/null
+++ b/netbpfload/netbpfload.mainline.rc
@@ -0,0 +1,8 @@
+service bpfloader /apex/com.android.tethering/bin/netbpfload
+    capabilities CHOWN SYS_ADMIN NET_ADMIN
+    group root graphics network_stack net_admin net_bw_acct net_bw_stats net_raw system
+    user root
+    rlimit memlock 1073741824 1073741824
+    oneshot
+    reboot_on_failure reboot,bpfloader-failed
+    override
diff --git a/netd/Android.bp b/netd/Android.bp
index 3cdbc97..eedbdae 100644
--- a/netd/Android.bp
+++ b/netd/Android.bp
@@ -14,6 +14,7 @@
 // limitations under the License.
 
 package {
+    default_team: "trendy_team_fwk_core_networking",
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
@@ -58,15 +59,18 @@
 cc_test {
     name: "netd_updatable_unit_test",
     defaults: ["netd_defaults"],
-    test_suites: ["general-tests", "mts-tethering"],
+    test_suites: [
+        "general-tests",
+        "mts-tethering",
+    ],
     test_config_template: ":net_native_test_config_template",
-    require_root: true,  // required by setrlimitForTest()
+    require_root: true, // required by setrlimitForTest()
     header_libs: [
         "bpf_connectivity_headers",
     ],
     srcs: [
         "BpfHandlerTest.cpp",
-        "BpfBaseTest.cpp"
+        "BpfBaseTest.cpp",
     ],
     static_libs: [
         "libbase",
diff --git a/remoteauth/framework/Android.bp b/remoteauth/framework/Android.bp
index 71b621a..2f1737f 100644
--- a/remoteauth/framework/Android.bp
+++ b/remoteauth/framework/Android.bp
@@ -13,6 +13,7 @@
 // limitations under the License.
 
 package {
+    default_team: "trendy_team_fwk_core_networking",
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
diff --git a/remoteauth/service/Android.bp b/remoteauth/service/Android.bp
index 8330efc..32ae54f 100644
--- a/remoteauth/service/Android.bp
+++ b/remoteauth/service/Android.bp
@@ -13,6 +13,7 @@
 // limitations under the License.
 
 package {
+    default_team: "trendy_team_fwk_core_networking",
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
diff --git a/remoteauth/service/jni/Android.bp b/remoteauth/service/jni/Android.bp
index a95a8fb..c0ac779 100644
--- a/remoteauth/service/jni/Android.bp
+++ b/remoteauth/service/jni/Android.bp
@@ -1,4 +1,5 @@
 package {
+    default_team: "trendy_team_fwk_core_networking",
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
diff --git a/remoteauth/service/jni/src/remoteauth_jni_android_platform.rs b/remoteauth/service/jni/src/remoteauth_jni_android_platform.rs
index 0a189f2..421fe7e 100644
--- a/remoteauth/service/jni/src/remoteauth_jni_android_platform.rs
+++ b/remoteauth/service/jni/src/remoteauth_jni_android_platform.rs
@@ -67,7 +67,7 @@
         logger::init(
             logger::Config::default()
                 .with_tag_on_device("remoteauth")
-                .with_min_level(log::Level::Trace)
+                .with_max_level(log::LevelFilter::Trace)
                 .with_filter("trace,jni=info"),
         );
     }
diff --git a/remoteauth/service/jni/src/remoteauth_jni_android_protocol.rs b/remoteauth/service/jni/src/remoteauth_jni_android_protocol.rs
index ac2eb8c..e44ab8b 100644
--- a/remoteauth/service/jni/src/remoteauth_jni_android_protocol.rs
+++ b/remoteauth/service/jni/src/remoteauth_jni_android_protocol.rs
@@ -30,7 +30,7 @@
     logger::init(
         logger::Config::default()
             .with_tag_on_device("remoteauth")
-            .with_min_level(log::Level::Trace)
+            .with_max_level(log::LevelFilter::Trace)
             .with_filter("trace,jni=info"),
     );
     get_boolean_result(native_init(env), "native_init")
diff --git a/remoteauth/tests/unit/Android.bp b/remoteauth/tests/unit/Android.bp
index 77e6f19..47b9e31 100644
--- a/remoteauth/tests/unit/Android.bp
+++ b/remoteauth/tests/unit/Android.bp
@@ -13,6 +13,7 @@
 // limitations under the License.
 
 package {
+    default_team: "trendy_team_fwk_core_networking",
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
diff --git a/service-t/Android.bp b/service-t/Android.bp
index de879f3..779f354 100644
--- a/service-t/Android.bp
+++ b/service-t/Android.bp
@@ -15,6 +15,7 @@
 //
 
 package {
+    default_team: "trendy_team_fwk_core_networking",
     // See: http://go/android-license-faq
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
@@ -74,6 +75,7 @@
         "com.android.tethering",
     ],
     visibility: [
+        "//frameworks/base/services/tests/VpnTests",
         "//frameworks/base/tests/vcn",
         "//packages/modules/Connectivity/service",
         "//packages/modules/Connectivity/tests:__subpackages__",
@@ -98,7 +100,7 @@
     min_sdk_version: "21",
     lint: {
         error_checks: ["NewApi"],
-        baseline_filename: "lint-baseline.xml",
+
     },
     srcs: [
         "src/com/android/server/connectivity/mdns/**/*.java",
diff --git a/service-t/jni/com_android_server_net_NetworkStatsService.cpp b/service-t/jni/com_android_server_net_NetworkStatsService.cpp
index 81912ae..c999398 100644
--- a/service-t/jni/com_android_server_net_NetworkStatsService.cpp
+++ b/service-t/jni/com_android_server_net_NetworkStatsService.cpp
@@ -34,10 +34,17 @@
 
 using android::bpf::bpfGetUidStats;
 using android::bpf::bpfGetIfaceStats;
+using android::bpf::bpfRegisterIface;
 using android::bpf::NetworkTraceHandler;
 
 namespace android {
 
+static void nativeRegisterIface(JNIEnv* env, jclass clazz, jstring iface) {
+    ScopedUtfChars iface8(env, iface);
+    if (iface8.c_str() == nullptr) return;
+    bpfRegisterIface(iface8.c_str());
+}
+
 static jobject statsValueToEntry(JNIEnv* env, StatsValue* stats) {
     // Find the Java class that represents the structure
     jclass gEntryClass = env->FindClass("android/net/NetworkStats$Entry");
@@ -45,8 +52,14 @@
         return nullptr;
     }
 
+    // Find the constructor.
+    jmethodID constructorID = env->GetMethodID(gEntryClass, "<init>", "()V");
+    if (constructorID == nullptr) {
+        return nullptr;
+    }
+
     // Create a new instance of the Java class
-    jobject result = env->AllocObject(gEntryClass);
+    jobject result = env->NewObject(gEntryClass, constructorID);
     if (result == nullptr) {
         return nullptr;
     }
@@ -63,7 +76,7 @@
 static jobject nativeGetTotalStat(JNIEnv* env, jclass clazz) {
     StatsValue stats = {};
 
-    if (bpfGetIfaceStats(NULL, &stats) == 0) {
+    if (bpfGetIfaceStats(nullptr, &stats) == 0) {
         return statsValueToEntry(env, &stats);
     } else {
         return nullptr;
@@ -72,7 +85,7 @@
 
 static jobject nativeGetIfaceStat(JNIEnv* env, jclass clazz, jstring iface) {
     ScopedUtfChars iface8(env, iface);
-    if (iface8.c_str() == NULL) {
+    if (iface8.c_str() == nullptr) {
         return nullptr;
     }
 
@@ -101,6 +114,11 @@
 
 static const JNINativeMethod gMethods[] = {
         {
+            "nativeRegisterIface",
+            "(Ljava/lang/String;)V",
+            (void*)nativeRegisterIface
+        },
+        {
             "nativeGetTotalStat",
             "()Landroid/net/NetworkStats$Entry;",
             (void*)nativeGetTotalStat
diff --git a/service-t/lint-baseline.xml b/service-t/lint-baseline.xml
index 38d3ab0..e4b92d6 100644
--- a/service-t/lint-baseline.xml
+++ b/service-t/lint-baseline.xml
@@ -1,104 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 8.0.0-dev" type="baseline" dependencies="true" variant="all" version="8.0.0-dev">
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 31 (current min is 30): `android.net.EthernetNetworkSpecifier#getInterfaceName`"
-        errorLine1="        if (!((EthernetNetworkSpecifier) spec).getInterfaceName().matches(iface)) {"
-        errorLine2="                                               ~~~~~~~~~~~~~~~~">
-        <location
-            file="packages/modules/Connectivity/service-t/src/com/android/server/ethernet/EthernetServiceImpl.java"
-            line="224"
-            column="48"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 31 (current min is 30): `android.net.UnderlyingNetworkInfo#getInterface`"
-        errorLine1="            delta.migrateTun(info.getOwnerUid(), info.getInterface(),"
-        errorLine2="                                                      ~~~~~~~~~~~~">
-        <location
-            file="packages/modules/Connectivity/service-t/src/com/android/server/net/NetworkStatsFactory.java"
-            line="276"
-            column="55"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 31 (current min is 30): `android.net.UnderlyingNetworkInfo#getOwnerUid`"
-        errorLine1="            delta.migrateTun(info.getOwnerUid(), info.getInterface(),"
-        errorLine2="                                  ~~~~~~~~~~~">
-        <location
-            file="packages/modules/Connectivity/service-t/src/com/android/server/net/NetworkStatsFactory.java"
-            line="276"
-            column="35"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 31 (current min is 30): `android.net.UnderlyingNetworkInfo#getUnderlyingInterfaces`"
-        errorLine1="                    info.getUnderlyingInterfaces());"
-        errorLine2="                         ~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="packages/modules/Connectivity/service-t/src/com/android/server/net/NetworkStatsFactory.java"
-            line="277"
-            column="26"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 31 (current min is 30): `java.net.InetAddress#parseNumericAddress`"
-        errorLine1="                        dnsAddresses.add(InetAddress.parseNumericAddress(address));"
-        errorLine2="                                                     ~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="packages/modules/Connectivity/service-t/src/com/android/server/ethernet/EthernetTracker.java"
-            line="875"
-            column="54"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 31 (current min is 30): `java.net.InetAddress#parseNumericAddress`"
-        errorLine1="                    staticIpConfigBuilder.setGateway(InetAddress.parseNumericAddress(value));"
-        errorLine2="                                                                 ~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="packages/modules/Connectivity/service-t/src/com/android/server/ethernet/EthernetTracker.java"
-            line="870"
-            column="66"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 31 (current min is 30): `libcore.io.IoUtils#closeQuietly`"
-        errorLine1="                IoUtils.closeQuietly(os);"
-        errorLine2="                        ~~~~~~~~~~~~">
-        <location
-            file="packages/modules/Connectivity/service-t/src/com/android/server/net/NetworkStatsRecorder.java"
-            line="556"
-            column="25"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 31 (current min is 30): `libcore.io.IoUtils#closeQuietly`"
-        errorLine1="                IoUtils.closeQuietly(sockFd);"
-        errorLine2="                        ~~~~~~~~~~~~">
-        <location
-            file="packages/modules/Connectivity/service-t/src/com/android/server/IpSecService.java"
-            line="1309"
-            column="25"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 31 (current min is 30): `libcore.io.IoUtils#closeQuietly`"
-        errorLine1="            IoUtils.closeQuietly(mSocket);"
-        errorLine2="                    ~~~~~~~~~~~~">
-        <location
-            file="packages/modules/Connectivity/service-t/src/com/android/server/IpSecService.java"
-            line="1034"
-            column="21"/>
-    </issue>
+<issues format="6" by="lint 8.4.0-alpha01" type="baseline" client="" dependencies="true" name="" variant="all" version="8.4.0-alpha01">
 
     <issue
         id="NewApi"
@@ -113,6 +14,17 @@
 
     <issue
         id="NewApi"
+        message="Cast from `EthernetNetworkSpecifier` to `NetworkSpecifier` requires API level 31 (current min is 30)"
+        errorLine1="                .setNetworkSpecifier(new EthernetNetworkSpecifier(ifaceName))"
+        errorLine2="                                     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="packages/modules/Connectivity/service-t/src/com/android/server/ethernet/EthernetNetworkFactory.java"
+            line="156"
+            column="38"/>
+    </issue>
+
+    <issue
+        id="NewApi"
         message="Call requires API level 31 (current min is 30): `new android.net.EthernetNetworkSpecifier`"
         errorLine1="            nc.setNetworkSpecifier(new EthernetNetworkSpecifier(iface));"
         errorLine2="                                   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
@@ -124,39 +36,6 @@
 
     <issue
         id="NewApi"
-        message="Call requires API level 31 (current min is 30): `new android.util.AtomicFile`"
-        errorLine1="        mFile = new AtomicFile(new File(path), logger);"
-        errorLine2="                ~~~~~~~~~~~~~~">
-        <location
-            file="packages/modules/Connectivity/service-t/src/com/android/server/net/PersistentInt.java"
-            line="53"
-            column="17"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 31 (current min is 30): `new java.net.InetSocketAddress`"
-        errorLine1="        super(handler, new RecvBuffer(buffer, new InetSocketAddress()));"
-        errorLine2="                                              ~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="packages/modules/Connectivity/service-t/src/com/android/server/connectivity/mdns/MulticastPacketReader.java"
-            line="66"
-            column="47"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Cast from `EthernetNetworkSpecifier` to `NetworkSpecifier` requires API level 31 (current min is 30)"
-        errorLine1="                .setNetworkSpecifier(new EthernetNetworkSpecifier(ifaceName))"
-        errorLine2="                                     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="packages/modules/Connectivity/service-t/src/com/android/server/ethernet/EthernetNetworkFactory.java"
-            line="156"
-            column="38"/>
-    </issue>
-
-    <issue
-        id="NewApi"
         message="Cast from `EthernetNetworkSpecifier` to `NetworkSpecifier` requires API level 31 (current min is 30)"
         errorLine1="            nc.setNetworkSpecifier(new EthernetNetworkSpecifier(iface));"
         errorLine2="                                   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
@@ -169,6 +48,28 @@
     <issue
         id="NewApi"
         message="Class requires API level 31 (current min is 30): `android.net.EthernetNetworkSpecifier`"
+        errorLine1="        if (!(spec instanceof EthernetNetworkSpecifier)) {"
+        errorLine2="                              ~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="packages/modules/Connectivity/service-t/src/com/android/server/ethernet/EthernetServiceImpl.java"
+            line="221"
+            column="31"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 31 (current min is 30): `android.net.EthernetNetworkSpecifier#getInterfaceName`"
+        errorLine1="        if (!((EthernetNetworkSpecifier) spec).getInterfaceName().matches(iface)) {"
+        errorLine2="                                               ~~~~~~~~~~~~~~~~">
+        <location
+            file="packages/modules/Connectivity/service-t/src/com/android/server/ethernet/EthernetServiceImpl.java"
+            line="224"
+            column="48"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Class requires API level 31 (current min is 30): `android.net.EthernetNetworkSpecifier`"
         errorLine1="        if (!((EthernetNetworkSpecifier) spec).getInterfaceName().matches(iface)) {"
         errorLine2="               ~~~~~~~~~~~~~~~~~~~~~~~~">
         <location
@@ -179,13 +80,178 @@
 
     <issue
         id="NewApi"
-        message="Class requires API level 31 (current min is 30): `android.net.EthernetNetworkSpecifier`"
-        errorLine1="        if (!(spec instanceof EthernetNetworkSpecifier)) {"
-        errorLine2="                              ~~~~~~~~~~~~~~~~~~~~~~~~">
+        message="Call requires API level 31 (current min is 30): `java.net.InetAddress#parseNumericAddress`"
+        errorLine1="                    staticIpConfigBuilder.setGateway(InetAddress.parseNumericAddress(value));"
+        errorLine2="                                                                 ~~~~~~~~~~~~~~~~~~~">
         <location
-            file="packages/modules/Connectivity/service-t/src/com/android/server/ethernet/EthernetServiceImpl.java"
-            line="221"
-            column="31"/>
+            file="packages/modules/Connectivity/service-t/src/com/android/server/ethernet/EthernetTracker.java"
+            line="885"
+            column="66"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 31 (current min is 30): `java.net.InetAddress#parseNumericAddress`"
+        errorLine1="                        dnsAddresses.add(InetAddress.parseNumericAddress(address));"
+        errorLine2="                                                     ~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="packages/modules/Connectivity/service-t/src/com/android/server/ethernet/EthernetTracker.java"
+            line="890"
+            column="54"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 31 (current min is 30): `libcore.io.IoUtils#closeQuietly`"
+        errorLine1="            IoUtils.closeQuietly(mSocket);"
+        errorLine2="                    ~~~~~~~~~~~~">
+        <location
+            file="packages/modules/Connectivity/service-t/src/com/android/server/IpSecService.java"
+            line="1042"
+            column="21"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 31 (current min is 30): `libcore.io.IoUtils#closeQuietly`"
+        errorLine1="                IoUtils.closeQuietly(sockFd);"
+        errorLine2="                        ~~~~~~~~~~~~">
+        <location
+            file="packages/modules/Connectivity/service-t/src/com/android/server/IpSecService.java"
+            line="1318"
+            column="25"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Field requires API level 31 (current min is 30): `android.system.OsConstants#UDP_ENCAP`"
+        errorLine1="                    OsConstants.UDP_ENCAP,"
+        errorLine2="                    ~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="packages/modules/Connectivity/service-t/src/com/android/server/IpSecService.java"
+            line="1326"
+            column="21"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Field requires API level 31 (current min is 30): `android.system.OsConstants#UDP_ENCAP_ESPINUDP`"
+        errorLine1="                    OsConstants.UDP_ENCAP_ESPINUDP);"
+        errorLine2="                    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="packages/modules/Connectivity/service-t/src/com/android/server/IpSecService.java"
+            line="1327"
+            column="21"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 33 (current min is 30): `BpfNetMaps`"
+        errorLine1="            return new BpfNetMaps(ctx);"
+        errorLine2="                   ~~~~~~~~~~~~~~">
+        <location
+            file="packages/modules/Connectivity/service-t/src/com/android/server/net/NetworkStatsFactory.java"
+            line="111"
+            column="20"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 33 (current min is 30): `swapActiveStatsMap`"
+        errorLine1="            mBpfNetMaps.swapActiveStatsMap();"
+        errorLine2="                        ~~~~~~~~~~~~~~~~~~">
+        <location
+            file="packages/modules/Connectivity/service-t/src/com/android/server/net/NetworkStatsFactory.java"
+            line="185"
+            column="25"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 31 (current min is 30): `android.net.UnderlyingNetworkInfo#getInterface`"
+        errorLine1="            delta.migrateTun(info.getOwnerUid(), info.getInterface(),"
+        errorLine2="                                                      ~~~~~~~~~~~~">
+        <location
+            file="packages/modules/Connectivity/service-t/src/com/android/server/net/NetworkStatsFactory.java"
+            line="240"
+            column="55"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 31 (current min is 30): `android.net.UnderlyingNetworkInfo#getOwnerUid`"
+        errorLine1="            delta.migrateTun(info.getOwnerUid(), info.getInterface(),"
+        errorLine2="                                  ~~~~~~~~~~~">
+        <location
+            file="packages/modules/Connectivity/service-t/src/com/android/server/net/NetworkStatsFactory.java"
+            line="240"
+            column="35"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 31 (current min is 30): `android.net.UnderlyingNetworkInfo#getUnderlyingInterfaces`"
+        errorLine1="                    info.getUnderlyingInterfaces());"
+        errorLine2="                         ~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="packages/modules/Connectivity/service-t/src/com/android/server/net/NetworkStatsFactory.java"
+            line="241"
+            column="26"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 31 (current min is 30): `libcore.io.IoUtils#closeQuietly`"
+        errorLine1="                IoUtils.closeQuietly(os);"
+        errorLine2="                        ~~~~~~~~~~~~">
+        <location
+            file="packages/modules/Connectivity/service-t/src/com/android/server/net/NetworkStatsRecorder.java"
+            line="580"
+            column="25"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 35 (current min is 34): `newInstance`"
+        errorLine1="                            opts = BroadcastOptionsShimImpl.newInstance("
+        errorLine2="                                                            ~~~~~~~~~~~">
+        <location
+            file="packages/modules/Connectivity/service-t/src/com/android/server/net/NetworkStatsService.java"
+            line="562"
+            column="61"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 31 (current min is 30): `new android.util.AtomicFile`"
+        errorLine1="        mFile = new AtomicFile(new File(path), logger);"
+        errorLine2="                ~~~~~~~~~~~~~~">
+        <location
+            file="packages/modules/Connectivity/service-t/src/com/android/server/net/PersistentInt.java"
+            line="53"
+            column="17"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 33 (current min is 30): `addOrUpdateInterfaceAddress`"
+        errorLine1="                    mCb.addOrUpdateInterfaceAddress(ifaddrMsg.index, la);"
+        errorLine2="                        ~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="packages/modules/Connectivity/service-t/src/com/android/server/connectivity/mdns/internal/SocketNetlinkMonitor.java"
+            line="69"
+            column="25"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 33 (current min is 30): `deleteInterfaceAddress`"
+        errorLine1="                mCb.deleteInterfaceAddress(ifaddrMsg.index, la);"
+        errorLine2="                    ~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="packages/modules/Connectivity/service-t/src/com/android/server/connectivity/mdns/internal/SocketNetlinkMonitor.java"
+            line="73"
+            column="21"/>
     </issue>
 
 </issues>
\ No newline at end of file
diff --git a/service-t/native/libs/libnetworkstats/Android.bp b/service-t/native/libs/libnetworkstats/Android.bp
index b9f3adb..c620634 100644
--- a/service-t/native/libs/libnetworkstats/Android.bp
+++ b/service-t/native/libs/libnetworkstats/Android.bp
@@ -15,6 +15,7 @@
 //
 
 package {
+    default_team: "trendy_team_fwk_core_networking",
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
@@ -57,9 +58,12 @@
 
 cc_test {
     name: "libnetworkstats_test",
-    test_suites: ["general-tests", "mts-tethering"],
+    test_suites: [
+        "general-tests",
+        "mts-tethering",
+    ],
     test_config_template: ":net_native_test_config_template",
-    require_root: true,  // required by setrlimitForTest()
+    require_root: true, // required by setrlimitForTest()
     header_libs: ["bpf_connectivity_headers"],
     srcs: [
         "BpfNetworkStatsTest.cpp",
diff --git a/service-t/native/libs/libnetworkstats/BpfNetworkStats.cpp b/service-t/native/libs/libnetworkstats/BpfNetworkStats.cpp
index 3101397..d3e331e 100644
--- a/service-t/native/libs/libnetworkstats/BpfNetworkStats.cpp
+++ b/service-t/native/libs/libnetworkstats/BpfNetworkStats.cpp
@@ -40,6 +40,35 @@
 
 using base::Result;
 
+BpfMap<uint32_t, IfaceValue>& getIfaceIndexNameMap() {
+    static BpfMap<uint32_t, IfaceValue> ifaceIndexNameMap(IFACE_INDEX_NAME_MAP_PATH);
+    return ifaceIndexNameMap;
+}
+
+const BpfMapRO<uint32_t, StatsValue>& getIfaceStatsMap() {
+    static BpfMapRO<uint32_t, StatsValue> ifaceStatsMap(IFACE_STATS_MAP_PATH);
+    return ifaceStatsMap;
+}
+
+Result<IfaceValue> ifindex2name(const uint32_t ifindex) {
+    Result<IfaceValue> v = getIfaceIndexNameMap().readValue(ifindex);
+    if (v.ok()) return v;
+    IfaceValue iv = {};
+    if (!if_indextoname(ifindex, iv.name)) return v;
+    getIfaceIndexNameMap().writeValue(ifindex, iv, BPF_ANY);
+    return iv;
+}
+
+void bpfRegisterIface(const char* iface) {
+    if (!iface) return;
+    if (strlen(iface) >= sizeof(IfaceValue)) return;
+    uint32_t ifindex = if_nametoindex(iface);
+    if (!ifindex) return;
+    IfaceValue ifname = {};
+    strlcpy(ifname.name, iface, sizeof(ifname.name));
+    getIfaceIndexNameMap().writeValue(ifindex, ifname, BPF_ANY);
+}
+
 int bpfGetUidStatsInternal(uid_t uid, StatsValue* stats,
                            const BpfMapRO<uint32_t, StatsValue>& appUidStatsMap) {
     auto statsEntry = appUidStatsMap.readValue(uid);
@@ -58,19 +87,19 @@
 
 int bpfGetIfaceStatsInternal(const char* iface, StatsValue* stats,
                              const BpfMapRO<uint32_t, StatsValue>& ifaceStatsMap,
-                             const BpfMapRO<uint32_t, IfaceValue>& ifaceNameMap) {
+                             const IfIndexToNameFunc ifindex2name) {
     *stats = {};
     int64_t unknownIfaceBytesTotal = 0;
     const auto processIfaceStats =
-            [iface, stats, &ifaceNameMap, &unknownIfaceBytesTotal](
+            [iface, stats, ifindex2name, &unknownIfaceBytesTotal](
                     const uint32_t& key,
                     const BpfMapRO<uint32_t, StatsValue>& ifaceStatsMap) -> Result<void> {
-        char ifname[IFNAMSIZ];
-        if (getIfaceNameFromMap(ifaceNameMap, ifaceStatsMap, key, ifname, key,
-                                &unknownIfaceBytesTotal)) {
+        Result<IfaceValue> ifname = ifindex2name(key);
+        if (!ifname.ok()) {
+            maybeLogUnknownIface(key, ifaceStatsMap, key, &unknownIfaceBytesTotal);
             return Result<void>();
         }
-        if (!iface || !strcmp(iface, ifname)) {
+        if (!iface || !strcmp(iface, ifname.value().name)) {
             Result<StatsValue> statsEntry = ifaceStatsMap.readValue(key);
             if (!statsEntry.ok()) {
                 return statsEntry.error();
@@ -84,9 +113,7 @@
 }
 
 int bpfGetIfaceStats(const char* iface, StatsValue* stats) {
-    static BpfMapRO<uint32_t, StatsValue> ifaceStatsMap(IFACE_STATS_MAP_PATH);
-    static BpfMapRO<uint32_t, IfaceValue> ifaceIndexNameMap(IFACE_INDEX_NAME_MAP_PATH);
-    return bpfGetIfaceStatsInternal(iface, stats, ifaceStatsMap, ifaceIndexNameMap);
+    return bpfGetIfaceStatsInternal(iface, stats, getIfaceStatsMap(), ifindex2name);
 }
 
 int bpfGetIfIndexStatsInternal(uint32_t ifindex, StatsValue* stats,
@@ -101,14 +128,13 @@
 }
 
 int bpfGetIfIndexStats(int ifindex, StatsValue* stats) {
-    static BpfMapRO<uint32_t, StatsValue> ifaceStatsMap(IFACE_STATS_MAP_PATH);
-    return bpfGetIfIndexStatsInternal(ifindex, stats, ifaceStatsMap);
+    return bpfGetIfIndexStatsInternal(ifindex, stats, getIfaceStatsMap());
 }
 
 stats_line populateStatsEntry(const StatsKey& statsKey, const StatsValue& statsEntry,
-                              const char* ifname) {
+                              const IfaceValue& ifname) {
     stats_line newLine;
-    strlcpy(newLine.iface, ifname, sizeof(newLine.iface));
+    strlcpy(newLine.iface, ifname.name, sizeof(newLine.iface));
     newLine.uid = (int32_t)statsKey.uid;
     newLine.set = (int32_t)statsKey.counterSet;
     newLine.tag = (int32_t)statsKey.tag;
@@ -121,22 +147,22 @@
 
 int parseBpfNetworkStatsDetailInternal(std::vector<stats_line>& lines,
                                        const BpfMapRO<StatsKey, StatsValue>& statsMap,
-                                       const BpfMapRO<uint32_t, IfaceValue>& ifaceMap) {
+                                       const IfIndexToNameFunc ifindex2name) {
     int64_t unknownIfaceBytesTotal = 0;
     const auto processDetailUidStats =
-            [&lines, &unknownIfaceBytesTotal, &ifaceMap](
+            [&lines, &unknownIfaceBytesTotal, &ifindex2name](
                     const StatsKey& key,
                     const BpfMapRO<StatsKey, StatsValue>& statsMap) -> Result<void> {
-        char ifname[IFNAMSIZ];
-        if (getIfaceNameFromMap(ifaceMap, statsMap, key.ifaceIndex, ifname, key,
-                                &unknownIfaceBytesTotal)) {
+        Result<IfaceValue> ifname = ifindex2name(key.ifaceIndex);
+        if (!ifname.ok()) {
+            maybeLogUnknownIface(key.ifaceIndex, statsMap, key, &unknownIfaceBytesTotal);
             return Result<void>();
         }
         Result<StatsValue> statsEntry = statsMap.readValue(key);
         if (!statsEntry.ok()) {
             return base::ResultError(statsEntry.error().message(), statsEntry.error().code());
         }
-        stats_line newLine = populateStatsEntry(key, statsEntry.value(), ifname);
+        stats_line newLine = populateStatsEntry(key, statsEntry.value(), ifname.value());
         lines.push_back(newLine);
         if (newLine.tag) {
             // account tagged traffic in the untagged stats (for historical reasons?)
@@ -166,7 +192,6 @@
 }
 
 int parseBpfNetworkStatsDetail(std::vector<stats_line>* lines) {
-    static BpfMapRO<uint32_t, IfaceValue> ifaceIndexNameMap(IFACE_INDEX_NAME_MAP_PATH);
     static BpfMapRO<uint32_t, uint32_t> configurationMap(CONFIGURATION_MAP_PATH);
     static BpfMap<StatsKey, StatsValue> statsMapA(STATS_MAP_A_PATH);
     static BpfMap<StatsKey, StatsValue> statsMapB(STATS_MAP_B_PATH);
@@ -196,7 +221,7 @@
     // TODO: the above comment feels like it may be obsolete / out of date,
     // since we no longer swap the map via netd binder rpc - though we do
     // still swap it.
-    int ret = parseBpfNetworkStatsDetailInternal(*lines, *inactiveStatsMap, ifaceIndexNameMap);
+    int ret = parseBpfNetworkStatsDetailInternal(*lines, *inactiveStatsMap, ifindex2name);
     if (ret) {
         ALOGE("parse detail network stats failed: %s", strerror(errno));
         return ret;
@@ -213,13 +238,14 @@
 
 int parseBpfNetworkStatsDevInternal(std::vector<stats_line>& lines,
                                     const BpfMapRO<uint32_t, StatsValue>& statsMap,
-                                    const BpfMapRO<uint32_t, IfaceValue>& ifaceMap) {
+                                    const IfIndexToNameFunc ifindex2name) {
     int64_t unknownIfaceBytesTotal = 0;
-    const auto processDetailIfaceStats = [&lines, &unknownIfaceBytesTotal, &ifaceMap, &statsMap](
+    const auto processDetailIfaceStats = [&lines, &unknownIfaceBytesTotal, ifindex2name, &statsMap](
                                              const uint32_t& key, const StatsValue& value,
                                              const BpfMapRO<uint32_t, StatsValue>&) {
-        char ifname[IFNAMSIZ];
-        if (getIfaceNameFromMap(ifaceMap, statsMap, key, ifname, key, &unknownIfaceBytesTotal)) {
+        Result<IfaceValue> ifname = ifindex2name(key);
+        if (!ifname.ok()) {
+            maybeLogUnknownIface(key, statsMap, key, &unknownIfaceBytesTotal);
             return Result<void>();
         }
         StatsKey fakeKey = {
@@ -227,7 +253,7 @@
                 .tag = (uint32_t)TAG_NONE,
                 .counterSet = (uint32_t)SET_ALL,
         };
-        lines.push_back(populateStatsEntry(fakeKey, value, ifname));
+        lines.push_back(populateStatsEntry(fakeKey, value, ifname.value()));
         return Result<void>();
     };
     Result<void> res = statsMap.iterateWithValue(processDetailIfaceStats);
@@ -242,9 +268,7 @@
 }
 
 int parseBpfNetworkStatsDev(std::vector<stats_line>* lines) {
-    static BpfMapRO<uint32_t, IfaceValue> ifaceIndexNameMap(IFACE_INDEX_NAME_MAP_PATH);
-    static BpfMapRO<uint32_t, StatsValue> ifaceStatsMap(IFACE_STATS_MAP_PATH);
-    return parseBpfNetworkStatsDevInternal(*lines, ifaceStatsMap, ifaceIndexNameMap);
+    return parseBpfNetworkStatsDevInternal(*lines, getIfaceStatsMap(), ifindex2name);
 }
 
 void groupNetworkStats(std::vector<stats_line>& lines) {
diff --git a/service-t/native/libs/libnetworkstats/BpfNetworkStatsTest.cpp b/service-t/native/libs/libnetworkstats/BpfNetworkStatsTest.cpp
index bcc4550..484c166 100644
--- a/service-t/native/libs/libnetworkstats/BpfNetworkStatsTest.cpp
+++ b/service-t/native/libs/libnetworkstats/BpfNetworkStatsTest.cpp
@@ -77,6 +77,10 @@
     BpfMap<uint32_t, IfaceValue> mFakeIfaceIndexNameMap;
     BpfMap<uint32_t, StatsValue> mFakeIfaceStatsMap;
 
+    IfIndexToNameFunc mIfIndex2Name = [this](const uint32_t ifindex){
+        return mFakeIfaceIndexNameMap.readValue(ifindex);
+    };
+
     void SetUp() {
         ASSERT_EQ(0, setrlimitForTest());
 
@@ -228,7 +232,7 @@
     populateFakeStats(TEST_UID1, 0, IFACE_INDEX1, TEST_COUNTERSET0, value1, mFakeStatsMap);
     populateFakeStats(TEST_UID1, 0, IFACE_INDEX2, TEST_COUNTERSET1, value1, mFakeStatsMap);
     populateFakeStats(TEST_UID2, 0, IFACE_INDEX3, TEST_COUNTERSET1, value1, mFakeStatsMap);
-    ASSERT_EQ(0, parseBpfNetworkStatsDetailInternal(lines, mFakeStatsMap, mFakeIfaceIndexNameMap));
+    ASSERT_EQ(0, parseBpfNetworkStatsDetailInternal(lines, mFakeStatsMap, mIfIndex2Name));
     ASSERT_EQ((unsigned long)3, lines.size());
 }
 
@@ -256,16 +260,15 @@
     EXPECT_RESULT_OK(mFakeIfaceStatsMap.writeValue(ifaceStatsKey, value1, BPF_ANY));
 
     StatsValue result1 = {};
-    ASSERT_EQ(0, bpfGetIfaceStatsInternal(IFACE_NAME1, &result1, mFakeIfaceStatsMap,
-                                          mFakeIfaceIndexNameMap));
+    ASSERT_EQ(0,
+              bpfGetIfaceStatsInternal(IFACE_NAME1, &result1, mFakeIfaceStatsMap, mIfIndex2Name));
     expectStatsEqual(value1, result1);
     StatsValue result2 = {};
-    ASSERT_EQ(0, bpfGetIfaceStatsInternal(IFACE_NAME2, &result2, mFakeIfaceStatsMap,
-                                          mFakeIfaceIndexNameMap));
+    ASSERT_EQ(0,
+              bpfGetIfaceStatsInternal(IFACE_NAME2, &result2, mFakeIfaceStatsMap, mIfIndex2Name));
     expectStatsEqual(value2, result2);
     StatsValue totalResult = {};
-    ASSERT_EQ(0, bpfGetIfaceStatsInternal(NULL, &totalResult, mFakeIfaceStatsMap,
-                                          mFakeIfaceIndexNameMap));
+    ASSERT_EQ(0, bpfGetIfaceStatsInternal(NULL, &totalResult, mFakeIfaceStatsMap, mIfIndex2Name));
     StatsValue totalValue = {
             .rxPackets = TEST_PACKET0 * 2 + TEST_PACKET1,
             .rxBytes = TEST_BYTES0 * 2 + TEST_BYTES1,
@@ -304,7 +307,7 @@
                       mFakeStatsMap);
     populateFakeStats(TEST_UID2, TEST_TAG, IFACE_INDEX1, TEST_COUNTERSET0, value1, mFakeStatsMap);
     std::vector<stats_line> lines;
-    ASSERT_EQ(0, parseBpfNetworkStatsDetailInternal(lines, mFakeStatsMap, mFakeIfaceIndexNameMap));
+    ASSERT_EQ(0, parseBpfNetworkStatsDetailInternal(lines, mFakeStatsMap, mIfIndex2Name));
     ASSERT_EQ((unsigned long)7, lines.size());
 }
 
@@ -324,7 +327,7 @@
     populateFakeStats(TEST_UID1, 0, IFACE_INDEX1, TEST_COUNTERSET1, value1, mFakeStatsMap);
     populateFakeStats(TEST_UID2, 0, IFACE_INDEX1, TEST_COUNTERSET0, value1, mFakeStatsMap);
     std::vector<stats_line> lines;
-    ASSERT_EQ(0, parseBpfNetworkStatsDetailInternal(lines, mFakeStatsMap, mFakeIfaceIndexNameMap));
+    ASSERT_EQ(0, parseBpfNetworkStatsDetailInternal(lines, mFakeStatsMap, mIfIndex2Name));
     ASSERT_EQ((unsigned long)4, lines.size());
 }
 
@@ -352,18 +355,20 @@
             .counterSet = TEST_COUNTERSET0,
             .ifaceIndex = ifaceIndex,
     };
-    char ifname[IFNAMSIZ];
     int64_t unknownIfaceBytesTotal = 0;
-    ASSERT_EQ(-ENODEV, getIfaceNameFromMap(mFakeIfaceIndexNameMap, mFakeStatsMap, ifaceIndex,
-                                           ifname, curKey, &unknownIfaceBytesTotal));
+    ASSERT_EQ(false, mFakeIfaceIndexNameMap.readValue(ifaceIndex).ok());
+    maybeLogUnknownIface(ifaceIndex, mFakeStatsMap, curKey, &unknownIfaceBytesTotal);
+
     ASSERT_EQ(((int64_t)(TEST_BYTES0 * 20 + TEST_BYTES1 * 20)), unknownIfaceBytesTotal);
     curKey.ifaceIndex = IFACE_INDEX2;
-    ASSERT_EQ(-ENODEV, getIfaceNameFromMap(mFakeIfaceIndexNameMap, mFakeStatsMap, ifaceIndex,
-                                           ifname, curKey, &unknownIfaceBytesTotal));
+
+    ASSERT_EQ(false, mFakeIfaceIndexNameMap.readValue(ifaceIndex).ok());
+    maybeLogUnknownIface(ifaceIndex, mFakeStatsMap, curKey, &unknownIfaceBytesTotal);
+
     ASSERT_EQ(-1, unknownIfaceBytesTotal);
     std::vector<stats_line> lines;
     // TODO: find a way to test the total of unknown Iface Bytes go above limit.
-    ASSERT_EQ(0, parseBpfNetworkStatsDetailInternal(lines, mFakeStatsMap, mFakeIfaceIndexNameMap));
+    ASSERT_EQ(0, parseBpfNetworkStatsDetailInternal(lines, mFakeStatsMap, mIfIndex2Name));
     ASSERT_EQ((unsigned long)1, lines.size());
     expectStatsLineEqual(value1, IFACE_NAME1, TEST_UID1, TEST_COUNTERSET0, 0, lines.front());
 }
@@ -394,8 +399,7 @@
     ifaceStatsKey = IFACE_INDEX4;
     EXPECT_RESULT_OK(mFakeIfaceStatsMap.writeValue(ifaceStatsKey, value2, BPF_ANY));
     std::vector<stats_line> lines;
-    ASSERT_EQ(0,
-              parseBpfNetworkStatsDevInternal(lines, mFakeIfaceStatsMap, mFakeIfaceIndexNameMap));
+    ASSERT_EQ(0, parseBpfNetworkStatsDevInternal(lines, mFakeIfaceStatsMap, mIfIndex2Name));
     ASSERT_EQ((unsigned long)4, lines.size());
 
     expectStatsLineEqual(value1, IFACE_NAME1, UID_ALL, SET_ALL, TAG_NONE, lines[0]);
@@ -439,13 +443,13 @@
     std::vector<stats_line> lines;
 
     // Test empty stats.
-    ASSERT_EQ(0, parseBpfNetworkStatsDetailInternal(lines, mFakeStatsMap, mFakeIfaceIndexNameMap));
+    ASSERT_EQ(0, parseBpfNetworkStatsDetailInternal(lines, mFakeStatsMap, mIfIndex2Name));
     ASSERT_EQ((size_t) 0, lines.size());
     lines.clear();
 
     // Test 1 line stats.
     populateFakeStats(TEST_UID1, TEST_TAG, IFACE_INDEX1, TEST_COUNTERSET0, value1, mFakeStatsMap);
-    ASSERT_EQ(0, parseBpfNetworkStatsDetailInternal(lines, mFakeStatsMap, mFakeIfaceIndexNameMap));
+    ASSERT_EQ(0, parseBpfNetworkStatsDetailInternal(lines, mFakeStatsMap, mIfIndex2Name));
     ASSERT_EQ((size_t) 2, lines.size());  // TEST_TAG != 0 -> 1 entry becomes 2 lines
     expectStatsLineEqual(value1, IFACE_NAME1, TEST_UID1, TEST_COUNTERSET0, 0, lines[0]);
     expectStatsLineEqual(value1, IFACE_NAME1, TEST_UID1, TEST_COUNTERSET0, TEST_TAG, lines[1]);
@@ -457,7 +461,7 @@
     populateFakeStats(TEST_UID1, TEST_TAG + 1, IFACE_INDEX1, TEST_COUNTERSET0, value2,
                       mFakeStatsMap);
     populateFakeStats(TEST_UID2, TEST_TAG, IFACE_INDEX1, TEST_COUNTERSET0, value1, mFakeStatsMap);
-    ASSERT_EQ(0, parseBpfNetworkStatsDetailInternal(lines, mFakeStatsMap, mFakeIfaceIndexNameMap));
+    ASSERT_EQ(0, parseBpfNetworkStatsDetailInternal(lines, mFakeStatsMap, mIfIndex2Name));
     ASSERT_EQ((size_t) 9, lines.size());
     lines.clear();
 
@@ -465,7 +469,7 @@
     populateFakeStats(TEST_UID1, TEST_TAG, IFACE_INDEX3, TEST_COUNTERSET0, value1, mFakeStatsMap);
     populateFakeStats(TEST_UID2, TEST_TAG, IFACE_INDEX3, TEST_COUNTERSET0, value1, mFakeStatsMap);
 
-    ASSERT_EQ(0, parseBpfNetworkStatsDetailInternal(lines, mFakeStatsMap, mFakeIfaceIndexNameMap));
+    ASSERT_EQ(0, parseBpfNetworkStatsDetailInternal(lines, mFakeStatsMap, mIfIndex2Name));
     ASSERT_EQ((size_t) 9, lines.size());
 
     // Verify Sorted & Grouped.
@@ -490,8 +494,7 @@
     ifaceStatsKey = IFACE_INDEX3;
     EXPECT_RESULT_OK(mFakeIfaceStatsMap.writeValue(ifaceStatsKey, value1, BPF_ANY));
 
-    ASSERT_EQ(0,
-              parseBpfNetworkStatsDevInternal(lines, mFakeIfaceStatsMap, mFakeIfaceIndexNameMap));
+    ASSERT_EQ(0, parseBpfNetworkStatsDevInternal(lines, mFakeIfaceStatsMap, mIfIndex2Name));
     ASSERT_EQ((size_t) 2, lines.size());
 
     expectStatsLineEqual(value3, IFACE_NAME1, UID_ALL, SET_ALL, TAG_NONE, lines[0]);
@@ -532,7 +535,7 @@
     // TODO: Mutate counterSet and enlarge TEST_MAP_SIZE if overflow on counterSet is possible.
 
     std::vector<stats_line> lines;
-    ASSERT_EQ(0, parseBpfNetworkStatsDetailInternal(lines, mFakeStatsMap, mFakeIfaceIndexNameMap));
+    ASSERT_EQ(0, parseBpfNetworkStatsDetailInternal(lines, mFakeStatsMap, mIfIndex2Name));
     ASSERT_EQ((size_t) 12, lines.size());
 
     // Uid 0 first
diff --git a/service-t/native/libs/libnetworkstats/include/netdbpf/BpfNetworkStats.h b/service-t/native/libs/libnetworkstats/include/netdbpf/BpfNetworkStats.h
index 8058d05..59eb195 100644
--- a/service-t/native/libs/libnetworkstats/include/netdbpf/BpfNetworkStats.h
+++ b/service-t/native/libs/libnetworkstats/include/netdbpf/BpfNetworkStats.h
@@ -55,36 +55,27 @@
 bool operator==(const stats_line& lhs, const stats_line& rhs);
 bool operator<(const stats_line& lhs, const stats_line& rhs);
 
+// This mirrors BpfMap.h's:
+//   Result<Value> readValue(const Key key) const
+// for a BpfMap<uint32_t, IfaceValue>
+using IfIndexToNameFunc = std::function<Result<IfaceValue>(const uint32_t)>;
+
 // For test only
 int bpfGetUidStatsInternal(uid_t uid, StatsValue* stats,
                            const BpfMapRO<uint32_t, StatsValue>& appUidStatsMap);
 // For test only
 int bpfGetIfaceStatsInternal(const char* iface, StatsValue* stats,
                              const BpfMapRO<uint32_t, StatsValue>& ifaceStatsMap,
-                             const BpfMapRO<uint32_t, IfaceValue>& ifaceNameMap);
+                             const IfIndexToNameFunc ifindex2name);
 // For test only
 int bpfGetIfIndexStatsInternal(uint32_t ifindex, StatsValue* stats,
                                const BpfMapRO<uint32_t, StatsValue>& ifaceStatsMap);
 // For test only
 int parseBpfNetworkStatsDetailInternal(std::vector<stats_line>& lines,
                                        const BpfMapRO<StatsKey, StatsValue>& statsMap,
-                                       const BpfMapRO<uint32_t, IfaceValue>& ifaceMap);
+                                       const IfIndexToNameFunc ifindex2name);
 // For test only
 int cleanStatsMapInternal(const base::unique_fd& cookieTagMap, const base::unique_fd& tagStatsMap);
-// For test only
-template <class Key>
-int getIfaceNameFromMap(const BpfMapRO<uint32_t, IfaceValue>& ifaceMap,
-                        const BpfMapRO<Key, StatsValue>& statsMap,
-                        uint32_t ifaceIndex, char* ifname,
-                        const Key& curKey, int64_t* unknownIfaceBytesTotal) {
-    auto iface = ifaceMap.readValue(ifaceIndex);
-    if (!iface.ok()) {
-        maybeLogUnknownIface(ifaceIndex, statsMap, curKey, unknownIfaceBytesTotal);
-        return -ENODEV;
-    }
-    strlcpy(ifname, iface.value().name, sizeof(IfaceValue));
-    return 0;
-}
 
 template <class Key>
 void maybeLogUnknownIface(int ifaceIndex, const BpfMapRO<Key, StatsValue>& statsMap,
@@ -112,8 +103,9 @@
 // For test only
 int parseBpfNetworkStatsDevInternal(std::vector<stats_line>& lines,
                                     const BpfMapRO<uint32_t, StatsValue>& statsMap,
-                                    const BpfMapRO<uint32_t, IfaceValue>& ifaceMap);
+                                    const IfIndexToNameFunc ifindex2name);
 
+void bpfRegisterIface(const char* iface);
 int bpfGetUidStats(uid_t uid, StatsValue* stats);
 int bpfGetIfaceStats(const char* iface, StatsValue* stats);
 int bpfGetIfIndexStats(int ifindex, StatsValue* stats);
diff --git a/service-t/src/com/android/server/IpSecXfrmController.java b/service-t/src/com/android/server/IpSecXfrmController.java
index c8abd40..3cfbf83 100644
--- a/service-t/src/com/android/server/IpSecXfrmController.java
+++ b/service-t/src/com/android/server/IpSecXfrmController.java
@@ -15,6 +15,7 @@
  */
 package com.android.server;
 
+import static com.android.net.module.util.netlink.NetlinkUtils.SOCKET_RECV_BUFSIZE;
 import static com.android.net.module.util.netlink.xfrm.XfrmNetlinkMessage.IPPROTO_ESP;
 import static com.android.net.module.util.netlink.xfrm.XfrmNetlinkMessage.NETLINK_XFRM;
 import static com.android.net.module.util.netlink.xfrm.XfrmNetlinkMessage.XFRM_MSG_NEWSA;
@@ -106,7 +107,8 @@
     public static class Dependencies {
         /** Get a new XFRM netlink socket and connect it */
         public FileDescriptor newNetlinkSocket() throws ErrnoException, SocketException {
-            final FileDescriptor fd = NetlinkUtils.netlinkSocketForProto(NETLINK_XFRM);
+            final FileDescriptor fd =
+                    NetlinkUtils.netlinkSocketForProto(NETLINK_XFRM, SOCKET_RECV_BUFSIZE);
             NetlinkUtils.connectToKernel(fd);
             return fd;
         }
diff --git a/service-t/src/com/android/server/NsdService.java b/service-t/src/com/android/server/NsdService.java
index 9b2f80b..cfb1a33 100644
--- a/service-t/src/com/android/server/NsdService.java
+++ b/service-t/src/com/android/server/NsdService.java
@@ -26,8 +26,9 @@
 import static android.net.nsd.NsdManager.MDNS_DISCOVERY_MANAGER_EVENT;
 import static android.net.nsd.NsdManager.MDNS_SERVICE_EVENT;
 import static android.net.nsd.NsdManager.RESOLVE_SERVICE_SUCCEEDED;
+import static android.net.nsd.NsdManager.SUBTYPE_LABEL_REGEX;
 import static android.net.nsd.NsdManager.TYPE_REGEX;
-import static android.net.nsd.NsdManager.TYPE_SUBTYPE_LABEL_REGEX;
+import static android.os.Process.SYSTEM_UID;
 import static android.provider.DeviceConfig.NAMESPACE_TETHERING;
 
 import static com.android.modules.utils.build.SdkLevel.isAtLeastU;
@@ -35,6 +36,8 @@
 import static com.android.server.connectivity.mdns.MdnsAdvertiser.AdvertiserMetrics;
 import static com.android.server.connectivity.mdns.MdnsConstants.NO_PACKET;
 import static com.android.server.connectivity.mdns.MdnsRecord.MAX_LABEL_LENGTH;
+import static com.android.server.connectivity.mdns.MdnsSearchOptions.AGGRESSIVE_QUERY_MODE;
+import static com.android.server.connectivity.mdns.MdnsSearchOptions.PASSIVE_QUERY_MODE;
 import static com.android.server.connectivity.mdns.util.MdnsUtils.Clock;
 
 import android.annotation.NonNull;
@@ -54,6 +57,7 @@
 import android.net.mdns.aidl.RegistrationInfo;
 import android.net.mdns.aidl.ResolutionInfo;
 import android.net.nsd.AdvertisingRequest;
+import android.net.nsd.DiscoveryRequest;
 import android.net.nsd.INsdManager;
 import android.net.nsd.INsdManagerCallback;
 import android.net.nsd.INsdServiceConnector;
@@ -89,6 +93,7 @@
 import com.android.modules.utils.build.SdkLevel;
 import com.android.net.module.util.CollectionUtils;
 import com.android.net.module.util.DeviceConfigUtils;
+import com.android.net.module.util.HandlerUtils;
 import com.android.net.module.util.InetAddressUtils;
 import com.android.net.module.util.PermissionUtils;
 import com.android.net.module.util.SharedLog;
@@ -112,6 +117,7 @@
 import java.net.NetworkInterface;
 import java.net.SocketException;
 import java.net.UnknownHostException;
+import java.time.Duration;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
@@ -250,6 +256,8 @@
 
     private final RemoteCallbackList<IOffloadEngine> mOffloadEngines =
             new RemoteCallbackList<>();
+    @NonNull
+    private final MdnsFeatureFlags mMdnsFeatureFlags;
 
     private static class OffloadEngineInfo {
         @NonNull final String mInterfaceName;
@@ -733,6 +741,33 @@
                 return new ArraySet<>(subtypeMap.values());
             }
 
+            private boolean checkTtl(
+                        @Nullable Duration ttl, @NonNull ClientInfo clientInfo) {
+                if (ttl == null) {
+                    return true;
+                }
+
+                final long ttlSeconds = ttl.toSeconds();
+                final int uid = clientInfo.getUid();
+
+                // Allows Thread module in the system_server to register TTL that is smaller than
+                // 30 seconds
+                final long minTtlSeconds = uid == SYSTEM_UID ? 0 : NsdManager.TTL_SECONDS_MIN;
+
+                // Allows Thread module in the system_server to register TTL that is larger than
+                // 10 hours
+                final long maxTtlSeconds =
+                        uid == SYSTEM_UID ? 0xffffffffL : NsdManager.TTL_SECONDS_MAX;
+
+                if (ttlSeconds < minTtlSeconds || ttlSeconds > maxTtlSeconds) {
+                    mServiceLogs.e("ttlSeconds exceeds allowed range (value = "
+                            + ttlSeconds + ", allowedRange = [" + minTtlSeconds
+                            + ", " + maxTtlSeconds + " ])");
+                    return false;
+                }
+                return true;
+            }
+
             @Override
             public boolean processMessage(Message msg) {
                 final ClientInfo clientInfo;
@@ -742,8 +777,8 @@
                 switch (msg.what) {
                     case NsdManager.DISCOVER_SERVICES: {
                         if (DBG) Log.d(TAG, "Discover services");
-                        final ListenerArgs args = (ListenerArgs) msg.obj;
-                        clientInfo = mClients.get(args.connector);
+                        final DiscoveryArgs discoveryArgs = (DiscoveryArgs) msg.obj;
+                        clientInfo = mClients.get(discoveryArgs.connector);
                         // If the binder death notification for a INsdManagerCallback was received
                         // before any calls are received by NsdService, the clientInfo would be
                         // cleared and cause NPE. Add a null check here to prevent this corner case.
@@ -758,10 +793,10 @@
                             break;
                         }
 
-                        final NsdServiceInfo info = args.serviceInfo;
+                        final DiscoveryRequest discoveryRequest = discoveryArgs.discoveryRequest;
                         transactionId = getUniqueId();
                         final Pair<String, List<String>> typeAndSubtype =
-                                parseTypeAndSubtype(info.getServiceType());
+                                parseTypeAndSubtype(discoveryRequest.getServiceType());
                         final String serviceType = typeAndSubtype == null
                                 ? null : typeAndSubtype.first;
                         if (clientInfo.mUseJavaBackend
@@ -773,41 +808,55 @@
                                 break;
                             }
 
+                            String subtype = discoveryRequest.getSubtype();
+                            if (subtype == null && !typeAndSubtype.second.isEmpty()) {
+                                subtype = typeAndSubtype.second.get(0);
+                            }
+
+                            if (subtype != null && !checkSubtypeLabel(subtype)) {
+                                clientInfo.onDiscoverServicesFailedImmediately(clientRequestId,
+                                        NsdManager.FAILURE_BAD_PARAMETERS, false /* isLegacy */);
+                                break;
+                            }
+
                             final String listenServiceType = serviceType + ".local";
                             maybeStartMonitoringSockets();
                             final MdnsListener listener = new DiscoveryListener(clientRequestId,
                                     transactionId, listenServiceType);
                             final MdnsSearchOptions.Builder optionsBuilder =
                                     MdnsSearchOptions.newBuilder()
-                                            .setNetwork(info.getNetwork())
+                                            .setNetwork(discoveryRequest.getNetwork())
                                             .setRemoveExpiredService(true)
-                                            .setIsPassiveMode(true);
-                            if (!typeAndSubtype.second.isEmpty()) {
-                                // The parsing ensures subtype starts with an underscore.
+                                            .setQueryMode(
+                                                    mMdnsFeatureFlags.isAggressiveQueryModeEnabled()
+                                                            ? AGGRESSIVE_QUERY_MODE
+                                                            : PASSIVE_QUERY_MODE);
+                            if (subtype != null) {
+                                // checkSubtypeLabels() ensures that subtypes start with '_' but
                                 // MdnsSearchOptions expects the underscore to not be present.
-                                optionsBuilder.addSubtype(
-                                        typeAndSubtype.second.get(0).substring(1));
+                                optionsBuilder.addSubtype(subtype.substring(1));
                             }
                             mMdnsDiscoveryManager.registerListener(
                                     listenServiceType, listener, optionsBuilder.build());
                             final ClientRequest request = storeDiscoveryManagerRequestMap(
                                     clientRequestId, transactionId, listener, clientInfo,
-                                    info.getNetwork());
-                            clientInfo.onDiscoverServicesStarted(clientRequestId, info, request);
+                                    discoveryRequest.getNetwork());
+                            clientInfo.onDiscoverServicesStarted(
+                                    clientRequestId, discoveryRequest, request);
                             clientInfo.log("Register a DiscoveryListener " + transactionId
                                     + " for service type:" + listenServiceType);
                         } else {
                             maybeStartDaemon();
-                            if (discoverServices(transactionId, info)) {
+                            if (discoverServices(transactionId, discoveryRequest)) {
                                 if (DBG) {
                                     Log.d(TAG, "Discover " + msg.arg2 + " " + transactionId
-                                            + info.getServiceType());
+                                            + discoveryRequest.getServiceType());
                                 }
                                 final ClientRequest request = storeLegacyRequestMap(clientRequestId,
                                         transactionId, clientInfo, msg.what,
                                         mClock.elapsedRealtime());
                                 clientInfo.onDiscoverServicesStarted(
-                                        clientRequestId, info, request);
+                                        clientRequestId, discoveryRequest, request);
                             } else {
                                 stopServiceDiscovery(transactionId);
                                 clientInfo.onDiscoverServicesFailedImmediately(clientRequestId,
@@ -882,10 +931,18 @@
                                 serviceType);
                         final String registerServiceType = typeSubtype == null
                                 ? null : typeSubtype.first;
+                        final String hostname = serviceInfo.getHostname();
+                        // Keep compatible with the legacy behavior: It's allowed to set host
+                        // addresses for a service registration although the host addresses
+                        // won't be registered. To register the addresses for a host, the
+                        // hostname must be specified.
+                        if (hostname == null) {
+                            serviceInfo.setHostAddresses(Collections.emptyList());
+                        }
                         if (clientInfo.mUseJavaBackend
                                 || mDeps.isMdnsAdvertiserEnabled(mContext)
                                 || useAdvertiserForType(registerServiceType)) {
-                            if (registerServiceType == null) {
+                            if (serviceType != null && registerServiceType == null) {
                                 Log.e(TAG, "Invalid service type: " + serviceType);
                                 clientInfo.onRegisterServiceFailedImmediately(clientRequestId,
                                         NsdManager.FAILURE_INTERNAL_ERROR, false /* isLegacy */);
@@ -908,14 +965,25 @@
                             } else {
                                 transactionId = getUniqueId();
                             }
-                            serviceInfo.setServiceType(registerServiceType);
-                            serviceInfo.setServiceName(truncateServiceName(
-                                    serviceInfo.getServiceName()));
+
+                            if (registerServiceType != null) {
+                                serviceInfo.setServiceType(registerServiceType);
+                                serviceInfo.setServiceName(
+                                        truncateServiceName(serviceInfo.getServiceName()));
+                            }
+
+                            if (!checkHostname(hostname)) {
+                                clientInfo.onRegisterServiceFailedImmediately(clientRequestId,
+                                        NsdManager.FAILURE_BAD_PARAMETERS, false /* isLegacy */);
+                                break;
+                            }
 
                             Set<String> subtypes = new ArraySet<>(serviceInfo.getSubtypes());
-                            for (String subType: typeSubtype.second) {
-                                if (!TextUtils.isEmpty(subType)) {
-                                    subtypes.add(subType);
+                            if (typeSubtype != null && typeSubtype.second != null) {
+                                for (String subType : typeSubtype.second) {
+                                    if (!TextUtils.isEmpty(subType)) {
+                                        subtypes.add(subType);
+                                    }
                                 }
                             }
                             subtypes = dedupSubtypeLabels(subtypes);
@@ -926,13 +994,21 @@
                                 break;
                             }
 
+                            if (!checkTtl(advertisingRequest.getTtl(), clientInfo)) {
+                                clientInfo.onRegisterServiceFailedImmediately(clientRequestId,
+                                        NsdManager.FAILURE_BAD_PARAMETERS, false /* isLegacy */);
+                                break;
+                            }
+
                             serviceInfo.setSubtypes(subtypes);
                             maybeStartMonitoringSockets();
                             final MdnsAdvertisingOptions mdnsAdvertisingOptions =
-                                    MdnsAdvertisingOptions.newBuilder().setIsOnlyUpdate(
-                                            isUpdateOnly).build();
+                                    MdnsAdvertisingOptions.newBuilder()
+                                            .setIsOnlyUpdate(isUpdateOnly)
+                                            .setTtl(advertisingRequest.getTtl())
+                                            .build();
                             mAdvertiser.addOrUpdateService(transactionId, serviceInfo,
-                                    mdnsAdvertisingOptions);
+                                    mdnsAdvertisingOptions, clientInfo.mUid);
                             storeAdvertiserRequestMap(clientRequestId, transactionId, clientInfo,
                                     serviceInfo.getNetwork());
                         } else {
@@ -1031,7 +1107,9 @@
                                     transactionId, resolveServiceType);
                             final MdnsSearchOptions options = MdnsSearchOptions.newBuilder()
                                     .setNetwork(info.getNetwork())
-                                    .setIsPassiveMode(true)
+                                    .setQueryMode(mMdnsFeatureFlags.isAggressiveQueryModeEnabled()
+                                            ? AGGRESSIVE_QUERY_MODE
+                                            : PASSIVE_QUERY_MODE)
                                     .setResolveInstanceName(info.getServiceName())
                                     .setRemoveExpiredService(true)
                                     .build();
@@ -1129,7 +1207,9 @@
                                 transactionId, resolveServiceType);
                         final MdnsSearchOptions options = MdnsSearchOptions.newBuilder()
                                 .setNetwork(info.getNetwork())
-                                .setIsPassiveMode(true)
+                                .setQueryMode(mMdnsFeatureFlags.isAggressiveQueryModeEnabled()
+                                        ? AGGRESSIVE_QUERY_MODE
+                                        : PASSIVE_QUERY_MODE)
                                 .setResolveInstanceName(info.getServiceName())
                                 .setRemoveExpiredService(true)
                                 .build();
@@ -1469,6 +1549,7 @@
                         network == null ? INetd.LOCAL_NET_ID : network.netId,
                         serviceInfo.getInterfaceIndex());
                 servInfo.setSubtypes(dedupSubtypeLabels(serviceInfo.getSubtypes()));
+                servInfo.setExpirationTime(serviceInfo.getExpirationTime());
                 return servInfo;
             }
 
@@ -1522,6 +1603,7 @@
                                 Log.e(TAG, "Invalid attribute", e);
                             }
                         }
+                        info.setHostname(getHostname(serviceInfo));
                         final List<InetAddress> addresses = getInetAddresses(serviceInfo);
                         if (addresses.size() != 0) {
                             info.setHostAddresses(addresses);
@@ -1558,6 +1640,7 @@
                             }
                         }
 
+                        info.setHostname(getHostname(serviceInfo));
                         final List<InetAddress> addresses = getInetAddresses(serviceInfo);
                         info.setHostAddresses(addresses);
                         clientInfo.onServiceUpdated(clientRequestId, info, request);
@@ -1604,6 +1687,16 @@
         return addresses;
     }
 
+    @NonNull
+    private static String getHostname(@NonNull MdnsServiceInfo serviceInfo) {
+        String[] hostname = serviceInfo.getHostName();
+        // Strip the "local" top-level domain.
+        if (hostname.length >= 2 && hostname[hostname.length - 1].equals("local")) {
+            hostname = Arrays.copyOf(hostname, hostname.length - 1);
+        }
+        return String.join(".", hostname);
+    }
+
     private static void setServiceNetworkForCallback(NsdServiceInfo info, int netId, int ifaceIdx) {
         switch (netId) {
             case NETID_UNSET:
@@ -1689,9 +1782,24 @@
         return new Pair<>(queryType, Collections.emptyList());
     }
 
+    /**
+     * Checks if the hostname is valid.
+     *
+     * <p>For now NsdService only allows single-label hostnames conforming to RFC 1035. In other
+     * words, the hostname should be at most 63 characters long and it only contains letters, digits
+     * and hyphens.
+     */
+    public static boolean checkHostname(@Nullable String hostname) {
+        if (hostname == null) {
+            return true;
+        }
+        String HOSTNAME_REGEX = "^[a-zA-Z]([a-zA-Z0-9-_]{0,61}[a-zA-Z0-9])?$";
+        return Pattern.compile(HOSTNAME_REGEX).matcher(hostname).matches();
+    }
+
     /** Returns {@code true} if {@code subtype} is a valid DNS-SD subtype label. */
     private static boolean checkSubtypeLabel(String subtype) {
-        return Pattern.compile("^" + TYPE_SUBTYPE_LABEL_REGEX + "$").matcher(subtype).matches();
+        return Pattern.compile("^" + SUBTYPE_LABEL_REGEX + "$").matcher(subtype).matches();
     }
 
     @VisibleForTesting
@@ -1728,7 +1836,7 @@
         am.addOnUidImportanceListener(new UidImportanceListener(handler),
                 mRunningAppActiveImportanceCutoff);
 
-        final MdnsFeatureFlags flags = new MdnsFeatureFlags.Builder()
+        mMdnsFeatureFlags = new MdnsFeatureFlags.Builder()
                 .setIsMdnsOffloadFeatureEnabled(mDeps.isTetheringFeatureNotChickenedOut(
                         mContext, MdnsFeatureFlags.NSD_FORCE_DISABLE_MDNS_OFFLOAD))
                 .setIncludeInetAddressRecordsInProbing(mDeps.isFeatureEnabled(
@@ -1741,17 +1849,21 @@
                         mContext, MdnsFeatureFlags.NSD_KNOWN_ANSWER_SUPPRESSION))
                 .setIsUnicastReplyEnabled(mDeps.isFeatureEnabled(
                         mContext, MdnsFeatureFlags.NSD_UNICAST_REPLY_ENABLED))
+                .setIsAggressiveQueryModeEnabled(mDeps.isFeatureEnabled(
+                        mContext, MdnsFeatureFlags.NSD_AGGRESSIVE_QUERY_MODE))
                 .setOverrideProvider(flag -> mDeps.isFeatureEnabled(
                         mContext, FORCE_ENABLE_FLAG_FOR_TEST_PREFIX + flag))
                 .build();
         mMdnsSocketClient =
                 new MdnsMultinetworkSocketClient(handler.getLooper(), mMdnsSocketProvider,
-                        LOGGER.forSubComponent("MdnsMultinetworkSocketClient"), flags);
+                        LOGGER.forSubComponent("MdnsMultinetworkSocketClient"), mMdnsFeatureFlags);
         mMdnsDiscoveryManager = deps.makeMdnsDiscoveryManager(new ExecutorProvider(),
-                mMdnsSocketClient, LOGGER.forSubComponent("MdnsDiscoveryManager"), flags);
+                mMdnsSocketClient, LOGGER.forSubComponent("MdnsDiscoveryManager"),
+                mMdnsFeatureFlags);
         handler.post(() -> mMdnsSocketClient.setCallback(mMdnsDiscoveryManager));
         mAdvertiser = deps.makeMdnsAdvertiser(handler.getLooper(), mMdnsSocketProvider,
-                new AdvertiserCallback(), LOGGER.forSubComponent("MdnsAdvertiser"), flags);
+                new AdvertiserCallback(), LOGGER.forSubComponent("MdnsAdvertiser"),
+                mMdnsFeatureFlags, mContext);
         mClock = deps.makeClock();
     }
 
@@ -1807,13 +1919,6 @@
         }
 
         /**
-         * @see DeviceConfigUtils#isTrunkStableFeatureEnabled
-         */
-        public boolean isTrunkStableFeatureEnabled(String feature) {
-            return DeviceConfigUtils.isTrunkStableFeatureEnabled(feature);
-        }
-
-        /**
          * @see MdnsDiscoveryManager
          */
         public MdnsDiscoveryManager makeMdnsDiscoveryManager(
@@ -1830,8 +1935,8 @@
         public MdnsAdvertiser makeMdnsAdvertiser(
                 @NonNull Looper looper, @NonNull MdnsSocketProvider socketProvider,
                 @NonNull MdnsAdvertiser.AdvertiserCallback cb, @NonNull SharedLog sharedLog,
-                MdnsFeatureFlags featureFlags) {
-            return new MdnsAdvertiser(looper, socketProvider, cb, sharedLog, featureFlags);
+                MdnsFeatureFlags featureFlags, Context context) {
+            return new MdnsAdvertiser(looper, socketProvider, cb, sharedLog, featureFlags, context);
         }
 
         /**
@@ -2017,9 +2122,10 @@
             final int clientRequestId = getClientRequestIdOrLog(clientInfo, transactionId);
             if (clientRequestId < 0) return;
 
-            // onRegisterServiceSucceeded only has the service name in its info. This aligns with
-            // historical behavior.
+            // onRegisterServiceSucceeded only has the service name and hostname in its info. This
+            // aligns with historical behavior.
             final NsdServiceInfo cbInfo = new NsdServiceInfo(registeredInfo.getServiceName(), null);
+            cbInfo.setHostname(registeredInfo.getHostname());
             final ClientRequest request = clientInfo.mClientRequests.get(clientRequestId);
             clientInfo.onRegisterServiceSucceeded(clientRequestId, cbInfo, request);
         }
@@ -2114,12 +2220,22 @@
         }
     }
 
+    private static final class DiscoveryArgs {
+        public final NsdServiceConnector connector;
+        public final DiscoveryRequest discoveryRequest;
+        DiscoveryArgs(NsdServiceConnector connector, DiscoveryRequest discoveryRequest) {
+            this.connector = connector;
+            this.discoveryRequest = discoveryRequest;
+        }
+    }
+
     private class NsdServiceConnector extends INsdServiceConnector.Stub
             implements IBinder.DeathRecipient  {
 
         @Override
         public void registerService(int listenerKey, AdvertisingRequest advertisingRequest)
                 throws RemoteException {
+            NsdManager.checkServiceInfoForRegistration(advertisingRequest.getServiceInfo());
             mNsdStateMachine.sendMessage(mNsdStateMachine.obtainMessage(
                     NsdManager.REGISTER_SERVICE, 0, listenerKey,
                     new AdvertisingArgs(this, advertisingRequest)
@@ -2134,10 +2250,10 @@
         }
 
         @Override
-        public void discoverServices(int listenerKey, NsdServiceInfo serviceInfo) {
+        public void discoverServices(int listenerKey, DiscoveryRequest discoveryRequest) {
             mNsdStateMachine.sendMessage(mNsdStateMachine.obtainMessage(
                     NsdManager.DISCOVER_SERVICES, 0, listenerKey,
-                    new ListenerArgs(this, serviceInfo)));
+                    new DiscoveryArgs(this, discoveryRequest)));
         }
 
         @Override
@@ -2224,7 +2340,7 @@
                 permissionsList.add(DEVICE_POWER);
             }
 
-            if (PermissionUtils.checkAnyPermissionOf(context,
+            if (PermissionUtils.hasAnyPermissionOf(context,
                     permissionsList.toArray(new String[0]))) {
                 return;
             }
@@ -2276,15 +2392,15 @@
         return mMDnsManager.stopOperation(transactionId);
     }
 
-    private boolean discoverServices(int transactionId, NsdServiceInfo serviceInfo) {
+    private boolean discoverServices(int transactionId, DiscoveryRequest discoveryRequest) {
         if (mMDnsManager == null) {
             Log.wtf(TAG, "discoverServices: mMDnsManager is null");
             return false;
         }
 
-        final String type = serviceInfo.getServiceType();
-        final int discoverInterface = getNetworkInterfaceIndex(serviceInfo);
-        if (serviceInfo.getNetwork() != null && discoverInterface == IFACE_IDX_ANY) {
+        final String type = discoveryRequest.getServiceType();
+        final int discoverInterface = getNetworkInterfaceIndex(discoveryRequest);
+        if (discoveryRequest.getNetwork() != null && discoverInterface == IFACE_IDX_ANY) {
             Log.e(TAG, "Interface to discover service on not found");
             return false;
         }
@@ -2334,7 +2450,26 @@
             }
             return IFACE_IDX_ANY;
         }
+        return getNetworkInterfaceIndex(network);
+    }
 
+    /**
+     * Returns the interface to use to discover a service on a specific network, or {@link
+     * IFACE_IDX_ANY} if no network is specified.
+     */
+    private int getNetworkInterfaceIndex(DiscoveryRequest discoveryRequest) {
+        final Network network = discoveryRequest.getNetwork();
+        if (network == null) {
+            return IFACE_IDX_ANY;
+        }
+        return getNetworkInterfaceIndex(network);
+    }
+
+    /**
+     * Returns the interface of a specific network, or {@link IFACE_IDX_ANY} if no interface is
+     * associated with {@code network}.
+     */
+    private int getNetworkInterfaceIndex(@NonNull Network network) {
         String interfaceName = getNetworkInterfaceName(network);
         if (interfaceName == null) {
             return IFACE_IDX_ANY;
@@ -2402,7 +2537,7 @@
 
     @Override
     public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
-        if (!PermissionUtils.checkDumpPermission(mContext, TAG, writer)) return;
+        if (!PermissionUtils.hasDumpPermission(mContext, TAG, writer)) return;
 
         final IndentingPrintWriter pw = new IndentingPrintWriter(writer, "  ");
         // Dump state machine logs
@@ -2414,6 +2549,14 @@
         pw.increaseIndent();
         mServiceLogs.reverseDump(pw);
         pw.decreaseIndent();
+
+        //Dump DiscoveryManager
+        pw.println();
+        pw.println("DiscoveryManager:");
+        pw.increaseIndent();
+        HandlerUtils.runWithScissorsForDump(
+                mNsdStateMachine.getHandler(), () -> mMdnsDiscoveryManager.dump(pw), 10_000);
+        pw.decreaseIndent();
     }
 
     private abstract static class ClientRequest {
@@ -2520,7 +2663,15 @@
     /* Information tracked per client */
     private class ClientInfo {
 
-        private static final int MAX_LIMIT = 10;
+        /**
+         * Maximum number of requests (callbacks) for a client.
+         *
+         * 200 listeners should be more than enough for most use-cases: even if a client tries to
+         * file callbacks for every service on a local network, there are generally much less than
+         * 200 devices on a local network (a /24 only allows 255 IPv4 devices), and while some
+         * devices may have multiple services, many devices do not advertise any.
+         */
+        private static final int MAX_LIMIT = 200;
         private final INsdManagerCallback mCb;
         /* Remembers a resolved service until getaddrinfo completes */
         private NsdServiceInfo mResolvedService;
@@ -2567,6 +2718,10 @@
             return sb.toString();
         }
 
+        public int getUid() {
+            return mUid;
+        }
+
         private boolean isPreSClient() {
             return mIsPreSClient;
         }
@@ -2709,12 +2864,12 @@
                     && !(request instanceof AdvertiserClientRequest);
         }
 
-        void onDiscoverServicesStarted(int listenerKey, NsdServiceInfo info,
+        void onDiscoverServicesStarted(int listenerKey, DiscoveryRequest discoveryRequest,
                 ClientRequest request) {
             mMetrics.reportServiceDiscoveryStarted(
                     isLegacyClientRequest(request), request.mTransactionId);
             try {
-                mCb.onDiscoverServicesStarted(listenerKey, info);
+                mCb.onDiscoverServicesStarted(listenerKey, discoveryRequest);
             } catch (RemoteException e) {
                 Log.e(TAG, "Error calling onDiscoverServicesStarted", e);
             }
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsAdvertiser.java b/service-t/src/com/android/server/connectivity/mdns/MdnsAdvertiser.java
index 135d957..c162bcc 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsAdvertiser.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsAdvertiser.java
@@ -17,11 +17,14 @@
 package com.android.server.connectivity.mdns;
 
 import static com.android.server.connectivity.mdns.MdnsConstants.NO_PACKET;
+import static com.android.server.connectivity.mdns.MdnsInterfaceAdvertiser.CONFLICT_HOST;
+import static com.android.server.connectivity.mdns.MdnsInterfaceAdvertiser.CONFLICT_SERVICE;
 import static com.android.server.connectivity.mdns.MdnsRecord.MAX_LABEL_LENGTH;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresApi;
+import android.content.Context;
 import android.net.LinkAddress;
 import android.net.Network;
 import android.net.nsd.NsdManager;
@@ -30,13 +33,16 @@
 import android.net.nsd.OffloadServiceInfo;
 import android.os.Build;
 import android.os.Looper;
+import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.Log;
 import android.util.SparseArray;
 
+import com.android.connectivity.resources.R;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.net.module.util.CollectionUtils;
 import com.android.net.module.util.SharedLog;
+import com.android.server.connectivity.ConnectivityResources;
 import com.android.server.connectivity.mdns.util.MdnsUtils;
 
 import java.util.ArrayList;
@@ -84,6 +90,7 @@
     private final Map<String, List<OffloadServiceInfoWrapper>> mInterfaceOffloadServices =
             new ArrayMap<>();
     private final MdnsFeatureFlags mMdnsFeatureFlags;
+    private final Map<String, Integer> mServiceTypeToOffloadPriority;
 
     /**
      * Dependencies for {@link MdnsAdvertiser}, useful for testing.
@@ -147,7 +154,9 @@
                 mSharedLog.wtf("Register succeeded for unknown registration");
                 return;
             }
-            if (mMdnsFeatureFlags.mIsMdnsOffloadFeatureEnabled) {
+            if (mMdnsFeatureFlags.mIsMdnsOffloadFeatureEnabled
+                    // TODO: Enable offload when the serviceInfo contains a custom host.
+                    && TextUtils.isEmpty(registration.getServiceInfo().getHostname())) {
                 final String interfaceName = advertiser.getSocketInterfaceName();
                 final List<OffloadServiceInfoWrapper> existingOffloadServiceInfoWrappers =
                         mInterfaceOffloadServices.computeIfAbsent(interfaceName,
@@ -175,8 +184,11 @@
         }
 
         @Override
-        public void onServiceConflict(@NonNull MdnsInterfaceAdvertiser advertiser, int serviceId) {
-            mSharedLog.i("Found conflict, restarted probing for service " + serviceId);
+        public void onServiceConflict(@NonNull MdnsInterfaceAdvertiser advertiser, int serviceId,
+                int conflictType) {
+            mSharedLog.i("Found conflict, restarted probing for service "
+                    + serviceId + " "
+                    + conflictType);
 
             final Registration registration = mRegistrations.get(serviceId);
             if (registration == null) return;
@@ -201,10 +213,22 @@
                 return;
             }
 
-            // Conflict was found during probing; rename once to find a name that has no conflict
-            registration.updateForConflict(
-                    registration.makeNewServiceInfoForConflict(1 /* renameCount */),
-                    1 /* renameCount */);
+            if ((conflictType & CONFLICT_SERVICE) != 0) {
+                // Service conflict was found during probing; rename once to find a name that has no
+                // conflict
+                registration.updateForServiceConflict(
+                        registration.makeNewServiceInfoForServiceConflict(1 /* renameCount */),
+                        1 /* renameCount */);
+            }
+
+            if ((conflictType & CONFLICT_HOST) != 0) {
+                // Host conflict was found during probing; rename once to find a name that has no
+                // conflict
+                registration.updateForHostConflict(
+                        registration.makeNewServiceInfoForHostConflict(1 /* renameCount */),
+                        1 /* renameCount */);
+            }
+
             registration.mConflictDuringProbingCount++;
 
             // Keep renaming if the new name conflicts in local registrations
@@ -227,23 +251,54 @@
         }
     };
 
-    private boolean hasAnyConflict(
+    private boolean hasAnyServiceConflict(
             @NonNull BiPredicate<Network, InterfaceAdvertiserRequest> applicableAdvertiserFilter,
-            @NonNull NsdServiceInfo newInfo) {
-        return any(mAdvertiserRequests, (network, adv) ->
-                applicableAdvertiserFilter.test(network, adv) && adv.hasConflict(newInfo));
+            @NonNull NsdServiceInfo newInfo,
+            @NonNull Registration originalRegistration) {
+        return any(
+                mAdvertiserRequests,
+                (network, adv) ->
+                        applicableAdvertiserFilter.test(network, adv)
+                                && adv.hasServiceConflict(newInfo, originalRegistration));
+    }
+
+    private boolean hasAnyHostConflict(
+            @NonNull BiPredicate<Network, InterfaceAdvertiserRequest> applicableAdvertiserFilter,
+            @NonNull NsdServiceInfo newInfo,
+            int clientUid) {
+        // Check if it conflicts with custom hosts.
+        if (any(
+                mAdvertiserRequests,
+                (network, adv) ->
+                        applicableAdvertiserFilter.test(network, adv)
+                                && adv.hasHostConflict(newInfo, clientUid))) {
+            return true;
+        }
+        // Check if it conflicts with the default hostname.
+        return MdnsUtils.equalsIgnoreDnsCase(newInfo.getHostname(), mDeviceHostName[0]);
     }
 
     private void updateRegistrationUntilNoConflict(
             @NonNull BiPredicate<Network, InterfaceAdvertiserRequest> applicableAdvertiserFilter,
             @NonNull Registration registration) {
-        int renameCount = 0;
         NsdServiceInfo newInfo = registration.getServiceInfo();
-        while (hasAnyConflict(applicableAdvertiserFilter, newInfo)) {
-            renameCount++;
-            newInfo = registration.makeNewServiceInfoForConflict(renameCount);
+
+        int renameServiceCount = 0;
+        while (hasAnyServiceConflict(applicableAdvertiserFilter, newInfo, registration)) {
+            renameServiceCount++;
+            newInfo = registration.makeNewServiceInfoForServiceConflict(renameServiceCount);
         }
-        registration.updateForConflict(newInfo, renameCount);
+        registration.updateForServiceConflict(newInfo, renameServiceCount);
+
+        if (!TextUtils.isEmpty(registration.getServiceInfo().getHostname())) {
+            int renameHostCount = 0;
+            while (hasAnyHostConflict(
+                    applicableAdvertiserFilter, newInfo, registration.mClientUid)) {
+                renameHostCount++;
+                newInfo = registration.makeNewServiceInfoForHostConflict(renameHostCount);
+            }
+            registration.updateForHostConflict(newInfo, renameHostCount);
+        }
     }
 
     private void maybeSendOffloadStop(final String interfaceName, int serviceId) {
@@ -322,17 +377,34 @@
 
         /**
          * Return whether using the proposed new {@link NsdServiceInfo} to add a registration would
-         * cause a conflict in this {@link InterfaceAdvertiserRequest}.
+         * cause a conflict of the service in this {@link InterfaceAdvertiserRequest}.
          */
-        boolean hasConflict(@NonNull NsdServiceInfo newInfo) {
-            return getConflictingService(newInfo) >= 0;
+        boolean hasServiceConflict(
+                @NonNull NsdServiceInfo newInfo, @NonNull Registration originalRegistration) {
+            return getConflictingRegistrationDueToService(newInfo, originalRegistration) >= 0;
         }
 
         /**
-         * Get the ID of a conflicting service, or -1 if none.
+         * Return whether using the proposed new {@link NsdServiceInfo} to add a registration would
+         * cause a conflict of the host in this {@link InterfaceAdvertiserRequest}.
+         *
+         * @param clientUid UID of the user who wants to advertise the serviceInfo.
          */
-        int getConflictingService(@NonNull NsdServiceInfo info) {
+        boolean hasHostConflict(@NonNull NsdServiceInfo newInfo, int clientUid) {
+            return getConflictingRegistrationDueToHost(newInfo, clientUid) >= 0;
+        }
+
+        /** Get the ID of a conflicting registration due to service, or -1 if none. */
+        int getConflictingRegistrationDueToService(
+                @NonNull NsdServiceInfo info, @NonNull Registration originalRegistration) {
+            if (TextUtils.isEmpty(info.getServiceName())) {
+                return -1;
+            }
             for (int i = 0; i < mPendingRegistrations.size(); i++) {
+                // Never conflict with itself
+                if (mPendingRegistrations.valueAt(i) == originalRegistration) {
+                    continue;
+                }
                 final NsdServiceInfo other = mPendingRegistrations.valueAt(i).getServiceInfo();
                 if (MdnsUtils.equalsIgnoreDnsCase(info.getServiceName(), other.getServiceName())
                         && MdnsUtils.equalsIgnoreDnsCase(info.getServiceType(),
@@ -344,15 +416,41 @@
         }
 
         /**
+         * Get the ID of a conflicting registration due to host, or -1 if none.
+         *
+         * <p>It's valid that multiple registrations from the same user are using the same hostname.
+         *
+         * <p>If there's already another registration with the same hostname requested by another
+         * user, this is considered a conflict.
+         */
+        int getConflictingRegistrationDueToHost(@NonNull NsdServiceInfo info, int clientUid) {
+            if (TextUtils.isEmpty(info.getHostname())) {
+                return -1;
+            }
+            for (int i = 0; i < mPendingRegistrations.size(); i++) {
+                final Registration otherRegistration = mPendingRegistrations.valueAt(i);
+                final NsdServiceInfo otherInfo = otherRegistration.getServiceInfo();
+                if (clientUid != otherRegistration.mClientUid
+                        && MdnsUtils.equalsIgnoreDnsCase(
+                                info.getHostname(), otherInfo.getHostname())) {
+                    return mPendingRegistrations.keyAt(i);
+                }
+            }
+            return -1;
+        }
+
+        /**
          * Add a service to advertise.
          *
-         * Conflicts must be checked via {@link #getConflictingService} before attempting to add.
+         * <p>Conflicts must be checked via {@link #getConflictingRegistrationDueToService} and
+         * {@link #getConflictingRegistrationDueToHost} before attempting to add.
          */
         void addService(int id, @NonNull Registration registration) {
             mPendingRegistrations.put(id, registration);
             for (int i = 0; i < mAdvertisers.size(); i++) {
                 try {
-                    mAdvertisers.valueAt(i).addService(id, registration.getServiceInfo());
+                    mAdvertisers.valueAt(i).addService(id, registration.getServiceInfo(),
+                            registration.getAdvertisingOptions());
                 } catch (NameConflictException e) {
                     mSharedLog.wtf("Name conflict adding services that should have unique names",
                             e);
@@ -418,7 +516,7 @@
                 final Registration registration = mPendingRegistrations.valueAt(i);
                 try {
                     advertiser.addService(mPendingRegistrations.keyAt(i),
-                            registration.getServiceInfo());
+                            registration.getServiceInfo(), registration.getAdvertisingOptions());
                 } catch (NameConflictException e) {
                     mSharedLog.wtf("Name conflict adding services that should have unique names",
                             e);
@@ -480,27 +578,37 @@
     }
 
     private static class Registration {
-        @NonNull
-        final String mOriginalName;
+        @Nullable
+        final String mOriginalServiceName;
+        @Nullable
+        final String mOriginalHostname;
         boolean mNotifiedRegistrationSuccess;
-        private int mConflictCount;
+        private int mServiceNameConflictCount;
+        private int mHostnameConflictCount;
         @NonNull
         private NsdServiceInfo mServiceInfo;
+        final int mClientUid;
+        private final MdnsAdvertisingOptions mAdvertisingOptions;
         int mConflictDuringProbingCount;
         int mConflictAfterProbingCount;
 
-        private Registration(@NonNull NsdServiceInfo serviceInfo) {
-            this.mOriginalName = serviceInfo.getServiceName();
+        private Registration(@NonNull NsdServiceInfo serviceInfo, int clientUid,
+                @NonNull MdnsAdvertisingOptions advertisingOptions) {
+            this.mOriginalServiceName = serviceInfo.getServiceName();
+            this.mOriginalHostname = serviceInfo.getHostname();
             this.mServiceInfo = serviceInfo;
+            this.mClientUid = clientUid;
+            this.mAdvertisingOptions = advertisingOptions;
         }
 
-        /**
-         * Matches between the NsdServiceInfo in the Registration and the provided argument.
-         */
-        public boolean matches(@Nullable NsdServiceInfo newInfo) {
-            return Objects.equals(newInfo.getServiceName(), mOriginalName) && Objects.equals(
-                    newInfo.getServiceType(), mServiceInfo.getServiceType()) && Objects.equals(
-                    newInfo.getNetwork(), mServiceInfo.getNetwork());
+        /** Check if the new {@link NsdServiceInfo} doesn't update any data other than subtypes. */
+        public boolean isSubtypeOnlyUpdate(@NonNull NsdServiceInfo newInfo) {
+            return Objects.equals(newInfo.getServiceName(), mOriginalServiceName)
+                    && Objects.equals(newInfo.getServiceType(), mServiceInfo.getServiceType())
+                    && newInfo.getPort() == mServiceInfo.getPort()
+                    && Objects.equals(newInfo.getHostname(), mOriginalHostname)
+                    && Objects.equals(newInfo.getHostAddresses(), mServiceInfo.getHostAddresses())
+                    && Objects.equals(newInfo.getNetwork(), mServiceInfo.getNetwork());
         }
 
         /**
@@ -517,8 +625,19 @@
          * @param newInfo New service info to use.
          * @param renameCount How many renames were done before reaching the current name.
          */
-        private void updateForConflict(@NonNull NsdServiceInfo newInfo, int renameCount) {
-            mConflictCount += renameCount;
+        private void updateForServiceConflict(@NonNull NsdServiceInfo newInfo, int renameCount) {
+            mServiceNameConflictCount += renameCount;
+            mServiceInfo = newInfo;
+        }
+
+        /**
+         * Update the registration to use a different host name, after a conflict was found.
+         *
+         * @param newInfo New service info to use.
+         * @param renameCount How many renames were done before reaching the current name.
+         */
+        private void updateForHostConflict(@NonNull NsdServiceInfo newInfo, int renameCount) {
+            mHostnameConflictCount += renameCount;
             mServiceInfo = newInfo;
         }
 
@@ -534,7 +653,7 @@
          * @param renameCount How much to increase the number suffix for this conflict.
          */
         @NonNull
-        public NsdServiceInfo makeNewServiceInfoForConflict(int renameCount) {
+        public NsdServiceInfo makeNewServiceInfoForServiceConflict(int renameCount) {
             // In case of conflict choose a different service name. After the first conflict use
             // "Name (2)", then "Name (3)" etc.
             // TODO: use a hidden method in NsdServiceInfo once MdnsAdvertiser is moved to service-t
@@ -543,17 +662,49 @@
             return newInfo;
         }
 
+        /**
+         * Make a new hostname for the registration, after a conflict was found.
+         *
+         * <p>If a name conflict was found during probing or because different advertising requests
+         * used the same name, the registration is attempted again with a new name (here using a
+         * number suffix, -1, -2, etc). Registration success is notified once probing succeeds with
+         * a new name.
+         *
+         * @param renameCount How much to increase the number suffix for this conflict.
+         */
+        @NonNull
+        public NsdServiceInfo makeNewServiceInfoForHostConflict(int renameCount) {
+            // In case of conflict choose a different hostname. After the first conflict use
+            // "Name-2", then "Name-3" etc.
+            final NsdServiceInfo newInfo = new NsdServiceInfo(mServiceInfo);
+            newInfo.setHostname(getUpdatedHostname(renameCount));
+            return newInfo;
+        }
+
         private String getUpdatedServiceName(int renameCount) {
-            final String suffix = " (" + (mConflictCount + renameCount + 1) + ")";
-            final String truncatedServiceName = MdnsUtils.truncateServiceName(mOriginalName,
+            final String suffix = " (" + (mServiceNameConflictCount + renameCount + 1) + ")";
+            final String truncatedServiceName = MdnsUtils.truncateServiceName(mOriginalServiceName,
                     MAX_LABEL_LENGTH - suffix.length());
             return truncatedServiceName + suffix;
         }
 
+        private String getUpdatedHostname(int renameCount) {
+            final String suffix = "-" + (mHostnameConflictCount + renameCount + 1);
+            final String truncatedHostname =
+                    MdnsUtils.truncateServiceName(
+                            mOriginalHostname, MAX_LABEL_LENGTH - suffix.length());
+            return truncatedHostname + suffix;
+        }
+
         @NonNull
         public NsdServiceInfo getServiceInfo() {
             return mServiceInfo;
         }
+
+        @NonNull
+        public MdnsAdvertisingOptions getAdvertisingOptions() {
+            return mAdvertisingOptions;
+        }
     }
 
     /**
@@ -621,14 +772,16 @@
 
     public MdnsAdvertiser(@NonNull Looper looper, @NonNull MdnsSocketProvider socketProvider,
             @NonNull AdvertiserCallback cb, @NonNull SharedLog sharedLog,
-            @NonNull MdnsFeatureFlags mDnsFeatureFlags) {
-        this(looper, socketProvider, cb, new Dependencies(), sharedLog, mDnsFeatureFlags);
+            @NonNull MdnsFeatureFlags mDnsFeatureFlags, @NonNull Context context) {
+        this(looper, socketProvider, cb, new Dependencies(), sharedLog, mDnsFeatureFlags,
+                context);
     }
 
     @VisibleForTesting
     MdnsAdvertiser(@NonNull Looper looper, @NonNull MdnsSocketProvider socketProvider,
             @NonNull AdvertiserCallback cb, @NonNull Dependencies deps,
-            @NonNull SharedLog sharedLog, @NonNull MdnsFeatureFlags mDnsFeatureFlags) {
+            @NonNull SharedLog sharedLog, @NonNull MdnsFeatureFlags mDnsFeatureFlags,
+            @NonNull Context context) {
         mLooper = looper;
         mCb = cb;
         mSocketProvider = socketProvider;
@@ -636,6 +789,31 @@
         mDeviceHostName = deps.generateHostname();
         mSharedLog = sharedLog;
         mMdnsFeatureFlags = mDnsFeatureFlags;
+        final ConnectivityResources res = new ConnectivityResources(context);
+        mServiceTypeToOffloadPriority = parseOffloadPriorityList(
+                res.get().getStringArray(R.array.config_nsdOffloadServicesPriority), sharedLog);
+    }
+
+    private static Map<String, Integer> parseOffloadPriorityList(
+            @NonNull String[] resValues, SharedLog sharedLog) {
+        final Map<String, Integer> priorities = new ArrayMap<>(resValues.length);
+        for (String entry : resValues) {
+            final String[] priorityAndType = entry.split(":", 2);
+            if (priorityAndType.length != 2) {
+                sharedLog.wtf("Invalid config_nsdOffloadServicesPriority ignored: " + entry);
+                continue;
+            }
+
+            final int priority;
+            try {
+                priority = Integer.parseInt(priorityAndType[0]);
+            } catch (NumberFormatException e) {
+                sharedLog.wtf("Invalid priority in config_nsdOffloadServicesPriority: " + entry);
+                continue;
+            }
+            priorities.put(MdnsUtils.toDnsLowerCase(priorityAndType[1]), priority);
+        }
+        return priorities;
     }
 
     private void checkThread() {
@@ -650,9 +828,10 @@
      * @param id A unique ID for the service.
      * @param service The service info to advertise.
      * @param advertisingOptions The advertising options.
+     * @param clientUid The UID who wants to advertise the service.
      */
     public void addOrUpdateService(int id, NsdServiceInfo service,
-            MdnsAdvertisingOptions advertisingOptions) {
+            MdnsAdvertisingOptions advertisingOptions, int clientUid) {
         checkThread();
         final Registration existingRegistration = mRegistrations.get(id);
         final Network network = service.getNetwork();
@@ -664,7 +843,7 @@
                 mCb.onRegisterServiceFailed(id, NsdManager.FAILURE_INTERNAL_ERROR);
                 return;
             }
-            if (!(existingRegistration.matches(service))) {
+            if (!(existingRegistration.isSubtypeOnlyUpdate(service))) {
                 mSharedLog.e("Update request can only update subType, serviceInfo: " + service
                         + ", existing serviceInfo: " + existingRegistration.getServiceInfo());
                 mCb.onRegisterServiceFailed(id, NsdManager.FAILURE_INTERNAL_ERROR);
@@ -684,7 +863,7 @@
             }
             mSharedLog.i("Adding service " + service + " with ID " + id + " and subtypes "
                     + subtypes + " advertisingOptions " + advertisingOptions);
-            registration = new Registration(service);
+            registration = new Registration(service, clientUid, advertisingOptions);
             final BiPredicate<Network, InterfaceAdvertiserRequest> checkConflictFilter;
             if (network == null) {
                 // If registering on all networks, no advertiser must have conflicts
@@ -777,16 +956,17 @@
     private OffloadServiceInfoWrapper createOffloadService(int serviceId,
             @NonNull Registration registration, byte[] rawOffloadPacket) {
         final NsdServiceInfo nsdServiceInfo = registration.getServiceInfo();
+        final Integer mapPriority = mServiceTypeToOffloadPriority.get(
+                MdnsUtils.toDnsLowerCase(nsdServiceInfo.getServiceType()));
+        // Higher values of priority are less prioritized
+        final int priority = mapPriority == null ? Integer.MAX_VALUE : mapPriority;
         final OffloadServiceInfo offloadServiceInfo = new OffloadServiceInfo(
                 new OffloadServiceInfo.Key(nsdServiceInfo.getServiceName(),
                         nsdServiceInfo.getServiceType()),
                 new ArrayList<>(nsdServiceInfo.getSubtypes()),
                 String.join(".", mDeviceHostName),
                 rawOffloadPacket,
-                // TODO: define overlayable resources in
-                // ServiceConnectivityResources that set the priority based on
-                // service type.
-                0 /* priority */,
+                priority,
                 // TODO: set the offloadType based on the callback timing.
                 OffloadEngine.OFFLOAD_TYPE_REPLY);
         return new OffloadServiceInfoWrapper(serviceId, offloadServiceInfo);
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsAdvertisingOptions.java b/service-t/src/com/android/server/connectivity/mdns/MdnsAdvertisingOptions.java
index e7a6ca7..a81d1e4 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsAdvertisingOptions.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsAdvertisingOptions.java
@@ -16,6 +16,11 @@
 
 package com.android.server.connectivity.mdns;
 
+import android.annotation.Nullable;
+
+import java.time.Duration;
+import java.util.Objects;
+
 /**
  * API configuration parameters for advertising the mDNS service.
  *
@@ -27,13 +32,15 @@
 
     private static MdnsAdvertisingOptions sDefaultOptions;
     private final boolean mIsOnlyUpdate;
+    @Nullable
+    private final Duration mTtl;
 
     /**
      * Parcelable constructs for a {@link MdnsAdvertisingOptions}.
      */
-    MdnsAdvertisingOptions(
-            boolean isOnlyUpdate) {
+    MdnsAdvertisingOptions(boolean isOnlyUpdate, @Nullable Duration ttl) {
         this.mIsOnlyUpdate = isOnlyUpdate;
+        this.mTtl = ttl;
     }
 
     /**
@@ -60,9 +67,36 @@
         return mIsOnlyUpdate;
     }
 
+    /**
+     * Returns the TTL for all records in a service.
+     */
+    @Nullable
+    public Duration getTtl() {
+        return mTtl;
+    }
+
+    @Override
+    public boolean equals(Object other) {
+        if (this == other) {
+            return true;
+        } else if (!(other instanceof MdnsAdvertisingOptions)) {
+            return false;
+        } else {
+            final MdnsAdvertisingOptions otherOptions = (MdnsAdvertisingOptions) other;
+            return mIsOnlyUpdate == otherOptions.mIsOnlyUpdate
+                    && Objects.equals(mTtl, otherOptions.mTtl);
+        }
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mIsOnlyUpdate, mTtl);
+    }
+
     @Override
     public String toString() {
-        return "MdnsAdvertisingOptions{" + "mIsOnlyUpdate=" + mIsOnlyUpdate + '}';
+        return "MdnsAdvertisingOptions{" + "mIsOnlyUpdate=" + mIsOnlyUpdate + ", mTtl=" + mTtl
+                + '}';
     }
 
     /**
@@ -70,6 +104,8 @@
      */
     public static final class Builder {
         private boolean mIsOnlyUpdate = false;
+        @Nullable
+        private Duration mTtl;
 
         private Builder() {
         }
@@ -83,10 +119,18 @@
         }
 
         /**
+         * Sets the TTL duration for all records of the service.
+         */
+        public Builder setTtl(@Nullable Duration ttl) {
+            this.mTtl = ttl;
+            return this;
+        }
+
+        /**
          * Builds a {@link MdnsAdvertisingOptions} with the arguments supplied to this builder.
          */
         public MdnsAdvertisingOptions build() {
-            return new MdnsAdvertisingOptions(mIsOnlyUpdate);
+            return new MdnsAdvertisingOptions(mIsOnlyUpdate, mTtl);
         }
     }
 }
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsDiscoveryManager.java b/service-t/src/com/android/server/connectivity/mdns/MdnsDiscoveryManager.java
index 766f999..21b7069 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsDiscoveryManager.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsDiscoveryManager.java
@@ -16,26 +16,29 @@
 
 package com.android.server.connectivity.mdns;
 
-import static com.android.server.connectivity.mdns.util.MdnsUtils.ensureRunningOnHandlerThread;
-
 import android.Manifest.permission;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
 import android.os.Handler;
 import android.os.HandlerThread;
+import android.os.Looper;
 import android.util.ArrayMap;
 import android.util.Log;
 import android.util.Pair;
 
+import androidx.annotation.GuardedBy;
+
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.net.module.util.SharedLog;
 import com.android.server.connectivity.mdns.util.MdnsUtils;
 
 import java.io.IOException;
+import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Objects;
+import java.util.concurrent.Executor;
 
 /**
  * This class keeps tracking the set of registered {@link MdnsServiceBrowserListener} instances, and
@@ -50,11 +53,13 @@
     @NonNull private final SharedLog sharedLog;
 
     @NonNull private final PerSocketServiceTypeClients perSocketServiceTypeClients;
-    @NonNull private final Handler handler;
-    @Nullable private final HandlerThread handlerThread;
-    @NonNull private final MdnsServiceCache serviceCache;
+    @NonNull private final DiscoveryExecutor discoveryExecutor;
     @NonNull private final MdnsFeatureFlags mdnsFeatureFlags;
 
+    // Only accessed on the handler thread, initialized before first use
+    @Nullable
+    private MdnsServiceCache serviceCache;
+
     private static class PerSocketServiceTypeClients {
         private final ArrayMap<Pair<String, SocketKey>, MdnsServiceTypeClient> clients =
                 new ArrayMap<>();
@@ -125,33 +130,82 @@
         this.sharedLog = sharedLog;
         this.perSocketServiceTypeClients = new PerSocketServiceTypeClients();
         this.mdnsFeatureFlags = mdnsFeatureFlags;
-        if (socketClient.getLooper() != null) {
-            this.handlerThread = null;
-            this.handler = new Handler(socketClient.getLooper());
-            this.serviceCache = new MdnsServiceCache(socketClient.getLooper(), mdnsFeatureFlags);
-        } else {
-            this.handlerThread = new HandlerThread(MdnsDiscoveryManager.class.getSimpleName());
-            this.handlerThread.start();
-            this.handler = new Handler(handlerThread.getLooper());
-            this.serviceCache = new MdnsServiceCache(handlerThread.getLooper(), mdnsFeatureFlags);
-        }
+        this.discoveryExecutor = new DiscoveryExecutor(socketClient.getLooper());
     }
 
-    private void checkAndRunOnHandlerThread(@NonNull Runnable function) {
-        if (this.handlerThread == null) {
-            function.run();
-        } else {
+    private static class DiscoveryExecutor implements Executor {
+        private final HandlerThread handlerThread;
+
+        @GuardedBy("pendingTasks")
+        @Nullable private Handler handler;
+        @GuardedBy("pendingTasks")
+        @NonNull private final ArrayList<Runnable> pendingTasks = new ArrayList<>();
+
+        DiscoveryExecutor(@Nullable Looper defaultLooper) {
+            if (defaultLooper != null) {
+                this.handlerThread = null;
+                synchronized (pendingTasks) {
+                    this.handler = new Handler(defaultLooper);
+                }
+            } else {
+                this.handlerThread = new HandlerThread(MdnsDiscoveryManager.class.getSimpleName()) {
+                    @Override
+                    protected void onLooperPrepared() {
+                        synchronized (pendingTasks) {
+                            handler = new Handler(getLooper());
+                            for (Runnable pendingTask : pendingTasks) {
+                                handler.post(pendingTask);
+                            }
+                            pendingTasks.clear();
+                        }
+                    }
+                };
+                this.handlerThread.start();
+            }
+        }
+
+        public void checkAndRunOnHandlerThread(@NonNull Runnable function) {
+            if (this.handlerThread == null) {
+                // Callers are expected to already be running on the handler when a defaultLooper
+                // was provided
+                function.run();
+            } else {
+                execute(function);
+            }
+        }
+
+        @Override
+        public void execute(Runnable function) {
+            final Handler handler;
+            synchronized (pendingTasks) {
+                if (this.handler == null) {
+                    pendingTasks.add(function);
+                    return;
+                } else {
+                    handler = this.handler;
+                }
+            }
             handler.post(function);
         }
+
+        void shutDown() {
+            if (this.handlerThread != null) {
+                this.handlerThread.quitSafely();
+            }
+        }
+
+        void ensureRunningOnHandlerThread() {
+            synchronized (pendingTasks) {
+                MdnsUtils.ensureRunningOnHandlerThread(handler);
+            }
+        }
     }
 
     /**
      * Do the cleanup of the MdnsDiscoveryManager
      */
     public void shutDown() {
-        if (this.handlerThread != null) {
-            this.handlerThread.quitSafely();
-        }
+        discoveryExecutor.shutDown();
     }
 
     /**
@@ -169,7 +223,7 @@
             @NonNull MdnsServiceBrowserListener listener,
             @NonNull MdnsSearchOptions searchOptions) {
         sharedLog.i("Registering listener for serviceType: " + serviceType);
-        checkAndRunOnHandlerThread(() ->
+        discoveryExecutor.checkAndRunOnHandlerThread(() ->
                 handleRegisterListener(serviceType, listener, searchOptions));
     }
 
@@ -191,7 +245,7 @@
                 new MdnsSocketClientBase.SocketCreationCallback() {
                     @Override
                     public void onSocketCreated(@NonNull SocketKey socketKey) {
-                        ensureRunningOnHandlerThread(handler);
+                        discoveryExecutor.ensureRunningOnHandlerThread();
                         // All listeners of the same service types shares the same
                         // MdnsServiceTypeClient.
                         MdnsServiceTypeClient serviceTypeClient =
@@ -206,7 +260,7 @@
 
                     @Override
                     public void onSocketDestroyed(@NonNull SocketKey socketKey) {
-                        ensureRunningOnHandlerThread(handler);
+                        discoveryExecutor.ensureRunningOnHandlerThread();
                         final MdnsServiceTypeClient serviceTypeClient =
                                 perSocketServiceTypeClients.get(serviceType, socketKey);
                         if (serviceTypeClient == null) return;
@@ -229,7 +283,8 @@
     public void unregisterListener(
             @NonNull String serviceType, @NonNull MdnsServiceBrowserListener listener) {
         sharedLog.i("Unregistering listener for serviceType:" + serviceType);
-        checkAndRunOnHandlerThread(() -> handleUnregisterListener(serviceType, listener));
+        discoveryExecutor.checkAndRunOnHandlerThread(() ->
+                handleUnregisterListener(serviceType, listener));
     }
 
     private void handleUnregisterListener(
@@ -260,7 +315,7 @@
 
     @Override
     public void onResponseReceived(@NonNull MdnsPacket packet, @NonNull SocketKey socketKey) {
-        checkAndRunOnHandlerThread(() ->
+        discoveryExecutor.checkAndRunOnHandlerThread(() ->
                 handleOnResponseReceived(packet, socketKey));
     }
 
@@ -282,7 +337,7 @@
     @Override
     public void onFailedToParseMdnsResponse(int receivedPacketNumber, int errorCode,
             @NonNull SocketKey socketKey) {
-        checkAndRunOnHandlerThread(() ->
+        discoveryExecutor.checkAndRunOnHandlerThread(() ->
                 handleOnFailedToParseMdnsResponse(receivedPacketNumber, errorCode, socketKey));
     }
 
@@ -296,12 +351,31 @@
     @VisibleForTesting
     MdnsServiceTypeClient createServiceTypeClient(@NonNull String serviceType,
             @NonNull SocketKey socketKey) {
+        discoveryExecutor.ensureRunningOnHandlerThread();
         sharedLog.log("createServiceTypeClient for type:" + serviceType + " " + socketKey);
         final String tag = serviceType + "-" + socketKey.getNetwork()
                 + "/" + socketKey.getInterfaceIndex();
+        final Looper looper = Looper.myLooper();
+        if (serviceCache == null) {
+            serviceCache = new MdnsServiceCache(looper, mdnsFeatureFlags);
+        }
         return new MdnsServiceTypeClient(
                 serviceType, socketClient,
                 executorProvider.newServiceTypeClientSchedulerExecutor(), socketKey,
-                sharedLog.forSubComponent(tag), handler.getLooper(), serviceCache);
+                sharedLog.forSubComponent(tag), looper, serviceCache);
+    }
+
+    /**
+     * Dump DiscoveryManager state.
+     */
+    public void dump(PrintWriter pw) {
+        discoveryExecutor.checkAndRunOnHandlerThread(() -> {
+            pw.println();
+            // Dump ServiceTypeClients
+            for (MdnsServiceTypeClient serviceTypeClient
+                    : perSocketServiceTypeClients.getAllMdnsServiceTypeClient()) {
+                serviceTypeClient.dump(pw);
+            }
+        });
     }
 }
\ No newline at end of file
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 9466162..56202fd 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsFeatureFlags.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsFeatureFlags.java
@@ -57,6 +57,11 @@
      */
     public static final String NSD_UNICAST_REPLY_ENABLED = "nsd_unicast_reply_enabled";
 
+    /**
+     * A feature flag to control whether the aggressive query mode should be enabled.
+     */
+    public static final String NSD_AGGRESSIVE_QUERY_MODE = "nsd_aggressive_query_mode";
+
     // Flag for offload feature
     public final boolean mIsMdnsOffloadFeatureEnabled;
 
@@ -75,6 +80,9 @@
     // Flag to enable replying unicast to queries requesting unicast replies
     public final boolean mIsUnicastReplyEnabled;
 
+    // Flag for aggressive query mode
+    public final boolean mIsAggressiveQueryModeEnabled;
+
     @Nullable
     private final FlagOverrideProvider mOverrideProvider;
 
@@ -103,6 +111,21 @@
     }
 
     /**
+     * Indicates whether {@link #NSD_AGGRESSIVE_QUERY_MODE} is enabled, including for testing.
+     */
+    public boolean isAggressiveQueryModeEnabled() {
+        return mIsAggressiveQueryModeEnabled || isForceEnabledForTest(NSD_AGGRESSIVE_QUERY_MODE);
+    }
+
+    /**
+     * Indicates whether {@link #NSD_KNOWN_ANSWER_SUPPRESSION} is enabled, including for testing.
+     */
+    public boolean isKnownAnswerSuppressionEnabled() {
+        return mIsKnownAnswerSuppressionEnabled
+                || isForceEnabledForTest(NSD_KNOWN_ANSWER_SUPPRESSION);
+    }
+
+    /**
      * The constructor for {@link MdnsFeatureFlags}.
      */
     public MdnsFeatureFlags(boolean isOffloadFeatureEnabled,
@@ -111,6 +134,7 @@
             boolean isLabelCountLimitEnabled,
             boolean isKnownAnswerSuppressionEnabled,
             boolean isUnicastReplyEnabled,
+            boolean isAggressiveQueryModeEnabled,
             @Nullable FlagOverrideProvider overrideProvider) {
         mIsMdnsOffloadFeatureEnabled = isOffloadFeatureEnabled;
         mIncludeInetAddressRecordsInProbing = includeInetAddressRecordsInProbing;
@@ -118,6 +142,7 @@
         mIsLabelCountLimitEnabled = isLabelCountLimitEnabled;
         mIsKnownAnswerSuppressionEnabled = isKnownAnswerSuppressionEnabled;
         mIsUnicastReplyEnabled = isUnicastReplyEnabled;
+        mIsAggressiveQueryModeEnabled = isAggressiveQueryModeEnabled;
         mOverrideProvider = overrideProvider;
     }
 
@@ -136,6 +161,7 @@
         private boolean mIsLabelCountLimitEnabled;
         private boolean mIsKnownAnswerSuppressionEnabled;
         private boolean mIsUnicastReplyEnabled;
+        private boolean mIsAggressiveQueryModeEnabled;
         private FlagOverrideProvider mOverrideProvider;
 
         /**
@@ -148,6 +174,7 @@
             mIsLabelCountLimitEnabled = true; // Default enabled.
             mIsKnownAnswerSuppressionEnabled = false;
             mIsUnicastReplyEnabled = true;
+            mIsAggressiveQueryModeEnabled = false;
             mOverrideProvider = null;
         }
 
@@ -224,6 +251,16 @@
         }
 
         /**
+         * Set whether the aggressive query mode is enabled.
+         *
+         * @see #NSD_AGGRESSIVE_QUERY_MODE
+         */
+        public Builder setIsAggressiveQueryModeEnabled(boolean isAggressiveQueryModeEnabled) {
+            mIsAggressiveQueryModeEnabled = isAggressiveQueryModeEnabled;
+            return this;
+        }
+
+        /**
          * Builds a {@link MdnsFeatureFlags} with the arguments supplied to this builder.
          */
         public MdnsFeatureFlags build() {
@@ -233,6 +270,7 @@
                     mIsLabelCountLimitEnabled,
                     mIsKnownAnswerSuppressionEnabled,
                     mIsUnicastReplyEnabled,
+                    mIsAggressiveQueryModeEnabled,
                     mOverrideProvider);
         }
     }
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsInterfaceAdvertiser.java b/service-t/src/com/android/server/connectivity/mdns/MdnsInterfaceAdvertiser.java
index 3a04dcd..c2363c0 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsInterfaceAdvertiser.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsInterfaceAdvertiser.java
@@ -22,10 +22,12 @@
 import android.annotation.Nullable;
 import android.annotation.RequiresApi;
 import android.net.LinkAddress;
+import android.net.nsd.NsdManager;
 import android.net.nsd.NsdServiceInfo;
 import android.os.Build;
 import android.os.Handler;
 import android.os.Looper;
+import android.util.ArraySet;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.net.module.util.HexDump;
@@ -37,6 +39,7 @@
 import java.io.IOException;
 import java.net.InetSocketAddress;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 
 /**
@@ -44,6 +47,9 @@
  */
 @RequiresApi(Build.VERSION_CODES.TIRAMISU)
 public class MdnsInterfaceAdvertiser implements MulticastPacketReader.PacketHandler {
+    public static final int CONFLICT_SERVICE = 1 << 0;
+    public static final int CONFLICT_HOST = 1 << 1;
+
     private static final boolean DBG = MdnsAdvertiser.DBG;
     @VisibleForTesting
     public static final long EXIT_ANNOUNCEMENT_DELAY_MS = 100L;
@@ -85,10 +91,15 @@
         /**
          * Called by the advertiser when a conflict was found, during or after probing.
          *
-         * If a conflict is found during probing, the {@link #renameServiceForConflict} must be
+         * <p>If a conflict is found during probing, the {@link #renameServiceForConflict} must be
          * called to restart probing and attempt registration with a different name.
+         *
+         * <p>{@code conflictType} is a bitmap telling which part of the service is conflicting. See
+         * {@link MdnsInterfaceAdvertiser#CONFLICT_SERVICE} and {@link
+         * MdnsInterfaceAdvertiser#CONFLICT_HOST}.
          */
-        void onServiceConflict(@NonNull MdnsInterfaceAdvertiser advertiser, int serviceId);
+        void onServiceConflict(
+                @NonNull MdnsInterfaceAdvertiser advertiser, int serviceId, int conflictType);
 
         /**
          * Called by the advertiser when it destroyed itself.
@@ -162,10 +173,11 @@
         @NonNull
         public MdnsReplySender makeReplySender(@NonNull String interfaceTag, @NonNull Looper looper,
                 @NonNull MdnsInterfaceSocket socket, @NonNull byte[] packetCreationBuffer,
-                @NonNull SharedLog sharedLog) {
+                @NonNull SharedLog sharedLog, @NonNull MdnsFeatureFlags mdnsFeatureFlags) {
             return new MdnsReplySender(looper, socket, packetCreationBuffer,
                     sharedLog.forSubComponent(
-                            MdnsReplySender.class.getSimpleName() + "/" + interfaceTag), DBG);
+                            MdnsReplySender.class.getSimpleName() + "/" + interfaceTag), DBG,
+                    mdnsFeatureFlags);
         }
 
         /** @see MdnsAnnouncer */
@@ -208,7 +220,7 @@
         mCb = cb;
         mCbHandler = new Handler(looper);
         mReplySender = deps.makeReplySender(sharedLog.getTag(), looper, socket,
-                packetCreationBuffer, sharedLog);
+                packetCreationBuffer, sharedLog, mdnsFeatureFlags);
         mPacketCreationBuffer = packetCreationBuffer;
         mAnnouncer = deps.makeMdnsAnnouncer(sharedLog.getTag(), looper, mReplySender,
                 mAnnouncingCallback, sharedLog);
@@ -246,8 +258,10 @@
      *
      * @throws NameConflictException There is already a service being advertised with that name.
      */
-    public void addService(int id, NsdServiceInfo service) throws NameConflictException {
-        final int replacedExitingService = mRecordRepository.addService(id, service);
+    public void addService(int id, NsdServiceInfo service,
+            @NonNull MdnsAdvertisingOptions advertisingOptions) throws NameConflictException {
+        final int replacedExitingService =
+                mRecordRepository.addService(id, service, advertisingOptions.getTtl());
         // Cancel announcements for the existing service. This only happens for exiting services
         // (so cancelling exiting announcements), as per RecordRepository.addService.
         if (replacedExitingService >= 0) {
@@ -335,6 +349,7 @@
         final MdnsProber.ProbingInfo probingInfo = mRecordRepository.setServiceProbing(serviceId);
         if (probingInfo == null) return false;
 
+        mAnnouncer.stop(serviceId);
         mProber.restartForConflict(probingInfo);
         return true;
     }
@@ -383,8 +398,16 @@
                     + packet.additionalRecords.size() + " additional from " + srcCopy);
         }
 
-        for (int conflictServiceId : mRecordRepository.getConflictingServices(packet)) {
-            mCbHandler.post(() -> mCb.onServiceConflict(this, conflictServiceId));
+        Map<Integer, Integer> conflictingServices =
+                mRecordRepository.getConflictingServices(packet);
+
+        for (Map.Entry<Integer, Integer> entry : conflictingServices.entrySet()) {
+            int serviceId = entry.getKey();
+            int conflictType = entry.getValue();
+            mCbHandler.post(
+                    () -> {
+                        mCb.onServiceConflict(this, serviceId, conflictType);
+                    });
         }
 
         // Even in case of conflict, add replies for other services. But in general conflicts would
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsMultinetworkSocketClient.java b/service-t/src/com/android/server/connectivity/mdns/MdnsMultinetworkSocketClient.java
index e7b0eaa..869ac9b 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsMultinetworkSocketClient.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsMultinetworkSocketClient.java
@@ -96,7 +96,8 @@
         @Override
         public void onInterfaceDestroyed(@NonNull SocketKey socketKey,
                 @NonNull MdnsInterfaceSocket socket) {
-            notifySocketDestroyed(socketKey);
+            mActiveSockets.remove(socketKey);
+            mSocketCreationCallback.onSocketDestroyed(socketKey);
             maybeCleanupPacketHandler(socketKey);
         }
 
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsPacketReader.java b/service-t/src/com/android/server/connectivity/mdns/MdnsPacketReader.java
index 4917188..32060a2 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsPacketReader.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsPacketReader.java
@@ -31,6 +31,11 @@
 
 /** Simple decoder for mDNS packets. */
 public class MdnsPacketReader {
+    // The total length in bytes should be less than 255 bytes anyway (including labels and label
+    // length) per RFC9267, so limit the number of labels to 128 (each label is 2 bytes with the
+    // length).
+    // https://www.rfc-editor.org/rfc/rfc9267.html#name-label-and-name-length-valid
+    private static final int LABEL_COUNT_LIMIT = 128;
     private final byte[] buf;
     private final int count;
     private final SparseArray<LabelEntry> labelDictionary;
@@ -140,6 +145,7 @@
     public String[] readLabels() throws IOException {
         List<String> result = new ArrayList<>(5);
         LabelEntry previousEntry = null;
+        int tracingHops = 0;
 
         while (getRemaining() > 0) {
             byte nextByte = peekByte();
@@ -164,6 +170,10 @@
                 // Follow the chain of labels starting at this pointer, adding all of them onto the
                 // result.
                 while (labelOffset != 0) {
+                    if (mMdnsFeatureFlags.mIsLabelCountLimitEnabled
+                            && tracingHops > LABEL_COUNT_LIMIT) {
+                        throw new IOException("Invalid MDNS response packet: Too many labels.");
+                    }
                     LabelEntry entry = labelDictionary.get(labelOffset);
                     if (entry == null) {
                         throw new IOException(
@@ -172,6 +182,7 @@
                     }
                     result.add(entry.label);
                     labelOffset = entry.nextOffset;
+                    tracingHops++;
                 }
                 break;
             } else {
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsRecordRepository.java b/service-t/src/com/android/server/connectivity/mdns/MdnsRecordRepository.java
index 585b097..ac64c3a 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsRecordRepository.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsRecordRepository.java
@@ -19,6 +19,8 @@
 import static com.android.server.connectivity.mdns.MdnsConstants.IPV4_SOCKET_ADDR;
 import static com.android.server.connectivity.mdns.MdnsConstants.IPV6_SOCKET_ADDR;
 import static com.android.server.connectivity.mdns.MdnsConstants.NO_PACKET;
+import static com.android.server.connectivity.mdns.MdnsInterfaceAdvertiser.CONFLICT_HOST;
+import static com.android.server.connectivity.mdns.MdnsInterfaceAdvertiser.CONFLICT_SERVICE;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -28,6 +30,8 @@
 import android.os.Build;
 import android.os.Looper;
 import android.os.SystemClock;
+import android.text.TextUtils;
+import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.SparseArray;
 
@@ -41,6 +45,7 @@
 import java.net.InetAddress;
 import java.net.InetSocketAddress;
 import java.net.NetworkInterface;
+import java.time.Duration;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
@@ -54,6 +59,8 @@
 import java.util.Set;
 import java.util.TreeMap;
 import java.util.concurrent.TimeUnit;
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
 
 /**
  * A repository of records advertised through {@link MdnsInterfaceAdvertiser}.
@@ -69,9 +76,9 @@
     // TTL for records with a host name as the resource record's name (e.g., A, AAAA, HINFO) or a
     // host name contained within the resource record's rdata (e.g., SRV, reverse mapping PTR
     // record)
-    private static final long NAME_RECORDS_TTL_MILLIS = TimeUnit.SECONDS.toMillis(120);
+    private static final long DEFAULT_NAME_RECORDS_TTL_MILLIS = TimeUnit.SECONDS.toMillis(120);
     // TTL for other records
-    private static final long NON_NAME_RECORDS_TTL_MILLIS = TimeUnit.MINUTES.toMillis(75);
+    private static final long DEFAULT_NON_NAME_RECORDS_TTL_MILLIS = TimeUnit.MINUTES.toMillis(75);
 
     // Top-level domain for link-local queries, as per RFC6762 3.
     private static final String LOCAL_TLD = "local";
@@ -158,11 +165,13 @@
         public final List<RecordInfo<?>> allRecords;
         @NonNull
         public final List<RecordInfo<MdnsPointerRecord>> ptrRecords;
-        @NonNull
+        @Nullable
         public final RecordInfo<MdnsServiceRecord> srvRecord;
-        @NonNull
+        @Nullable
         public final RecordInfo<MdnsTextRecord> txtRecord;
         @NonNull
+        public final List<RecordInfo<MdnsInetAddressRecord>> addressRecords;
+        @NonNull
         public final NsdServiceInfo serviceInfo;
 
         /**
@@ -185,6 +194,9 @@
          */
         private boolean isProbing;
 
+        @Nullable
+        private Duration ttl;
+
         /**
          * Create a ServiceRegistration with only update the subType.
          */
@@ -192,75 +204,122 @@
             NsdServiceInfo newServiceInfo = new NsdServiceInfo(serviceInfo);
             newServiceInfo.setSubtypes(newSubtypes);
             return new ServiceRegistration(srvRecord.record.getServiceHost(), newServiceInfo,
-                    repliedServiceCount, sentPacketCount, exiting, isProbing);
+                    repliedServiceCount, sentPacketCount, exiting, isProbing, ttl);
         }
 
         /**
          * Create a ServiceRegistration for dns-sd service registration (RFC6763).
          */
         ServiceRegistration(@NonNull String[] deviceHostname, @NonNull NsdServiceInfo serviceInfo,
-                int repliedServiceCount, int sentPacketCount, boolean exiting, boolean isProbing) {
+                int repliedServiceCount, int sentPacketCount, boolean exiting, boolean isProbing,
+                @Nullable Duration ttl) {
             this.serviceInfo = serviceInfo;
 
-            final String[] serviceType = splitServiceType(serviceInfo);
-            final String[] serviceName = splitFullyQualifiedName(serviceInfo, serviceType);
+            final long nonNameRecordsTtlMillis;
+            final long nameRecordsTtlMillis;
 
-            // Service PTR records
-            ptrRecords = new ArrayList<>(serviceInfo.getSubtypes().size() + 1);
-            ptrRecords.add(new RecordInfo<>(
-                    serviceInfo,
-                    new MdnsPointerRecord(
-                            serviceType,
-                            0L /* receiptTimeMillis */,
-                            false /* cacheFlush */,
-                            NON_NAME_RECORDS_TTL_MILLIS,
-                            serviceName),
-                    true /* sharedName */));
-            for (String subtype : serviceInfo.getSubtypes()) {
-                ptrRecords.add(new RecordInfo<>(
-                    serviceInfo,
-                    new MdnsPointerRecord(
-                            MdnsUtils.constructFullSubtype(serviceType, subtype),
-                            0L /* receiptTimeMillis */,
-                            false /* cacheFlush */,
-                            NON_NAME_RECORDS_TTL_MILLIS,
-                            serviceName),
-                    true /* sharedName */));
+            // When custom TTL is specified, all records of the service will use the custom TTL.
+            // This is typically useful for SRP (Service Registration Protocol:
+            // https://datatracker.ietf.org/doc/html/draft-ietf-dnssd-srp-24) Advertising Proxy
+            // where all records in a single SRP are required the same TTL.
+            if (ttl != null) {
+                nonNameRecordsTtlMillis = ttl.toMillis();
+                nameRecordsTtlMillis = ttl.toMillis();
+            } else {
+                nonNameRecordsTtlMillis = DEFAULT_NON_NAME_RECORDS_TTL_MILLIS;
+                nameRecordsTtlMillis = DEFAULT_NAME_RECORDS_TTL_MILLIS;
             }
 
-            srvRecord = new RecordInfo<>(
-                    serviceInfo,
-                    new MdnsServiceRecord(serviceName,
-                            0L /* receiptTimeMillis */,
-                            true /* cacheFlush */,
-                            NAME_RECORDS_TTL_MILLIS, 0 /* servicePriority */, 0 /* serviceWeight */,
-                            serviceInfo.getPort(),
-                            deviceHostname),
-                    false /* sharedName */);
-
-            txtRecord = new RecordInfo<>(
-                    serviceInfo,
-                    new MdnsTextRecord(serviceName,
-                            0L /* receiptTimeMillis */,
-                            true /* cacheFlush */, // Service name is verified unique after probing
-                            NON_NAME_RECORDS_TTL_MILLIS,
-                            attrsToTextEntries(serviceInfo.getAttributes())),
-                    false /* sharedName */);
-
+            final boolean hasService = !TextUtils.isEmpty(serviceInfo.getServiceType());
+            final boolean hasCustomHost = !TextUtils.isEmpty(serviceInfo.getHostname());
+            final String[] hostname =
+                    hasCustomHost
+                            ? new String[] {serviceInfo.getHostname(), LOCAL_TLD}
+                            : deviceHostname;
             final ArrayList<RecordInfo<?>> allRecords = new ArrayList<>(5);
-            allRecords.addAll(ptrRecords);
-            allRecords.add(srvRecord);
-            allRecords.add(txtRecord);
-            // Service type enumeration record (RFC6763 9.)
-            allRecords.add(new RecordInfo<>(
-                    serviceInfo,
-                    new MdnsPointerRecord(
-                            DNS_SD_SERVICE_TYPE,
-                            0L /* receiptTimeMillis */,
-                            false /* cacheFlush */,
-                            NON_NAME_RECORDS_TTL_MILLIS,
-                            serviceType),
-                    true /* sharedName */));
+
+            if (hasService) {
+                final String[] serviceType = splitServiceType(serviceInfo);
+                final String[] serviceName = splitFullyQualifiedName(serviceInfo, serviceType);
+                // Service PTR records
+                ptrRecords = new ArrayList<>(serviceInfo.getSubtypes().size() + 1);
+                ptrRecords.add(new RecordInfo<>(
+                        serviceInfo,
+                        new MdnsPointerRecord(
+                                serviceType,
+                                0L /* receiptTimeMillis */,
+                                false /* cacheFlush */,
+                                nonNameRecordsTtlMillis,
+                                serviceName),
+                        true /* sharedName */));
+                for (String subtype : serviceInfo.getSubtypes()) {
+                    ptrRecords.add(new RecordInfo<>(
+                            serviceInfo,
+                            new MdnsPointerRecord(
+                                    MdnsUtils.constructFullSubtype(serviceType, subtype),
+                                    0L /* receiptTimeMillis */,
+                                    false /* cacheFlush */,
+                                    nonNameRecordsTtlMillis,
+                                    serviceName),
+                            true /* sharedName */));
+                }
+
+                srvRecord = new RecordInfo<>(
+                        serviceInfo,
+                        new MdnsServiceRecord(serviceName,
+                                0L /* receiptTimeMillis */,
+                                true /* cacheFlush */,
+                                nameRecordsTtlMillis,
+                                0 /* servicePriority */, 0 /* serviceWeight */,
+                                serviceInfo.getPort(),
+                                hostname),
+                        false /* sharedName */);
+
+                txtRecord = new RecordInfo<>(
+                        serviceInfo,
+                        new MdnsTextRecord(serviceName,
+                                0L /* receiptTimeMillis */,
+                                // Service name is verified unique after probing
+                                true /* cacheFlush */,
+                                nonNameRecordsTtlMillis,
+                                attrsToTextEntries(serviceInfo.getAttributes())),
+                        false /* sharedName */);
+
+                allRecords.addAll(ptrRecords);
+                allRecords.add(srvRecord);
+                allRecords.add(txtRecord);
+                // Service type enumeration record (RFC6763 9.)
+                allRecords.add(new RecordInfo<>(
+                        serviceInfo,
+                        new MdnsPointerRecord(
+                                DNS_SD_SERVICE_TYPE,
+                                0L /* receiptTimeMillis */,
+                                false /* cacheFlush */,
+                                nonNameRecordsTtlMillis,
+                                serviceType),
+                        true /* sharedName */));
+            } else {
+                ptrRecords = Collections.emptyList();
+                srvRecord = null;
+                txtRecord = null;
+            }
+
+            if (hasCustomHost) {
+                addressRecords = new ArrayList<>(serviceInfo.getHostAddresses().size());
+                for (InetAddress address : serviceInfo.getHostAddresses()) {
+                    addressRecords.add(new RecordInfo<>(
+                                    serviceInfo,
+                                    new MdnsInetAddressRecord(hostname,
+                                            0L /* receiptTimeMillis */,
+                                            true /* cacheFlush */,
+                                            nameRecordsTtlMillis,
+                                            address),
+                                    false /* sharedName */));
+                }
+                allRecords.addAll(addressRecords);
+            } else {
+                addressRecords = Collections.emptyList();
+            }
 
             this.allRecords = Collections.unmodifiableList(allRecords);
             this.repliedServiceCount = repliedServiceCount;
@@ -276,9 +335,9 @@
          * @param serviceInfo Service to advertise
          */
         ServiceRegistration(@NonNull String[] deviceHostname, @NonNull NsdServiceInfo serviceInfo,
-                int repliedServiceCount, int sentPacketCount) {
+                int repliedServiceCount, int sentPacketCount, @Nullable Duration ttl) {
             this(deviceHostname, serviceInfo,repliedServiceCount, sentPacketCount,
-                    false /* exiting */, true /* isProbing */);
+                    false /* exiting */, true /* isProbing */, ttl);
         }
 
         void setProbing(boolean probing) {
@@ -300,7 +359,7 @@
                             revDnsAddr,
                             0L /* receiptTimeMillis */,
                             true /* cacheFlush */,
-                            NAME_RECORDS_TTL_MILLIS,
+                            DEFAULT_NAME_RECORDS_TTL_MILLIS,
                             mDeviceHostname),
                     false /* sharedName */));
 
@@ -310,7 +369,7 @@
                             mDeviceHostname,
                             0L /* receiptTimeMillis */,
                             true /* cacheFlush */,
-                            NAME_RECORDS_TTL_MILLIS,
+                            DEFAULT_NAME_RECORDS_TTL_MILLIS,
                             addr.getAddress()),
                     false /* sharedName */));
         }
@@ -339,17 +398,20 @@
      * This may remove/replace any existing service that used the name added but is exiting.
      * @param serviceId A unique service ID.
      * @param serviceInfo Service info to add.
+     * @param ttl the TTL duration for all records of {@code serviceInfo} or {@code null}
      * @return If the added service replaced another with a matching name (which was exiting), the
      *         ID of the replaced service.
      * @throws NameConflictException There is already a (non-exiting) service using the name.
      */
-    public int addService(int serviceId, NsdServiceInfo serviceInfo) throws NameConflictException {
+    public int addService(int serviceId, NsdServiceInfo serviceInfo, @Nullable Duration ttl)
+            throws NameConflictException {
         if (mServices.contains(serviceId)) {
             throw new IllegalArgumentException(
                     "Service ID must not be reused across registrations: " + serviceId);
         }
 
-        final int existing = getServiceByName(serviceInfo.getServiceName());
+        final int existing =
+                getServiceByNameAndType(serviceInfo.getServiceName(), serviceInfo.getServiceType());
         // It's OK to re-add a service that is exiting
         if (existing >= 0 && !mServices.get(existing).exiting) {
             throw new NameConflictException(existing);
@@ -357,7 +419,7 @@
 
         final ServiceRegistration registration = new ServiceRegistration(
                 mDeviceHostname, serviceInfo, NO_PACKET /* repliedServiceCount */,
-                NO_PACKET /* sentPacketCount */);
+                NO_PACKET /* sentPacketCount */, ttl);
         mServices.put(serviceId, registration);
 
         // Remove existing exiting service
@@ -366,34 +428,41 @@
     }
 
     /**
-     * @return The ID of the service identified by its name, or -1 if none.
+     * @return The ID of the service identified by its name and type, or -1 if none.
      */
-    private int getServiceByName(@NonNull String serviceName) {
+    private int getServiceByNameAndType(
+            @Nullable String serviceName, @Nullable String serviceType) {
+        if (TextUtils.isEmpty(serviceName) || TextUtils.isEmpty(serviceType)) {
+            return -1;
+        }
         for (int i = 0; i < mServices.size(); i++) {
-            final ServiceRegistration registration = mServices.valueAt(i);
-            if (MdnsUtils.equalsIgnoreDnsCase(serviceName,
-                    registration.serviceInfo.getServiceName())) {
+            final NsdServiceInfo info = mServices.valueAt(i).serviceInfo;
+            if (MdnsUtils.equalsIgnoreDnsCase(serviceName, info.getServiceName())
+                    && MdnsUtils.equalsIgnoreDnsCase(serviceType, info.getServiceType())) {
                 return mServices.keyAt(i);
             }
         }
         return -1;
     }
 
-    private MdnsProber.ProbingInfo makeProbingInfo(int serviceId,
-            @NonNull MdnsServiceRecord srvRecord,
-            @NonNull List<MdnsInetAddressRecord> inetAddressRecords) {
+    private MdnsProber.ProbingInfo makeProbingInfo(
+            int serviceId, ServiceRegistration registration) {
         final List<MdnsRecord> probingRecords = new ArrayList<>();
         // Probe with cacheFlush cleared; it is set when announcing, as it was verified unique:
         // RFC6762 10.2
-        probingRecords.add(new MdnsServiceRecord(srvRecord.getName(),
-                0L /* receiptTimeMillis */,
-                false /* cacheFlush */,
-                srvRecord.getTtl(),
-                srvRecord.getServicePriority(), srvRecord.getServiceWeight(),
-                srvRecord.getServicePort(),
-                srvRecord.getServiceHost()));
+        if (registration.srvRecord != null) {
+            MdnsServiceRecord srvRecord = registration.srvRecord.record;
+            probingRecords.add(new MdnsServiceRecord(srvRecord.getName(),
+                    0L /* receiptTimeMillis */,
+                    false /* cacheFlush */,
+                    srvRecord.getTtl(),
+                    srvRecord.getServicePriority(), srvRecord.getServiceWeight(),
+                    srvRecord.getServicePort(),
+                    srvRecord.getServiceHost()));
+        }
 
-        for (MdnsInetAddressRecord inetAddressRecord : inetAddressRecords) {
+        for (MdnsInetAddressRecord inetAddressRecord :
+                makeProbingInetAddressRecords(registration.serviceInfo)) {
             probingRecords.add(new MdnsInetAddressRecord(inetAddressRecord.getName(),
                     0L /* receiptTimeMillis */,
                     false /* cacheFlush */,
@@ -490,6 +559,16 @@
         return ret;
     }
 
+    private boolean isTruncatedKnownAnswerPacket(MdnsPacket packet) {
+        if (!mMdnsFeatureFlags.isKnownAnswerSuppressionEnabled()
+                // Should ignore the response packet.
+                || (packet.flags & MdnsConstants.FLAGS_RESPONSE) != 0) {
+            return false;
+        }
+        // Check the packet contains no questions and as many more Known-Answer records as will fit.
+        return packet.questions.size() == 0 && packet.answers.size() != 0;
+    }
+
     /**
      * Get the reply to send to an incoming packet.
      *
@@ -500,6 +579,9 @@
     public MdnsReplyInfo getReply(MdnsPacket packet, InetSocketAddress src) {
         final long now = SystemClock.elapsedRealtime();
 
+        // TODO: b/322142420 - Set<RecordInfo<?>> may contain duplicate records wrapped in different
+        // RecordInfo<?>s when custom host is enabled.
+
         // Use LinkedHashSet for preserving the insert order of the RRs, so that RRs of the same
         // service or host are grouped together (which is more developer-friendly).
         final Set<RecordInfo<?>> answerInfo = new LinkedHashSet<>();
@@ -510,8 +592,10 @@
         for (MdnsRecord question : packet.questions) {
             // Add answers from general records
             if (addReplyFromService(question, mGeneralRecords, null /* servicePtrRecord */,
-                    null /* serviceSrvRecord */, null /* serviceTxtRecord */, replyUnicastEnabled,
-                    now, answerInfo, additionalAnswerInfo, Collections.emptyList())) {
+                    null /* serviceSrvRecord */, null /* serviceTxtRecord */,
+                    null /* hostname */,
+                    replyUnicastEnabled, now, answerInfo, additionalAnswerInfo,
+                    Collections.emptyList())) {
                 replyUnicast &= question.isUnicastReplyRequested();
             }
 
@@ -520,7 +604,9 @@
                 final ServiceRegistration registration = mServices.valueAt(i);
                 if (registration.exiting || registration.isProbing) continue;
                 if (addReplyFromService(question, registration.allRecords, registration.ptrRecords,
-                        registration.srvRecord, registration.txtRecord, replyUnicastEnabled, now,
+                        registration.srvRecord, registration.txtRecord,
+                        registration.serviceInfo.getHostname(),
+                        replyUnicastEnabled, now,
                         answerInfo, additionalAnswerInfo, packet.answers)) {
                     replyUnicast &= question.isUnicastReplyRequested();
                     registration.repliedServiceCount++;
@@ -538,7 +624,12 @@
         final List<MdnsRecord> additionalAnswerRecords =
                 new ArrayList<>(additionalAnswerInfo.size());
         for (RecordInfo<?> info : additionalAnswerInfo) {
-            additionalAnswerRecords.add(info.record);
+            // Different RecordInfos may contain the same record.
+            // For example, when there are multiple services referring to the same custom host,
+            // there are multiple RecordInfos containing the same address record.
+            if (!additionalAnswerRecords.contains(info.record)) {
+                additionalAnswerRecords.add(info.record);
+            }
         }
 
         // RFC6762 6.1: negative responses
@@ -550,7 +641,20 @@
                 answerInfo.iterator(), additionalAnswerInfo.iterator());
 
         if (answerInfo.size() == 0 && additionalAnswerRecords.size() == 0) {
-            return null;
+            // RFC6762 7.2. Multipacket Known-Answer Suppression
+            // Sometimes a Multicast DNS querier will already have too many answers
+            // to fit in the Known-Answer Section of its query packets. In this
+            // case, it should issue a Multicast DNS query containing a question and
+            // as many Known-Answer records as will fit.  It MUST then set the TC
+            // (Truncated) bit in the header before sending the query.  It MUST
+            // immediately follow the packet with another query packet containing no
+            // questions and as many more Known-Answer records as will fit.  If
+            // there are still too many records remaining to fit in the packet, it
+            // again sets the TC bit and continues until all the Known-Answer
+            // records have been sent.
+            if (!isTruncatedKnownAnswerPacket(packet)) {
+                return null;
+            }
         }
 
         // Determine the send delay
@@ -595,10 +699,14 @@
             if (!replyUnicast) {
                 info.lastAdvertisedTimeMs = info.lastSentTimeMs;
             }
-            answerRecords.add(info.record);
+            // Different RecordInfos may the contain the same record
+            if (!answerRecords.contains(info.record)) {
+                answerRecords.add(info.record);
+            }
         }
 
-        return new MdnsReplyInfo(answerRecords, additionalAnswerRecords, delayMs, dest);
+        return new MdnsReplyInfo(answerRecords, additionalAnswerRecords, delayMs, dest, src,
+                new ArrayList<>(packet.answers));
     }
 
     private boolean isKnownAnswer(MdnsRecord answer, @NonNull List<MdnsRecord> knownAnswerRecords) {
@@ -618,6 +726,7 @@
             @Nullable List<RecordInfo<MdnsPointerRecord>> servicePtrRecords,
             @Nullable RecordInfo<MdnsServiceRecord> serviceSrvRecord,
             @Nullable RecordInfo<MdnsTextRecord> serviceTxtRecord,
+            @Nullable String hostname,
             boolean replyUnicastEnabled, long now, @NonNull Set<RecordInfo<?>> answerInfo,
             @NonNull Set<RecordInfo<?>> additionalAnswerInfo,
             @NonNull List<MdnsRecord> knownAnswerRecords) {
@@ -658,7 +767,7 @@
             // RR TTL as known by the Multicast DNS responder, the responder MUST
             // send an answer so as to update the querier's cache before the record
             // becomes in danger of expiration.
-            if (mMdnsFeatureFlags.mIsKnownAnswerSuppressionEnabled
+            if (mMdnsFeatureFlags.isKnownAnswerSuppressionEnabled()
                     && isKnownAnswer(info.record, knownAnswerRecords)) {
                 continue;
             }
@@ -689,7 +798,7 @@
                     true /* cacheFlush */,
                     // TODO: RFC6762 6.1: "In general, the TTL given for an NSEC record SHOULD
                     // be the same as the TTL that the record would have had, had it existed."
-                    NAME_RECORDS_TTL_MILLIS,
+                    DEFAULT_NAME_RECORDS_TTL_MILLIS,
                     question.getName(),
                     new int[] { question.getType() });
             additionalAnswerInfo.add(
@@ -711,11 +820,7 @@
 
         // RFC6763 12.1&.2: if including PTR or SRV record, include the address records it names
         if (hasDnsSdPtrRecordAnswer || hasDnsSdSrvRecordAnswer) {
-            for (RecordInfo<?> record : mGeneralRecords) {
-                if (record.record instanceof MdnsInetAddressRecord) {
-                    additionalAnswerInfo.add(record);
-                }
-            }
+            additionalAnswerInfo.addAll(getInetAddressRecordsForHostname(hostname));
         }
         return true;
     }
@@ -829,29 +934,46 @@
             MdnsProber.ProbingInfo probeSuccessInfo)
             throws IOException {
 
-        final ServiceRegistration registration = mServices.get(probeSuccessInfo.getServiceId());
-        if (registration == null) throw new IOException(
-                "Service is not registered: " + probeSuccessInfo.getServiceId());
+        int serviceId = probeSuccessInfo.getServiceId();
+        final ServiceRegistration registration = mServices.get(serviceId);
+        if (registration == null) {
+            throw new IOException("Service is not registered: " + serviceId);
+        }
         registration.setProbing(false);
 
-        final ArrayList<MdnsRecord> answers = new ArrayList<>();
+        final Set<MdnsRecord> answersSet = new LinkedHashSet<>();
         final ArrayList<MdnsRecord> additionalAnswers = new ArrayList<>();
 
-        // Interface address records in general records
-        for (RecordInfo<?> record : mGeneralRecords) {
-            answers.add(record.record);
+        // When using default host, add interface address records from general records
+        if (TextUtils.isEmpty(registration.serviceInfo.getHostname())) {
+            for (RecordInfo<?> record : mGeneralRecords) {
+                answersSet.add(record.record);
+            }
+        } else {
+            // TODO: b/321617573 - include PTR records for addresses
+            // The custom host may have more addresses in other registrations
+            forEachActiveServiceRegistrationWithHostname(
+                    registration.serviceInfo.getHostname(),
+                    (id, otherRegistration) -> {
+                        if (otherRegistration.isProbing) {
+                            return;
+                        }
+                        for (RecordInfo<?> addressRecordInfo : otherRegistration.addressRecords) {
+                            answersSet.add(addressRecordInfo.record);
+                        }
+                    });
         }
 
         // All service records
         for (RecordInfo<?> info : registration.allRecords) {
-            answers.add(info.record);
+            answersSet.add(info.record);
         }
 
         addNsecRecordsForUniqueNames(additionalAnswers,
                 mGeneralRecords.iterator(), registration.allRecords.iterator());
 
-        return new MdnsAnnouncer.AnnouncementInfo(probeSuccessInfo.getServiceId(),
-                answers, additionalAnswers);
+        return new MdnsAnnouncer.AnnouncementInfo(
+                probeSuccessInfo.getServiceId(), new ArrayList<>(answersSet), additionalAnswers);
     }
 
     /**
@@ -870,8 +992,13 @@
         for (RecordInfo<MdnsPointerRecord> ptrRecord : registration.ptrRecords) {
             answers.add(ptrRecord.record);
         }
-        answers.add(registration.srvRecord.record);
-        answers.add(registration.txtRecord.record);
+        if (registration.srvRecord != null) {
+            answers.add(registration.srvRecord.record);
+        }
+        if (registration.txtRecord != null) {
+            answers.add(registration.txtRecord.record);
+        }
+        // TODO: Support custom host. It currently only supports default host.
         for (RecordInfo<?> record : mGeneralRecords) {
             if (record.record instanceof MdnsInetAddressRecord) {
                 answers.add(record.record);
@@ -886,70 +1013,181 @@
                 Collections.emptyList() /* additionalRecords */);
     }
 
+    /** Check if the record is in any service registration */
+    private boolean hasInetAddressRecord(@NonNull MdnsInetAddressRecord record) {
+        for (int i = 0; i < mServices.size(); i++) {
+            final ServiceRegistration registration = mServices.valueAt(i);
+            if (registration.exiting) continue;
+
+            for (RecordInfo<MdnsInetAddressRecord> localRecord : registration.addressRecords) {
+                if (Objects.equals(localRecord.record, record)) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
     /**
      * Get the service IDs of services conflicting with a received packet.
+     *
+     * <p>It returns a Map of service ID => conflict type. Conflict type is a bitmap telling which
+     * part of the service is conflicting. See {@link MdnsInterfaceAdvertiser#CONFLICT_SERVICE} and
+     * {@link MdnsInterfaceAdvertiser#CONFLICT_HOST}.
      */
-    public Set<Integer> getConflictingServices(MdnsPacket packet) {
+    public Map<Integer, Integer> getConflictingServices(MdnsPacket packet) {
         // Avoid allocating a new set for each incoming packet: use an empty set by default.
-        Set<Integer> conflicting = Collections.emptySet();
+        Map<Integer, Integer> conflicting = Collections.emptyMap();
         for (MdnsRecord record : packet.answers) {
             for (int i = 0; i < mServices.size(); i++) {
                 final ServiceRegistration registration = mServices.valueAt(i);
                 if (registration.exiting) continue;
 
-                // Only look for conflicts in service name, as a different service name can be used
-                // if there is a conflict, but there is nothing actionable if any other conflict
-                // happens. In fact probing is only done for the service name in the SRV record.
-                // This means only SRV and TXT records need to be checked.
-                final RecordInfo<MdnsServiceRecord> srvRecord = registration.srvRecord;
-                if (!MdnsUtils.equalsDnsLabelIgnoreDnsCase(record.getName(),
-                        srvRecord.record.getName())) {
-                    continue;
+                int conflictType = 0;
+
+                if (conflictForService(record, registration)) {
+                    conflictType |= CONFLICT_SERVICE;
                 }
 
-                // As per RFC6762 9., it's fine if the "conflict" is an identical record with same
-                // data.
-                if (record instanceof MdnsServiceRecord) {
-                    final MdnsServiceRecord local = srvRecord.record;
-                    final MdnsServiceRecord other = (MdnsServiceRecord) record;
-                    // Note "equals" does not consider TTL or receipt time, as intended here
-                    if (Objects.equals(local, other)) {
-                        continue;
+                if (conflictForHost(record, registration)) {
+                    conflictType |= CONFLICT_HOST;
+                }
+
+                if (conflictType != 0) {
+                    if (conflicting.isEmpty()) {
+                        // Conflict was found: use a mutable set
+                        conflicting = new ArrayMap<>();
                     }
+                    final int serviceId = mServices.keyAt(i);
+                    conflicting.put(serviceId, conflictType);
                 }
-
-                if (record instanceof MdnsTextRecord) {
-                    final MdnsTextRecord local = registration.txtRecord.record;
-                    final MdnsTextRecord other = (MdnsTextRecord) record;
-                    if (Objects.equals(local, other)) {
-                        continue;
-                    }
-                }
-
-                if (conflicting.size() == 0) {
-                    // Conflict was found: use a mutable set
-                    conflicting = new ArraySet<>();
-                }
-                final int serviceId = mServices.keyAt(i);
-                conflicting.add(serviceId);
             }
         }
 
         return conflicting;
     }
 
-    private List<MdnsInetAddressRecord> makeProbingInetAddressRecords() {
-        final List<MdnsInetAddressRecord> records = new ArrayList<>();
-        if (mMdnsFeatureFlags.mIncludeInetAddressRecordsInProbing) {
-            for (RecordInfo<?> record : mGeneralRecords) {
-                if (record.record instanceof MdnsInetAddressRecord) {
-                    records.add((MdnsInetAddressRecord) record.record);
-                }
+
+    private static boolean conflictForService(
+            @NonNull MdnsRecord record, @NonNull ServiceRegistration registration) {
+        if (registration.srvRecord == null) {
+            return false;
+        }
+
+        final RecordInfo<MdnsServiceRecord> srvRecord = registration.srvRecord;
+        if (!MdnsUtils.equalsDnsLabelIgnoreDnsCase(record.getName(), srvRecord.record.getName())) {
+            return false;
+        }
+
+        // As per RFC6762 9., it's fine if the "conflict" is an identical record with same
+        // data.
+        if (record instanceof MdnsServiceRecord) {
+            final MdnsServiceRecord local = srvRecord.record;
+            final MdnsServiceRecord other = (MdnsServiceRecord) record;
+            // Note "equals" does not consider TTL or receipt time, as intended here
+            if (Objects.equals(local, other)) {
+                return false;
             }
         }
+
+        if (record instanceof MdnsTextRecord) {
+            final MdnsTextRecord local = registration.txtRecord.record;
+            final MdnsTextRecord other = (MdnsTextRecord) record;
+            if (Objects.equals(local, other)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    private boolean conflictForHost(
+            @NonNull MdnsRecord record, @NonNull ServiceRegistration registration) {
+        // Only custom hosts are checked. When using the default host, the hostname is derived from
+        // a UUID and it's supposed to be unique.
+        if (registration.serviceInfo.getHostname() == null) {
+            return false;
+        }
+
+        // The record's name cannot be registered by NsdManager so it's not a conflict.
+        if (record.getName().length != 2 || !record.getName()[1].equals(LOCAL_TLD)) {
+            return false;
+        }
+
+        // Different names. There won't be a conflict.
+        if (!MdnsUtils.equalsIgnoreDnsCase(
+                record.getName()[0], registration.serviceInfo.getHostname())) {
+            return false;
+        }
+
+        // If this registration has any address record and there's no identical record in the
+        // repository, it's a conflict. There will be no conflict if no registration has addresses
+        // for that hostname.
+        if (record instanceof MdnsInetAddressRecord) {
+            if (!registration.addressRecords.isEmpty()) {
+                return !hasInetAddressRecord((MdnsInetAddressRecord) record);
+            }
+        }
+
+        return false;
+    }
+
+    private List<RecordInfo<MdnsInetAddressRecord>> getInetAddressRecordsForHostname(
+            @Nullable String hostname) {
+        List<RecordInfo<MdnsInetAddressRecord>> records = new ArrayList<>();
+        if (TextUtils.isEmpty(hostname)) {
+            forEachAddressRecord(mGeneralRecords, records::add);
+        } else {
+            forEachActiveServiceRegistrationWithHostname(
+                    hostname,
+                    (id, service) -> {
+                        if (service.isProbing) return;
+                        records.addAll(service.addressRecords);
+                    });
+        }
         return records;
     }
 
+    private List<MdnsInetAddressRecord> makeProbingInetAddressRecords(
+            @NonNull NsdServiceInfo serviceInfo) {
+        final List<MdnsInetAddressRecord> records = new ArrayList<>();
+        if (TextUtils.isEmpty(serviceInfo.getHostname())) {
+            if (mMdnsFeatureFlags.mIncludeInetAddressRecordsInProbing) {
+                forEachAddressRecord(mGeneralRecords, r -> records.add(r.record));
+            }
+        } else {
+            forEachActiveServiceRegistrationWithHostname(
+                    serviceInfo.getHostname(),
+                    (id, service) -> {
+                        for (RecordInfo<MdnsInetAddressRecord> recordInfo :
+                                service.addressRecords) {
+                            records.add(recordInfo.record);
+                        }
+                    });
+        }
+        return records;
+    }
+
+    private static void forEachAddressRecord(
+            List<RecordInfo<?>> records, Consumer<RecordInfo<MdnsInetAddressRecord>> consumer) {
+        for (RecordInfo<?> record : records) {
+            if (record.record instanceof MdnsInetAddressRecord) {
+                consumer.accept((RecordInfo<MdnsInetAddressRecord>) record);
+            }
+        }
+    }
+
+    private void forEachActiveServiceRegistrationWithHostname(
+            @NonNull String hostname, BiConsumer<Integer, ServiceRegistration> consumer) {
+        for (int i = 0; i < mServices.size(); ++i) {
+            int id = mServices.keyAt(i);
+            ServiceRegistration service = mServices.valueAt(i);
+            if (service.exiting) continue;
+            if (MdnsUtils.equalsIgnoreDnsCase(service.serviceInfo.getHostname(), hostname)) {
+                consumer.accept(id, service);
+            }
+        }
+    }
+
     /**
      * (Re)set a service to the probing state.
      * @return The {@link MdnsProber.ProbingInfo} to send for probing.
@@ -960,8 +1198,8 @@
         if (registration == null) return null;
 
         registration.setProbing(true);
-        return makeProbingInfo(
-                serviceId, registration.srvRecord.record, makeProbingInetAddressRecords());
+
+        return makeProbingInfo(serviceId, registration);
     }
 
     /**
@@ -995,10 +1233,9 @@
         if (existing == null) return null;
 
         final ServiceRegistration newService = new ServiceRegistration(mDeviceHostname, newInfo,
-                existing.repliedServiceCount, existing.sentPacketCount);
+                existing.repliedServiceCount, existing.sentPacketCount, existing.ttl);
         mServices.put(serviceId, newService);
-        return makeProbingInfo(
-                serviceId, newService.srvRecord.record, makeProbingInetAddressRecords());
+        return makeProbingInfo(serviceId, newService);
     }
 
     /**
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsReplyInfo.java b/service-t/src/com/android/server/connectivity/mdns/MdnsReplyInfo.java
index ce61b54..8747f67 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsReplyInfo.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsReplyInfo.java
@@ -32,22 +32,32 @@
     public final long sendDelayMs;
     @NonNull
     public final InetSocketAddress destination;
+    @NonNull
+    public final InetSocketAddress source;
+    @NonNull
+    public final List<MdnsRecord> knownAnswers;
 
     public MdnsReplyInfo(
             @NonNull List<MdnsRecord> answers,
             @NonNull List<MdnsRecord> additionalAnswers,
             long sendDelayMs,
-            @NonNull InetSocketAddress destination) {
+            @NonNull InetSocketAddress destination,
+            @NonNull InetSocketAddress source,
+            @NonNull List<MdnsRecord> knownAnswers) {
         this.answers = answers;
         this.additionalAnswers = additionalAnswers;
         this.sendDelayMs = sendDelayMs;
         this.destination = destination;
+        this.source = source;
+        this.knownAnswers = knownAnswers;
     }
 
     @Override
     public String toString() {
-        return "{MdnsReplyInfo to " + destination + ", answers: " + answers.size()
+        return "{MdnsReplyInfo: " + source + " to " + destination
+                + ", answers: " + answers.size()
                 + ", additionalAnswers: " + additionalAnswers.size()
+                + ", knownAnswers: " + knownAnswers.size()
                 + ", sendDelayMs " + sendDelayMs + "}";
     }
 }
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsReplySender.java b/service-t/src/com/android/server/connectivity/mdns/MdnsReplySender.java
index 651b643..db3845a 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsReplySender.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsReplySender.java
@@ -16,6 +16,8 @@
 
 package com.android.server.connectivity.mdns;
 
+import static com.android.server.connectivity.mdns.MdnsConstants.IPV4_SOCKET_ADDR;
+import static com.android.server.connectivity.mdns.MdnsConstants.IPV6_SOCKET_ADDR;
 import static com.android.server.connectivity.mdns.util.MdnsUtils.ensureRunningOnHandlerThread;
 
 import android.annotation.NonNull;
@@ -24,6 +26,8 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
+import android.util.ArrayMap;
+import android.util.ArraySet;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.net.module.util.SharedLog;
@@ -35,7 +39,10 @@
 import java.net.Inet6Address;
 import java.net.InetSocketAddress;
 import java.net.MulticastSocket;
+import java.util.ArrayList;
 import java.util.Collections;
+import java.util.Map;
+import java.util.Set;
 
 /**
  * A class that handles sending mDNS replies to a {@link MulticastSocket}, possibly queueing them
@@ -60,6 +67,12 @@
     private final boolean mEnableDebugLog;
     @NonNull
     private final Dependencies mDependencies;
+    // RFC6762 15.2. Multipacket Known-Answer lists
+    // Multicast DNS responders associate the initial truncated query with its
+    // continuation packets by examining the source IP address in each packet.
+    private final Map<InetSocketAddress, MdnsReplyInfo> mSrcReplies = new ArrayMap<>();
+    @NonNull
+    private final MdnsFeatureFlags mMdnsFeatureFlags;
 
     /**
      * Dependencies of MdnsReplySender, for injection in tests.
@@ -80,24 +93,50 @@
         public void removeMessages(@NonNull Handler handler, int what) {
             handler.removeMessages(what);
         }
+
+        /**
+         * @see Handler#removeMessages(int)
+         */
+        public void removeMessages(@NonNull Handler handler, int what, @NonNull Object object) {
+            handler.removeMessages(what, object);
+        }
     }
 
     public MdnsReplySender(@NonNull Looper looper, @NonNull MdnsInterfaceSocket socket,
             @NonNull byte[] packetCreationBuffer, @NonNull SharedLog sharedLog,
-            boolean enableDebugLog) {
-        this(looper, socket, packetCreationBuffer, sharedLog, enableDebugLog, new Dependencies());
+            boolean enableDebugLog, @NonNull MdnsFeatureFlags mdnsFeatureFlags) {
+        this(looper, socket, packetCreationBuffer, sharedLog, enableDebugLog, new Dependencies(),
+                mdnsFeatureFlags);
     }
 
     @VisibleForTesting
     public MdnsReplySender(@NonNull Looper looper, @NonNull MdnsInterfaceSocket socket,
             @NonNull byte[] packetCreationBuffer, @NonNull SharedLog sharedLog,
-            boolean enableDebugLog, @NonNull Dependencies dependencies) {
+            boolean enableDebugLog, @NonNull Dependencies dependencies,
+            @NonNull MdnsFeatureFlags mdnsFeatureFlags) {
         mHandler = new SendHandler(looper);
         mSocket = socket;
         mPacketCreationBuffer = packetCreationBuffer;
         mSharedLog = sharedLog;
         mEnableDebugLog = enableDebugLog;
         mDependencies = dependencies;
+        mMdnsFeatureFlags = mdnsFeatureFlags;
+    }
+
+    static InetSocketAddress getReplyDestination(@NonNull InetSocketAddress queuingDest,
+            @NonNull InetSocketAddress incomingDest) {
+        // The queuing reply is multicast, just use the current destination.
+        if (queuingDest.equals(IPV4_SOCKET_ADDR) || queuingDest.equals(IPV6_SOCKET_ADDR)) {
+            return queuingDest;
+        }
+
+        // The incoming reply is multicast, change the reply from unicast to multicast since
+        // replying unicast when the query requests unicast reply is optional.
+        if (incomingDest.equals(IPV4_SOCKET_ADDR) || incomingDest.equals(IPV6_SOCKET_ADDR)) {
+            return incomingDest;
+        }
+
+        return queuingDest;
     }
 
     /**
@@ -105,9 +144,53 @@
      */
     public void queueReply(@NonNull MdnsReplyInfo reply) {
         ensureRunningOnHandlerThread(mHandler);
-        // TODO: implement response aggregation (RFC 6762 6.4)
-        mDependencies.sendMessageDelayed(
-                mHandler, mHandler.obtainMessage(MSG_SEND, reply), reply.sendDelayMs);
+
+        if (mMdnsFeatureFlags.isKnownAnswerSuppressionEnabled()) {
+            mDependencies.removeMessages(mHandler, MSG_SEND, reply.source);
+
+            final MdnsReplyInfo queuingReply = mSrcReplies.remove(reply.source);
+            final ArraySet<MdnsRecord> answers = new ArraySet<>();
+            final Set<MdnsRecord> additionalAnswers = new ArraySet<>();
+            final Set<MdnsRecord> knownAnswers = new ArraySet<>();
+            if (queuingReply != null) {
+                answers.addAll(queuingReply.answers);
+                additionalAnswers.addAll(queuingReply.additionalAnswers);
+                knownAnswers.addAll(queuingReply.knownAnswers);
+            }
+            answers.addAll(reply.answers);
+            additionalAnswers.addAll(reply.additionalAnswers);
+            knownAnswers.addAll(reply.knownAnswers);
+            // RFC6762 7.2. Multipacket Known-Answer Suppression
+            // If the responder sees any of its answers listed in the Known-Answer
+            // lists of subsequent packets from the querying host, it MUST delete
+            // that answer from the list of answers it is planning to give.
+            for (MdnsRecord knownAnswer : knownAnswers) {
+                final int idx = answers.indexOf(knownAnswer);
+                if (idx >= 0 && knownAnswer.getTtl() > answers.valueAt(idx).getTtl() / 2) {
+                    answers.removeAt(idx);
+                }
+            }
+
+            if (answers.size() == 0) {
+                return;
+            }
+
+            final MdnsReplyInfo newReply = new MdnsReplyInfo(
+                    new ArrayList<>(answers),
+                    new ArrayList<>(additionalAnswers),
+                    reply.sendDelayMs,
+                    queuingReply == null ? reply.destination
+                            : getReplyDestination(queuingReply.destination, reply.destination),
+                    reply.source,
+                    new ArrayList<>(knownAnswers));
+
+            mSrcReplies.put(newReply.source, newReply);
+            mDependencies.sendMessageDelayed(mHandler,
+                    mHandler.obtainMessage(MSG_SEND, newReply.source), newReply.sendDelayMs);
+        } else {
+            mDependencies.sendMessageDelayed(
+                    mHandler, mHandler.obtainMessage(MSG_SEND, reply), reply.sendDelayMs);
+        }
 
         if (mEnableDebugLog) {
             mSharedLog.v("Scheduling " + reply);
@@ -147,7 +230,21 @@
 
         @Override
         public void handleMessage(@NonNull Message msg) {
-            final MdnsReplyInfo replyInfo = (MdnsReplyInfo) msg.obj;
+            final MdnsReplyInfo replyInfo;
+            if (mMdnsFeatureFlags.isKnownAnswerSuppressionEnabled()) {
+                // Retrieve the MdnsReplyInfo from the map via a source address, as the reply info
+                // will be combined or updated.
+                final InetSocketAddress source = (InetSocketAddress) msg.obj;
+                replyInfo = mSrcReplies.remove(source);
+            } else {
+                replyInfo = (MdnsReplyInfo) msg.obj;
+            }
+
+            if (replyInfo == null) {
+                mSharedLog.wtf("Unknown reply info.");
+                return;
+            }
+
             if (mEnableDebugLog) mSharedLog.v("Sending " + replyInfo);
 
             final int flags = 0x8400; // Response, authoritative (rfc6762 18.4)
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsSearchOptions.java b/service-t/src/com/android/server/connectivity/mdns/MdnsSearchOptions.java
index 63835d9..086094b 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsSearchOptions.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsSearchOptions.java
@@ -39,6 +39,15 @@
  * @hide
  */
 public class MdnsSearchOptions implements Parcelable {
+    // Passive query mode scans less frequently in order to conserve battery and produce less
+    // network traffic.
+    public static final int PASSIVE_QUERY_MODE = 0;
+    // Active query mode scans frequently.
+    public static final int ACTIVE_QUERY_MODE = 1;
+    // Aggressive query mode scans more frequently than the active mode at first, and sends both
+    // unicast and multicast queries simultaneously, but in long sessions it eventually sends as
+    // many queries as the PASSIVE mode.
+    public static final int AGGRESSIVE_QUERY_MODE = 2;
 
     /** @hide */
     public static final Parcelable.Creator<MdnsSearchOptions> CREATOR =
@@ -47,7 +56,7 @@
                 public MdnsSearchOptions createFromParcel(Parcel source) {
                     return new MdnsSearchOptions(
                             source.createStringArrayList(),
-                            source.readInt() == 1,
+                            source.readInt(),
                             source.readInt() == 1,
                             source.readParcelable(null),
                             source.readString(),
@@ -64,7 +73,7 @@
     private final List<String> subtypes;
     @Nullable
     private final String resolveInstanceName;
-    private final boolean isPassiveMode;
+    private final int queryMode;
     private final boolean onlyUseIpv6OnIpv6OnlyNetworks;
     private final int numOfQueriesBeforeBackoff;
     private final boolean removeExpiredService;
@@ -74,7 +83,7 @@
     /** Parcelable constructs for a {@link MdnsSearchOptions}. */
     MdnsSearchOptions(
             List<String> subtypes,
-            boolean isPassiveMode,
+            int queryMode,
             boolean removeExpiredService,
             @Nullable Network network,
             @Nullable String resolveInstanceName,
@@ -84,7 +93,7 @@
         if (subtypes != null) {
             this.subtypes.addAll(subtypes);
         }
-        this.isPassiveMode = isPassiveMode;
+        this.queryMode = queryMode;
         this.onlyUseIpv6OnIpv6OnlyNetworks = onlyUseIpv6OnIpv6OnlyNetworks;
         this.numOfQueriesBeforeBackoff = numOfQueriesBeforeBackoff;
         this.removeExpiredService = removeExpiredService;
@@ -111,11 +120,10 @@
     }
 
     /**
-     * @return {@code true} if the passive mode is used. The passive mode scans less frequently in
-     * order to conserve battery and produce less network traffic.
+     * @return the current query mode.
      */
-    public boolean isPassiveMode() {
-        return isPassiveMode;
+    public int getQueryMode() {
+        return queryMode;
     }
 
     /**
@@ -166,7 +174,7 @@
     @Override
     public void writeToParcel(Parcel out, int flags) {
         out.writeStringList(subtypes);
-        out.writeInt(isPassiveMode ? 1 : 0);
+        out.writeInt(queryMode);
         out.writeInt(removeExpiredService ? 1 : 0);
         out.writeParcelable(mNetwork, 0);
         out.writeString(resolveInstanceName);
@@ -177,7 +185,7 @@
     /** A builder to create {@link MdnsSearchOptions}. */
     public static final class Builder {
         private final Set<String> subtypes;
-        private boolean isPassiveMode = true;
+        private int queryMode = PASSIVE_QUERY_MODE;
         private boolean onlyUseIpv6OnIpv6OnlyNetworks = false;
         private int numOfQueriesBeforeBackoff = 3;
         private boolean removeExpiredService;
@@ -212,14 +220,12 @@
         }
 
         /**
-         * Sets if the passive mode scan should be used. The passive mode scans less frequently in
-         * order to conserve battery and produce less network traffic.
+         * Sets which query mode should be used.
          *
-         * @param isPassiveMode If set to {@code true}, passive mode will be used. If set to {@code
-         *                      false}, active mode will be used.
+         * @param queryMode the query mode should be used.
          */
-        public Builder setIsPassiveMode(boolean isPassiveMode) {
-            this.isPassiveMode = isPassiveMode;
+        public Builder setQueryMode(int queryMode) {
+            this.queryMode = queryMode;
             return this;
         }
 
@@ -276,7 +282,7 @@
         public MdnsSearchOptions build() {
             return new MdnsSearchOptions(
                     new ArrayList<>(subtypes),
-                    isPassiveMode,
+                    queryMode,
                     removeExpiredService,
                     mNetwork,
                     resolveInstanceName,
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsServiceInfo.java b/service-t/src/com/android/server/connectivity/mdns/MdnsServiceInfo.java
index 78df6df..f60a95e 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsServiceInfo.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsServiceInfo.java
@@ -28,6 +28,7 @@
 import com.android.net.module.util.ByteUtils;
 
 import java.nio.charset.Charset;
+import java.time.Instant;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
@@ -62,7 +63,8 @@
                             source.createStringArrayList(),
                             source.createTypedArrayList(TextEntry.CREATOR),
                             source.readInt(),
-                            source.readParcelable(null));
+                            source.readParcelable(Network.class.getClassLoader()),
+                            Instant.ofEpochSecond(source.readLong()));
                 }
 
                 @Override
@@ -89,6 +91,9 @@
     @Nullable
     private final Network network;
 
+    @NonNull
+    private final Instant expirationTime;
+
     /** Constructs a {@link MdnsServiceInfo} object with default values. */
     public MdnsServiceInfo(
             String serviceInstanceName,
@@ -110,7 +115,8 @@
                 textStrings,
                 /* textEntries= */ null,
                 /* interfaceIndex= */ INTERFACE_INDEX_UNSPECIFIED,
-                /* network= */ null);
+                /* network= */ null,
+                /* expirationTime= */ Instant.MAX);
     }
 
     /** Constructs a {@link MdnsServiceInfo} object with default values. */
@@ -135,7 +141,8 @@
                 textStrings,
                 textEntries,
                 /* interfaceIndex= */ INTERFACE_INDEX_UNSPECIFIED,
-                /* network= */ null);
+                /* network= */ null,
+                /* expirationTime= */ Instant.MAX);
     }
 
     /**
@@ -165,7 +172,8 @@
                 textStrings,
                 textEntries,
                 interfaceIndex,
-                /* network= */ null);
+                /* network= */ null,
+                /* expirationTime= */ Instant.MAX);
     }
 
     /**
@@ -184,7 +192,8 @@
             @Nullable List<String> textStrings,
             @Nullable List<TextEntry> textEntries,
             int interfaceIndex,
-            @Nullable Network network) {
+            @Nullable Network network,
+            @NonNull Instant expirationTime) {
         this.serviceInstanceName = serviceInstanceName;
         this.serviceType = serviceType;
         this.subtypes = new ArrayList<>();
@@ -217,6 +226,7 @@
         this.attributes = Collections.unmodifiableMap(attributes);
         this.interfaceIndex = interfaceIndex;
         this.network = network;
+        this.expirationTime = Instant.ofEpochSecond(expirationTime.getEpochSecond());
     }
 
     private static List<TextEntry> parseTextStrings(List<String> textStrings) {
@@ -314,6 +324,17 @@
     }
 
     /**
+     * Returns the timestamp after when this service is expired or {@code null} if the expiration
+     * time is unknown.
+     *
+     * A service is considered expired if any of its DNS record is expired.
+     */
+    @NonNull
+    public Instant getExpirationTime() {
+        return expirationTime;
+    }
+
+    /**
      * Returns attribute value for {@code key} as a UTF-8 string. It's the caller who must make sure
      * that the value of {@code key} is indeed a UTF-8 string. {@code null} will be returned if no
      * attribute value exists for {@code key}.
@@ -364,6 +385,7 @@
         out.writeTypedList(textEntries);
         out.writeInt(interfaceIndex);
         out.writeParcelable(network, 0);
+        out.writeLong(expirationTime.getEpochSecond());
     }
 
     @Override
@@ -377,7 +399,8 @@
                 + ", interfaceIndex: " + interfaceIndex
                 + ", network: " + network
                 + ", textStrings: " + textStrings
-                + ", textEntries: " + textEntries;
+                + ", textEntries: " + textEntries
+                + ", expirationTime: " + expirationTime;
     }
 
 
@@ -496,4 +519,4 @@
             out.writeByteArray(value);
         }
     }
-}
\ No newline at end of file
+}
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 df0a040..8f41b94 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsServiceTypeClient.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsServiceTypeClient.java
@@ -35,10 +35,13 @@
 import com.android.net.module.util.SharedLog;
 import com.android.server.connectivity.mdns.util.MdnsUtils;
 
+import java.io.PrintWriter;
 import java.net.Inet4Address;
 import java.net.Inet6Address;
+import java.time.Instant;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.Iterator;
 import java.util.List;
@@ -81,7 +84,7 @@
                     notifyRemovedServiceToListeners(previousResponse, "Service record expired");
                 }
             };
-    private final ArrayMap<MdnsServiceBrowserListener, MdnsSearchOptions> listeners =
+    private final ArrayMap<MdnsServiceBrowserListener, ListenerInfo> listeners =
             new ArrayMap<>();
     private final boolean removeServiceAfterTtlExpires =
             MdnsConfigs.removeServiceAfterTtlExpires();
@@ -95,6 +98,32 @@
     private long currentSessionId = 0;
     private long lastSentTime;
 
+    private static class ListenerInfo {
+        @NonNull
+        final MdnsSearchOptions searchOptions;
+        final Set<String> discoveredServiceNames;
+
+        ListenerInfo(@NonNull MdnsSearchOptions searchOptions,
+                @Nullable ListenerInfo previousInfo) {
+            this.searchOptions = searchOptions;
+            this.discoveredServiceNames = previousInfo == null
+                    ? MdnsUtils.newSet() : previousInfo.discoveredServiceNames;
+        }
+
+        /**
+         * Set the given service name as discovered.
+         *
+         * @return true if the service name was not discovered before.
+         */
+        boolean setServiceDiscovered(@NonNull String serviceName) {
+            return discoveredServiceNames.add(MdnsUtils.toDnsLowerCase(serviceName));
+        }
+
+        void unsetServiceDiscovered(@NonNull String serviceName) {
+            discoveredServiceNames.remove(MdnsUtils.toDnsLowerCase(serviceName));
+        }
+    }
+
     private class QueryTaskHandler extends Handler {
         QueryTaskHandler(Looper looper) {
             super(looper);
@@ -113,7 +142,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,
-                            servicesToResolve.size() < listeners.size() /* sendDiscoveryQueries */);
+                            getAllDiscoverySubtypes(), needSendDiscoveryQueries(listeners));
                     executor.submit(queryTask);
                     break;
                 }
@@ -281,6 +310,7 @@
             textStrings = response.getTextRecord().getStrings();
             textEntries = response.getTextRecord().getEntries();
         }
+        Instant now = Instant.now();
         // TODO: Throw an error message if response doesn't have Inet6 or Inet4 address.
         return new MdnsServiceInfo(
                 serviceInstanceName,
@@ -293,7 +323,8 @@
                 textStrings,
                 textEntries,
                 response.getInterfaceIndex(),
-                response.getNetwork());
+                response.getNetwork(),
+                now.plusMillis(response.getMinRemainingTtl(now.toEpochMilli())));
     }
 
     /**
@@ -311,12 +342,16 @@
         ensureRunningOnHandlerThread(handler);
         this.searchOptions = searchOptions;
         boolean hadReply = false;
-        if (listeners.put(listener, searchOptions) == null) {
+        final ListenerInfo existingInfo = listeners.get(listener);
+        final ListenerInfo listenerInfo = new ListenerInfo(searchOptions, existingInfo);
+        listeners.put(listener, listenerInfo);
+        if (existingInfo == null) {
             for (MdnsResponse existingResponse : serviceCache.getCachedServices(cacheKey)) {
                 if (!responseMatchesOptions(existingResponse, searchOptions)) continue;
                 final MdnsServiceInfo info =
                         buildMdnsServiceInfoFromResponse(existingResponse, serviceTypeLabels);
                 listener.onServiceNameDiscovered(info, true /* isServiceFromCache */);
+                listenerInfo.setServiceDiscovered(info.getServiceInstanceName());
                 if (existingResponse.isComplete()) {
                     listener.onServiceFound(info, true /* isServiceFromCache */);
                     hadReply = true;
@@ -329,8 +364,7 @@
         // Keep tracking the ScheduledFuture for the task so we can cancel it if caller is not
         // interested anymore.
         final QueryTaskConfig taskConfig = new QueryTaskConfig(
-                searchOptions.getSubtypes(),
-                searchOptions.isPassiveMode(),
+                searchOptions.getQueryMode(),
                 searchOptions.onlyUseIpv6OnIpv6OnlyNetworks(),
                 searchOptions.numOfQueriesBeforeBackoff(),
                 socketKey);
@@ -357,13 +391,22 @@
             final QueryTask queryTask = new QueryTask(
                     mdnsQueryScheduler.scheduleFirstRun(taskConfig, now,
                             minRemainingTtl, currentSessionId), servicesToResolve,
-                    servicesToResolve.size() < listeners.size() /* sendDiscoveryQueries */);
+                    getAllDiscoverySubtypes(), needSendDiscoveryQueries(listeners));
             executor.submit(queryTask);
         }
 
         serviceCache.registerServiceExpiredCallback(cacheKey, serviceExpiredCallback);
     }
 
+    private Set<String> getAllDiscoverySubtypes() {
+        final Set<String> subtypes = MdnsUtils.newSet();
+        for (int i = 0; i < listeners.size(); i++) {
+            final MdnsSearchOptions listenerOptions = listeners.valueAt(i).searchOptions;
+            subtypes.addAll(listenerOptions.getSubtypes());
+        }
+        return subtypes;
+    }
+
     /**
      * Get the executor service.
      */
@@ -480,9 +523,10 @@
     private void notifyRemovedServiceToListeners(@NonNull MdnsResponse response,
             @NonNull String message) {
         for (int i = 0; i < listeners.size(); i++) {
-            if (!responseMatchesOptions(response, listeners.valueAt(i))) continue;
+            if (!responseMatchesOptions(response, listeners.valueAt(i).searchOptions)) continue;
             final MdnsServiceBrowserListener listener = listeners.keyAt(i);
             if (response.getServiceInstanceName() != null) {
+                listeners.valueAt(i).unsetServiceDiscovered(response.getServiceInstanceName());
                 final MdnsServiceInfo serviceInfo = buildMdnsServiceInfoFromResponse(
                         response, serviceTypeLabels);
                 if (response.isComplete()) {
@@ -511,10 +555,9 @@
         final MdnsResponse currentResponse =
                 serviceCache.getCachedService(serviceInstanceName, cacheKey);
 
-        boolean newServiceFound = false;
+        final boolean newInCache = currentResponse == null;
         boolean serviceBecomesComplete = false;
-        if (currentResponse == null) {
-            newServiceFound = true;
+        if (newInCache) {
             if (serviceInstanceName != null) {
                 serviceCache.addOrUpdateService(cacheKey, response);
             }
@@ -525,25 +568,28 @@
             serviceBecomesComplete = !before && after;
         }
         sharedLog.i(String.format(
-                "Handling response from service: %s, newServiceFound: %b, serviceBecomesComplete:"
+                "Handling response from service: %s, newInCache: %b, serviceBecomesComplete:"
                         + " %b, responseIsComplete: %b",
-                serviceInstanceName, newServiceFound, serviceBecomesComplete,
+                serviceInstanceName, newInCache, serviceBecomesComplete,
                 response.isComplete()));
         MdnsServiceInfo serviceInfo =
                 buildMdnsServiceInfoFromResponse(response, serviceTypeLabels);
 
         for (int i = 0; i < listeners.size(); i++) {
-            if (!responseMatchesOptions(response, listeners.valueAt(i))) continue;
+            // If a service stops matching the options (currently can only happen if it loses a
+            // subtype), service lost callbacks should also be sent; this is not done today as
+            // only expiration of SRV records is used, not PTR records used for subtypes, so
+            // services never lose PTR record subtypes.
+            if (!responseMatchesOptions(response, listeners.valueAt(i).searchOptions)) continue;
             final MdnsServiceBrowserListener listener = listeners.keyAt(i);
+            final ListenerInfo listenerInfo = listeners.valueAt(i);
+            final boolean newServiceFound = listenerInfo.setServiceDiscovered(serviceInstanceName);
             if (newServiceFound) {
                 sharedLog.log("onServiceNameDiscovered: " + serviceInfo);
                 listener.onServiceNameDiscovered(serviceInfo, false /* isServiceFromCache */);
             }
 
             if (response.isComplete()) {
-                // There is a bug here: the newServiceFound is global right now. The state needs
-                // to be per listener because of the  responseMatchesOptions() filter.
-                // Otherwise, it won't handle the subType update properly.
                 if (newServiceFound || serviceBecomesComplete) {
                     sharedLog.log("onServiceFound: " + serviceInfo);
                     listener.onServiceFound(serviceInfo, false /* isServiceFromCache */);
@@ -579,10 +625,14 @@
     private List<MdnsResponse> makeResponsesForResolve(@NonNull SocketKey socketKey) {
         final List<MdnsResponse> resolveResponses = new ArrayList<>();
         for (int i = 0; i < listeners.size(); i++) {
-            final String resolveName = listeners.valueAt(i).getResolveInstanceName();
+            final String resolveName = listeners.valueAt(i).searchOptions.getResolveInstanceName();
             if (resolveName == null) {
                 continue;
             }
+            if (CollectionUtils.any(resolveResponses,
+                    r -> MdnsUtils.equalsIgnoreDnsCase(resolveName, r.getServiceInstanceName()))) {
+                continue;
+            }
             MdnsResponse knownResponse =
                     serviceCache.getCachedService(resolveName, cacheKey);
             if (knownResponse == null) {
@@ -599,6 +649,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;
 
@@ -631,11 +692,15 @@
     private class QueryTask implements Runnable {
         private final MdnsQueryScheduler.ScheduledQueryTaskArgs taskArgs;
         private final List<MdnsResponse> servicesToResolve = new ArrayList<>();
+        private final List<String> subtypes = new ArrayList<>();
         private final boolean sendDiscoveryQueries;
         QueryTask(@NonNull MdnsQueryScheduler.ScheduledQueryTaskArgs taskArgs,
-                @NonNull List<MdnsResponse> servicesToResolve, boolean sendDiscoveryQueries) {
+                @NonNull Collection<MdnsResponse> servicesToResolve,
+                @NonNull Collection<String> subtypes,
+                boolean sendDiscoveryQueries) {
             this.taskArgs = taskArgs;
             this.servicesToResolve.addAll(servicesToResolve);
+            this.subtypes.addAll(subtypes);
             this.sendDiscoveryQueries = sendDiscoveryQueries;
         }
 
@@ -648,7 +713,7 @@
                                 socketClient,
                                 createMdnsPacketWriter(),
                                 serviceType,
-                                taskArgs.config.subtypes,
+                                subtypes,
                                 taskArgs.config.expectUnicastResponse,
                                 taskArgs.config.transactionId,
                                 taskArgs.config.socketKey,
@@ -660,7 +725,7 @@
                                 .call();
             } catch (RuntimeException e) {
                 sharedLog.e(String.format("Failed to run EnqueueMdnsQueryCallable for subtype: %s",
-                        TextUtils.join(",", taskArgs.config.subtypes)), e);
+                        TextUtils.join(",", subtypes)), e);
                 result = Pair.create(INVALID_TRANSACTION_ID, new ArrayList<>());
             }
             dependencies.sendMessage(
@@ -695,4 +760,13 @@
                 args.sessionId, timeToNextTasksWithBackoffInMs));
         return timeToNextTasksWithBackoffInMs;
     }
+
+    /**
+     * Dump ServiceTypeClient state.
+     */
+    public void dump(PrintWriter pw) {
+        ensureRunningOnHandlerThread(handler);
+        pw.println("ServiceTypeClient: Type{" + serviceType + "} " + socketKey + " with "
+                + listeners.size() + " listeners.");
+    }
 }
\ No newline at end of file
diff --git a/service-t/src/com/android/server/connectivity/mdns/QueryTaskConfig.java b/service-t/src/com/android/server/connectivity/mdns/QueryTaskConfig.java
index 19282b0..0894166 100644
--- a/service-t/src/com/android/server/connectivity/mdns/QueryTaskConfig.java
+++ b/service-t/src/com/android/server/connectivity/mdns/QueryTaskConfig.java
@@ -16,15 +16,14 @@
 
 package com.android.server.connectivity.mdns;
 
+import static com.android.server.connectivity.mdns.MdnsSearchOptions.AGGRESSIVE_QUERY_MODE;
+import static com.android.server.connectivity.mdns.MdnsSearchOptions.PASSIVE_QUERY_MODE;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 
 import com.android.internal.annotations.VisibleForTesting;
 
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.List;
-
 /**
  * A configuration for the PeriodicalQueryTask that contains parameters to build a query packet.
  * Call to getConfigForNextRun returns a config that can be used to build the next query task.
@@ -33,19 +32,26 @@
 
     private static final int INITIAL_TIME_BETWEEN_BURSTS_MS =
             (int) MdnsConfigs.initialTimeBetweenBurstsMs();
-    private static final int TIME_BETWEEN_BURSTS_MS = (int) MdnsConfigs.timeBetweenBurstsMs();
+    private static final int MAX_TIME_BETWEEN_ACTIVE_PASSIVE_BURSTS_MS =
+            (int) MdnsConfigs.timeBetweenBurstsMs();
     private static final int QUERIES_PER_BURST = (int) MdnsConfigs.queriesPerBurst();
     private static final int TIME_BETWEEN_QUERIES_IN_BURST_MS =
             (int) MdnsConfigs.timeBetweenQueriesInBurstMs();
     private static final int QUERIES_PER_BURST_PASSIVE_MODE =
             (int) MdnsConfigs.queriesPerBurstPassive();
     private static final int UNSIGNED_SHORT_MAX_VALUE = 65536;
-    // The following fields are used by QueryTask so we need to test them.
     @VisibleForTesting
-    final List<String> subtypes;
+    // RFC 6762 5.2: The interval between the first two queries MUST be at least one second.
+    static final int INITIAL_AGGRESSIVE_TIME_BETWEEN_BURSTS_MS = 1000;
+    @VisibleForTesting
+    // Basically this tries to send one query per typical DTIM interval 100ms, to maximize the
+    // chances that a query will be received if devices are using a DTIM multiplier (in which case
+    // they only listen once every [multiplier] DTIM intervals).
+    static final int TIME_BETWEEN_RETRANSMISSION_QUERIES_IN_BURST_MS = 100;
+    static final int MAX_TIME_BETWEEN_AGGRESSIVE_BURSTS_MS = 60000;
     private final boolean alwaysAskForUnicastResponse =
             MdnsConfigs.alwaysAskForUnicastResponseInEachBurst();
-    private final boolean usePassiveMode;
+    private final int queryMode;
     final boolean onlyUseIpv6OnIpv6OnlyNetworks;
     private final int numOfQueriesBeforeBackoff;
     @VisibleForTesting
@@ -65,8 +71,7 @@
             boolean expectUnicastResponse, boolean isFirstBurst, int burstCounter,
             int queriesPerBurst, int timeBetweenBurstsInMs,
             long delayUntilNextTaskWithoutBackoffMs) {
-        this.subtypes = new ArrayList<>(other.subtypes);
-        this.usePassiveMode = other.usePassiveMode;
+        this.queryMode = other.queryMode;
         this.onlyUseIpv6OnIpv6OnlyNetworks = other.onlyUseIpv6OnIpv6OnlyNetworks;
         this.numOfQueriesBeforeBackoff = other.numOfQueriesBeforeBackoff;
         this.transactionId = transactionId;
@@ -79,36 +84,72 @@
         this.queryCount = queryCount;
         this.socketKey = other.socketKey;
     }
-    QueryTaskConfig(@NonNull Collection<String> subtypes,
-            boolean usePassiveMode,
+
+    QueryTaskConfig(int queryMode,
             boolean onlyUseIpv6OnIpv6OnlyNetworks,
             int numOfQueriesBeforeBackoff,
             @Nullable SocketKey socketKey) {
-        this.usePassiveMode = usePassiveMode;
+        this.queryMode = queryMode;
         this.onlyUseIpv6OnIpv6OnlyNetworks = onlyUseIpv6OnIpv6OnlyNetworks;
         this.numOfQueriesBeforeBackoff = numOfQueriesBeforeBackoff;
-        this.subtypes = new ArrayList<>(subtypes);
         this.queriesPerBurst = QUERIES_PER_BURST;
         this.burstCounter = 0;
         this.transactionId = 1;
         this.expectUnicastResponse = true;
         this.isFirstBurst = true;
         // Config the scan frequency based on the scan mode.
-        if (this.usePassiveMode) {
+        if (this.queryMode == AGGRESSIVE_QUERY_MODE) {
+            this.timeBetweenBurstsInMs = INITIAL_AGGRESSIVE_TIME_BETWEEN_BURSTS_MS;
+            this.delayUntilNextTaskWithoutBackoffMs =
+                    TIME_BETWEEN_RETRANSMISSION_QUERIES_IN_BURST_MS;
+        } else if (this.queryMode == PASSIVE_QUERY_MODE) {
             // In passive scan mode, sends a single burst of QUERIES_PER_BURST queries, and then
             // in each TIME_BETWEEN_BURSTS interval, sends QUERIES_PER_BURST_PASSIVE_MODE
             // queries.
-            this.timeBetweenBurstsInMs = TIME_BETWEEN_BURSTS_MS;
+            this.timeBetweenBurstsInMs = MAX_TIME_BETWEEN_ACTIVE_PASSIVE_BURSTS_MS;
+            this.delayUntilNextTaskWithoutBackoffMs = TIME_BETWEEN_QUERIES_IN_BURST_MS;
         } else {
             // In active scan mode, sends a burst of QUERIES_PER_BURST queries,
             // TIME_BETWEEN_QUERIES_IN_BURST_MS apart, then waits for the scan interval, and
             // then repeats. The scan interval starts as INITIAL_TIME_BETWEEN_BURSTS_MS and
             // doubles until it maxes out at TIME_BETWEEN_BURSTS_MS.
             this.timeBetweenBurstsInMs = INITIAL_TIME_BETWEEN_BURSTS_MS;
+            this.delayUntilNextTaskWithoutBackoffMs = TIME_BETWEEN_QUERIES_IN_BURST_MS;
         }
         this.socketKey = socketKey;
         this.queryCount = 0;
-        this.delayUntilNextTaskWithoutBackoffMs = TIME_BETWEEN_QUERIES_IN_BURST_MS;
+    }
+
+    long getDelayUntilNextTaskWithoutBackoff(boolean isFirstQueryInBurst,
+            boolean isLastQueryInBurst) {
+        if (isFirstQueryInBurst && queryMode == AGGRESSIVE_QUERY_MODE) {
+            return 0;
+        }
+        if (isLastQueryInBurst) {
+            return timeBetweenBurstsInMs;
+        }
+        return queryMode == AGGRESSIVE_QUERY_MODE
+                ? TIME_BETWEEN_RETRANSMISSION_QUERIES_IN_BURST_MS
+                : TIME_BETWEEN_QUERIES_IN_BURST_MS;
+    }
+
+    boolean getNextExpectUnicastResponse(boolean isLastQueryInBurst) {
+        if (!isLastQueryInBurst) {
+            return false;
+        }
+        if (queryMode == AGGRESSIVE_QUERY_MODE) {
+            return true;
+        }
+        return alwaysAskForUnicastResponse;
+    }
+
+    int getNextTimeBetweenBurstsMs(boolean isLastQueryInBurst) {
+        if (!isLastQueryInBurst) {
+            return timeBetweenBurstsInMs;
+        }
+        final int maxTimeBetweenBursts = queryMode == AGGRESSIVE_QUERY_MODE
+                ? MAX_TIME_BETWEEN_AGGRESSIVE_BURSTS_MS : MAX_TIME_BETWEEN_ACTIVE_PASSIVE_BURSTS_MS;
+        return Math.min(timeBetweenBurstsInMs * 2, maxTimeBetweenBursts);
     }
 
     /**
@@ -120,43 +161,26 @@
         if (newTransactionId > UNSIGNED_SHORT_MAX_VALUE) {
             newTransactionId = 1;
         }
-        boolean newExpectUnicastResponse = false;
-        boolean newIsFirstBurst = isFirstBurst;
+
         int newQueriesPerBurst = queriesPerBurst;
         int newBurstCounter = burstCounter + 1;
-        long newDelayUntilNextTaskWithoutBackoffMs = delayUntilNextTaskWithoutBackoffMs;
-        int newTimeBetweenBurstsInMs = timeBetweenBurstsInMs;
-        // Only the first query expects uni-cast response.
-        if (newBurstCounter == queriesPerBurst) {
+        final boolean isFirstQueryInBurst = newBurstCounter == 1;
+        final boolean isLastQueryInBurst = newBurstCounter == queriesPerBurst;
+        boolean newIsFirstBurst = isFirstBurst && !isLastQueryInBurst;
+        if (isLastQueryInBurst) {
             newBurstCounter = 0;
-
-            if (alwaysAskForUnicastResponse) {
-                newExpectUnicastResponse = true;
-            }
             // In passive scan mode, sends a single burst of QUERIES_PER_BURST queries, and
             // then in each TIME_BETWEEN_BURSTS interval, sends QUERIES_PER_BURST_PASSIVE_MODE
             // queries.
-            if (isFirstBurst) {
-                newIsFirstBurst = false;
-                if (usePassiveMode) {
-                    newQueriesPerBurst = QUERIES_PER_BURST_PASSIVE_MODE;
-                }
+            if (isFirstBurst && queryMode == PASSIVE_QUERY_MODE) {
+                newQueriesPerBurst = QUERIES_PER_BURST_PASSIVE_MODE;
             }
-            // In active scan mode, sends a burst of QUERIES_PER_BURST queries,
-            // TIME_BETWEEN_QUERIES_IN_BURST_MS apart, then waits for the scan interval, and
-            // then repeats. The scan interval starts as INITIAL_TIME_BETWEEN_BURSTS_MS and
-            // doubles until it maxes out at TIME_BETWEEN_BURSTS_MS.
-            newDelayUntilNextTaskWithoutBackoffMs = timeBetweenBurstsInMs;
-            if (timeBetweenBurstsInMs < TIME_BETWEEN_BURSTS_MS) {
-                newTimeBetweenBurstsInMs = Math.min(timeBetweenBurstsInMs * 2,
-                        TIME_BETWEEN_BURSTS_MS);
-            }
-        } else {
-            newDelayUntilNextTaskWithoutBackoffMs = TIME_BETWEEN_QUERIES_IN_BURST_MS;
         }
+
         return new QueryTaskConfig(this, newQueryCount, newTransactionId,
-                newExpectUnicastResponse, newIsFirstBurst, newBurstCounter, newQueriesPerBurst,
-                newTimeBetweenBurstsInMs, newDelayUntilNextTaskWithoutBackoffMs);
+                getNextExpectUnicastResponse(isLastQueryInBurst), newIsFirstBurst, newBurstCounter,
+                newQueriesPerBurst, getNextTimeBetweenBurstsMs(isLastQueryInBurst),
+                getDelayUntilNextTaskWithoutBackoff(isFirstQueryInBurst, isLastQueryInBurst));
     }
 
     /**
diff --git a/service-t/src/com/android/server/connectivity/mdns/util/MdnsUtils.java b/service-t/src/com/android/server/connectivity/mdns/util/MdnsUtils.java
index 8fc8114..d553210 100644
--- a/service-t/src/com/android/server/connectivity/mdns/util/MdnsUtils.java
+++ b/service-t/src/com/android/server/connectivity/mdns/util/MdnsUtils.java
@@ -86,7 +86,10 @@
     /**
      * Compare two strings by DNS case-insensitive lowercase.
      */
-    public static boolean equalsIgnoreDnsCase(@NonNull String a, @NonNull String b) {
+    public static boolean equalsIgnoreDnsCase(@Nullable String a, @Nullable String b) {
+        if (a == null || b == null) {
+            return a == null && b == null;
+        }
         if (a.length() != b.length()) return false;
         for (int i = 0; i < a.length(); i++) {
             if (toDnsLowerCase(a.charAt(i)) != toDnsLowerCase(b.charAt(i))) {
diff --git a/service-t/src/com/android/server/ethernet/EthernetServiceImpl.java b/service-t/src/com/android/server/ethernet/EthernetServiceImpl.java
index e7af569..b8689d6 100644
--- a/service-t/src/com/android/server/ethernet/EthernetServiceImpl.java
+++ b/service-t/src/com/android/server/ethernet/EthernetServiceImpl.java
@@ -19,6 +19,7 @@
 import static android.net.NetworkCapabilities.TRANSPORT_ETHERNET;
 import static android.net.NetworkCapabilities.TRANSPORT_TEST;
 
+import android.annotation.CheckResult;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
@@ -72,8 +73,9 @@
                 methodName + " is only available on automotive devices.");
     }
 
-    private boolean checkUseRestrictedNetworksPermission() {
-        return PermissionUtils.checkAnyPermissionOf(mContext,
+    @CheckResult
+    private boolean hasUseRestrictedNetworksPermission() {
+        return PermissionUtils.hasAnyPermissionOf(mContext,
                 android.Manifest.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS);
     }
 
@@ -92,7 +94,7 @@
     @Override
     public String[] getAvailableInterfaces() throws RemoteException {
         PermissionUtils.enforceAccessNetworkStatePermission(mContext, TAG);
-        return mTracker.getClientModeInterfaces(checkUseRestrictedNetworksPermission());
+        return mTracker.getClientModeInterfaces(hasUseRestrictedNetworksPermission());
     }
 
     /**
@@ -146,7 +148,7 @@
     public void addListener(IEthernetServiceListener listener) throws RemoteException {
         Objects.requireNonNull(listener, "listener must not be null");
         PermissionUtils.enforceAccessNetworkStatePermission(mContext, TAG);
-        mTracker.addListener(listener, checkUseRestrictedNetworksPermission());
+        mTracker.addListener(listener, hasUseRestrictedNetworksPermission());
     }
 
     /**
@@ -187,7 +189,7 @@
     @Override
     protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
         final IndentingPrintWriter pw = new IndentingPrintWriter(writer, "  ");
-        if (!PermissionUtils.checkDumpPermission(mContext, TAG, pw)) return;
+        if (!PermissionUtils.hasDumpPermission(mContext, TAG, pw)) return;
 
         pw.println("Current Ethernet state: ");
         pw.increaseIndent();
diff --git a/service-t/src/com/android/server/net/BpfInterfaceMapHelper.java b/service-t/src/com/android/server/net/BpfInterfaceMapHelper.java
new file mode 100644
index 0000000..3c95b8e
--- /dev/null
+++ b/service-t/src/com/android/server/net/BpfInterfaceMapHelper.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2022 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.net;
+
+import android.os.Build;
+import android.system.ErrnoException;
+import android.util.IndentingPrintWriter;
+import android.util.Log;
+
+import androidx.annotation.RequiresApi;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.net.module.util.BpfDump;
+import com.android.net.module.util.BpfMap;
+import com.android.net.module.util.IBpfMap;
+import com.android.net.module.util.Struct.S32;
+
+/**
+ * Monitor interface added (without removed) and right interface name and its index to bpf map.
+ */
+@RequiresApi(Build.VERSION_CODES.TIRAMISU)
+public class BpfInterfaceMapHelper {
+    private static final String TAG = BpfInterfaceMapHelper.class.getSimpleName();
+    // This is current path but may be changed soon.
+    private static final String IFACE_INDEX_NAME_MAP_PATH =
+            "/sys/fs/bpf/netd_shared/map_netd_iface_index_name_map";
+    private final IBpfMap<S32, InterfaceMapValue> mIndexToIfaceBpfMap;
+
+    public BpfInterfaceMapHelper() {
+        this(new Dependencies());
+    }
+
+    @VisibleForTesting
+    public BpfInterfaceMapHelper(Dependencies deps) {
+        mIndexToIfaceBpfMap = deps.getInterfaceMap();
+    }
+
+    /**
+     * Dependencies of BpfInerfaceMapUpdater, for injection in tests.
+     */
+    @VisibleForTesting
+    public static class Dependencies {
+        /** Create BpfMap for updating interface and index mapping. */
+        public IBpfMap<S32, InterfaceMapValue> getInterfaceMap() {
+            try {
+                return new BpfMap<>(IFACE_INDEX_NAME_MAP_PATH,
+                    S32.class, InterfaceMapValue.class);
+            } catch (ErrnoException e) {
+                Log.e(TAG, "Cannot create interface map: " + e);
+                return null;
+            }
+        }
+    }
+
+    /** get interface name by interface index from bpf map */
+    public String getIfNameByIndex(final int index) {
+        try {
+            final InterfaceMapValue value = mIndexToIfaceBpfMap.getValue(new S32(index));
+            if (value == null) {
+                Log.e(TAG, "No if name entry for index " + index);
+                return null;
+            }
+            return value.getInterfaceNameString();
+        } catch (ErrnoException e) {
+            Log.e(TAG, "Failed to get entry for index " + index + ": " + e);
+            return null;
+        }
+    }
+
+    /**
+     * Dump BPF map
+     *
+     * @param pw print writer
+     */
+    public void dump(final IndentingPrintWriter pw) {
+        pw.println("BPF map status:");
+        pw.increaseIndent();
+        BpfDump.dumpMapStatus(mIndexToIfaceBpfMap, pw, "IfaceIndexNameMap",
+                IFACE_INDEX_NAME_MAP_PATH);
+        pw.decreaseIndent();
+        pw.println("BPF map content:");
+        pw.increaseIndent();
+        BpfDump.dumpMap(mIndexToIfaceBpfMap, pw, "IfaceIndexNameMap",
+                (key, value) -> "ifaceIndex=" + key.val
+                        + " ifaceName=" + value.getInterfaceNameString());
+        pw.decreaseIndent();
+    }
+}
diff --git a/service-t/src/com/android/server/net/BpfInterfaceMapUpdater.java b/service-t/src/com/android/server/net/BpfInterfaceMapUpdater.java
deleted file mode 100644
index 27c0f9f..0000000
--- a/service-t/src/com/android/server/net/BpfInterfaceMapUpdater.java
+++ /dev/null
@@ -1,175 +0,0 @@
-/*
- * Copyright (C) 2022 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.net;
-
-import android.content.Context;
-import android.net.INetd;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.os.ServiceSpecificException;
-import android.system.ErrnoException;
-import android.util.IndentingPrintWriter;
-import android.util.Log;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.net.module.util.BaseNetdUnsolicitedEventListener;
-import com.android.net.module.util.BpfDump;
-import com.android.net.module.util.BpfMap;
-import com.android.net.module.util.IBpfMap;
-import com.android.net.module.util.InterfaceParams;
-import com.android.net.module.util.Struct.S32;
-
-/**
- * Monitor interface added (without removed) and right interface name and its index to bpf map.
- */
-public class BpfInterfaceMapUpdater {
-    private static final String TAG = BpfInterfaceMapUpdater.class.getSimpleName();
-    // This is current path but may be changed soon.
-    private static final String IFACE_INDEX_NAME_MAP_PATH =
-            "/sys/fs/bpf/netd_shared/map_netd_iface_index_name_map";
-    private final IBpfMap<S32, InterfaceMapValue> mIndexToIfaceBpfMap;
-    private final INetd mNetd;
-    private final Handler mHandler;
-    private final Dependencies mDeps;
-
-    public BpfInterfaceMapUpdater(Context ctx, Handler handler) {
-        this(ctx, handler, new Dependencies());
-    }
-
-    @VisibleForTesting
-    public BpfInterfaceMapUpdater(Context ctx, Handler handler, Dependencies deps) {
-        mDeps = deps;
-        mIndexToIfaceBpfMap = deps.getInterfaceMap();
-        mNetd = deps.getINetd(ctx);
-        mHandler = handler;
-    }
-
-    /**
-     * Dependencies of BpfInerfaceMapUpdater, for injection in tests.
-     */
-    @VisibleForTesting
-    public static class Dependencies {
-        /** Create BpfMap for updating interface and index mapping. */
-        public IBpfMap<S32, InterfaceMapValue> getInterfaceMap() {
-            try {
-                return new BpfMap<>(IFACE_INDEX_NAME_MAP_PATH, BpfMap.BPF_F_RDWR,
-                    S32.class, InterfaceMapValue.class);
-            } catch (ErrnoException e) {
-                Log.e(TAG, "Cannot create interface map: " + e);
-                return null;
-            }
-        }
-
-        /** Get InterfaceParams for giving interface name. */
-        public InterfaceParams getInterfaceParams(String ifaceName) {
-            return InterfaceParams.getByName(ifaceName);
-        }
-
-        /** Get INetd binder object. */
-        public INetd getINetd(Context ctx) {
-            return INetd.Stub.asInterface((IBinder) ctx.getSystemService(Context.NETD_SERVICE));
-        }
-    }
-
-    /**
-     * Start listening interface update event.
-     * Query current interface names before listening.
-     */
-    public void start() {
-        mHandler.post(() -> {
-            if (mIndexToIfaceBpfMap == null) {
-                Log.wtf(TAG, "Fail to start: Null bpf map");
-                return;
-            }
-
-            try {
-                // TODO: use a NetlinkMonitor and listen for RTM_NEWLINK messages instead.
-                mNetd.registerUnsolicitedEventListener(new InterfaceChangeObserver());
-            } catch (RemoteException e) {
-                Log.wtf(TAG, "Unable to register netd UnsolicitedEventListener, " + e);
-            }
-
-            final String[] ifaces;
-            try {
-                // TODO: use a netlink dump to get the current interface list.
-                ifaces = mNetd.interfaceGetList();
-            } catch (RemoteException | ServiceSpecificException e) {
-                Log.wtf(TAG, "Unable to query interface names by netd, " + e);
-                return;
-            }
-
-            for (String ifaceName : ifaces) {
-                addInterface(ifaceName);
-            }
-        });
-    }
-
-    private void addInterface(String ifaceName) {
-        final InterfaceParams iface = mDeps.getInterfaceParams(ifaceName);
-        if (iface == null) {
-            Log.e(TAG, "Unable to get InterfaceParams for " + ifaceName);
-            return;
-        }
-
-        try {
-            mIndexToIfaceBpfMap.updateEntry(new S32(iface.index), new InterfaceMapValue(ifaceName));
-        } catch (ErrnoException e) {
-            Log.e(TAG, "Unable to update entry for " + ifaceName + ", " + e);
-        }
-    }
-
-    private class InterfaceChangeObserver extends BaseNetdUnsolicitedEventListener {
-        @Override
-        public void onInterfaceAdded(String ifName) {
-            mHandler.post(() -> addInterface(ifName));
-        }
-    }
-
-    /** get interface name by interface index from bpf map */
-    public String getIfNameByIndex(final int index) {
-        try {
-            final InterfaceMapValue value = mIndexToIfaceBpfMap.getValue(new S32(index));
-            if (value == null) {
-                Log.e(TAG, "No if name entry for index " + index);
-                return null;
-            }
-            return value.getInterfaceNameString();
-        } catch (ErrnoException e) {
-            Log.e(TAG, "Failed to get entry for index " + index + ": " + e);
-            return null;
-        }
-    }
-
-    /**
-     * Dump BPF map
-     *
-     * @param pw print writer
-     */
-    public void dump(final IndentingPrintWriter pw) {
-        pw.println("BPF map status:");
-        pw.increaseIndent();
-        BpfDump.dumpMapStatus(mIndexToIfaceBpfMap, pw, "IfaceIndexNameMap",
-                IFACE_INDEX_NAME_MAP_PATH);
-        pw.decreaseIndent();
-        pw.println("BPF map content:");
-        pw.increaseIndent();
-        BpfDump.dumpMap(mIndexToIfaceBpfMap, pw, "IfaceIndexNameMap",
-                (key, value) -> "ifaceIndex=" + key.val
-                        + " ifaceName=" + value.getInterfaceNameString());
-        pw.decreaseIndent();
-    }
-}
diff --git a/service-t/src/com/android/server/net/NetworkStatsObservers.java b/service-t/src/com/android/server/net/NetworkStatsObservers.java
index 21cf351..cab29e3 100644
--- a/service-t/src/com/android/server/net/NetworkStatsObservers.java
+++ b/service-t/src/com/android/server/net/NetworkStatsObservers.java
@@ -32,7 +32,6 @@
 import android.net.NetworkTemplate;
 import android.net.netstats.IUsageCallback;
 import android.os.Handler;
-import android.os.HandlerThread;
 import android.os.IBinder;
 import android.os.Looper;
 import android.os.Message;
@@ -46,6 +45,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.net.module.util.PerUidCounter;
 
+import java.util.Objects;
 import java.util.concurrent.atomic.AtomicInteger;
 
 /**
@@ -78,8 +78,11 @@
     // Sequence number of DataUsageRequests
     private final AtomicInteger mNextDataUsageRequestId = new AtomicInteger();
 
-    // Lazily instantiated when an observer is registered.
-    private volatile Handler mHandler;
+    private final Handler mHandler;
+
+    NetworkStatsObservers(@NonNull Looper looper) {
+        mHandler = new Handler(Objects.requireNonNull(looper), mHandlerCallback);
+    }
 
     /**
      * Creates a wrapper that contains the caller context and a normalized request.
@@ -100,7 +103,7 @@
         if (LOG) Log.d(TAG, "Registering observer for " + requestInfo);
         mDataUsageRequestsPerUid.incrementCountOrThrow(callingUid);
 
-        getHandler().sendMessage(mHandler.obtainMessage(MSG_REGISTER, requestInfo));
+        mHandler.sendMessage(mHandler.obtainMessage(MSG_REGISTER, requestInfo));
         return request;
     }
 
@@ -110,7 +113,7 @@
      * <p>It will unregister the observer asynchronously, so it is safe to call from any thread.
      */
     public void unregister(DataUsageRequest request, int callingUid) {
-        getHandler().sendMessage(mHandler.obtainMessage(MSG_UNREGISTER, callingUid, 0 /* ignore */,
+        mHandler.sendMessage(mHandler.obtainMessage(MSG_UNREGISTER, callingUid, 0 /* ignore */,
                 request));
     }
 
@@ -125,34 +128,10 @@
                 long currentTime) {
         StatsContext statsContext = new StatsContext(xtSnapshot, uidSnapshot, activeIfaces,
                 activeUidIfaces, currentTime);
-        getHandler().sendMessage(mHandler.obtainMessage(MSG_UPDATE_STATS, statsContext));
+        mHandler.sendMessage(mHandler.obtainMessage(MSG_UPDATE_STATS, statsContext));
     }
 
-    private Handler getHandler() {
-        if (mHandler == null) {
-            synchronized (this) {
-                if (mHandler == null) {
-                    if (LOGV) Log.v(TAG, "Creating handler");
-                    mHandler = new Handler(getHandlerLooperLocked(), mHandlerCallback);
-                }
-            }
-        }
-        return mHandler;
-    }
-
-    @VisibleForTesting
-    protected Looper getHandlerLooperLocked() {
-        // TODO: Currently, callbacks are dispatched on this thread if the caller register
-        //  callback without supplying a Handler. To ensure that the service handler thread
-        //  is not blocked by client code, the observers must create their own thread. Once
-        //  all callbacks are dispatched outside of the handler thread, the service handler
-        //  thread can be used here.
-        HandlerThread handlerThread = new HandlerThread(TAG);
-        handlerThread.start();
-        return handlerThread.getLooper();
-    }
-
-    private Handler.Callback mHandlerCallback = new Handler.Callback() {
+    private final Handler.Callback mHandlerCallback = new Handler.Callback() {
         @Override
         public boolean handleMessage(Message msg) {
             switch (msg.what) {
diff --git a/service-t/src/com/android/server/net/NetworkStatsService.java b/service-t/src/com/android/server/net/NetworkStatsService.java
index a36149e..64b17eb 100644
--- a/service-t/src/com/android/server/net/NetworkStatsService.java
+++ b/service-t/src/com/android/server/net/NetworkStatsService.java
@@ -476,7 +476,7 @@
     private final LocationPermissionChecker mLocationPermissionChecker;
 
     @NonNull
-    private final BpfInterfaceMapUpdater mInterfaceMapUpdater;
+    private final BpfInterfaceMapHelper mInterfaceMapHelper;
 
     @Nullable
     private final SkDestroyListener mSkDestroyListener;
@@ -593,7 +593,7 @@
                 INetd.Stub.asInterface((IBinder) context.getSystemService(Context.NETD_SERVICE)),
                 alarmManager, wakeLock, getDefaultClock(),
                 new DefaultNetworkStatsSettings(), new NetworkStatsFactory(context),
-                new NetworkStatsObservers(), new Dependencies());
+                new Dependencies());
 
         return service;
     }
@@ -603,8 +603,7 @@
     @VisibleForTesting
     NetworkStatsService(Context context, INetd netd, AlarmManager alarmManager,
             PowerManager.WakeLock wakeLock, Clock clock, NetworkStatsSettings settings,
-            NetworkStatsFactory factory, NetworkStatsObservers statsObservers,
-            @NonNull Dependencies deps) {
+            NetworkStatsFactory factory, @NonNull Dependencies deps) {
         mContext = Objects.requireNonNull(context, "missing Context");
         mNetd = Objects.requireNonNull(netd, "missing Netd");
         mAlarmManager = Objects.requireNonNull(alarmManager, "missing AlarmManager");
@@ -612,7 +611,6 @@
         mSettings = Objects.requireNonNull(settings, "missing NetworkStatsSettings");
         mWakeLock = Objects.requireNonNull(wakeLock, "missing WakeLock");
         mStatsFactory = Objects.requireNonNull(factory, "missing factory");
-        mStatsObservers = Objects.requireNonNull(statsObservers, "missing NetworkStatsObservers");
         mDeps = Objects.requireNonNull(deps, "missing Dependencies");
         mStatsDir = mDeps.getOrCreateStatsDir();
         if (!mStatsDir.exists()) {
@@ -622,14 +620,14 @@
         final HandlerThread handlerThread = mDeps.makeHandlerThread();
         handlerThread.start();
         mHandler = new NetworkStatsHandler(handlerThread.getLooper());
+        mStatsObservers = new NetworkStatsObservers(handlerThread.getLooper());
         mNetworkStatsSubscriptionsMonitor = deps.makeSubscriptionsMonitor(mContext,
                 (command) -> mHandler.post(command) , this);
         mContentResolver = mContext.getContentResolver();
         mContentObserver = mDeps.makeContentObserver(mHandler, mSettings,
                 mNetworkStatsSubscriptionsMonitor);
         mLocationPermissionChecker = mDeps.makeLocationPermissionChecker(mContext);
-        mInterfaceMapUpdater = mDeps.makeBpfInterfaceMapUpdater(mContext, mHandler);
-        mInterfaceMapUpdater.start();
+        mInterfaceMapHelper = mDeps.makeBpfInterfaceMapHelper();
         mUidCounterSetMap = mDeps.getUidCounterSetMap();
         mCookieTagMap = mDeps.getCookieTagMap();
         mStatsMapA = mDeps.getStatsMapA();
@@ -798,18 +796,16 @@
             return new LocationPermissionChecker(context);
         }
 
-        /** Create BpfInterfaceMapUpdater to update bpf interface map. */
+        /** Create BpfInterfaceMapHelper to update bpf interface map. */
         @NonNull
-        public BpfInterfaceMapUpdater makeBpfInterfaceMapUpdater(
-                @NonNull Context ctx, @NonNull Handler handler) {
-            return new BpfInterfaceMapUpdater(ctx, handler);
+        public BpfInterfaceMapHelper makeBpfInterfaceMapHelper() {
+            return new BpfInterfaceMapHelper();
         }
 
         /** Get counter sets map for each UID. */
         public IBpfMap<S32, U8> getUidCounterSetMap() {
             try {
-                return new BpfMap<S32, U8>(UID_COUNTERSET_MAP_PATH, BpfMap.BPF_F_RDWR,
-                        S32.class, U8.class);
+                return new BpfMap<>(UID_COUNTERSET_MAP_PATH, S32.class, U8.class);
             } catch (ErrnoException e) {
                 Log.wtf(TAG, "Cannot open uid counter set map: " + e);
                 return null;
@@ -819,8 +815,8 @@
         /** Gets the cookie tag map */
         public IBpfMap<CookieTagMapKey, CookieTagMapValue> getCookieTagMap() {
             try {
-                return new BpfMap<CookieTagMapKey, CookieTagMapValue>(COOKIE_TAG_MAP_PATH,
-                        BpfMap.BPF_F_RDWR, CookieTagMapKey.class, CookieTagMapValue.class);
+                return new BpfMap<>(COOKIE_TAG_MAP_PATH,
+                        CookieTagMapKey.class, CookieTagMapValue.class);
             } catch (ErrnoException e) {
                 Log.wtf(TAG, "Cannot open cookie tag map: " + e);
                 return null;
@@ -830,8 +826,7 @@
         /** Gets stats map A */
         public IBpfMap<StatsMapKey, StatsMapValue> getStatsMapA() {
             try {
-                return new BpfMap<StatsMapKey, StatsMapValue>(STATS_MAP_A_PATH,
-                        BpfMap.BPF_F_RDWR, StatsMapKey.class, StatsMapValue.class);
+                return new BpfMap<>(STATS_MAP_A_PATH, StatsMapKey.class, StatsMapValue.class);
             } catch (ErrnoException e) {
                 Log.wtf(TAG, "Cannot open stats map A: " + e);
                 return null;
@@ -841,8 +836,7 @@
         /** Gets stats map B */
         public IBpfMap<StatsMapKey, StatsMapValue> getStatsMapB() {
             try {
-                return new BpfMap<StatsMapKey, StatsMapValue>(STATS_MAP_B_PATH,
-                        BpfMap.BPF_F_RDWR, StatsMapKey.class, StatsMapValue.class);
+                return new BpfMap<>(STATS_MAP_B_PATH, StatsMapKey.class, StatsMapValue.class);
             } catch (ErrnoException e) {
                 Log.wtf(TAG, "Cannot open stats map B: " + e);
                 return null;
@@ -852,8 +846,8 @@
         /** Gets the uid stats map */
         public IBpfMap<UidStatsMapKey, StatsMapValue> getAppUidStatsMap() {
             try {
-                return new BpfMap<UidStatsMapKey, StatsMapValue>(APP_UID_STATS_MAP_PATH,
-                        BpfMap.BPF_F_RDWR, UidStatsMapKey.class, StatsMapValue.class);
+                return new BpfMap<>(APP_UID_STATS_MAP_PATH,
+                        UidStatsMapKey.class, StatsMapValue.class);
             } catch (ErrnoException e) {
                 Log.wtf(TAG, "Cannot open app uid stats map: " + e);
                 return null;
@@ -863,8 +857,7 @@
         /** Gets interface stats map */
         public IBpfMap<S32, StatsMapValue> getIfaceStatsMap() {
             try {
-                return new BpfMap<S32, StatsMapValue>(IFACE_STATS_MAP_PATH,
-                        BpfMap.BPF_F_RDWR, S32.class, StatsMapValue.class);
+                return new BpfMap<>(IFACE_STATS_MAP_PATH, S32.class, StatsMapValue.class);
             } catch (ErrnoException e) {
                 throw new IllegalStateException("Failed to open interface stats map", e);
             }
@@ -1467,7 +1460,7 @@
 
     private int restrictFlagsForCaller(int flags, @Nullable String callingPackage) {
         // All non-privileged callers are not allowed to turn off POLL_ON_OPEN.
-        final boolean isPrivileged = PermissionUtils.checkAnyPermissionOf(mContext,
+        final boolean isPrivileged = PermissionUtils.hasAnyPermissionOf(mContext,
                 NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK,
                 android.Manifest.permission.NETWORK_STACK);
         if (!isPrivileged) {
@@ -2215,6 +2208,7 @@
             // both total usage and UID details.
             final String baseIface = snapshot.getLinkProperties().getInterfaceName();
             if (baseIface != null) {
+                nativeRegisterIface(baseIface);
                 findOrCreateNetworkIdentitySet(mActiveIfaces, baseIface).add(ident);
                 findOrCreateNetworkIdentitySet(mActiveUidIfaces, baseIface).add(ident);
 
@@ -2236,7 +2230,7 @@
                             .setDefaultNetwork(true)
                             .setOemManaged(ident.getOemManaged())
                             .setSubId(ident.getSubId()).build();
-                    final String ifaceVt = IFACE_VT + getSubIdForMobile(snapshot);
+                    final String ifaceVt = IFACE_VT + getSubIdForCellularOrSatellite(snapshot);
                     findOrCreateNetworkIdentitySet(mActiveIfaces, ifaceVt).add(vtIdent);
                     findOrCreateNetworkIdentitySet(mActiveUidIfaces, ifaceVt).add(vtIdent);
                 }
@@ -2286,6 +2280,7 @@
                 // baseIface has been handled, so ignore it.
                 if (TextUtils.equals(baseIface, iface)) continue;
                 if (iface != null) {
+                    nativeRegisterIface(iface);
                     findOrCreateNetworkIdentitySet(mActiveIfaces, iface).add(ident);
                     findOrCreateNetworkIdentitySet(mActiveUidIfaces, iface).add(ident);
                     if (isMobile) {
@@ -2304,9 +2299,15 @@
         mMobileIfaces = mobileIfaces.toArray(new String[0]);
     }
 
-    private static int getSubIdForMobile(@NonNull NetworkStateSnapshot state) {
-        if (!state.getNetworkCapabilities().hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) {
-            throw new IllegalArgumentException("Mobile state need capability TRANSPORT_CELLULAR");
+    private static int getSubIdForCellularOrSatellite(@NonNull NetworkStateSnapshot state) {
+        if (!state.getNetworkCapabilities().hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)
+                // Both cellular and satellite are 2 different network transport at Mobile using
+                // same telephony network specifier. So adding satellite transport to consider
+                // for, when satellite network is active at mobile.
+                && !state.getNetworkCapabilities().hasTransport(
+                NetworkCapabilities.TRANSPORT_SATELLITE)) {
+            throw new IllegalArgumentException(
+                    "Mobile state need capability TRANSPORT_CELLULAR or TRANSPORT_SATELLITE");
         }
 
         final NetworkSpecifier spec = state.getNetworkCapabilities().getNetworkSpecifier();
@@ -2671,7 +2672,7 @@
 
     @Override
     protected void dump(FileDescriptor fd, PrintWriter rawWriter, String[] args) {
-        if (!PermissionUtils.checkDumpPermission(mContext, TAG, rawWriter)) return;
+        if (!PermissionUtils.hasDumpPermission(mContext, TAG, rawWriter)) return;
 
         long duration = DateUtils.DAY_IN_MILLIS;
         final HashSet<String> argSet = new HashSet<String>();
@@ -2891,9 +2892,9 @@
             }
 
             pw.println();
-            pw.println("InterfaceMapUpdater:");
+            pw.println("InterfaceMapHelper:");
             pw.increaseIndent();
-            mInterfaceMapUpdater.dump(pw);
+            mInterfaceMapHelper.dump(pw);
             pw.decreaseIndent();
 
             pw.println();
@@ -3040,7 +3041,7 @@
         BpfDump.dumpMap(statsMap, pw, mapName,
                 "ifaceIndex ifaceName tag_hex uid_int cnt_set rxBytes rxPackets txBytes txPackets",
                 (key, value) -> {
-                    final String ifName = mInterfaceMapUpdater.getIfNameByIndex(key.ifaceIndex);
+                    final String ifName = mInterfaceMapHelper.getIfNameByIndex(key.ifaceIndex);
                     return key.ifaceIndex + " "
                             + (ifName != null ? ifName : "unknown") + " "
                             + "0x" + Long.toHexString(key.tag) + " "
@@ -3058,7 +3059,7 @@
         BpfDump.dumpMap(mIfaceStatsMap, pw, "mIfaceStatsMap",
                 "ifaceIndex ifaceName rxBytes rxPackets txBytes txPackets",
                 (key, value) -> {
-                    final String ifName = mInterfaceMapUpdater.getIfNameByIndex(key.val);
+                    final String ifName = mInterfaceMapHelper.getIfNameByIndex(key.val);
                     return key.val + " "
                             + (ifName != null ? ifName : "unknown") + " "
                             + value.rxBytes + " "
@@ -3419,6 +3420,7 @@
     }
 
     // TODO: Read stats by using BpfNetMapsReader.
+    private static native void nativeRegisterIface(String iface);
     @Nullable
     private static native NetworkStats.Entry nativeGetTotalStat();
     @Nullable
diff --git a/service-t/src/com/android/server/net/TrafficStatsRateLimitCache.java b/service-t/src/com/android/server/net/TrafficStatsRateLimitCache.java
new file mode 100644
index 0000000..8598ac4
--- /dev/null
+++ b/service-t/src/com/android/server/net/TrafficStatsRateLimitCache.java
@@ -0,0 +1,134 @@
+/*
+ * 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.
+ */
+
+package com.android.server.net;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.net.NetworkStats;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.time.Clock;
+import java.util.HashMap;
+import java.util.Objects;
+
+/**
+ * A thread-safe cache for storing and retrieving {@link NetworkStats.Entry} objects,
+ * with an adjustable expiry duration to manage data freshness.
+ */
+class TrafficStatsRateLimitCache {
+    private final Clock mClock;
+    private final long mExpiryDurationMs;
+
+    /**
+     * Constructs a new {@link TrafficStatsRateLimitCache} with the specified expiry duration.
+     *
+     * @param clock The {@link Clock} to use for determining timestamps.
+     * @param expiryDurationMs The expiry duration in milliseconds.
+     */
+    TrafficStatsRateLimitCache(@NonNull Clock clock, long expiryDurationMs) {
+        mClock = clock;
+        mExpiryDurationMs = expiryDurationMs;
+    }
+
+    private static class TrafficStatsCacheKey {
+        @Nullable
+        public final String iface;
+        public final int uid;
+
+        TrafficStatsCacheKey(@Nullable String iface, int uid) {
+            this.iface = iface;
+            this.uid = uid;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (!(o instanceof TrafficStatsCacheKey)) return false;
+            TrafficStatsCacheKey that = (TrafficStatsCacheKey) o;
+            return uid == that.uid && Objects.equals(iface, that.iface);
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(iface, uid);
+        }
+    }
+
+    private static class TrafficStatsCacheValue {
+        public final long timestamp;
+        @NonNull
+        public final NetworkStats.Entry entry;
+
+        TrafficStatsCacheValue(long timestamp, NetworkStats.Entry entry) {
+            this.timestamp = timestamp;
+            this.entry = entry;
+        }
+    }
+
+    @GuardedBy("mMap")
+    private final HashMap<TrafficStatsCacheKey, TrafficStatsCacheValue> mMap = new HashMap<>();
+
+    /**
+     * Retrieves a {@link NetworkStats.Entry} from the cache, associated with the given key.
+     *
+     * @param iface The interface name to include in the cache key. Null if not applicable.
+     * @param uid The UID to include in the cache key. {@code UID_ALL} if not applicable.
+     * @return The cached {@link NetworkStats.Entry}, or null if not found or expired.
+     */
+    @Nullable
+    NetworkStats.Entry get(String iface, int uid) {
+        final TrafficStatsCacheKey key = new TrafficStatsCacheKey(iface, uid);
+        synchronized (mMap) { // Synchronize for thread-safety
+            final TrafficStatsCacheValue value = mMap.get(key);
+            if (value != null && !isExpired(value.timestamp)) {
+                return value.entry;
+            } else {
+                mMap.remove(key); // Remove expired entries
+                return null;
+            }
+        }
+    }
+
+    /**
+     * Stores a {@link NetworkStats.Entry} in the cache, associated with the given key.
+     *
+     * @param iface The interface name to include in the cache key. Null if not applicable.
+     * @param uid   The UID to include in the cache key. {@code UID_ALL} if not applicable.
+     * @param entry The {@link NetworkStats.Entry} to store in the cache.
+     */
+    void put(String iface, int uid, @NonNull final NetworkStats.Entry entry) {
+        Objects.requireNonNull(entry);
+        final TrafficStatsCacheKey key = new TrafficStatsCacheKey(iface, uid);
+        synchronized (mMap) { // Synchronize for thread-safety
+            mMap.put(key, new TrafficStatsCacheValue(mClock.millis(), entry));
+        }
+    }
+
+    /**
+     * Clear the cache.
+     */
+    void clear() {
+        synchronized (mMap) {
+            mMap.clear();
+        }
+    }
+
+    private boolean isExpired(long timestamp) {
+        return mClock.millis() > timestamp + mExpiryDurationMs;
+    }
+}
diff --git a/service/Android.bp b/service/Android.bp
index 7c5da0d..c35c4f8 100644
--- a/service/Android.bp
+++ b/service/Android.bp
@@ -15,6 +15,7 @@
 //
 
 package {
+    default_team: "trendy_team_fwk_core_networking",
     // See: http://go/android-license-faq
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
@@ -70,9 +71,6 @@
     apex_available: [
         "com.android.tethering",
     ],
-    lint: {
-        baseline_filename: "lint-baseline.xml",
-    },
 }
 
 // The library name match the service-connectivity jarjar rules that put the JNI utils in the
@@ -181,6 +179,8 @@
         "unsupportedappusage",
         "ServiceConnectivityResources",
         "framework-statsd",
+        "framework-permission",
+        "framework-permission-s",
     ],
     static_libs: [
         // Do not add libs here if they are already included
@@ -188,7 +188,7 @@
         "androidx.annotation_annotation",
         "connectivity-net-module-utils-bpf",
         "connectivity_native_aidl_interface-lateststable-java",
-        "dnsresolver_aidl_interface-V13-java",
+        "dnsresolver_aidl_interface-V14-java",
         "modules-utils-shell-command-handler",
         "net-utils-device-common",
         "net-utils-device-common-ip",
@@ -199,13 +199,14 @@
         "PlatformProperties",
         "service-connectivity-protos",
         "service-connectivity-stats-protos",
+        "net-utils-multicast-forwarding-structs",
     ],
     apex_available: [
         "com.android.tethering",
     ],
     lint: {
-        strict_updatability_linting: true,
         baseline_filename: "lint-baseline.xml",
+
     },
     visibility: [
         "//packages/modules/Connectivity/service-t",
@@ -231,7 +232,7 @@
     ],
     lint: {
         strict_updatability_linting: true,
-        baseline_filename: "lint-baseline.xml",
+
     },
 }
 
@@ -267,6 +268,8 @@
         "framework-tethering.impl",
         "framework-wifi",
         "libprotobuf-java-nano",
+        "framework-permission",
+        "framework-permission-s",
     ],
     jarjar_rules: ":connectivity-jarjar-rules",
     apex_available: [
@@ -275,9 +278,6 @@
     optimize: {
         proguard_flags_files: ["proguard.flags"],
     },
-    lint: {
-        strict_updatability_linting: true,
-    },
 }
 
 // A special library created strictly for use by the tests as they need the
@@ -290,18 +290,12 @@
 java_library {
     name: "service-connectivity-for-tests",
     defaults: ["service-connectivity-defaults"],
-    lint: {
-        baseline_filename: "lint-baseline.xml",
-    },
 }
 
 java_library {
     name: "service-connectivity",
     defaults: ["service-connectivity-defaults"],
     installable: true,
-    lint: {
-        baseline_filename: "lint-baseline.xml",
-    },
 }
 
 java_library_static {
@@ -316,9 +310,6 @@
     ],
     static_libs: ["ConnectivityServiceprotos"],
     apex_available: ["com.android.tethering"],
-    lint: {
-        baseline_filename: "lint-baseline.xml",
-    },
 }
 
 genrule {
diff --git a/service/ServiceConnectivityResources/Android.bp b/service/ServiceConnectivityResources/Android.bp
index 2260596..2621256 100644
--- a/service/ServiceConnectivityResources/Android.bp
+++ b/service/ServiceConnectivityResources/Android.bp
@@ -16,6 +16,7 @@
 
 // APK to hold all the wifi overlayable resources.
 package {
+    default_team: "trendy_team_fwk_core_networking",
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
diff --git a/service/ServiceConnectivityResources/OWNERS b/service/ServiceConnectivityResources/OWNERS
new file mode 100644
index 0000000..df41ff2
--- /dev/null
+++ b/service/ServiceConnectivityResources/OWNERS
@@ -0,0 +1,2 @@
+per-file res/values/config_thread.xml = file:platform/packages/modules/Connectivity:main:/thread/OWNERS
+per-file res/values/overlayable.xml = file:platform/packages/modules/Connectivity:main:/thread/OWNERS
diff --git a/service/ServiceConnectivityResources/res/values-af/strings.xml b/service/ServiceConnectivityResources/res/values-af/strings.xml
index 086c6e3..a0f927e 100644
--- a/service/ServiceConnectivityResources/res/values-af/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-af/strings.xml
@@ -22,6 +22,9 @@
     <string name="network_available_sign_in" msgid="2622520134876355561">"Meld by netwerk aan"</string>
     <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
     <skip />
+    <string name="mobile_network_available_no_internet" msgid="1000871587359324217">"Geen internet nie"</string>
+    <string name="mobile_network_available_no_internet_detailed" msgid="5438738723127062816">"Jou <xliff:g id="NETWORK_CARRIER">%1$s</xliff:g>-data is dalk op. Tik vir opsies."</string>
+    <string name="mobile_network_available_no_internet_detailed_unknown_carrier" msgid="5375681117265354337">"Jou data is dalk op. Tik vir opsies."</string>
     <string name="wifi_no_internet" msgid="1326348603404555475">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> het geen internettoegang nie"</string>
     <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"Tik vir opsies"</string>
     <string name="mobile_no_internet" msgid="4087718456753201450">"Selnetwerk het nie internettoegang nie"</string>
diff --git a/service/ServiceConnectivityResources/res/values-am/strings.xml b/service/ServiceConnectivityResources/res/values-am/strings.xml
index 886b353..b9ce7f0 100644
--- a/service/ServiceConnectivityResources/res/values-am/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-am/strings.xml
@@ -22,6 +22,9 @@
     <string name="network_available_sign_in" msgid="2622520134876355561">"ወደ አውታረ መረብ በመለያ ይግቡ"</string>
     <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
     <skip />
+    <string name="mobile_network_available_no_internet" msgid="1000871587359324217">"በይነመረብ የለም"</string>
+    <string name="mobile_network_available_no_internet_detailed" msgid="5438738723127062816">"ከ<xliff:g id="NETWORK_CARRIER">%1$s</xliff:g> ውሂብ ጨርሰው ሊሆን ይችላል። ለአማራጮች መታ ያድርጉ።"</string>
+    <string name="mobile_network_available_no_internet_detailed_unknown_carrier" msgid="5375681117265354337">"ውሂብ ጨርሰው ሊሆን ይችላል። ለአማራጮች መታ ያድርጉ።"</string>
     <string name="wifi_no_internet" msgid="1326348603404555475">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> ምንም የበይነ መረብ መዳረሻ የለም"</string>
     <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"ለአማራጮች መታ ያድርጉ"</string>
     <string name="mobile_no_internet" msgid="4087718456753201450">"የተንቀሳቃሽ ስልክ አውታረ መረብ የበይነመረብ መዳረሻ የለውም"</string>
diff --git a/service/ServiceConnectivityResources/res/values-ar/strings.xml b/service/ServiceConnectivityResources/res/values-ar/strings.xml
index 07d9c2e..92dd9a1 100644
--- a/service/ServiceConnectivityResources/res/values-ar/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-ar/strings.xml
@@ -22,6 +22,9 @@
     <string name="network_available_sign_in" msgid="2622520134876355561">"تسجيل الدخول إلى الشبكة"</string>
     <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
     <skip />
+    <string name="mobile_network_available_no_internet" msgid="1000871587359324217">"لا يتوفّر اتصال بالإنترنت"</string>
+    <string name="mobile_network_available_no_internet_detailed" msgid="5438738723127062816">"قد تكون البيانات التي يوفِّرها \"<xliff:g id="NETWORK_CARRIER">%1$s</xliff:g>\" نفدت. انقر لعرض الخيارات."</string>
+    <string name="mobile_network_available_no_internet_detailed_unknown_carrier" msgid="5375681117265354337">"قد تكون البيانات نفدت. انقر لعرض الخيارات."</string>
     <string name="wifi_no_internet" msgid="1326348603404555475">"لا يتوفّر في <xliff:g id="NETWORK_SSID">%1$s</xliff:g> إمكانية الاتصال بالإنترنت."</string>
     <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"انقر للحصول على الخيارات."</string>
     <string name="mobile_no_internet" msgid="4087718456753201450">"شبكة الجوّال هذه غير متصلة بالإنترنت"</string>
diff --git a/service/ServiceConnectivityResources/res/values-as/strings.xml b/service/ServiceConnectivityResources/res/values-as/strings.xml
index e753cb3..a3c2c28 100644
--- a/service/ServiceConnectivityResources/res/values-as/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-as/strings.xml
@@ -18,10 +18,13 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="connectivityResourcesAppLabel" msgid="2476261877900882974">"ছিষ্টেম সংযোগৰ উৎস"</string>
-    <string name="wifi_available_sign_in" msgid="8041178343789805553">"ৱাই-ফাই নেটৱৰ্কত ছাইন ইন কৰক"</string>
+    <string name="wifi_available_sign_in" msgid="8041178343789805553">"Wi-Fi নেটৱৰ্কত ছাইন ইন কৰক"</string>
     <string name="network_available_sign_in" msgid="2622520134876355561">"নেটৱৰ্কত ছাইন ইন কৰক"</string>
     <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
     <skip />
+    <string name="mobile_network_available_no_internet" msgid="1000871587359324217">"ইণ্টাৰনেট নাই"</string>
+    <string name="mobile_network_available_no_internet_detailed" msgid="5438738723127062816">"আপোনাৰ <xliff:g id="NETWORK_CARRIER">%1$s</xliff:g>ৰ ডেটা হয়তো শেষ হৈছে। বিকল্পৰ বাবে টিপক।"</string>
+    <string name="mobile_network_available_no_internet_detailed_unknown_carrier" msgid="5375681117265354337">"আপোনাৰ ডেটা হয়তো শেষ হৈছে। বিকল্পৰ বাবে টিপক।"</string>
     <string name="wifi_no_internet" msgid="1326348603404555475">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g>ৰ ইণ্টাৰনেটৰ এক্সেছ নাই"</string>
     <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"অধিক বিকল্পৰ বাবে টিপক"</string>
     <string name="mobile_no_internet" msgid="4087718456753201450">"ম’বাইল নেটৱৰ্কৰ কোনো ইণ্টাৰনেটৰ এক্সেছ নাই"</string>
diff --git a/service/ServiceConnectivityResources/res/values-az/strings.xml b/service/ServiceConnectivityResources/res/values-az/strings.xml
index f33a3e3..ab6e0fb 100644
--- a/service/ServiceConnectivityResources/res/values-az/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-az/strings.xml
@@ -22,6 +22,9 @@
     <string name="network_available_sign_in" msgid="2622520134876355561">"Şəbəkəyə daxil olun"</string>
     <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
     <skip />
+    <string name="mobile_network_available_no_internet" msgid="1000871587359324217">"İnternet yoxdur"</string>
+    <string name="mobile_network_available_no_internet_detailed" msgid="5438738723127062816">"<xliff:g id="NETWORK_CARRIER">%1$s</xliff:g> datası bitmiş ola bilər. Seçimlər üçün toxunun."</string>
+    <string name="mobile_network_available_no_internet_detailed_unknown_carrier" msgid="5375681117265354337">"Data bitmiş ola bilər. Seçimlər üçün toxunun."</string>
     <string name="wifi_no_internet" msgid="1326348603404555475">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> üçün internet girişi əlçatan deyil"</string>
     <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"Seçimlər üçün tıklayın"</string>
     <string name="mobile_no_internet" msgid="4087718456753201450">"Mobil şəbəkənin internetə girişi yoxdur"</string>
diff --git a/service/ServiceConnectivityResources/res/values-b+sr+Latn/strings.xml b/service/ServiceConnectivityResources/res/values-b+sr+Latn/strings.xml
index 7398e7c..5bbf143 100644
--- a/service/ServiceConnectivityResources/res/values-b+sr+Latn/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-b+sr+Latn/strings.xml
@@ -22,6 +22,9 @@
     <string name="network_available_sign_in" msgid="2622520134876355561">"Prijavite se na mrežu"</string>
     <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
     <skip />
+    <string name="mobile_network_available_no_internet" msgid="1000871587359324217">"Nema interneta"</string>
+    <string name="mobile_network_available_no_internet_detailed" msgid="5438738723127062816">"Možda ste potrošili <xliff:g id="NETWORK_CARRIER">%1$s</xliff:g> podatke. Dodirnite za opcije."</string>
+    <string name="mobile_network_available_no_internet_detailed_unknown_carrier" msgid="5375681117265354337">"Možda ste potrošili podatke. Dodirnite za opcije."</string>
     <string name="wifi_no_internet" msgid="1326348603404555475">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> nema pristup internetu"</string>
     <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"Dodirnite za opcije"</string>
     <string name="mobile_no_internet" msgid="4087718456753201450">"Mobilna mreža nema pristup internetu"</string>
diff --git a/service/ServiceConnectivityResources/res/values-be/strings.xml b/service/ServiceConnectivityResources/res/values-be/strings.xml
index 3459cc7..30a81b3 100644
--- a/service/ServiceConnectivityResources/res/values-be/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-be/strings.xml
@@ -22,6 +22,9 @@
     <string name="network_available_sign_in" msgid="2622520134876355561">"Увайдзіце ў сетку"</string>
     <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
     <skip />
+    <string name="mobile_network_available_no_internet" msgid="1000871587359324217">"Няма падключэння да інтэрнэту"</string>
+    <string name="mobile_network_available_no_internet_detailed" msgid="5438738723127062816">"Магчыма, скончыўся трафік, выдзелены аператарам \"<xliff:g id="NETWORK_CARRIER">%1$s</xliff:g>\". Націсніце, каб паглядзець даступныя дзеянні."</string>
+    <string name="mobile_network_available_no_internet_detailed_unknown_carrier" msgid="5375681117265354337">"Магчыма, скончыўся трафік. Націсніце, каб паглядзець даступныя дзеянні."</string>
     <string name="wifi_no_internet" msgid="1326348603404555475">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> не мае доступу ў інтэрнэт"</string>
     <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"Дакраніцеся, каб убачыць параметры"</string>
     <string name="mobile_no_internet" msgid="4087718456753201450">"Мабільная сетка не мае доступу ў інтэрнэт"</string>
diff --git a/service/ServiceConnectivityResources/res/values-bg/strings.xml b/service/ServiceConnectivityResources/res/values-bg/strings.xml
index b4ae618..d52ee22 100644
--- a/service/ServiceConnectivityResources/res/values-bg/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-bg/strings.xml
@@ -22,6 +22,9 @@
     <string name="network_available_sign_in" msgid="2622520134876355561">"Вход в мрежата"</string>
     <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
     <skip />
+    <string name="mobile_network_available_no_internet" msgid="1000871587359324217">"Няма интернет"</string>
+    <string name="mobile_network_available_no_internet_detailed" msgid="5438738723127062816">"Възможно е да сте изчерпали данните си от <xliff:g id="NETWORK_CARRIER">%1$s</xliff:g>. Докоснете за опции."</string>
+    <string name="mobile_network_available_no_internet_detailed_unknown_carrier" msgid="5375681117265354337">"Възможно е да сте изчерпали данните си. Докоснете за опции."</string>
     <string name="wifi_no_internet" msgid="1326348603404555475">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> няма достъп до интернет"</string>
     <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"Докоснете за опции"</string>
     <string name="mobile_no_internet" msgid="4087718456753201450">"Мобилната мрежа няма достъп до интернет"</string>
diff --git a/service/ServiceConnectivityResources/res/values-bn/strings.xml b/service/ServiceConnectivityResources/res/values-bn/strings.xml
index 3b32973..9a68bcd 100644
--- a/service/ServiceConnectivityResources/res/values-bn/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-bn/strings.xml
@@ -22,6 +22,9 @@
     <string name="network_available_sign_in" msgid="2622520134876355561">"নেটওয়ার্কে সাইন-ইন করুন"</string>
     <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
     <skip />
+    <string name="mobile_network_available_no_internet" msgid="1000871587359324217">"ইন্টারনেট কানেকশন নেই"</string>
+    <string name="mobile_network_available_no_internet_detailed" msgid="5438738723127062816">"আপনার <xliff:g id="NETWORK_CARRIER">%1$s</xliff:g>-এর ডেটা হয়ত শেষ হয়ে গেছে। বিকল্প পাওয়ার জন্য ট্যাপ করুন।"</string>
+    <string name="mobile_network_available_no_internet_detailed_unknown_carrier" msgid="5375681117265354337">"আপনার ডেটা হয়ত শেষ হয়ে গেছে। বিকল্প পাওয়ার জন্য ট্যাপ করুন।"</string>
     <string name="wifi_no_internet" msgid="1326348603404555475">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g>-এর ইন্টারনেটে অ্যাক্সেস নেই"</string>
     <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"বিকল্পগুলির জন্য আলতো চাপুন"</string>
     <string name="mobile_no_internet" msgid="4087718456753201450">"মোবাইল নেটওয়ার্কে কোনও ইন্টারনেট অ্যাক্সেস নেই"</string>
diff --git a/service/ServiceConnectivityResources/res/values-bs/strings.xml b/service/ServiceConnectivityResources/res/values-bs/strings.xml
index 0bc0a7c..ae401fb 100644
--- a/service/ServiceConnectivityResources/res/values-bs/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-bs/strings.xml
@@ -22,6 +22,9 @@
     <string name="network_available_sign_in" msgid="2622520134876355561">"Prijava na mrežu"</string>
     <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
     <skip />
+    <string name="mobile_network_available_no_internet" msgid="1000871587359324217">"Nema internetske veze"</string>
+    <string name="mobile_network_available_no_internet_detailed" msgid="5438738723127062816">"Možda ste iskoristili prijenos podataka na mobilnoj mreži <xliff:g id="NETWORK_CARRIER">%1$s</xliff:g>. Dodirnite za opcije."</string>
+    <string name="mobile_network_available_no_internet_detailed_unknown_carrier" msgid="5375681117265354337">"Možda ste iskoristili prijenos podataka na mobilnoj mreži. Dodirnite za opcije."</string>
     <string name="wifi_no_internet" msgid="1326348603404555475">"Mreža <xliff:g id="NETWORK_SSID">%1$s</xliff:g> nema pristup internetu"</string>
     <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"Dodirnite za opcije"</string>
     <string name="mobile_no_internet" msgid="4087718456753201450">"Mobilna mreža nema pristup internetu"</string>
diff --git a/service/ServiceConnectivityResources/res/values-ca/strings.xml b/service/ServiceConnectivityResources/res/values-ca/strings.xml
index 22b9dbd..325b7ea 100644
--- a/service/ServiceConnectivityResources/res/values-ca/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-ca/strings.xml
@@ -22,6 +22,9 @@
     <string name="network_available_sign_in" msgid="2622520134876355561">"Inicia la sessió a la xarxa"</string>
     <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
     <skip />
+    <string name="mobile_network_available_no_internet" msgid="1000871587359324217">"Sense connexió a Internet"</string>
+    <string name="mobile_network_available_no_internet_detailed" msgid="5438738723127062816">"Pot ser que t\'hagis quedat sense dades de <xliff:g id="NETWORK_CARRIER">%1$s</xliff:g>. Toca per veure les opcions."</string>
+    <string name="mobile_network_available_no_internet_detailed_unknown_carrier" msgid="5375681117265354337">"Pot ser que t\'hagis quedat sense dades. Toca per veure les opcions."</string>
     <string name="wifi_no_internet" msgid="1326348603404555475">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> no té accés a Internet"</string>
     <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"Toca per veure les opcions"</string>
     <string name="mobile_no_internet" msgid="4087718456753201450">"La xarxa mòbil no té accés a Internet"</string>
diff --git a/service/ServiceConnectivityResources/res/values-cs/strings.xml b/service/ServiceConnectivityResources/res/values-cs/strings.xml
index ccf21ee..a785c10 100644
--- a/service/ServiceConnectivityResources/res/values-cs/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-cs/strings.xml
@@ -22,6 +22,9 @@
     <string name="network_available_sign_in" msgid="2622520134876355561">"Přihlásit se k síti"</string>
     <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
     <skip />
+    <string name="mobile_network_available_no_internet" msgid="1000871587359324217">"Nejste připojeni k internetu"</string>
+    <string name="mobile_network_available_no_internet_detailed" msgid="5438738723127062816">"Možná vám došla data od poskytovatele <xliff:g id="NETWORK_CARRIER">%1$s</xliff:g>. Klepnutím zobrazíte možnosti."</string>
+    <string name="mobile_network_available_no_internet_detailed_unknown_carrier" msgid="5375681117265354337">"Možná vám došla data. Klepnutím zobrazíte možnosti."</string>
     <string name="wifi_no_internet" msgid="1326348603404555475">"Síť <xliff:g id="NETWORK_SSID">%1$s</xliff:g> nemá přístup k internetu"</string>
     <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"Klepnutím zobrazíte možnosti"</string>
     <string name="mobile_no_internet" msgid="4087718456753201450">"Mobilní síť nemá přístup k internetu"</string>
diff --git a/service/ServiceConnectivityResources/res/values-da/strings.xml b/service/ServiceConnectivityResources/res/values-da/strings.xml
index a33143e..9d7b0fe 100644
--- a/service/ServiceConnectivityResources/res/values-da/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-da/strings.xml
@@ -22,6 +22,9 @@
     <string name="network_available_sign_in" msgid="2622520134876355561">"Log ind på netværk"</string>
     <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
     <skip />
+    <string name="mobile_network_available_no_internet" msgid="1000871587359324217">"Intet internet"</string>
+    <string name="mobile_network_available_no_internet_detailed" msgid="5438738723127062816">"Du er muligvis løbet tør for data fra <xliff:g id="NETWORK_CARRIER">%1$s</xliff:g>. Tryk for at se valgmuligheder."</string>
+    <string name="mobile_network_available_no_internet_detailed_unknown_carrier" msgid="5375681117265354337">"Du er muligvis løbet tør for data. Tryk for at se valgmuligheder."</string>
     <string name="wifi_no_internet" msgid="1326348603404555475">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> har ingen internetforbindelse"</string>
     <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"Tryk for at se valgmuligheder"</string>
     <string name="mobile_no_internet" msgid="4087718456753201450">"Mobilnetværket har ingen internetadgang"</string>
diff --git a/service/ServiceConnectivityResources/res/values-de/strings.xml b/service/ServiceConnectivityResources/res/values-de/strings.xml
index 96cc7d2..536ebda 100644
--- a/service/ServiceConnectivityResources/res/values-de/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-de/strings.xml
@@ -22,6 +22,9 @@
     <string name="network_available_sign_in" msgid="2622520134876355561">"Im Netzwerk anmelden"</string>
     <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
     <skip />
+    <string name="mobile_network_available_no_internet" msgid="1000871587359324217">"Kein Internet"</string>
+    <string name="mobile_network_available_no_internet_detailed" msgid="5438738723127062816">"Möglicherweise sind deine mobilen Daten von <xliff:g id="NETWORK_CARRIER">%1$s</xliff:g> aufgebraucht. Tippe für Optionen."</string>
+    <string name="mobile_network_available_no_internet_detailed_unknown_carrier" msgid="5375681117265354337">"Möglicherweise sind deine mobilen Daten aufgebraucht. Tippe für Optionen."</string>
     <string name="wifi_no_internet" msgid="1326348603404555475">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> hat keinen Internetzugriff"</string>
     <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"Für Optionen tippen"</string>
     <string name="mobile_no_internet" msgid="4087718456753201450">"Mobiles Netzwerk hat keinen Internetzugriff"</string>
diff --git a/service/ServiceConnectivityResources/res/values-el/strings.xml b/service/ServiceConnectivityResources/res/values-el/strings.xml
index b5f319d..c8eebc7 100644
--- a/service/ServiceConnectivityResources/res/values-el/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-el/strings.xml
@@ -22,6 +22,9 @@
     <string name="network_available_sign_in" msgid="2622520134876355561">"Σύνδεση στο δίκτυο"</string>
     <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
     <skip />
+    <string name="mobile_network_available_no_internet" msgid="1000871587359324217">"Χωρίς σύνδεση στο διαδίκτυο"</string>
+    <string name="mobile_network_available_no_internet_detailed" msgid="5438738723127062816">"Ενδέχεται να έχουν εξαντληθεί τα δεδομένα σας από τον πάροχο <xliff:g id="NETWORK_CARRIER">%1$s</xliff:g>. Πατήστε για να δείτε τις επιλογές."</string>
+    <string name="mobile_network_available_no_internet_detailed_unknown_carrier" msgid="5375681117265354337">"Ενδέχεται να έχουν εξαντληθεί τα δεδομένα σας. Πατήστε για να δείτε τις επιλογές."</string>
     <string name="wifi_no_internet" msgid="1326348603404555475">"Η εφαρμογή <xliff:g id="NETWORK_SSID">%1$s</xliff:g> δεν έχει πρόσβαση στο διαδίκτυο"</string>
     <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"Πατήστε για να δείτε τις επιλογές"</string>
     <string name="mobile_no_internet" msgid="4087718456753201450">"Το δίκτυο κινητής τηλεφωνίας δεν έχει πρόσβαση στο διαδίκτυο."</string>
diff --git a/service/ServiceConnectivityResources/res/values-en-rAU/strings.xml b/service/ServiceConnectivityResources/res/values-en-rAU/strings.xml
index c490cf8..37a5ec0 100644
--- a/service/ServiceConnectivityResources/res/values-en-rAU/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-en-rAU/strings.xml
@@ -22,6 +22,9 @@
     <string name="network_available_sign_in" msgid="2622520134876355561">"Sign in to network"</string>
     <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
     <skip />
+    <string name="mobile_network_available_no_internet" msgid="1000871587359324217">"No Internet"</string>
+    <string name="mobile_network_available_no_internet_detailed" msgid="5438738723127062816">"You may be out of data from <xliff:g id="NETWORK_CARRIER">%1$s</xliff:g>. Tap for options."</string>
+    <string name="mobile_network_available_no_internet_detailed_unknown_carrier" msgid="5375681117265354337">"You may be out of data. Tap for options."</string>
     <string name="wifi_no_internet" msgid="1326348603404555475">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> has no Internet access"</string>
     <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"Tap for options"</string>
     <string name="mobile_no_internet" msgid="4087718456753201450">"Mobile network has no Internet access"</string>
diff --git a/service/ServiceConnectivityResources/res/values-en-rCA/strings.xml b/service/ServiceConnectivityResources/res/values-en-rCA/strings.xml
index 9827f4e..836369e 100644
--- a/service/ServiceConnectivityResources/res/values-en-rCA/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-en-rCA/strings.xml
@@ -22,6 +22,9 @@
     <string name="network_available_sign_in" msgid="2622520134876355561">"Sign in to network"</string>
     <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
     <skip />
+    <string name="mobile_network_available_no_internet" msgid="1000871587359324217">"No internet"</string>
+    <string name="mobile_network_available_no_internet_detailed" msgid="5438738723127062816">"You may be out of data from <xliff:g id="NETWORK_CARRIER">%1$s</xliff:g>. Tap for options."</string>
+    <string name="mobile_network_available_no_internet_detailed_unknown_carrier" msgid="5375681117265354337">"You may be out of data. Tap for options."</string>
     <string name="wifi_no_internet" msgid="1326348603404555475">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> has no internet access"</string>
     <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"Tap for options"</string>
     <string name="mobile_no_internet" msgid="4087718456753201450">"Mobile network has no internet access"</string>
diff --git a/service/ServiceConnectivityResources/res/values-en-rGB/strings.xml b/service/ServiceConnectivityResources/res/values-en-rGB/strings.xml
index c490cf8..37a5ec0 100644
--- a/service/ServiceConnectivityResources/res/values-en-rGB/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-en-rGB/strings.xml
@@ -22,6 +22,9 @@
     <string name="network_available_sign_in" msgid="2622520134876355561">"Sign in to network"</string>
     <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
     <skip />
+    <string name="mobile_network_available_no_internet" msgid="1000871587359324217">"No Internet"</string>
+    <string name="mobile_network_available_no_internet_detailed" msgid="5438738723127062816">"You may be out of data from <xliff:g id="NETWORK_CARRIER">%1$s</xliff:g>. Tap for options."</string>
+    <string name="mobile_network_available_no_internet_detailed_unknown_carrier" msgid="5375681117265354337">"You may be out of data. Tap for options."</string>
     <string name="wifi_no_internet" msgid="1326348603404555475">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> has no Internet access"</string>
     <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"Tap for options"</string>
     <string name="mobile_no_internet" msgid="4087718456753201450">"Mobile network has no Internet access"</string>
diff --git a/service/ServiceConnectivityResources/res/values-en-rIN/strings.xml b/service/ServiceConnectivityResources/res/values-en-rIN/strings.xml
index c490cf8..37a5ec0 100644
--- a/service/ServiceConnectivityResources/res/values-en-rIN/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-en-rIN/strings.xml
@@ -22,6 +22,9 @@
     <string name="network_available_sign_in" msgid="2622520134876355561">"Sign in to network"</string>
     <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
     <skip />
+    <string name="mobile_network_available_no_internet" msgid="1000871587359324217">"No Internet"</string>
+    <string name="mobile_network_available_no_internet_detailed" msgid="5438738723127062816">"You may be out of data from <xliff:g id="NETWORK_CARRIER">%1$s</xliff:g>. Tap for options."</string>
+    <string name="mobile_network_available_no_internet_detailed_unknown_carrier" msgid="5375681117265354337">"You may be out of data. Tap for options."</string>
     <string name="wifi_no_internet" msgid="1326348603404555475">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> has no Internet access"</string>
     <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"Tap for options"</string>
     <string name="mobile_no_internet" msgid="4087718456753201450">"Mobile network has no Internet access"</string>
diff --git a/service/ServiceConnectivityResources/res/values-en-rXC/strings.xml b/service/ServiceConnectivityResources/res/values-en-rXC/strings.xml
index 67c3659..258e570 100644
--- a/service/ServiceConnectivityResources/res/values-en-rXC/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-en-rXC/strings.xml
@@ -22,6 +22,9 @@
     <string name="network_available_sign_in" msgid="2622520134876355561">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‏‎‎‎‏‏‎‎‏‎‏‎‎‎‏‎‎‎‎‎‎‏‎‏‎‏‎‏‏‏‏‏‏‏‏‏‎‎‏‏‏‎‏‎‏‎‏‏‎‏‏‏‏‏‎‏‎‎‏‎Sign in to network‎‏‎‎‏‎"</string>
     <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
     <skip />
+    <string name="mobile_network_available_no_internet" msgid="1000871587359324217">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‏‎‏‏‏‏‎‎‎‏‏‏‏‎‎‏‏‏‏‎‏‏‎‎‏‏‏‏‏‏‎‎‏‏‎‎‏‎‏‎‏‎‏‎‏‏‏‏‎‎‎‎‎‏‏‏‎‎‏‎No internet‎‏‎‎‏‎"</string>
+    <string name="mobile_network_available_no_internet_detailed" msgid="5438738723127062816">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‎‏‏‎‏‏‏‏‎‏‎‎‏‎‎‏‎‎‎‎‎‎‎‏‏‎‏‏‎‎‏‎‎‏‎‎‎‏‎‎‏‏‎‎‏‏‏‎‎‎‏‎‎‏‎‎‎‎‎‎You may be out of data from ‎‏‎‎‏‏‎<xliff:g id="NETWORK_CARRIER">%1$s</xliff:g>‎‏‎‎‏‏‏‎. Tap for options.‎‏‎‎‏‎"</string>
+    <string name="mobile_network_available_no_internet_detailed_unknown_carrier" msgid="5375681117265354337">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‎‏‎‏‎‎‏‏‎‏‎‎‏‎‎‎‎‎‏‎‏‏‏‏‏‏‎‏‎‎‏‏‏‎‎‎‏‏‎‎‎‎‎‎‏‎‏‏‏‏‎‎‏‏‎‎‎‎‏‎You may be out of data. Tap for options.‎‏‎‎‏‎"</string>
     <string name="wifi_no_internet" msgid="1326348603404555475">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‎‏‎‎‏‏‎‏‎‎‎‎‎‏‎‎‎‏‏‎‎‎‎‎‏‏‏‎‎‎‎‏‎‏‎‎‏‎‎‎‎‏‏‏‎‎‏‎‏‎‎‏‏‎‏‎‎‏‏‎‎‏‎‎‏‏‎<xliff:g id="NETWORK_SSID">%1$s</xliff:g>‎‏‎‎‏‏‏‎ has no internet access‎‏‎‎‏‎"</string>
     <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‎‎‎‎‎‏‏‏‏‏‎‎‏‎‎‏‏‏‏‎‏‏‏‏‎‏‏‎‏‎‏‎‎‏‏‎‏‏‎‏‎‎‏‎‎‏‎‏‎‏‏‎‎‎‏‏‎‏‎‎Tap for options‎‏‎‎‏‎"</string>
     <string name="mobile_no_internet" msgid="4087718456753201450">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‎‎‎‏‎‏‏‏‎‏‎‎‏‏‏‏‏‏‎‎‏‎‎‎‏‏‏‎‏‎‏‏‏‎‎‏‎‎‏‎‏‎‏‎‎‏‎‎‎‎‏‎‎‏‎‏‎‏‎‎Mobile network has no internet access‎‏‎‎‏‎"</string>
diff --git a/service/ServiceConnectivityResources/res/values-es-rUS/strings.xml b/service/ServiceConnectivityResources/res/values-es-rUS/strings.xml
index b24dee0..3471243 100644
--- a/service/ServiceConnectivityResources/res/values-es-rUS/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-es-rUS/strings.xml
@@ -22,6 +22,9 @@
     <string name="network_available_sign_in" msgid="2622520134876355561">"Acceder a la red"</string>
     <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
     <skip />
+    <string name="mobile_network_available_no_internet" msgid="1000871587359324217">"Sin Internet"</string>
+    <string name="mobile_network_available_no_internet_detailed" msgid="5438738723127062816">"Es posible que no tengas más datos de <xliff:g id="NETWORK_CARRIER">%1$s</xliff:g>. Presiona para ver las opciones."</string>
+    <string name="mobile_network_available_no_internet_detailed_unknown_carrier" msgid="5375681117265354337">"Es posible que no tengas más datos. Presiona para ver las opciones."</string>
     <string name="wifi_no_internet" msgid="1326348603404555475">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> no tiene acceso a Internet"</string>
     <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"Presiona para ver opciones"</string>
     <string name="mobile_no_internet" msgid="4087718456753201450">"La red móvil no tiene acceso a Internet"</string>
diff --git a/service/ServiceConnectivityResources/res/values-es/strings.xml b/service/ServiceConnectivityResources/res/values-es/strings.xml
index f4a7e3d..e8401ed 100644
--- a/service/ServiceConnectivityResources/res/values-es/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-es/strings.xml
@@ -22,6 +22,9 @@
     <string name="network_available_sign_in" msgid="2622520134876355561">"Iniciar sesión en la red"</string>
     <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
     <skip />
+    <string name="mobile_network_available_no_internet" msgid="1000871587359324217">"Sin Internet"</string>
+    <string name="mobile_network_available_no_internet_detailed" msgid="5438738723127062816">"Es posible que ya no tengas datos de <xliff:g id="NETWORK_CARRIER">%1$s</xliff:g>. Toca para ver las opciones."</string>
+    <string name="mobile_network_available_no_internet_detailed_unknown_carrier" msgid="5375681117265354337">"Es posible que ya no tengas datos. Toca para ver las opciones."</string>
     <string name="wifi_no_internet" msgid="1326348603404555475">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> no tiene acceso a Internet"</string>
     <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"Toca para ver opciones"</string>
     <string name="mobile_no_internet" msgid="4087718456753201450">"La red móvil no tiene acceso a Internet"</string>
diff --git a/service/ServiceConnectivityResources/res/values-et/strings.xml b/service/ServiceConnectivityResources/res/values-et/strings.xml
index cf997b3..1c36b0f 100644
--- a/service/ServiceConnectivityResources/res/values-et/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-et/strings.xml
@@ -22,6 +22,9 @@
     <string name="network_available_sign_in" msgid="2622520134876355561">"Võrku sisselogimine"</string>
     <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
     <skip />
+    <string name="mobile_network_available_no_internet" msgid="1000871587359324217">"Internetiühendus puudub"</string>
+    <string name="mobile_network_available_no_internet_detailed" msgid="5438738723127062816">"Teie andmesidemaht operaatorilt <xliff:g id="NETWORK_CARRIER">%1$s</xliff:g> võib olla otsas. Puudutage valikute nägemiseks."</string>
+    <string name="mobile_network_available_no_internet_detailed_unknown_carrier" msgid="5375681117265354337">"Andmesidemaht võib olla otsas. Puudutage valikute nägemiseks."</string>
     <string name="wifi_no_internet" msgid="1326348603404555475">"Võrgul <xliff:g id="NETWORK_SSID">%1$s</xliff:g> puudub Interneti-ühendus"</string>
     <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"Puudutage valikute nägemiseks"</string>
     <string name="mobile_no_internet" msgid="4087718456753201450">"Mobiilsidevõrgul puudub Interneti-ühendus"</string>
diff --git a/service/ServiceConnectivityResources/res/values-eu/strings.xml b/service/ServiceConnectivityResources/res/values-eu/strings.xml
index 13f9eb4..81d8ddb 100644
--- a/service/ServiceConnectivityResources/res/values-eu/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-eu/strings.xml
@@ -22,6 +22,9 @@
     <string name="network_available_sign_in" msgid="2622520134876355561">"Hasi saioa sarean"</string>
     <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
     <skip />
+    <string name="mobile_network_available_no_internet" msgid="1000871587359324217">"Ez zaude Internetera konektatuta"</string>
+    <string name="mobile_network_available_no_internet_detailed" msgid="5438738723127062816">"Agian agortu egin dituzu <xliff:g id="NETWORK_CARRIER">%1$s</xliff:g> operadorearen planean sartzen zaizkizun datuak. Sakatu hau zer aukera dituzun ikusteko."</string>
+    <string name="mobile_network_available_no_internet_detailed_unknown_carrier" msgid="5375681117265354337">"Datuak agortuko zitzaizkizun, agian. Sakatu hau zer aukera dituzun ikusteko."</string>
     <string name="wifi_no_internet" msgid="1326348603404555475">"Ezin da konektatu Internetera <xliff:g id="NETWORK_SSID">%1$s</xliff:g> sarearen bidez"</string>
     <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"Sakatu aukerak ikusteko"</string>
     <string name="mobile_no_internet" msgid="4087718456753201450">"Sare mugikorra ezin da konektatu Internetera"</string>
diff --git a/service/ServiceConnectivityResources/res/values-fa/strings.xml b/service/ServiceConnectivityResources/res/values-fa/strings.xml
index 296ce8e..02c60df 100644
--- a/service/ServiceConnectivityResources/res/values-fa/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-fa/strings.xml
@@ -22,6 +22,9 @@
     <string name="network_available_sign_in" msgid="2622520134876355561">"ورود به سیستم شبکه"</string>
     <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
     <skip />
+    <string name="mobile_network_available_no_internet" msgid="1000871587359324217">"اتصال اینترنت وجود ندارد"</string>
+    <string name="mobile_network_available_no_internet_detailed" msgid="5438738723127062816">"ممکن است داده <xliff:g id="NETWORK_CARRIER">%1$s</xliff:g> تمام شده باشد. برای گزینه‌ها ضربه بزنید."</string>
+    <string name="mobile_network_available_no_internet_detailed_unknown_carrier" msgid="5375681117265354337">"ممکن است داده شما تمام شده باشد. برای گزینه‌ها ضربه بزنید."</string>
     <string name="wifi_no_internet" msgid="1326348603404555475">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> به اینترنت دسترسی ندارد"</string>
     <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"برای گزینه‌ها ضربه بزنید"</string>
     <string name="mobile_no_internet" msgid="4087718456753201450">"شبکه تلفن همراه به اینترنت دسترسی ندارد"</string>
diff --git a/service/ServiceConnectivityResources/res/values-fi/strings.xml b/service/ServiceConnectivityResources/res/values-fi/strings.xml
index 07d2907..9c700d4 100644
--- a/service/ServiceConnectivityResources/res/values-fi/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-fi/strings.xml
@@ -22,6 +22,9 @@
     <string name="network_available_sign_in" msgid="2622520134876355561">"Kirjaudu verkkoon"</string>
     <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
     <skip />
+    <string name="mobile_network_available_no_internet" msgid="1000871587359324217">"Ei internetyhteyttä"</string>
+    <string name="mobile_network_available_no_internet_detailed" msgid="5438738723127062816">"Data (<xliff:g id="NETWORK_CARRIER">%1$s</xliff:g>) on ehkä lopussa. Näytä vaihtoehdot napauttamalla."</string>
+    <string name="mobile_network_available_no_internet_detailed_unknown_carrier" msgid="5375681117265354337">"Datasi on ehkä lopussa. Näytä vaihtoehdot napauttamalla."</string>
     <string name="wifi_no_internet" msgid="1326348603404555475">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> ei ole yhteydessä internetiin"</string>
     <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"Näytä vaihtoehdot napauttamalla."</string>
     <string name="mobile_no_internet" msgid="4087718456753201450">"Mobiiliverkko ei ole yhteydessä internetiin"</string>
diff --git a/service/ServiceConnectivityResources/res/values-fr-rCA/strings.xml b/service/ServiceConnectivityResources/res/values-fr-rCA/strings.xml
index 7d5b366..2b7f031 100644
--- a/service/ServiceConnectivityResources/res/values-fr-rCA/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-fr-rCA/strings.xml
@@ -22,6 +22,9 @@
     <string name="network_available_sign_in" msgid="2622520134876355561">"Connectez-vous au réseau"</string>
     <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
     <skip />
+    <string name="mobile_network_available_no_internet" msgid="1000871587359324217">"Aucune connexion Internet"</string>
+    <string name="mobile_network_available_no_internet_detailed" msgid="5438738723127062816">"Votre forfait de données avec <xliff:g id="NETWORK_CARRIER">%1$s</xliff:g> est peut-être épuisé. Touchez pour afficher les options."</string>
+    <string name="mobile_network_available_no_internet_detailed_unknown_carrier" msgid="5375681117265354337">"Votre forfait de données est peut-être épuisé. Touchez pour afficher les options."</string>
     <string name="wifi_no_internet" msgid="1326348603404555475">"Le réseau <xliff:g id="NETWORK_SSID">%1$s</xliff:g> n\'offre aucun accès à Internet"</string>
     <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"Touchez pour afficher les options"</string>
     <string name="mobile_no_internet" msgid="4087718456753201450">"Le réseau cellulaire n\'offre aucun accès à Internet"</string>
diff --git a/service/ServiceConnectivityResources/res/values-fr/strings.xml b/service/ServiceConnectivityResources/res/values-fr/strings.xml
index 2331d9b..fd179af 100644
--- a/service/ServiceConnectivityResources/res/values-fr/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-fr/strings.xml
@@ -22,6 +22,9 @@
     <string name="network_available_sign_in" msgid="2622520134876355561">"Se connecter au réseau"</string>
     <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
     <skip />
+    <string name="mobile_network_available_no_internet" msgid="1000871587359324217">"Aucun accès à Internet"</string>
+    <string name="mobile_network_available_no_internet_detailed" msgid="5438738723127062816">"Vous avez peut-être consommé l\'intégralité de votre forfait de données de <xliff:g id="NETWORK_CARRIER">%1$s</xliff:g>. Appuyez pour voir les options disponibles."</string>
+    <string name="mobile_network_available_no_internet_detailed_unknown_carrier" msgid="5375681117265354337">"Vous avez peut-être consommé l\'intégralité de votre forfait de données. Appuyez pour voir les options disponibles."</string>
     <string name="wifi_no_internet" msgid="1326348603404555475">"Aucune connexion à Internet pour <xliff:g id="NETWORK_SSID">%1$s</xliff:g>"</string>
     <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"Appuyez ici pour afficher des options."</string>
     <string name="mobile_no_internet" msgid="4087718456753201450">"Le réseau mobile ne dispose d\'aucun accès à Internet"</string>
diff --git a/service/ServiceConnectivityResources/res/values-gl/strings.xml b/service/ServiceConnectivityResources/res/values-gl/strings.xml
index f46f84b..1276730 100644
--- a/service/ServiceConnectivityResources/res/values-gl/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-gl/strings.xml
@@ -22,6 +22,9 @@
     <string name="network_available_sign_in" msgid="2622520134876355561">"Inicia sesión na rede"</string>
     <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
     <skip />
+    <string name="mobile_network_available_no_internet" msgid="1000871587359324217">"Sen conexión a Internet"</string>
+    <string name="mobile_network_available_no_internet_detailed" msgid="5438738723127062816">"Pode que non che queden datos de <xliff:g id="NETWORK_CARRIER">%1$s</xliff:g>. Toca para ver as opcións."</string>
+    <string name="mobile_network_available_no_internet_detailed_unknown_carrier" msgid="5375681117265354337">"Pode que non che queden datos. Toca para ver as opcións."</string>
     <string name="wifi_no_internet" msgid="1326348603404555475">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> non ten acceso a Internet"</string>
     <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"Toca para ver opcións."</string>
     <string name="mobile_no_internet" msgid="4087718456753201450">"A rede de telefonía móbil non ten acceso a Internet"</string>
diff --git a/service/ServiceConnectivityResources/res/values-gu/strings.xml b/service/ServiceConnectivityResources/res/values-gu/strings.xml
index ec9ecd3..cc0bca0 100644
--- a/service/ServiceConnectivityResources/res/values-gu/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-gu/strings.xml
@@ -22,6 +22,9 @@
     <string name="network_available_sign_in" msgid="2622520134876355561">"નેટવર્ક પર સાઇન ઇન કરો"</string>
     <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
     <skip />
+    <string name="mobile_network_available_no_internet" msgid="1000871587359324217">"કોઈ ઇન્ટરનેટ કનેક્શન નથી"</string>
+    <string name="mobile_network_available_no_internet_detailed" msgid="5438738723127062816">"તમારી પાસે <xliff:g id="NETWORK_CARRIER">%1$s</xliff:g>નો ડેટા બાકી ન હોય તેવું બની શકે છે. વિકલ્પો માટે ટૅપ કરો."</string>
+    <string name="mobile_network_available_no_internet_detailed_unknown_carrier" msgid="5375681117265354337">"તમારી પાસે ડેટા બાકી ન હોય તેવું બની શકે છે. વિકલ્પો માટે ટૅપ કરો."</string>
     <string name="wifi_no_internet" msgid="1326348603404555475">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> ઇન્ટરનેટ ઍક્સેસ ધરાવતું નથી"</string>
     <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"વિકલ્પો માટે ટૅપ કરો"</string>
     <string name="mobile_no_internet" msgid="4087718456753201450">"મોબાઇલ નેટવર્ક કોઈ ઇન્ટરનેટ ઍક્સેસ ધરાવતું નથી"</string>
diff --git a/service/ServiceConnectivityResources/res/values-hi/strings.xml b/service/ServiceConnectivityResources/res/values-hi/strings.xml
index 6e3bc6b..4826a3c 100644
--- a/service/ServiceConnectivityResources/res/values-hi/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-hi/strings.xml
@@ -22,6 +22,9 @@
     <string name="network_available_sign_in" msgid="2622520134876355561">"नेटवर्क में साइन इन करें"</string>
     <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
     <skip />
+    <string name="mobile_network_available_no_internet" msgid="1000871587359324217">"इंटरनेट कनेक्शन नहीं है"</string>
+    <string name="mobile_network_available_no_internet_detailed" msgid="5438738723127062816">"शायद आपके डिवाइस में <xliff:g id="NETWORK_CARRIER">%1$s</xliff:g> का डेटा खत्म हो गया है. विकल्पों के लिए टैप करें."</string>
+    <string name="mobile_network_available_no_internet_detailed_unknown_carrier" msgid="5375681117265354337">"शायद आपके डिवाइस में डेटा खत्म हो गया है. विकल्पों के लिए टैप करें."</string>
     <string name="wifi_no_internet" msgid="1326348603404555475">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> का इंटरनेट नहीं चल रहा है"</string>
     <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"विकल्पों के लिए टैप करें"</string>
     <string name="mobile_no_internet" msgid="4087718456753201450">"मोबाइल नेटवर्क पर इंटरनेट ऐक्सेस नहीं है"</string>
diff --git a/service/ServiceConnectivityResources/res/values-hr/strings.xml b/service/ServiceConnectivityResources/res/values-hr/strings.xml
index 6a6de4c..ace8c81 100644
--- a/service/ServiceConnectivityResources/res/values-hr/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-hr/strings.xml
@@ -22,6 +22,9 @@
     <string name="network_available_sign_in" msgid="2622520134876355561">"Prijava na mrežu"</string>
     <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
     <skip />
+    <string name="mobile_network_available_no_internet" msgid="1000871587359324217">"Nema interneta"</string>
+    <string name="mobile_network_available_no_internet_detailed" msgid="5438738723127062816">"Možda ste potrošili podatkovni promet od operatera <xliff:g id="NETWORK_CARRIER">%1$s</xliff:g>. Dodirnite za opcije."</string>
+    <string name="mobile_network_available_no_internet_detailed_unknown_carrier" msgid="5375681117265354337">"Možda ste potrošili podatkovni promet. Dodirnite za opcije."</string>
     <string name="wifi_no_internet" msgid="1326348603404555475">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> nema pristup internetu"</string>
     <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"Dodirnite za opcije"</string>
     <string name="mobile_no_internet" msgid="4087718456753201450">"Mobilna mreža nema pristup internetu"</string>
diff --git a/service/ServiceConnectivityResources/res/values-hu/strings.xml b/service/ServiceConnectivityResources/res/values-hu/strings.xml
index 1d39d30..ec96193 100644
--- a/service/ServiceConnectivityResources/res/values-hu/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-hu/strings.xml
@@ -22,6 +22,9 @@
     <string name="network_available_sign_in" msgid="2622520134876355561">"Bejelentkezés a hálózatba"</string>
     <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
     <skip />
+    <string name="mobile_network_available_no_internet" msgid="1000871587359324217">"Nincs internet"</string>
+    <string name="mobile_network_available_no_internet_detailed" msgid="5438738723127062816">"Előfordulhat, hogy elfogyott az adatkerete a szolgáltatónál (<xliff:g id="NETWORK_CARRIER">%1$s</xliff:g>). Koppintson a lehetőségek megjelenítéséhez."</string>
+    <string name="mobile_network_available_no_internet_detailed_unknown_carrier" msgid="5375681117265354337">"Előfordulhat, hogy elfogyott az adatkerete. Koppintson a lehetőségek megjelenítéséhez."</string>
     <string name="wifi_no_internet" msgid="1326348603404555475">"A(z) <xliff:g id="NETWORK_SSID">%1$s</xliff:g> hálózaton nincs internet-hozzáférés"</string>
     <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"Koppintson a beállítások megjelenítéséhez"</string>
     <string name="mobile_no_internet" msgid="4087718456753201450">"A mobilhálózaton nincs internet-hozzáférés"</string>
diff --git a/service/ServiceConnectivityResources/res/values-hy/strings.xml b/service/ServiceConnectivityResources/res/values-hy/strings.xml
index 99386d4..8d82899 100644
--- a/service/ServiceConnectivityResources/res/values-hy/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-hy/strings.xml
@@ -22,6 +22,9 @@
     <string name="network_available_sign_in" msgid="2622520134876355561">"Մուտք գործեք ցանց"</string>
     <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
     <skip />
+    <string name="mobile_network_available_no_internet" msgid="1000871587359324217">"Կապ չկա"</string>
+    <string name="mobile_network_available_no_internet_detailed" msgid="5438738723127062816">"Հնարավոր է՝ <xliff:g id="NETWORK_CARRIER">%1$s</xliff:g> օպերատորի ձեր սահմանաչափը սպառվել է։ Հպեք՝ տարբերակները տեսնելու համար։"</string>
+    <string name="mobile_network_available_no_internet_detailed_unknown_carrier" msgid="5375681117265354337">"Հնարավոր է՝ ձեր սահմանաչափը սպառվել է։ Հպեք՝ տարբերակները տեսնելու համար։"</string>
     <string name="wifi_no_internet" msgid="1326348603404555475">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> ցանցը չունի մուտք ինտերնետին"</string>
     <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"Հպեք՝ ընտրանքները տեսնելու համար"</string>
     <string name="mobile_no_internet" msgid="4087718456753201450">"Բջջային ցանցը չի ապահովում ինտերնետ կապ"</string>
diff --git a/service/ServiceConnectivityResources/res/values-in/strings.xml b/service/ServiceConnectivityResources/res/values-in/strings.xml
index f47d257..41226a2 100644
--- a/service/ServiceConnectivityResources/res/values-in/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-in/strings.xml
@@ -22,6 +22,9 @@
     <string name="network_available_sign_in" msgid="2622520134876355561">"Login ke jaringan"</string>
     <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
     <skip />
+    <string name="mobile_network_available_no_internet" msgid="1000871587359324217">"Tidak ada internet"</string>
+    <string name="mobile_network_available_no_internet_detailed" msgid="5438738723127062816">"Data <xliff:g id="NETWORK_CARRIER">%1$s</xliff:g> Anda mungkin sudah habis. Ketuk untuk melihat opsi."</string>
+    <string name="mobile_network_available_no_internet_detailed_unknown_carrier" msgid="5375681117265354337">"Data seluler Anda mungkin sudah habis. Ketuk untuk melihat opsi."</string>
     <string name="wifi_no_internet" msgid="1326348603404555475">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> tidak memiliki akses internet"</string>
     <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"Ketuk untuk melihat opsi"</string>
     <string name="mobile_no_internet" msgid="4087718456753201450">"Jaringan seluler tidak memiliki akses internet"</string>
diff --git a/service/ServiceConnectivityResources/res/values-is/strings.xml b/service/ServiceConnectivityResources/res/values-is/strings.xml
index eeba231..423413e 100644
--- a/service/ServiceConnectivityResources/res/values-is/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-is/strings.xml
@@ -22,6 +22,9 @@
     <string name="network_available_sign_in" msgid="2622520134876355561">"Skrá inn á net"</string>
     <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
     <skip />
+    <string name="mobile_network_available_no_internet" msgid="1000871587359324217">"Engin nettenging"</string>
+    <string name="mobile_network_available_no_internet_detailed" msgid="5438738723127062816">"Gagnamagnið þitt frá <xliff:g id="NETWORK_CARRIER">%1$s</xliff:g> gæti verið búið. Ýttu til að sjá valkosti."</string>
+    <string name="mobile_network_available_no_internet_detailed_unknown_carrier" msgid="5375681117265354337">"Gagnamagnið þitt gæti verið búið. Ýttu til að sjá valkosti."</string>
     <string name="wifi_no_internet" msgid="1326348603404555475">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> er ekki með internetaðgang"</string>
     <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"Ýttu til að sjá valkosti"</string>
     <string name="mobile_no_internet" msgid="4087718456753201450">"Farsímakerfið er ekki tengt við internetið"</string>
diff --git a/service/ServiceConnectivityResources/res/values-it/strings.xml b/service/ServiceConnectivityResources/res/values-it/strings.xml
index ec3ff8c..62fc74e 100644
--- a/service/ServiceConnectivityResources/res/values-it/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-it/strings.xml
@@ -22,6 +22,9 @@
     <string name="network_available_sign_in" msgid="2622520134876355561">"Accedi alla rete"</string>
     <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
     <skip />
+    <string name="mobile_network_available_no_internet" msgid="1000871587359324217">"Nessuna connessione a internet"</string>
+    <string name="mobile_network_available_no_internet_detailed" msgid="5438738723127062816">"I dati di <xliff:g id="NETWORK_CARRIER">%1$s</xliff:g> potrebbero essere esauriti. Tocca per le opzioni."</string>
+    <string name="mobile_network_available_no_internet_detailed_unknown_carrier" msgid="5375681117265354337">"Potresti avere esaurito i dati. Tocca per le opzioni."</string>
     <string name="wifi_no_internet" msgid="1326348603404555475">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> non ha accesso a Internet"</string>
     <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"Tocca per le opzioni"</string>
     <string name="mobile_no_internet" msgid="4087718456753201450">"La rete mobile non ha accesso a Internet"</string>
diff --git a/service/ServiceConnectivityResources/res/values-iw/strings.xml b/service/ServiceConnectivityResources/res/values-iw/strings.xml
index d123ebb..b5b4071 100644
--- a/service/ServiceConnectivityResources/res/values-iw/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-iw/strings.xml
@@ -22,6 +22,9 @@
     <string name="network_available_sign_in" msgid="2622520134876355561">"היכנס לרשת"</string>
     <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
     <skip />
+    <string name="mobile_network_available_no_internet" msgid="1000871587359324217">"אין אינטרנט"</string>
+    <string name="mobile_network_available_no_internet_detailed" msgid="5438738723127062816">"יכול להיות שחבילת הגלישה שלך אצל <xliff:g id="NETWORK_CARRIER">%1$s</xliff:g> נגמרה. אפשר להקיש כדי להציג את האפשרויות."</string>
+    <string name="mobile_network_available_no_internet_detailed_unknown_carrier" msgid="5375681117265354337">"יכול להיות שחבילת הגלישה נגמרה. אפשר להקיש כדי להציג את האפשרויות."</string>
     <string name="wifi_no_internet" msgid="1326348603404555475">"ל-<xliff:g id="NETWORK_SSID">%1$s</xliff:g> אין גישה לאינטרנט"</string>
     <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"הקש לקבלת אפשרויות"</string>
     <string name="mobile_no_internet" msgid="4087718456753201450">"לרשת הסלולרית אין גישה לאינטרנט"</string>
diff --git a/service/ServiceConnectivityResources/res/values-ja/strings.xml b/service/ServiceConnectivityResources/res/values-ja/strings.xml
index 7bb6f85..0021efa 100644
--- a/service/ServiceConnectivityResources/res/values-ja/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-ja/strings.xml
@@ -22,6 +22,9 @@
     <string name="network_available_sign_in" msgid="2622520134876355561">"ネットワークにログインしてください"</string>
     <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
     <skip />
+    <string name="mobile_network_available_no_internet" msgid="1000871587359324217">"インターネットに接続されていません"</string>
+    <string name="mobile_network_available_no_internet_detailed" msgid="5438738723127062816">"<xliff:g id="NETWORK_CARRIER">%1$s</xliff:g> の使用可能なデータ量が不足している可能性があります。タップするとオプションが表示されます。"</string>
+    <string name="mobile_network_available_no_internet_detailed_unknown_carrier" msgid="5375681117265354337">"使用可能なデータ量が不足している可能性があります。タップするとオプションが表示されます。"</string>
     <string name="wifi_no_internet" msgid="1326348603404555475">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> はインターネットにアクセスできません"</string>
     <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"タップしてその他のオプションを表示"</string>
     <string name="mobile_no_internet" msgid="4087718456753201450">"モバイル ネットワークがインターネットに接続されていません"</string>
diff --git a/service/ServiceConnectivityResources/res/values-ka/strings.xml b/service/ServiceConnectivityResources/res/values-ka/strings.xml
index f42c567..b05d59b 100644
--- a/service/ServiceConnectivityResources/res/values-ka/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-ka/strings.xml
@@ -22,6 +22,9 @@
     <string name="network_available_sign_in" msgid="2622520134876355561">"ქსელში შესვლა"</string>
     <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
     <skip />
+    <string name="mobile_network_available_no_internet" msgid="1000871587359324217">"ინტერნეტი არ არის"</string>
+    <string name="mobile_network_available_no_internet_detailed" msgid="5438738723127062816">"შესაძლოა, დაგიმთავრდათ მობილური ინტერნეტი <xliff:g id="NETWORK_CARRIER">%1$s</xliff:g>-სგან. შეეხეთ ვარიანტების სანახავად."</string>
+    <string name="mobile_network_available_no_internet_detailed_unknown_carrier" msgid="5375681117265354337">"შესაძლოა, მობილური ინტერნეტი დაგიმთავრდათ. შეეხეთ ვარიანტების სანახავად."</string>
     <string name="wifi_no_internet" msgid="1326348603404555475">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g>-ს არ აქვს ინტერნეტზე წვდომა"</string>
     <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"შეეხეთ ვარიანტების სანახავად"</string>
     <string name="mobile_no_internet" msgid="4087718456753201450">"მობილურ ქსელს არ აქვს ინტერნეტზე წვდომა"</string>
diff --git a/service/ServiceConnectivityResources/res/values-kk/strings.xml b/service/ServiceConnectivityResources/res/values-kk/strings.xml
index efe23b6..099e3a6 100644
--- a/service/ServiceConnectivityResources/res/values-kk/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-kk/strings.xml
@@ -22,6 +22,9 @@
     <string name="network_available_sign_in" msgid="2622520134876355561">"Желіге кіру"</string>
     <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
     <skip />
+    <string name="mobile_network_available_no_internet" msgid="1000871587359324217">"Интернет жоқ"</string>
+    <string name="mobile_network_available_no_internet_detailed" msgid="5438738723127062816">"<xliff:g id="NETWORK_CARRIER">%1$s</xliff:g> трафигі таусылған болуы мүмкін. Опцияларды көру үшін түртіңіз."</string>
+    <string name="mobile_network_available_no_internet_detailed_unknown_carrier" msgid="5375681117265354337">"Трафик таусылған болуы мүмкін. Опцияларды көру үшін түртіңіз."</string>
     <string name="wifi_no_internet" msgid="1326348603404555475">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> желісінің интернетті пайдалану мүмкіндігі шектеулі."</string>
     <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"Опциялар үшін түртіңіз"</string>
     <string name="mobile_no_internet" msgid="4087718456753201450">"Мобильдік желі интернетке қосылмаған."</string>
diff --git a/service/ServiceConnectivityResources/res/values-km/strings.xml b/service/ServiceConnectivityResources/res/values-km/strings.xml
index fa06c5b..5ca4f00 100644
--- a/service/ServiceConnectivityResources/res/values-km/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-km/strings.xml
@@ -22,6 +22,9 @@
     <string name="network_available_sign_in" msgid="2622520134876355561">"ចូលទៅបណ្តាញ"</string>
     <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
     <skip />
+    <string name="mobile_network_available_no_internet" msgid="1000871587359324217">"គ្មាន​អ៊ីនធឺណិតទេ"</string>
+    <string name="mobile_network_available_no_internet_detailed" msgid="5438738723127062816">"អ្នកប្រហែលអស់ទិន្នន័យពី <xliff:g id="NETWORK_CARRIER">%1$s</xliff:g> ហើយ។ ចុចដើម្បីទទួលបានជម្រើស។"</string>
+    <string name="mobile_network_available_no_internet_detailed_unknown_carrier" msgid="5375681117265354337">"អ្នកប្រហែលអស់ទិន្នន័យហើយ។ ចុចដើម្បីទទួលបានជម្រើស។"</string>
     <string name="wifi_no_internet" msgid="1326348603404555475">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> មិនមាន​ការតភ្ជាប់អ៊ីនធឺណិត​ទេ"</string>
     <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"ប៉ះសម្រាប់ជម្រើស"</string>
     <string name="mobile_no_internet" msgid="4087718456753201450">"បណ្ដាញ​ទូរសព្ទ​ចល័ត​មិនមានការតភ្ជាប់​អ៊ីនធឺណិតទេ"</string>
diff --git a/service/ServiceConnectivityResources/res/values-kn/strings.xml b/service/ServiceConnectivityResources/res/values-kn/strings.xml
index 98a2d9c..044aff6 100644
--- a/service/ServiceConnectivityResources/res/values-kn/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-kn/strings.xml
@@ -22,6 +22,9 @@
     <string name="network_available_sign_in" msgid="2622520134876355561">"ನೆಟ್‌ವರ್ಕ್‌ಗೆ ಸೈನ್ ಇನ್ ಮಾಡಿ"</string>
     <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
     <skip />
+    <string name="mobile_network_available_no_internet" msgid="1000871587359324217">"ಇಂಟರ್ನೆಟ್ ಇಲ್ಲ"</string>
+    <string name="mobile_network_available_no_internet_detailed" msgid="5438738723127062816">"<xliff:g id="NETWORK_CARRIER">%1$s</xliff:g> ನ ನಿಮ್ಮ ಡೇಟಾ ಮುಗಿದಿರಬಹುದು. ಆಯ್ಕೆಗಳಿಗಾಗಿ ಟ್ಯಾಪ್ ಮಾಡಿ."</string>
+    <string name="mobile_network_available_no_internet_detailed_unknown_carrier" msgid="5375681117265354337">"ನಿಮ್ಮ ಡೇಟಾ ಮುಗಿದಿರಬಹುದು. ಆಯ್ಕೆಗಳಿಗಾಗಿ ಟ್ಯಾಪ್ ಮಾಡಿ."</string>
     <string name="wifi_no_internet" msgid="1326348603404555475">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> ಯಾವುದೇ ಇಂಟರ್ನೆಟ್ ಸಂಪರ್ಕವನ್ನು ಹೊಂದಿಲ್ಲ"</string>
     <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"ಆಯ್ಕೆಗಳಿಗೆ ಟ್ಯಾಪ್ ಮಾಡಿ"</string>
     <string name="mobile_no_internet" msgid="4087718456753201450">"ಮೊಬೈಲ್ ನೆಟ್‌ವರ್ಕ್‌ ಯಾವುದೇ ಇಂಟರ್ನೆಟ್ ಪ್ರವೇಶವನ್ನು ಹೊಂದಿಲ್ಲ"</string>
@@ -30,7 +33,7 @@
     <string name="network_partial_connectivity" msgid="5549503845834993258">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> ಸೀಮಿತ ಸಂಪರ್ಕ ಕಲ್ಪಿಸುವಿಕೆಯನ್ನು ಹೊಂದಿದೆ"</string>
     <string name="network_partial_connectivity_detailed" msgid="4732435946300249845">"ಹೇಗಾದರೂ ಸಂಪರ್ಕಿಸಲು ಟ್ಯಾಪ್ ಮಾಡಿ"</string>
     <string name="network_switch_metered" msgid="5016937523571166319">"<xliff:g id="NETWORK_TYPE">%1$s</xliff:g> ಗೆ ಬದಲಾಯಿಸಲಾಗಿದೆ"</string>
-    <string name="network_switch_metered_detail" msgid="1257300152739542096">"<xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g> ಇಂಟರ್ನೆಟ್ ಪ್ರವೇಶ ಹೊಂದಿಲ್ಲದಿರುವಾಗ, ಸಾಧನವು <xliff:g id="NEW_NETWORK">%1$s</xliff:g> ಬಳಸುತ್ತದೆ. ಶುಲ್ಕಗಳು ಅನ್ವಯವಾಗಬಹುದು."</string>
+    <string name="network_switch_metered_detail" msgid="1257300152739542096">"<xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g> ಇಂಟರ್ನೆಟ್ ಆ್ಯಕ್ಸೆಸ್ ಹೊಂದಿಲ್ಲದಿರುವಾಗ, ಸಾಧನವು <xliff:g id="NEW_NETWORK">%1$s</xliff:g> ಬಳಸುತ್ತದೆ. ಶುಲ್ಕಗಳು ಅನ್ವಯವಾಗಬಹುದು."</string>
     <string name="network_switch_metered_toast" msgid="70691146054130335">"<xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g> ರಿಂದ <xliff:g id="NEW_NETWORK">%2$s</xliff:g> ಗೆ ಬದಲಾಯಿಸಲಾಗಿದೆ"</string>
   <string-array name="network_switch_type_name">
     <item msgid="3004933964374161223">"ಮೊಬೈಲ್ ಡೇಟಾ"</item>
diff --git a/service/ServiceConnectivityResources/res/values-ko/strings.xml b/service/ServiceConnectivityResources/res/values-ko/strings.xml
index eef59a9..1d1e989 100644
--- a/service/ServiceConnectivityResources/res/values-ko/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-ko/strings.xml
@@ -22,6 +22,9 @@
     <string name="network_available_sign_in" msgid="2622520134876355561">"네트워크에 로그인"</string>
     <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
     <skip />
+    <string name="mobile_network_available_no_internet" msgid="1000871587359324217">"인터넷 연결 없음"</string>
+    <string name="mobile_network_available_no_internet_detailed" msgid="5438738723127062816">"<xliff:g id="NETWORK_CARRIER">%1$s</xliff:g>에서 제공하는 데이터를 모두 소진했을 수 있습니다. 탭하여 옵션을 확인하세요."</string>
+    <string name="mobile_network_available_no_internet_detailed_unknown_carrier" msgid="5375681117265354337">"데이터를 모두 소진했을 수 있습니다. 탭하여 옵션을 확인하세요."</string>
     <string name="wifi_no_internet" msgid="1326348603404555475">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g>이(가) 인터넷에 액세스할 수 없습니다."</string>
     <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"탭하여 옵션 보기"</string>
     <string name="mobile_no_internet" msgid="4087718456753201450">"모바일 네트워크에 인터넷이 연결되어 있지 않습니다."</string>
diff --git a/service/ServiceConnectivityResources/res/values-ky/strings.xml b/service/ServiceConnectivityResources/res/values-ky/strings.xml
index 0027c8a..08ffd2a 100644
--- a/service/ServiceConnectivityResources/res/values-ky/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-ky/strings.xml
@@ -22,6 +22,9 @@
     <string name="network_available_sign_in" msgid="2622520134876355561">"Тармакка кирүү"</string>
     <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
     <skip />
+    <string name="mobile_network_available_no_internet" msgid="1000871587359324217">"Интернет жок"</string>
+    <string name="mobile_network_available_no_internet_detailed" msgid="5438738723127062816">"<xliff:g id="NETWORK_CARRIER">%1$s</xliff:g> трафиги түгөнгөн окшойт. Параметрлерди ачуу үчүн таптаңыз."</string>
+    <string name="mobile_network_available_no_internet_detailed_unknown_carrier" msgid="5375681117265354337">"Трафик түгөнгөн окшойт. Параметрлерди ачуу үчүн таптаңыз."</string>
     <string name="wifi_no_internet" msgid="1326348603404555475">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> Интернетке туташуусу жок"</string>
     <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"Параметрлерди ачуу үчүн таптап коюңуз"</string>
     <string name="mobile_no_internet" msgid="4087718456753201450">"Мобилдик Интернет жок"</string>
diff --git a/service/ServiceConnectivityResources/res/values-lo/strings.xml b/service/ServiceConnectivityResources/res/values-lo/strings.xml
index 64419f9..2967441 100644
--- a/service/ServiceConnectivityResources/res/values-lo/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-lo/strings.xml
@@ -22,6 +22,9 @@
     <string name="network_available_sign_in" msgid="2622520134876355561">"ລົງຊື່ເຂົ້າເຄືອຂ່າຍ"</string>
     <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
     <skip />
+    <string name="mobile_network_available_no_internet" msgid="1000871587359324217">"ບໍ່ມີອິນເຕີເນັດ"</string>
+    <string name="mobile_network_available_no_internet_detailed" msgid="5438738723127062816">"ອິນເຕີເນັດຂອງທ່ານຈາກເຄືອຂ່າຍ <xliff:g id="NETWORK_CARRIER">%1$s</xliff:g> ອາດໝົດ. ແຕະເພື່ອເບິ່ງຕົວເລືອກ."</string>
+    <string name="mobile_network_available_no_internet_detailed_unknown_carrier" msgid="5375681117265354337">"ອິນເຕີເນັດຂອງທ່ານອາດໝົດ. ແຕະເພື່ອເບິ່ງຕົວເລືອກ."</string>
     <string name="wifi_no_internet" msgid="1326348603404555475">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> ບໍ່ມີການເຊື່ອມຕໍ່ອິນເຕີເນັດ"</string>
     <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"ແຕະເພື່ອເບິ່ງຕົວເລືອກ"</string>
     <string name="mobile_no_internet" msgid="4087718456753201450">"ເຄືອຂ່າຍມືຖືບໍ່ສາມາດເຂົ້າເຖິງອິນເຕີເນັດໄດ້"</string>
diff --git a/service/ServiceConnectivityResources/res/values-lt/strings.xml b/service/ServiceConnectivityResources/res/values-lt/strings.xml
index f73f142..1f2569d 100644
--- a/service/ServiceConnectivityResources/res/values-lt/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-lt/strings.xml
@@ -22,6 +22,9 @@
     <string name="network_available_sign_in" msgid="2622520134876355561">"Prisijungti prie tinklo"</string>
     <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
     <skip />
+    <string name="mobile_network_available_no_internet" msgid="1000871587359324217">"Nėra interneto ryšio"</string>
+    <string name="mobile_network_available_no_internet_detailed" msgid="5438738723127062816">"Gali būti, kad nebeturite „<xliff:g id="NETWORK_CARRIER">%1$s</xliff:g>“ duomenų. Palieskite, kad būtų rodomos parinktys."</string>
+    <string name="mobile_network_available_no_internet_detailed_unknown_carrier" msgid="5375681117265354337">"Gali būti, kad nebeturite duomenų. Palieskite, kad būtų rodomos parinktys."</string>
     <string name="wifi_no_internet" msgid="1326348603404555475">"„<xliff:g id="NETWORK_SSID">%1$s</xliff:g>“ negali pasiekti interneto"</string>
     <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"Palieskite, kad būtų rodomos parinktys."</string>
     <string name="mobile_no_internet" msgid="4087718456753201450">"Mobiliojo ryšio tinkle nėra prieigos prie interneto"</string>
diff --git a/service/ServiceConnectivityResources/res/values-lv/strings.xml b/service/ServiceConnectivityResources/res/values-lv/strings.xml
index ce063a5..837a21f 100644
--- a/service/ServiceConnectivityResources/res/values-lv/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-lv/strings.xml
@@ -22,6 +22,9 @@
     <string name="network_available_sign_in" msgid="2622520134876355561">"Pierakstīšanās tīklā"</string>
     <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
     <skip />
+    <string name="mobile_network_available_no_internet" msgid="1000871587359324217">"Nav interneta"</string>
+    <string name="mobile_network_available_no_internet_detailed" msgid="5438738723127062816">"Iespējams, ir sasniegts <xliff:g id="NETWORK_CARRIER">%1$s</xliff:g> datu limits. Pieskarieties, lai skatītu iespējas."</string>
+    <string name="mobile_network_available_no_internet_detailed_unknown_carrier" msgid="5375681117265354337">"Iespējams, ir sasniegts datu limits. Pieskarieties, lai skatītu iespējas."</string>
     <string name="wifi_no_internet" msgid="1326348603404555475">"Tīklā <xliff:g id="NETWORK_SSID">%1$s</xliff:g> nav piekļuves internetam"</string>
     <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"Pieskarieties, lai skatītu opcijas."</string>
     <string name="mobile_no_internet" msgid="4087718456753201450">"Mobilajā tīklā nav piekļuves internetam."</string>
diff --git a/service/ServiceConnectivityResources/res/values-mk/strings.xml b/service/ServiceConnectivityResources/res/values-mk/strings.xml
index fb105e0..6fa1563 100644
--- a/service/ServiceConnectivityResources/res/values-mk/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-mk/strings.xml
@@ -22,6 +22,9 @@
     <string name="network_available_sign_in" msgid="2622520134876355561">"Најавете се на мрежа"</string>
     <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
     <skip />
+    <string name="mobile_network_available_no_internet" msgid="1000871587359324217">"Нема интернет"</string>
+    <string name="mobile_network_available_no_internet_detailed" msgid="5438738723127062816">"Можеби се потрошил мобилниот интернет од <xliff:g id="NETWORK_CARRIER">%1$s</xliff:g>. Допрете за опции."</string>
+    <string name="mobile_network_available_no_internet_detailed_unknown_carrier" msgid="5375681117265354337">"Можеби се потрошил мобилниот интернет. Допрете за опции."</string>
     <string name="wifi_no_internet" msgid="1326348603404555475">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> нема интернет-пристап"</string>
     <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"Допрете за опции"</string>
     <string name="mobile_no_internet" msgid="4087718456753201450">"Мобилната мрежа нема интернет-пристап"</string>
diff --git a/service/ServiceConnectivityResources/res/values-ml/strings.xml b/service/ServiceConnectivityResources/res/values-ml/strings.xml
index 9a51238..0797902 100644
--- a/service/ServiceConnectivityResources/res/values-ml/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-ml/strings.xml
@@ -22,6 +22,9 @@
     <string name="network_available_sign_in" msgid="2622520134876355561">"നെറ്റ്‌വർക്കിലേക്ക് സൈൻ ഇൻ ചെയ്യുക"</string>
     <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
     <skip />
+    <string name="mobile_network_available_no_internet" msgid="1000871587359324217">"ഇന്റർനെറ്റ് ഇല്ല"</string>
+    <string name="mobile_network_available_no_internet_detailed" msgid="5438738723127062816">"നിങ്ങളുടെ <xliff:g id="NETWORK_CARRIER">%1$s</xliff:g> എന്നതിലെ ഡാറ്റ തീർന്നിട്ടുണ്ടാകാം. ഓപ്ഷനുകൾക്ക് ടാപ്പ് ചെയ്യുക."</string>
+    <string name="mobile_network_available_no_internet_detailed_unknown_carrier" msgid="5375681117265354337">"നിങ്ങളുടെ ഡാറ്റ തീർന്നിട്ടുണ്ടാകാം. ഓപ്ഷനുകൾക്ക് ടാപ്പ് ചെയ്യുക."</string>
     <string name="wifi_no_internet" msgid="1326348603404555475">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> എന്നതിന് ഇന്റർനെറ്റ് ആക്‌സസ് ഇല്ല"</string>
     <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"ഓപ്ഷനുകൾക്ക് ടാപ്പുചെയ്യുക"</string>
     <string name="mobile_no_internet" msgid="4087718456753201450">"മൊബെെൽ നെറ്റ്‌വർക്കിന് ഇന്റർനെറ്റ് ആക്‌സസ് ഇല്ല"</string>
diff --git a/service/ServiceConnectivityResources/res/values-mn/strings.xml b/service/ServiceConnectivityResources/res/values-mn/strings.xml
index 8372533..2f13ef4 100644
--- a/service/ServiceConnectivityResources/res/values-mn/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-mn/strings.xml
@@ -22,6 +22,9 @@
     <string name="network_available_sign_in" msgid="2622520134876355561">"Сүлжээнд нэвтэрнэ үү"</string>
     <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
     <skip />
+    <string name="mobile_network_available_no_internet" msgid="1000871587359324217">"Интернэт байхгүй"</string>
+    <string name="mobile_network_available_no_internet_detailed" msgid="5438738723127062816">"Таны <xliff:g id="NETWORK_CARRIER">%1$s</xliff:g>-н дата дууссан байж магадгүй. Сонголтыг харахын тулд товшино уу."</string>
+    <string name="mobile_network_available_no_internet_detailed_unknown_carrier" msgid="5375681117265354337">"Таны дата дууссан байж магадгүй. Сонголтыг харахын тулд товшино уу."</string>
     <string name="wifi_no_internet" msgid="1326348603404555475">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g>-д интернэтийн хандалт алга"</string>
     <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"Сонголт хийхийн тулд товшино уу"</string>
     <string name="mobile_no_internet" msgid="4087718456753201450">"Мобайл сүлжээнд интернэт хандалт байхгүй байна"</string>
diff --git a/service/ServiceConnectivityResources/res/values-mr/strings.xml b/service/ServiceConnectivityResources/res/values-mr/strings.xml
index 658b19b..4797ff1 100644
--- a/service/ServiceConnectivityResources/res/values-mr/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-mr/strings.xml
@@ -22,6 +22,9 @@
     <string name="network_available_sign_in" msgid="2622520134876355561">"नेटवर्कवर साइन इन करा"</string>
     <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
     <skip />
+    <string name="mobile_network_available_no_internet" msgid="1000871587359324217">"इंटरनेट नाही"</string>
+    <string name="mobile_network_available_no_internet_detailed" msgid="5438738723127062816">"तुमचा <xliff:g id="NETWORK_CARRIER">%1$s</xliff:g> वरील डेटा संपला असेल. पर्यायांसाठी टॅप करा."</string>
+    <string name="mobile_network_available_no_internet_detailed_unknown_carrier" msgid="5375681117265354337">"तुमचा डेटा संपला असेल. पर्यायांसाठी टॅप करा."</string>
     <string name="wifi_no_internet" msgid="1326348603404555475">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> ला इंटरनेट अ‍ॅक्सेस नाही"</string>
     <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"पर्यायांसाठी टॅप करा"</string>
     <string name="mobile_no_internet" msgid="4087718456753201450">"मोबाइल नेटवर्कला इंटरनेट ॲक्सेस नाही"</string>
diff --git a/service/ServiceConnectivityResources/res/values-ms/strings.xml b/service/ServiceConnectivityResources/res/values-ms/strings.xml
index 84b242c..de38c9c 100644
--- a/service/ServiceConnectivityResources/res/values-ms/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-ms/strings.xml
@@ -22,6 +22,9 @@
     <string name="network_available_sign_in" msgid="2622520134876355561">"Log masuk ke rangkaian"</string>
     <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
     <skip />
+    <string name="mobile_network_available_no_internet" msgid="1000871587359324217">"Tiada Internet"</string>
+    <string name="mobile_network_available_no_internet_detailed" msgid="5438738723127062816">"Anda mungkin kehabisan data daripada <xliff:g id="NETWORK_CARRIER">%1$s</xliff:g>. Ketik untuk melihat pilihan."</string>
+    <string name="mobile_network_available_no_internet_detailed_unknown_carrier" msgid="5375681117265354337">"Anda mungkin kehabisan data. Ketik untuk melihat pilihan."</string>
     <string name="wifi_no_internet" msgid="1326348603404555475">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> tiada akses Internet"</string>
     <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"Ketik untuk mendapatkan pilihan"</string>
     <string name="mobile_no_internet" msgid="4087718456753201450">"Rangkaian mudah alih tiada akses Internet"</string>
diff --git a/service/ServiceConnectivityResources/res/values-my/strings.xml b/service/ServiceConnectivityResources/res/values-my/strings.xml
index 6832263..fce3d58 100644
--- a/service/ServiceConnectivityResources/res/values-my/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-my/strings.xml
@@ -22,6 +22,9 @@
     <string name="network_available_sign_in" msgid="2622520134876355561">"ကွန်ယက်သို့ လက်မှတ်ထိုးဝင်ရန်"</string>
     <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
     <skip />
+    <string name="mobile_network_available_no_internet" msgid="1000871587359324217">"အင်တာနက် မရှိပါ"</string>
+    <string name="mobile_network_available_no_internet_detailed" msgid="5438738723127062816">"<xliff:g id="NETWORK_CARRIER">%1$s</xliff:g> ထံမှ ဒေတာကုန်သွားခြင်း ဖြစ်နိုင်သည်။ ရွေးစရာများကြည့်ရန် တို့ပါ။"</string>
+    <string name="mobile_network_available_no_internet_detailed_unknown_carrier" msgid="5375681117265354337">"ဒေတာကုန်သွားခြင်း ဖြစ်နိုင်သည်။ ရွေးစရာများကြည့်ရန် တို့ပါ။"</string>
     <string name="wifi_no_internet" msgid="1326348603404555475">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> တွင် အင်တာနက်အသုံးပြုခွင့် မရှိပါ"</string>
     <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"အခြားရွေးချယ်စရာများကိုကြည့်ရန် တို့ပါ"</string>
     <string name="mobile_no_internet" msgid="4087718456753201450">"မိုဘိုင်းကွန်ရက်တွင် အင်တာနက်ချိတ်ဆက်မှု မရှိပါ"</string>
diff --git a/service/ServiceConnectivityResources/res/values-nb/strings.xml b/service/ServiceConnectivityResources/res/values-nb/strings.xml
index fff6530..33c30b8 100644
--- a/service/ServiceConnectivityResources/res/values-nb/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-nb/strings.xml
@@ -22,6 +22,9 @@
     <string name="network_available_sign_in" msgid="2622520134876355561">"Logg på nettverk"</string>
     <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
     <skip />
+    <string name="mobile_network_available_no_internet" msgid="1000871587359324217">"Ingen internettilkobling"</string>
+    <string name="mobile_network_available_no_internet_detailed" msgid="5438738723127062816">"Du har kanskje gått tom for data fra <xliff:g id="NETWORK_CARRIER">%1$s</xliff:g>. Trykk for å se alternativer."</string>
+    <string name="mobile_network_available_no_internet_detailed_unknown_carrier" msgid="5375681117265354337">"Du har kanskje gått tom for data. Trykk for å se alternativer."</string>
     <string name="wifi_no_internet" msgid="1326348603404555475">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> har ingen internettilkobling"</string>
     <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"Trykk for å få alternativer"</string>
     <string name="mobile_no_internet" msgid="4087718456753201450">"Mobilnettverket har ingen internettilgang"</string>
diff --git a/service/ServiceConnectivityResources/res/values-ne/strings.xml b/service/ServiceConnectivityResources/res/values-ne/strings.xml
index 2eaf162..a2f9997 100644
--- a/service/ServiceConnectivityResources/res/values-ne/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-ne/strings.xml
@@ -22,6 +22,9 @@
     <string name="network_available_sign_in" msgid="2622520134876355561">"सञ्जालमा साइन इन गर्नुहोस्"</string>
     <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
     <skip />
+    <string name="mobile_network_available_no_internet" msgid="1000871587359324217">"इन्टरनेट छैन"</string>
+    <string name="mobile_network_available_no_internet_detailed" msgid="5438738723127062816">"<xliff:g id="NETWORK_CARRIER">%1$s</xliff:g> को मोबाइल डेटा सकियो होला। विकल्पहरू हेर्न ट्याप गर्नुहोस्।"</string>
+    <string name="mobile_network_available_no_internet_detailed_unknown_carrier" msgid="5375681117265354337">"मोबाइल डेटा सकियो होला। विकल्पहरू हेर्न ट्याप गर्नुहोस्।"</string>
     <string name="wifi_no_internet" msgid="1326348603404555475">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> को इन्टरनेटमाथि पहुँच छैन"</string>
     <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"विकल्पहरूका लागि ट्याप गर्नुहोस्"</string>
     <string name="mobile_no_internet" msgid="4087718456753201450">"मोबाइल नेटवर्कको इन्टरनेटमाथि पहुँच छैन"</string>
diff --git a/service/ServiceConnectivityResources/res/values-nl/strings.xml b/service/ServiceConnectivityResources/res/values-nl/strings.xml
index 394c552..116e255 100644
--- a/service/ServiceConnectivityResources/res/values-nl/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-nl/strings.xml
@@ -22,6 +22,9 @@
     <string name="network_available_sign_in" msgid="2622520134876355561">"Inloggen bij netwerk"</string>
     <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
     <skip />
+    <string name="mobile_network_available_no_internet" msgid="1000871587359324217">"Geen internet"</string>
+    <string name="mobile_network_available_no_internet_detailed" msgid="5438738723127062816">"Je hebt misschien geen data meer van <xliff:g id="NETWORK_CARRIER">%1$s</xliff:g>. Tik voor opties."</string>
+    <string name="mobile_network_available_no_internet_detailed_unknown_carrier" msgid="5375681117265354337">"Je hebt misschien geen data meer. Tik voor opties."</string>
     <string name="wifi_no_internet" msgid="1326348603404555475">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> heeft geen internettoegang"</string>
     <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"Tik voor opties"</string>
     <string name="mobile_no_internet" msgid="4087718456753201450">"Mobiel netwerk heeft geen internettoegang"</string>
diff --git a/service/ServiceConnectivityResources/res/values-or/strings.xml b/service/ServiceConnectivityResources/res/values-or/strings.xml
index 49a773a..d15d42e 100644
--- a/service/ServiceConnectivityResources/res/values-or/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-or/strings.xml
@@ -22,6 +22,9 @@
     <string name="network_available_sign_in" msgid="2622520134876355561">"ନେଟ୍‌ୱର୍କରେ ସାଇନ୍‍ ଇନ୍‍ କରନ୍ତୁ"</string>
     <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
     <skip />
+    <string name="mobile_network_available_no_internet" msgid="1000871587359324217">"ଇଣ୍ଟରନେଟ ନାହିଁ"</string>
+    <string name="mobile_network_available_no_internet_detailed" msgid="5438738723127062816">"ଆପଣଙ୍କ <xliff:g id="NETWORK_CARRIER">%1$s</xliff:g> ଡାଟା ଶେଷ ହୋଇଥାଇପାରେ। ବିକଳ୍ପଗୁଡ଼ିକ ପାଇଁ ଟାପ କରନ୍ତୁ।"</string>
+    <string name="mobile_network_available_no_internet_detailed_unknown_carrier" msgid="5375681117265354337">"ଆପଣଙ୍କ ଡାଟା ଶେଷ ହୋଇଥାଇପାରେ। ବିକଳ୍ପଗୁଡ଼ିକ ପାଇଁ ଟାପ କରନ୍ତୁ।"</string>
     <string name="wifi_no_internet" msgid="1326348603404555475">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g>ର ଇଣ୍ଟର୍ନେଟ୍ ଆକ୍ସେସ୍ ନାହିଁ"</string>
     <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"ବିକଳ୍ପ ପାଇଁ ଟାପ୍‍ କରନ୍ତୁ"</string>
     <string name="mobile_no_internet" msgid="4087718456753201450">"ମୋବାଇଲ୍ ନେଟ୍‌ୱାର୍କରେ ଇଣ୍ଟର୍ନେଟ୍ ଆକ୍ସେସ୍ ନାହିଁ"</string>
diff --git a/service/ServiceConnectivityResources/res/values-pa/strings.xml b/service/ServiceConnectivityResources/res/values-pa/strings.xml
index 9f71cac..103d094 100644
--- a/service/ServiceConnectivityResources/res/values-pa/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-pa/strings.xml
@@ -22,6 +22,9 @@
     <string name="network_available_sign_in" msgid="2622520134876355561">"ਨੈੱਟਵਰਕ \'ਤੇ ਸਾਈਨ-ਇਨ ਕਰੋ"</string>
     <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
     <skip />
+    <string name="mobile_network_available_no_internet" msgid="1000871587359324217">"ਇੰਟਰਨੈੱਟ ਨਹੀਂ ਹੈ"</string>
+    <string name="mobile_network_available_no_internet_detailed" msgid="5438738723127062816">"ਸ਼ਾਇਦ ਤੁਹਾਡੇ <xliff:g id="NETWORK_CARRIER">%1$s</xliff:g> ਦਾ ਡਾਟਾ ਖਤਮ ਹੋ ਗਿਆ ਹੈ। ਵਿਕਲਪਾਂ ਲਈ ਟੈਪ ਕਰੋ।"</string>
+    <string name="mobile_network_available_no_internet_detailed_unknown_carrier" msgid="5375681117265354337">"ਸ਼ਾਇਦ ਤੁਹਾਡਾ ਡਾਟਾ ਖਤਮ ਹੋ ਗਿਆ ਹੈ। ਵਿਕਲਪਾਂ ਲਈ ਟੈਪ ਕਰੋ।"</string>
     <string name="wifi_no_internet" msgid="1326348603404555475">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> ਕੋਲ ਇੰਟਰਨੈੱਟ ਪਹੁੰਚ ਨਹੀਂ ਹੈ"</string>
     <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"ਵਿਕਲਪਾਂ ਲਈ ਟੈਪ ਕਰੋ"</string>
     <string name="mobile_no_internet" msgid="4087718456753201450">"ਮੋਬਾਈਲ ਨੈੱਟਵਰਕ ਕੋਲ ਇੰਟਰਨੈੱਟ ਤੱਕ ਪਹੁੰਚ ਨਹੀਂ ਹੈ"</string>
diff --git a/service/ServiceConnectivityResources/res/values-pl/strings.xml b/service/ServiceConnectivityResources/res/values-pl/strings.xml
index cc84e29..bafa57a 100644
--- a/service/ServiceConnectivityResources/res/values-pl/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-pl/strings.xml
@@ -22,6 +22,9 @@
     <string name="network_available_sign_in" msgid="2622520134876355561">"Zaloguj się do sieci"</string>
     <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
     <skip />
+    <string name="mobile_network_available_no_internet" msgid="1000871587359324217">"Brak internetu"</string>
+    <string name="mobile_network_available_no_internet_detailed" msgid="5438738723127062816">"Prawdopodobnie wyczerpał się pakiet danych z: <xliff:g id="NETWORK_CARRIER">%1$s</xliff:g>. Kliknij, aby wyświetlić opcje."</string>
+    <string name="mobile_network_available_no_internet_detailed_unknown_carrier" msgid="5375681117265354337">"Prawdopodobnie wyczerpał się pakiet danych. Kliknij, aby wyświetlić opcje."</string>
     <string name="wifi_no_internet" msgid="1326348603404555475">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> nie ma dostępu do internetu"</string>
     <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"Kliknij, by wyświetlić opcje"</string>
     <string name="mobile_no_internet" msgid="4087718456753201450">"Sieć komórkowa nie ma dostępu do internetu"</string>
diff --git a/service/ServiceConnectivityResources/res/values-pt-rBR/strings.xml b/service/ServiceConnectivityResources/res/values-pt-rBR/strings.xml
index 3c15a76..a2d5ad2 100644
--- a/service/ServiceConnectivityResources/res/values-pt-rBR/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-pt-rBR/strings.xml
@@ -22,6 +22,9 @@
     <string name="network_available_sign_in" msgid="2622520134876355561">"Fazer login na rede"</string>
     <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
     <skip />
+    <string name="mobile_network_available_no_internet" msgid="1000871587359324217">"Sem Internet"</string>
+    <string name="mobile_network_available_no_internet_detailed" msgid="5438738723127062816">"Os dados da operadora <xliff:g id="NETWORK_CARRIER">%1$s</xliff:g> podem ter acabado. Toque para conferir suas opções."</string>
+    <string name="mobile_network_available_no_internet_detailed_unknown_carrier" msgid="5375681117265354337">"Os dados podem ter acabado. Toque para conferir suas opções."</string>
     <string name="wifi_no_internet" msgid="1326348603404555475">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> não tem acesso à Internet"</string>
     <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"Toque para ver opções"</string>
     <string name="mobile_no_internet" msgid="4087718456753201450">"A rede móvel não tem acesso à Internet"</string>
diff --git a/service/ServiceConnectivityResources/res/values-pt-rPT/strings.xml b/service/ServiceConnectivityResources/res/values-pt-rPT/strings.xml
index 48dde75..05934f3 100644
--- a/service/ServiceConnectivityResources/res/values-pt-rPT/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-pt-rPT/strings.xml
@@ -22,6 +22,9 @@
     <string name="network_available_sign_in" msgid="2622520134876355561">"Início de sessão na rede"</string>
     <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
     <skip />
+    <string name="mobile_network_available_no_internet" msgid="1000871587359324217">"Sem Internet"</string>
+    <string name="mobile_network_available_no_internet_detailed" msgid="5438738723127062816">"É possível que já não tenha dados da <xliff:g id="NETWORK_CARRIER">%1$s</xliff:g>. Toque para aceder às opções."</string>
+    <string name="mobile_network_available_no_internet_detailed_unknown_carrier" msgid="5375681117265354337">"É possível que já não tenha dados. Toque para aceder às opções."</string>
     <string name="wifi_no_internet" msgid="1326348603404555475">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> não tem acesso à Internet"</string>
     <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"Toque para obter mais opções"</string>
     <string name="mobile_no_internet" msgid="4087718456753201450">"A rede móvel não tem acesso à Internet"</string>
diff --git a/service/ServiceConnectivityResources/res/values-pt/strings.xml b/service/ServiceConnectivityResources/res/values-pt/strings.xml
index 3c15a76..a2d5ad2 100644
--- a/service/ServiceConnectivityResources/res/values-pt/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-pt/strings.xml
@@ -22,6 +22,9 @@
     <string name="network_available_sign_in" msgid="2622520134876355561">"Fazer login na rede"</string>
     <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
     <skip />
+    <string name="mobile_network_available_no_internet" msgid="1000871587359324217">"Sem Internet"</string>
+    <string name="mobile_network_available_no_internet_detailed" msgid="5438738723127062816">"Os dados da operadora <xliff:g id="NETWORK_CARRIER">%1$s</xliff:g> podem ter acabado. Toque para conferir suas opções."</string>
+    <string name="mobile_network_available_no_internet_detailed_unknown_carrier" msgid="5375681117265354337">"Os dados podem ter acabado. Toque para conferir suas opções."</string>
     <string name="wifi_no_internet" msgid="1326348603404555475">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> não tem acesso à Internet"</string>
     <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"Toque para ver opções"</string>
     <string name="mobile_no_internet" msgid="4087718456753201450">"A rede móvel não tem acesso à Internet"</string>
diff --git a/service/ServiceConnectivityResources/res/values-ro/strings.xml b/service/ServiceConnectivityResources/res/values-ro/strings.xml
index bf4479a..082bba8 100644
--- a/service/ServiceConnectivityResources/res/values-ro/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-ro/strings.xml
@@ -22,6 +22,9 @@
     <string name="network_available_sign_in" msgid="2622520134876355561">"Conectează-te la rețea"</string>
     <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
     <skip />
+    <string name="mobile_network_available_no_internet" msgid="1000871587359324217">"Fără conexiune la internet"</string>
+    <string name="mobile_network_available_no_internet_detailed" msgid="5438738723127062816">"Este posibil să fi epuizat datele de la <xliff:g id="NETWORK_CARRIER">%1$s</xliff:g>. Atinge pentru opțiuni."</string>
+    <string name="mobile_network_available_no_internet_detailed_unknown_carrier" msgid="5375681117265354337">"Este posibil să fi epuizat datele. Atinge pentru opțiuni."</string>
     <string name="wifi_no_internet" msgid="1326348603404555475">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> nu are acces la internet"</string>
     <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"Atinge pentru opțiuni"</string>
     <string name="mobile_no_internet" msgid="4087718456753201450">"Rețeaua mobilă nu are acces la internet"</string>
diff --git a/service/ServiceConnectivityResources/res/values-ru/strings.xml b/service/ServiceConnectivityResources/res/values-ru/strings.xml
index 2e074ed..3c5b7dd 100644
--- a/service/ServiceConnectivityResources/res/values-ru/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-ru/strings.xml
@@ -22,6 +22,9 @@
     <string name="network_available_sign_in" msgid="2622520134876355561">"Регистрация в сети"</string>
     <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
     <skip />
+    <string name="mobile_network_available_no_internet" msgid="1000871587359324217">"Нет интернета"</string>
+    <string name="mobile_network_available_no_internet_detailed" msgid="5438738723127062816">"Возможно, закончился трафик по тарифу оператора \"<xliff:g id="NETWORK_CARRIER">%1$s</xliff:g>\". Нажмите, чтобы посмотреть варианты дальнейших действий."</string>
+    <string name="mobile_network_available_no_internet_detailed_unknown_carrier" msgid="5375681117265354337">"Возможно, у вас закончился трафик. Нажмите, чтобы посмотреть варианты дальнейших действий."</string>
     <string name="wifi_no_internet" msgid="1326348603404555475">"Сеть \"<xliff:g id="NETWORK_SSID">%1$s</xliff:g>\" не подключена к Интернету"</string>
     <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"Нажмите, чтобы показать варианты."</string>
     <string name="mobile_no_internet" msgid="4087718456753201450">"Мобильная сеть не подключена к Интернету"</string>
diff --git a/service/ServiceConnectivityResources/res/values-si/strings.xml b/service/ServiceConnectivityResources/res/values-si/strings.xml
index a4f720a..70e0252 100644
--- a/service/ServiceConnectivityResources/res/values-si/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-si/strings.xml
@@ -22,6 +22,9 @@
     <string name="network_available_sign_in" msgid="2622520134876355561">"ජාලයට පුරනය වන්න"</string>
     <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
     <skip />
+    <string name="mobile_network_available_no_internet" msgid="1000871587359324217">"අන්තර්ජාලය නැත"</string>
+    <string name="mobile_network_available_no_internet_detailed" msgid="5438738723127062816">"ඔබට <xliff:g id="NETWORK_CARRIER">%1$s</xliff:g> වෙතින් දත්ත අවසන් විය හැක. විකල්ප සඳහා තට්ටු කරන්න."</string>
+    <string name="mobile_network_available_no_internet_detailed_unknown_carrier" msgid="5375681117265354337">"ඔබට දත්ත අවසන් විය හැක. විකල්ප සඳහා තට්ටු කරන්න."</string>
     <string name="wifi_no_internet" msgid="1326348603404555475">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> හට අන්තර්ජාල ප්‍රවේශය නැත"</string>
     <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"විකල්ප සඳහා තට්ටු කරන්න"</string>
     <string name="mobile_no_internet" msgid="4087718456753201450">"ජංගම ජාලවලට අන්තර්ජාල ප්‍රවේශය නැත"</string>
diff --git a/service/ServiceConnectivityResources/res/values-sk/strings.xml b/service/ServiceConnectivityResources/res/values-sk/strings.xml
index 432b670..58b6aab 100644
--- a/service/ServiceConnectivityResources/res/values-sk/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-sk/strings.xml
@@ -22,6 +22,9 @@
     <string name="network_available_sign_in" msgid="2622520134876355561">"Prihlásenie do siete"</string>
     <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
     <skip />
+    <string name="mobile_network_available_no_internet" msgid="1000871587359324217">"Žiadny internet"</string>
+    <string name="mobile_network_available_no_internet_detailed" msgid="5438738723127062816">"Možno vám došli dáta od operátora <xliff:g id="NETWORK_CARRIER">%1$s</xliff:g>. Klepnutím si zobrazíte možnosti."</string>
+    <string name="mobile_network_available_no_internet_detailed_unknown_carrier" msgid="5375681117265354337">"Možno vám došli dáta. Klepnutím si zobrazíte možnosti."</string>
     <string name="wifi_no_internet" msgid="1326348603404555475">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> nemá prístup k internetu"</string>
     <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"Klepnutím získate možnosti"</string>
     <string name="mobile_no_internet" msgid="4087718456753201450">"Mobilná sieť nemá prístup k internetu"</string>
diff --git a/service/ServiceConnectivityResources/res/values-sl/strings.xml b/service/ServiceConnectivityResources/res/values-sl/strings.xml
index b727614..1e28ee8 100644
--- a/service/ServiceConnectivityResources/res/values-sl/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-sl/strings.xml
@@ -22,6 +22,9 @@
     <string name="network_available_sign_in" msgid="2622520134876355561">"Prijava v omrežje"</string>
     <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
     <skip />
+    <string name="mobile_network_available_no_internet" msgid="1000871587359324217">"Ni internetne povezave"</string>
+    <string name="mobile_network_available_no_internet_detailed" msgid="5438738723127062816">"Morda ste že porabili zakupljeno količino prenosa podatkov v mobilnem omrežju <xliff:g id="NETWORK_CARRIER">%1$s</xliff:g>. Dotaknite se za možnosti."</string>
+    <string name="mobile_network_available_no_internet_detailed_unknown_carrier" msgid="5375681117265354337">"Morda ste že porabili zakupljeno količino prenosa podatkov v mobilnem omrežju. Dotaknite se za možnosti."</string>
     <string name="wifi_no_internet" msgid="1326348603404555475">"Omrežje <xliff:g id="NETWORK_SSID">%1$s</xliff:g> nima dostopa do interneta"</string>
     <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"Dotaknite se za možnosti"</string>
     <string name="mobile_no_internet" msgid="4087718456753201450">"Mobilno omrežje nima dostopa do interneta"</string>
diff --git a/service/ServiceConnectivityResources/res/values-sq/strings.xml b/service/ServiceConnectivityResources/res/values-sq/strings.xml
index 85bd84f..48ac926 100644
--- a/service/ServiceConnectivityResources/res/values-sq/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-sq/strings.xml
@@ -22,6 +22,9 @@
     <string name="network_available_sign_in" msgid="2622520134876355561">"Identifikohu në rrjet"</string>
     <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
     <skip />
+    <string name="mobile_network_available_no_internet" msgid="1000871587359324217">"Nuk ka internet"</string>
+    <string name="mobile_network_available_no_internet_detailed" msgid="5438738723127062816">"Mund të të kenë mbaruar të dhënat nga <xliff:g id="NETWORK_CARRIER">%1$s</xliff:g>. Trokit për opsionet."</string>
+    <string name="mobile_network_available_no_internet_detailed_unknown_carrier" msgid="5375681117265354337">"Mund të të kenë mbaruar të dhënat. Trokit për opsionet."</string>
     <string name="wifi_no_internet" msgid="1326348603404555475">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> nuk ka qasje në internet"</string>
     <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"Trokit për opsionet"</string>
     <string name="mobile_no_internet" msgid="4087718456753201450">"Rrjeti celular nuk ka qasje në internet"</string>
diff --git a/service/ServiceConnectivityResources/res/values-sr/strings.xml b/service/ServiceConnectivityResources/res/values-sr/strings.xml
index 928dc79..7bf1bb3 100644
--- a/service/ServiceConnectivityResources/res/values-sr/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-sr/strings.xml
@@ -22,6 +22,9 @@
     <string name="network_available_sign_in" msgid="2622520134876355561">"Пријавите се на мрежу"</string>
     <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
     <skip />
+    <string name="mobile_network_available_no_internet" msgid="1000871587359324217">"Нема интернета"</string>
+    <string name="mobile_network_available_no_internet_detailed" msgid="5438738723127062816">"Можда сте потрошили <xliff:g id="NETWORK_CARRIER">%1$s</xliff:g> податке. Додирните за опције."</string>
+    <string name="mobile_network_available_no_internet_detailed_unknown_carrier" msgid="5375681117265354337">"Можда сте потрошили податке. Додирните за опције."</string>
     <string name="wifi_no_internet" msgid="1326348603404555475">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> нема приступ интернету"</string>
     <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"Додирните за опције"</string>
     <string name="mobile_no_internet" msgid="4087718456753201450">"Мобилна мрежа нема приступ интернету"</string>
diff --git a/service/ServiceConnectivityResources/res/values-sv/strings.xml b/service/ServiceConnectivityResources/res/values-sv/strings.xml
index d714124..61d49e7 100644
--- a/service/ServiceConnectivityResources/res/values-sv/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-sv/strings.xml
@@ -22,6 +22,9 @@
     <string name="network_available_sign_in" msgid="2622520134876355561">"Logga in på nätverket"</string>
     <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
     <skip />
+    <string name="mobile_network_available_no_internet" msgid="1000871587359324217">"Inget internet"</string>
+    <string name="mobile_network_available_no_internet_detailed" msgid="5438738723127062816">"Din data från <xliff:g id="NETWORK_CARRIER">%1$s</xliff:g> kan ha tagit slut. Tryck för alternativ."</string>
+    <string name="mobile_network_available_no_internet_detailed_unknown_carrier" msgid="5375681117265354337">"Din data kan ha tagit slut. Tryck för alternativ."</string>
     <string name="wifi_no_internet" msgid="1326348603404555475">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> har ingen internetanslutning"</string>
     <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"Tryck för alternativ"</string>
     <string name="mobile_no_internet" msgid="4087718456753201450">"Mobilnätverket har ingen internetanslutning"</string>
diff --git a/service/ServiceConnectivityResources/res/values-sw/strings.xml b/service/ServiceConnectivityResources/res/values-sw/strings.xml
index 15d6cab..29ec013 100644
--- a/service/ServiceConnectivityResources/res/values-sw/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-sw/strings.xml
@@ -22,6 +22,9 @@
     <string name="network_available_sign_in" msgid="2622520134876355561">"Ingia katika mtandao"</string>
     <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
     <skip />
+    <string name="mobile_network_available_no_internet" msgid="1000871587359324217">"Hakuna intaneti"</string>
+    <string name="mobile_network_available_no_internet_detailed" msgid="5438738723127062816">"Huenda data ya <xliff:g id="NETWORK_CARRIER">%1$s</xliff:g> imeisha. Gusa ili upate chaguo."</string>
+    <string name="mobile_network_available_no_internet_detailed_unknown_carrier" msgid="5375681117265354337">"Huenda data yako imeisha. Gusa ili upate chaguo."</string>
     <string name="wifi_no_internet" msgid="1326348603404555475">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> haina uwezo wa kufikia intaneti"</string>
     <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"Gusa ili upate chaguo"</string>
     <string name="mobile_no_internet" msgid="4087718456753201450">"Mtandao wa simu hauna uwezo wa kufikia intaneti"</string>
diff --git a/service/ServiceConnectivityResources/res/values-ta/strings.xml b/service/ServiceConnectivityResources/res/values-ta/strings.xml
index 9850a35..3bb82bb 100644
--- a/service/ServiceConnectivityResources/res/values-ta/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-ta/strings.xml
@@ -22,6 +22,9 @@
     <string name="network_available_sign_in" msgid="2622520134876355561">"நெட்வொர்க்கில் உள்நுழையவும்"</string>
     <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
     <skip />
+    <string name="mobile_network_available_no_internet" msgid="1000871587359324217">"இணைய இணைப்பு இல்லை"</string>
+    <string name="mobile_network_available_no_internet_detailed" msgid="5438738723127062816">"<xliff:g id="NETWORK_CARRIER">%1$s</xliff:g> டேட்டா தீர்ந்திருக்கக்கூடும். விருப்பங்களுக்கு தட்டவும்."</string>
+    <string name="mobile_network_available_no_internet_detailed_unknown_carrier" msgid="5375681117265354337">"உங்கள் டேட்டா தீர்ந்திருக்கக்கூடும். விருப்பங்களுக்கு தட்டவும்."</string>
     <string name="wifi_no_internet" msgid="1326348603404555475">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> நெட்வொர்க்கிற்கு இணைய அணுகல் இல்லை"</string>
     <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"விருப்பங்களுக்கு, தட்டவும்"</string>
     <string name="mobile_no_internet" msgid="4087718456753201450">"மொபைல் நெட்வொர்க்கிற்கு இணைய அணுகல் இல்லை"</string>
diff --git a/service/ServiceConnectivityResources/res/values-te/strings.xml b/service/ServiceConnectivityResources/res/values-te/strings.xml
index f7182a8..42e010c 100644
--- a/service/ServiceConnectivityResources/res/values-te/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-te/strings.xml
@@ -22,6 +22,9 @@
     <string name="network_available_sign_in" msgid="2622520134876355561">"నెట్‌వర్క్‌కి సైన్ ఇన్ చేయండి"</string>
     <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
     <skip />
+    <string name="mobile_network_available_no_internet" msgid="1000871587359324217">"ఇంటర్నెట్ లేదు"</string>
+    <string name="mobile_network_available_no_internet_detailed" msgid="5438738723127062816">"మీ <xliff:g id="NETWORK_CARRIER">%1$s</xliff:g> డేటా అయిపోయి ఉండవచ్చు. ఆప్షన్‌ల కోసం ట్యాప్ చేయండి."</string>
+    <string name="mobile_network_available_no_internet_detailed_unknown_carrier" msgid="5375681117265354337">"మీ డేటా అయిపోయి ఉండవచ్చు. ఆప్షన్‌ల కోసం ట్యాప్ చేయండి."</string>
     <string name="wifi_no_internet" msgid="1326348603404555475">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g>కి ఇంటర్నెట్ యాక్సెస్ లేదు"</string>
     <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"ఎంపికల కోసం నొక్కండి"</string>
     <string name="mobile_no_internet" msgid="4087718456753201450">"మొబైల్ నెట్‌వర్క్‌కు ఇంటర్నెట్ యాక్సెస్ లేదు"</string>
diff --git a/service/ServiceConnectivityResources/res/values-th/strings.xml b/service/ServiceConnectivityResources/res/values-th/strings.xml
index 7049309..fe99257 100644
--- a/service/ServiceConnectivityResources/res/values-th/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-th/strings.xml
@@ -22,6 +22,9 @@
     <string name="network_available_sign_in" msgid="2622520134876355561">"ลงชื่อเข้าใช้เครือข่าย"</string>
     <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
     <skip />
+    <string name="mobile_network_available_no_internet" msgid="1000871587359324217">"ไม่มีอินเทอร์เน็ต"</string>
+    <string name="mobile_network_available_no_internet_detailed" msgid="5438738723127062816">"คุณอาจถึงขีดจำกัดการใช้งานอินเทอร์เน็ตของ <xliff:g id="NETWORK_CARRIER">%1$s</xliff:g> แล้ว แตะเพื่อดูตัวเลือก"</string>
+    <string name="mobile_network_available_no_internet_detailed_unknown_carrier" msgid="5375681117265354337">"คุณอาจใช้งานอินเทอร์เน็ตถึงขีดจำกัดแล้ว แตะเพื่อดูตัวเลือก"</string>
     <string name="wifi_no_internet" msgid="1326348603404555475">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> เข้าถึงอินเทอร์เน็ตไม่ได้"</string>
     <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"แตะเพื่อดูตัวเลือก"</string>
     <string name="mobile_no_internet" msgid="4087718456753201450">"เครือข่ายมือถือไม่มีการเข้าถึงอินเทอร์เน็ต"</string>
diff --git a/service/ServiceConnectivityResources/res/values-tl/strings.xml b/service/ServiceConnectivityResources/res/values-tl/strings.xml
index c866fd4..de42455 100644
--- a/service/ServiceConnectivityResources/res/values-tl/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-tl/strings.xml
@@ -22,6 +22,9 @@
     <string name="network_available_sign_in" msgid="2622520134876355561">"Mag-sign in sa network"</string>
     <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
     <skip />
+    <string name="mobile_network_available_no_internet" msgid="1000871587359324217">"Walang internet"</string>
+    <string name="mobile_network_available_no_internet_detailed" msgid="5438738723127062816">"Posibleng ubos na ang data mo sa <xliff:g id="NETWORK_CARRIER">%1$s</xliff:g>. I-tap para sa mga opsyon."</string>
+    <string name="mobile_network_available_no_internet_detailed_unknown_carrier" msgid="5375681117265354337">"Posibleng naubusan ka ng data. I-tap para sa mga opsyon."</string>
     <string name="wifi_no_internet" msgid="1326348603404555475">"Walang access sa internet ang <xliff:g id="NETWORK_SSID">%1$s</xliff:g>"</string>
     <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"I-tap para sa mga opsyon"</string>
     <string name="mobile_no_internet" msgid="4087718456753201450">"Walang access sa internet ang mobile network"</string>
diff --git a/service/ServiceConnectivityResources/res/values-tr/strings.xml b/service/ServiceConnectivityResources/res/values-tr/strings.xml
index c4930a8..c65b210 100644
--- a/service/ServiceConnectivityResources/res/values-tr/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-tr/strings.xml
@@ -22,6 +22,9 @@
     <string name="network_available_sign_in" msgid="2622520134876355561">"Ağda oturum açın"</string>
     <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
     <skip />
+    <string name="mobile_network_available_no_internet" msgid="1000871587359324217">"İnternet bağlantısı yok"</string>
+    <string name="mobile_network_available_no_internet_detailed" msgid="5438738723127062816">"<xliff:g id="NETWORK_CARRIER">%1$s</xliff:g> veri paketiniz tükenmiş olabilir. Seçenekler için dokunun."</string>
+    <string name="mobile_network_available_no_internet_detailed_unknown_carrier" msgid="5375681117265354337">"Veri paketiniz tükenmiş olabilir. Seçenekler için dokunun."</string>
     <string name="wifi_no_internet" msgid="1326348603404555475">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> ağının internet bağlantısı yok"</string>
     <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"Seçenekler için dokunun"</string>
     <string name="mobile_no_internet" msgid="4087718456753201450">"Mobil ağın internet bağlantısı yok"</string>
diff --git a/service/ServiceConnectivityResources/res/values-uk/strings.xml b/service/ServiceConnectivityResources/res/values-uk/strings.xml
index 8811263..5950a24 100644
--- a/service/ServiceConnectivityResources/res/values-uk/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-uk/strings.xml
@@ -22,6 +22,9 @@
     <string name="network_available_sign_in" msgid="2622520134876355561">"Вхід у мережу"</string>
     <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
     <skip />
+    <string name="mobile_network_available_no_internet" msgid="1000871587359324217">"Немає Інтернету"</string>
+    <string name="mobile_network_available_no_internet_detailed" msgid="5438738723127062816">"Можливо, обсяг мобільного трафіку від <xliff:g id="NETWORK_CARRIER">%1$s</xliff:g> вичерпано. Торкніться, щоб відкрити опції."</string>
+    <string name="mobile_network_available_no_internet_detailed_unknown_carrier" msgid="5375681117265354337">"Можливо, обсяг мобільного трафіку вичерпано. Торкніться, щоб відкрити опції."</string>
     <string name="wifi_no_internet" msgid="1326348603404555475">"Мережа <xliff:g id="NETWORK_SSID">%1$s</xliff:g> не має доступу до Інтернету"</string>
     <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"Торкніться, щоб відкрити опції"</string>
     <string name="mobile_no_internet" msgid="4087718456753201450">"Мобільна мережа не має доступу до Інтернету"</string>
diff --git a/service/ServiceConnectivityResources/res/values-ur/strings.xml b/service/ServiceConnectivityResources/res/values-ur/strings.xml
index 8f9656c..6af95b0 100644
--- a/service/ServiceConnectivityResources/res/values-ur/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-ur/strings.xml
@@ -22,6 +22,9 @@
     <string name="network_available_sign_in" msgid="2622520134876355561">"نیٹ ورک میں سائن ان کریں"</string>
     <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
     <skip />
+    <string name="mobile_network_available_no_internet" msgid="1000871587359324217">"انٹرنیٹ نہیں ہے"</string>
+    <string name="mobile_network_available_no_internet_detailed" msgid="5438738723127062816">"ہو سکتا ہے کہ آپ کے <xliff:g id="NETWORK_CARRIER">%1$s</xliff:g> کا ڈیٹا ختم ہو جائے۔ اختیارات کے لیے تھپتھپائیں۔"</string>
+    <string name="mobile_network_available_no_internet_detailed_unknown_carrier" msgid="5375681117265354337">"ہو سکتا ہے کہ آپ کا ڈیٹا ختم ہو جائے۔ اختیارات کے لیے تھپتھپائیں۔"</string>
     <string name="wifi_no_internet" msgid="1326348603404555475">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> کو انٹرنیٹ تک رسائی حاصل نہیں ہے"</string>
     <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"اختیارات کیلئے تھپتھپائیں"</string>
     <string name="mobile_no_internet" msgid="4087718456753201450">"موبائل نیٹ ورک کو انٹرنیٹ تک رسائی حاصل نہیں ہے"</string>
diff --git a/service/ServiceConnectivityResources/res/values-uz/strings.xml b/service/ServiceConnectivityResources/res/values-uz/strings.xml
index d7285ad..29bc99e 100644
--- a/service/ServiceConnectivityResources/res/values-uz/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-uz/strings.xml
@@ -22,6 +22,9 @@
     <string name="network_available_sign_in" msgid="2622520134876355561">"Tarmoqqa kirish"</string>
     <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
     <skip />
+    <string name="mobile_network_available_no_internet" msgid="1000871587359324217">"Internet yoʻq"</string>
+    <string name="mobile_network_available_no_internet_detailed" msgid="5438738723127062816">"<xliff:g id="NETWORK_CARRIER">%1$s</xliff:g> tarifida mobil trafik qolmagan boʻlishi mumkin. Yechim olish uchun bosing."</string>
+    <string name="mobile_network_available_no_internet_detailed_unknown_carrier" msgid="5375681117265354337">"Internet trafik qolmagan boʻlishi mumkin. Yechim olish uchun bosing."</string>
     <string name="wifi_no_internet" msgid="1326348603404555475">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> nomli tarmoqda internetga ruxsati yoʻq"</string>
     <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"Variantlarni ko‘rsatish uchun bosing"</string>
     <string name="mobile_no_internet" msgid="4087718456753201450">"Mobil tarmoq internetga ulanmagan"</string>
diff --git a/service/ServiceConnectivityResources/res/values-vi/strings.xml b/service/ServiceConnectivityResources/res/values-vi/strings.xml
index 239fb81..42d168e 100644
--- a/service/ServiceConnectivityResources/res/values-vi/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-vi/strings.xml
@@ -22,6 +22,9 @@
     <string name="network_available_sign_in" msgid="2622520134876355561">"Đăng nhập vào mạng"</string>
     <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
     <skip />
+    <string name="mobile_network_available_no_internet" msgid="1000871587359324217">"Không có kết nối Internet"</string>
+    <string name="mobile_network_available_no_internet_detailed" msgid="5438738723127062816">"Có thể bạn đã dùng hết dữ liệu của <xliff:g id="NETWORK_CARRIER">%1$s</xliff:g>. Hãy nhấn để xem các lựa chọn."</string>
+    <string name="mobile_network_available_no_internet_detailed_unknown_carrier" msgid="5375681117265354337">"Có thể bạn đã dùng hết dữ liệu. Hãy nhấn để xem các lựa chọn."</string>
     <string name="wifi_no_internet" msgid="1326348603404555475">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> không có quyền truy cập Internet"</string>
     <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"Nhấn để biết tùy chọn"</string>
     <string name="mobile_no_internet" msgid="4087718456753201450">"Mạng di động không có quyền truy cập Internet"</string>
diff --git a/service/ServiceConnectivityResources/res/values-zh-rCN/strings.xml b/service/ServiceConnectivityResources/res/values-zh-rCN/strings.xml
index e318c0b..07271bf 100644
--- a/service/ServiceConnectivityResources/res/values-zh-rCN/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-zh-rCN/strings.xml
@@ -22,6 +22,9 @@
     <string name="network_available_sign_in" msgid="2622520134876355561">"登录到网络"</string>
     <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
     <skip />
+    <string name="mobile_network_available_no_internet" msgid="1000871587359324217">"未连接到互联网"</string>
+    <string name="mobile_network_available_no_internet_detailed" msgid="5438738723127062816">"<xliff:g id="NETWORK_CARRIER">%1$s</xliff:g> 提供的数据流量可能已用尽。点按即可查看选项。"</string>
+    <string name="mobile_network_available_no_internet_detailed_unknown_carrier" msgid="5375681117265354337">"您的数据流量可能已用尽。点按即可查看选项。"</string>
     <string name="wifi_no_internet" msgid="1326348603404555475">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> 无法访问互联网"</string>
     <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"点按即可查看相关选项"</string>
     <string name="mobile_no_internet" msgid="4087718456753201450">"此移动网络无法访问互联网"</string>
diff --git a/service/ServiceConnectivityResources/res/values-zh-rHK/strings.xml b/service/ServiceConnectivityResources/res/values-zh-rHK/strings.xml
index af3dccd..92c605b 100644
--- a/service/ServiceConnectivityResources/res/values-zh-rHK/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-zh-rHK/strings.xml
@@ -22,6 +22,9 @@
     <string name="network_available_sign_in" msgid="2622520134876355561">"登入網絡"</string>
     <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
     <skip />
+    <string name="mobile_network_available_no_internet" msgid="1000871587359324217">"沒有互聯網連線"</string>
+    <string name="mobile_network_available_no_internet_detailed" msgid="5438738723127062816">"你的「<xliff:g id="NETWORK_CARRIER">%1$s</xliff:g>」數據可能已用完。輕按即可查看選項。"</string>
+    <string name="mobile_network_available_no_internet_detailed_unknown_carrier" msgid="5375681117265354337">"你的數據可能已用完。輕按即可查看選項。"</string>
     <string name="wifi_no_internet" msgid="1326348603404555475">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g>未有連接至互聯網"</string>
     <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"輕按即可查看選項"</string>
     <string name="mobile_no_internet" msgid="4087718456753201450">"流動網絡並未連接互聯網"</string>
diff --git a/service/ServiceConnectivityResources/res/values-zh-rTW/strings.xml b/service/ServiceConnectivityResources/res/values-zh-rTW/strings.xml
index 6441707..81f9ddb 100644
--- a/service/ServiceConnectivityResources/res/values-zh-rTW/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-zh-rTW/strings.xml
@@ -22,6 +22,9 @@
     <string name="network_available_sign_in" msgid="2622520134876355561">"登入網路"</string>
     <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
     <skip />
+    <string name="mobile_network_available_no_internet" msgid="1000871587359324217">"沒有網際網路連線"</string>
+    <string name="mobile_network_available_no_internet_detailed" msgid="5438738723127062816">"<xliff:g id="NETWORK_CARRIER">%1$s</xliff:g> 提供的數據流量可能已用盡。輕觸即可查看選項。"</string>
+    <string name="mobile_network_available_no_internet_detailed_unknown_carrier" msgid="5375681117265354337">"數據流量可能已用盡。輕觸即可查看選項。"</string>
     <string name="wifi_no_internet" msgid="1326348603404555475">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> 沒有網際網路連線"</string>
     <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"輕觸即可查看選項"</string>
     <string name="mobile_no_internet" msgid="4087718456753201450">"這個行動網路沒有網際網路連線"</string>
diff --git a/service/ServiceConnectivityResources/res/values-zu/strings.xml b/service/ServiceConnectivityResources/res/values-zu/strings.xml
index b59f0d1..0338484 100644
--- a/service/ServiceConnectivityResources/res/values-zu/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-zu/strings.xml
@@ -22,6 +22,9 @@
     <string name="network_available_sign_in" msgid="2622520134876355561">"Ngena ngemvume kunethiwekhi"</string>
     <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
     <skip />
+    <string name="mobile_network_available_no_internet" msgid="1000871587359324217">"Ayikho i-inthanethi"</string>
+    <string name="mobile_network_available_no_internet_detailed" msgid="5438738723127062816">"Kungenzeka uphelelwe idatha evela ku-<xliff:g id="NETWORK_CARRIER">%1$s</xliff:g>. Thepha ukuze uthole okungakhethwa kukho."</string>
+    <string name="mobile_network_available_no_internet_detailed_unknown_carrier" msgid="5375681117265354337">"Kungenzeka uphelelwe yidatha. Thepha ukuze uthole okungakhethwa kukho."</string>
     <string name="wifi_no_internet" msgid="1326348603404555475">"I-<xliff:g id="NETWORK_SSID">%1$s</xliff:g> ayinakho ukufinyelela kwe-inthanethi"</string>
     <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"Thepha ukuze uthole izinketho"</string>
     <string name="mobile_no_internet" msgid="4087718456753201450">"Inethiwekhi yeselula ayinakho ukufinyelela kwe-inthanethi"</string>
diff --git a/service/ServiceConnectivityResources/res/values/config.xml b/service/ServiceConnectivityResources/res/values/config.xml
index 6f9d46f..2d3647a 100644
--- a/service/ServiceConnectivityResources/res/values/config.xml
+++ b/service/ServiceConnectivityResources/res/values/config.xml
@@ -128,6 +128,13 @@
     <string-array translatable="false" name="config_networkNotifySwitches">
     </string-array>
 
+    <!-- An array of priorities of service types for services to be offloaded via
+         NsdManager#registerOffloadEngine.
+         Format is [priority int]:[service type], for example: "0:_testservice._tcp"
+    -->
+    <string-array translatable="false" name="config_nsdOffloadServicesPriority">
+    </string-array>
+
     <!-- Whether to use an ongoing notification for signing in to captive portals, instead of a
          notification that can be dismissed. -->
     <bool name="config_ongoingSignInNotification">false</bool>
diff --git a/service/ServiceConnectivityResources/res/values/config_thread.xml b/service/ServiceConnectivityResources/res/values/config_thread.xml
index 14b5427..f7e47f5 100644
--- a/service/ServiceConnectivityResources/res/values/config_thread.xml
+++ b/service/ServiceConnectivityResources/res/values/config_thread.xml
@@ -20,10 +20,15 @@
 -->
 
 <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- Sets to {@code true} to enable Thread on the device by default. Note this is the default
+    value, the actual Thread enabled state can be changed by the {@link
+    ThreadNetworkController#setEnabled} API.
+    -->
+    <bool name="config_thread_default_enabled">true</bool>
+
     <!-- Whether to use location APIs in the algorithm to determine country code or not.
     If disabled, will use other sources (telephony, wifi, etc) to determine device location for
     Thread Network regulatory purposes.
     -->
     <bool name="config_thread_location_use_for_country_code_enabled">true</bool>
-
 </resources>
diff --git a/service/ServiceConnectivityResources/res/values/overlayable.xml b/service/ServiceConnectivityResources/res/values/overlayable.xml
index 1c07599..d9af5a3 100644
--- a/service/ServiceConnectivityResources/res/values/overlayable.xml
+++ b/service/ServiceConnectivityResources/res/values/overlayable.xml
@@ -31,6 +31,7 @@
             <item type="integer" name="config_networkWakeupPacketMask"/>
             <item type="integer" name="config_networkNotifySwitchType"/>
             <item type="array" name="config_networkNotifySwitches"/>
+            <item type="array" name="config_nsdOffloadServicesPriority"/>
             <item type="bool" name="config_ongoingSignInNotification"/>
             <item type="bool" name="config_autoCancelNetworkNotifications"/>
             <item type="bool" name="config_notifyNoInternetAsDialogWhenHighPriority"/>
@@ -45,6 +46,7 @@
             <item type="integer" name="config_netstats_validate_import" />
 
             <!-- Configuration values for ThreadNetworkService -->
+            <item type="bool" name="config_thread_default_enabled" />
             <item type="bool" name="config_thread_location_use_for_country_code_enabled" />
         </policy>
     </overlayable>
diff --git a/service/libconnectivity/Android.bp b/service/libconnectivity/Android.bp
index e063af7..3a72134 100644
--- a/service/libconnectivity/Android.bp
+++ b/service/libconnectivity/Android.bp
@@ -15,6 +15,7 @@
 //
 
 package {
+    default_team: "trendy_team_fwk_core_networking",
     // See: http://go/android-license-faq
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
diff --git a/service/lint-baseline.xml b/service/lint-baseline.xml
index 5149e6d..3e11d52 100644
--- a/service/lint-baseline.xml
+++ b/service/lint-baseline.xml
@@ -1,5 +1,82 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 8.0.0-dev" type="baseline" dependencies="true" variant="all" version="8.0.0-dev">
+<issues format="6" by="lint 8.4.0-alpha01" type="baseline" client="" dependencies="true" name="" variant="all" version="8.4.0-alpha01">
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 33 (current min is 30): `getUidRule`"
+        errorLine1="        return BpfNetMapsReader.getUidRule(sUidOwnerMap, childChain, uid);"
+        errorLine2="                                ~~~~~~~~~~">
+        <location
+            file="packages/modules/Connectivity/service/src/com/android/server/BpfNetMaps.java"
+            line="643"
+            column="33"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 31 (current min is 30): `BpfBitmap`"
+        errorLine1="                return new BpfBitmap(BLOCKED_PORTS_MAP_PATH);"
+        errorLine2="                       ~~~~~~~~~~~~~">
+        <location
+            file="packages/modules/Connectivity/service/src/com/android/server/connectivity/ConnectivityNativeService.java"
+            line="61"
+            column="24"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 31 (current min is 30): `set`"
+        errorLine1="            mBpfBlockedPortsMap.set(port);"
+        errorLine2="                                ~~~">
+        <location
+            file="packages/modules/Connectivity/service/src/com/android/server/connectivity/ConnectivityNativeService.java"
+            line="96"
+            column="33"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 31 (current min is 30): `unset`"
+        errorLine1="            mBpfBlockedPortsMap.unset(port);"
+        errorLine2="                                ~~~~~">
+        <location
+            file="packages/modules/Connectivity/service/src/com/android/server/connectivity/ConnectivityNativeService.java"
+            line="107"
+            column="33"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 31 (current min is 30): `clear`"
+        errorLine1="            mBpfBlockedPortsMap.clear();"
+        errorLine2="                                ~~~~~">
+        <location
+            file="packages/modules/Connectivity/service/src/com/android/server/connectivity/ConnectivityNativeService.java"
+            line="118"
+            column="33"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 31 (current min is 30): `get`"
+        errorLine1="                if (mBpfBlockedPortsMap.get(i)) portMap.add(i);"
+        errorLine2="                                        ~~~">
+        <location
+            file="packages/modules/Connectivity/service/src/com/android/server/connectivity/ConnectivityNativeService.java"
+            line="131"
+            column="41"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 31 (current min is 30): `android.os.BatteryStatsManager#reportNetworkInterfaceForTransports`"
+        errorLine1="            batteryStats.reportNetworkInterfaceForTransports(iface, transportTypes);"
+        errorLine2="                         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="packages/modules/Connectivity/service/src/com/android/server/ConnectivityService.java"
+            line="1447"
+            column="26"/>
+    </issue>
 
     <issue
         id="NewApi"
@@ -8,23 +85,562 @@
         errorLine2="                     ~~~~~~~~~~~~~~~~~~">
         <location
             file="packages/modules/Connectivity/service/src/com/android/server/ConnectivityService.java"
-            line="1358"
+            line="1458"
             column="22"/>
     </issue>
 
     <issue
         id="NewApi"
+        message="Call requires API level 33 (current min is 30): `getProgramId`"
+        errorLine1="            return BpfUtils.getProgramId(attachType);"
+        errorLine2="                            ~~~~~~~~~~~~">
+        <location
+            file="packages/modules/Connectivity/service/src/com/android/server/ConnectivityService.java"
+            line="1572"
+            column="29"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Class requires API level 31 (current min is 30): `android.net.NetworkPolicyManager`"
+        errorLine1="        mPolicyManager = mContext.getSystemService(NetworkPolicyManager.class);"
+        errorLine2="                                                   ~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="packages/modules/Connectivity/service/src/com/android/server/ConnectivityService.java"
+            line="1740"
+            column="52"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 31 (current min is 30): `android.net.NetworkPolicyManager#registerNetworkPolicyCallback`"
+        errorLine1="        mPolicyManager.registerNetworkPolicyCallback(null, mPolicyCallback);"
+        errorLine2="                       ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="packages/modules/Connectivity/service/src/com/android/server/ConnectivityService.java"
+            line="1753"
+            column="24"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Cast to `UidFrozenStateChangedCallback` requires API level 34 (current min is 30)"
+        errorLine1="                    new UidFrozenStateChangedCallback() {"
+        errorLine2="                    ^">
+        <location
+            file="packages/modules/Connectivity/service/src/com/android/server/ConnectivityService.java"
+            line="1888"
+            column="21"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Class requires API level 34 (current min is 30): `android.app.ActivityManager.UidFrozenStateChangedCallback`"
+        errorLine1="                    new UidFrozenStateChangedCallback() {"
+        errorLine2="                        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="packages/modules/Connectivity/service/src/com/android/server/ConnectivityService.java"
+            line="1888"
+            column="25"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 34 (current min is 30): `android.app.ActivityManager#registerUidFrozenStateChangedCallback`"
+        errorLine1="            activityManager.registerUidFrozenStateChangedCallback("
+        errorLine2="                            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="packages/modules/Connectivity/service/src/com/android/server/ConnectivityService.java"
+            line="1907"
+            column="29"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 31 (current min is 30): `android.net.NetworkPolicyManager#isUidNetworkingBlocked`"
+        errorLine1="            return mPolicyManager.isUidNetworkingBlocked(uid, metered);"
+        errorLine2="                                  ~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="packages/modules/Connectivity/service/src/com/android/server/ConnectivityService.java"
+            line="2162"
+            column="35"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 31 (current min is 30): `android.net.NetworkPolicyManager#getRestrictBackgroundStatus`"
+        errorLine1="            return mPolicyManager.getRestrictBackgroundStatus(callerUid);"
+        errorLine2="                                  ~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="packages/modules/Connectivity/service/src/com/android/server/ConnectivityService.java"
+            line="2947"
+            column="35"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 31 (current min is 30): `android.net.NetworkStateSnapshot#getNetwork`"
+        errorLine1="            final NetworkAgentInfo nai = getNetworkAgentInfoForNetwork(snapshot.getNetwork());"
+        errorLine2="                                                                                ~~~~~~~~~~">
+        <location
+            file="packages/modules/Connectivity/service/src/com/android/server/ConnectivityService.java"
+            line="2963"
+            column="81"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 31 (current min is 30): `android.net.NetworkStateSnapshot#getLinkProperties`"
+        errorLine1="                        snapshot.getLinkProperties(), snapshot.getNetworkCapabilities(),"
+        errorLine2="                                 ~~~~~~~~~~~~~~~~~">
+        <location
+            file="packages/modules/Connectivity/service/src/com/android/server/ConnectivityService.java"
+            line="2966"
+            column="34"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 31 (current min is 30): `android.net.NetworkStateSnapshot#getNetworkCapabilities`"
+        errorLine1="                        snapshot.getLinkProperties(), snapshot.getNetworkCapabilities(),"
+        errorLine2="                                                               ~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="packages/modules/Connectivity/service/src/com/android/server/ConnectivityService.java"
+            line="2966"
+            column="64"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 31 (current min is 30): `android.net.NetworkStateSnapshot#getNetwork`"
+        errorLine1="                        snapshot.getNetwork(), snapshot.getSubscriberId()));"
+        errorLine2="                                 ~~~~~~~~~~">
+        <location
+            file="packages/modules/Connectivity/service/src/com/android/server/ConnectivityService.java"
+            line="2967"
+            column="34"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 31 (current min is 30): `android.net.NetworkStateSnapshot#getSubscriberId`"
+        errorLine1="                        snapshot.getNetwork(), snapshot.getSubscriberId()));"
+        errorLine2="                                                        ~~~~~~~~~~~~~~~">
+        <location
+            file="packages/modules/Connectivity/service/src/com/android/server/ConnectivityService.java"
+            line="2967"
+            column="57"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Class requires API level 31 (current min is 30): `android.net.NetworkPolicyManager.NetworkPolicyCallback`"
+        errorLine1="    private final NetworkPolicyCallback mPolicyCallback = new NetworkPolicyCallback() {"
+        errorLine2="                                                              ~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="packages/modules/Connectivity/service/src/com/android/server/ConnectivityService.java"
+            line="3210"
+            column="63"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 33 (current min is 30): `dump`"
+        errorLine1="            mBpfNetMaps.dump(pw, fd, verbose);"
+        errorLine2="                        ~~~~">
+        <location
+            file="packages/modules/Connectivity/service/src/com/android/server/ConnectivityService.java"
+            line="4155"
+            column="25"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 31 (current min is 30): `android.os.Build#isDebuggable`"
+        errorLine1="        if (!Build.isDebuggable()) {"
+        errorLine2="                   ~~~~~~~~~~~~">
+        <location
+            file="packages/modules/Connectivity/service/src/com/android/server/ConnectivityService.java"
+            line="5721"
+            column="20"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Class requires API level 31 (current min is 30): `android.net.NetworkPolicyManager`"
+        errorLine1="                 mContext.getSystemService(NetworkPolicyManager.class);"
+        errorLine2="                                           ~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="packages/modules/Connectivity/service/src/com/android/server/ConnectivityService.java"
+            line="6174"
+            column="44"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 31 (current min is 30): `android.net.NetworkPolicyManager#getMultipathPreference`"
+        errorLine1="            networkPreference = netPolicyManager.getMultipathPreference(network);"
+        errorLine2="                                                 ~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="packages/modules/Connectivity/service/src/com/android/server/ConnectivityService.java"
+            line="6179"
+            column="50"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 31 (current min is 30): `new android.net.UnderlyingNetworkInfo`"
+        errorLine1="        return new UnderlyingNetworkInfo(nai.networkCapabilities.getOwnerUid(),"
+        errorLine2="               ~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="packages/modules/Connectivity/service/src/com/android/server/ConnectivityService.java"
+            line="6819"
+            column="16"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 31 (current min is 30): `android.net.NetworkPolicyManager#isUidRestrictedOnMeteredNetworks`"
+        errorLine1="            if (mPolicyManager.isUidRestrictedOnMeteredNetworks(uid)) {"
+        errorLine2="                               ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="packages/modules/Connectivity/service/src/com/android/server/ConnectivityService.java"
+            line="7822"
+            column="32"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 31 (current min is 30): `android.os.Build#isDebuggable`"
+        errorLine1="            if (Build.isDebuggable()) {"
+        errorLine2="                      ~~~~~~~~~~~~">
+        <location
+            file="packages/modules/Connectivity/service/src/com/android/server/ConnectivityService.java"
+            line="9943"
+            column="23"/>
+    </issue>
+
+    <issue
+        id="NewApi"
         message="Call requires API level 31 (current min is 30): `android.app.usage.NetworkStatsManager#notifyNetworkStatus`"
         errorLine1="            mStatsManager.notifyNetworkStatus(getDefaultNetworks(),"
         errorLine2="                          ~~~~~~~~~~~~~~~~~~~">
         <location
             file="packages/modules/Connectivity/service/src/com/android/server/ConnectivityService.java"
-            line="9938"
+            line="10909"
             column="27"/>
     </issue>
 
     <issue
         id="NewApi"
+        message="Call requires API level 31 (current min is 30): `libcore.io.IoUtils#closeQuietly`"
+        errorLine1="                IoUtils.closeQuietly(pfd);"
+        errorLine2="                        ~~~~~~~~~~~~">
+        <location
+            file="packages/modules/Connectivity/service/src/com/android/server/ConnectivityService.java"
+            line="10962"
+            column="25"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 31 (current min is 30): `libcore.io.IoUtils#closeQuietly`"
+        errorLine1="                IoUtils.closeQuietly(pfd);"
+        errorLine2="                        ~~~~~~~~~~~~">
+        <location
+            file="packages/modules/Connectivity/service/src/com/android/server/ConnectivityService.java"
+            line="10979"
+            column="25"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Class requires API level 31 (current min is 30): `android.net.NetworkWatchlistManager`"
+        errorLine1="        NetworkWatchlistManager nwm = mContext.getSystemService(NetworkWatchlistManager.class);"
+        errorLine2="                                                                ~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="packages/modules/Connectivity/service/src/com/android/server/ConnectivityService.java"
+            line="11035"
+            column="65"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 31 (current min is 30): `android.net.NetworkWatchlistManager#getWatchlistConfigHash`"
+        errorLine1="        return nwm.getWatchlistConfigHash();"
+        errorLine2="                   ~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="packages/modules/Connectivity/service/src/com/android/server/ConnectivityService.java"
+            line="11041"
+            column="20"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 33 (current min is 30): `getProgramId`"
+        errorLine1="                        final int ret = BpfUtils.getProgramId(type);"
+        errorLine2="                                                 ~~~~~~~~~~~~">
+        <location
+            file="packages/modules/Connectivity/service/src/com/android/server/ConnectivityService.java"
+            line="11180"
+            column="50"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 31 (current min is 30): `android.os.BatteryStatsManager#reportMobileRadioPowerState`"
+        errorLine1="                    bs.reportMobileRadioPowerState(isActive, uid);"
+        errorLine2="                       ~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="packages/modules/Connectivity/service/src/com/android/server/ConnectivityService.java"
+            line="12254"
+            column="24"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 31 (current min is 30): `android.os.BatteryStatsManager#reportWifiRadioPowerState`"
+        errorLine1="                    bs.reportWifiRadioPowerState(isActive, uid);"
+        errorLine2="                       ~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="packages/modules/Connectivity/service/src/com/android/server/ConnectivityService.java"
+            line="12257"
+            column="24"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 33 (current min is 30): `addNiceApp`"
+        errorLine1="                mBpfNetMaps.addNiceApp(uid);"
+        errorLine2="                            ~~~~~~~~~~">
+        <location
+            file="packages/modules/Connectivity/service/src/com/android/server/ConnectivityService.java"
+            line="13079"
+            column="29"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 33 (current min is 30): `removeNiceApp`"
+        errorLine1="                mBpfNetMaps.removeNiceApp(uid);"
+        errorLine2="                            ~~~~~~~~~~~~~">
+        <location
+            file="packages/modules/Connectivity/service/src/com/android/server/ConnectivityService.java"
+            line="13081"
+            column="29"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 33 (current min is 30): `addNaughtyApp`"
+        errorLine1="                mBpfNetMaps.addNaughtyApp(uid);"
+        errorLine2="                            ~~~~~~~~~~~~~">
+        <location
+            file="packages/modules/Connectivity/service/src/com/android/server/ConnectivityService.java"
+            line="13094"
+            column="29"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 33 (current min is 30): `removeNaughtyApp`"
+        errorLine1="                mBpfNetMaps.removeNaughtyApp(uid);"
+        errorLine2="                            ~~~~~~~~~~~~~~~~">
+        <location
+            file="packages/modules/Connectivity/service/src/com/android/server/ConnectivityService.java"
+            line="13096"
+            column="29"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 31 (current min is 30): `android.os.UserHandle#getUid`"
+        errorLine1="            final int uid = uh.getUid(appId);"
+        errorLine2="                               ~~~~~~">
+        <location
+            file="packages/modules/Connectivity/service/src/com/android/server/ConnectivityService.java"
+            line="13112"
+            column="32"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 33 (current min is 30): `setUidRule`"
+        errorLine1="            mBpfNetMaps.setUidRule(chain, uid, firewallRule);"
+        errorLine2="                        ~~~~~~~~~~">
+        <location
+            file="packages/modules/Connectivity/service/src/com/android/server/ConnectivityService.java"
+            line="13130"
+            column="25"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 33 (current min is 30): `setChildChain`"
+        errorLine1="            mBpfNetMaps.setChildChain(chain, enable);"
+        errorLine2="                        ~~~~~~~~~~~~~">
+        <location
+            file="packages/modules/Connectivity/service/src/com/android/server/ConnectivityService.java"
+            line="13195"
+            column="25"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 33 (current min is 30): `isChainEnabled`"
+        errorLine1="        return mBpfNetMaps.isChainEnabled(chain);"
+        errorLine2="                           ~~~~~~~~~~~~~~">
+        <location
+            file="packages/modules/Connectivity/service/src/com/android/server/ConnectivityService.java"
+            line="13213"
+            column="28"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 33 (current min is 30): `replaceUidChain`"
+        errorLine1="        mBpfNetMaps.replaceUidChain(chain, uids);"
+        errorLine2="                    ~~~~~~~~~~~~~~~">
+        <location
+            file="packages/modules/Connectivity/service/src/com/android/server/ConnectivityService.java"
+            line="13220"
+            column="21"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 31 (current min is 30): `BpfMap`"
+        errorLine1="        mBpfDscpIpv4Policies = new BpfMap&lt;Struct.S32, DscpPolicyValue&gt;(IPV4_POLICY_MAP_PATH,"
+        errorLine2="                               ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="packages/modules/Connectivity/service/src/com/android/server/connectivity/DscpPolicyTracker.java"
+            line="88"
+            column="32"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 31 (current min is 30): `BpfMap`"
+        errorLine1="        mBpfDscpIpv6Policies = new BpfMap&lt;Struct.S32, DscpPolicyValue&gt;(IPV6_POLICY_MAP_PATH,"
+        errorLine2="                               ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="packages/modules/Connectivity/service/src/com/android/server/connectivity/DscpPolicyTracker.java"
+            line="90"
+            column="32"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 31 (current min is 30): `insertOrReplaceEntry`"
+        errorLine1="                mBpfDscpIpv4Policies.insertOrReplaceEntry("
+        errorLine2="                                     ~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="packages/modules/Connectivity/service/src/com/android/server/connectivity/DscpPolicyTracker.java"
+            line="183"
+            column="38"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 31 (current min is 30): `insertOrReplaceEntry`"
+        errorLine1="                mBpfDscpIpv6Policies.insertOrReplaceEntry("
+        errorLine2="                                     ~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="packages/modules/Connectivity/service/src/com/android/server/connectivity/DscpPolicyTracker.java"
+            line="194"
+            column="38"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 31 (current min is 30): `replaceEntry`"
+        errorLine1="            mBpfDscpIpv4Policies.replaceEntry(new Struct.S32(index), DscpPolicyValue.NONE);"
+        errorLine2="                                 ~~~~~~~~~~~~">
+        <location
+            file="packages/modules/Connectivity/service/src/com/android/server/connectivity/DscpPolicyTracker.java"
+            line="261"
+            column="34"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 31 (current min is 30): `replaceEntry`"
+        errorLine1="            mBpfDscpIpv6Policies.replaceEntry(new Struct.S32(index), DscpPolicyValue.NONE);"
+        errorLine2="                                 ~~~~~~~~~~~~">
+        <location
+            file="packages/modules/Connectivity/service/src/com/android/server/connectivity/DscpPolicyTracker.java"
+            line="262"
+            column="34"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 31 (current min is 30): `java.net.InetAddress#parseNumericAddress`"
+        errorLine1='            InetAddress.parseNumericAddress("::").getAddress();'
+        errorLine2="                        ~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="packages/modules/Connectivity/service/src/com/android/server/connectivity/DscpPolicyValue.java"
+            line="99"
+            column="25"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 31 (current min is 30): `new android.net.NetworkStateSnapshot`"
+        errorLine1="            return new NetworkStateSnapshot(network, new NetworkCapabilities(networkCapabilities),"
+        errorLine2="                   ~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="packages/modules/Connectivity/service/src/com/android/server/connectivity/NetworkAgentInfo.java"
+            line="1353"
+            column="20"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 31 (current min is 30): `libcore.io.IoUtils#closeQuietly`"
+        errorLine1="            IoUtils.closeQuietly(mFileDescriptor);"
+        errorLine2="                    ~~~~~~~~~~~~">
+        <location
+            file="packages/modules/Connectivity/service/src/com/android/server/connectivity/NetworkDiagnostics.java"
+            line="570"
+            column="21"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Field requires API level 31 (current min is 30): `android.os.Build.VERSION#DEVICE_INITIAL_SDK_INT`"
+        errorLine1="            return Build.VERSION.DEVICE_INITIAL_SDK_INT;"
+        errorLine2="                   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="packages/modules/Connectivity/service/src/com/android/server/connectivity/PermissionMonitor.java"
+            line="212"
+            column="20"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 31 (current min is 30): `android.os.SystemConfigManager#getSystemPermissionUids`"
+        errorLine1="        for (final int uid : mSystemConfigManager.getSystemPermissionUids(INTERNET)) {"
+        errorLine2="                                                  ~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="packages/modules/Connectivity/service/src/com/android/server/connectivity/PermissionMonitor.java"
+            line="396"
+            column="51"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 31 (current min is 30): `android.os.SystemConfigManager#getSystemPermissionUids`"
+        errorLine1="        for (final int uid : mSystemConfigManager.getSystemPermissionUids(UPDATE_DEVICE_STATS)) {"
+        errorLine2="                                                  ~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="packages/modules/Connectivity/service/src/com/android/server/connectivity/PermissionMonitor.java"
+            line="404"
+            column="51"/>
+    </issue>
+
+    <issue
+        id="NewApi"
         message="Call requires API level 31 (current min is 30): `android.content.pm.ApplicationInfo#isOem`"
         errorLine1="        return appInfo.isVendor() || appInfo.isOem() || appInfo.isProduct();"
         errorLine2="                                             ~~~~~">
@@ -58,441 +674,34 @@
 
     <issue
         id="NewApi"
-        message="Call requires API level 31 (current min is 30): `android.net.NetworkPolicyManager#getMultipathPreference`"
-        errorLine1="            networkPreference = netPolicyManager.getMultipathPreference(network);"
-        errorLine2="                                                 ~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="packages/modules/Connectivity/service/src/com/android/server/ConnectivityService.java"
-            line="5498"
-            column="50"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 31 (current min is 30): `android.net.NetworkPolicyManager#getRestrictBackgroundStatus`"
-        errorLine1="            return mPolicyManager.getRestrictBackgroundStatus(callerUid);"
-        errorLine2="                                  ~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="packages/modules/Connectivity/service/src/com/android/server/ConnectivityService.java"
-            line="2565"
-            column="35"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 31 (current min is 30): `android.net.NetworkPolicyManager#isUidNetworkingBlocked`"
-        errorLine1="            return mPolicyManager.isUidNetworkingBlocked(uid, metered);"
-        errorLine2="                                  ~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="packages/modules/Connectivity/service/src/com/android/server/ConnectivityService.java"
-            line="1914"
-            column="35"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 31 (current min is 30): `android.net.NetworkPolicyManager#isUidRestrictedOnMeteredNetworks`"
-        errorLine1="            if (mPolicyManager.isUidRestrictedOnMeteredNetworks(uid)) {"
-        errorLine2="                               ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="packages/modules/Connectivity/service/src/com/android/server/ConnectivityService.java"
-            line="7094"
-            column="32"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 31 (current min is 30): `android.net.NetworkPolicyManager#registerNetworkPolicyCallback`"
-        errorLine1="        mPolicyManager.registerNetworkPolicyCallback(null, mPolicyCallback);"
-        errorLine2="                       ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="packages/modules/Connectivity/service/src/com/android/server/ConnectivityService.java"
-            line="1567"
-            column="24"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 31 (current min is 30): `android.net.NetworkStateSnapshot#getLinkProperties`"
-        errorLine1="                        snapshot.getLinkProperties(), snapshot.getNetworkCapabilities(),"
-        errorLine2="                                 ~~~~~~~~~~~~~~~~~">
-        <location
-            file="packages/modules/Connectivity/service/src/com/android/server/ConnectivityService.java"
-            line="2584"
-            column="34"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 31 (current min is 30): `android.net.NetworkStateSnapshot#getNetworkCapabilities`"
-        errorLine1="                        snapshot.getLinkProperties(), snapshot.getNetworkCapabilities(),"
-        errorLine2="                                                               ~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="packages/modules/Connectivity/service/src/com/android/server/ConnectivityService.java"
-            line="2584"
-            column="64"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 31 (current min is 30): `android.net.NetworkStateSnapshot#getNetwork`"
-        errorLine1="                        snapshot.getNetwork(), snapshot.getSubscriberId()));"
-        errorLine2="                                 ~~~~~~~~~~">
-        <location
-            file="packages/modules/Connectivity/service/src/com/android/server/ConnectivityService.java"
-            line="2585"
-            column="34"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 31 (current min is 30): `android.net.NetworkStateSnapshot#getNetwork`"
-        errorLine1="            final NetworkAgentInfo nai = getNetworkAgentInfoForNetwork(snapshot.getNetwork());"
-        errorLine2="                                                                                ~~~~~~~~~~">
-        <location
-            file="packages/modules/Connectivity/service/src/com/android/server/ConnectivityService.java"
-            line="2581"
-            column="81"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 31 (current min is 30): `android.net.NetworkStateSnapshot#getSubscriberId`"
-        errorLine1="                        snapshot.getNetwork(), snapshot.getSubscriberId()));"
-        errorLine2="                                                        ~~~~~~~~~~~~~~~">
-        <location
-            file="packages/modules/Connectivity/service/src/com/android/server/ConnectivityService.java"
-            line="2585"
-            column="57"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 31 (current min is 30): `android.net.NetworkWatchlistManager#getWatchlistConfigHash`"
-        errorLine1="        return nwm.getWatchlistConfigHash();"
-        errorLine2="                   ~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="packages/modules/Connectivity/service/src/com/android/server/ConnectivityService.java"
-            line="10060"
-            column="20"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 31 (current min is 30): `android.net.PacProxyManager#addPacProxyInstalledListener`"
-        errorLine1="        mPacProxyManager.addPacProxyInstalledListener("
-        errorLine2="                         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="packages/modules/Connectivity/service/src/com/android/server/connectivity/ProxyTracker.java"
-            line="111"
-            column="26"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 31 (current min is 30): `android.net.PacProxyManager#setCurrentProxyScriptUrl`"
-        errorLine1="                        () -&gt; mPacProxyManager.setCurrentProxyScriptUrl(proxyProperties));"
-        errorLine2="                                               ~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="packages/modules/Connectivity/service/src/com/android/server/connectivity/ProxyTracker.java"
-            line="208"
-            column="48"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 31 (current min is 30): `android.net.PacProxyManager#setCurrentProxyScriptUrl`"
-        errorLine1="        mPacProxyManager.setCurrentProxyScriptUrl(proxyInfo);"
-        errorLine2="                         ~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="packages/modules/Connectivity/service/src/com/android/server/connectivity/ProxyTracker.java"
-            line="252"
-            column="26"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 31 (current min is 30): `android.os.BatteryStatsManager#reportMobileRadioPowerState`"
-        errorLine1="                    bs.reportMobileRadioPowerState(isActive, NO_UID);"
-        errorLine2="                       ~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="packages/modules/Connectivity/service/src/com/android/server/ConnectivityService.java"
-            line="11006"
-            column="24"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 31 (current min is 30): `android.os.BatteryStatsManager#reportNetworkInterfaceForTransports`"
-        errorLine1="            batteryStats.reportNetworkInterfaceForTransports(iface, transportTypes);"
-        errorLine2="                         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="packages/modules/Connectivity/service/src/com/android/server/ConnectivityService.java"
-            line="1347"
-            column="26"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 31 (current min is 30): `android.os.BatteryStatsManager#reportWifiRadioPowerState`"
-        errorLine1="                    bs.reportWifiRadioPowerState(isActive, NO_UID);"
-        errorLine2="                       ~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="packages/modules/Connectivity/service/src/com/android/server/ConnectivityService.java"
-            line="11009"
-            column="24"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 31 (current min is 30): `android.os.Build#isDebuggable`"
-        errorLine1="            if (Build.isDebuggable()) {"
-        errorLine2="                      ~~~~~~~~~~~~">
-        <location
-            file="packages/modules/Connectivity/service/src/com/android/server/ConnectivityService.java"
-            line="9074"
-            column="23"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 31 (current min is 30): `android.os.Build#isDebuggable`"
-        errorLine1="        if (!Build.isDebuggable()) {"
-        errorLine2="                   ~~~~~~~~~~~~">
-        <location
-            file="packages/modules/Connectivity/service/src/com/android/server/ConnectivityService.java"
-            line="5039"
-            column="20"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 31 (current min is 30): `android.os.SystemConfigManager#getSystemPermissionUids`"
-        errorLine1="        for (final int uid : mSystemConfigManager.getSystemPermissionUids(INTERNET)) {"
-        errorLine2="                                                  ~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="packages/modules/Connectivity/service/src/com/android/server/connectivity/PermissionMonitor.java"
-            line="396"
-            column="51"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 31 (current min is 30): `android.os.SystemConfigManager#getSystemPermissionUids`"
-        errorLine1="        for (final int uid : mSystemConfigManager.getSystemPermissionUids(UPDATE_DEVICE_STATS)) {"
-        errorLine2="                                                  ~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="packages/modules/Connectivity/service/src/com/android/server/connectivity/PermissionMonitor.java"
-            line="404"
-            column="51"/>
-    </issue>
-
-    <issue
-        id="NewApi"
         message="Call requires API level 31 (current min is 30): `android.os.UserHandle#getUid`"
         errorLine1="                    final int uid = handle.getUid(appId);"
         errorLine2="                                           ~~~~~~">
         <location
             file="packages/modules/Connectivity/service/src/com/android/server/connectivity/PermissionMonitor.java"
-            line="1069"
+            line="1070"
             column="44"/>
     </issue>
 
     <issue
         id="NewApi"
-        message="Call requires API level 31 (current min is 30): `android.system.Os#getsockoptInt`"
-        errorLine1="                tcpDetails.tos = Os.getsockoptInt(fd, IPPROTO_IP, IP_TOS);"
-        errorLine2="                                    ~~~~~~~~~~~~~">
+        message="Call requires API level 33 (current min is 30): `updateUidLockdownRule`"
+        errorLine1="            mBpfNetMaps.updateUidLockdownRule(uid, add);"
+        errorLine2="                        ~~~~~~~~~~~~~~~~~~~~~">
         <location
-            file="packages/modules/Connectivity/service/src/com/android/server/connectivity/TcpKeepaliveController.java"
-            line="285"
-            column="37"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 31 (current min is 30): `android.system.Os#getsockoptInt`"
-        errorLine1="                tcpDetails.ttl = Os.getsockoptInt(fd, IPPROTO_IP, IP_TTL);"
-        errorLine2="                                    ~~~~~~~~~~~~~">
-        <location
-            file="packages/modules/Connectivity/service/src/com/android/server/connectivity/TcpKeepaliveController.java"
-            line="287"
-            column="37"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 31 (current min is 30): `android.system.Os#getsockoptInt`"
-        errorLine1="            tcpDetails.ack = Os.getsockoptInt(fd, IPPROTO_TCP, TCP_QUEUE_SEQ);"
-        errorLine2="                                ~~~~~~~~~~~~~">
-        <location
-            file="packages/modules/Connectivity/service/src/com/android/server/connectivity/TcpKeepaliveController.java"
-            line="265"
-            column="33"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 31 (current min is 30): `android.system.Os#getsockoptInt`"
-        errorLine1="            tcpDetails.seq = Os.getsockoptInt(fd, IPPROTO_TCP, TCP_QUEUE_SEQ);"
-        errorLine2="                                ~~~~~~~~~~~~~">
-        <location
-            file="packages/modules/Connectivity/service/src/com/android/server/connectivity/TcpKeepaliveController.java"
-            line="262"
-            column="33"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 31 (current min is 30): `android.system.Os#ioctlInt`"
-        errorLine1="        final int result = Os.ioctlInt(fd, SIOCINQ);"
-        errorLine2="                              ~~~~~~~~">
-        <location
-            file="packages/modules/Connectivity/service/src/com/android/server/connectivity/TcpKeepaliveController.java"
-            line="392"
-            column="31"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 31 (current min is 30): `android.system.Os#ioctlInt`"
-        errorLine1="        final int result = Os.ioctlInt(fd, SIOCOUTQ);"
-        errorLine2="                              ~~~~~~~~">
-        <location
-            file="packages/modules/Connectivity/service/src/com/android/server/connectivity/TcpKeepaliveController.java"
-            line="402"
-            column="31"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 31 (current min is 30): `java.net.InetAddress#parseNumericAddress`"
-        errorLine1='            InetAddress.parseNumericAddress("::").getAddress();'
-        errorLine2="                        ~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="packages/modules/Connectivity/service/src/com/android/server/connectivity/DscpPolicyValue.java"
-            line="99"
+            file="packages/modules/Connectivity/service/src/com/android/server/connectivity/PermissionMonitor.java"
+            line="1123"
             column="25"/>
     </issue>
 
     <issue
         id="NewApi"
-        message="Call requires API level 31 (current min is 30): `java.net.InetAddress#parseNumericAddress`"
-        errorLine1='    private static final InetAddress GOOGLE_DNS_4 = InetAddress.parseNumericAddress("8.8.8.8");'
-        errorLine2="                                                                ~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="packages/modules/Connectivity/service/src/com/android/server/connectivity/ClatCoordinator.java"
-            line="89"
-            column="65"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 31 (current min is 30): `libcore.io.IoUtils#closeQuietly`"
-        errorLine1="                IoUtils.closeQuietly(pfd);"
-        errorLine2="                        ~~~~~~~~~~~~">
-        <location
-            file="packages/modules/Connectivity/service/src/com/android/server/ConnectivityService.java"
-            line="9991"
-            column="25"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 31 (current min is 30): `libcore.io.IoUtils#closeQuietly`"
-        errorLine1="                IoUtils.closeQuietly(pfd);"
-        errorLine2="                        ~~~~~~~~~~~~">
-        <location
-            file="packages/modules/Connectivity/service/src/com/android/server/ConnectivityService.java"
-            line="10008"
-            column="25"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 31 (current min is 30): `libcore.io.IoUtils#closeQuietly`"
-        errorLine1="            IoUtils.closeQuietly(mFileDescriptor);"
-        errorLine2="                    ~~~~~~~~~~~~">
-        <location
-            file="packages/modules/Connectivity/service/src/com/android/server/connectivity/NetworkDiagnostics.java"
-            line="481"
-            column="21"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 31 (current min is 30): `new android.net.NetworkStateSnapshot`"
-        errorLine1="            return new NetworkStateSnapshot(network, new NetworkCapabilities(networkCapabilities),"
-        errorLine2="                   ~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="packages/modules/Connectivity/service/src/com/android/server/connectivity/NetworkAgentInfo.java"
-            line="1269"
-            column="20"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 31 (current min is 30): `new android.net.UnderlyingNetworkInfo`"
-        errorLine1="        return new UnderlyingNetworkInfo(nai.networkCapabilities.getOwnerUid(),"
-        errorLine2="               ~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="packages/modules/Connectivity/service/src/com/android/server/ConnectivityService.java"
-            line="6123"
-            column="16"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Class requires API level 31 (current min is 30): `android.net.NetworkPolicyManager.NetworkPolicyCallback`"
-        errorLine1="    private final NetworkPolicyCallback mPolicyCallback = new NetworkPolicyCallback() {"
-        errorLine2="                                                              ~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="packages/modules/Connectivity/service/src/com/android/server/ConnectivityService.java"
-            line="2827"
-            column="63"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Class requires API level 31 (current min is 30): `android.net.NetworkPolicyManager`"
-        errorLine1="                 mContext.getSystemService(NetworkPolicyManager.class);"
-        errorLine2="                                           ~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="packages/modules/Connectivity/service/src/com/android/server/ConnectivityService.java"
-            line="5493"
-            column="44"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Class requires API level 31 (current min is 30): `android.net.NetworkPolicyManager`"
-        errorLine1="        mPolicyManager = mContext.getSystemService(NetworkPolicyManager.class);"
-        errorLine2="                                                   ~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="packages/modules/Connectivity/service/src/com/android/server/ConnectivityService.java"
-            line="1554"
-            column="52"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Class requires API level 31 (current min is 30): `android.net.NetworkWatchlistManager`"
-        errorLine1="        NetworkWatchlistManager nwm = mContext.getSystemService(NetworkWatchlistManager.class);"
-        errorLine2="                                                                ~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="packages/modules/Connectivity/service/src/com/android/server/ConnectivityService.java"
-            line="10054"
-            column="65"/>
-    </issue>
-
-    <issue
-        id="NewApi"
         message="Class requires API level 31 (current min is 30): `android.net.PacProxyManager.PacProxyInstalledListener`"
         errorLine1="    private class PacProxyInstalledListener implements PacProxyManager.PacProxyInstalledListener {"
         errorLine2="                                                       ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
         <location
             file="packages/modules/Connectivity/service/src/com/android/server/connectivity/ProxyTracker.java"
-            line="90"
+            line="92"
             column="56"/>
     </issue>
 
@@ -503,8 +712,107 @@
         errorLine2="                                                    ~~~~~~~~~~~~~~~">
         <location
             file="packages/modules/Connectivity/service/src/com/android/server/connectivity/ProxyTracker.java"
-            line="108"
+            line="111"
             column="53"/>
     </issue>
 
+    <issue
+        id="NewApi"
+        message="Call requires API level 31 (current min is 30): `android.net.PacProxyManager#addPacProxyInstalledListener`"
+        errorLine1="            mPacProxyManager.addPacProxyInstalledListener("
+        errorLine2="                             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="packages/modules/Connectivity/service/src/com/android/server/connectivity/ProxyTracker.java"
+            line="115"
+            column="30"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 31 (current min is 30): `android.net.PacProxyManager#setCurrentProxyScriptUrl`"
+        errorLine1="                        () -&gt; mPacProxyManager.setCurrentProxyScriptUrl(proxyProperties));"
+        errorLine2="                                               ~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="packages/modules/Connectivity/service/src/com/android/server/connectivity/ProxyTracker.java"
+            line="213"
+            column="48"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 31 (current min is 30): `android.net.PacProxyManager#setCurrentProxyScriptUrl`"
+        errorLine1="            mPacProxyManager.setCurrentProxyScriptUrl(proxyInfo);"
+        errorLine2="                             ~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="packages/modules/Connectivity/service/src/com/android/server/connectivity/ProxyTracker.java"
+            line="259"
+            column="30"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 31 (current min is 30): `android.system.Os#getsockoptInt`"
+        errorLine1="            tcpDetails.seq = Os.getsockoptInt(fd, IPPROTO_TCP, TCP_QUEUE_SEQ);"
+        errorLine2="                                ~~~~~~~~~~~~~">
+        <location
+            file="packages/modules/Connectivity/service/src/com/android/server/connectivity/TcpKeepaliveController.java"
+            line="269"
+            column="33"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 31 (current min is 30): `android.system.Os#getsockoptInt`"
+        errorLine1="            tcpDetails.ack = Os.getsockoptInt(fd, IPPROTO_TCP, TCP_QUEUE_SEQ);"
+        errorLine2="                                ~~~~~~~~~~~~~">
+        <location
+            file="packages/modules/Connectivity/service/src/com/android/server/connectivity/TcpKeepaliveController.java"
+            line="272"
+            column="33"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 31 (current min is 30): `android.system.Os#getsockoptInt`"
+        errorLine1="                tcpDetails.tos = Os.getsockoptInt(fd, IPPROTO_IP, IP_TOS);"
+        errorLine2="                                    ~~~~~~~~~~~~~">
+        <location
+            file="packages/modules/Connectivity/service/src/com/android/server/connectivity/TcpKeepaliveController.java"
+            line="292"
+            column="37"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 31 (current min is 30): `android.system.Os#getsockoptInt`"
+        errorLine1="                tcpDetails.ttl = Os.getsockoptInt(fd, IPPROTO_IP, IP_TTL);"
+        errorLine2="                                    ~~~~~~~~~~~~~">
+        <location
+            file="packages/modules/Connectivity/service/src/com/android/server/connectivity/TcpKeepaliveController.java"
+            line="294"
+            column="37"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 31 (current min is 30): `android.system.Os#ioctlInt`"
+        errorLine1="        final int result = Os.ioctlInt(fd, SIOCINQ);"
+        errorLine2="                              ~~~~~~~~">
+        <location
+            file="packages/modules/Connectivity/service/src/com/android/server/connectivity/TcpKeepaliveController.java"
+            line="401"
+            column="31"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 31 (current min is 30): `android.system.Os#ioctlInt`"
+        errorLine1="        final int result = Os.ioctlInt(fd, SIOCOUTQ);"
+        errorLine2="                              ~~~~~~~~">
+        <location
+            file="packages/modules/Connectivity/service/src/com/android/server/connectivity/TcpKeepaliveController.java"
+            line="411"
+            column="31"/>
+    </issue>
+
 </issues>
\ No newline at end of file
diff --git a/service/native/libs/libclat/Android.bp b/service/native/libs/libclat/Android.bp
index 5c6b123..6c1c2c4 100644
--- a/service/native/libs/libclat/Android.bp
+++ b/service/native/libs/libclat/Android.bp
@@ -13,6 +13,7 @@
 // limitations under the License.
 
 package {
+    default_team: "trendy_team_fwk_core_networking",
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
@@ -38,7 +39,10 @@
 cc_test {
     name: "libclat_test",
     defaults: ["netd_defaults"],
-    test_suites: ["general-tests", "mts-tethering"],
+    test_suites: [
+        "general-tests",
+        "mts-tethering",
+    ],
     test_config_template: ":net_native_test_config_template",
     srcs: [
         "clatutils_test.cpp",
diff --git a/service/proguard.flags b/service/proguard.flags
index cf25f05..ed9a65f 100644
--- a/service/proguard.flags
+++ b/service/proguard.flags
@@ -15,3 +15,7 @@
     static final % EVENT_*;
 }
 
+# b/313539492 Keep the onLocalNetworkInfoChanged method in classes extending Connectivity.NetworkCallback.
+-keepclassmembers class * extends **android.net.ConnectivityManager$NetworkCallback {
+    public void onLocalNetworkInfoChanged(**android.net.Network, **android.net.LocalNetworkInfo);
+}
diff --git a/service/src/com/android/server/BpfLoaderRcUtils.java b/service/src/com/android/server/BpfLoaderRcUtils.java
deleted file mode 100644
index 293e757..0000000
--- a/service/src/com/android/server/BpfLoaderRcUtils.java
+++ /dev/null
@@ -1,161 +0,0 @@
-/*
- * Copyright (C) 2023 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;
-
-import android.annotation.NonNull;
-import android.util.Log;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.modules.utils.build.SdkLevel;
-
-import java.io.BufferedReader;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.nio.charset.StandardCharsets;
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * BpfRcUtils is responsible for comparing the bpf loader rc file.
- *
- * {@hide}
- */
-public class BpfLoaderRcUtils {
-    public static final String TAG = BpfLoaderRcUtils.class.getSimpleName();
-
-    private static final List<String> BPF_LOADER_RC_S_T = List.of(
-            "service bpfloader /system/bin/bpfloader",
-            "capabilities CHOWN SYS_ADMIN NET_ADMIN",
-            "rlimit memlock 1073741824 1073741824",
-            "oneshot",
-            "reboot_on_failure reboot,bpfloader-failed",
-            "updatable"
-    );
-
-    private static final List<String> BPF_LOADER_RC_U = List.of(
-            "service bpfloader /system/bin/bpfloader",
-            "capabilities CHOWN SYS_ADMIN NET_ADMIN",
-            "group root graphics network_stack net_admin net_bw_acct net_bw_stats net_raw system",
-            "user root",
-            "rlimit memlock 1073741824 1073741824",
-            "oneshot",
-            "reboot_on_failure reboot,bpfloader-failed",
-            "updatable"
-    );
-
-    private static final List<String> BPF_LOADER_RC_UQPR2 = List.of(
-            "service bpfloader /system/bin/netbpfload",
-            "capabilities CHOWN SYS_ADMIN NET_ADMIN",
-            "group root graphics network_stack net_admin net_bw_acct net_bw_stats net_raw system",
-            "user root",
-            "rlimit memlock 1073741824 1073741824",
-            "oneshot",
-            "reboot_on_failure reboot,bpfloader-failed",
-            "updatable"
-    );
-
-
-    private static final String BPF_LOADER_RC_FILE_PATH = "/etc/init/bpfloader.rc";
-    private static final String NET_BPF_LOAD_RC_FILE_PATH = "/etc/init/netbpfload.rc";
-
-    private BpfLoaderRcUtils() {
-    }
-
-    /**
-     * Load the bpf rc file content from the input stream.
-     */
-    @VisibleForTesting
-    public static List<String> loadExistingBpfRcFile(@NonNull InputStream inputStream) {
-        List<String> contents = new ArrayList<>();
-        boolean bpfSectionFound = false;
-        try (BufferedReader br = new BufferedReader(
-                new InputStreamReader(inputStream, StandardCharsets.ISO_8859_1))) {
-            String line;
-            while ((line = br.readLine()) != null) {
-                line = line.trim();
-                if (line.isEmpty()) {
-                    continue;
-                }
-                if (line.startsWith("#")) {
-                    continue;
-                }
-                // If bpf service section was found and new service or action section start. The
-                // read should stop.
-                if (bpfSectionFound && (line.startsWith("service ") || (line.startsWith("on ")))) {
-                    break;
-                }
-                if (line.startsWith("service bpfloader ")) {
-                    bpfSectionFound = true;
-                }
-                if (bpfSectionFound) {
-                    contents.add(line);
-                }
-            }
-        } catch (IOException e) {
-            Log.wtf("read input stream failed.", e);
-            contents.clear();
-            return contents;
-        }
-        return contents;
-    }
-
-    /**
-     * Check the bpfLoader rc file on the system image matches any of the template files.
-     */
-    public static boolean checkBpfLoaderRc() {
-        File bpfRcFile = new File(BPF_LOADER_RC_FILE_PATH);
-        if (!bpfRcFile.exists()) {
-            if (SdkLevel.isAtLeastU()) {
-                bpfRcFile = new File(NET_BPF_LOAD_RC_FILE_PATH);
-            }
-            if (!bpfRcFile.exists()) {
-                Log.wtf(TAG,
-                        "neither " + BPF_LOADER_RC_FILE_PATH + " nor " + NET_BPF_LOAD_RC_FILE_PATH
-                                + " exist.");
-                return false;
-            }
-            // Check bpf rc file in U QPR2
-            return compareBpfLoaderRc(bpfRcFile, BPF_LOADER_RC_UQPR2);
-        }
-
-        if (SdkLevel.isAtLeastU()) {
-            // Check bpf rc file in U
-            return compareBpfLoaderRc(bpfRcFile, BPF_LOADER_RC_U);
-        }
-        // Check bpf rc file in S/T
-        return compareBpfLoaderRc(bpfRcFile, BPF_LOADER_RC_S_T);
-    }
-
-    private static boolean compareBpfLoaderRc(@NonNull File bpfRcFile,
-            @NonNull List<String> template) {
-        try {
-            List<String> actualContent = loadExistingBpfRcFile(new FileInputStream(bpfRcFile));
-            if (!actualContent.equals(template)) {
-                Log.wtf(TAG, "BPF rc file is not same as the template files " + actualContent);
-                return false;
-            }
-        } catch (FileNotFoundException e) {
-            Log.wtf(bpfRcFile.getPath() + " doesn't exist.", e);
-            return false;
-        }
-        return true;
-    }
-}
diff --git a/service/src/com/android/server/BpfNetMaps.java b/service/src/com/android/server/BpfNetMaps.java
index ad9cfbe..a7fddd0 100644
--- a/service/src/com/android/server/BpfNetMaps.java
+++ b/service/src/com/android/server/BpfNetMaps.java
@@ -31,7 +31,6 @@
 import static android.net.BpfNetMapsConstants.UID_OWNER_MAP_PATH;
 import static android.net.BpfNetMapsConstants.UID_PERMISSION_MAP_PATH;
 import static android.net.BpfNetMapsConstants.UID_RULES_CONFIGURATION_KEY;
-import static android.net.BpfNetMapsUtils.PRE_T;
 import static android.net.BpfNetMapsUtils.getMatchByFirewallChain;
 import static android.net.BpfNetMapsUtils.isFirewallAllowList;
 import static android.net.BpfNetMapsUtils.matchToString;
@@ -68,6 +67,7 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.modules.utils.BackgroundThread;
+import com.android.modules.utils.build.SdkLevel;
 import com.android.net.module.util.BpfDump;
 import com.android.net.module.util.BpfMap;
 import com.android.net.module.util.IBpfMap;
@@ -95,7 +95,7 @@
  */
 public class BpfNetMaps {
     static {
-        if (!PRE_T) {
+        if (SdkLevel.isAtLeastT()) {
             System.loadLibrary("service-connectivity");
         }
     }
@@ -184,60 +184,67 @@
         sIngressDiscardMap = ingressDiscardMap;
     }
 
+    @RequiresApi(Build.VERSION_CODES.TIRAMISU)
     private static IBpfMap<S32, U32> getConfigurationMap() {
         try {
             return new BpfMap<>(
-                    CONFIGURATION_MAP_PATH, BpfMap.BPF_F_RDWR, S32.class, U32.class);
+                    CONFIGURATION_MAP_PATH, S32.class, U32.class);
         } catch (ErrnoException e) {
             throw new IllegalStateException("Cannot open netd configuration map", e);
         }
     }
 
+    @RequiresApi(Build.VERSION_CODES.TIRAMISU)
     private static IBpfMap<S32, UidOwnerValue> getUidOwnerMap() {
         try {
             return new BpfMap<>(
-                    UID_OWNER_MAP_PATH, BpfMap.BPF_F_RDWR, S32.class, UidOwnerValue.class);
+                    UID_OWNER_MAP_PATH, S32.class, UidOwnerValue.class);
         } catch (ErrnoException e) {
             throw new IllegalStateException("Cannot open uid owner map", e);
         }
     }
 
+    @RequiresApi(Build.VERSION_CODES.TIRAMISU)
     private static IBpfMap<S32, U8> getUidPermissionMap() {
         try {
             return new BpfMap<>(
-                    UID_PERMISSION_MAP_PATH, BpfMap.BPF_F_RDWR, S32.class, U8.class);
+                    UID_PERMISSION_MAP_PATH, S32.class, U8.class);
         } catch (ErrnoException e) {
             throw new IllegalStateException("Cannot open uid permission map", e);
         }
     }
 
+    @RequiresApi(Build.VERSION_CODES.TIRAMISU)
     private static IBpfMap<CookieTagMapKey, CookieTagMapValue> getCookieTagMap() {
         try {
-            return new BpfMap<>(COOKIE_TAG_MAP_PATH, BpfMap.BPF_F_RDWR,
+            return new BpfMap<>(COOKIE_TAG_MAP_PATH,
                     CookieTagMapKey.class, CookieTagMapValue.class);
         } catch (ErrnoException e) {
             throw new IllegalStateException("Cannot open cookie tag map", e);
         }
     }
 
+    @RequiresApi(Build.VERSION_CODES.TIRAMISU)
     private static IBpfMap<S32, U8> getDataSaverEnabledMap() {
         try {
             return new BpfMap<>(
-                    DATA_SAVER_ENABLED_MAP_PATH, BpfMap.BPF_F_RDWR, S32.class, U8.class);
+                    DATA_SAVER_ENABLED_MAP_PATH, S32.class, U8.class);
         } catch (ErrnoException e) {
             throw new IllegalStateException("Cannot open data saver enabled map", e);
         }
     }
 
+    @RequiresApi(Build.VERSION_CODES.TIRAMISU)
     private static IBpfMap<IngressDiscardKey, IngressDiscardValue> getIngressDiscardMap() {
         try {
-            return new BpfMap<>(INGRESS_DISCARD_MAP_PATH, BpfMap.BPF_F_RDWR,
+            return new BpfMap<>(INGRESS_DISCARD_MAP_PATH,
                     IngressDiscardKey.class, IngressDiscardValue.class);
         } catch (ErrnoException e) {
             throw new IllegalStateException("Cannot open ingress discard map", e);
         }
     }
 
+    @RequiresApi(Build.VERSION_CODES.TIRAMISU)
     private static void initBpfMaps() {
         if (sConfigurationMap == null) {
             sConfigurationMap = getConfigurationMap();
@@ -295,6 +302,7 @@
      * Initializes the class if it is not already initialized. This method will open maps but not
      * cause any other effects. This method may be called multiple times on any thread.
      */
+    @RequiresApi(Build.VERSION_CODES.TIRAMISU)
     private static synchronized void ensureInitialized(final Context context) {
         if (sInitialized) return;
         initBpfMaps();
@@ -348,7 +356,7 @@
     public BpfNetMaps(final Context context) {
         this(context, null);
 
-        if (PRE_T) throw new IllegalArgumentException("BpfNetMaps need to use netd before T");
+        if (!SdkLevel.isAtLeastT()) throw new IllegalArgumentException("BpfNetMaps need to use netd before T");
     }
 
     public BpfNetMaps(final Context context, final INetd netd) {
@@ -357,7 +365,7 @@
 
     @VisibleForTesting
     public BpfNetMaps(final Context context, final INetd netd, final Dependencies deps) {
-        if (!PRE_T) {
+        if (SdkLevel.isAtLeastT()) {
             ensureInitialized(context);
         }
         mNetd = netd;
@@ -371,7 +379,7 @@
     }
 
     private void throwIfPreT(final String msg) {
-        if (PRE_T) {
+        if (!SdkLevel.isAtLeastT()) {
             throw new UnsupportedOperationException(msg);
         }
     }
@@ -712,7 +720,7 @@
      *                                  cause of the failure.
      */
     public void addUidInterfaceRules(final String ifName, final int[] uids) throws RemoteException {
-        if (PRE_T) {
+        if (!SdkLevel.isAtLeastT()) {
             mNetd.firewallAddUidInterfaceRules(ifName, uids);
             return;
         }
@@ -750,7 +758,7 @@
      *                                  cause of the failure.
      */
     public void removeUidInterfaceRules(final int[] uids) throws RemoteException {
-        if (PRE_T) {
+        if (!SdkLevel.isAtLeastT()) {
             mNetd.firewallRemoveUidInterfaceRules(uids);
             return;
         }
@@ -829,7 +837,7 @@
      * @throws RemoteException when netd has crashed.
      */
     public void setNetPermForUids(final int permissions, final int[] uids) throws RemoteException {
-        if (PRE_T) {
+        if (!SdkLevel.isAtLeastT()) {
             mNetd.trafficSetNetPermForUids(permissions, uids);
             return;
         }
@@ -1019,7 +1027,7 @@
     @RequiresApi(Build.VERSION_CODES.TIRAMISU)
     public void dump(final IndentingPrintWriter pw, final FileDescriptor fd, boolean verbose)
             throws IOException, ServiceSpecificException {
-        if (PRE_T) {
+        if (!SdkLevel.isAtLeastT()) {
             throw new ServiceSpecificException(
                     EOPNOTSUPP, "dumpsys connectivity trafficcontroller dump not available on pre-T"
                     + " devices, use dumpsys netd trafficcontroller instead.");
diff --git a/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java
index a995439..30b14b2 100755
--- a/service/src/com/android/server/ConnectivityService.java
+++ b/service/src/com/android/server/ConnectivityService.java
@@ -38,6 +38,7 @@
 import static android.net.ConnectivityManager.BLOCKED_REASON_NONE;
 import static android.net.ConnectivityManager.CALLBACK_IP_CHANGED;
 import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_BACKGROUND;
 import static android.net.ConnectivityManager.FIREWALL_RULE_ALLOW;
 import static android.net.ConnectivityManager.FIREWALL_RULE_DEFAULT;
 import static android.net.ConnectivityManager.FIREWALL_RULE_DENY;
@@ -108,15 +109,15 @@
 import static com.android.net.module.util.BpfUtils.BPF_CGROUP_INET_INGRESS;
 import static com.android.net.module.util.BpfUtils.BPF_CGROUP_INET_SOCK_CREATE;
 import static com.android.net.module.util.NetworkMonitorUtils.isPrivateDnsValidationRequired;
-import static com.android.net.module.util.PermissionUtils.checkAnyPermissionOf;
 import static com.android.net.module.util.PermissionUtils.enforceAnyPermissionOf;
 import static com.android.net.module.util.PermissionUtils.enforceNetworkStackPermission;
 import static com.android.net.module.util.PermissionUtils.enforceNetworkStackPermissionOr;
+import static com.android.net.module.util.PermissionUtils.hasAnyPermissionOf;
 import static com.android.server.ConnectivityStatsLog.CONNECTIVITY_STATE_SAMPLE;
-
-import static java.util.Map.Entry;
+import static com.android.server.connectivity.ConnectivityFlags.REQUEST_RESTRICTED_WIFI;
 
 import android.Manifest;
+import android.annotation.CheckResult;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SuppressLint;
@@ -255,6 +256,7 @@
 import android.stats.connectivity.ValidatedState;
 import android.sysprop.NetworkProperties;
 import android.system.ErrnoException;
+import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 import android.text.TextUtils;
 import android.util.ArrayMap;
@@ -337,6 +339,7 @@
 import com.android.server.connectivity.ProxyTracker;
 import com.android.server.connectivity.QosCallbackTracker;
 import com.android.server.connectivity.RoutingCoordinatorService;
+import com.android.server.connectivity.SatelliteAccessController;
 import com.android.server.connectivity.UidRangeUtils;
 import com.android.server.connectivity.VpnNetworkPreferenceInfo;
 import com.android.server.connectivity.wear.CompanionDeviceManagerProxyService;
@@ -351,7 +354,6 @@
 import java.io.PrintWriter;
 import java.io.Writer;
 import java.net.Inet4Address;
-import java.net.Inet6Address;
 import java.net.InetAddress;
 import java.net.InetSocketAddress;
 import java.net.SocketException;
@@ -375,6 +377,8 @@
 import java.util.TreeSet;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
 
 /**
  * @hide
@@ -469,6 +473,8 @@
 
     private volatile boolean mLockdownEnabled;
 
+    private final boolean mRequestRestrictedWifiEnabled;
+
     /**
      * Stale copy of uid blocked reasons provided by NPMS. As long as they are accessed only in
      * internal handler thread, they don't need a lock.
@@ -564,6 +570,10 @@
     // See {@link ConnectivitySettingsManager#setMobileDataPreferredUids}
     @VisibleForTesting
     static final int PREFERENCE_ORDER_MOBILE_DATA_PREFERERRED = 30;
+    // Order of setting satellite network preference fallback when default message application
+    // with role_sms role and android.permission.SATELLITE_COMMUNICATION permission detected
+    @VisibleForTesting
+    static final int PREFERENCE_ORDER_SATELLITE_FALLBACK = 40;
     // Preference order that signifies the network shouldn't be set as a default network for
     // the UIDs, only give them access to it. TODO : replace this with a boolean
     // in NativeUidRangeConfig
@@ -834,6 +844,11 @@
     private static final int EVENT_UID_FROZEN_STATE_CHANGED = 61;
 
     /**
+     * Event to inform the ConnectivityService handler when a uid has lost carrier privileges.
+     */
+    private static final int EVENT_UID_CARRIER_PRIVILEGES_LOST = 62;
+
+    /**
      * Argument for {@link #EVENT_PROVISIONING_NOTIFICATION} to indicate that the notification
      * should be shown.
      */
@@ -922,6 +937,7 @@
     private final QosCallbackTracker mQosCallbackTracker;
     private final NetworkNotificationManager mNotifier;
     private final LingerMonitor mLingerMonitor;
+    private final SatelliteAccessController mSatelliteAccessController;
 
     // sequence number of NetworkRequests
     private int mNextNetworkRequestId = NetworkRequest.FIRST_REQUEST_ID;
@@ -1272,6 +1288,14 @@
     }
     private final LegacyTypeTracker mLegacyTypeTracker = new LegacyTypeTracker(this);
 
+    @VisibleForTesting
+    void onCarrierPrivilegesLost(Integer uid, Integer subId) {
+        if (mRequestRestrictedWifiEnabled) {
+            mHandler.sendMessage(mHandler.obtainMessage(
+                    EVENT_UID_CARRIER_PRIVILEGES_LOST, uid, subId));
+        }
+    }
+
     final LocalPriorityDump mPriorityDumper = new LocalPriorityDump();
     /**
      * Helper class which parses out priority arguments and dumps sections according to their
@@ -1490,15 +1514,31 @@
          */
         @Nullable
         public CarrierPrivilegeAuthenticator makeCarrierPrivilegeAuthenticator(
-                @NonNull final Context context, @NonNull final TelephonyManager tm) {
+                @NonNull final Context context,
+                @NonNull final TelephonyManager tm,
+                boolean requestRestrictedWifiEnabled,
+                @NonNull BiConsumer<Integer, Integer> listener) {
             if (isAtLeastT()) {
-                return new CarrierPrivilegeAuthenticator(context, tm);
+                return new CarrierPrivilegeAuthenticator(
+                        context, tm, requestRestrictedWifiEnabled, listener);
             } else {
                 return null;
             }
         }
 
         /**
+         * @see SatelliteAccessController
+         */
+        @Nullable
+        public SatelliteAccessController makeSatelliteAccessController(
+                @NonNull final Context context,
+                Consumer<Set<Integer>> updateSatelliteNetworkFallbackUidCallback,
+                @NonNull final Handler connectivityServiceInternalHandler) {
+            return new SatelliteAccessController(context, updateSatelliteNetworkFallbackUidCallback,
+                    connectivityServiceInternalHandler);
+        }
+
+        /**
          * @see DeviceConfigUtils#isTetheringFeatureEnabled
          */
         public boolean isFeatureEnabled(Context context, String name) {
@@ -1761,8 +1801,20 @@
         mTelephonyManager = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
         mAppOpsManager = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE);
         mLocationPermissionChecker = mDeps.makeLocationPermissionChecker(mContext);
-        mCarrierPrivilegeAuthenticator =
-                mDeps.makeCarrierPrivilegeAuthenticator(mContext, mTelephonyManager);
+        mRequestRestrictedWifiEnabled = mDeps.isAtLeastU()
+                && mDeps.isFeatureEnabled(context, REQUEST_RESTRICTED_WIFI);
+        mCarrierPrivilegeAuthenticator = mDeps.makeCarrierPrivilegeAuthenticator(
+                mContext, mTelephonyManager, mRequestRestrictedWifiEnabled,
+                this::onCarrierPrivilegesLost);
+
+        if (mDeps.isAtLeastU()
+                && mDeps
+                .isFeatureNotChickenedOut(mContext, ALLOW_SATALLITE_NETWORK_FALLBACK)) {
+            mSatelliteAccessController = mDeps.makeSatelliteAccessController(
+                    mContext, this::updateSatelliteNetworkPreferenceUids, mHandler);
+        } else {
+            mSatelliteAccessController = null;
+        }
 
         // To ensure uid state is synchronized with Network Policy, register for
         // NetworkPolicyManagerService events must happen prior to NetworkPolicyManagerService
@@ -1896,8 +1948,8 @@
         mMulticastRoutingCoordinatorService =
                 mDeps.makeMulticastRoutingCoordinatorService(mHandler);
 
-        mDestroyFrozenSockets = mDeps.isAtLeastU()
-                && mDeps.isFeatureEnabled(context, KEY_DESTROY_FROZEN_SOCKETS_VERSION);
+        mDestroyFrozenSockets = mDeps.isAtLeastV() || (mDeps.isAtLeastU()
+                && mDeps.isFeatureEnabled(context, KEY_DESTROY_FROZEN_SOCKETS_VERSION));
         mDelayDestroyFrozenSockets = mDeps.isAtLeastU()
                 && mDeps.isFeatureEnabled(context, DELAY_DESTROY_FROZEN_SOCKETS_VERSION);
         mAllowSysUiConnectivityReports = mDeps.isFeatureNotChickenedOut(
@@ -1926,10 +1978,6 @@
             activityManager.registerUidFrozenStateChangedCallback(
                     (Runnable r) -> r.run(), frozenStateChangedCallback);
         }
-
-        if (mDeps.isFeatureNotChickenedOut(mContext, LOG_BPF_RC)) {
-            mHandler.post(BpfLoaderRcUtils::checkBpfLoaderRc);
-        }
     }
 
     /**
@@ -2032,6 +2080,18 @@
                 new Pair<>(network, proxyInfo)).sendToTarget();
     }
 
+    /**
+     * Called when satellite network fallback uids at {@link SatelliteAccessController}
+     * cache was updated based on {@link
+     * android.app.role.OnRoleHoldersChangedListener#onRoleHoldersChanged(String, UserHandle)},
+     * to create multilayer request with preference order
+     * {@link #PREFERENCE_ORDER_SATELLITE_FALLBACK} there on.
+     *
+     */
+    private void updateSatelliteNetworkPreferenceUids(Set<Integer> satelliteNetworkFallbackUids) {
+        handleSetSatelliteNetworkPreference(satelliteNetworkFallbackUids);
+    }
+
     private void handleAlwaysOnNetworkRequest(
             NetworkRequest networkRequest, String settingName, boolean defaultValue) {
         final boolean enable = toBool(Settings.Global.getInt(
@@ -2651,7 +2711,7 @@
         Objects.requireNonNull(packageName);
         Objects.requireNonNull(lp);
         enforceNetworkStackOrSettingsPermission();
-        if (!checkAccessPermission(-1 /* pid */, uid)) {
+        if (!hasAccessPermission(-1 /* pid */, uid)) {
             return null;
         }
         return linkPropertiesRestrictedForCallerPermissions(lp, -1 /* callerPid */, uid);
@@ -2687,7 +2747,7 @@
         Objects.requireNonNull(nc);
         Objects.requireNonNull(packageName);
         enforceNetworkStackOrSettingsPermission();
-        if (!checkAccessPermission(-1 /* pid */, uid)) {
+        if (!hasAccessPermission(-1 /* pid */, uid)) {
             return null;
         }
         return createWithLocationInfoSanitizedIfNecessaryWhenParceled(
@@ -2698,14 +2758,14 @@
 
     private void redactUnderlyingNetworksForCapabilities(NetworkCapabilities nc, int pid, int uid) {
         if (nc.getUnderlyingNetworks() != null
-                && !checkNetworkFactoryOrSettingsPermission(pid, uid)) {
+                && !hasNetworkFactoryOrSettingsPermission(pid, uid)) {
             nc.setUnderlyingNetworks(null);
         }
     }
 
     private boolean canSeeAllowedUids(final int pid, final int uid, final int netOwnerUid) {
         return Process.SYSTEM_UID == uid
-                || checkAnyPermissionOf(mContext, pid, uid,
+                || hasAnyPermissionOf(mContext, pid, uid,
                         android.Manifest.permission.NETWORK_FACTORY);
     }
 
@@ -2718,14 +2778,14 @@
         // it happens for some reason (e.g. the package is uninstalled while CS is trying to
         // send the callback) it would crash the system server with NPE.
         final NetworkCapabilities newNc = new NetworkCapabilities(nc);
-        if (!checkSettingsPermission(callerPid, callerUid)) {
+        if (!hasSettingsPermission(callerPid, callerUid)) {
             newNc.setUids(null);
             newNc.setSSID(null);
         }
         if (newNc.getNetworkSpecifier() != null) {
             newNc.setNetworkSpecifier(newNc.getNetworkSpecifier().redact());
         }
-        if (!checkAnyPermissionOf(mContext, callerPid, callerUid,
+        if (!hasAnyPermissionOf(mContext, callerPid, callerUid,
                 android.Manifest.permission.NETWORK_STACK,
                 NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK)) {
             newNc.setAdministratorUids(new int[0]);
@@ -2793,11 +2853,12 @@
          * Returns whether the app holds local mac address permission or not (might return cached
          * result if the permission was already checked before).
          */
+        @CheckResult
         public boolean hasLocalMacAddressPermission() {
             if (mHasLocalMacAddressPermission == null) {
                 // If there is no cached result, perform the check now.
-                mHasLocalMacAddressPermission =
-                        checkLocalMacAddressPermission(mCallingPid, mCallingUid);
+                mHasLocalMacAddressPermission = ConnectivityService.this
+                        .hasLocalMacAddressPermission(mCallingPid, mCallingUid);
             }
             return mHasLocalMacAddressPermission;
         }
@@ -2806,10 +2867,12 @@
          * Returns whether the app holds settings permission or not (might return cached
          * result if the permission was already checked before).
          */
+        @CheckResult
         public boolean hasSettingsPermission() {
             if (mHasSettingsPermission == null) {
                 // If there is no cached result, perform the check now.
-                mHasSettingsPermission = checkSettingsPermission(mCallingPid, mCallingUid);
+                mHasSettingsPermission =
+                        ConnectivityService.this.hasSettingsPermission(mCallingPid, mCallingUid);
             }
             return mHasSettingsPermission;
         }
@@ -2913,7 +2976,7 @@
             return new LinkProperties(lp);
         }
 
-        if (checkSettingsPermission(callerPid, callerUid)) {
+        if (hasSettingsPermission(callerPid, callerUid)) {
             return new LinkProperties(lp, true /* parcelSensitiveFields */);
         }
 
@@ -2929,7 +2992,7 @@
             int callerUid, String callerPackageName) {
         // There is no need to track the effective UID of the request here. If the caller
         // lacks the settings permission, the effective UID is the same as the calling ID.
-        if (!checkSettingsPermission()) {
+        if (!hasSettingsPermission()) {
             // Unprivileged apps can only pass in null or their own UID.
             if (nc.getUids() == null) {
                 // If the caller passes in null, the callback will also match networks that do not
@@ -3369,7 +3432,8 @@
     public static final String ALLOW_SYSUI_CONNECTIVITY_REPORTS =
             "allow_sysui_connectivity_reports";
 
-    public static final String LOG_BPF_RC = "log_bpf_rc_force_disable";
+    public static final String ALLOW_SATALLITE_NETWORK_FALLBACK =
+            "allow_satallite_network_fallback";
 
     private void enforceInternetPermission() {
         mContext.enforceCallingOrSelfPermission(
@@ -3383,7 +3447,8 @@
                 "ConnectivityService");
     }
 
-    private boolean checkAccessPermission(int pid, int uid) {
+    @CheckResult
+    private boolean hasAccessPermission(int pid, int uid) {
         return mContext.checkPermission(android.Manifest.permission.ACCESS_NETWORK_STATE, pid, uid)
                 == PERMISSION_GRANTED;
     }
@@ -3469,7 +3534,8 @@
                 NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK);
     }
 
-    private boolean checkNetworkFactoryOrSettingsPermission(int pid, int uid) {
+    @CheckResult
+    private boolean hasNetworkFactoryOrSettingsPermission(int pid, int uid) {
         return PERMISSION_GRANTED == mContext.checkPermission(
                 android.Manifest.permission.NETWORK_FACTORY, pid, uid)
                 || PERMISSION_GRANTED == mContext.checkPermission(
@@ -3479,13 +3545,14 @@
                 || UserHandle.getAppId(uid) == Process.BLUETOOTH_UID;
     }
 
-    private boolean checkSettingsPermission() {
-        return PermissionUtils.checkAnyPermissionOf(mContext,
-                android.Manifest.permission.NETWORK_SETTINGS,
+    @CheckResult
+    private boolean hasSettingsPermission() {
+        return hasAnyPermissionOf(mContext, android.Manifest.permission.NETWORK_SETTINGS,
                 NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK);
     }
 
-    private boolean checkSettingsPermission(int pid, int uid) {
+    @CheckResult
+    private boolean hasSettingsPermission(int pid, int uid) {
         return PERMISSION_GRANTED == mContext.checkPermission(
                 android.Manifest.permission.NETWORK_SETTINGS, pid, uid)
                 || PERMISSION_GRANTED == mContext.checkPermission(
@@ -3522,33 +3589,36 @@
                 "ConnectivityService");
     }
 
-    private boolean checkNetworkStackPermission() {
-        return PermissionUtils.checkAnyPermissionOf(mContext,
-                android.Manifest.permission.NETWORK_STACK,
+    @CheckResult
+    private boolean hasNetworkStackPermission() {
+        return hasAnyPermissionOf(mContext, android.Manifest.permission.NETWORK_STACK,
                 NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK);
     }
 
-    private boolean checkNetworkStackPermission(int pid, int uid) {
-        return checkAnyPermissionOf(mContext, pid, uid,
-                android.Manifest.permission.NETWORK_STACK,
+    @CheckResult
+    private boolean hasNetworkStackPermission(int pid, int uid) {
+        return hasAnyPermissionOf(mContext, pid, uid, android.Manifest.permission.NETWORK_STACK,
                 NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK);
     }
 
-    private boolean checkSystemBarServicePermission(int pid, int uid) {
-        return checkAnyPermissionOf(mContext, pid, uid,
+    @CheckResult
+    private boolean hasSystemBarServicePermission(int pid, int uid) {
+        return hasAnyPermissionOf(mContext, pid, uid,
                 android.Manifest.permission.STATUS_BAR_SERVICE);
     }
 
-    private boolean checkNetworkSignalStrengthWakeupPermission(int pid, int uid) {
-        return checkAnyPermissionOf(mContext, pid, uid,
+    @CheckResult
+    private boolean hasNetworkSignalStrengthWakeupPermission(int pid, int uid) {
+        return hasAnyPermissionOf(mContext, pid, uid,
                 android.Manifest.permission.NETWORK_SIGNAL_STRENGTH_WAKEUP,
                 NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK,
                 android.Manifest.permission.NETWORK_SETTINGS);
     }
 
-    private boolean checkConnectivityRestrictedNetworksPermission(int callingUid,
+    @CheckResult
+    private boolean hasConnectivityRestrictedNetworksPermission(int callingUid,
             boolean checkUidsAllowedList) {
-        if (PermissionUtils.checkAnyPermissionOf(mContext,
+        if (hasAnyPermissionOf(mContext,
                 android.Manifest.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS)) {
             return true;
         }
@@ -3556,8 +3626,7 @@
         // fallback to ConnectivityInternalPermission
         // TODO: Remove this fallback check after all apps have declared
         //  CONNECTIVITY_USE_RESTRICTED_NETWORKS.
-        if (PermissionUtils.checkAnyPermissionOf(mContext,
-                android.Manifest.permission.CONNECTIVITY_INTERNAL)) {
+        if (hasAnyPermissionOf(mContext, android.Manifest.permission.CONNECTIVITY_INTERNAL)) {
             return true;
         }
 
@@ -3571,7 +3640,7 @@
 
     private void enforceConnectivityRestrictedNetworksPermission(boolean checkUidsAllowedList) {
         final int callingUid = mDeps.getCallingUid();
-        if (!checkConnectivityRestrictedNetworksPermission(callingUid, checkUidsAllowedList)) {
+        if (!hasConnectivityRestrictedNetworksPermission(callingUid, checkUidsAllowedList)) {
             throw new SecurityException("ConnectivityService: user " + callingUid
                     + " has no permission to access restricted network.");
         }
@@ -3581,7 +3650,8 @@
         mContext.enforceCallingOrSelfPermission(KeepaliveTracker.PERMISSION, "ConnectivityService");
     }
 
-    private boolean checkLocalMacAddressPermission(int pid, int uid) {
+    @CheckResult
+    private boolean hasLocalMacAddressPermission(int pid, int uid) {
         return PERMISSION_GRANTED == mContext.checkPermission(
                 Manifest.permission.LOCAL_MAC_ADDRESS, pid, uid);
     }
@@ -3731,6 +3801,10 @@
             updateMobileDataPreferredUids();
         }
 
+        if (mSatelliteAccessController != null) {
+            mSatelliteAccessController.start();
+        }
+
         // On T+ devices, register callback for statsd to pull NETWORK_BPF_MAP_INFO atom
         if (mDeps.isAtLeastT()) {
             mBpfNetMaps.setPullAtomCallback(mContext);
@@ -3875,12 +3949,13 @@
     @Override
     protected void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter writer,
             @Nullable String[] args) {
-        if (!checkDumpPermission(mContext, TAG, writer)) return;
+        if (!hasDumpPermission(mContext, TAG, writer)) return;
 
         mPriorityDumper.dump(fd, writer, args);
     }
 
-    private boolean checkDumpPermission(Context context, String tag, PrintWriter pw) {
+    @CheckResult
+    private boolean hasDumpPermission(Context context, String tag, PrintWriter pw) {
         if (context.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
                 != PackageManager.PERMISSION_GRANTED) {
             pw.println("Permission Denial: can't dump " + tag + " from from pid="
@@ -5312,6 +5387,13 @@
         return false;
     }
 
+    private int getSubscriptionIdFromNetworkCaps(@NonNull final NetworkCapabilities caps) {
+        if (mCarrierPrivilegeAuthenticator != null) {
+            return mCarrierPrivilegeAuthenticator.getSubIdFromNetworkCapabilities(caps);
+        }
+        return SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+    }
+
     private void handleRegisterNetworkRequestWithIntent(@NonNull final Message msg) {
         final NetworkRequestInfo nri = (NetworkRequestInfo) (msg.obj);
         // handleRegisterNetworkRequestWithIntent() doesn't apply to multilayer requests.
@@ -5697,7 +5779,7 @@
     }
 
     private RequestInfoPerUidCounter getRequestCounter(NetworkRequestInfo nri) {
-        return checkAnyPermissionOf(mContext,
+        return hasAnyPermissionOf(mContext,
                 nri.mPid, nri.mUid, NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK)
                 ? mSystemNetworkRequestCounter : mNetworkRequestCounter;
     }
@@ -5921,7 +6003,7 @@
             if (nm == null) return;
 
             if (request == CaptivePortal.APP_REQUEST_REEVALUATION_REQUIRED) {
-                checkNetworkStackPermission();
+                enforceNetworkStackPermission(mContext);
                 nm.forceReevaluation(mDeps.getCallingUid());
             }
         }
@@ -5951,7 +6033,7 @@
      * @see MultinetworkPolicyTracker#getAvoidBadWifi()
      */
     public boolean shouldAvoidBadWifi() {
-        if (!checkNetworkStackPermission()) {
+        if (!hasNetworkStackPermission()) {
             throw new SecurityException("avoidBadWifi requires NETWORK_STACK permission");
         }
         return avoidBadWifi();
@@ -6402,6 +6484,9 @@
                     UidFrozenStateChangedArgs args = (UidFrozenStateChangedArgs) msg.obj;
                     handleFrozenUids(args.mUids, args.mFrozenStates);
                     break;
+                case EVENT_UID_CARRIER_PRIVILEGES_LOST:
+                    handleUidCarrierPrivilegesLost(msg.arg1, msg.arg2);
+                    break;
             }
         }
     }
@@ -7471,20 +7556,25 @@
     // specific SSID/SignalStrength, or the calling app has permission to do so.
     private void ensureSufficientPermissionsForRequest(NetworkCapabilities nc,
             int callerPid, int callerUid, String callerPackageName) {
-        if (null != nc.getSsid() && !checkSettingsPermission(callerPid, callerUid)) {
+        if (null != nc.getSsid() && !hasSettingsPermission(callerPid, callerUid)) {
             throw new SecurityException("Insufficient permissions to request a specific SSID");
         }
 
         if (nc.hasSignalStrength()
-                && !checkNetworkSignalStrengthWakeupPermission(callerPid, callerUid)) {
+                && !hasNetworkSignalStrengthWakeupPermission(callerPid, callerUid)) {
             throw new SecurityException(
                     "Insufficient permissions to request a specific signal strength");
         }
         mAppOpsManager.checkPackage(callerUid, callerPackageName);
 
-        if (!nc.getSubscriptionIds().isEmpty()) {
-            enforceNetworkFactoryPermission();
+        if (nc.getSubscriptionIds().isEmpty()) {
+            return;
         }
+        if (mRequestRestrictedWifiEnabled
+                && canRequestRestrictedNetworkDueToCarrierPrivileges(nc, callerUid)) {
+            return;
+        }
+        enforceNetworkFactoryPermission();
     }
 
     private int[] getSignalStrengthThresholds(@NonNull final NetworkAgentInfo nai) {
@@ -7574,7 +7664,7 @@
             int reqTypeInt, Messenger messenger, int timeoutMs, final IBinder binder,
             int legacyType, int callbackFlags, @NonNull String callingPackageName,
             @Nullable String callingAttributionTag) {
-        if (legacyType != TYPE_NONE && !checkNetworkStackPermission()) {
+        if (legacyType != TYPE_NONE && !hasNetworkStackPermission()) {
             if (isTargetSdkAtleast(Build.VERSION_CODES.M, mDeps.getCallingUid(),
                     callingPackageName)) {
                 throw new SecurityException("Insufficient permissions to specify legacy type");
@@ -7764,6 +7854,22 @@
         applicationNetworkCapabilities.enforceSelfCertifiedNetworkCapabilitiesDeclared(
                 networkCapabilities);
     }
+
+    private boolean canRequestRestrictedNetworkDueToCarrierPrivileges(
+            NetworkCapabilities networkCapabilities, int callingUid) {
+        if (mRequestRestrictedWifiEnabled) {
+            // For U+ devices, callers with carrier privilege could request restricted networks
+            // with CBS capabilities, or any restricted WiFi networks.
+            return ((networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_CBS)
+                || networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI))
+                && hasCarrierPrivilegeForNetworkCaps(callingUid, networkCapabilities));
+        } else {
+            // For T+ devices, callers with carrier privilege could request with CBS
+            // capabilities.
+            return (networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_CBS)
+                && hasCarrierPrivilegeForNetworkCaps(callingUid, networkCapabilities));
+        }
+    }
     private void enforceNetworkRequestPermissions(NetworkCapabilities networkCapabilities,
             String callingPackageName, String callingAttributionTag, final int callingUid) {
         if (shouldCheckCapabilitiesDeclaration(networkCapabilities, callingUid,
@@ -7771,13 +7877,11 @@
             enforceRequestCapabilitiesDeclaration(callingPackageName, networkCapabilities,
                     callingUid);
         }
-        if (networkCapabilities.hasCapability(NET_CAPABILITY_NOT_RESTRICTED) == false) {
-            // For T+ devices, callers with carrier privilege could request with CBS capabilities.
-            if (networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_CBS)
-                    && hasCarrierPrivilegeForNetworkCaps(callingUid, networkCapabilities)) {
-                return;
+        if (!networkCapabilities.hasCapability(NET_CAPABILITY_NOT_RESTRICTED)) {
+            if (!canRequestRestrictedNetworkDueToCarrierPrivileges(
+                    networkCapabilities, callingUid)) {
+                enforceConnectivityRestrictedNetworksPermission(true /* checkUidsAllowedList */);
             }
-            enforceConnectivityRestrictedNetworksPermission(true /* checkUidsAllowedList */);
         } else {
             enforceChangePermission(callingPackageName, callingAttributionTag);
         }
@@ -9044,6 +9148,40 @@
         }
     }
 
+    private void handleUidCarrierPrivilegesLost(int uid, int subId) {
+        ensureRunningOnConnectivityServiceThread();
+        // A NetworkRequest needs to be revoked when all the conditions are met
+        //   1. It requests restricted network
+        //   2. The requestor uid matches the uid with the callback
+        //   3. The app doesn't have Carrier Privileges
+        //   4. The app doesn't have permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS
+        for (final NetworkRequest nr : mNetworkRequests.keySet()) {
+            if ((nr.isRequest() || nr.isListen())
+                    && !nr.hasCapability(NET_CAPABILITY_NOT_RESTRICTED)
+                    && nr.getRequestorUid() == uid
+                    && getSubscriptionIdFromNetworkCaps(nr.networkCapabilities) == subId
+                    && !hasConnectivityRestrictedNetworksPermission(uid, true)) {
+                declareNetworkRequestUnfulfillable(nr);
+            }
+        }
+
+        // A NetworkAgent's allowedUids may need to be updated if the app has lost
+        // carrier config
+        for (final NetworkAgentInfo nai : mNetworkAgentInfos) {
+            if (nai.networkCapabilities.getAllowedUidsNoCopy().contains(uid)
+                    && getSubscriptionIdFromNetworkCaps(nai.networkCapabilities) == subId) {
+                final NetworkCapabilities nc = new NetworkCapabilities(nai.networkCapabilities);
+                NetworkAgentInfo.restrictCapabilitiesFromNetworkAgent(
+                        nc,
+                        uid,
+                        false /* hasAutomotiveFeature (irrelevant) */,
+                        mDeps,
+                        mCarrierPrivilegeAuthenticator);
+                updateCapabilities(nai.getScore(), nai, nc);
+            }
+        }
+    }
+
     /**
      * Update the NetworkCapabilities for {@code nai} to {@code nc}. Specifically:
      *
@@ -9491,7 +9629,6 @@
         final ArraySet<Integer> toAdd = new ArraySet<>(newUids);
         toRemove.removeAll(newUids);
         toAdd.removeAll(prevUids);
-
         try {
             if (!toAdd.isEmpty()) {
                 mNetd.networkAddUidRangesParcel(new NativeUidRangeConfig(
@@ -11137,17 +11274,28 @@
                 err.getFileDescriptor(), args);
     }
 
-    private Boolean parseBooleanArgument(final String arg) {
-        if ("true".equals(arg)) {
-            return true;
-        } else if ("false".equals(arg)) {
-            return false;
-        } else {
-            return null;
-        }
-    }
-
     private class ShellCmd extends BasicShellCommandHandler {
+
+        private Boolean parseBooleanArgument(final String arg) {
+            if ("true".equals(arg)) {
+                return true;
+            } else if ("false".equals(arg)) {
+                return false;
+            } else {
+                getOutPrintWriter().println("Invalid boolean argument: " + arg);
+                return null;
+            }
+        }
+
+        private Integer parseIntegerArgument(final String arg) {
+            try {
+                return Integer.valueOf(arg);
+            } catch (NumberFormatException ne) {
+                getOutPrintWriter().println("Invalid integer argument: " + arg);
+                return null;
+            }
+        }
+
         @Override
         public int onCommand(String cmd) {
             if (cmd == null) {
@@ -11224,6 +11372,38 @@
                         }
                         return 0;
                     }
+                    case "set-background-networking-enabled-for-uid": {
+                        final Integer uid = parseIntegerArgument(getNextArg());
+                        final Boolean enabled = parseBooleanArgument(getNextArg());
+                        if (null == enabled || null == uid) {
+                            onHelp();
+                            return -1;
+                        }
+                        final int rule = enabled ? FIREWALL_RULE_ALLOW : FIREWALL_RULE_DEFAULT;
+                        setUidFirewallRule(FIREWALL_CHAIN_BACKGROUND, uid, rule);
+                        final String msg = (enabled ? "Enabled" : "Disabled")
+                                + " background networking for  uid " + uid;
+                        Log.i(TAG, msg);
+                        pw.println(msg);
+                        return 0;
+                    }
+                    case "get-background-networking-enabled-for-uid": {
+                        final Integer uid = parseIntegerArgument(getNextArg());
+                        if (null == uid) {
+                            onHelp();
+                            return -1;
+                        }
+                        final int rule = getUidFirewallRule(FIREWALL_CHAIN_BACKGROUND, uid);
+                        if (FIREWALL_RULE_ALLOW == rule) {
+                            pw.println(uid + ": allow");
+                        } else if (FIREWALL_RULE_DENY == rule  || FIREWALL_RULE_DEFAULT == rule) {
+                            pw.println(uid + ": deny");
+                        } else {
+                            throw new IllegalStateException(
+                                    "Unknown rule " + rule + " for uid " + uid);
+                        }
+                        return 0;
+                    }
                     case "reevaluate":
                         // Usage : adb shell cmd connectivity reevaluate <netId>
                         // If netId is omitted, then reevaluate the default network
@@ -11284,6 +11464,10 @@
                     + "    no effect if the chain is disabled.");
             pw.println("  get-package-networking-enabled [package name]");
             pw.println("    Get the deny bit in FIREWALL_CHAIN_OEM_DENY_3 for package.");
+            pw.println("  set-background-networking-enabled-for-uid [uid] [true|false]");
+            pw.println("    Set the allow bit in FIREWALL_CHAIN_BACKGROUND for the given uid.");
+            pw.println("  get-background-networking-enabled-for-uid [uid]");
+            pw.println("    Get the allow bit in FIREWALL_CHAIN_BACKGROUND for the given uid.");
         }
     }
 
@@ -11324,7 +11508,7 @@
 
         // Connection owner UIDs are visible only to the network stack and to the VpnService-based
         // VPN, if any, that applies to the UID that owns the connection.
-        if (checkNetworkStackPermission()) return uid;
+        if (hasNetworkStackPermission()) return uid;
 
         final NetworkAgentInfo vpn = getVpnForUid(uid);
         if (vpn == null || getVpnType(vpn) != VpnManager.TYPE_VPN_SERVICE
@@ -11584,7 +11768,7 @@
             if (report == null) {
                 continue;
             }
-            if (!checkConnectivityDiagnosticsPermissions(
+            if (!hasConnectivityDiagnosticsPermissions(
                     nri.mPid, nri.mUid, nai, cbInfo.mCallingPackageName)) {
                 continue;
             }
@@ -11747,7 +11931,7 @@
                 continue;
             }
 
-            if (!checkConnectivityDiagnosticsPermissions(
+            if (!hasConnectivityDiagnosticsPermissions(
                     nri.mPid, nri.mUid, nai, cbInfo.mCallingPackageName)) {
                 continue;
             }
@@ -11791,14 +11975,15 @@
         return false;
     }
 
+    @CheckResult
     @VisibleForTesting
-    boolean checkConnectivityDiagnosticsPermissions(
+    boolean hasConnectivityDiagnosticsPermissions(
             int callbackPid, int callbackUid, NetworkAgentInfo nai, String callbackPackageName) {
-        if (checkNetworkStackPermission(callbackPid, callbackUid)) {
+        if (hasNetworkStackPermission(callbackPid, callbackUid)) {
             return true;
         }
         if (mAllowSysUiConnectivityReports
-                && checkSystemBarServicePermission(callbackPid, callbackUid)) {
+                && hasSystemBarServicePermission(callbackPid, callbackUid)) {
             return true;
         }
 
@@ -12664,16 +12849,27 @@
 
     @VisibleForTesting
     @NonNull
-    ArraySet<NetworkRequestInfo> createNrisFromMobileDataPreferredUids(
-            @NonNull final Set<Integer> uids) {
+    ArraySet<NetworkRequestInfo> createNrisForPreferenceOrder(@NonNull final Set<Integer> uids,
+            @NonNull final List<NetworkRequest> requests,
+            final int preferenceOrder) {
         final ArraySet<NetworkRequestInfo> nris = new ArraySet<>();
         if (uids.size() == 0) {
             // Should not create NetworkRequestInfo if no preferences. Without uid range in
             // NetworkRequestInfo, makeDefaultForApps() would treat it as a illegal NRI.
-            if (DBG) log("Don't create NetworkRequestInfo because no preferences");
             return nris;
         }
 
+        final Set<UidRange> ranges = new ArraySet<>();
+        for (final int uid : uids) {
+            ranges.add(new UidRange(uid, uid));
+        }
+        setNetworkRequestUids(requests, ranges);
+        nris.add(new NetworkRequestInfo(Process.myUid(), requests, preferenceOrder));
+        return nris;
+    }
+
+    ArraySet<NetworkRequestInfo> createNrisFromMobileDataPreferredUids(
+            @NonNull final Set<Integer> uids) {
         final List<NetworkRequest> requests = new ArrayList<>();
         // The NRI should be comprised of two layers:
         // - The request for the mobile network preferred.
@@ -12682,14 +12878,28 @@
                 TRANSPORT_CELLULAR, NetworkRequest.Type.REQUEST));
         requests.add(createDefaultInternetRequestForTransport(
                 TYPE_NONE, NetworkRequest.Type.TRACK_DEFAULT));
-        final Set<UidRange> ranges = new ArraySet<>();
-        for (final int uid : uids) {
-            ranges.add(new UidRange(uid, uid));
-        }
-        setNetworkRequestUids(requests, ranges);
-        nris.add(new NetworkRequestInfo(Process.myUid(), requests,
-                PREFERENCE_ORDER_MOBILE_DATA_PREFERERRED));
-        return nris;
+        return createNrisForPreferenceOrder(uids, requests, PREFERENCE_ORDER_MOBILE_DATA_PREFERERRED
+        );
+    }
+
+    ArraySet<NetworkRequestInfo> createMultiLayerNrisFromSatelliteNetworkFallbackUids(
+            @NonNull final Set<Integer> uids) {
+        final List<NetworkRequest> requests = new ArrayList<>();
+
+        // request: track default(unrestricted internet network)
+        requests.add(createDefaultInternetRequestForTransport(
+                TYPE_NONE, NetworkRequest.Type.TRACK_DEFAULT));
+
+        // request: restricted Satellite internet
+        final NetworkCapabilities cap = new NetworkCapabilities.Builder()
+                .addCapability(NET_CAPABILITY_INTERNET)
+                .addCapability(NET_CAPABILITY_NOT_VCN_MANAGED)
+                .removeCapability(NET_CAPABILITY_NOT_RESTRICTED)
+                .addTransportType(NetworkCapabilities.TRANSPORT_SATELLITE)
+                .build();
+        requests.add(createNetworkRequest(NetworkRequest.Type.REQUEST, cap));
+
+        return createNrisForPreferenceOrder(uids, requests, PREFERENCE_ORDER_SATELLITE_FALLBACK);
     }
 
     private void handleMobileDataPreferredUidsChanged() {
@@ -12701,6 +12911,16 @@
         rematchAllNetworksAndRequests();
     }
 
+    private void handleSetSatelliteNetworkPreference(
+            @NonNull final Set<Integer> satelliteNetworkPreferredUids) {
+        removeDefaultNetworkRequestsForPreference(PREFERENCE_ORDER_SATELLITE_FALLBACK);
+        addPerAppDefaultNetworkRequests(
+                createMultiLayerNrisFromSatelliteNetworkFallbackUids(satelliteNetworkPreferredUids)
+        );
+        // Finally, rematch.
+        rematchAllNetworksAndRequests();
+    }
+
     private void handleIngressRateLimitChanged() {
         final long oldIngressRateLimit = mIngressRateLimit;
         mIngressRateLimit = ConnectivitySettingsManager.getIngressRateLimitInBytesPerSecond(
diff --git a/service/src/com/android/server/connectivity/CarrierPrivilegeAuthenticator.java b/service/src/com/android/server/connectivity/CarrierPrivilegeAuthenticator.java
index 5705ebe..04d0fc1 100644
--- a/service/src/com/android/server/connectivity/CarrierPrivilegeAuthenticator.java
+++ b/service/src/com/android/server/connectivity/CarrierPrivilegeAuthenticator.java
@@ -40,12 +40,13 @@
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 import android.util.Log;
-import android.util.SparseIntArray;
+import android.util.SparseArray;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.modules.utils.HandlerExecutor;
+import com.android.modules.utils.build.SdkLevel;
 import com.android.net.module.util.DeviceConfigUtils;
 import com.android.networkstack.apishim.TelephonyManagerShimImpl;
 import com.android.networkstack.apishim.common.TelephonyManagerShim;
@@ -55,6 +56,7 @@
 import java.util.ArrayList;
 import java.util.List;
 import java.util.concurrent.Executor;
+import java.util.function.BiConsumer;
 
 /**
  * Tracks the uid of the carrier privileged app that provides the carrier config.
@@ -71,7 +73,8 @@
     private final TelephonyManagerShim mTelephonyManagerShim;
     private final TelephonyManager mTelephonyManager;
     @GuardedBy("mLock")
-    private final SparseIntArray mCarrierServiceUid = new SparseIntArray(2 /* initialCapacity */);
+    private final SparseArray<CarrierServiceUidWithSubId> mCarrierServiceUidWithSubId =
+            new SparseArray<>(2 /* initialCapacity */);
     @GuardedBy("mLock")
     private int mModemCount = 0;
     private final Object mLock = new Object();
@@ -79,11 +82,16 @@
     @NonNull
     private final List<PrivilegeListener> mCarrierPrivilegesChangedListeners = new ArrayList<>();
     private final boolean mUseCallbacksForServiceChanged;
+    private final boolean mRequestRestrictedWifiEnabled;
+    @NonNull
+    private final BiConsumer<Integer, Integer> mListener;
 
     public CarrierPrivilegeAuthenticator(@NonNull final Context c,
             @NonNull final Dependencies deps,
             @NonNull final TelephonyManager t,
-            @NonNull final TelephonyManagerShim telephonyManagerShim) {
+            @NonNull final TelephonyManagerShim telephonyManagerShim,
+            final boolean requestRestrictedWifiEnabled,
+            @NonNull BiConsumer<Integer, Integer> listener) {
         mContext = c;
         mTelephonyManager = t;
         mTelephonyManagerShim = telephonyManagerShim;
@@ -92,6 +100,8 @@
         mHandler = new Handler(thread.getLooper());
         mUseCallbacksForServiceChanged = deps.isFeatureEnabled(
                 c, CARRIER_SERVICE_CHANGED_USE_CALLBACK);
+        mRequestRestrictedWifiEnabled = requestRestrictedWifiEnabled;
+        mListener = listener;
         final IntentFilter filter = new IntentFilter();
         filter.addAction(TelephonyManager.ACTION_MULTI_SIM_CONFIG_CHANGED);
         synchronized (mLock) {
@@ -113,8 +123,10 @@
     }
 
     public CarrierPrivilegeAuthenticator(@NonNull final Context c,
-            @NonNull final TelephonyManager t) {
-        this(c, new Dependencies(), t, TelephonyManagerShimImpl.newInstance(t));
+            @NonNull final TelephonyManager t, final boolean requestRestrictedWifiEnabled,
+            @NonNull BiConsumer<Integer, Integer> listener) {
+        this(c, new Dependencies(), t, TelephonyManagerShimImpl.newInstance(t),
+                requestRestrictedWifiEnabled, listener);
     }
 
     public static class Dependencies {
@@ -142,6 +154,29 @@
         }
     }
 
+    private static class CarrierServiceUidWithSubId {
+        final int mUid;
+        final int mSubId;
+
+        CarrierServiceUidWithSubId(int uid, int subId) {
+            mUid = uid;
+            mSubId = subId;
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (!(obj instanceof CarrierServiceUidWithSubId)) {
+                return false;
+            }
+            CarrierServiceUidWithSubId compare = (CarrierServiceUidWithSubId) obj;
+            return (mUid == compare.mUid && mSubId == compare.mSubId);
+        }
+
+        @Override
+        public int hashCode() {
+            return mUid * 31 + mSubId;
+        }
+    }
     private class PrivilegeListener implements CarrierPrivilegesListenerShim {
         public final int mLogicalSlot;
 
@@ -171,7 +206,18 @@
                 return;
             }
             synchronized (mLock) {
-                mCarrierServiceUid.put(mLogicalSlot, carrierServiceUid);
+                CarrierServiceUidWithSubId oldPair =
+                        mCarrierServiceUidWithSubId.get(mLogicalSlot);
+                int subId = getSubId(mLogicalSlot);
+                mCarrierServiceUidWithSubId.put(
+                        mLogicalSlot,
+                        new CarrierServiceUidWithSubId(carrierServiceUid, subId));
+                if (oldPair != null
+                        && oldPair.mUid != Process.INVALID_UID
+                        && oldPair.mSubId != SubscriptionManager.INVALID_SUBSCRIPTION_ID
+                        && !oldPair.equals(mCarrierServiceUidWithSubId.get(mLogicalSlot))) {
+                    mListener.accept(oldPair.mUid, oldPair.mSubId);
+                }
             }
         }
     }
@@ -193,7 +239,14 @@
     private void unregisterCarrierPrivilegesListeners() {
         for (PrivilegeListener carrierPrivilegesListener : mCarrierPrivilegesChangedListeners) {
             removeCarrierPrivilegesListener(carrierPrivilegesListener);
-            mCarrierServiceUid.delete(carrierPrivilegesListener.mLogicalSlot);
+            CarrierServiceUidWithSubId oldPair =
+                    mCarrierServiceUidWithSubId.get(carrierPrivilegesListener.mLogicalSlot);
+            mCarrierServiceUidWithSubId.remove(carrierPrivilegesListener.mLogicalSlot);
+            if (oldPair != null
+                    && oldPair.mUid != Process.INVALID_UID
+                    && oldPair.mSubId != SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
+                mListener.accept(oldPair.mUid, oldPair.mSubId);
+            }
         }
         mCarrierPrivilegesChangedListeners.clear();
     }
@@ -230,8 +283,24 @@
      */
     public boolean isCarrierServiceUidForNetworkCapabilities(int callingUid,
             @NonNull NetworkCapabilities networkCapabilities) {
-        if (callingUid == Process.INVALID_UID) return false;
-        final int subId;
+        if (callingUid == Process.INVALID_UID) {
+            return false;
+        }
+        int subId = getSubIdFromNetworkCapabilities(networkCapabilities);
+        if (SubscriptionManager.INVALID_SUBSCRIPTION_ID == subId) {
+            return false;
+        }
+        return callingUid == getCarrierServiceUidForSubId(subId);
+    }
+
+    /**
+     * Extract the SubscriptionId from the NetworkCapabilities.
+     *
+     * @param networkCapabilities the network capabilities which may contains the SubscriptionId.
+     * @return the SubscriptionId.
+     */
+    public int getSubIdFromNetworkCapabilities(@NonNull NetworkCapabilities networkCapabilities) {
+        int subId;
         if (networkCapabilities.hasSingleTransportBesidesTest(TRANSPORT_CELLULAR)) {
             subId = getSubIdFromTelephonySpecifier(networkCapabilities.getNetworkSpecifier());
         } else if (networkCapabilities.hasSingleTransportBesidesTest(TRANSPORT_WIFI)) {
@@ -239,6 +308,12 @@
         } else {
             subId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
         }
+        if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID
+                && mRequestRestrictedWifiEnabled
+                && networkCapabilities.getSubscriptionIds().size() == 1) {
+            subId = networkCapabilities.getSubscriptionIds().toArray(new Integer[0])[0];
+        }
+
         if (subId != SubscriptionManager.INVALID_SUBSCRIPTION_ID
                 && !networkCapabilities.getSubscriptionIds().contains(subId)) {
             // Ideally, the code above should just use networkCapabilities.getSubscriptionIds()
@@ -250,34 +325,60 @@
             Log.wtf(TAG, "NetworkCapabilities subIds are inconsistent between "
                     + "specifier/transportInfo and mSubIds : " + networkCapabilities);
         }
-        if (SubscriptionManager.INVALID_SUBSCRIPTION_ID == subId) return false;
-        return callingUid == getCarrierServiceUidForSubId(subId);
+        return subId;
+    }
+
+    @VisibleForTesting
+    protected int getSubId(int slotIndex) {
+        if (SdkLevel.isAtLeastU()) {
+            return SubscriptionManager.getSubscriptionId(slotIndex);
+        } else {
+            SubscriptionManager sm = mContext.getSystemService(SubscriptionManager.class);
+            int[] subIds = sm.getSubscriptionIds(slotIndex);
+            if (subIds != null && subIds.length > 0) {
+                return subIds[0];
+            }
+            return SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+        }
     }
 
     @VisibleForTesting
     void updateCarrierServiceUid() {
         synchronized (mLock) {
-            mCarrierServiceUid.clear();
+            SparseArray<CarrierServiceUidWithSubId> copy = mCarrierServiceUidWithSubId.clone();
+            mCarrierServiceUidWithSubId.clear();
             for (int i = 0; i < mModemCount; i++) {
-                mCarrierServiceUid.put(i, getCarrierServicePackageUidForSlot(i));
+                int subId = getSubId(i);
+                mCarrierServiceUidWithSubId.put(
+                        i,
+                        new CarrierServiceUidWithSubId(
+                                getCarrierServicePackageUidForSlot(i), subId));
+            }
+            for (int i = 0; i < copy.size(); ++i) {
+                CarrierServiceUidWithSubId oldPair = copy.valueAt(i);
+                CarrierServiceUidWithSubId newPair = mCarrierServiceUidWithSubId.get(copy.keyAt(i));
+                if (oldPair.mUid != Process.INVALID_UID
+                        && oldPair.mSubId != SubscriptionManager.INVALID_SUBSCRIPTION_ID
+                        && !oldPair.equals(newPair)) {
+                    mListener.accept(oldPair.mUid, oldPair.mSubId);
+                }
             }
         }
     }
 
     @VisibleForTesting
     int getCarrierServiceUidForSubId(int subId) {
-        final int slotId = getSlotIndex(subId);
         synchronized (mLock) {
-            return mCarrierServiceUid.get(slotId, Process.INVALID_UID);
+            for (int i = 0; i < mCarrierServiceUidWithSubId.size(); ++i) {
+                if (mCarrierServiceUidWithSubId.valueAt(i).mSubId == subId) {
+                    return mCarrierServiceUidWithSubId.valueAt(i).mUid;
+                }
+            }
+            return Process.INVALID_UID;
         }
     }
 
     @VisibleForTesting
-    protected int getSlotIndex(int subId) {
-        return SubscriptionManager.getSlotIndex(subId);
-    }
-
-    @VisibleForTesting
     int getUidForPackage(String pkgName) {
         if (pkgName == null) {
             return Process.INVALID_UID;
@@ -340,12 +441,14 @@
 
     public void dump(IndentingPrintWriter pw) {
         pw.println("CarrierPrivilegeAuthenticator:");
+        pw.println("mRequestRestrictedWifiEnabled = " + mRequestRestrictedWifiEnabled);
         synchronized (mLock) {
-            final int size = mCarrierServiceUid.size();
-            for (int i = 0; i < size; ++i) {
-                final int logicalSlot = mCarrierServiceUid.keyAt(i);
-                final int serviceUid = mCarrierServiceUid.valueAt(i);
-                pw.println("Logical slot = " + logicalSlot + " : uid = " + serviceUid);
+            for (int i = 0; i < mCarrierServiceUidWithSubId.size(); ++i) {
+                final int logicalSlot = mCarrierServiceUidWithSubId.keyAt(i);
+                final int serviceUid = mCarrierServiceUidWithSubId.valueAt(i).mUid;
+                final int subId = mCarrierServiceUidWithSubId.valueAt(i).mSubId;
+                pw.println("Logical slot = " + logicalSlot + " : uid = " + serviceUid
+                        + " : subId = " + subId);
             }
         }
     }
diff --git a/service/src/com/android/server/connectivity/ClatCoordinator.java b/service/src/com/android/server/connectivity/ClatCoordinator.java
index 17de146..daaf91d 100644
--- a/service/src/com/android/server/connectivity/ClatCoordinator.java
+++ b/service/src/com/android/server/connectivity/ClatCoordinator.java
@@ -256,7 +256,7 @@
         public IBpfMap<ClatIngress6Key, ClatIngress6Value> getBpfIngress6Map() {
             try {
                 return new BpfMap<>(CLAT_INGRESS6_MAP_PATH,
-                    BpfMap.BPF_F_RDWR, ClatIngress6Key.class, ClatIngress6Value.class);
+                       ClatIngress6Key.class, ClatIngress6Value.class);
             } catch (ErrnoException e) {
                 Log.e(TAG, "Cannot create ingress6 map: " + e);
                 return null;
@@ -268,7 +268,7 @@
         public IBpfMap<ClatEgress4Key, ClatEgress4Value> getBpfEgress4Map() {
             try {
                 return new BpfMap<>(CLAT_EGRESS4_MAP_PATH,
-                    BpfMap.BPF_F_RDWR, ClatEgress4Key.class, ClatEgress4Value.class);
+                       ClatEgress4Key.class, ClatEgress4Value.class);
             } catch (ErrnoException e) {
                 Log.e(TAG, "Cannot create egress4 map: " + e);
                 return null;
@@ -280,7 +280,7 @@
         public IBpfMap<CookieTagMapKey, CookieTagMapValue> getBpfCookieTagMap() {
             try {
                 return new BpfMap<>(COOKIE_TAG_MAP_PATH,
-                        BpfMap.BPF_F_RDWR, CookieTagMapKey.class, CookieTagMapValue.class);
+                       CookieTagMapKey.class, CookieTagMapValue.class);
             } catch (ErrnoException e) {
                 Log.wtf(TAG, "Cannot open cookie tag map: " + e);
                 return null;
diff --git a/service/src/com/android/server/connectivity/ConnectivityFlags.java b/service/src/com/android/server/connectivity/ConnectivityFlags.java
index f8f76ef..bf09160 100644
--- a/service/src/com/android/server/connectivity/ConnectivityFlags.java
+++ b/service/src/com/android/server/connectivity/ConnectivityFlags.java
@@ -36,6 +36,8 @@
     public static final String CARRIER_SERVICE_CHANGED_USE_CALLBACK =
             "carrier_service_changed_use_callback_version";
 
+    public static final String REQUEST_RESTRICTED_WIFI =
+            "request_restricted_wifi";
     private boolean mNoRematchAllRequestsOnRegister;
 
     /**
diff --git a/service/src/com/android/server/connectivity/DscpPolicyTracker.java b/service/src/com/android/server/connectivity/DscpPolicyTracker.java
index 8d566b6..15d6adb 100644
--- a/service/src/com/android/server/connectivity/DscpPolicyTracker.java
+++ b/service/src/com/android/server/connectivity/DscpPolicyTracker.java
@@ -85,10 +85,10 @@
     public DscpPolicyTracker() throws ErrnoException {
         mAttachedIfaces = new HashSet<String>();
         mIfaceIndexToPolicyIdBpfMapIndex = new HashMap<Integer, SparseIntArray>();
-        mBpfDscpIpv4Policies = new BpfMap<Struct.S32, DscpPolicyValue>(IPV4_POLICY_MAP_PATH,
-                BpfMap.BPF_F_RDWR, Struct.S32.class, DscpPolicyValue.class);
-        mBpfDscpIpv6Policies = new BpfMap<Struct.S32, DscpPolicyValue>(IPV6_POLICY_MAP_PATH,
-                BpfMap.BPF_F_RDWR, Struct.S32.class, DscpPolicyValue.class);
+        mBpfDscpIpv4Policies = new BpfMap<>(IPV4_POLICY_MAP_PATH,
+                Struct.S32.class, DscpPolicyValue.class);
+        mBpfDscpIpv6Policies = new BpfMap<>(IPV6_POLICY_MAP_PATH,
+                Struct.S32.class, DscpPolicyValue.class);
     }
 
     private boolean isUnusedIndex(int index) {
diff --git a/service/src/com/android/server/connectivity/KeepaliveStatsTracker.java b/service/src/com/android/server/connectivity/KeepaliveStatsTracker.java
index 7a8b41b..48af9fa 100644
--- a/service/src/com/android/server/connectivity/KeepaliveStatsTracker.java
+++ b/service/src/com/android/server/connectivity/KeepaliveStatsTracker.java
@@ -74,9 +74,10 @@
 public class KeepaliveStatsTracker {
     private static final String TAG = KeepaliveStatsTracker.class.getSimpleName();
     private static final int INVALID_KEEPALIVE_ID = -1;
-    // 1 hour acceptable deviation in metrics collection duration time.
+    // 2 hour acceptable deviation in metrics collection duration time to account for the 1 hour
+    // window of AlarmManager.
     private static final long MAX_EXPECTED_DURATION_MS =
-            AutomaticOnOffKeepaliveTracker.METRICS_COLLECTION_DURATION_MS + 1 * 60 * 60 * 1_000L;
+            AutomaticOnOffKeepaliveTracker.METRICS_COLLECTION_DURATION_MS + 2 * 60 * 60 * 1_000L;
 
     @NonNull private final Handler mConnectivityServiceHandler;
     @NonNull private final Dependencies mDependencies;
diff --git a/service/src/com/android/server/connectivity/NetworkAgentInfo.java b/service/src/com/android/server/connectivity/NetworkAgentInfo.java
index 50cad45..76993a6 100644
--- a/service/src/com/android/server/connectivity/NetworkAgentInfo.java
+++ b/service/src/com/android/server/connectivity/NetworkAgentInfo.java
@@ -1551,7 +1551,7 @@
      * @param hasAutomotiveFeature true if this device has the automotive feature, false otherwise
      * @param authenticator the carrier privilege authenticator to check for telephony constraints
      */
-    public void restrictCapabilitiesFromNetworkAgent(@NonNull final NetworkCapabilities nc,
+    public static void restrictCapabilitiesFromNetworkAgent(@NonNull final NetworkCapabilities nc,
             final int creatorUid, final boolean hasAutomotiveFeature,
             @NonNull final ConnectivityService.Dependencies deps,
             @Nullable final CarrierPrivilegeAuthenticator authenticator) {
@@ -1564,7 +1564,7 @@
         }
     }
 
-    private boolean areAllowedUidsAcceptableFromNetworkAgent(
+    private static boolean areAllowedUidsAcceptableFromNetworkAgent(
             @NonNull final NetworkCapabilities nc, final boolean hasAutomotiveFeature,
             @NonNull final ConnectivityService.Dependencies deps,
             @Nullable final CarrierPrivilegeAuthenticator carrierPrivilegeAuthenticator) {
diff --git a/service/src/com/android/server/connectivity/SatelliteAccessController.java b/service/src/com/android/server/connectivity/SatelliteAccessController.java
new file mode 100644
index 0000000..b53abce
--- /dev/null
+++ b/service/src/com/android/server/connectivity/SatelliteAccessController.java
@@ -0,0 +1,236 @@
+/*
+ * 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.
+ */
+
+package com.android.server.connectivity;
+
+import android.Manifest;
+import android.annotation.NonNull;
+import android.app.role.OnRoleHoldersChangedListener;
+import android.app.role.RoleManager;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.os.Handler;
+import android.os.Process;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.util.ArraySet;
+import android.util.Log;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.Executor;
+import java.util.function.Consumer;
+
+/**
+ * Tracks the uid of all the default messaging application which are role_sms role and
+ * satellite_communication permission complaint and requests ConnectivityService to create multi
+ * layer request with satellite internet access support for the default message application.
+ * @hide
+ */
+public class SatelliteAccessController {
+    private static final String TAG = SatelliteAccessController.class.getSimpleName();
+    private final Context mContext;
+    private final Dependencies mDeps;
+    private final DefaultMessageRoleListener mDefaultMessageRoleListener;
+    private final UserManager mUserManager;
+    private final Consumer<Set<Integer>> mCallback;
+    private final Handler mConnectivityServiceHandler;
+
+    // At this sparseArray, Key is userId and values are uids of SMS apps that are allowed
+    // to use satellite network as fallback.
+    private final SparseArray<Set<Integer>> mAllUsersSatelliteNetworkFallbackUidCache =
+            new SparseArray<>();
+
+    /**
+     *  Monitor {@link android.app.role.OnRoleHoldersChangedListener#onRoleHoldersChanged(String,
+     *  UserHandle)},
+     *
+     */
+    private final class DefaultMessageRoleListener
+            implements OnRoleHoldersChangedListener {
+        @Override
+        public void onRoleHoldersChanged(String role, UserHandle userHandle) {
+            if (RoleManager.ROLE_SMS.equals(role)) {
+                Log.i(TAG, "ROLE_SMS Change detected ");
+                onRoleSmsChanged(userHandle);
+            }
+        }
+
+        public void register() {
+            try {
+                mDeps.addOnRoleHoldersChangedListenerAsUser(
+                        mConnectivityServiceHandler::post, this, UserHandle.ALL);
+            } catch (RuntimeException e) {
+                Log.wtf(TAG, "Could not register satellite controller listener due to " + e);
+            }
+        }
+    }
+
+    public SatelliteAccessController(@NonNull final Context c,
+            Consumer<Set<Integer>> callback,
+            @NonNull final Handler connectivityServiceInternalHandler) {
+        this(c, new Dependencies(c), callback, connectivityServiceInternalHandler);
+    }
+
+    public static class Dependencies {
+        private final RoleManager mRoleManager;
+
+        private Dependencies(Context context) {
+            mRoleManager = context.getSystemService(RoleManager.class);
+        }
+
+        /** See {@link RoleManager#getRoleHoldersAsUser(String, UserHandle)} */
+        public List<String> getRoleHoldersAsUser(String roleName, UserHandle userHandle) {
+            return mRoleManager.getRoleHoldersAsUser(roleName, userHandle);
+        }
+
+        /** See {@link RoleManager#addOnRoleHoldersChangedListenerAsUser} */
+        public void addOnRoleHoldersChangedListenerAsUser(@NonNull Executor executor,
+                @NonNull OnRoleHoldersChangedListener listener, UserHandle user) {
+            mRoleManager.addOnRoleHoldersChangedListenerAsUser(executor, listener, user);
+        }
+    }
+
+    @VisibleForTesting
+    SatelliteAccessController(@NonNull final Context c, @NonNull final Dependencies deps,
+            Consumer<Set<Integer>> callback,
+            @NonNull final Handler connectivityServiceInternalHandler) {
+        mContext = c;
+        mDeps = deps;
+        mUserManager = mContext.getSystemService(UserManager.class);
+        mDefaultMessageRoleListener = new DefaultMessageRoleListener();
+        mCallback = callback;
+        mConnectivityServiceHandler = connectivityServiceInternalHandler;
+    }
+
+    private Set<Integer> updateSatelliteNetworkFallbackUidListCache(List<String> packageNames,
+            @NonNull UserHandle userHandle) {
+        Set<Integer> fallbackUids = new ArraySet<>();
+        PackageManager pm =
+                mContext.createContextAsUser(userHandle, 0).getPackageManager();
+        if (pm != null) {
+            for (String packageName : packageNames) {
+                // Check if SATELLITE_COMMUNICATION permission is enabled for default sms
+                // application package before adding it part of satellite network fallback uid
+                // cache list.
+                if (isSatellitePermissionEnabled(pm, packageName)) {
+                    int uid = getUidForPackage(pm, packageName);
+                    if (uid != Process.INVALID_UID) {
+                        fallbackUids.add(uid);
+                    }
+                }
+            }
+        } else {
+            Log.wtf(TAG, "package manager found null");
+        }
+        return fallbackUids;
+    }
+
+    //Check if satellite communication is enabled for the package
+    private boolean isSatellitePermissionEnabled(PackageManager packageManager,
+            String packageName) {
+        return packageManager.checkPermission(
+                Manifest.permission.SATELLITE_COMMUNICATION, packageName)
+                == PackageManager.PERMISSION_GRANTED;
+    }
+
+    private int getUidForPackage(PackageManager packageManager, String pkgName) {
+        if (pkgName == null) {
+            return Process.INVALID_UID;
+        }
+        try {
+            ApplicationInfo applicationInfo = packageManager.getApplicationInfo(pkgName, 0);
+            return applicationInfo.uid;
+        } catch (PackageManager.NameNotFoundException exception) {
+            Log.e(TAG, "Unable to find uid for package: " + pkgName);
+        }
+        return Process.INVALID_UID;
+    }
+
+    // on Role sms change triggered by OnRoleHoldersChangedListener()
+    // TODO(b/326373613): using UserLifecycleListener, callback to be received when user removed for
+    // user delete scenario. This to be used to update uid list and ML Layer request can also be
+    // updated.
+    private void onRoleSmsChanged(@NonNull UserHandle userHandle) {
+        int userId = userHandle.getIdentifier();
+        if (userId == Process.INVALID_UID) {
+            Log.wtf(TAG, "Invalid User Id");
+            return;
+        }
+
+        //Returns empty list if no package exists
+        final List<String> packageNames =
+                mDeps.getRoleHoldersAsUser(RoleManager.ROLE_SMS, userHandle);
+
+        // Store previous satellite fallback uid available
+        final Set<Integer> prevUidsForUser =
+                mAllUsersSatelliteNetworkFallbackUidCache.get(userId, new ArraySet<>());
+
+        Log.i(TAG, "currentUser : role_sms_packages: " + userId + " : " + packageNames);
+        final Set<Integer> newUidsForUser = !packageNames.isEmpty()
+                ? updateSatelliteNetworkFallbackUidListCache(packageNames, userHandle)
+                : new ArraySet<>();
+        Log.i(TAG, "satellite_fallback_uid: " + newUidsForUser);
+
+        // on Role change, update the multilayer request at ConnectivityService with updated
+        // satellite network fallback uid cache list of multiple users as applicable
+        if (newUidsForUser.equals(prevUidsForUser)) {
+            return;
+        }
+
+        mAllUsersSatelliteNetworkFallbackUidCache.put(userId, newUidsForUser);
+
+        // Merge all uids of multiple users available
+        Set<Integer> mergedSatelliteNetworkFallbackUidCache = new ArraySet<>();
+        for (int i = 0; i < mAllUsersSatelliteNetworkFallbackUidCache.size(); i++) {
+            mergedSatelliteNetworkFallbackUidCache.addAll(
+                    mAllUsersSatelliteNetworkFallbackUidCache.valueAt(i));
+        }
+        Log.i(TAG, "merged uid list for multi layer request : "
+                + mergedSatelliteNetworkFallbackUidCache);
+
+        // trigger multiple layer request for satellite network fallback of multi user uids
+        mCallback.accept(mergedSatelliteNetworkFallbackUidCache);
+    }
+
+    private List<String> getRoleSmsChangedPackageName(UserHandle userHandle) {
+        try {
+            return mDeps.getRoleHoldersAsUser(RoleManager.ROLE_SMS, userHandle);
+        } catch (RuntimeException e) {
+            Log.wtf(TAG, "Could not get package name at role sms change update due to: " + e);
+            return null;
+        }
+    }
+
+    /** Register OnRoleHoldersChangedListener */
+    public void start() {
+        mConnectivityServiceHandler.post(this::updateAllUserRoleSmsUids);
+        mDefaultMessageRoleListener.register();
+    }
+
+    private void updateAllUserRoleSmsUids() {
+        List<UserHandle> existingUsers = mUserManager.getUserHandles(true /* excludeDying */);
+        // Iterate through the user handles and obtain their uids with role sms and satellite
+        // communication permission
+        for (UserHandle userHandle : existingUsers) {
+            onRoleSmsChanged(userHandle);
+        }
+    }
+}
diff --git a/staticlibs/Android.bp b/staticlibs/Android.bp
index 8f018c0..f7b42a6 100644
--- a/staticlibs/Android.bp
+++ b/staticlibs/Android.bp
@@ -28,34 +28,35 @@
 // though they are not in the current.txt files.
 
 package {
+    default_team: "trendy_team_fwk_core_networking",
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
 java_library {
-  name: "net-utils-device-common",
-  srcs: [
-      "device/com/android/net/module/util/arp/ArpPacket.java",
-      "device/com/android/net/module/util/DeviceConfigUtils.java",
-      "device/com/android/net/module/util/DomainUtils.java",
-      "device/com/android/net/module/util/FdEventsReader.java",
-      "device/com/android/net/module/util/NetworkMonitorUtils.java",
-      "device/com/android/net/module/util/PacketReader.java",
-      "device/com/android/net/module/util/SharedLog.java",
-      "device/com/android/net/module/util/SocketUtils.java",
-      "device/com/android/net/module/util/FeatureVersions.java",
-      "device/com/android/net/module/util/HandlerUtils.java",
-      // This library is used by system modules, for which the system health impact of Kotlin
-      // has not yet been evaluated. Annotations may need jarjar'ing.
-      // "src_devicecommon/**/*.kt",
-  ],
-  sdk_version: "module_current",
-  min_sdk_version: "30",
-  target_sdk_version: "30",
-  apex_available: [
-      "//apex_available:anyapex",
-      "//apex_available:platform",
-  ],
-  visibility: [
+    name: "net-utils-device-common",
+    srcs: [
+        "device/com/android/net/module/util/arp/ArpPacket.java",
+        "device/com/android/net/module/util/DeviceConfigUtils.java",
+        "device/com/android/net/module/util/DomainUtils.java",
+        "device/com/android/net/module/util/FdEventsReader.java",
+        "device/com/android/net/module/util/NetworkMonitorUtils.java",
+        "device/com/android/net/module/util/PacketReader.java",
+        "device/com/android/net/module/util/SharedLog.java",
+        "device/com/android/net/module/util/SocketUtils.java",
+        "device/com/android/net/module/util/FeatureVersions.java",
+        "device/com/android/net/module/util/HandlerUtils.java",
+        // This library is used by system modules, for which the system health impact of Kotlin
+        // has not yet been evaluated. Annotations may need jarjar'ing.
+        // "src_devicecommon/**/*.kt",
+    ],
+    sdk_version: "module_current",
+    min_sdk_version: "30",
+    target_sdk_version: "30",
+    apex_available: [
+        "//apex_available:anyapex",
+        "//apex_available:platform",
+    ],
+    visibility: [
         "//frameworks/base/packages/Tethering",
         "//packages/modules/Connectivity:__subpackages__",
         "//packages/modules/Connectivity/framework:__subpackages__",
@@ -65,26 +66,26 @@
         "//frameworks/opt/net/telephony",
         "//packages/modules/NetworkStack:__subpackages__",
         "//packages/modules/CaptivePortalLogin",
-  ],
-  static_libs: [
-      "net-utils-framework-common",
-  ],
-  libs: [
-      "androidx.annotation_annotation",
-      "framework-annotations-lib",
-      "framework-configinfrastructure",
-      "framework-connectivity.stubs.module_lib",
-  ],
-  lint: {
-      strict_updatability_linting: true,
-      error_checks: ["NewApi"],
-  },
+    ],
+    static_libs: [
+        "net-utils-framework-common",
+    ],
+    libs: [
+        "androidx.annotation_annotation",
+        "framework-annotations-lib",
+        "framework-configinfrastructure",
+        "framework-connectivity.stubs.module_lib",
+    ],
+    lint: {
+        strict_updatability_linting: true,
+        error_checks: ["NewApi"],
+    },
 }
 
 java_defaults {
     name: "lib_mockito_extended",
     static_libs: [
-        "mockito-target-extended-minus-junit4"
+        "mockito-target-extended-minus-junit4",
     ],
     jni_libs: [
         "libdexmakerjvmtiagent",
@@ -95,12 +96,12 @@
 java_library {
     name: "net-utils-dnspacket-common",
     srcs: [
-    "framework/**/DnsPacket.java",
-    "framework/**/DnsPacketUtils.java",
-    "framework/**/DnsSvcbPacket.java",
-    "framework/**/DnsSvcbRecord.java",
-    "framework/**/HexDump.java",
-    "framework/**/NetworkStackConstants.java",
+        "framework/**/DnsPacket.java",
+        "framework/**/DnsPacketUtils.java",
+        "framework/**/DnsSvcbPacket.java",
+        "framework/**/DnsSvcbRecord.java",
+        "framework/**/HexDump.java",
+        "framework/**/NetworkStackConstants.java",
     ],
     sdk_version: "module_current",
     visibility: [
@@ -187,6 +188,33 @@
     },
 }
 
+// The net-utils-multicast-forwarding-structs library requires the callers to
+// contain net-utils-device-common-bpf.
+java_library {
+    name: "net-utils-multicast-forwarding-structs",
+    srcs: [
+        "device/com/android/net/module/util/structs/StructMf6cctl.java",
+        "device/com/android/net/module/util/structs/StructMif6ctl.java",
+        "device/com/android/net/module/util/structs/StructMrt6Msg.java",
+    ],
+    sdk_version: "module_current",
+    min_sdk_version: "30",
+    visibility: [
+        "//packages/modules/Connectivity:__subpackages__",
+    ],
+    libs: [
+        // Only Struct.java is needed from "net-utils-device-common-bpf"
+        "net-utils-device-common-bpf",
+    ],
+    apex_available: [
+        "com.android.tethering",
+    ],
+    lint: {
+        strict_updatability_linting: true,
+        error_checks: ["NewApi"],
+    },
+}
+
 // The net-utils-device-common-netlink library requires the callers to contain
 // net-utils-device-common-struct.
 java_library {
@@ -247,7 +275,7 @@
         "//apex_available:platform",
     ],
     lint: {
-        strict_updatability_linting: true,
+        baseline_filename: "lint-baseline.xml",
         error_checks: ["NewApi"],
     },
 }
@@ -271,13 +299,11 @@
         "//cts/tests/tests/net",
         "//cts/tests/tests/wifi",
         "//packages/modules/Connectivity/tests/cts/net",
-        "//frameworks/base/packages/Tethering",
         "//packages/modules/Connectivity/Tethering",
         "//frameworks/base/tests:__subpackages__",
         "//frameworks/opt/net/ike",
         "//frameworks/opt/telephony",
         "//frameworks/base/wifi:__subpackages__",
-        "//frameworks/base/packages/Connectivity:__subpackages__",
         "//packages/modules/Connectivity:__subpackages__",
         "//packages/modules/NetworkStack:__subpackages__",
         "//packages/modules/CaptivePortalLogin",
@@ -464,10 +490,10 @@
 filegroup {
     name: "net-utils-wifi-service-common-srcs",
     srcs: [
-       "device/android/net/NetworkFactory.java",
-       "device/android/net/NetworkFactoryImpl.java",
-       "device/android/net/NetworkFactoryLegacyImpl.java",
-       "device/android/net/NetworkFactoryShim.java",
+        "device/android/net/NetworkFactory.java",
+        "device/android/net/NetworkFactoryImpl.java",
+        "device/android/net/NetworkFactoryLegacyImpl.java",
+        "device/android/net/NetworkFactoryShim.java",
     ],
     visibility: [
         "//frameworks/opt/net/wifi/service",
diff --git a/staticlibs/client-libs/Android.bp b/staticlibs/client-libs/Android.bp
index c938dd6..f665584 100644
--- a/staticlibs/client-libs/Android.bp
+++ b/staticlibs/client-libs/Android.bp
@@ -1,4 +1,5 @@
 package {
+    default_team: "trendy_team_fwk_core_networking",
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
@@ -10,17 +11,17 @@
     apex_available: [
         "//apex_available:platform",
         "com.android.tethering",
-        "com.android.wifi"
+        "com.android.wifi",
     ],
     visibility: [
         "//packages/modules/Connectivity:__subpackages__",
         "//frameworks/base/services:__subpackages__",
         "//frameworks/base/packages:__subpackages__",
-        "//packages/modules/Wifi/service:__subpackages__"
+        "//packages/modules/Wifi/service:__subpackages__",
     ],
     libs: ["androidx.annotation_annotation"],
     static_libs: [
         "netd_aidl_interface-lateststable-java",
-        "netd_event_listener_interface-lateststable-java"
-    ]
+        "netd_event_listener_interface-lateststable-java",
+    ],
 }
diff --git a/staticlibs/client-libs/netd/com/android/net/module/util/NetdUtils.java b/staticlibs/client-libs/netd/com/android/net/module/util/NetdUtils.java
index 1d8b4eb..d99eedc 100644
--- a/staticlibs/client-libs/netd/com/android/net/module/util/NetdUtils.java
+++ b/staticlibs/client-libs/netd/com/android/net/module/util/NetdUtils.java
@@ -258,7 +258,7 @@
     }
 
     /** Add or remove |route|. */
-    private static void modifyRoute(final INetd netd, final ModifyOperation op, final int netId,
+    public static void modifyRoute(final INetd netd, final ModifyOperation op, final int netId,
             final RouteInfo route) {
         final String ifName = route.getInterface();
         final String dst = route.getDestination().toString();
diff --git a/staticlibs/client-libs/tests/unit/Android.bp b/staticlibs/client-libs/tests/unit/Android.bp
index 03e3e70..7aafd69 100644
--- a/staticlibs/client-libs/tests/unit/Android.bp
+++ b/staticlibs/client-libs/tests/unit/Android.bp
@@ -1,4 +1,5 @@
 package {
+    default_team: "trendy_team_fwk_core_networking",
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
@@ -26,7 +27,7 @@
         "//packages/modules/Connectivity/tests:__subpackages__",
         "//packages/modules/Connectivity/Tethering/tests:__subpackages__",
         "//packages/modules/NetworkStack/tests/integration",
-    ]
+    ],
 }
 
 android_test {
diff --git a/staticlibs/device/com/android/net/module/util/BpfBitmap.java b/staticlibs/device/com/android/net/module/util/BpfBitmap.java
index acb3ca5..b62a430 100644
--- a/staticlibs/device/com/android/net/module/util/BpfBitmap.java
+++ b/staticlibs/device/com/android/net/module/util/BpfBitmap.java
@@ -38,8 +38,7 @@
      * @param path The path of the BPF map.
      */
     public BpfBitmap(@NonNull String path) throws ErrnoException {
-        mBpfMap = new BpfMap<Struct.S32, Struct.S64>(path, BpfMap.BPF_F_RDWR,
-                Struct.S32.class, Struct.S64.class);
+        mBpfMap = new BpfMap<>(path, Struct.S32.class, Struct.S64.class);
     }
 
     /**
diff --git a/staticlibs/device/com/android/net/module/util/BpfMap.java b/staticlibs/device/com/android/net/module/util/BpfMap.java
index e3ef0f0..da77ae8 100644
--- a/staticlibs/device/com/android/net/module/util/BpfMap.java
+++ b/staticlibs/device/com/android/net/module/util/BpfMap.java
@@ -27,7 +27,6 @@
 import androidx.annotation.Nullable;
 import androidx.annotation.RequiresApi;
 
-import java.io.IOException;
 import java.nio.ByteBuffer;
 import java.nio.ByteOrder;
 import java.util.NoSuchElementException;
@@ -110,6 +109,17 @@
     }
 
     /**
+     * Create a R/W BpfMap map wrapper with "path" of filesystem.
+     *
+     * @throws ErrnoException if the BPF map associated with {@code path} cannot be retrieved.
+     * @throws NullPointerException if {@code path} is null.
+     */
+    public BpfMap(@NonNull final String path, final Class<K> key,
+            final Class<V> value) throws ErrnoException, NullPointerException {
+        this(path, BPF_F_RDWR, key, value);
+    }
+
+    /**
      * Update an existing or create a new key -> value entry in an eBbpf map.
      * (use insertOrReplaceEntry() if you need to know whether insert or replace happened)
      */
diff --git a/staticlibs/device/com/android/net/module/util/DeviceConfigUtils.java b/staticlibs/device/com/android/net/module/util/DeviceConfigUtils.java
index 42f26f4..5b7cbb8 100644
--- a/staticlibs/device/com/android/net/module/util/DeviceConfigUtils.java
+++ b/staticlibs/device/com/android/net/module/util/DeviceConfigUtils.java
@@ -64,9 +64,6 @@
     @VisibleForTesting
     public static final long DEFAULT_PACKAGE_VERSION = 1000;
 
-    private static final String CORE_NETWORKING_TRUNK_STABLE_NAMESPACE = "android_core_networking";
-    private static final String CORE_NETWORKING_TRUNK_STABLE_FLAG_PACKAGE = "com.android.net.flags";
-
     @VisibleForTesting
     public static void resetPackageVersionCacheForTest() {
         sPackageVersion = -1;
@@ -409,31 +406,4 @@
 
         return pkgs.get(0).activityInfo.applicationInfo.packageName;
     }
-
-    /**
-     * Check whether one specific trunk stable flag in android_core_networking namespace is enabled.
-     * This method reads trunk stable feature flag value from DeviceConfig directly since
-     * java_aconfig_library soong module is not available in the mainline branch.
-     * After the mainline branch support the aconfig soong module, this function must be removed and
-     * java_aconfig_library must be used instead to check if the feature is enabled.
-     *
-     * @param flagName The name of the trunk stable flag
-     * @return true if this feature is enabled, or false if disabled.
-     */
-    public static boolean isTrunkStableFeatureEnabled(final String flagName) {
-        return isTrunkStableFeatureEnabled(
-                CORE_NETWORKING_TRUNK_STABLE_NAMESPACE,
-                CORE_NETWORKING_TRUNK_STABLE_FLAG_PACKAGE,
-                flagName
-        );
-    }
-
-    private static boolean isTrunkStableFeatureEnabled(final String namespace,
-            final String packageName, final String flagName) {
-        return DeviceConfig.getBoolean(
-                namespace,
-                packageName + "." + flagName,
-                false /* defaultValue */
-        );
-    }
 }
diff --git a/staticlibs/device/com/android/net/module/util/netlink/InetDiagMessage.java b/staticlibs/device/com/android/net/module/util/netlink/InetDiagMessage.java
index b980c7d..fecaa09 100644
--- a/staticlibs/device/com/android/net/module/util/netlink/InetDiagMessage.java
+++ b/staticlibs/device/com/android/net/module/util/netlink/InetDiagMessage.java
@@ -31,6 +31,7 @@
 import static com.android.net.module.util.netlink.NetlinkConstants.stringForProtocol;
 import static com.android.net.module.util.netlink.NetlinkUtils.DEFAULT_RECV_BUFSIZE;
 import static com.android.net.module.util.netlink.NetlinkUtils.IO_TIMEOUT_MS;
+import static com.android.net.module.util.netlink.NetlinkUtils.SOCKET_RECV_BUFSIZE;
 import static com.android.net.module.util.netlink.NetlinkUtils.TCP_ALIVE_STATE_FILTER;
 import static com.android.net.module.util.netlink.NetlinkUtils.connectToKernel;
 import static com.android.net.module.util.netlink.StructNlMsgHdr.NLM_F_DUMP;
@@ -281,7 +282,7 @@
         int uid = INVALID_UID;
         FileDescriptor fd = null;
         try {
-            fd = NetlinkUtils.netlinkSocketForProto(NETLINK_INET_DIAG);
+            fd = NetlinkUtils.netlinkSocketForProto(NETLINK_INET_DIAG, SOCKET_RECV_BUFSIZE);
             connectToKernel(fd);
             uid = lookupUid(protocol, local, remote, fd);
         } catch (ErrnoException | SocketException | IllegalArgumentException
diff --git a/staticlibs/device/com/android/net/module/util/netlink/NduseroptMessage.java b/staticlibs/device/com/android/net/module/util/netlink/NduseroptMessage.java
index bdf574d..2e9a99b 100644
--- a/staticlibs/device/com/android/net/module/util/netlink/NduseroptMessage.java
+++ b/staticlibs/device/com/android/net/module/util/netlink/NduseroptMessage.java
@@ -20,6 +20,7 @@
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
 
 import java.net.Inet6Address;
 import java.net.InetAddress;
@@ -63,6 +64,20 @@
     /** The IP address that sent the packet containing the option. */
     public final InetAddress srcaddr;
 
+    @VisibleForTesting
+    public NduseroptMessage(@NonNull final StructNlMsgHdr header, byte family, int optslen,
+            int ifindex, byte icmptype, byte icmpcode, @NonNull final NdOption option,
+            final InetAddress srcaddr) {
+        super(header);
+        this.family = family;
+        this.opts_len = optslen;
+        this.ifindex = ifindex;
+        this.icmp_type = icmptype;
+        this.icmp_code = icmpcode;
+        this.option = option;
+        this.srcaddr = srcaddr;
+    }
+
     NduseroptMessage(@NonNull StructNlMsgHdr header, @NonNull ByteBuffer buf)
             throws UnknownHostException {
         super(header);
diff --git a/staticlibs/device/com/android/net/module/util/netlink/NetlinkUtils.java b/staticlibs/device/com/android/net/module/util/netlink/NetlinkUtils.java
index 7c2be2c..541a375 100644
--- a/staticlibs/device/com/android/net/module/util/netlink/NetlinkUtils.java
+++ b/staticlibs/device/com/android/net/module/util/netlink/NetlinkUtils.java
@@ -29,6 +29,7 @@
 import static android.system.OsConstants.SO_RCVBUF;
 import static android.system.OsConstants.SO_RCVTIMEO;
 import static android.system.OsConstants.SO_SNDTIMEO;
+
 import static com.android.net.module.util.netlink.NetlinkConstants.hexify;
 import static com.android.net.module.util.netlink.NetlinkConstants.NLMSG_DONE;
 import static com.android.net.module.util.netlink.NetlinkConstants.RTNL_FAMILY_IP6MR;
@@ -49,6 +50,7 @@
 import java.io.IOException;
 import java.io.InterruptedIOException;
 import java.net.Inet6Address;
+import java.net.InetAddress;
 import java.net.SocketException;
 import java.nio.ByteBuffer;
 import java.nio.ByteOrder;
@@ -56,7 +58,6 @@
 import java.util.List;
 import java.util.Objects;
 import java.util.function.Consumer;
-import java.util.stream.Collectors;
 
 /**
  * Utilities for netlink related class that may not be able to fit into a specific class.
@@ -85,6 +86,7 @@
 
     public static final int DEFAULT_RECV_BUFSIZE = 8 * 1024;
     public static final int SOCKET_RECV_BUFSIZE = 64 * 1024;
+    public static final int SOCKET_DUMP_RECV_BUFSIZE = 128 * 1024;
 
     /**
      * Return whether the input ByteBuffer contains enough remaining bytes for
@@ -159,7 +161,7 @@
      */
     public static void sendOneShotKernelMessage(int nlProto, byte[] msg) throws ErrnoException {
         final String errPrefix = "Error in NetlinkSocket.sendOneShotKernelMessage";
-        final FileDescriptor fd = netlinkSocketForProto(nlProto);
+        final FileDescriptor fd = netlinkSocketForProto(nlProto, SOCKET_RECV_BUFSIZE);
 
         try {
             connectToKernel(fd);
@@ -177,19 +179,19 @@
     }
 
     /**
-     * Send an RTM_NEWADDR message to kernel to add or update an IPv6 address.
+     * Send an RTM_NEWADDR message to kernel to add or update an IP address.
      *
      * @param ifIndex interface index.
-     * @param ip IPv6 address to be added.
-     * @param prefixlen IPv6 address prefix length.
-     * @param flags IPv6 address flags.
-     * @param scope IPv6 address scope.
-     * @param preferred The preferred lifetime of IPv6 address.
-     * @param valid The valid lifetime of IPv6 address.
+     * @param ip IP address to be added.
+     * @param prefixlen IP address prefix length.
+     * @param flags IP address flags.
+     * @param scope IP address scope.
+     * @param preferred The preferred lifetime of IP address.
+     * @param valid The valid lifetime of IP address.
      */
-    public static boolean sendRtmNewAddressRequest(int ifIndex, @NonNull final Inet6Address ip,
+    public static boolean sendRtmNewAddressRequest(int ifIndex, @NonNull final InetAddress ip,
             short prefixlen, int flags, byte scope, long preferred, long valid) {
-        Objects.requireNonNull(ip, "IPv6 address to be added should not be null.");
+        Objects.requireNonNull(ip, "IP address to be added should not be null.");
         final byte[] msg = RtNetlinkAddressMessage.newRtmNewAddressMessage(1 /* seqNo*/, ip,
                 prefixlen, flags, scope, ifIndex, preferred, valid);
         try {
@@ -223,22 +225,41 @@
     }
 
     /**
-     * Create netlink socket with the given netlink protocol type.
+     * Create netlink socket with the given netlink protocol type and buffersize.
+     *
+     * @param nlProto the netlink protocol
+     * @param bufferSize the receive buffer size to set when the value is not 0
      *
      * @return fd the fileDescriptor of the socket.
      * @throws ErrnoException if the FileDescriptor not connect to be created successfully
      */
-    public static FileDescriptor netlinkSocketForProto(int nlProto) throws ErrnoException {
-        final FileDescriptor fd = Os.socket(AF_NETLINK, SOCK_DGRAM, nlProto);
-        Os.setsockoptInt(fd, SOL_SOCKET, SO_RCVBUF, SOCKET_RECV_BUFSIZE);
+    public static FileDescriptor netlinkSocketForProto(int nlProto, int bufferSize)
+            throws ErrnoException {
+        final FileDescriptor fd = Os.socket(AF_NETLINK, SOCK_DGRAM | SOCK_CLOEXEC, nlProto);
+        if (bufferSize > 0) {
+            Os.setsockoptInt(fd, SOL_SOCKET, SO_RCVBUF, bufferSize);
+        }
         return fd;
     }
 
     /**
+     * Create netlink socket with the given netlink protocol type. Receive buffer size is not set.
+     *
+     * @param nlProto the netlink protocol
+     *
+     * @return fd the fileDescriptor of the socket.
+     * @throws ErrnoException if the FileDescriptor not connect to be created successfully
+     */
+    public static FileDescriptor netlinkSocketForProto(int nlProto)
+            throws ErrnoException {
+        return netlinkSocketForProto(nlProto, 0);
+    }
+
+    /**
      * Construct a netlink inet_diag socket.
      */
     public static FileDescriptor createNetLinkInetDiagSocket() throws ErrnoException {
-        return Os.socket(AF_NETLINK, SOCK_DGRAM | SOCK_CLOEXEC, NETLINK_INET_DIAG);
+        return netlinkSocketForProto(NETLINK_INET_DIAG);
     }
 
     /**
@@ -373,7 +394,7 @@
             Consumer<T> func)
             throws SocketException, InterruptedIOException, ErrnoException {
         // Create socket
-        final FileDescriptor fd = netlinkSocketForProto(nlFamily);
+        final FileDescriptor fd = netlinkSocketForProto(nlFamily, SOCKET_DUMP_RECV_BUFSIZE);
         try {
             getAndProcessNetlinkDumpMessagesWithFd(fd, dumpRequestMessage, nlFamily,
                     msgClass, func);
diff --git a/staticlibs/device/com/android/net/module/util/netlink/RtNetlinkAddressMessage.java b/staticlibs/device/com/android/net/module/util/netlink/RtNetlinkAddressMessage.java
index cbe0ab0..4846df7 100644
--- a/staticlibs/device/com/android/net/module/util/netlink/RtNetlinkAddressMessage.java
+++ b/staticlibs/device/com/android/net/module/util/netlink/RtNetlinkAddressMessage.java
@@ -16,6 +16,7 @@
 
 package com.android.net.module.util.netlink;
 
+import static com.android.net.module.util.Inet4AddressUtils.getBroadcastAddress;
 import static com.android.net.module.util.netlink.StructNlMsgHdr.NLM_F_ACK;
 import static com.android.net.module.util.netlink.StructNlMsgHdr.NLM_F_REPLACE;
 import static com.android.net.module.util.netlink.StructNlMsgHdr.NLM_F_REQUEST;
@@ -28,6 +29,7 @@
 
 import com.android.net.module.util.HexDump;
 
+import java.net.Inet4Address;
 import java.net.Inet6Address;
 import java.net.InetAddress;
 import java.nio.ByteBuffer;
@@ -48,6 +50,8 @@
  */
 public class RtNetlinkAddressMessage extends NetlinkMessage {
     public static final short IFA_ADDRESS        = 1;
+    public static final short IFA_LOCAL          = 2;
+    public static final short IFA_BROADCAST      = 4;
     public static final short IFA_CACHEINFO      = 6;
     public static final short IFA_FLAGS          = 8;
 
@@ -71,6 +75,7 @@
         mIfacacheInfo = structIfacacheInfo;
         mFlags = flags;
     }
+
     private RtNetlinkAddressMessage(@NonNull StructNlMsgHdr header) {
         this(header, null, null, null, 0);
     }
@@ -158,6 +163,24 @@
         // still be packed to ByteBuffer even if the flag is 0.
         final StructNlAttr flags = new StructNlAttr(IFA_FLAGS, mFlags);
         flags.pack(byteBuffer);
+
+        // Add the required IFA_LOCAL and IFA_BROADCAST attributes for IPv4 addresses. The IFA_LOCAL
+        // attribute represents the local address, which is equivalent to IFA_ADDRESS on a normally
+        // configured broadcast interface, however, for PPP interfaces, IFA_ADDRESS indicates the
+        // destination address and the local address is provided in the IFA_LOCAL attribute. If the
+        // IFA_LOCAL attribute is not present in the RTM_NEWADDR message, the kernel replies with an
+        // error netlink message with invalid parameters. IFA_BROADCAST is also required, otherwise
+        // the broadcast on the interface is 0.0.0.0. See include/uapi/linux/if_addr.h for details.
+        // For IPv6 addresses, the IFA_ADDRESS attribute applies and introduces no ambiguity.
+        if (mIpAddress instanceof Inet4Address) {
+            final StructNlAttr localAddress = new StructNlAttr(IFA_LOCAL, mIpAddress);
+            localAddress.pack(byteBuffer);
+
+            final Inet4Address broadcast =
+                    getBroadcastAddress((Inet4Address) mIpAddress, mIfaddrmsg.prefixLen);
+            final StructNlAttr broadcastAddress = new StructNlAttr(IFA_BROADCAST, broadcast);
+            broadcastAddress.pack(byteBuffer);
+        }
     }
 
     /**
@@ -184,7 +207,7 @@
                 0 /* tstamp */);
         msg.mFlags = flags;
 
-        final byte[] bytes = new byte[msg.getRequiredSpace()];
+        final byte[] bytes = new byte[msg.getRequiredSpace(family)];
         nlmsghdr.nlmsg_len = bytes.length;
         final ByteBuffer byteBuffer = ByteBuffer.wrap(bytes);
         byteBuffer.order(ByteOrder.nativeOrder());
@@ -237,7 +260,7 @@
     // RtNetlinkAddressMessage, e.g. RTM_DELADDR sent from user space to kernel to delete an
     // IP address only requires IFA_ADDRESS attribute. The caller should check if these attributes
     // are necessary to carry when constructing a RtNetlinkAddressMessage.
-    private int getRequiredSpace() {
+    private int getRequiredSpace(int family) {
         int spaceRequired = StructNlMsgHdr.STRUCT_SIZE + StructIfaddrMsg.STRUCT_SIZE;
         // IFA_ADDRESS attr
         spaceRequired += NetlinkConstants.alignedLengthOf(
@@ -247,6 +270,14 @@
                 StructNlAttr.NLA_HEADERLEN + StructIfacacheInfo.STRUCT_SIZE);
         // IFA_FLAGS "u32" attr
         spaceRequired += StructNlAttr.NLA_HEADERLEN + 4;
+        if (family == OsConstants.AF_INET) {
+            // IFA_LOCAL attr
+            spaceRequired += NetlinkConstants.alignedLengthOf(
+                    StructNlAttr.NLA_HEADERLEN + mIpAddress.getAddress().length);
+            // IFA_BROADCAST attr
+            spaceRequired += NetlinkConstants.alignedLengthOf(
+                    StructNlAttr.NLA_HEADERLEN + mIpAddress.getAddress().length);
+        }
         return spaceRequired;
     }
 
diff --git a/staticlibs/device/com/android/net/module/util/netlink/RtNetlinkRouteMessage.java b/staticlibs/device/com/android/net/module/util/netlink/RtNetlinkRouteMessage.java
index b2b1e93..545afea 100644
--- a/staticlibs/device/com/android/net/module/util/netlink/RtNetlinkRouteMessage.java
+++ b/staticlibs/device/com/android/net/module/util/netlink/RtNetlinkRouteMessage.java
@@ -19,10 +19,8 @@
 import static android.system.OsConstants.AF_INET;
 import static android.system.OsConstants.AF_INET6;
 
-import static android.system.OsConstants.NETLINK_ROUTE;
 import static com.android.net.module.util.NetworkStackConstants.IPV4_ADDR_ANY;
 import static com.android.net.module.util.NetworkStackConstants.IPV6_ADDR_ANY;
-import static com.android.net.module.util.netlink.NetlinkConstants.hexify;
 import static com.android.net.module.util.netlink.NetlinkConstants.RTNL_FAMILY_IP6MR;
 
 import android.annotation.SuppressLint;
@@ -38,9 +36,6 @@
 import java.net.Inet6Address;
 import java.net.InetAddress;
 import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-import java.nio.IntBuffer;
-import java.util.Arrays;
 
 /**
  * A NetlinkMessage subclass for rtnetlink route messages.
@@ -86,18 +81,27 @@
     private long mSinceLastUseMillis; // Milliseconds since the route was used,
                                       // for resolved multicast routes
 
-    public RtNetlinkRouteMessage(StructNlMsgHdr header, StructRtMsg rtMsg) {
+
+    @VisibleForTesting
+    public RtNetlinkRouteMessage(final StructNlMsgHdr header, final StructRtMsg rtMsg,
+            final IpPrefix source, final IpPrefix destination, final InetAddress gateway,
+            int iif, int oif, final StructRtaCacheInfo cacheInfo) {
         super(header);
         mRtmsg = rtMsg;
-        mSource = null;
-        mDestination = null;
-        mGateway = null;
-        mIifIndex = 0;
-        mOifIndex = 0;
-        mRtaCacheInfo = null;
+        mSource = source;
+        mDestination = destination;
+        mGateway = gateway;
+        mIifIndex = iif;
+        mOifIndex = oif;
+        mRtaCacheInfo = cacheInfo;
         mSinceLastUseMillis = -1;
     }
 
+    public RtNetlinkRouteMessage(StructNlMsgHdr header, StructRtMsg rtMsg) {
+        this(header, rtMsg, null /* source */, null /* destination */, null /* gateway */,
+                0 /* iif */, 0 /* oif */, null /* cacheInfo */);
+    }
+
     /**
      * Returns the rtnetlink family.
      */
diff --git a/staticlibs/device/com/android/net/module/util/netlink/StructRtMsg.java b/staticlibs/device/com/android/net/module/util/netlink/StructRtMsg.java
index 3cd7292..6d9318c 100644
--- a/staticlibs/device/com/android/net/module/util/netlink/StructRtMsg.java
+++ b/staticlibs/device/com/android/net/module/util/netlink/StructRtMsg.java
@@ -18,6 +18,7 @@
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
 
 import com.android.net.module.util.Struct;
 import com.android.net.module.util.Struct.Field;
@@ -57,8 +58,9 @@
     @Field(order = 8, type = Type.U32)
     public final long flags;
 
-    StructRtMsg(short family, short dstLen, short srcLen, short tos, short table, short protocol,
-            short scope, short type, long flags) {
+    @VisibleForTesting
+    public StructRtMsg(short family, short dstLen, short srcLen, short tos, short table,
+            short protocol, short scope, short type, long flags) {
         this.family = family;
         this.dstLen = dstLen;
         this.srcLen = srcLen;
diff --git a/staticlibs/device/com/android/net/module/util/structs/FragmentHeader.java b/staticlibs/device/com/android/net/module/util/structs/FragmentHeader.java
new file mode 100644
index 0000000..3da6a38
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/structs/FragmentHeader.java
@@ -0,0 +1,56 @@
+/*
+ * 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.
+ */
+
+package com.android.net.module.util.structs;
+
+import com.android.net.module.util.Struct;
+import com.android.net.module.util.Struct.Field;
+import com.android.net.module.util.Struct.Type;
+
+/**
+ * IPv6 Fragment Extension header, as per https://tools.ietf.org/html/rfc2460.
+ *
+ * 0                   1                   2                   3
+ * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |  Next Header  |   Reserved    |      Fragment Offset    |Res|M|
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |                         Identification                        |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ */
+public class FragmentHeader extends Struct {
+    @Field(order = 0, type = Type.U8)
+    public final short nextHeader;
+    @Field(order = 1, type = Type.S8)
+    public final byte reserved;
+    @Field(order = 2, type = Type.U16)
+    public final int fragmentOffset;
+    @Field(order = 3, type = Type.S32)
+    public final int identification;
+
+    public FragmentHeader(final short nextHeader, final byte reserved, final int fragmentOffset,
+            final int identification) {
+        this.nextHeader = nextHeader;
+        this.reserved = reserved;
+        this.fragmentOffset = fragmentOffset;
+        this.identification = identification;
+    }
+
+    public FragmentHeader(final short nextHeader, final int fragmentOffset,
+            final int identification) {
+        this(nextHeader, (byte) 0, fragmentOffset, identification);
+    }
+}
diff --git a/staticlibs/framework/com/android/net/module/util/PermissionUtils.java b/staticlibs/framework/com/android/net/module/util/PermissionUtils.java
index f167d3d..0d7d96f 100644
--- a/staticlibs/framework/com/android/net/module/util/PermissionUtils.java
+++ b/staticlibs/framework/com/android/net/module/util/PermissionUtils.java
@@ -23,6 +23,7 @@
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 import static android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK;
 
+import android.annotation.CheckResult;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
@@ -45,8 +46,9 @@
     /**
      * Return true if the context has one of given permission.
      */
-    public static boolean checkAnyPermissionOf(@NonNull Context context,
-            @NonNull String... permissions) {
+    @CheckResult
+    public static boolean hasAnyPermissionOf(@NonNull Context context,
+                                             @NonNull String... permissions) {
         for (String permission : permissions) {
             if (context.checkCallingOrSelfPermission(permission) == PERMISSION_GRANTED) {
                 return true;
@@ -56,11 +58,12 @@
     }
 
     /**
-     * Return true if the context has one of give permission that is allowed
+     * Return true if the context has one of given permission that is allowed
      * for a particular process and user ID running in the system.
      */
-    public static boolean checkAnyPermissionOf(@NonNull Context context,
-            int pid, int uid, @NonNull String... permissions) {
+    @CheckResult
+    public static boolean hasAnyPermissionOf(@NonNull Context context,
+                                             int pid, int uid, @NonNull String... permissions) {
         for (String permission : permissions) {
             if (context.checkPermission(permission, pid, uid) == PERMISSION_GRANTED) {
                 return true;
@@ -74,7 +77,7 @@
      */
     public static void enforceAnyPermissionOf(@NonNull Context context,
             @NonNull String... permissions) {
-        if (!checkAnyPermissionOf(context, permissions)) {
+        if (!hasAnyPermissionOf(context, permissions)) {
             throw new SecurityException("Requires one of the following permissions: "
                     + String.join(", ", permissions) + ".");
         }
@@ -133,7 +136,8 @@
     /**
      * Return true if the context has DUMP permission.
      */
-    public static boolean checkDumpPermission(Context context, String tag, PrintWriter pw) {
+    @CheckResult
+    public static boolean hasDumpPermission(Context context, String tag, PrintWriter pw) {
         if (context.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
                 != PERMISSION_GRANTED) {
             pw.println("Permission Denial: can't dump " + tag + " from from pid="
diff --git a/staticlibs/lint-baseline.xml b/staticlibs/lint-baseline.xml
new file mode 100644
index 0000000..2ee3a43
--- /dev/null
+++ b/staticlibs/lint-baseline.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<issues format="6" by="lint 8.4.0-alpha04" type="baseline" client="" dependencies="true" name="" variant="all" version="8.4.0-alpha04">
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 31 (current min is 30): `makeNetlinkSocketAddress`"
+        errorLine1="            Os.bind(fd, makeNetlinkSocketAddress(0, mBindGroups));"
+        errorLine2="                        ~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="packages/modules/Connectivity/staticlibs/device/com/android/net/module/util/ip/NetlinkMonitor.java"
+            line="111"
+            column="25"/>
+    </issue>
+
+</issues>
diff --git a/staticlibs/native/bpf_headers/Android.bp b/staticlibs/native/bpf_headers/Android.bp
index 41184ea..d55584a 100644
--- a/staticlibs/native/bpf_headers/Android.bp
+++ b/staticlibs/native/bpf_headers/Android.bp
@@ -13,6 +13,7 @@
 // limitations under the License.
 
 package {
+    default_team: "trendy_team_fwk_core_networking",
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
diff --git a/staticlibs/native/bpf_syscall_wrappers/Android.bp b/staticlibs/native/bpf_syscall_wrappers/Android.bp
index b3efc21..1e0cb22 100644
--- a/staticlibs/native/bpf_syscall_wrappers/Android.bp
+++ b/staticlibs/native/bpf_syscall_wrappers/Android.bp
@@ -13,6 +13,7 @@
 // limitations under the License.
 
 package {
+    default_team: "trendy_team_fwk_core_networking",
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
diff --git a/staticlibs/native/bpfmapjni/Android.bp b/staticlibs/native/bpfmapjni/Android.bp
index 8babcce..7e6b4ec 100644
--- a/staticlibs/native/bpfmapjni/Android.bp
+++ b/staticlibs/native/bpfmapjni/Android.bp
@@ -13,6 +13,7 @@
 // limitations under the License.
 
 package {
+    default_team: "trendy_team_fwk_core_networking",
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
diff --git a/staticlibs/native/bpfutiljni/Android.bp b/staticlibs/native/bpfutiljni/Android.bp
index 39a2795..1ef01a6 100644
--- a/staticlibs/native/bpfutiljni/Android.bp
+++ b/staticlibs/native/bpfutiljni/Android.bp
@@ -13,6 +13,7 @@
 // limitations under the License.
 
 package {
+    default_team: "trendy_team_fwk_core_networking",
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
diff --git a/staticlibs/native/ip_checksum/Android.bp b/staticlibs/native/ip_checksum/Android.bp
index 9878d73..e2e118e 100644
--- a/staticlibs/native/ip_checksum/Android.bp
+++ b/staticlibs/native/ip_checksum/Android.bp
@@ -13,6 +13,7 @@
 // limitations under the License.
 
 package {
+    default_team: "trendy_team_fwk_core_networking",
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
diff --git a/staticlibs/native/netjniutils/Android.bp b/staticlibs/native/netjniutils/Android.bp
index ca3bbbc..4cab459 100644
--- a/staticlibs/native/netjniutils/Android.bp
+++ b/staticlibs/native/netjniutils/Android.bp
@@ -13,6 +13,7 @@
 // limitations under the License.
 
 package {
+    default_team: "trendy_team_fwk_core_networking",
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
diff --git a/staticlibs/native/nettestutils/Android.bp b/staticlibs/native/nettestutils/Android.bp
index df3bb42..ef87f04 100644
--- a/staticlibs/native/nettestutils/Android.bp
+++ b/staticlibs/native/nettestutils/Android.bp
@@ -13,6 +13,7 @@
 // limitations under the License.
 
 package {
+    default_team: "trendy_team_fwk_core_networking",
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
diff --git a/staticlibs/native/tcutils/Android.bp b/staticlibs/native/tcutils/Android.bp
index 9a38745..926590d 100644
--- a/staticlibs/native/tcutils/Android.bp
+++ b/staticlibs/native/tcutils/Android.bp
@@ -13,6 +13,7 @@
 // limitations under the License.
 
 package {
+    default_team: "trendy_team_fwk_core_networking",
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
diff --git a/staticlibs/netd/Android.bp b/staticlibs/netd/Android.bp
index 2b7e620..59ef20d 100644
--- a/staticlibs/netd/Android.bp
+++ b/staticlibs/netd/Android.bp
@@ -13,6 +13,7 @@
 // limitations under the License.
 
 package {
+    default_team: "trendy_team_fwk_core_networking",
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
diff --git a/staticlibs/netd/libnetdutils/Android.bp b/staticlibs/netd/libnetdutils/Android.bp
index fdb9380..2ae5911 100644
--- a/staticlibs/netd/libnetdutils/Android.bp
+++ b/staticlibs/netd/libnetdutils/Android.bp
@@ -1,4 +1,5 @@
 package {
+    default_team: "trendy_team_fwk_core_networking",
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
@@ -22,7 +23,10 @@
         "Utils.cpp",
     ],
     defaults: ["netd_defaults"],
-    cflags: ["-Wall", "-Werror"],
+    cflags: [
+        "-Wall",
+        "-Werror",
+    ],
     shared_libs: [
         "libbase",
         "liblog",
diff --git a/staticlibs/tests/unit/Android.bp b/staticlibs/tests/unit/Android.bp
index 0dfca57..4c226cc 100644
--- a/staticlibs/tests/unit/Android.bp
+++ b/staticlibs/tests/unit/Android.bp
@@ -3,12 +3,16 @@
 //########################################################################
 
 package {
+    default_team: "trendy_team_fwk_core_networking",
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
 android_library {
     name: "NetworkStaticLibTestsLib",
-    srcs: ["src/**/*.java","src/**/*.kt"],
+    srcs: [
+        "src/**/*.java",
+        "src/**/*.kt",
+    ],
     min_sdk_version: "30",
     defaults: ["framework-connectivity-test-defaults"],
     static_libs: [
@@ -34,8 +38,7 @@
         "//packages/modules/NetworkStack/tests/integration",
     ],
     lint: {
-        strict_updatability_linting: true,
-        test: true
+        test: true,
     },
 }
 
@@ -52,5 +55,4 @@
     ],
     jarjar_rules: "jarjar-rules.txt",
     test_suites: ["device-tests"],
-    lint: { strict_updatability_linting: true },
 }
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/DeviceConfigUtilsTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/DeviceConfigUtilsTest.java
index 06b3e2f..f32337d 100644
--- a/staticlibs/tests/unit/src/com/android/net/module/util/DeviceConfigUtilsTest.java
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/DeviceConfigUtilsTest.java
@@ -71,10 +71,6 @@
 public class DeviceConfigUtilsTest {
     private static final String TEST_NAME_SPACE = "connectivity";
     private static final String TEST_EXPERIMENT_FLAG = "experiment_flag";
-    private static final String CORE_NETWORKING_TRUNK_STABLE_NAMESPACE = "android_core_networking";
-    private static final String TEST_TRUNK_STABLE_FLAG = "trunk_stable_feature";
-    private static final String TEST_CORE_NETWORKING_TRUNK_STABLE_FLAG_PROPERTY =
-            "com.android.net.flags.trunk_stable_feature";
     private static final int TEST_FLAG_VALUE = 28;
     private static final String TEST_FLAG_VALUE_STRING = "28";
     private static final int TEST_DEFAULT_FLAG_VALUE = 0;
@@ -507,25 +503,4 @@
         verify(mContext, never()).getPackageName();
         verify(mPm, never()).getPackageInfo(anyString(), anyInt());
     }
-
-    @Test
-    public void testIsCoreNetworkingTrunkStableFeatureEnabled() {
-        doReturn(null).when(() -> DeviceConfig.getProperty(
-                CORE_NETWORKING_TRUNK_STABLE_NAMESPACE,
-                TEST_CORE_NETWORKING_TRUNK_STABLE_FLAG_PROPERTY));
-        assertFalse(DeviceConfigUtils.isTrunkStableFeatureEnabled(
-                TEST_TRUNK_STABLE_FLAG));
-
-        doReturn("false").when(() -> DeviceConfig.getProperty(
-                CORE_NETWORKING_TRUNK_STABLE_NAMESPACE,
-                TEST_CORE_NETWORKING_TRUNK_STABLE_FLAG_PROPERTY));
-        assertFalse(DeviceConfigUtils.isTrunkStableFeatureEnabled(
-                TEST_TRUNK_STABLE_FLAG));
-
-        doReturn("true").when(() -> DeviceConfig.getProperty(
-                CORE_NETWORKING_TRUNK_STABLE_NAMESPACE,
-                TEST_CORE_NETWORKING_TRUNK_STABLE_FLAG_PROPERTY));
-        assertTrue(DeviceConfigUtils.isTrunkStableFeatureEnabled(
-                TEST_TRUNK_STABLE_FLAG));
-    }
 }
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/PermissionUtilsTest.kt b/staticlibs/tests/unit/src/com/android/net/module/util/PermissionUtilsTest.kt
index d5b43fb..8586e82 100644
--- a/staticlibs/tests/unit/src/com/android/net/module/util/PermissionUtilsTest.kt
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/PermissionUtilsTest.kt
@@ -25,12 +25,12 @@
 import android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK
 import androidx.test.filters.SmallTest
 import androidx.test.platform.app.InstrumentationRegistry
-import com.android.net.module.util.PermissionUtils.checkAnyPermissionOf
 import com.android.net.module.util.PermissionUtils.enforceAnyPermissionOf
 import com.android.net.module.util.PermissionUtils.enforceNetworkStackPermission
 import com.android.net.module.util.PermissionUtils.enforceNetworkStackPermissionOr
 import com.android.net.module.util.PermissionUtils.enforcePackageNameMatchesUid
 import com.android.net.module.util.PermissionUtils.enforceSystemFeature
+import com.android.net.module.util.PermissionUtils.hasAnyPermissionOf
 import com.android.testutils.DevSdkIgnoreRule
 import com.android.testutils.DevSdkIgnoreRunner
 import kotlin.test.assertEquals
@@ -78,18 +78,18 @@
             .checkCallingOrSelfPermission(TEST_PERMISSION1)
         doReturn(PERMISSION_DENIED).`when`(mockContext)
             .checkCallingOrSelfPermission(TEST_PERMISSION2)
-        assertTrue(checkAnyPermissionOf(mockContext, TEST_PERMISSION1, TEST_PERMISSION2))
+        assertTrue(hasAnyPermissionOf(mockContext, TEST_PERMISSION1, TEST_PERMISSION2))
         enforceAnyPermissionOf(mockContext, TEST_PERMISSION1, TEST_PERMISSION2)
 
         doReturn(PERMISSION_DENIED).`when`(mockContext)
             .checkCallingOrSelfPermission(TEST_PERMISSION1)
         doReturn(PERMISSION_GRANTED).`when`(mockContext)
             .checkCallingOrSelfPermission(TEST_PERMISSION2)
-        assertTrue(checkAnyPermissionOf(mockContext, TEST_PERMISSION1, TEST_PERMISSION2))
+        assertTrue(hasAnyPermissionOf(mockContext, TEST_PERMISSION1, TEST_PERMISSION2))
         enforceAnyPermissionOf(mockContext, TEST_PERMISSION1, TEST_PERMISSION2)
 
         doReturn(PERMISSION_DENIED).`when`(mockContext).checkCallingOrSelfPermission(any())
-        assertFalse(checkAnyPermissionOf(mockContext, TEST_PERMISSION1, TEST_PERMISSION2))
+        assertFalse(hasAnyPermissionOf(mockContext, TEST_PERMISSION1, TEST_PERMISSION2))
         assertFailsWith<SecurityException>("Expect fail but permission granted.") {
             enforceAnyPermissionOf(mockContext, TEST_PERMISSION1, TEST_PERMISSION2)
         }
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/netlink/NetlinkUtilsTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/NetlinkUtilsTest.java
index 0958f11..f64adb8 100644
--- a/staticlibs/tests/unit/src/com/android/net/module/util/netlink/NetlinkUtilsTest.java
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/NetlinkUtilsTest.java
@@ -21,6 +21,8 @@
 import static android.system.OsConstants.AF_UNSPEC;
 import static android.system.OsConstants.EACCES;
 import static android.system.OsConstants.NETLINK_ROUTE;
+import static android.system.OsConstants.SOL_SOCKET;
+import static android.system.OsConstants.SO_RCVBUF;
 import static com.android.net.module.util.netlink.NetlinkConstants.RTNL_FAMILY_IP6MR;
 import static com.android.net.module.util.netlink.NetlinkUtils.DEFAULT_RECV_BUFSIZE;
 import static com.android.net.module.util.netlink.StructNlMsgHdr.NLM_F_DUMP;
@@ -33,6 +35,8 @@
 import static org.junit.Assume.assumeFalse;
 
 import android.content.Context;
+import android.net.util.SocketUtils;
+import android.os.Build;
 import android.system.ErrnoException;
 import android.system.NetlinkSocketAddress;
 import android.system.Os;
@@ -43,6 +47,7 @@
 
 import com.android.modules.utils.build.SdkLevel;
 import com.android.net.module.util.Struct;
+import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
 
 import libcore.io.IoUtils;
 
@@ -204,4 +209,23 @@
             assertNotNull("Route doesn't contain destination: " + route, route.getDestination());
         }
     }
+
+    @Test @IgnoreUpTo(Build.VERSION_CODES.R) // getsockoptInt requires > R
+    public void testNetlinkSocketForProto_defaultBufferSize() throws Exception {
+        final FileDescriptor fd = NetlinkUtils.netlinkSocketForProto(NETLINK_ROUTE);
+        final int bufferSize = Os.getsockoptInt(fd, SOL_SOCKET, SO_RCVBUF) / 2;
+
+        assertTrue("bufferSize: " + bufferSize, bufferSize > 0); // whatever the default value is
+        SocketUtils.closeSocket(fd);
+    }
+
+    @Test @IgnoreUpTo(Build.VERSION_CODES.R) // getsockoptInt requires > R
+    public void testNetlinkSocketForProto_setBufferSize() throws Exception {
+        final FileDescriptor fd = NetlinkUtils.netlinkSocketForProto(NETLINK_ROUTE,
+                8000);
+        final int bufferSize = Os.getsockoptInt(fd, SOL_SOCKET, SO_RCVBUF) / 2;
+
+        assertEquals(8000, bufferSize);
+        SocketUtils.closeSocket(fd);
+    }
 }
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/netlink/RtNetlinkAddressMessageTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/RtNetlinkAddressMessageTest.java
index 01126d2..1d08525 100644
--- a/staticlibs/tests/unit/src/com/android/net/module/util/netlink/RtNetlinkAddressMessageTest.java
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/RtNetlinkAddressMessageTest.java
@@ -42,6 +42,7 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.net.Inet4Address;
 import java.net.Inet6Address;
 import java.nio.ByteBuffer;
 import java.nio.ByteOrder;
@@ -179,6 +180,57 @@
     }
 
     @Test
+    public void testCreateRtmNewAddressMessage_IPv4Address() {
+        // Hexadecimal representation of our created packet.
+        final String expectedNewAddressHex =
+                // struct nlmsghdr
+                "4c000000"      // length = 76
+                + "1400"        // type = 20 (RTM_NEWADDR)
+                + "0501"        // flags = NLM_F_ACK | NLM_F_REQUEST | NLM_F_REPLACE
+                + "01000000"    // seqno = 1
+                + "00000000"    // pid = 0 (send to kernel)
+                // struct IfaddrMsg
+                + "02"          // family = inet
+                + "18"          // prefix len = 24
+                + "00"          // flags = 0
+                + "00"          // scope = RT_SCOPE_UNIVERSE
+                + "14000000"    // ifindex = 20
+                // struct nlattr: IFA_ADDRESS
+                + "0800"        // len
+                + "0100"        // type
+                + "C0A80491"    // IPv4 address = 192.168.4.145
+                // struct nlattr: IFA_CACHEINFO
+                + "1400"        // len
+                + "0600"        // type
+                + "C0A80000"    // preferred = 43200s
+                + "C0A80000"    // valid = 43200s
+                + "00000000"    // cstamp
+                + "00000000"    // tstamp
+                // struct nlattr: IFA_FLAGS
+                + "0800"        // len
+                + "0800"        // type
+                + "00000000"    // flags = 0
+                // struct nlattr: IFA_LOCAL
+                + "0800"        // len
+                + "0200"        // type
+                + "C0A80491"    // local address = 192.168.4.145
+                // struct nlattr: IFA_BROADCAST
+                + "0800"        // len
+                + "0400"        // type
+                + "C0A804FF";   // broadcast address = 192.168.4.255
+        final byte[] expectedNewAddress =
+                HexEncoding.decode(expectedNewAddressHex.toCharArray(), false);
+
+        final Inet4Address ipAddress =
+                (Inet4Address) InetAddresses.parseNumericAddress("192.168.4.145");
+        final byte[] bytes = RtNetlinkAddressMessage.newRtmNewAddressMessage(1 /* seqno */,
+                ipAddress, (short) 24 /* prefix len */, 0 /* flags */,
+                (byte) RT_SCOPE_UNIVERSE /* scope */, 20 /* ifindex */,
+                (long) 0xA8C0 /* preferred */, (long) 0xA8C0 /* valid */);
+        assertArrayEquals(expectedNewAddress, bytes);
+    }
+
+    @Test
     public void testCreateRtmDelAddressMessage() {
         // Hexadecimal representation of our created packet.
         final String expectedDelAddressHex =
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/structs/FragmentHeaderTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/structs/FragmentHeaderTest.java
new file mode 100644
index 0000000..1a78ca5
--- /dev/null
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/structs/FragmentHeaderTest.java
@@ -0,0 +1,76 @@
+/*
+ * 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.
+ */
+
+package com.android.net.module.util.structs;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.Arrays;
+
+@RunWith(AndroidJUnit4.class)
+public class FragmentHeaderTest {
+    private static final byte[] HEADER_BYTES = new byte[] {
+        17, /* nextHeader  */
+        0, /* reserved */
+        15, 1, /* fragmentOffset */
+        1, 2, 3, 4 /* identification */
+    };
+
+    @Test
+    public void testConstructor() {
+        FragmentHeader fragHdr = new FragmentHeader((short) 10 /* nextHeader */,
+                (byte) 11 /* reserved */,
+                12 /* fragmentOffset */,
+                13 /* identification */);
+
+        assertEquals(10, fragHdr.nextHeader);
+        assertEquals(11, fragHdr.reserved);
+        assertEquals(12, fragHdr.fragmentOffset);
+        assertEquals(13, fragHdr.identification);
+    }
+
+    @Test
+    public void testParseFragmentHeader() {
+        final ByteBuffer buf = ByteBuffer.wrap(HEADER_BYTES);
+        buf.order(ByteOrder.BIG_ENDIAN);
+        FragmentHeader fragHdr = FragmentHeader.parse(FragmentHeader.class, buf);
+
+        assertEquals(17, fragHdr.nextHeader);
+        assertEquals(0, fragHdr.reserved);
+        assertEquals(0xF01, fragHdr.fragmentOffset);
+        assertEquals(0x1020304, fragHdr.identification);
+    }
+
+    @Test
+    public void testWriteToBytes() {
+        FragmentHeader fragHdr = new FragmentHeader((short) 17 /* nextHeader */,
+                (byte) 0 /* reserved */,
+                0xF01 /* fragmentOffset */,
+                0x1020304 /* identification */);
+
+        byte[] bytes = fragHdr.writeToBytes(ByteOrder.BIG_ENDIAN);
+
+        assertArrayEquals("bytes = " + Arrays.toString(bytes), HEADER_BYTES, bytes);
+    }
+}
diff --git a/staticlibs/testutils/Android.bp b/staticlibs/testutils/Android.bp
index a5c4fea..a8e5a69 100644
--- a/staticlibs/testutils/Android.bp
+++ b/staticlibs/testutils/Android.bp
@@ -13,6 +13,7 @@
 // limitations under the License.
 
 package {
+    default_team: "trendy_team_fwk_core_networking",
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
@@ -24,11 +25,11 @@
     ],
     defaults: [
         "framework-connectivity-test-defaults",
-        "lib_mockito_extended"
+        "lib_mockito_extended",
     ],
     libs: [
         "androidx.annotation_annotation",
-        "net-utils-device-common-bpf",  // TestBpfMap extends IBpfMap.
+        "net-utils-device-common-bpf", // TestBpfMap extends IBpfMap.
     ],
     static_libs: [
         "androidx.test.ext.junit",
@@ -42,7 +43,9 @@
         "net-utils-device-common-wear",
         "modules-utils-build_system",
     ],
-    lint: { strict_updatability_linting: true },
+    lint: {
+        strict_updatability_linting: true,
+    },
 }
 
 java_library {
@@ -72,9 +75,11 @@
         "jsr305",
     ],
     static_libs: [
-        "kotlin-test"
+        "kotlin-test",
     ],
-    lint: { strict_updatability_linting: true },
+    lint: {
+        strict_updatability_linting: true,
+    },
 }
 
 java_test_host {
@@ -91,6 +96,8 @@
         "cts",
         "mts-networking",
         "mcts-networking",
+        "mts-tethering",
+        "mcts-tethering",
     ],
     data: [":ConnectivityTestPreparer"],
 }
diff --git a/staticlibs/testutils/app/connectivitychecker/Android.bp b/staticlibs/testutils/app/connectivitychecker/Android.bp
index 049ec9e..5af8c14 100644
--- a/staticlibs/testutils/app/connectivitychecker/Android.bp
+++ b/staticlibs/testutils/app/connectivitychecker/Android.bp
@@ -13,6 +13,7 @@
 // limitations under the License.
 
 package {
+    default_team: "trendy_team_fwk_core_networking",
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
@@ -30,5 +31,7 @@
         "net-tests-utils",
     ],
     host_required: ["net-tests-utils-host-common"],
-    lint: { strict_updatability_linting: true },
+    lint: {
+        strict_updatability_linting: true,
+    },
 }
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/ConnectUtil.kt b/staticlibs/testutils/devicetests/com/android/testutils/ConnectUtil.kt
index b1d64f8..8090d5b 100644
--- a/staticlibs/testutils/devicetests/com/android/testutils/ConnectUtil.kt
+++ b/staticlibs/testutils/devicetests/com/android/testutils/ConnectUtil.kt
@@ -116,7 +116,10 @@
         }
     }
 
-    private fun connectToWifiConfig(config: WifiConfiguration) {
+    // Suppress warning because WifiManager methods to connect to a config are
+    // documented not to be deprecated for privileged users.
+    @Suppress("DEPRECATION")
+    fun connectToWifiConfig(config: WifiConfiguration) {
         repeat(MAX_WIFI_CONNECT_RETRIES) {
             val error = runAsShell(permission.NETWORK_SETTINGS) {
                 val listener = ConnectWifiListener()
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/DevSdkIgnoreRunner.kt b/staticlibs/testutils/devicetests/com/android/testutils/DevSdkIgnoreRunner.kt
index 10accd4..69fdbf8 100644
--- a/staticlibs/testutils/devicetests/com/android/testutils/DevSdkIgnoreRunner.kt
+++ b/staticlibs/testutils/devicetests/com/android/testutils/DevSdkIgnoreRunner.kt
@@ -31,6 +31,7 @@
 import org.junit.runner.notification.Failure
 import org.junit.runner.notification.RunNotifier
 import org.junit.runners.Parameterized
+import org.mockito.Mockito
 
 /**
  * A runner that can skip tests based on the development SDK as defined in [DevSdkIgnoreRule].
@@ -124,6 +125,9 @@
             notifier.fireTestFailure(Failure(leakMonitorDesc,
                     IllegalStateException("Unexpected thread changes: $threadsDiff")))
         }
+        // Clears up internal state of all inline mocks.
+        // TODO: Call clearInlineMocks() at the end of each test.
+        Mockito.framework().clearInlineMocks()
         notifier.fireTestFinished(leakMonitorDesc)
     }
 
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/ExternalPacketForwarder.kt b/staticlibs/testutils/devicetests/com/android/testutils/ExternalPacketForwarder.kt
new file mode 100644
index 0000000..36eb795
--- /dev/null
+++ b/staticlibs/testutils/devicetests/com/android/testutils/ExternalPacketForwarder.kt
@@ -0,0 +1,44 @@
+/*
+ * 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
+ */
+
+package com.android.testutils
+
+import java.io.FileDescriptor
+
+class ExternalPacketForwarder(
+    srcFd: FileDescriptor,
+    mtu: Int,
+    dstFd: FileDescriptor,
+    forwardMap: Map<Int, Int>
+) : PacketForwarderBase(srcFd, mtu, dstFd, forwardMap) {
+
+    /**
+     * Prepares a packet for forwarding by potentially updating the
+     * source port based on the specified port remapping rules.
+     *
+     * @param buf The packet data as a byte array.
+     * @param version The IP version of the packet (e.g., 4 for IPv4).
+     */
+    override fun remapPort(buf: ByteArray, version: Int) {
+        val transportOffset = getTransportOffset(version)
+        val intPort = getRemappedPort(buf, transportOffset)
+
+        // Copy remapped source port.
+        if (intPort != 0) {
+            setPortAt(intPort, buf, transportOffset)
+        }
+   }
+}
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/InternalPacketForwarder.kt b/staticlibs/testutils/devicetests/com/android/testutils/InternalPacketForwarder.kt
new file mode 100644
index 0000000..58829dc
--- /dev/null
+++ b/staticlibs/testutils/devicetests/com/android/testutils/InternalPacketForwarder.kt
@@ -0,0 +1,43 @@
+/*
+ * 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
+ */
+
+package com.android.testutils
+
+import java.io.FileDescriptor
+
+class InternalPacketForwarder(
+    srcFd: FileDescriptor,
+    mtu: Int,
+    dstFd: FileDescriptor,
+    forwardMap: Map<Int, Int>
+) : PacketForwarderBase(srcFd, mtu, dstFd, forwardMap) {
+    /**
+     * Prepares a packet for forwarding by potentially updating the
+     * destination port based on the specified port remapping rules.
+     *
+     * @param buf The packet data as a byte array.
+     * @param version The IP version of the packet (e.g., 4 for IPv4).
+     */
+    override fun remapPort(buf: ByteArray, version: Int) {
+        val transportOffset = getTransportOffset(version) + DESTINATION_PORT_OFFSET
+        val extPort = getRemappedPort(buf, transportOffset)
+
+        // Copy remapped destination port.
+        if (extPort != 0) {
+            setPortAt(extPort, buf, transportOffset)
+        }
+    }
+}
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/PacketBridge.kt b/staticlibs/testutils/devicetests/com/android/testutils/PacketBridge.kt
index 1a2cc88..0b736d1 100644
--- a/staticlibs/testutils/devicetests/com/android/testutils/PacketBridge.kt
+++ b/staticlibs/testutils/devicetests/com/android/testutils/PacketBridge.kt
@@ -40,7 +40,8 @@
 class PacketBridge(
     context: Context,
     addresses: List<LinkAddress>,
-    dnsAddr: InetAddress
+    dnsAddr: InetAddress,
+    portMapping: List<Pair<Int, Int>>
 ) {
     private val binder = Binder()
 
@@ -56,6 +57,10 @@
     // Register test networks to ConnectivityService.
     private val internalNetworkCallback: TestableNetworkCallback
     private val externalNetworkCallback: TestableNetworkCallback
+
+    private val internalForwardMap = HashMap<Int, Int>()
+    private val externalForwardMap = HashMap<Int, Int>()
+
     val internalNetwork: Network
     val externalNetwork: Network
     init {
@@ -65,14 +70,28 @@
         externalNetworkCallback = exCb
         internalNetwork = inNet
         externalNetwork = exNet
+        for (mapping in portMapping) {
+            internalForwardMap[mapping.first] = mapping.second
+            externalForwardMap[mapping.second] = mapping.first
+        }
     }
 
     // Set up the packet bridge.
     private val internalFd = internalIface.fileDescriptor.fileDescriptor
     private val externalFd = externalIface.fileDescriptor.fileDescriptor
 
-    private val pr1 = PacketForwarder(internalFd, 1500, externalFd)
-    private val pr2 = PacketForwarder(externalFd, 1500, internalFd)
+    private val pr1 = InternalPacketForwarder(
+        internalFd,
+        1500,
+        externalFd,
+        internalForwardMap
+    )
+    private val pr2 = ExternalPacketForwarder(
+        externalFd,
+        1500,
+        internalFd,
+        externalForwardMap
+    )
 
     fun start() {
         IoUtils.setBlocking(internalFd, true /* blocking */)
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/PacketForwarder.java b/staticlibs/testutils/devicetests/com/android/testutils/PacketForwarderBase.java
similarity index 68%
rename from staticlibs/testutils/devicetests/com/android/testutils/PacketForwarder.java
rename to staticlibs/testutils/devicetests/com/android/testutils/PacketForwarderBase.java
index d8efb7d..5c79eb0 100644
--- a/staticlibs/testutils/devicetests/com/android/testutils/PacketForwarder.java
+++ b/staticlibs/testutils/devicetests/com/android/testutils/PacketForwarderBase.java
@@ -32,6 +32,7 @@
 
 import java.io.FileDescriptor;
 import java.io.IOException;
+import java.util.Map;
 import java.util.Objects;
 
 /**
@@ -57,8 +58,9 @@
  * from the http server, the same mechanism is applied but in a different direction,
  * where the source and destination will be swapped.
  */
-public class PacketForwarder extends Thread {
+public abstract class PacketForwarderBase extends Thread {
     private static final String TAG = "PacketForwarder";
+    static final int DESTINATION_PORT_OFFSET = 2;
 
     // The source fd to read packets from.
     @NonNull
@@ -70,8 +72,10 @@
     @NonNull
     final FileDescriptor mDstFd;
 
+    @NonNull
+    final Map<Integer, Integer> mPortRemapRules;
     /**
-     * Construct a {@link PacketForwarder}.
+     * Construct a {@link PacketForwarderBase}.
      *
      * This class reads packets from {@code srcFd} of a {@link TestNetworkInterface}, and
      * forwards them to the {@code dstFd} of another {@link TestNetworkInterface}.
@@ -82,13 +86,49 @@
      * @param srcFd   {@link FileDescriptor} to read packets from.
      * @param mtu     MTU of the test network.
      * @param dstFd   {@link FileDescriptor} to write packets to.
+     * @param portRemapRules    port remap rules
      */
-    public PacketForwarder(@NonNull FileDescriptor srcFd, int mtu,
-                           @NonNull FileDescriptor dstFd) {
+    public PacketForwarderBase(@NonNull FileDescriptor srcFd, int mtu,
+                           @NonNull FileDescriptor dstFd,
+                           @NonNull Map<Integer, Integer> portRemapRules) {
         super(TAG);
         mSrcFd = Objects.requireNonNull(srcFd);
         mBuf = new byte[mtu];
         mDstFd = Objects.requireNonNull(dstFd);
+        mPortRemapRules = Objects.requireNonNull(portRemapRules);
+    }
+
+    /**
+     * A method to prepare forwarding packets between two instances of {@link TestNetworkInterface},
+     * which includes ports mapping.
+     * Subclasses should override this method to implement the needed port remapping.
+     * For internal forwarder will remapped destination port,
+     * external forwarder will remapped source port.
+     * Example:
+     * An outgoing packet from the internal interface with
+     * source 1.2.3.4:1234 and destination 8.8.8.8:80
+     * might be translated to 8.8.8.8:1234 -> 1.2.3.4:8080 before forwarding.
+     * An outgoing packet from the external interface with
+     * source 1.2.3.4:8080 and destination 8.8.8.8:1234
+     * might be translated to 8.8.8.8:80 -> 1.2.3.4:1234 before forwarding.
+     */
+    abstract void remapPort(@NonNull byte[] buf, int version);
+
+    /**
+     * Retrieves a potentially remapped port number from a packet.
+     *
+     * @param buf            The packet data as a byte array.
+     * @param transportOffset The offset within the packet where the transport layer port begins.
+     * @return The remapped port if a mapping exists in the internal forwarding map,
+     *         otherwise returns 0 (indicating no remapping).
+     */
+    int getRemappedPort(@NonNull byte[] buf, int transportOffset) {
+        int port = PacketReflectorUtil.getPortAt(buf, transportOffset);
+        return mPortRemapRules.getOrDefault(port, 0);
+    }
+
+    int getTransportOffset(int version) {
+        return version == 4 ? IPV4_HEADER_LENGTH : IPV6_HEADER_LENGTH;
     }
 
     private void forwardPacket(@NonNull byte[] buf, int len) {
@@ -99,7 +139,13 @@
         }
     }
 
-    // Reads one packet from mSrcFd, and writes the packet to the mDstFd for supported protocols.
+    /**
+     * Reads one packet from mSrcFd, and writes the packet to the mDestFd for supported protocols.
+     * This includes:
+     * 1.Address Swapping: Swaps source and destination IP addresses.
+     * 2.Port Remapping: Remap port if necessary.
+     * 3.Checksum Recalculation: Updates IP and transport layer checksums to reflect changes.
+     */
     private void processPacket() {
         final int len = PacketReflectorUtil.readPacket(mSrcFd, mBuf);
         if (len < 1) {
@@ -142,13 +188,19 @@
         if (len < ipHdrLen + transportHdrLen) {
             throw new IllegalStateException("Unexpected buffer length: " + len);
         }
-        // Swap addresses.
+
+        // Swap source and destination address.
         PacketReflectorUtil.swapAddresses(mBuf, version);
 
+        // Remapping the port.
+        remapPort(mBuf, version);
+
+        // Fix IP and Transport layer checksum.
+        PacketReflectorUtil.fixPacketChecksum(mBuf, len, version, proto);
+
         // Send the packet to the destination fd.
         forwardPacket(mBuf, len);
     }
-
     @Override
     public void run() {
         Log.i(TAG, "starting fd=" + mSrcFd + " valid=" + mSrcFd.valid());
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/TestHttpServer.kt b/staticlibs/testutils/devicetests/com/android/testutils/TestHttpServer.kt
index 740bf63..f1f0c1c 100644
--- a/staticlibs/testutils/devicetests/com/android/testutils/TestHttpServer.kt
+++ b/staticlibs/testutils/devicetests/com/android/testutils/TestHttpServer.kt
@@ -25,8 +25,10 @@
  * A minimal HTTP server running on a random available port.
  *
  * @param host The host to listen to, or null to listen on all hosts
+ * @param port The port to listen to, or 0 to auto select
  */
-class TestHttpServer(host: String? = null) : NanoHTTPD(host, 0 /* auto-select the port */) {
+class TestHttpServer
+    @JvmOverloads constructor(host: String? = null, port: Int = 0) : NanoHTTPD(host, port) {
     // Map of URL path -> HTTP response code
     private val responses = HashMap<Request, Response>()
 
diff --git a/tests/benchmark/Android.bp b/tests/benchmark/Android.bp
index 6ea5347..7854bb5 100644
--- a/tests/benchmark/Android.bp
+++ b/tests/benchmark/Android.bp
@@ -15,6 +15,7 @@
 //
 
 package {
+    default_team: "trendy_team_fwk_core_networking",
     // See: http://go/android-license-faq
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
@@ -40,4 +41,3 @@
     test_suites: ["device-tests"],
     jarjar_rules: ":connectivity-jarjar-rules",
 }
-
diff --git a/tests/common/Android.bp b/tests/common/Android.bp
index 7b5c298..6e9d614 100644
--- a/tests/common/Android.bp
+++ b/tests/common/Android.bp
@@ -17,6 +17,7 @@
 // Tests in this folder are included both in unit tests and CTS.
 // They must be fast and stable, and exercise public or test APIs.
 package {
+    default_team: "trendy_team_fwk_core_networking",
     // See: http://go/android-license-faq
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
@@ -93,7 +94,10 @@
     name: "ConnectivityCoverageTests",
     // Tethering started on SDK 30
     min_sdk_version: "30",
-    test_suites: ["general-tests", "mts-tethering"],
+    test_suites: [
+        "general-tests",
+        "mts-tethering",
+    ],
     defaults: [
         "ConnectivityTestsLatestSdkDefaults",
         "framework-connectivity-internal-test-defaults",
@@ -185,7 +189,7 @@
 // See SuiteModuleLoader.java.
 // TODO: why are the modules separated by + instead of being separate entries in the array?
 mainline_presubmit_modules = [
-        "CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk+com.google.android.resolv.apex+com.google.android.tethering.apex",
+    "CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk+com.google.android.resolv.apex+com.google.android.tethering.apex",
 ]
 
 cc_defaults {
diff --git a/tests/common/java/android/net/NetworkCapabilitiesTest.java b/tests/common/java/android/net/NetworkCapabilitiesTest.java
index 3a3459b..6eb56c7b 100644
--- a/tests/common/java/android/net/NetworkCapabilitiesTest.java
+++ b/tests/common/java/android/net/NetworkCapabilitiesTest.java
@@ -54,13 +54,13 @@
 import static android.net.NetworkCapabilities.SIGNAL_STRENGTH_UNSPECIFIED;
 import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
 import static android.net.NetworkCapabilities.TRANSPORT_ETHERNET;
+import static android.net.NetworkCapabilities.TRANSPORT_SATELLITE;
 import static android.net.NetworkCapabilities.TRANSPORT_TEST;
 import static android.net.NetworkCapabilities.TRANSPORT_USB;
 import static android.net.NetworkCapabilities.TRANSPORT_VPN;
 import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
 import static android.net.NetworkCapabilities.TRANSPORT_WIFI_AWARE;
 import static android.os.Process.INVALID_UID;
-
 import static com.android.modules.utils.build.SdkLevel.isAtLeastS;
 import static com.android.modules.utils.build.SdkLevel.isAtLeastT;
 import static com.android.modules.utils.build.SdkLevel.isAtLeastV;
@@ -68,7 +68,6 @@
 import static com.android.testutils.MiscAsserts.assertEmpty;
 import static com.android.testutils.MiscAsserts.assertThrows;
 import static com.android.testutils.ParcelUtils.assertParcelingIsLossless;
-
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -82,25 +81,22 @@
 import android.net.wifi.aware.PeerHandle;
 import android.net.wifi.aware.WifiAwareNetworkSpecifier;
 import android.os.Build;
-import android.test.suitebuilder.annotation.SmallTest;
 import android.util.ArraySet;
 import android.util.Range;
-
+import androidx.test.filters.SmallTest;
 import com.android.testutils.CompatUtil;
 import com.android.testutils.ConnectivityModuleTest;
 import com.android.testutils.DevSdkIgnoreRule;
 import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
 import com.android.testutils.DevSdkIgnoreRunner;
-
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mockito;
-
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
 import java.util.Set;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mockito;
 
 @SmallTest
 @RunWith(DevSdkIgnoreRunner.class)
@@ -761,6 +757,47 @@
     }
 
     @Test
+    public void testSetNetworkSpecifierWithCellularAndSatelliteMultiTransportNc() {
+        final TelephonyNetworkSpecifier specifier = new TelephonyNetworkSpecifier(1);
+        NetworkCapabilities nc = new NetworkCapabilities.Builder()
+                .addTransportType(TRANSPORT_CELLULAR)
+                .addTransportType(TRANSPORT_SATELLITE)
+                .setNetworkSpecifier(specifier)
+                .build();
+        // Adding a specifier did not crash with 2 transports if it is cellular + satellite
+        assertEquals(specifier, nc.getNetworkSpecifier());
+    }
+
+    @Test
+    public void testSetNetworkSpecifierWithWifiAndSatelliteMultiTransportNc() {
+        final TelephonyNetworkSpecifier specifier = new TelephonyNetworkSpecifier(1);
+        NetworkCapabilities.Builder nc1 = new NetworkCapabilities.Builder();
+        nc1.addTransportType(TRANSPORT_SATELLITE).addTransportType(TRANSPORT_WIFI);
+        // Adding multiple transports specifier to crash, apart from cellular + satellite
+        // combination
+        assertThrows("Cannot set NetworkSpecifier on a NetworkCapability with multiple transports!",
+                IllegalStateException.class,
+                () -> nc1.build().setNetworkSpecifier(specifier));
+        assertThrows("Cannot set NetworkSpecifier on a NetworkCapability with multiple transports!",
+                IllegalStateException.class,
+                () -> nc1.setNetworkSpecifier(specifier));
+    }
+
+    @Test
+    public void testSetNetworkSpecifierOnTestWithCellularAndSatelliteMultiTransportNc() {
+        final TelephonyNetworkSpecifier specifier = new TelephonyNetworkSpecifier(1);
+        NetworkCapabilities nc = new NetworkCapabilities.Builder()
+                .addTransportType(TRANSPORT_TEST)
+                .addTransportType(TRANSPORT_CELLULAR)
+                .addTransportType(TRANSPORT_SATELLITE)
+                .setNetworkSpecifier(specifier)
+                .build();
+        // Adding a specifier did not crash with 3 transports , TEST + CELLULAR + SATELLITE and if
+        // one is test
+        assertEquals(specifier, nc.getNetworkSpecifier());
+    }
+
+    @Test
     public void testSetNetworkSpecifierOnTestMultiTransportNc() {
         final NetworkSpecifier specifier = CompatUtil.makeEthernetNetworkSpecifier("eth0");
         NetworkCapabilities nc = new NetworkCapabilities.Builder()
diff --git a/tests/common/java/android/net/nsd/NsdServiceInfoTest.java b/tests/common/java/android/net/nsd/NsdServiceInfoTest.java
index 79c4980..8e89037 100644
--- a/tests/common/java/android/net/nsd/NsdServiceInfoTest.java
+++ b/tests/common/java/android/net/nsd/NsdServiceInfoTest.java
@@ -119,6 +119,7 @@
         fullInfo.setSubtypes(Set.of("_thread", "_matter"));
         fullInfo.setPort(4242);
         fullInfo.setHostAddresses(List.of(IPV4_ADDRESS));
+        fullInfo.setHostname("home");
         fullInfo.setNetwork(new Network(123));
         fullInfo.setInterfaceIndex(456);
         checkParcelable(fullInfo);
@@ -134,6 +135,7 @@
         attributedInfo.setServiceType("_kitten._tcp");
         attributedInfo.setPort(4242);
         attributedInfo.setHostAddresses(List.of(IPV6_ADDRESS, IPV4_ADDRESS));
+        attributedInfo.setHostname("home");
         attributedInfo.setAttribute("color", "pink");
         attributedInfo.setAttribute("sound", (new String("にゃあ")).getBytes("UTF-8"));
         attributedInfo.setAttribute("adorable", (String) null);
@@ -169,6 +171,7 @@
         assertEquals(original.getServiceName(), result.getServiceName());
         assertEquals(original.getServiceType(), result.getServiceType());
         assertEquals(original.getHost(), result.getHost());
+        assertEquals(original.getHostname(), result.getHostname());
         assertTrue(original.getPort() == result.getPort());
         assertEquals(original.getNetwork(), result.getNetwork());
         assertEquals(original.getInterfaceIndex(), result.getInterfaceIndex());
diff --git a/tests/cts/hostside/Android.bp b/tests/cts/hostside/Android.bp
index a0aafc6..f6c0430 100644
--- a/tests/cts/hostside/Android.bp
+++ b/tests/cts/hostside/Android.bp
@@ -12,17 +12,14 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-next_app_data = [ ":CtsHostsideNetworkTestsAppNext" ]
+next_app_data = [":CtsHostsideNetworkTestsAppNext"]
 
 // The above line is put in place to prevent any future automerger merge conflict between aosp,
 // downstream branches. The CtsHostsideNetworkTestsAppNext target will not exist in
 // some downstream branches, but it should exist in aosp and some downstream branches.
 
-
-
-
-
 package {
+    default_team: "trendy_team_fwk_core_networking",
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
@@ -30,7 +27,10 @@
     name: "CtsHostsideNetworkTests",
     defaults: ["cts_defaults"],
     // Only compile source java files in this apk.
-    srcs: ["src/**/*.java"],
+    srcs: [
+        "src/**/*.java",
+        ":ArgumentConstants",
+    ],
     libs: [
         "net-tests-utils-host-device-common",
         "cts-tradefed",
@@ -45,7 +45,7 @@
         "general-tests",
         "mcts-tethering",
         "mts-tethering",
-        "sts"
+        "sts",
     ],
     data: [
         ":CtsHostsideNetworkTestsApp",
diff --git a/tests/cts/hostside/aidl/Android.bp b/tests/cts/hostside/aidl/Android.bp
index 2751f6f..18a5897 100644
--- a/tests/cts/hostside/aidl/Android.bp
+++ b/tests/cts/hostside/aidl/Android.bp
@@ -13,6 +13,7 @@
 // limitations under the License.
 
 package {
+    default_team: "trendy_team_fwk_core_networking",
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
diff --git a/tests/cts/hostside/app/Android.bp b/tests/cts/hostside/app/Android.bp
index 470bb17..cf4afa9 100644
--- a/tests/cts/hostside/app/Android.bp
+++ b/tests/cts/hostside/app/Android.bp
@@ -15,6 +15,7 @@
 //
 
 package {
+    default_team: "trendy_team_fwk_core_networking",
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
@@ -35,7 +36,10 @@
         "android.test.runner",
         "android.test.base",
     ],
-    srcs: ["src/**/*.java"],
+    srcs: [
+        "src/**/*.java",
+         ":ArgumentConstants",
+    ],
     // Tag this module as a cts test artifact
     test_suites: [
         "general-tests",
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractDefaultRestrictionsTest.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractDefaultRestrictionsTest.java
new file mode 100644
index 0000000..8a3e790
--- /dev/null
+++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractDefaultRestrictionsTest.java
@@ -0,0 +1,92 @@
+/*
+ * 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.
+ */
+
+package com.android.cts.net.hostside;
+
+import static android.app.ActivityManager.PROCESS_STATE_TOP_SLEEPING;
+
+import static org.junit.Assume.assumeTrue;
+
+import android.os.SystemClock;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Base class for default, always-on network restrictions.
+ */
+abstract class AbstractDefaultRestrictionsTest extends AbstractRestrictBackgroundNetworkTestCase {
+
+    @Before
+    public final void setUp() throws Exception {
+        super.setUp();
+
+        removePowerSaveModeWhitelist(TEST_APP2_PKG);
+        removePowerSaveModeExceptIdleWhitelist(TEST_APP2_PKG);
+
+        registerBroadcastReceiver();
+        assumeTrue("Feature not enabled", isNetworkBlockedForTopSleepingAndAbove());
+    }
+
+    @After
+    public final void tearDown() throws Exception {
+        super.tearDown();
+
+        removePowerSaveModeWhitelist(TEST_APP2_PKG);
+        removePowerSaveModeExceptIdleWhitelist(TEST_APP2_PKG);
+    }
+
+    @Test
+    public void testFgsNetworkAccess() throws Exception {
+        assertProcessStateBelow(PROCESS_STATE_TOP_SLEEPING);
+        SystemClock.sleep(PROCESS_STATE_TRANSITION_DELAY_MS);
+        assertNetworkAccess(false, null);
+
+        launchComponentAndAssertNetworkAccess(TYPE_COMPONENT_FOREGROUND_SERVICE);
+    }
+
+    @Test
+    public void testActivityNetworkAccess() throws Exception {
+        assertProcessStateBelow(PROCESS_STATE_TOP_SLEEPING);
+        SystemClock.sleep(PROCESS_STATE_TRANSITION_DELAY_MS);
+        assertNetworkAccess(false, null);
+
+        launchComponentAndAssertNetworkAccess(TYPE_COMPONENT_ACTIVTIY);
+    }
+
+    @Test
+    public void testBackgroundNetworkAccess_inFullAllowlist() throws Exception {
+        assertProcessStateBelow(PROCESS_STATE_TOP_SLEEPING);
+        SystemClock.sleep(PROCESS_STATE_TRANSITION_DELAY_MS);
+        assertNetworkAccess(false, null);
+
+        addPowerSaveModeWhitelist(TEST_APP2_PKG);
+        assertProcessStateBelow(PROCESS_STATE_TOP_SLEEPING);
+        assertNetworkAccess(true, null);
+    }
+
+    @Test
+    public void testBackgroundNetworkAccess_inExceptIdleAllowlist() throws Exception {
+        assertProcessStateBelow(PROCESS_STATE_TOP_SLEEPING);
+        SystemClock.sleep(PROCESS_STATE_TRANSITION_DELAY_MS);
+        assertNetworkAccess(false, null);
+
+        addPowerSaveModeExceptIdleWhitelist(TEST_APP2_PKG);
+        assertProcessStateBelow(PROCESS_STATE_TOP_SLEEPING);
+        assertNetworkAccess(true, null);
+    }
+}
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java
index 29aac3c..1241e18 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java
+++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java
@@ -23,6 +23,7 @@
 import static android.net.ConnectivityManager.ACTION_RESTRICT_BACKGROUND_CHANGED;
 import static android.os.BatteryManager.BATTERY_PLUGGED_ANY;
 
+import static com.android.cts.net.arguments.InstrumentationArguments.ARG_WAIVE_BIND_PRIORITY;
 import static com.android.cts.net.hostside.NetworkPolicyTestUtils.executeShellCommand;
 import static com.android.cts.net.hostside.NetworkPolicyTestUtils.forceRunJob;
 import static com.android.cts.net.hostside.NetworkPolicyTestUtils.getConnectivityManager;
@@ -65,11 +66,13 @@
 import android.util.Pair;
 
 import androidx.annotation.Nullable;
+import androidx.test.platform.app.InstrumentationRegistry;
 
 import com.android.compatibility.common.util.AmUtils;
 import com.android.compatibility.common.util.BatteryUtils;
 import com.android.compatibility.common.util.DeviceConfigStateHelper;
 import com.android.compatibility.common.util.ThrowingRunnable;
+import com.android.modules.utils.build.SdkLevel;
 
 import org.junit.Rule;
 import org.junit.rules.RuleChain;
@@ -90,6 +93,8 @@
 
     protected static final String TEST_PKG = "com.android.cts.net.hostside";
     protected static final String TEST_APP2_PKG = "com.android.cts.net.hostside.app2";
+    // TODO(b/321797685): Configure it via device-config once it is available.
+    protected static final long PROCESS_STATE_TRANSITION_DELAY_MS = TimeUnit.SECONDS.toMillis(5);
 
     private static final String TEST_APP2_ACTIVITY_CLASS = TEST_APP2_PKG + ".MyActivity";
     private static final String TEST_APP2_SERVICE_CLASS = TEST_APP2_PKG + ".MyForegroundService";
@@ -97,7 +102,6 @@
 
     private static final ComponentName TEST_JOB_COMPONENT = new ComponentName(
             TEST_APP2_PKG, TEST_APP2_JOB_SERVICE_CLASS);
-
     private static final int TEST_JOB_ID = 7357437;
 
     private static final int SLEEP_TIME_SEC = 1;
@@ -152,12 +156,14 @@
     private static final IntentFilter BATTERY_CHANGED_FILTER =
             new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
 
-    private static final String APP_NOT_FOREGROUND_ERROR = "app_not_fg";
-
     protected static final long TEMP_POWERSAVE_WHITELIST_DURATION_MS = 20_000; // 20 sec
 
     private static final long BROADCAST_TIMEOUT_MS = 5_000;
 
+    // Should be kept in sync with the constant in NetworkPolicyManagerService.
+    // TODO: b/322115994 - remove once the feature is in staging.
+    private static final boolean ALWAYS_RESTRICT_BACKGROUND_NETWORK = false;
+
     protected Context mContext;
     protected Instrumentation mInstrumentation;
     protected ConnectivityManager mCm;
@@ -181,7 +187,16 @@
         mUid = getUid(TEST_APP2_PKG);
         mMyUid = getUid(mContext.getPackageName());
         mServiceClient = new MyServiceClient(mContext);
-        mServiceClient.bind();
+
+        final Bundle args = InstrumentationRegistry.getArguments();
+        final int bindPriorityFlags;
+        if (Boolean.valueOf(args.getString(ARG_WAIVE_BIND_PRIORITY, "false"))) {
+            bindPriorityFlags = Context.BIND_WAIVE_PRIORITY;
+        } else {
+            bindPriorityFlags = Context.BIND_NOT_FOREGROUND;
+        }
+        mServiceClient.bind(bindPriorityFlags);
+
         mPowerManager = mContext.getSystemService(PowerManager.class);
         executeShellCommand("cmd netpolicy start-watching " + mUid);
         // Some of the test cases assume that Data saver mode is initially disabled, which might not
@@ -205,6 +220,21 @@
         if (null != lock && lock.isHeld()) lock.release();
     }
 
+    /**
+     * Check if the feature blocking network for top_sleeping and lower priority proc-states is
+     * enabled. This is a manual check because the feature flag infrastructure may not be available
+     * in all the branches that will get this code.
+     * TODO: b/322115994 - Use @RequiresFlagsEnabled with
+     * Flags.FLAG_NETWORK_BLOCKED_FOR_TOP_SLEEPING_AND_ABOVE once the tests are moved to cts.
+     */
+    protected boolean isNetworkBlockedForTopSleepingAndAbove() {
+        if (!SdkLevel.isAtLeastV()) {
+            return false;
+        }
+        final String output = executeShellCommand("device_config get backstage_power"
+                + " com.android.server.net.network_blocked_for_top_sleeping_and_above");
+        return Boolean.parseBoolean(output) && ALWAYS_RESTRICT_BACKGROUND_NETWORK;
+    }
     protected int getUid(String packageName) throws Exception {
         return mContext.getPackageManager().getPackageUid(packageName, 0);
     }
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/ConnOnActivityStartTest.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/ConnOnActivityStartTest.java
index 4004789..c1d576d 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/ConnOnActivityStartTest.java
+++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/ConnOnActivityStartTest.java
@@ -18,6 +18,7 @@
 
 
 import static android.app.ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
+import static android.app.ActivityManager.PROCESS_STATE_TOP_SLEEPING;
 
 import static com.android.cts.net.hostside.NetworkPolicyTestUtils.getUiDevice;
 import static com.android.cts.net.hostside.NetworkPolicyTestUtils.setRestrictBackground;
@@ -28,8 +29,13 @@
 import static com.android.cts.net.hostside.Property.METERED_NETWORK;
 import static com.android.cts.net.hostside.Property.NON_METERED_NETWORK;
 
+import static org.junit.Assume.assumeTrue;
+
+import android.os.SystemClock;
 import android.util.Log;
 
+import com.android.compatibility.common.util.ThrowingRunnable;
+
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
@@ -63,14 +69,14 @@
     @RequiredProperties({BATTERY_SAVER_MODE})
     public void testStartActivity_batterySaver() throws Exception {
         setBatterySaverMode(true);
-        assertLaunchedActivityHasNetworkAccess("testStartActivity_batterySaver");
+        assertLaunchedActivityHasNetworkAccess("testStartActivity_batterySaver", null);
     }
 
     @Test
     @RequiredProperties({DATA_SAVER_MODE, METERED_NETWORK})
     public void testStartActivity_dataSaver() throws Exception {
         setRestrictBackground(true);
-        assertLaunchedActivityHasNetworkAccess("testStartActivity_dataSaver");
+        assertLaunchedActivityHasNetworkAccess("testStartActivity_dataSaver", null);
     }
 
     @Test
@@ -79,7 +85,7 @@
         setDozeMode(true);
         // TODO (235284115): We need to turn on Doze every time before starting
         // the activity.
-        assertLaunchedActivityHasNetworkAccess("testStartActivity_doze");
+        assertLaunchedActivityHasNetworkAccess("testStartActivity_doze", null);
     }
 
     @Test
@@ -89,11 +95,24 @@
         setAppIdle(true);
         // TODO (235284115): We need to put the app into app standby mode every
         // time before starting the activity.
-        assertLaunchedActivityHasNetworkAccess("testStartActivity_appStandby");
+        assertLaunchedActivityHasNetworkAccess("testStartActivity_appStandby", null);
     }
 
-    private void assertLaunchedActivityHasNetworkAccess(String testName) throws Exception {
+    @Test
+    public void testStartActivity_default() throws Exception {
+        assumeTrue("Feature not enabled", isNetworkBlockedForTopSleepingAndAbove());
+        assertLaunchedActivityHasNetworkAccess("testStartActivity_default", () -> {
+            assertProcessStateBelow(PROCESS_STATE_TOP_SLEEPING);
+            SystemClock.sleep(PROCESS_STATE_TRANSITION_DELAY_MS);
+        });
+    }
+
+    private void assertLaunchedActivityHasNetworkAccess(String testName,
+            ThrowingRunnable onBeginIteration) throws Exception {
         for (int i = 0; i < TEST_ITERATION_COUNT; ++i) {
+            if (onBeginIteration != null) {
+                onBeginIteration.run();
+            }
             Log.i(TAG, testName + " start #" + i);
             launchComponentAndAssertNetworkAccess(TYPE_COMPONENT_ACTIVTIY);
             getUiDevice().pressHome();
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/DefaultRestrictionsMeteredTest.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/DefaultRestrictionsMeteredTest.java
new file mode 100644
index 0000000..f3a1026
--- /dev/null
+++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/DefaultRestrictionsMeteredTest.java
@@ -0,0 +1,23 @@
+/*
+ * 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.
+ */
+
+package com.android.cts.net.hostside;
+
+import static com.android.cts.net.hostside.Property.METERED_NETWORK;
+
+@RequiredProperties({METERED_NETWORK})
+public class DefaultRestrictionsMeteredTest extends AbstractDefaultRestrictionsTest {
+}
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/DefaultRestrictionsNonMeteredTest.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/DefaultRestrictionsNonMeteredTest.java
new file mode 100644
index 0000000..5651dd0
--- /dev/null
+++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/DefaultRestrictionsNonMeteredTest.java
@@ -0,0 +1,23 @@
+/*
+ * 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.
+ */
+
+package com.android.cts.net.hostside;
+
+import static com.android.cts.net.hostside.Property.NON_METERED_NETWORK;
+
+@RequiredProperties({NON_METERED_NETWORK})
+public class DefaultRestrictionsNonMeteredTest extends AbstractDefaultRestrictionsTest {
+}
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/MyServiceClient.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/MyServiceClient.java
index 93cc911..980ecd5 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/MyServiceClient.java
+++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/MyServiceClient.java
@@ -34,26 +34,30 @@
 
     private Context mContext;
     private ServiceConnection mServiceConnection;
-    private IMyService mService;
+    private volatile IMyService mService;
+    private final ConditionVariable mServiceCondition = new ConditionVariable();
 
     public MyServiceClient(Context context) {
         mContext = context;
     }
 
-    public void bind() {
+    /**
+     * Binds to a service in the test app to communicate state.
+     * @param bindPriorityFlags Flags to influence the process-state of the bound app.
+     */
+    public void bind(int bindPriorityFlags) {
         if (mService != null) {
             throw new IllegalStateException("Already bound");
         }
-
-        final ConditionVariable cv = new ConditionVariable();
         mServiceConnection = new ServiceConnection() {
             @Override
             public void onServiceConnected(ComponentName name, IBinder service) {
                 mService = IMyService.Stub.asInterface(service);
-                cv.open();
+                mServiceCondition.open();
             }
             @Override
             public void onServiceDisconnected(ComponentName name) {
+                mServiceCondition.close();
                 mService = null;
             }
         };
@@ -63,12 +67,8 @@
         // Needs to use BIND_NOT_FOREGROUND so app2 does not run in
         // the same process state as app
         mContext.bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE
-                | Context.BIND_NOT_FOREGROUND);
-        cv.block(TIMEOUT_MS);
-        if (mService == null) {
-            throw new IllegalStateException(
-                    "Could not bind to MyService service after " + TIMEOUT_MS + "ms");
-        }
+                | bindPriorityFlags);
+        ensureServiceConnection();
     }
 
     public void unbind() {
@@ -77,37 +77,56 @@
         }
     }
 
+    private void ensureServiceConnection() {
+        if (mService != null) {
+            return;
+        }
+        mServiceCondition.block(TIMEOUT_MS);
+        if (mService == null) {
+            throw new IllegalStateException(
+                    "Could not bind to MyService service after " + TIMEOUT_MS + "ms");
+        }
+    }
+
     public void registerBroadcastReceiver() throws RemoteException {
+        ensureServiceConnection();
         mService.registerBroadcastReceiver();
     }
 
     public int getCounters(String receiverName, String action) throws RemoteException {
+        ensureServiceConnection();
         return mService.getCounters(receiverName, action);
     }
 
     public String checkNetworkStatus() throws RemoteException {
+        ensureServiceConnection();
         return mService.checkNetworkStatus();
     }
 
     public String getRestrictBackgroundStatus() throws RemoteException {
+        ensureServiceConnection();
         return mService.getRestrictBackgroundStatus();
     }
 
     public void sendNotification(int notificationId, String notificationType)
             throws RemoteException {
+        ensureServiceConnection();
         mService.sendNotification(notificationId, notificationType);
     }
 
     public void registerNetworkCallback(final NetworkRequest request, INetworkCallback cb)
             throws RemoteException {
+        ensureServiceConnection();
         mService.registerNetworkCallback(request, cb);
     }
 
     public void unregisterNetworkCallback() throws RemoteException {
+        ensureServiceConnection();
         mService.unregisterNetworkCallback();
     }
 
     public int scheduleJob(JobInfo jobInfo) throws RemoteException {
+        ensureServiceConnection();
         return mService.scheduleJob(jobInfo);
     }
 }
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/NetworkCallbackTest.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/NetworkCallbackTest.java
index eb2347d..5552b8f 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/NetworkCallbackTest.java
+++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/NetworkCallbackTest.java
@@ -17,6 +17,7 @@
 package com.android.cts.net.hostside;
 
 import static android.app.ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
+import static android.app.ActivityManager.PROCESS_STATE_TOP_SLEEPING;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
 import static android.net.NetworkCapabilities.SIGNAL_STRENGTH_UNSPECIFIED;
 
@@ -34,6 +35,7 @@
 import android.net.NetworkCapabilities;
 import android.net.NetworkRequest;
 import android.net.cts.util.CtsNetUtils;
+import android.os.SystemClock;
 import android.util.Log;
 
 import com.android.modules.utils.build.SdkLevel;
@@ -43,6 +45,7 @@
 import org.junit.Rule;
 import org.junit.Test;
 
+import java.util.ArrayList;
 import java.util.Objects;
 import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.TimeUnit;
@@ -145,12 +148,22 @@
         public Network expectAvailableCallbackAndGetNetwork() {
             final CallbackInfo cb = nextCallback(TEST_CONNECT_TIMEOUT_MS);
             if (cb.state != CallbackState.AVAILABLE) {
-                fail("Network is not available. Instead obtained the following callback :"
-                        + cb);
+                fail("Network is not available. Instead obtained the following callback :" + cb);
             }
             return cb.network;
         }
 
+        public void drainAndWaitForIdle() {
+            try {
+                do {
+                    mCallbacks.drainTo(new ArrayList<>());
+                } while (mCallbacks.poll(TEST_CALLBACK_TIMEOUT_MS, TimeUnit.MILLISECONDS) != null);
+            } catch (InterruptedException ie) {
+                Log.e(TAG, "Interrupted while draining callback queue", ie);
+                Thread.currentThread().interrupt();
+            }
+        }
+
         public void expectBlockedStatusCallback(Network expectedNetwork, boolean expectBlocked) {
             expectCallback(CallbackState.BLOCKED_STATUS, expectedNetwork, expectBlocked);
         }
@@ -225,7 +238,7 @@
         // Check that the network is metered.
         mTestNetworkCallback.expectCapabilitiesCallbackEventually(mNetwork,
                 false /* hasCapability */, NET_CAPABILITY_NOT_METERED);
-        mTestNetworkCallback.expectBlockedStatusCallback(mNetwork, false);
+        mTestNetworkCallback.drainAndWaitForIdle();
 
         // Before Android T, DNS queries over private DNS should be but are not restricted by Power
         // Saver or Data Saver. The issue is fixed in mainline update and apps can no longer request
@@ -357,6 +370,58 @@
         }
     }
 
+    @Test
+    public void testOnBlockedStatusChanged_default() throws Exception {
+        assumeTrue("Feature not enabled", isNetworkBlockedForTopSleepingAndAbove());
+
+        try {
+            assertProcessStateBelow(PROCESS_STATE_TOP_SLEEPING);
+            assertNetworkAccess(false, null);
+            assertNetworkAccessBlockedByBpf(true, mUid, true /* metered */);
+
+            launchActivity();
+            assertTopState();
+            assertNetworkAccess(true, null);
+            mTestNetworkCallback.expectBlockedStatusCallbackEventually(mNetwork, false);
+            assertNetworkAccessBlockedByBpf(false, mUid, true /* metered */);
+
+            finishActivity();
+            assertProcessStateBelow(PROCESS_STATE_TOP_SLEEPING);
+            SystemClock.sleep(PROCESS_STATE_TRANSITION_DELAY_MS);
+            assertNetworkAccess(false, null);
+            mTestNetworkCallback.expectBlockedStatusCallbackEventually(mNetwork, true);
+            assertNetworkAccessBlockedByBpf(true, mUid, true /* metered */);
+
+        } finally {
+            mMeterednessConfiguration.resetNetworkMeteredness();
+        }
+
+        // Set to non-metered network
+        mMeterednessConfiguration.configureNetworkMeteredness(false);
+        mTestNetworkCallback.expectCapabilitiesCallbackEventually(mNetwork,
+                true /* hasCapability */, NET_CAPABILITY_NOT_METERED);
+        try {
+            assertProcessStateBelow(PROCESS_STATE_TOP_SLEEPING);
+            assertNetworkAccess(false, null);
+            assertNetworkAccessBlockedByBpf(true, mUid, false /* metered */);
+
+            launchActivity();
+            assertTopState();
+            assertNetworkAccess(true, null);
+            mTestNetworkCallback.expectBlockedStatusCallbackEventually(mNetwork, false);
+            assertNetworkAccessBlockedByBpf(false, mUid, false /* metered */);
+
+            finishActivity();
+            assertProcessStateBelow(PROCESS_STATE_TOP_SLEEPING);
+            SystemClock.sleep(PROCESS_STATE_TRANSITION_DELAY_MS);
+            assertNetworkAccess(false, null);
+            mTestNetworkCallback.expectBlockedStatusCallbackEventually(mNetwork, true);
+            assertNetworkAccessBlockedByBpf(true, mUid, false /* metered */);
+        } finally {
+            mMeterednessConfiguration.resetNetworkMeteredness();
+        }
+    }
+
     // TODO: 1. test against VPN lockdown.
     //       2. test against multiple networks.
 }
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/NetworkPolicyManagerTest.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/NetworkPolicyManagerTest.java
index 7aeca77..968e270 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/NetworkPolicyManagerTest.java
+++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/NetworkPolicyManagerTest.java
@@ -17,6 +17,7 @@
 package com.android.cts.net.hostside;
 
 import static android.app.ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
+import static android.app.ActivityManager.PROCESS_STATE_TOP_SLEEPING;
 import static android.os.Process.SYSTEM_UID;
 
 import static com.android.cts.net.hostside.NetworkPolicyTestUtils.assertIsUidRestrictedOnMeteredNetworks;
@@ -28,6 +29,9 @@
 
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+
+import android.os.SystemClock;
 
 import org.junit.After;
 import org.junit.Before;
@@ -238,4 +242,33 @@
             assertIsUidRestrictedOnMeteredNetworks(mUid, false /* expectedResult */);
         }
     }
+
+    @Test
+    public void testIsUidNetworkingBlocked_whenInBackground() throws Exception {
+        assumeTrue("Feature not enabled", isNetworkBlockedForTopSleepingAndAbove());
+
+        try {
+            assertProcessStateBelow(PROCESS_STATE_TOP_SLEEPING);
+            SystemClock.sleep(PROCESS_STATE_TRANSITION_DELAY_MS);
+            assertNetworkingBlockedStatusForUid(mUid, METERED, true /* expectedResult */);
+            assertTrue(isUidNetworkingBlocked(mUid, NON_METERED));
+
+            launchActivity();
+            assertTopState();
+            assertNetworkingBlockedStatusForUid(mUid, METERED, false /* expectedResult */);
+            assertFalse(isUidNetworkingBlocked(mUid, NON_METERED));
+
+            finishActivity();
+            assertProcessStateBelow(PROCESS_STATE_TOP_SLEEPING);
+            SystemClock.sleep(PROCESS_STATE_TRANSITION_DELAY_MS);
+            assertNetworkingBlockedStatusForUid(mUid, METERED, true /* expectedResult */);
+            assertTrue(isUidNetworkingBlocked(mUid, NON_METERED));
+
+            addPowerSaveModeWhitelist(TEST_APP2_PKG);
+            assertNetworkingBlockedStatusForUid(mUid, METERED, false /* expectedResult */);
+            assertFalse(isUidNetworkingBlocked(mUid, NON_METERED));
+        } finally {
+            removePowerSaveModeWhitelist(TEST_APP2_PKG);
+        }
+    }
 }
diff --git a/tests/cts/hostside/app2/Android.bp b/tests/cts/hostside/app2/Android.bp
index db92f5c..c526172 100644
--- a/tests/cts/hostside/app2/Android.bp
+++ b/tests/cts/hostside/app2/Android.bp
@@ -15,6 +15,7 @@
 //
 
 package {
+    default_team: "trendy_team_fwk_core_networking",
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
diff --git a/tests/cts/hostside/certs/Android.bp b/tests/cts/hostside/certs/Android.bp
index 60b5476..301973e 100644
--- a/tests/cts/hostside/certs/Android.bp
+++ b/tests/cts/hostside/certs/Android.bp
@@ -1,4 +1,5 @@
 package {
+    default_team: "trendy_team_fwk_core_networking",
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
diff --git a/thread/flags/Android.bp b/tests/cts/hostside/instrumentation_arguments/Android.bp
similarity index 64%
rename from thread/flags/Android.bp
rename to tests/cts/hostside/instrumentation_arguments/Android.bp
index 225022c..cdede36 100644
--- a/thread/flags/Android.bp
+++ b/tests/cts/hostside/instrumentation_arguments/Android.bp
@@ -1,4 +1,3 @@
-//
 // Copyright (C) 2024 The Android Open Source Project
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
@@ -12,24 +11,12 @@
 // 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 {
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
-aconfig_declarations {
-    name: "thread_aconfig_flags",
-    package: "com.android.net.thread.flags",
-    srcs: ["thread_base.aconfig"],
-}
-
-java_aconfig_library {
-    name: "thread_aconfig_flags_lib",
-    aconfig_declarations: "thread_aconfig_flags",
-    min_sdk_version: "30",
-    apex_available: [
-        "//apex_available:platform",
-        "com.android.tethering",
-    ],
+filegroup {
+    name: "ArgumentConstants",
+    srcs: ["src/**/*.java"],
 }
diff --git a/tests/cts/hostside/instrumentation_arguments/src/com/android/cts/net/arguments/InstrumentationArguments.java b/tests/cts/hostside/instrumentation_arguments/src/com/android/cts/net/arguments/InstrumentationArguments.java
new file mode 100644
index 0000000..472e347
--- /dev/null
+++ b/tests/cts/hostside/instrumentation_arguments/src/com/android/cts/net/arguments/InstrumentationArguments.java
@@ -0,0 +1,21 @@
+/*
+ * 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.
+ */
+
+package com.android.cts.net.arguments;
+
+public interface InstrumentationArguments {
+    String ARG_WAIVE_BIND_PRIORITY = "waive_bind_priority";
+}
diff --git a/tests/cts/hostside/networkslicingtestapp/Android.bp b/tests/cts/hostside/networkslicingtestapp/Android.bp
index 2aa3f69..100b6e4 100644
--- a/tests/cts/hostside/networkslicingtestapp/Android.bp
+++ b/tests/cts/hostside/networkslicingtestapp/Android.bp
@@ -15,6 +15,7 @@
 //
 
 package {
+    default_team: "trendy_team_fwk_core_networking",
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
@@ -39,8 +40,8 @@
 android_test_helper_app {
     name: "CtsHostsideNetworkCapTestsAppWithoutProperty",
     defaults: [
-           "cts_support_defaults",
-           "CtsHostsideNetworkCapTestsAppDefaults"
+        "cts_support_defaults",
+        "CtsHostsideNetworkCapTestsAppDefaults",
     ],
     manifest: "AndroidManifestWithoutProperty.xml",
 }
@@ -48,8 +49,8 @@
 android_test_helper_app {
     name: "CtsHostsideNetworkCapTestsAppWithProperty",
     defaults: [
-           "cts_support_defaults",
-           "CtsHostsideNetworkCapTestsAppDefaults"
+        "cts_support_defaults",
+        "CtsHostsideNetworkCapTestsAppDefaults",
     ],
     manifest: "AndroidManifestWithProperty.xml",
 }
@@ -57,8 +58,8 @@
 android_test_helper_app {
     name: "CtsHostsideNetworkCapTestsAppSdk33",
     defaults: [
-           "cts_support_defaults",
-           "CtsHostsideNetworkCapTestsAppDefaults"
+        "cts_support_defaults",
+        "CtsHostsideNetworkCapTestsAppDefaults",
     ],
     target_sdk_version: "33",
     manifest: "AndroidManifestWithoutProperty.xml",
diff --git a/tests/cts/hostside/src/com/android/cts/net/HostsideConnOnActivityStartTest.java b/tests/cts/hostside/src/com/android/cts/net/HostsideConnOnActivityStartTest.java
index 849ac7c..880e826 100644
--- a/tests/cts/hostside/src/com/android/cts/net/HostsideConnOnActivityStartTest.java
+++ b/tests/cts/hostside/src/com/android/cts/net/HostsideConnOnActivityStartTest.java
@@ -16,6 +16,8 @@
 
 package com.android.cts.net;
 
+import static com.android.cts.net.arguments.InstrumentationArguments.ARG_WAIVE_BIND_PRIORITY;
+
 import android.platform.test.annotations.FlakyTest;
 
 import com.android.testutils.SkipPresubmit;
@@ -26,9 +28,12 @@
 
 import org.junit.Test;
 
+import java.util.Map;
+
 @SkipPresubmit(reason = "Out of SLO flakiness")
 public class HostsideConnOnActivityStartTest extends HostsideNetworkTestCase {
     private static final String TEST_CLASS = TEST_PKG + ".ConnOnActivityStartTest";
+
     @BeforeClassWithInfo
     public static void setUpOnce(TestInformation testInfo) throws Exception {
         uninstallPackage(testInfo, TEST_APP2_PKG, false);
@@ -60,4 +65,11 @@
     public void testStartActivity_appStandby() throws Exception {
         runDeviceTests(TEST_PKG, TEST_CLASS, "testStartActivity_appStandby");
     }
+
+    // TODO(b/321848487): Annotate with @RequiresFlagsEnabled to mirror the device-side test.
+    @Test
+    public void testStartActivity_default() throws Exception {
+        runDeviceTestsWithArgs(TEST_PKG, TEST_CLASS, "testStartActivity_default",
+                Map.of(ARG_WAIVE_BIND_PRIORITY, "true"));
+    }
 }
diff --git a/tests/cts/hostside/src/com/android/cts/net/HostsideDefaultNetworkRestrictionsTests.java b/tests/cts/hostside/src/com/android/cts/net/HostsideDefaultNetworkRestrictionsTests.java
new file mode 100644
index 0000000..0d01fc1
--- /dev/null
+++ b/tests/cts/hostside/src/com/android/cts/net/HostsideDefaultNetworkRestrictionsTests.java
@@ -0,0 +1,103 @@
+/*
+ * 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.
+ */
+
+package com.android.cts.net;
+
+import static com.android.cts.net.arguments.InstrumentationArguments.ARG_WAIVE_BIND_PRIORITY;
+
+import com.android.testutils.SkipPresubmit;
+import com.android.tradefed.device.DeviceNotAvailableException;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.Map;
+
+// TODO(b/321848487): Annotate with @RequiresFlagsEnabled to mirror the device-side tests.
+@SkipPresubmit(reason = "Monitoring for flakiness")
+public class HostsideDefaultNetworkRestrictionsTests extends HostsideNetworkTestCase {
+    private static final String METERED_TEST_CLASS = TEST_PKG + ".DefaultRestrictionsMeteredTest";
+    private static final String NON_METERED_TEST_CLASS =
+            TEST_PKG + ".DefaultRestrictionsNonMeteredTest";
+
+    @Before
+    public void setUp() throws Exception {
+        uninstallPackage(TEST_APP2_PKG, false);
+        installPackage(TEST_APP2_APK);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        uninstallPackage(TEST_APP2_PKG, true);
+    }
+
+    private void runMeteredTest(String methodName) throws DeviceNotAvailableException {
+        runDeviceTestsWithArgs(TEST_PKG, METERED_TEST_CLASS, methodName,
+                Map.of(ARG_WAIVE_BIND_PRIORITY, "true"));
+    }
+
+    private void runNonMeteredTest(String methodName) throws DeviceNotAvailableException {
+        runDeviceTestsWithArgs(TEST_PKG, NON_METERED_TEST_CLASS, methodName,
+                Map.of(ARG_WAIVE_BIND_PRIORITY, "true"));
+    }
+
+    @Test
+    public void testMeteredNetworkAccess_defaultRestrictions_testActivityNetworkAccess()
+            throws Exception {
+        runMeteredTest("testActivityNetworkAccess");
+    }
+
+    @Test
+    public void testMeteredNetworkAccess_defaultRestrictions_testFgsNetworkAccess()
+            throws Exception {
+        runMeteredTest("testFgsNetworkAccess");
+    }
+
+    @Test
+    public void testMeteredNetworkAccess_defaultRestrictions_inFullAllowlist() throws Exception {
+        runMeteredTest("testBackgroundNetworkAccess_inFullAllowlist");
+    }
+
+    @Test
+    public void testMeteredNetworkAccess_defaultRestrictions_inExceptIdleAllowlist()
+            throws Exception {
+        runMeteredTest("testBackgroundNetworkAccess_inExceptIdleAllowlist");
+    }
+
+    @Test
+    public void testNonMeteredNetworkAccess_defaultRestrictions_testActivityNetworkAccess()
+            throws Exception {
+        runNonMeteredTest("testActivityNetworkAccess");
+    }
+
+    @Test
+    public void testNonMeteredNetworkAccess_defaultRestrictions_testFgsNetworkAccess()
+            throws Exception {
+        runNonMeteredTest("testFgsNetworkAccess");
+    }
+
+    @Test
+    public void testNonMeteredNetworkAccess_defaultRestrictions_inFullAllowlist() throws Exception {
+        runNonMeteredTest("testBackgroundNetworkAccess_inFullAllowlist");
+    }
+
+    @Test
+    public void testNonMeteredNetworkAccess_defaultRestrictions_inExceptIdleAllowlist()
+            throws Exception {
+        runNonMeteredTest("testBackgroundNetworkAccess_inExceptIdleAllowlist");
+    }
+}
diff --git a/tests/cts/hostside/src/com/android/cts/net/HostsideNetworkCallbackTests.java b/tests/cts/hostside/src/com/android/cts/net/HostsideNetworkCallbackTests.java
index 04bd1ad..361f7c7 100644
--- a/tests/cts/hostside/src/com/android/cts/net/HostsideNetworkCallbackTests.java
+++ b/tests/cts/hostside/src/com/android/cts/net/HostsideNetworkCallbackTests.java
@@ -15,12 +15,16 @@
  */
 package com.android.cts.net;
 
+import static com.android.cts.net.arguments.InstrumentationArguments.ARG_WAIVE_BIND_PRIORITY;
+
 import com.android.testutils.SkipPresubmit;
 
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 
+import java.util.Map;
+
 @SkipPresubmit(reason = "Out of SLO flakiness")
 public class HostsideNetworkCallbackTests extends HostsideNetworkTestCase {
 
@@ -46,5 +50,12 @@
         runDeviceTests(TEST_PKG,
                 TEST_PKG + ".NetworkCallbackTest", "testOnBlockedStatusChanged_powerSaver");
     }
+
+    // TODO(b/321848487): Annotate with @RequiresFlagsEnabled to mirror the device-side test.
+    @Test
+    public void testOnBlockedStatusChanged_default() throws Exception {
+        runDeviceTestsWithArgs(TEST_PKG, TEST_PKG + ".NetworkCallbackTest",
+                "testOnBlockedStatusChanged_default", Map.of(ARG_WAIVE_BIND_PRIORITY, "true"));
+    }
 }
 
diff --git a/tests/cts/hostside/src/com/android/cts/net/HostsideNetworkPolicyManagerTests.java b/tests/cts/hostside/src/com/android/cts/net/HostsideNetworkPolicyManagerTests.java
index 3ddb88b..e97db58 100644
--- a/tests/cts/hostside/src/com/android/cts/net/HostsideNetworkPolicyManagerTests.java
+++ b/tests/cts/hostside/src/com/android/cts/net/HostsideNetworkPolicyManagerTests.java
@@ -16,10 +16,14 @@
 
 package com.android.cts.net;
 
+import static com.android.cts.net.arguments.InstrumentationArguments.ARG_WAIVE_BIND_PRIORITY;
+
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 
+import java.util.Map;
+
 public class HostsideNetworkPolicyManagerTests extends HostsideNetworkTestCase {
     @Before
     public void setUp() throws Exception {
@@ -71,4 +75,12 @@
         runDeviceTests(TEST_PKG,
                 TEST_PKG + ".NetworkPolicyManagerTest", "testIsUidRestrictedOnMeteredNetworks");
     }
+
+    // TODO(b/321848487): Annotate with @RequiresFlagsEnabled to mirror the device-side test.
+    @Test
+    public void testIsUidNetworkingBlocked_whenInBackground() throws Exception {
+        runDeviceTestsWithArgs(TEST_PKG, TEST_PKG + ".NetworkPolicyManagerTest",
+                "testIsUidNetworkingBlocked_whenInBackground",
+                Map.of(ARG_WAIVE_BIND_PRIORITY, "true"));
+    }
 }
diff --git a/tests/cts/hostside/src/com/android/cts/net/HostsideNetworkTestCase.java b/tests/cts/hostside/src/com/android/cts/net/HostsideNetworkTestCase.java
index 3358fd7..ca95ed6 100644
--- a/tests/cts/hostside/src/com/android/cts/net/HostsideNetworkTestCase.java
+++ b/tests/cts/hostside/src/com/android/cts/net/HostsideNetworkTestCase.java
@@ -31,10 +31,13 @@
 import com.android.tradefed.testtype.junit4.AfterClassWithInfo;
 import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
 import com.android.tradefed.testtype.junit4.BeforeClassWithInfo;
+import com.android.tradefed.testtype.junit4.DeviceTestRunOptions;
 import com.android.tradefed.util.RunUtil;
 
 import org.junit.runner.RunWith;
 
+import java.util.Map;
+
 @RunWith(DeviceJUnit4ClassRunner.class)
 abstract class HostsideNetworkTestCase extends BaseHostJUnit4Test {
     protected static final boolean DEBUG = false;
@@ -146,6 +149,17 @@
                 + packageName + ", u=" + currentUser);
     }
 
+    protected boolean runDeviceTestsWithArgs(String packageName, String className,
+            String methodName, Map<String, String> args) throws DeviceNotAvailableException {
+        final DeviceTestRunOptions deviceTestRunOptions = new DeviceTestRunOptions(packageName)
+                .setTestClassName(className)
+                .setTestMethodName(methodName);
+        for (Map.Entry<String, String> arg : args.entrySet()) {
+            deviceTestRunOptions.addInstrumentationArg(arg.getKey(), arg.getValue());
+        }
+        return runDeviceTests(deviceTestRunOptions);
+    }
+
     protected String runCommand(String command) throws DeviceNotAvailableException {
         Log.d(TAG, "Command: '" + command + "'");
         final String output = getDevice().executeShellCommand(command);
diff --git a/tests/cts/multidevices/Android.bp b/tests/cts/multidevices/Android.bp
new file mode 100644
index 0000000..5ac4229
--- /dev/null
+++ b/tests/cts/multidevices/Android.bp
@@ -0,0 +1,42 @@
+// 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.
+
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+python_test_host {
+    name: "CtsConnectivityMultiDevicesTestCases",
+    main: "connectivity_multi_devices_test.py",
+    srcs: ["connectivity_multi_devices_test.py"],
+    libs: [
+        "mobly",
+    ],
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+    test_options: {
+        unit_test: false,
+    },
+    data: [
+        // Package the snippet with the mobly test
+        ":connectivity_multi_devices_snippet",
+    ],
+    version: {
+        py3: {
+            embedded_launcher: true,
+        },
+    },
+}
diff --git a/tests/cts/multidevices/AndroidTest.xml b/tests/cts/multidevices/AndroidTest.xml
new file mode 100644
index 0000000..5312b4d
--- /dev/null
+++ b/tests/cts/multidevices/AndroidTest.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<configuration description="Config for CTS Connectivity multi devices test cases">
+    <option name="test-suite-tag" value="cts" />
+    <option name="config-descriptor:metadata" key="component" value="networking" />
+    <option name="config-descriptor:metadata" key="token" value="SIM_CARD" />
+    <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
+    <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+
+    <device name="device1">
+        <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+            <option name="test-file-name" value="connectivity_multi_devices_snippet.apk" />
+        </target_preparer>
+        <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+        </target_preparer>
+    </device>
+    <device name="device2">
+        <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+            <option name="test-file-name" value="connectivity_multi_devices_snippet.apk" />
+        </target_preparer>
+        <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+        </target_preparer>
+    </device>
+
+    <test class="com.android.tradefed.testtype.mobly.MoblyBinaryHostTest">
+      <!-- The mobly-par-file-name should match the module name -->
+      <option name="mobly-par-file-name" value="CtsConnectivityMultiDevicesTestCases" />
+      <!-- Timeout limit in milliseconds for all test cases of the python binary -->
+      <option name="mobly-test-timeout" value="180000" />
+    </test>
+</configuration>
+
diff --git a/tests/cts/multidevices/connectivity_multi_devices_test.py b/tests/cts/multidevices/connectivity_multi_devices_test.py
new file mode 100644
index 0000000..ab88504
--- /dev/null
+++ b/tests/cts/multidevices/connectivity_multi_devices_test.py
@@ -0,0 +1,110 @@
+# Lint as: python3
+"""Connectivity multi devices tests."""
+import base64
+import sys
+import uuid
+
+from mobly import asserts
+from mobly import base_test
+from mobly import test_runner
+from mobly import utils
+from mobly.controllers import android_device
+
+CONNECTIVITY_MULTI_DEVICES_SNIPPET_PACKAGE = "com.google.snippet.connectivity"
+
+
+class UpstreamType:
+  CELLULAR = 1
+  WIFI = 2
+
+
+class ConnectivityMultiDevicesTest(base_test.BaseTestClass):
+
+  def setup_class(self):
+    # Declare that two Android devices are needed.
+    self.clientDevice, self.serverDevice = self.register_controller(
+        android_device, min_number=2
+    )
+
+    def setup_device(device):
+      device.load_snippet(
+          "connectivity_multi_devices_snippet",
+          CONNECTIVITY_MULTI_DEVICES_SNIPPET_PACKAGE,
+      )
+
+    # Set up devices in parallel to save time.
+    utils.concurrent_exec(
+        setup_device,
+        ((self.clientDevice,), (self.serverDevice,)),
+        max_workers=2,
+        raise_on_exception=True,
+    )
+
+  @staticmethod
+  def generate_uuid32_base64():
+    """Generates a UUID32 and encodes it in Base64.
+
+    Returns:
+        str: The Base64-encoded UUID32 string. Which is 22 characters.
+    """
+    return base64.b64encode(uuid.uuid1().bytes).decode("utf-8").strip("=")
+
+  def _do_test_hotspot_for_upstream_type(self, upstream_type):
+    """Test hotspot with the specified upstream type.
+
+    This test create a hotspot, make the client connect
+    to it, and verify the packet is forwarded by the hotspot.
+    """
+    server = self.serverDevice.connectivity_multi_devices_snippet
+    client = self.clientDevice.connectivity_multi_devices_snippet
+
+    # Assert pre-conditions specific to each upstream type.
+    asserts.skip_if(not client.hasWifiFeature(), "Client requires Wifi feature")
+    asserts.skip_if(
+      not server.hasHotspotFeature(), "Server requires hotspot feature"
+    )
+    if upstream_type == UpstreamType.CELLULAR:
+      asserts.skip_if(
+          not server.hasTelephonyFeature(), "Server requires Telephony feature"
+      )
+      server.requestCellularAndEnsureDefault()
+    elif upstream_type == UpstreamType.WIFI:
+      asserts.skip_if(
+          not server.isStaApConcurrencySupported(),
+          "Server requires Wifi AP + STA concurrency",
+      )
+      server.ensureWifiIsDefault()
+    else:
+      raise ValueError(f"Invalid upstream type: {upstream_type}")
+
+    # Generate ssid/passphrase with random characters to make sure nearby devices won't
+    # connect unexpectedly. Note that total length of ssid cannot go over 32.
+    testSsid = "HOTSPOT-" + self.generate_uuid32_base64()
+    testPassphrase = self.generate_uuid32_base64()
+
+    try:
+      # Create a hotspot with fixed SSID and password.
+      server.startHotspot(testSsid, testPassphrase)
+
+      # Make the client connects to the hotspot.
+      client.connectToWifi(testSsid, testPassphrase, True)
+
+    finally:
+      if upstream_type == UpstreamType.CELLULAR:
+        server.unrequestCellular()
+      # Teardown the hotspot.
+      server.stopAllTethering()
+
+  def test_hotspot_upstream_wifi(self):
+    self._do_test_hotspot_for_upstream_type(UpstreamType.WIFI)
+
+  def test_hotspot_upstream_cellular(self):
+    self._do_test_hotspot_for_upstream_type(UpstreamType.CELLULAR)
+
+
+if __name__ == "__main__":
+  # Take test args
+  if "--" in sys.argv:
+    index = sys.argv.index("--")
+    sys.argv = sys.argv[:1] + sys.argv[index + 1 :]
+  test_runner.main()
diff --git a/tests/cts/multidevices/snippet/Android.bp b/tests/cts/multidevices/snippet/Android.bp
new file mode 100644
index 0000000..5940cbb
--- /dev/null
+++ b/tests/cts/multidevices/snippet/Android.bp
@@ -0,0 +1,37 @@
+// 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.
+
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test_helper_app {
+    name: "connectivity_multi_devices_snippet",
+    defaults: [
+        "ConnectivityTestsLatestSdkDefaults",
+        "cts_defaults",
+        "framework-connectivity-test-defaults",
+    ],
+    srcs: [
+        "ConnectivityMultiDevicesSnippet.kt",
+    ],
+    manifest: "AndroidManifest.xml",
+    static_libs: [
+        "androidx.test.runner",
+        "mobly-snippet-lib",
+        "cts-net-utils",
+    ],
+    platform_apis: true,
+    min_sdk_version: "30", // R
+}
diff --git a/tests/cts/multidevices/snippet/AndroidManifest.xml b/tests/cts/multidevices/snippet/AndroidManifest.xml
new file mode 100644
index 0000000..9ed8146
--- /dev/null
+++ b/tests/cts/multidevices/snippet/AndroidManifest.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<manifest
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.google.snippet.connectivity">
+  <!-- Declare the minimum Android SDK version and internet permission,
+       which are required by Mobly Snippet Lib since it uses network socket. -->
+  <uses-sdk android:minSdkVersion="30" />
+  <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
+  <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
+  <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
+  <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
+  <uses-permission android:name="android.permission.INTERNET" />
+  <application>
+    <!-- Add any classes that implement the Snippet interface as meta-data, whose
+         value is a comma-separated string, each section being the package path
+         of a snippet class -->
+    <meta-data
+        android:name="mobly-snippets"
+        android:value="com.google.snippet.connectivity.ConnectivityMultiDevicesSnippet" />
+  </application>
+  <!-- Add an instrumentation tag so that the app can be launched through an
+       instrument command. The runner `com.google.android.mobly.snippet.SnippetRunner`
+       is derived from `AndroidJUnitRunner`, and is required to use the
+       Mobly Snippet Lib. -->
+  <instrumentation
+      android:name="com.google.android.mobly.snippet.SnippetRunner"
+      android:targetPackage="com.google.snippet.connectivity" />
+</manifest>
diff --git a/tests/cts/multidevices/snippet/ConnectivityMultiDevicesSnippet.kt b/tests/cts/multidevices/snippet/ConnectivityMultiDevicesSnippet.kt
new file mode 100644
index 0000000..115210b
--- /dev/null
+++ b/tests/cts/multidevices/snippet/ConnectivityMultiDevicesSnippet.kt
@@ -0,0 +1,170 @@
+/*
+ * 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.
+ */
+
+package com.google.snippet.connectivity
+
+import android.Manifest.permission.OVERRIDE_WIFI_CONFIG
+import android.content.pm.PackageManager.FEATURE_TELEPHONY
+import android.content.pm.PackageManager.FEATURE_WIFI
+import android.net.ConnectivityManager
+import android.net.Network
+import android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED
+import android.net.NetworkCapabilities.TRANSPORT_WIFI
+import android.net.NetworkRequest
+import android.net.cts.util.CtsNetUtils
+import android.net.cts.util.CtsTetheringUtils
+import android.net.wifi.ScanResult
+import android.net.wifi.SoftApConfiguration
+import android.net.wifi.SoftApConfiguration.SECURITY_TYPE_WPA2_PSK
+import android.net.wifi.WifiConfiguration
+import android.net.wifi.WifiManager
+import android.net.wifi.WifiNetworkSpecifier
+import android.net.wifi.WifiSsid
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.testutils.ConnectUtil
+import com.android.testutils.RecorderCallback.CallbackEntry.Available
+import com.android.testutils.RecorderCallback.CallbackEntry.CapabilitiesChanged
+import com.android.testutils.TestableNetworkCallback
+import com.android.testutils.runAsShell
+import com.google.android.mobly.snippet.Snippet
+import com.google.android.mobly.snippet.rpc.Rpc
+
+class ConnectivityMultiDevicesSnippet : Snippet {
+    private val context = InstrumentationRegistry.getInstrumentation().getTargetContext()
+    private val wifiManager = context.getSystemService(WifiManager::class.java)!!
+    private val cm = context.getSystemService(ConnectivityManager::class.java)!!
+    private val pm = context.packageManager
+    private val ctsNetUtils = CtsNetUtils(context)
+    private val ctsTetheringUtils = CtsTetheringUtils(context)
+    private var oldSoftApConfig: SoftApConfiguration? = null
+
+    @Rpc(description = "Check whether the device has wifi feature.")
+    fun hasWifiFeature() = pm.hasSystemFeature(FEATURE_WIFI)
+
+    @Rpc(description = "Check whether the device has telephony feature.")
+    fun hasTelephonyFeature() = pm.hasSystemFeature(FEATURE_TELEPHONY)
+
+    @Rpc(description = "Check whether the device supporters AP + STA concurrency.")
+    fun isStaApConcurrencySupported() {
+        wifiManager.isStaApConcurrencySupported()
+    }
+
+    @Rpc(description = "Request cellular connection and ensure it is the default network.")
+    fun requestCellularAndEnsureDefault() {
+        ctsNetUtils.disableWifi()
+        val network = ctsNetUtils.connectToCell()
+        ctsNetUtils.expectNetworkIsSystemDefault(network)
+    }
+
+    @Rpc(description = "Unrequest cellular connection.")
+    fun unrequestCellular() {
+        ctsNetUtils.disconnectFromCell()
+    }
+
+    @Rpc(description = "Ensure any wifi is connected and is the default network.")
+    fun ensureWifiIsDefault() {
+        val network = ctsNetUtils.ensureWifiConnected()
+        ctsNetUtils.expectNetworkIsSystemDefault(network)
+    }
+
+    @Rpc(description = "Connect to specified wifi network.")
+    // Suppress warning because WifiManager methods to connect to a config are
+    // documented not to be deprecated for privileged users.
+    @Suppress("DEPRECATION")
+    fun connectToWifi(ssid: String, passphrase: String, requireValidation: Boolean): Network {
+        val specifier = WifiNetworkSpecifier.Builder()
+            .setSsid(ssid)
+            .setWpa2Passphrase(passphrase)
+            .setBand(ScanResult.WIFI_BAND_24_GHZ)
+            .build()
+        val wifiConfig = WifiConfiguration()
+        wifiConfig.SSID = "\"" + ssid + "\""
+        wifiConfig.preSharedKey = "\"" + passphrase + "\""
+        wifiConfig.hiddenSSID = true
+        wifiConfig.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA2_PSK)
+        wifiConfig.allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.TKIP)
+        wifiConfig.allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.CCMP)
+
+        // Register network callback for the specific wifi.
+        val networkCallback = TestableNetworkCallback()
+        val wifiRequest = NetworkRequest.Builder().addTransportType(TRANSPORT_WIFI)
+            .setNetworkSpecifier(specifier)
+            .build()
+        cm.registerNetworkCallback(wifiRequest, networkCallback)
+
+        try {
+            // Add the test configuration and connect to it.
+            val connectUtil = ConnectUtil(context)
+            connectUtil.connectToWifiConfig(wifiConfig)
+
+            val event = networkCallback.expect<Available>()
+            if (requireValidation) {
+                networkCallback.eventuallyExpect<CapabilitiesChanged> {
+                    it.caps.hasCapability(NET_CAPABILITY_VALIDATED)
+                }
+            }
+            return event.network
+        } finally {
+            cm.unregisterNetworkCallback(networkCallback)
+        }
+    }
+
+    @Rpc(description = "Check whether the device supports hotspot feature.")
+    fun hasHotspotFeature(): Boolean {
+        val tetheringCallback = ctsTetheringUtils.registerTetheringEventCallback()
+        try {
+            return tetheringCallback.isWifiTetheringSupported(context)
+        } finally {
+            ctsTetheringUtils.unregisterTetheringEventCallback(tetheringCallback)
+        }
+    }
+
+    @Rpc(description = "Start a hotspot with given SSID and passphrase.")
+    fun startHotspot(ssid: String, passphrase: String) {
+        // Store old config.
+        runAsShell(OVERRIDE_WIFI_CONFIG) {
+            oldSoftApConfig = wifiManager.getSoftApConfiguration()
+        }
+
+        val softApConfig = SoftApConfiguration.Builder()
+            .setWifiSsid(WifiSsid.fromBytes(ssid.toByteArray()))
+            .setPassphrase(passphrase, SECURITY_TYPE_WPA2_PSK)
+            .setBand(SoftApConfiguration.BAND_2GHZ)
+            .build()
+        runAsShell(OVERRIDE_WIFI_CONFIG) {
+            wifiManager.setSoftApConfiguration(softApConfig)
+        }
+        val tetheringCallback = ctsTetheringUtils.registerTetheringEventCallback()
+        try {
+            tetheringCallback.expectNoTetheringActive()
+            ctsTetheringUtils.startWifiTethering(tetheringCallback)
+        } finally {
+            ctsTetheringUtils.unregisterTetheringEventCallback(tetheringCallback)
+        }
+    }
+
+    @Rpc(description = "Stop all tethering.")
+    fun stopAllTethering() {
+        ctsTetheringUtils.stopAllTethering()
+
+        // Restore old config.
+        oldSoftApConfig?.let {
+            runAsShell(OVERRIDE_WIFI_CONFIG) {
+                wifiManager.setSoftApConfiguration(it)
+            }
+        }
+    }
+}
diff --git a/tests/cts/net/Android.bp b/tests/cts/net/Android.bp
index 3d53d6c..074c587 100644
--- a/tests/cts/net/Android.bp
+++ b/tests/cts/net/Android.bp
@@ -13,6 +13,7 @@
 // limitations under the License.
 
 package {
+    default_team: "trendy_team_fwk_core_networking",
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
@@ -54,27 +55,26 @@
         "junit",
         "junit-params",
         "modules-utils-build",
+        "net-tests-utils",
         "net-utils-framework-common",
         "truth",
         "TetheringIntegrationTestsBaseLib",
     ],
 
-    // uncomment when b/13249961 is fixed
-    // sdk_version: "current",
-    platform_apis: true,
+    min_sdk_version: "30",
     per_testcase_directory: true,
     host_required: ["net-tests-utils-host-common"],
     test_config_template: "AndroidTestTemplate.xml",
     data: [
         ":ConnectivityTestPreparer",
         ":CtsCarrierServicePackage",
-    ]
+    ],
 }
 
 // Networking CTS tests for development and release. These tests always target the platform SDK
 // version, and are subject to all the restrictions appropriate to that version. Before SDK
-// finalization, these tests have a min_sdk_version of 10000, and cannot be installed on release
-// devices.
+// finalization, these tests have a min_sdk_version of 10000, but they can still be installed on
+// release devices as their min_sdk_version is set to a production version.
 android_test {
     name: "CtsNetTestCases",
     defaults: [
@@ -87,6 +87,14 @@
     ],
     test_suites: [
         "cts",
+        "mts-dnsresolver",
+        "mts-networking",
+        "mts-tethering",
+        "mts-wifi",
+        "mcts-dnsresolver",
+        "mcts-networking",
+        "mcts-tethering",
+        "mcts-wifi",
         "general-tests",
     ],
 }
diff --git a/tests/cts/net/api23Test/Android.bp b/tests/cts/net/api23Test/Android.bp
index 1f1dd5d..2ec3a70 100644
--- a/tests/cts/net/api23Test/Android.bp
+++ b/tests/cts/net/api23Test/Android.bp
@@ -13,6 +13,7 @@
 // limitations under the License.
 
 package {
+    default_team: "trendy_team_fwk_core_networking",
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
diff --git a/tests/cts/net/api23Test/src/android/net/cts/api23test/ConnectivityManagerApi23Test.java b/tests/cts/net/api23Test/src/android/net/cts/api23test/ConnectivityManagerApi23Test.java
index 8d68c5f..af1af43 100644
--- a/tests/cts/net/api23Test/src/android/net/cts/api23test/ConnectivityManagerApi23Test.java
+++ b/tests/cts/net/api23Test/src/android/net/cts/api23test/ConnectivityManagerApi23Test.java
@@ -65,7 +65,7 @@
         }
         ConnectivityReceiver.prepare();
 
-        mCtsNetUtils.toggleWifi();
+        mCtsNetUtils.reconnectWifiAndWaitForConnectivityAction();
 
         // The connectivity broadcast has been sent; push through a terminal broadcast
         // to wait for in the receive to confirm it didn't see the connectivity change.
@@ -88,7 +88,7 @@
                 .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
         Thread.sleep(200);
 
-        mCtsNetUtils.toggleWifi();
+        mCtsNetUtils.reconnectWifiAndWaitForConnectivityAction();
 
         Intent getConnectivityCount = new Intent(GET_WIFI_CONNECTIVITY_ACTION_COUNT);
         assertEquals(2, sendOrderedBroadcastAndReturnResultCode(
@@ -106,7 +106,7 @@
         filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
         mContext.registerReceiver(receiver, filter);
 
-        mCtsNetUtils.toggleWifi();
+        mCtsNetUtils.reconnectWifiAndWaitForConnectivityAction();
         Intent finalIntent = new Intent(ConnectivityReceiver.FINAL_ACTION);
         finalIntent.setClass(mContext, ConnectivityReceiver.class);
         mContext.sendBroadcast(finalIntent);
diff --git a/tests/cts/net/appForApi23/Android.bp b/tests/cts/net/appForApi23/Android.bp
index b39690f..d300743 100644
--- a/tests/cts/net/appForApi23/Android.bp
+++ b/tests/cts/net/appForApi23/Android.bp
@@ -13,6 +13,7 @@
 // limitations under the License.
 
 package {
+    default_team: "trendy_team_fwk_core_networking",
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
diff --git a/tests/cts/net/jni/Android.bp b/tests/cts/net/jni/Android.bp
index a421349..fbf4f29 100644
--- a/tests/cts/net/jni/Android.bp
+++ b/tests/cts/net/jni/Android.bp
@@ -13,6 +13,7 @@
 // limitations under the License.
 
 package {
+    default_team: "trendy_team_fwk_core_networking",
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
diff --git a/tests/cts/net/native/Android.bp b/tests/cts/net/native/Android.bp
index 153ff51..3f24592 100644
--- a/tests/cts/net/native/Android.bp
+++ b/tests/cts/net/native/Android.bp
@@ -15,6 +15,7 @@
 // Build the unit tests.
 
 package {
+    default_team: "trendy_team_fwk_core_networking",
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
diff --git a/tests/cts/net/native/dns/Android.bp b/tests/cts/net/native/dns/Android.bp
index a9e3715..8e24fba 100644
--- a/tests/cts/net/native/dns/Android.bp
+++ b/tests/cts/net/native/dns/Android.bp
@@ -1,4 +1,5 @@
 package {
+    default_team: "trendy_team_fwk_core_networking",
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
diff --git a/tests/cts/net/src/android/net/cts/BatteryStatsManagerTest.java b/tests/cts/net/src/android/net/cts/BatteryStatsManagerTest.java
index 466514c..3e5d0ba 100644
--- a/tests/cts/net/src/android/net/cts/BatteryStatsManagerTest.java
+++ b/tests/cts/net/src/android/net/cts/BatteryStatsManagerTest.java
@@ -199,7 +199,8 @@
         Log.d(TAG, "Generate traffic on wifi network.");
         generateNetworkTraffic(wifiNetwork, url);
         // Wifi battery stats are updated when wifi on.
-        mCtsNetUtils.toggleWifi();
+        mCtsNetUtils.disableWifi();
+        mCtsNetUtils.ensureWifiConnected();
 
         // Check wifi battery stats are updated.
         runAsShell(UPDATE_DEVICE_STATS,
diff --git a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
index 544f300..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());
@@ -1353,9 +1355,7 @@
     public void testToggleWifiConnectivityAction() throws Exception {
         assumeTrue(mPackageManager.hasSystemFeature(FEATURE_WIFI));
 
-        // toggleWifi calls connectToWifi and disconnectFromWifi, which both wait for
-        // CONNECTIVITY_ACTION broadcasts.
-        mCtsNetUtils.toggleWifi();
+        mCtsNetUtils.reconnectWifiAndWaitForConnectivityAction();
     }
 
     /** Verify restricted networks cannot be requested. */
@@ -1554,6 +1554,40 @@
         }
     }
 
+    @Test @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) @ConnectivityModuleTest
+    public void testSetBackgroundNetworkingShellCommand() {
+        final int testUid = 54352;
+        runShellCommand("cmd connectivity set-background-networking-enabled-for-uid " + testUid
+                + " true");
+        int rule = runAsShell(NETWORK_SETTINGS,
+                () -> mCm.getUidFirewallRule(FIREWALL_CHAIN_BACKGROUND, testUid));
+        assertEquals(rule, FIREWALL_RULE_ALLOW);
+
+        runShellCommand("cmd connectivity set-background-networking-enabled-for-uid " + testUid
+                + " false");
+        rule = runAsShell(NETWORK_SETTINGS,
+                () -> mCm.getUidFirewallRule(FIREWALL_CHAIN_BACKGROUND, testUid));
+        assertEquals(rule, FIREWALL_RULE_DENY);
+    }
+
+    @Test @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) @ConnectivityModuleTest
+    public void testGetBackgroundNetworkingShellCommand() {
+        final int testUid = 54312;
+        runAsShell(NETWORK_SETTINGS,
+                () -> mCm.setUidFirewallRule(FIREWALL_CHAIN_BACKGROUND, testUid,
+                        FIREWALL_RULE_ALLOW));
+        String output = runShellCommand(
+                "cmd connectivity get-background-networking-enabled-for-uid " + testUid);
+        assertTrue(output.contains("allow"));
+
+        runAsShell(NETWORK_SETTINGS,
+                () -> mCm.setUidFirewallRule(FIREWALL_CHAIN_BACKGROUND, testUid,
+                        FIREWALL_RULE_DEFAULT));
+        output = runShellCommand(
+                "cmd connectivity get-background-networking-enabled-for-uid " + testUid);
+        assertTrue(output.contains("deny"));
+    }
+
     // TODO: move the following socket keep alive test to dedicated test class.
     /**
      * Callback used in tcp keepalive offload that allows caller to wait callback fires.
diff --git a/tests/cts/net/src/android/net/cts/DiscoveryRequestTest.kt b/tests/cts/net/src/android/net/cts/DiscoveryRequestTest.kt
new file mode 100644
index 0000000..909a5bc
--- /dev/null
+++ b/tests/cts/net/src/android/net/cts/DiscoveryRequestTest.kt
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2023 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 android.net.cts
+
+import android.net.Network
+import android.net.nsd.DiscoveryRequest
+import android.os.Build
+import androidx.test.filters.SmallTest
+import com.android.testutils.ConnectivityModuleTest
+import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
+import com.android.testutils.DevSdkIgnoreRunner
+import com.android.testutils.assertParcelingIsLossless
+import com.android.testutils.assertThrows
+import kotlin.test.assertEquals
+import kotlin.test.assertNotEquals
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/** CTS tests for {@link DiscoveryRequest}. */
+@IgnoreUpTo(Build.VERSION_CODES.S_V2)
+@RunWith(DevSdkIgnoreRunner::class)
+@SmallTest
+@ConnectivityModuleTest
+class DiscoveryRequestTest {
+    @Test
+    fun testParcelingIsLossLess() {
+        val requestWithNullFields =
+                DiscoveryRequest.Builder("_ipps._tcp").build()
+        val requestWithAllFields =
+                DiscoveryRequest.Builder("_ipps._tcp")
+                                .setSubtype("_xyz")
+                                .setNetwork(Network(1))
+                                .build()
+
+        assertParcelingIsLossless(requestWithNullFields)
+        assertParcelingIsLossless(requestWithAllFields)
+    }
+
+    @Test
+    fun testBuilder_success() {
+        val request = DiscoveryRequest.Builder("_ipps._tcp")
+                                      .setSubtype("_xyz")
+                                      .setNetwork(Network(1))
+                                      .build()
+
+        assertEquals("_ipps._tcp", request.serviceType)
+        assertEquals("_xyz", request.subtype)
+        assertEquals(Network(1), request.network)
+    }
+
+    @Test
+    fun testBuilderConstructor_emptyServiceType_throwsIllegalArgument() {
+        assertThrows(IllegalArgumentException::class.java) {
+            DiscoveryRequest.Builder("")
+        }
+    }
+
+    @Test
+    fun testEquality() {
+        val request1 = DiscoveryRequest.Builder("_ipps._tcp").build()
+        val request2 = DiscoveryRequest.Builder("_ipps._tcp").build()
+        val request3 = DiscoveryRequest.Builder("_ipps._tcp")
+                .setSubtype("_xyz")
+                .setNetwork(Network(1))
+                .build()
+        val request4 = DiscoveryRequest.Builder("_ipps._tcp")
+                .setSubtype("_xyz")
+                .setNetwork(Network(1))
+                .build()
+
+        assertEquals(request1, request2)
+        assertEquals(request3, request4)
+        assertNotEquals(request1, request3)
+        assertNotEquals(request2, request4)
+    }
+}
diff --git a/tests/cts/net/src/android/net/cts/DnsResolverTest.java b/tests/cts/net/src/android/net/cts/DnsResolverTest.java
index 9ff0f2f..752891f 100644
--- a/tests/cts/net/src/android/net/cts/DnsResolverTest.java
+++ b/tests/cts/net/src/android/net/cts/DnsResolverTest.java
@@ -23,6 +23,7 @@
 import static android.net.DnsResolver.TYPE_AAAA;
 import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
 import static android.net.cts.util.CtsNetUtils.TestNetworkCallback;
+import static android.provider.DeviceConfig.NAMESPACE_CONNECTIVITY;
 import static android.system.OsConstants.ETIMEDOUT;
 
 import static com.android.testutils.DevSdkIgnoreRuleKt.SC_V2;
@@ -59,11 +60,14 @@
 import com.android.net.module.util.DnsPacket;
 import com.android.testutils.DevSdkIgnoreRule;
 import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
+import com.android.testutils.DeviceConfigRule;
 import com.android.testutils.DnsResolverModuleTest;
 import com.android.testutils.SkipPresubmit;
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.ClassRule;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -80,6 +84,8 @@
 @AppModeFull(reason = "WRITE_SECURE_SETTINGS permission can't be granted to instant apps")
 @RunWith(AndroidJUnit4.class)
 public class DnsResolverTest {
+    @ClassRule
+    public static final DeviceConfigRule DEVICE_CONFIG_CLASS_RULE = new DeviceConfigRule();
     @Rule
     public final DevSdkIgnoreRule ignoreRule = new DevSdkIgnoreRule();
 
@@ -123,6 +129,20 @@
 
     private TestNetworkCallback mWifiRequestCallback = null;
 
+    /**
+     * @see BeforeClass
+     */
+    @BeforeClass
+    public static void beforeClass() throws Exception {
+        // Use async private DNS resolution to avoid flakes due to races applying the setting
+        DEVICE_CONFIG_CLASS_RULE.setConfig(NAMESPACE_CONNECTIVITY,
+                "networkmonitor_async_privdns_resolution", "1");
+        // Make sure NetworkMonitor is restarted before and after the test so the flag is applied
+        // and cleaned up.
+        maybeToggleWifiAndCell();
+        DEVICE_CONFIG_CLASS_RULE.runAfterNextCleanup(DnsResolverTest::maybeToggleWifiAndCell);
+    }
+
     @Before
     public void setUp() throws Exception {
         mContext = InstrumentationRegistry.getContext();
@@ -144,6 +164,12 @@
         }
     }
 
+    private static void maybeToggleWifiAndCell() throws Exception {
+        final CtsNetUtils utils = new CtsNetUtils(InstrumentationRegistry.getContext());
+        utils.reconnectWifiIfSupported();
+        utils.reconnectCellIfSupported();
+    }
+
     private static String byteArrayToHexString(byte[] bytes) {
         char[] hexChars = new char[bytes.length * 2];
         for (int i = 0; i < bytes.length; ++i) {
diff --git a/tests/cts/net/src/android/net/cts/IpSecBaseTest.java b/tests/cts/net/src/android/net/cts/IpSecBaseTest.java
index 7f710d7..2a6c638 100644
--- a/tests/cts/net/src/android/net/cts/IpSecBaseTest.java
+++ b/tests/cts/net/src/android/net/cts/IpSecBaseTest.java
@@ -26,12 +26,15 @@
 import static android.system.OsConstants.FIONREAD;
 
 import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
 
 import android.content.Context;
 import android.net.ConnectivityManager;
 import android.net.IpSecAlgorithm;
 import android.net.IpSecManager;
 import android.net.IpSecTransform;
+import android.net.IpSecTransformState;
+import android.os.OutcomeReceiver;
 import android.platform.test.annotations.AppModeFull;
 import android.system.ErrnoException;
 import android.system.Os;
@@ -65,8 +68,12 @@
 import java.net.SocketImpl;
 import java.net.SocketOptions;
 import java.util.Arrays;
+import java.util.BitSet;
 import java.util.HashSet;
 import java.util.Set;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicInteger;
 
 @RunWith(AndroidJUnit4.class)
@@ -83,6 +90,7 @@
     protected static final byte[] TEST_DATA = "Best test data ever!".getBytes();
     protected static final int DATA_BUFFER_LEN = 4096;
     protected static final int SOCK_TIMEOUT = 500;
+    protected static final int REPLAY_BITMAP_LEN_BYTE = 512;
 
     private static final byte[] KEY_DATA = {
         0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
@@ -122,6 +130,47 @@
                                 .getSystemService(Context.CONNECTIVITY_SERVICE);
     }
 
+    protected static void checkTransformState(
+            IpSecTransform transform,
+            long txHighestSeqNum,
+            long rxHighestSeqNum,
+            long packetCnt,
+            long byteCnt,
+            byte[] replayBitmap)
+            throws Exception {
+        final CompletableFuture<IpSecTransformState> futureIpSecTransform =
+                new CompletableFuture<>();
+        transform.requestIpSecTransformState(
+                Executors.newSingleThreadExecutor(),
+                new OutcomeReceiver<IpSecTransformState, RuntimeException>() {
+                    @Override
+                    public void onResult(IpSecTransformState state) {
+                        futureIpSecTransform.complete(state);
+                    }
+                });
+
+        final IpSecTransformState transformState =
+                futureIpSecTransform.get(SOCK_TIMEOUT, TimeUnit.MILLISECONDS);
+
+        assertEquals(txHighestSeqNum, transformState.getTxHighestSequenceNumber());
+        assertEquals(rxHighestSeqNum, transformState.getRxHighestSequenceNumber());
+        assertEquals(packetCnt, transformState.getPacketCount());
+        assertEquals(byteCnt, transformState.getByteCount());
+        assertArrayEquals(replayBitmap, transformState.getReplayBitmap());
+    }
+
+    protected static void checkTransformStateNoTraffic(IpSecTransform transform) throws Exception {
+        checkTransformState(transform, 0L, 0L, 0L, 0L, newReplayBitmap(0));
+    }
+
+    protected static byte[] newReplayBitmap(int receivedPktCnt) {
+        final BitSet bitSet = new BitSet(REPLAY_BITMAP_LEN_BYTE * 8);
+        for (int i = 0; i < receivedPktCnt; i++) {
+            bitSet.set(i);
+        }
+        return Arrays.copyOf(bitSet.toByteArray(), REPLAY_BITMAP_LEN_BYTE);
+    }
+
     /** Checks if an IPsec algorithm is enabled on the device */
     protected static boolean hasIpSecAlgorithm(String algorithm) {
         if (SdkLevel.isAtLeastS()) {
diff --git a/tests/cts/net/src/android/net/cts/IpSecManagerTest.java b/tests/cts/net/src/android/net/cts/IpSecManagerTest.java
index fe86a90..a40ed0f 100644
--- a/tests/cts/net/src/android/net/cts/IpSecManagerTest.java
+++ b/tests/cts/net/src/android/net/cts/IpSecManagerTest.java
@@ -63,11 +63,13 @@
 import static org.junit.Assert.fail;
 import static org.junit.Assume.assumeTrue;
 
+import android.net.InetAddresses;
 import android.net.IpSecAlgorithm;
 import android.net.IpSecManager;
 import android.net.IpSecManager.SecurityParameterIndex;
 import android.net.IpSecManager.UdpEncapsulationSocket;
 import android.net.IpSecTransform;
+import android.net.NetworkUtils;
 import android.net.TrafficStats;
 import android.os.Build;
 import android.platform.test.annotations.AppModeFull;
@@ -381,6 +383,22 @@
         assumeTrue("Not supported by kernel", isIpv6UdpEncapSupportedByKernel());
     }
 
+    // TODO: b/319532485 Figure out whether to support x86_32
+    private static boolean isRequestTransformStateSupportedByKernel() {
+        return NetworkUtils.isKernel64Bit() || !NetworkUtils.isKernelX86();
+    }
+
+    // Package private for use in IpSecManagerTunnelTest
+    static boolean isRequestTransformStateSupported() {
+        return SdkLevel.isAtLeastV() && isRequestTransformStateSupportedByKernel();
+    }
+
+    // Package private for use in IpSecManagerTunnelTest
+    static void assumeRequestIpSecTransformStateSupported() {
+        assumeTrue("Not supported before V", SdkLevel.isAtLeastV());
+        assumeTrue("Not supported by kernel", isRequestTransformStateSupportedByKernel());
+    }
+
     @Test
     public void testCreateTransformIpv4() throws Exception {
         doTestCreateTransform(IPV4_LOOPBACK, false);
@@ -1596,4 +1614,32 @@
             assertTrue("Returned invalid port", encapSocket.getPort() != 0);
         }
     }
+
+    @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    @Test
+    public void testRequestIpSecTransformState() throws Exception {
+        assumeRequestIpSecTransformStateSupported();
+
+        final InetAddress localAddr = InetAddresses.parseNumericAddress(IPV6_LOOPBACK);
+        try (SecurityParameterIndex spi = mISM.allocateSecurityParameterIndex(localAddr);
+                IpSecTransform transform =
+                        buildTransportModeTransform(spi, localAddr, null /* encapSocket*/)) {
+            final SocketPair<JavaUdpSocket> sockets =
+                    getJavaUdpSocketPair(localAddr, mISM, transform, false);
+
+            sockets.mLeftSock.sendTo(TEST_DATA, localAddr, sockets.mRightSock.getPort());
+            sockets.mRightSock.receive();
+
+            final int expectedPacketCount = 1;
+            final int expectedInnerPacketSize = TEST_DATA.length + UDP_HDRLEN;
+
+            checkTransformState(
+                    transform,
+                    expectedPacketCount,
+                    expectedPacketCount,
+                    2 * (long) expectedPacketCount,
+                    2 * (long) expectedInnerPacketSize,
+                    newReplayBitmap(expectedPacketCount));
+        }
+    }
 }
diff --git a/tests/cts/net/src/android/net/cts/IpSecManagerTunnelTest.java b/tests/cts/net/src/android/net/cts/IpSecManagerTunnelTest.java
index 1ede5c1..22a51d6 100644
--- a/tests/cts/net/src/android/net/cts/IpSecManagerTunnelTest.java
+++ b/tests/cts/net/src/android/net/cts/IpSecManagerTunnelTest.java
@@ -19,7 +19,9 @@
 import static android.app.AppOpsManager.OP_MANAGE_IPSEC_TUNNELS;
 import static android.net.IpSecManager.UdpEncapsulationSocket;
 import static android.net.cts.IpSecManagerTest.assumeExperimentalIpv6UdpEncapSupported;
+import static android.net.cts.IpSecManagerTest.assumeRequestIpSecTransformStateSupported;
 import static android.net.cts.IpSecManagerTest.isIpv6UdpEncapSupported;
+import static android.net.cts.IpSecManagerTest.isRequestTransformStateSupported;
 import static android.net.cts.PacketUtils.AES_CBC_BLK_SIZE;
 import static android.net.cts.PacketUtils.AES_CBC_IV_LEN;
 import static android.net.cts.PacketUtils.BytePayload;
@@ -117,6 +119,8 @@
 
     private static final int TIMEOUT_MS = 500;
 
+    private static final int PACKET_COUNT = 5000;
+
     // Static state to reduce setup/teardown
     private static ConnectivityManager sCM;
     private static TestNetworkManager sTNM;
@@ -256,7 +260,7 @@
     }
 
     /* Test runnables for callbacks after IPsec tunnels are set up. */
-    private abstract class IpSecTunnelTestRunnable {
+    private interface IpSecTunnelTestRunnable {
         /**
          * Runs the test code, and returns the inner socket port, if any.
          *
@@ -282,8 +286,7 @@
                 throws Exception;
     }
 
-    private int getPacketSize(
-            int innerFamily, int outerFamily, boolean useEncap, boolean transportInTunnelMode) {
+    private static int getInnerPacketSize(int innerFamily, boolean transportInTunnelMode) {
         int expectedPacketSize = TEST_DATA.length + UDP_HDRLEN;
 
         // Inner Transport mode packet size
@@ -299,6 +302,13 @@
         // Inner IP Header
         expectedPacketSize += innerFamily == AF_INET ? IP4_HDRLEN : IP6_HDRLEN;
 
+        return expectedPacketSize;
+    }
+
+    private static int getPacketSize(
+            int innerFamily, int outerFamily, boolean useEncap, boolean transportInTunnelMode) {
+        int expectedPacketSize = getInnerPacketSize(innerFamily, transportInTunnelMode);
+
         // Tunnel mode transform size
         expectedPacketSize =
                 PacketUtils.calculateEspPacketSize(
@@ -401,6 +411,20 @@
                             spi, TEST_DATA, useEncap, expectedPacketSize);
                     socket.close();
 
+                    if (isRequestTransformStateSupported()) {
+                        final int innerPacketSize =
+                                getInnerPacketSize(innerFamily, transportInTunnelMode);
+
+                        checkTransformState(
+                                outTunnelTransform,
+                                seqNum,
+                                0L,
+                                seqNum,
+                                seqNum * (long) innerPacketSize,
+                                newReplayBitmap(0));
+                        checkTransformStateNoTraffic(inTunnelTransform);
+                    }
+
                     return innerSocketPort;
                 }
             };
@@ -524,6 +548,22 @@
 
                     socket.close();
 
+                    if (isRequestTransformStateSupported()) {
+                        final int innerFamily =
+                                localInner instanceof Inet4Address ? AF_INET : AF_INET6;
+                        final int innerPacketSize =
+                                getInnerPacketSize(innerFamily, transportInTunnelMode);
+
+                        checkTransformStateNoTraffic(outTunnelTransform);
+                        checkTransformState(
+                                inTunnelTransform,
+                                0L,
+                                seqNum,
+                                seqNum,
+                                seqNum * (long) innerPacketSize,
+                                newReplayBitmap(seqNum));
+                    }
+
                     return 0;
                 }
             };
@@ -1127,6 +1167,18 @@
         return innerSocketPort;
     }
 
+    private int buildTunnelNetworkAndRunTestsSimple(int spi, IpSecTunnelTestRunnable test)
+            throws Exception {
+        return buildTunnelNetworkAndRunTests(
+                LOCAL_INNER_6,
+                REMOTE_INNER_6,
+                LOCAL_OUTER_6,
+                REMOTE_OUTER_6,
+                spi,
+                null /* encapSocket */,
+                test);
+    }
+
     private static void receiveAndValidatePacket(JavaUdpSocket socket) throws Exception {
         byte[] socketResponseBytes = socket.receive();
         assertArrayEquals(TEST_DATA, socketResponseBytes);
@@ -1691,4 +1743,101 @@
         assumeExperimentalIpv6UdpEncapSupported();
         doTestMigrateTunnelModeTransform(AF_INET6, AF_INET6, true, false);
     }
+
+    @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    @Test
+    public void testRequestIpSecTransformStateForRx() throws Exception {
+        assumeRequestIpSecTransformStateSupported();
+
+        final int spi = getRandomSpi(LOCAL_OUTER_6, REMOTE_OUTER_6);
+        buildTunnelNetworkAndRunTestsSimple(
+                spi,
+                (ipsecNetwork,
+                        tunnelIface,
+                        tunUtils,
+                        inTunnelTransform,
+                        outTunnelTransform,
+                        localOuter,
+                        remoteOuter,
+                        seqNum) -> {
+                    // Build a socket and send traffic
+                    final JavaUdpSocket socket = new JavaUdpSocket(LOCAL_INNER_6);
+                    ipsecNetwork.bindSocket(socket.mSocket);
+                    int innerSocketPort = socket.getPort();
+
+                    for (int i = 1; i < PACKET_COUNT + 1; i++) {
+                        byte[] pkt =
+                                getTunnelModePacket(
+                                        spi,
+                                        REMOTE_INNER_6,
+                                        LOCAL_INNER_6,
+                                        remoteOuter,
+                                        localOuter,
+                                        innerSocketPort,
+                                        0,
+                                        i);
+                        tunUtils.injectPacket(pkt);
+                        receiveAndValidatePacket(socket);
+                    }
+
+                    final int innerPacketSize = getInnerPacketSize(AF_INET6, false);
+                    checkTransformState(
+                            inTunnelTransform,
+                            0L,
+                            PACKET_COUNT,
+                            PACKET_COUNT,
+                            PACKET_COUNT * (long) innerPacketSize,
+                            newReplayBitmap(REPLAY_BITMAP_LEN_BYTE * 8));
+
+                    return innerSocketPort;
+                });
+    }
+
+    @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    @Test
+    public void testRequestIpSecTransformStateForTx() throws Exception {
+        assumeRequestIpSecTransformStateSupported();
+
+        final int spi = getRandomSpi(LOCAL_OUTER_6, REMOTE_OUTER_6);
+        buildTunnelNetworkAndRunTestsSimple(
+                spi,
+                (ipsecNetwork,
+                        tunnelIface,
+                        tunUtils,
+                        inTunnelTransform,
+                        outTunnelTransform,
+                        localOuter,
+                        remoteOuter,
+                        seqNum) -> {
+                    // Build a socket and send traffic
+                    final JavaUdpSocket outSocket = new JavaUdpSocket(LOCAL_INNER_6);
+                    ipsecNetwork.bindSocket(outSocket.mSocket);
+                    int innerSocketPort = outSocket.getPort();
+
+                    int expectedPacketSize =
+                            getPacketSize(
+                                    AF_INET6,
+                                    AF_INET6,
+                                    false /* useEncap */,
+                                    false /* transportInTunnelMode */);
+
+                    for (int i = 0; i < PACKET_COUNT; i++) {
+                        outSocket.sendTo(TEST_DATA, REMOTE_INNER_6, innerSocketPort);
+                        tunUtils.awaitEspPacketNoPlaintext(
+                                spi, TEST_DATA, false /* useEncap */, expectedPacketSize);
+                    }
+
+                    final int innerPacketSize =
+                            getInnerPacketSize(AF_INET6, false /* transportInTunnelMode */);
+                    checkTransformState(
+                            outTunnelTransform,
+                            PACKET_COUNT,
+                            0L,
+                            PACKET_COUNT,
+                            PACKET_COUNT * (long) innerPacketSize,
+                            newReplayBitmap(0));
+
+                    return innerSocketPort;
+                });
+    }
 }
diff --git a/tests/cts/net/src/android/net/cts/IpSecTransformStateTest.java b/tests/cts/net/src/android/net/cts/IpSecTransformStateTest.java
new file mode 100644
index 0000000..7b42306
--- /dev/null
+++ b/tests/cts/net/src/android/net/cts/IpSecTransformStateTest.java
@@ -0,0 +1,99 @@
+/*
+ * 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.
+ */
+
+package android.net.cts;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.fail;
+
+import android.net.IpSecTransformState;
+import android.os.Build;
+import android.os.SystemClock;
+
+import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
+import com.android.testutils.DevSdkIgnoreRunner;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+@RunWith(DevSdkIgnoreRunner.class)
+public class IpSecTransformStateTest {
+    @Rule public final DevSdkIgnoreRule ignoreRule = new DevSdkIgnoreRule();
+
+    private static final long TIMESTAMP_MILLIS = 1000L;
+    private static final long HIGHEST_SEQ_NUMBER_TX = 10000L;
+    private static final long HIGHEST_SEQ_NUMBER_RX = 20000L;
+    private static final long PACKET_COUNT = 9000L;
+    private static final long BYTE_COUNT = 900000L;
+
+    private static final int REPLAY_BITMAP_LEN_BYTE = 512;
+    private static final byte[] REPLAY_BITMAP_NO_PACKETS = new byte[REPLAY_BITMAP_LEN_BYTE];
+    private static final byte[] REPLAY_BITMAP_ALL_RECEIVED = new byte[REPLAY_BITMAP_LEN_BYTE];
+
+    static {
+        for (int i = 0; i < REPLAY_BITMAP_ALL_RECEIVED.length; i++) {
+            REPLAY_BITMAP_ALL_RECEIVED[i] = (byte) 0xff;
+        }
+    }
+
+    @Test
+    public void testBuildAndGet() {
+        final IpSecTransformState state =
+                new IpSecTransformState.Builder()
+                        .setTimestampMillis(TIMESTAMP_MILLIS)
+                        .setTxHighestSequenceNumber(HIGHEST_SEQ_NUMBER_TX)
+                        .setRxHighestSequenceNumber(HIGHEST_SEQ_NUMBER_RX)
+                        .setPacketCount(PACKET_COUNT)
+                        .setByteCount(BYTE_COUNT)
+                        .setReplayBitmap(REPLAY_BITMAP_ALL_RECEIVED)
+                        .build();
+
+        assertEquals(TIMESTAMP_MILLIS, state.getTimestampMillis());
+        assertEquals(HIGHEST_SEQ_NUMBER_TX, state.getTxHighestSequenceNumber());
+        assertEquals(HIGHEST_SEQ_NUMBER_RX, state.getRxHighestSequenceNumber());
+        assertEquals(PACKET_COUNT, state.getPacketCount());
+        assertEquals(BYTE_COUNT, state.getByteCount());
+        assertArrayEquals(REPLAY_BITMAP_ALL_RECEIVED, state.getReplayBitmap());
+    }
+
+    @Test
+    public void testSelfGeneratedTimestampMillis() {
+        final long elapsedRealtimeBefore = SystemClock.elapsedRealtime();
+
+        final IpSecTransformState state =
+                new IpSecTransformState.Builder().setReplayBitmap(REPLAY_BITMAP_NO_PACKETS).build();
+
+        final long elapsedRealtimeAfter = SystemClock.elapsedRealtime();
+
+        // Verify  elapsedRealtimeBefore <= state.getTimestampMillis() <= elapsedRealtimeAfter
+        assertFalse(elapsedRealtimeBefore > state.getTimestampMillis());
+        assertFalse(elapsedRealtimeAfter < state.getTimestampMillis());
+    }
+
+    @Test
+    public void testBuildWithoutReplayBitmap() throws Exception {
+        try {
+            new IpSecTransformState.Builder().build();
+            fail("Expected expcetion if replay bitmap is not set");
+        } catch (NullPointerException expected) {
+        }
+    }
+}
diff --git a/tests/cts/net/src/android/net/cts/MultinetworkApiTest.java b/tests/cts/net/src/android/net/cts/MultinetworkApiTest.java
index 17a9ca2..bca18f5 100644
--- a/tests/cts/net/src/android/net/cts/MultinetworkApiTest.java
+++ b/tests/cts/net/src/android/net/cts/MultinetworkApiTest.java
@@ -17,6 +17,12 @@
 package android.net.cts;
 
 import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
+import static android.provider.DeviceConfig.NAMESPACE_CONNECTIVITY;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.fail;
 
 import android.content.ContentResolver;
 import android.content.Context;
@@ -28,9 +34,21 @@
 import android.platform.test.annotations.AppModeFull;
 import android.system.ErrnoException;
 import android.system.OsConstants;
-import android.test.AndroidTestCase;
 
-public class MultinetworkApiTest extends AndroidTestCase {
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.testutils.DeviceConfigRule;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class MultinetworkApiTest {
+    @Rule
+    public final DeviceConfigRule mDeviceConfigRule = new DeviceConfigRule();
 
     static {
         System.loadLibrary("nativemultinetwork_jni");
@@ -58,20 +76,17 @@
     private CtsNetUtils mCtsNetUtils;
     private String mOldMode;
     private String mOldDnsSpecifier;
+    private Context mContext;
 
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-        mCM = (ConnectivityManager) getContext().getSystemService(Context.CONNECTIVITY_SERVICE);
-        mCR = getContext().getContentResolver();
-        mCtsNetUtils = new CtsNetUtils(getContext());
+    @Before
+    public void setUp() throws Exception {
+        mContext = InstrumentationRegistry.getInstrumentation().getContext();
+        mCM = mContext.getSystemService(ConnectivityManager.class);
+        mCR = mContext.getContentResolver();
+        mCtsNetUtils = new CtsNetUtils(mContext);
     }
 
-    @Override
-    protected void tearDown() throws Exception {
-        super.tearDown();
-    }
-
+    @Test
     public void testGetaddrinfo() throws ErrnoException {
         for (Network network : mCtsNetUtils.getTestableNetworks()) {
             int errno = runGetaddrinfoCheck(network.getNetworkHandle());
@@ -82,6 +97,7 @@
         }
     }
 
+    @Test
     @AppModeFull(reason = "CHANGE_NETWORK_STATE permission can't be granted to instant apps")
     public void testSetprocnetwork() throws ErrnoException {
         // Hopefully no prior test in this process space has set a default network.
@@ -125,6 +141,7 @@
         }
     }
 
+    @Test
     @AppModeFull(reason = "CHANGE_NETWORK_STATE permission can't be granted to instant apps")
     public void testSetsocknetwork() throws ErrnoException {
         for (Network network : mCtsNetUtils.getTestableNetworks()) {
@@ -136,6 +153,7 @@
         }
     }
 
+    @Test
     public void testNativeDatagramTransmission() throws ErrnoException {
         for (Network network : mCtsNetUtils.getTestableNetworks()) {
             int errno = runDatagramCheck(network.getNetworkHandle());
@@ -146,6 +164,7 @@
         }
     }
 
+    @Test
     public void testNoSuchNetwork() {
         final Network eNoNet = new Network(54321);
         assertNull(mCM.getNetworkInfo(eNoNet));
@@ -158,6 +177,7 @@
         // assertEquals(-OsConstants.ENONET, runGetaddrinfoCheck(eNoNetHandle));
     }
 
+    @Test
     public void testNetworkHandle() {
         // Test Network -> NetworkHandle -> Network results in the same Network.
         for (Network network : mCtsNetUtils.getTestableNetworks()) {
@@ -181,6 +201,7 @@
         } catch (IllegalArgumentException e) {}
     }
 
+    @Test
     public void testResNApi() throws Exception {
         final Network[] testNetworks = mCtsNetUtils.getTestableNetworks();
 
@@ -201,9 +222,21 @@
         }
     }
 
+    @Test
     @AppModeFull(reason = "WRITE_SECURE_SETTINGS permission can't be granted to instant apps")
-    public void testResNApiNXDomainPrivateDns() throws InterruptedException {
+    public void testResNApiNXDomainPrivateDns() throws Exception {
+        // Use async private DNS resolution to avoid flakes due to races applying the setting
+        mDeviceConfigRule.setConfig(NAMESPACE_CONNECTIVITY,
+                "networkmonitor_async_privdns_resolution", "1");
+        mCtsNetUtils.reconnectWifiIfSupported();
+        mCtsNetUtils.reconnectCellIfSupported();
+
         mCtsNetUtils.storePrivateDnsSetting();
+
+        mDeviceConfigRule.runAfterNextCleanup(() -> {
+            mCtsNetUtils.reconnectWifiIfSupported();
+            mCtsNetUtils.reconnectCellIfSupported();
+        });
         // Enable private DNS strict mode and set server to dns.google before doing NxDomain test.
         // b/144521720
         try {
diff --git a/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt b/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt
index 84b6745..beb9274 100644
--- a/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt
+++ b/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt
@@ -21,6 +21,7 @@
 import android.app.Instrumentation
 import android.content.Context
 import android.content.pm.PackageManager
+import android.content.pm.PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION
 import android.net.ConnectivityManager
 import android.net.EthernetNetworkSpecifier
 import android.net.INetworkAgent
@@ -70,6 +71,7 @@
 import android.net.TelephonyNetworkSpecifier
 import android.net.TestNetworkInterface
 import android.net.TestNetworkManager
+import android.net.TransportInfo
 import android.net.Uri
 import android.net.VpnManager
 import android.net.VpnTransportInfo
@@ -150,6 +152,7 @@
 import kotlin.test.assertTrue
 import kotlin.test.fail
 import org.junit.After
+import org.junit.Assume.assumeTrue
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -574,27 +577,13 @@
     }
 
     private fun doTestAllowedUids(
-            subId: Int,
-            transport: Int,
-            uid: Int,
-            expectUidsPresent: Boolean
-    ) {
-        doTestAllowedUids(subId, intArrayOf(transport), uid, expectUidsPresent)
-    }
-
-    private fun doTestAllowedUids(
-            subId: Int,
             transports: IntArray,
             uid: Int,
-            expectUidsPresent: Boolean
+            expectUidsPresent: Boolean,
+            specifier: NetworkSpecifier?,
+            transportInfo: TransportInfo?
     ) {
         val callback = TestableNetworkCallback(DEFAULT_TIMEOUT_MS)
-        val specifier = when {
-            transports.size != 1 -> null
-            TRANSPORT_ETHERNET in transports -> EthernetNetworkSpecifier("testInterface")
-            TRANSPORT_CELLULAR in transports -> TelephonyNetworkSpecifier(subId)
-            else -> null
-        }
         val agent = createNetworkAgent(initialNc = NetworkCapabilities.Builder().run {
             addTransportType(TRANSPORT_TEST)
             transports.forEach { addTransportType(it) }
@@ -602,10 +591,7 @@
             addCapability(NET_CAPABILITY_NOT_SUSPENDED)
             removeCapability(NET_CAPABILITY_NOT_RESTRICTED)
             setNetworkSpecifier(specifier)
-            if (TRANSPORT_WIFI in transports && SdkLevel.isAtLeastV()) {
-                // setSubscriptionId only exists in V+
-                setTransportInfo(WifiInfo.Builder().setSubscriptionId(subId).build())
-            }
+            setTransportInfo(transportInfo)
             setAllowedUids(setOf(uid))
             setOwnerUid(Process.myUid())
             setAdministratorUids(intArrayOf(Process.myUid()))
@@ -630,6 +616,45 @@
         // callback will be unregistered in tearDown()
     }
 
+    private fun doTestAllowedUids(
+            transport: Int,
+            uid: Int,
+            expectUidsPresent: Boolean
+    ) {
+        doTestAllowedUids(intArrayOf(transport), uid, expectUidsPresent,
+                specifier = null, transportInfo = null)
+    }
+
+    private fun doTestAllowedUidsWithSubId(
+            subId: Int,
+            transport: Int,
+            uid: Int,
+            expectUidsPresent: Boolean
+    ) {
+        doTestAllowedUidsWithSubId(subId, intArrayOf(transport), uid, expectUidsPresent)
+    }
+
+    private fun doTestAllowedUidsWithSubId(
+            subId: Int,
+            transports: IntArray,
+            uid: Int,
+            expectUidsPresent: Boolean
+    ) {
+        val specifier = when {
+            transports.size != 1 -> null
+            TRANSPORT_ETHERNET in transports -> EthernetNetworkSpecifier("testInterface")
+            TRANSPORT_CELLULAR in transports -> TelephonyNetworkSpecifier(subId)
+            else -> null
+        }
+        val transportInfo = if (TRANSPORT_WIFI in transports && SdkLevel.isAtLeastV()) {
+            // setSubscriptionId only exists in V+
+            WifiInfo.Builder().setSubscriptionId(subId).build()
+        } else {
+            null
+        }
+        doTestAllowedUids(transports, uid, expectUidsPresent, specifier, transportInfo)
+    }
+
     private fun setHoldCarrierPrivilege(hold: Boolean, subId: Int) {
         fun getCertHash(): String {
             val pkgInfo = realContext.packageManager.getPackageInfo(realContext.opPackageName,
@@ -723,6 +748,19 @@
     @Test
     @IgnoreUpTo(Build.VERSION_CODES.S)
     fun testAllowedUids() {
+        doTestAllowedUids(TRANSPORT_CELLULAR, Process.myUid(), expectUidsPresent = false)
+        doTestAllowedUids(TRANSPORT_WIFI, Process.myUid(), expectUidsPresent = false)
+        doTestAllowedUids(TRANSPORT_BLUETOOTH, Process.myUid(), expectUidsPresent = false)
+
+        // TODO(b/315136340): Allow ownerUid to see allowedUids and add cases that expect uids
+        // present
+    }
+
+    @Test
+    @IgnoreUpTo(Build.VERSION_CODES.S)
+    fun testAllowedUids_WithCarrierServicePackage() {
+        assumeTrue(realContext.packageManager.hasSystemFeature(FEATURE_TELEPHONY_SUBSCRIPTION))
+
         // Use a different package than this one to make sure that a package that doesn't hold
         // carrier service permission can be set as an allowed UID.
         val servicePackage = "android.net.cts.carrierservicepackage"
@@ -735,12 +773,17 @@
 
         val tm = realContext.getSystemService(TelephonyManager::class.java)!!
         val defaultSubId = SubscriptionManager.getDefaultSubscriptionId()
+        assertTrue(defaultSubId != SubscriptionManager.INVALID_SUBSCRIPTION_ID,
+                "getDefaultSubscriptionId returns INVALID_SUBSCRIPTION_ID")
         tryTest {
             // This process is not the carrier service UID, so allowedUids should be ignored in all
             // the following cases.
-            doTestAllowedUids(defaultSubId, TRANSPORT_CELLULAR, uid, expectUidsPresent = false)
-            doTestAllowedUids(defaultSubId, TRANSPORT_WIFI, uid, expectUidsPresent = false)
-            doTestAllowedUids(defaultSubId, TRANSPORT_BLUETOOTH, uid, expectUidsPresent = false)
+            doTestAllowedUidsWithSubId(defaultSubId, TRANSPORT_CELLULAR, uid,
+                    expectUidsPresent = false)
+            doTestAllowedUidsWithSubId(defaultSubId, TRANSPORT_WIFI, uid,
+                    expectUidsPresent = false)
+            doTestAllowedUidsWithSubId(defaultSubId, TRANSPORT_BLUETOOTH, uid,
+                    expectUidsPresent = false)
 
             // The tools to set the carrier service package override do not exist before U,
             // so there is no way to test the rest of this test on < U.
@@ -783,9 +826,10 @@
                 // TODO(b/315136340): Allow ownerUid to see allowedUids and enable below test case
                 // doTestAllowedUids(defaultSubId, TRANSPORT_WIFI, uid, expectUidsPresent = true)
             }
-            doTestAllowedUids(defaultSubId, TRANSPORT_BLUETOOTH, uid, expectUidsPresent = false)
-            doTestAllowedUids(defaultSubId, intArrayOf(TRANSPORT_CELLULAR, TRANSPORT_WIFI), uid,
+            doTestAllowedUidsWithSubId(defaultSubId, TRANSPORT_BLUETOOTH, uid,
                     expectUidsPresent = false)
+            doTestAllowedUidsWithSubId(defaultSubId, intArrayOf(TRANSPORT_CELLULAR, TRANSPORT_WIFI),
+                    uid, expectUidsPresent = false)
         } cleanupStep {
             if (SdkLevel.isAtLeastU()) setCarrierServicePackageOverride(defaultSubId, null)
         } cleanup {
diff --git a/tests/cts/net/src/android/net/cts/NetworkRequestTest.java b/tests/cts/net/src/android/net/cts/NetworkRequestTest.java
index 594f3fb..6ec4e62 100644
--- a/tests/cts/net/src/android/net/cts/NetworkRequestTest.java
+++ b/tests/cts/net/src/android/net/cts/NetworkRequestTest.java
@@ -32,6 +32,8 @@
 
 import static com.android.testutils.DevSdkIgnoreRuleKt.VANILLA_ICE_CREAM;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import static junit.framework.Assert.fail;
 
 import static org.junit.Assert.assertArrayEquals;
@@ -62,6 +64,7 @@
 import com.android.networkstack.apishim.NetworkRequestShimImpl;
 import com.android.networkstack.apishim.common.NetworkRequestShim;
 import com.android.networkstack.apishim.common.UnsupportedApiLevelException;
+import com.android.testutils.ConnectivityModuleTest;
 import com.android.testutils.DevSdkIgnoreRule;
 import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
 
@@ -72,6 +75,7 @@
 import java.util.Set;
 
 @RunWith(AndroidJUnit4.class)
+@ConnectivityModuleTest
 public class NetworkRequestTest {
     @Rule
     public final DevSdkIgnoreRule ignoreRule = new DevSdkIgnoreRule();
@@ -173,6 +177,20 @@
     }
 
     @Test
+    @IgnoreUpTo(Build.VERSION_CODES.S)
+    public void testSubscriptionIds() {
+        int[] subIds = {1, 2};
+        assertTrue(
+                new NetworkRequest.Builder().build()
+                        .getSubscriptionIds().isEmpty());
+        assertThat(new NetworkRequest.Builder()
+                .setSubscriptionIds(Set.of(subIds[0], subIds[1]))
+                .build()
+                .getSubscriptionIds())
+                .containsExactly(subIds[0], subIds[1]);
+    }
+
+    @Test
     @IgnoreUpTo(Build.VERSION_CODES.Q)
     public void testRequestorPackageName() {
         assertNull(new NetworkRequest.Builder().build().getRequestorPackageName());
diff --git a/tests/cts/net/src/android/net/cts/NsdManagerTest.kt b/tests/cts/net/src/android/net/cts/NsdManagerTest.kt
index 1309e79..dbececf 100644
--- a/tests/cts/net/src/android/net/cts/NsdManagerTest.kt
+++ b/tests/cts/net/src/android/net/cts/NsdManagerTest.kt
@@ -53,6 +53,7 @@
 import android.net.cts.NsdServiceInfoCallbackRecord.ServiceInfoCallbackEvent.ServiceUpdatedLost
 import android.net.cts.NsdServiceInfoCallbackRecord.ServiceInfoCallbackEvent.UnregisterCallbackSucceeded
 import android.net.cts.util.CtsNetUtils
+import android.net.nsd.DiscoveryRequest
 import android.net.nsd.NsdManager
 import android.net.nsd.NsdServiceInfo
 import android.net.nsd.OffloadEngine
@@ -77,6 +78,7 @@
 import androidx.test.platform.app.InstrumentationRegistry
 import com.android.compatibility.common.util.PollingCheck
 import com.android.compatibility.common.util.PropertyUtil
+import com.android.compatibility.common.util.SystemUtil
 import com.android.modules.utils.build.SdkLevel.isAtLeastU
 import com.android.net.module.util.DnsPacket
 import com.android.net.module.util.HexDump
@@ -160,7 +162,11 @@
     private val cm by lazy { context.getSystemService(ConnectivityManager::class.java)!! }
     private val serviceName = "NsdTest%09d".format(Random().nextInt(1_000_000_000))
     private val serviceName2 = "NsdTest%09d".format(Random().nextInt(1_000_000_000))
+    private val serviceName3 = "NsdTest%09d".format(Random().nextInt(1_000_000_000))
     private val serviceType = "_nmt%09d._tcp".format(Random().nextInt(1_000_000_000))
+    private val serviceType2 = "_nmt%09d._tcp".format(Random().nextInt(1_000_000_000))
+    private val customHostname = "NsdTestHost%09d".format(Random().nextInt(1_000_000_000))
+    private val customHostname2 = "NsdTestHost%09d".format(Random().nextInt(1_000_000_000))
     private val handlerThread = HandlerThread(NsdManagerTest::class.java.simpleName)
     private val ctsNetUtils by lazy{ CtsNetUtils(context) }
 
@@ -679,6 +685,48 @@
         }
     }
 
+    @Test
+    fun testRegisterService_twoServicesWithSameNameButDifferentTypes_registeredAndDiscoverable() {
+        val si1 = NsdServiceInfo().also {
+            it.network = testNetwork1.network
+            it.serviceName = serviceName
+            it.serviceType = serviceType
+            it.port = TEST_PORT
+        }
+        val si2 = NsdServiceInfo().also {
+            it.network = testNetwork1.network
+            it.serviceName = serviceName
+            it.serviceType = serviceType2
+            it.port = TEST_PORT + 1
+        }
+        val registrationRecord1 = NsdRegistrationRecord()
+        val registrationRecord2 = NsdRegistrationRecord()
+        val discoveryRecord1 = NsdDiscoveryRecord()
+        val discoveryRecord2 = NsdDiscoveryRecord()
+        tryTest {
+            registerService(registrationRecord1, si1)
+            registerService(registrationRecord2, si2)
+
+            nsdManager.discoverServices(serviceType,
+                    NsdManager.PROTOCOL_DNS_SD,
+                    testNetwork1.network, Executor { it.run() }, discoveryRecord1)
+            nsdManager.discoverServices(serviceType2,
+                    NsdManager.PROTOCOL_DNS_SD,
+                    testNetwork1.network, Executor { it.run() }, discoveryRecord2)
+
+            discoveryRecord1.waitForServiceDiscovered(serviceName, serviceType,
+                    testNetwork1.network)
+            discoveryRecord2.waitForServiceDiscovered(serviceName, serviceType2,
+                    testNetwork1.network)
+        } cleanupStep {
+            nsdManager.stopServiceDiscovery(discoveryRecord1)
+            nsdManager.stopServiceDiscovery(discoveryRecord2)
+        } cleanup {
+            nsdManager.unregisterService(registrationRecord1)
+            nsdManager.unregisterService(registrationRecord2)
+        }
+    }
+
     fun checkOffloadServiceInfo(serviceInfo: OffloadServiceInfo, si: NsdServiceInfo) {
         val expectedServiceType = si.serviceType.split(",")[0]
         assertEquals(si.serviceName, serviceInfo.key.serviceName)
@@ -686,7 +734,8 @@
         assertEquals(listOf("_subtype"), serviceInfo.subtypes)
         assertTrue(serviceInfo.hostname.startsWith("Android_"))
         assertTrue(serviceInfo.hostname.endsWith("local"))
-        assertEquals(0, serviceInfo.priority)
+        // Test service types should not be in the priority list
+        assertEquals(Integer.MAX_VALUE, serviceInfo.priority)
         assertEquals(OffloadEngine.OFFLOAD_TYPE_REPLY.toLong(), serviceInfo.offloadType)
         val offloadPayload = serviceInfo.offloadPayload
         assertNotNull(offloadPayload)
@@ -1041,9 +1090,11 @@
             nsdManager.discoverServices("_subtype1.$serviceType",
                     NsdManager.PROTOCOL_DNS_SD,
                     testNetwork1.network, Executor { it.run() }, subtype1DiscoveryRecord)
-            nsdManager.discoverServices("_subtype2.$serviceType",
-                    NsdManager.PROTOCOL_DNS_SD,
-                    testNetwork1.network, Executor { it.run() }, subtype2DiscoveryRecord)
+
+            nsdManager.discoverServices(
+                    DiscoveryRequest.Builder(serviceType).setSubtype("_subtype2")
+                            .setNetwork(testNetwork1.network).build(),
+                    Executor { it.run() }, subtype2DiscoveryRecord)
 
             val info1 = subtype1DiscoveryRecord.waitForServiceDiscovered(
                     serviceName, serviceType, testNetwork1.network)
@@ -1098,6 +1149,73 @@
     }
 
     @Test
+    fun testSubtypeAdvertisingAndDiscovery_nonAlphanumericalSubtypes() {
+        // All non-alphanumerical characters between 0x20 and 0x7e, with a leading underscore
+        val nonAlphanumSubtype = "_ !\"#\$%&'()*+-/:;<=>?@[\\]^_`{|}"
+        // Test both legacy syntax and the subtypes setter, on different networks
+        val si1 = makeTestServiceInfo(network = testNetwork1.network).apply {
+            serviceType = "$serviceType,_test1,$nonAlphanumSubtype"
+        }
+        val si2 = makeTestServiceInfo(network = testNetwork2.network).apply {
+            subtypes = setOf("_test2", nonAlphanumSubtype)
+        }
+
+        val registrationRecord1 = NsdRegistrationRecord()
+        val registrationRecord2 = NsdRegistrationRecord()
+        val subtypeDiscoveryRecord1 = NsdDiscoveryRecord()
+        val subtypeDiscoveryRecord2 = NsdDiscoveryRecord()
+        tryTest {
+            registerService(registrationRecord1, si1)
+            registerService(registrationRecord2, si2)
+            nsdManager.discoverServices(DiscoveryRequest.Builder(serviceType)
+                .setSubtype(nonAlphanumSubtype)
+                .setNetwork(testNetwork1.network)
+                .build(), { it.run() }, subtypeDiscoveryRecord1)
+            nsdManager.discoverServices("$nonAlphanumSubtype.$serviceType",
+                NsdManager.PROTOCOL_DNS_SD, testNetwork2.network, { it.run() },
+                subtypeDiscoveryRecord2)
+
+            val discoveredInfo1 = subtypeDiscoveryRecord1.waitForServiceDiscovered(serviceName,
+                serviceType, testNetwork1.network)
+            val discoveredInfo2 = subtypeDiscoveryRecord2.waitForServiceDiscovered(serviceName,
+                serviceType, testNetwork2.network)
+            assertTrue(discoveredInfo1.subtypes.contains(nonAlphanumSubtype))
+            assertTrue(discoveredInfo2.subtypes.contains(nonAlphanumSubtype))
+        } cleanupStep {
+            nsdManager.stopServiceDiscovery(subtypeDiscoveryRecord1)
+            subtypeDiscoveryRecord1.expectCallback<DiscoveryStopped>()
+        } cleanupStep {
+            nsdManager.stopServiceDiscovery(subtypeDiscoveryRecord2)
+            subtypeDiscoveryRecord2.expectCallback<DiscoveryStopped>()
+        } cleanup {
+            nsdManager.unregisterService(registrationRecord1)
+            nsdManager.unregisterService(registrationRecord2)
+        }
+    }
+
+    @Test
+    fun testSubtypeDiscovery_typeMatchButSubtypeNotMatch_notDiscovered() {
+        val si1 = makeTestServiceInfo(network = testNetwork1.network).apply {
+            serviceType += ",_subtype1"
+        }
+        val registrationRecord = NsdRegistrationRecord()
+        val subtype2DiscoveryRecord = NsdDiscoveryRecord()
+        tryTest {
+            registerService(registrationRecord, si1)
+            val request = DiscoveryRequest.Builder(serviceType)
+                    .setSubtype("_subtype2").setNetwork(testNetwork1.network).build()
+            nsdManager.discoverServices(request, { it.run() }, subtype2DiscoveryRecord)
+            subtype2DiscoveryRecord.expectCallback<DiscoveryStarted>()
+            subtype2DiscoveryRecord.assertNoCallback(timeoutMs = 2000)
+        } cleanupStep {
+            nsdManager.stopServiceDiscovery(subtype2DiscoveryRecord)
+            subtype2DiscoveryRecord.expectCallback<DiscoveryStopped>()
+        } cleanup {
+            nsdManager.unregisterService(registrationRecord)
+        }
+    }
+
+    @Test
     fun testSubtypeAdvertising_tooManySubtypes_returnsFailureBadParameters() {
         val si = makeTestServiceInfo(network = testNetwork1.network)
         // Sets 101 subtypes in total
@@ -1162,6 +1280,83 @@
     }
 
     @Test
+    fun testRegisterServiceWithCustomHostAndAddresses_conflictDuringProbing_hostRenamed() {
+        val si = makeTestServiceInfo(testNetwork1.network).apply {
+            hostname = customHostname
+            hostAddresses = listOf(
+                    parseNumericAddress("192.0.2.24"),
+                    parseNumericAddress("2001:db8::3"))
+        }
+
+        val packetReader = TapPacketReader(Handler(handlerThread.looper),
+                testNetwork1.iface.fileDescriptor.fileDescriptor, 1500 /* maxPacketSize */)
+        packetReader.startAsyncForTest()
+        handlerThread.waitForIdle(TIMEOUT_MS)
+
+        // Register service on testNetwork1
+        val registrationRecord = NsdRegistrationRecord()
+        nsdManager.registerService(si, NsdManager.PROTOCOL_DNS_SD, { it.run() },
+                registrationRecord)
+
+        tryTest {
+            assertNotNull(packetReader.pollForProbe(serviceName, serviceType),
+                    "Did not find a probe for the service")
+            packetReader.sendResponse(buildConflictingAnnouncementForCustomHost())
+
+            // Registration must use an updated hostname to avoid the conflict
+            val cb = registrationRecord.expectCallback<ServiceRegistered>(REGISTRATION_TIMEOUT_MS)
+            // Service name is not renamed because there's no conflict on the service name.
+            assertEquals(serviceName, cb.serviceInfo.serviceName)
+            val hostname = cb.serviceInfo.hostname ?: fail("Missing hostname")
+            hostname.let {
+                assertTrue("Unexpected registered hostname: $it",
+                        it.startsWith(customHostname) && it != customHostname)
+            }
+        } cleanupStep {
+            nsdManager.unregisterService(registrationRecord)
+            registrationRecord.expectCallback<ServiceUnregistered>()
+        } cleanup {
+            packetReader.handler.post { packetReader.stop() }
+            handlerThread.waitForIdle(TIMEOUT_MS)
+        }
+    }
+
+    @Test
+    fun testRegisterServiceWithCustomHostNoAddresses_noConflictDuringProbing_notRenamed() {
+        val si = makeTestServiceInfo(testNetwork1.network).apply {
+            hostname = customHostname
+        }
+
+        val packetReader = TapPacketReader(Handler(handlerThread.looper),
+                testNetwork1.iface.fileDescriptor.fileDescriptor, 1500 /* maxPacketSize */)
+        packetReader.startAsyncForTest()
+        handlerThread.waitForIdle(TIMEOUT_MS)
+
+        // Register service on testNetwork1
+        val registrationRecord = NsdRegistrationRecord()
+        nsdManager.registerService(si, NsdManager.PROTOCOL_DNS_SD, { it.run() },
+                registrationRecord)
+
+        tryTest {
+            assertNotNull(packetReader.pollForProbe(serviceName, serviceType),
+                    "Did not find a probe for the service")
+            // Not a conflict because no record is registered for the hostname
+            packetReader.sendResponse(buildConflictingAnnouncementForCustomHost())
+
+            // Registration is not renamed because there's no conflict
+            val cb = registrationRecord.expectCallback<ServiceRegistered>(REGISTRATION_TIMEOUT_MS)
+            assertEquals(serviceName, cb.serviceInfo.serviceName)
+            assertEquals(customHostname, cb.serviceInfo.hostname)
+        } cleanupStep {
+            nsdManager.unregisterService(registrationRecord)
+            registrationRecord.expectCallback<ServiceUnregistered>()
+        } cleanup {
+            packetReader.handler.post { packetReader.stop() }
+            handlerThread.waitForIdle(TIMEOUT_MS)
+        }
+    }
+
+    @Test
     fun testRegisterWithConflictAfterProbing() {
         // This test requires shims supporting T+ APIs (NsdServiceInfo.network)
         assumeTrue(TestUtils.shouldTestTApis())
@@ -1236,6 +1431,121 @@
         }
     }
 
+    @Test
+    fun testRegisterServiceWithCustomHostAndAddresses_conflictAfterProbing_hostRenamed() {
+        val si = makeTestServiceInfo(testNetwork1.network).apply {
+            hostname = customHostname
+            hostAddresses = listOf(
+                    parseNumericAddress("192.0.2.24"),
+                    parseNumericAddress("2001:db8::3"))
+        }
+
+        // Register service on testNetwork1
+        val registrationRecord = NsdRegistrationRecord()
+        val discoveryRecord = NsdDiscoveryRecord()
+        val registeredService = registerService(registrationRecord, si)
+        val packetReader = TapPacketReader(
+                Handler(handlerThread.looper),
+                testNetwork1.iface.fileDescriptor.fileDescriptor, 1500 /* maxPacketSize */)
+        packetReader.startAsyncForTest()
+        handlerThread.waitForIdle(TIMEOUT_MS)
+
+        tryTest {
+            repeat(3) {
+                assertNotNull(packetReader.pollForAdvertisement(serviceName, serviceType),
+                        "Expect 3 announcements sent after initial probing")
+            }
+
+            assertEquals(si.serviceName, registeredService.serviceName)
+            assertEquals(si.hostname, registeredService.hostname)
+
+            nsdManager.discoverServices(serviceType, NsdManager.PROTOCOL_DNS_SD,
+                    testNetwork1.network, { it.run() }, discoveryRecord)
+            val discoveredInfo = discoveryRecord.waitForServiceDiscovered(
+                    si.serviceName, serviceType)
+
+            // Send a conflicting announcement
+            val conflictingAnnouncement = buildConflictingAnnouncementForCustomHost()
+            packetReader.sendResponse(conflictingAnnouncement)
+
+            // Expect to see probes (RFC6762 9., service is reset to probing state)
+            assertNotNull(packetReader.pollForProbe(serviceName, serviceType),
+                    "Probe not received within timeout after conflict")
+
+            // Send the conflicting packet again to reply to the probe
+            packetReader.sendResponse(conflictingAnnouncement)
+
+            val newRegistration =
+                    registrationRecord
+                            .expectCallbackEventually<ServiceRegistered>(REGISTRATION_TIMEOUT_MS) {
+                                it.serviceInfo.serviceName == serviceName
+                                        && it.serviceInfo.hostname.let { hostname ->
+                                    hostname != null
+                                            && hostname.startsWith(customHostname)
+                                            && hostname != customHostname
+                                }
+                            }
+
+            val resolvedInfo = resolveService(discoveredInfo)
+            assertEquals(newRegistration.serviceInfo.serviceName, resolvedInfo.serviceName)
+            assertEquals(newRegistration.serviceInfo.hostname, resolvedInfo.hostname)
+
+            discoveryRecord.assertNoCallback()
+        } cleanupStep {
+            nsdManager.stopServiceDiscovery(discoveryRecord)
+            discoveryRecord.expectCallback<DiscoveryStopped>()
+        } cleanupStep {
+            nsdManager.unregisterService(registrationRecord)
+            registrationRecord.expectCallback<ServiceUnregistered>()
+        } cleanup {
+            packetReader.handler.post { packetReader.stop() }
+            handlerThread.waitForIdle(TIMEOUT_MS)
+        }
+    }
+
+    @Test
+    fun testRegisterServiceWithCustomHostNoAddresses_noConflictAfterProbing_notRenamed() {
+        val si = makeTestServiceInfo(testNetwork1.network).apply {
+            hostname = customHostname
+        }
+
+        // Register service on testNetwork1
+        val registrationRecord = NsdRegistrationRecord()
+        val discoveryRecord = NsdDiscoveryRecord()
+        val registeredService = registerService(registrationRecord, si)
+        val packetReader = TapPacketReader(Handler(handlerThread.looper),
+                testNetwork1.iface.fileDescriptor.fileDescriptor, 1500 /* maxPacketSize */)
+        packetReader.startAsyncForTest()
+        handlerThread.waitForIdle(TIMEOUT_MS)
+
+        tryTest {
+            assertNotNull(packetReader.pollForAdvertisement(serviceName, serviceType),
+                    "No announcements sent after initial probing")
+
+            assertEquals(si.serviceName, registeredService.serviceName)
+            assertEquals(si.hostname, registeredService.hostname)
+
+            // Send a conflicting announcement
+            val conflictingAnnouncement = buildConflictingAnnouncementForCustomHost()
+            packetReader.sendResponse(conflictingAnnouncement)
+
+            nsdManager.discoverServices(serviceType, NsdManager.PROTOCOL_DNS_SD,
+                    testNetwork1.network, { it.run() }, discoveryRecord)
+
+            // The service is not renamed
+            discoveryRecord.waitForServiceDiscovered(si.serviceName, serviceType)
+        } cleanupStep {
+            nsdManager.stopServiceDiscovery(discoveryRecord)
+            discoveryRecord.expectCallback<DiscoveryStopped>()
+        } cleanupStep {
+            nsdManager.unregisterService(registrationRecord)
+            registrationRecord.expectCallback<ServiceUnregistered>()
+        } cleanup {
+            packetReader.handler.post { packetReader.stop() }
+            handlerThread.waitForIdle(TIMEOUT_MS)
+        }
+    }
+
     // Test that even if only a PTR record is received as a reply when discovering, without the
     // SRV, TXT, address records as recommended (but not mandated) by RFC 6763 12, the service can
     // still be discovered.
@@ -1407,6 +1717,177 @@
         }
     }
 
+    @Test
+    fun testReplyWhenKnownAnswerSuppressionFlagSet() {
+        // The flag may be removed in the future but known-answer suppression should be enabled by
+        // default in that case. The rule will reset flags automatically on teardown.
+        deviceConfigRule.setConfig(NAMESPACE_TETHERING, "test_nsd_known_answer_suppression", "1")
+        deviceConfigRule.setConfig(NAMESPACE_TETHERING, "test_nsd_unicast_reply_enabled", "1")
+
+        val si = makeTestServiceInfo(testNetwork1.network)
+
+        // Register service on testNetwork1
+        val registrationRecord = NsdRegistrationRecord()
+        var nsResponder: NSResponder? = null
+        tryTest {
+            registerService(registrationRecord, si)
+            val packetReader = TapPacketReader(Handler(handlerThread.looper),
+                    testNetwork1.iface.fileDescriptor.fileDescriptor, 1500 /* maxPacketSize */)
+            packetReader.startAsyncForTest()
+
+            handlerThread.waitForIdle(TIMEOUT_MS)
+            /*
+            Send a query with a known answer. Expect to receive a response containing TXT record
+            only.
+            Generated with:
+            scapy.raw(scapy.DNS(rd=0, qr=0, aa=0, qd =
+                    scapy.DNSQR(qname='_nmt123456789._tcp.local', qtype='PTR',
+                            qclass=0x8001) /
+                    scapy.DNSQR(qname='NsdTest123456789._nmt123456789._tcp.local', qtype='TXT',
+                            qclass=0x8001),
+                    an = scapy.DNSRR(rrname='_nmt123456789._tcp.local', type='PTR', ttl=4500,
+                            rdata='NsdTest123456789._nmt123456789._tcp.local')
+            )).hex()
+            */
+            val query = HexDump.hexStringToByteArray("0000000000020001000000000d5f6e6d74313233343" +
+                    "536373839045f746370056c6f63616c00000c8001104e7364546573743132333435363738390" +
+                    "d5f6e6d74313233343536373839045f746370056c6f63616c00001080010d5f6e6d743132333" +
+                    "43536373839045f746370056c6f63616c00000c000100001194002b104e73645465737431323" +
+                    "33435363738390d5f6e6d74313233343536373839045f746370056c6f63616c00")
+            replaceServiceNameAndTypeWithTestSuffix(query)
+
+            val testSrcAddr = makeLinkLocalAddressOfOtherDeviceOnPrefix(testNetwork1.network)
+            nsResponder = NSResponder(packetReader, mapOf(
+                    testSrcAddr to MacAddress.fromString("01:02:03:04:05:06")
+            )).apply { start() }
+
+            packetReader.sendResponse(buildMdnsPacket(query, testSrcAddr))
+            // The reply is sent unicast to the source address. There may be announcements sent
+            // multicast around this time, so filter by destination address.
+            val reply = packetReader.pollForMdnsPacket { pkt ->
+                pkt.isReplyFor("$serviceName.$serviceType.local", DnsResolver.TYPE_TXT) &&
+                        !pkt.isReplyFor("$serviceType.local", DnsResolver.TYPE_PTR) &&
+                        pkt.dstAddr == testSrcAddr
+            }
+            assertNotNull(reply)
+
+            /*
+            Send a query with a known answer (TTL is less than half). Expect to receive a response
+            containing both PTR and TXT records.
+            Generated with:
+            scapy.raw(scapy.DNS(rd=0, qr=0, aa=0, qd =
+                    scapy.DNSQR(qname='_nmt123456789._tcp.local', qtype='PTR',
+                            qclass=0x8001) /
+                    scapy.DNSQR(qname='NsdTest123456789._nmt123456789._tcp.local', qtype='TXT',
+                            qclass=0x8001),
+                    an = scapy.DNSRR(rrname='_nmt123456789._tcp.local', type='PTR', ttl=2150,
+                            rdata='NsdTest123456789._nmt123456789._tcp.local')
+            )).hex()
+            */
+            val query2 = HexDump.hexStringToByteArray("0000000000020001000000000d5f6e6d7431323334" +
+                    "3536373839045f746370056c6f63616c00000c8001104e736454657374313233343536373839" +
+                    "0d5f6e6d74313233343536373839045f746370056c6f63616c00001080010d5f6e6d74313233" +
+                    "343536373839045f746370056c6f63616c00000c000100000866002b104e7364546573743132" +
+                    "333435363738390d5f6e6d74313233343536373839045f746370056c6f63616c00")
+            replaceServiceNameAndTypeWithTestSuffix(query2)
+
+            packetReader.sendResponse(buildMdnsPacket(query2, testSrcAddr))
+            // The reply is sent unicast to the source address. There may be announcements sent
+            // multicast around this time, so filter by destination address.
+            val reply2 = packetReader.pollForMdnsPacket { pkt ->
+                pkt.isReplyFor("$serviceName.$serviceType.local", DnsResolver.TYPE_TXT) &&
+                        pkt.isReplyFor("$serviceType.local", DnsResolver.TYPE_PTR) &&
+                        pkt.dstAddr == testSrcAddr
+            }
+            assertNotNull(reply2)
+        } cleanup {
+            nsResponder?.stop()
+            nsdManager.unregisterService(registrationRecord)
+            registrationRecord.expectCallback<ServiceUnregistered>()
+        }
+    }
+
+    @Test
+    fun testReplyWithMultipacketWhenKnownAnswerSuppressionFlagSet() {
+        // The flag may be removed in the future but known-answer suppression should be enabled by
+        // default in that case. The rule will reset flags automatically on teardown.
+        deviceConfigRule.setConfig(NAMESPACE_TETHERING, "test_nsd_known_answer_suppression", "1")
+        deviceConfigRule.setConfig(NAMESPACE_TETHERING, "test_nsd_unicast_reply_enabled", "1")
+
+        val si = makeTestServiceInfo(testNetwork1.network)
+
+        // Register service on testNetwork1
+        val registrationRecord = NsdRegistrationRecord()
+        var nsResponder: NSResponder? = null
+        tryTest {
+            registerService(registrationRecord, si)
+            val packetReader = TapPacketReader(Handler(handlerThread.looper),
+                    testNetwork1.iface.fileDescriptor.fileDescriptor, 1500 /* maxPacketSize */)
+            packetReader.startAsyncForTest()
+
+            handlerThread.waitForIdle(TIMEOUT_MS)
+            /*
+            Send a query with truncated bit set.
+            Generated with:
+            scapy.raw(scapy.DNS(rd=0, qr=0, aa=0, tc=1, qd=
+                    scapy.DNSQR(qname='_nmt123456789._tcp.local', qtype='PTR',
+                            qclass=0x8001) /
+                    scapy.DNSQR(qname='NsdTest123456789._nmt123456789._tcp.local', qtype='TXT',
+                            qclass=0x8001)
+            )).hex()
+            */
+            val query = HexDump.hexStringToByteArray("0000020000020000000000000d5f6e6d74313233343" +
+                    "536373839045f746370056c6f63616c00000c8001104e7364546573743132333435363738390" +
+                    "d5f6e6d74313233343536373839045f746370056c6f63616c0000108001")
+            replaceServiceNameAndTypeWithTestSuffix(query)
+            /*
+            Send a known answer packet (other service) with truncated bit set.
+            Generated with:
+            scapy.raw(scapy.DNS(rd=0, qr=0, aa=0, tc=1, qd=None,
+                    an = scapy.DNSRR(rrname='_test._tcp.local', type='PTR', ttl=4500,
+                            rdata='NsdTest._test._tcp.local')
+            )).hex()
+            */
+            val knownAnswer1 = HexDump.hexStringToByteArray("000002000000000100000000055f74657374" +
+                    "045f746370056c6f63616c00000c000100001194001a074e736454657374055f74657374045f" +
+                    "746370056c6f63616c00")
+            replaceServiceNameAndTypeWithTestSuffix(knownAnswer1)
+            /*
+            Send a known answer packet.
+            Generated with:
+            scapy.raw(scapy.DNS(rd=0, qr=0, aa=0, qd=None,
+                    an = scapy.DNSRR(rrname='_nmt123456789._tcp.local', type='PTR', ttl=4500,
+                            rdata='NsdTest123456789._nmt123456789._tcp.local')
+            )).hex()
+            */
+            val knownAnswer2 = HexDump.hexStringToByteArray("0000000000000001000000000d5f6e6d7431" +
+                    "3233343536373839045f746370056c6f63616c00000c000100001194002b104e736454657374" +
+                    "3132333435363738390d5f6e6d74313233343536373839045f746370056c6f63616c00")
+            replaceServiceNameAndTypeWithTestSuffix(knownAnswer2)
+
+            val testSrcAddr = makeLinkLocalAddressOfOtherDeviceOnPrefix(testNetwork1.network)
+            nsResponder = NSResponder(packetReader, mapOf(
+                    testSrcAddr to MacAddress.fromString("01:02:03:04:05:06")
+            )).apply { start() }
+
+            packetReader.sendResponse(buildMdnsPacket(query, testSrcAddr))
+            packetReader.sendResponse(buildMdnsPacket(knownAnswer1, testSrcAddr))
+            packetReader.sendResponse(buildMdnsPacket(knownAnswer2, testSrcAddr))
+            // The reply is sent unicast to the source address. There may be announcements sent
+            // multicast around this time, so filter by destination address.
+            val reply = packetReader.pollForMdnsPacket { pkt ->
+                pkt.isReplyFor("$serviceName.$serviceType.local", DnsResolver.TYPE_TXT) &&
+                        !pkt.isReplyFor("$serviceType.local", DnsResolver.TYPE_PTR) &&
+                        pkt.dstAddr == testSrcAddr
+            }
+            assertNotNull(reply)
+        } cleanup {
+            nsResponder?.stop()
+            nsdManager.unregisterService(registrationRecord)
+            registrationRecord.expectCallback<ServiceUnregistered>()
+        }
+    }
+
     private fun makeLinkLocalAddressOfOtherDeviceOnPrefix(network: Network): Inet6Address {
         val lp = cm.getLinkProperties(network) ?: fail("No LinkProperties for net $network")
         // Expect to have a /64 link-local address
@@ -1420,6 +1901,295 @@
         return Inet6Address.getByAddress(addrBytes) as Inet6Address
     }
 
+    @Test
+    fun testAdvertisingAndDiscovery_servicesWithCustomHost_customHostAddressesFound() {
+        val hostAddresses1 = listOf(
+                parseNumericAddress("192.0.2.23"),
+                parseNumericAddress("2001:db8::1"),
+                parseNumericAddress("2001:db8::2"))
+        val hostAddresses2 = listOf(
+                parseNumericAddress("192.0.2.24"),
+                parseNumericAddress("2001:db8::3"))
+        val si1 = NsdServiceInfo().also {
+            it.network = testNetwork1.network
+            it.serviceName = serviceName
+            it.serviceType = serviceType
+            it.port = TEST_PORT
+            it.hostname = customHostname
+            it.hostAddresses = hostAddresses1
+        }
+        val si2 = NsdServiceInfo().also {
+            it.network = testNetwork1.network
+            it.serviceName = serviceName2
+            it.serviceType = serviceType
+            it.port = TEST_PORT + 1
+            it.hostname = customHostname2
+            it.hostAddresses = hostAddresses2
+        }
+        val registrationRecord1 = NsdRegistrationRecord()
+        val registrationRecord2 = NsdRegistrationRecord()
+
+        val discoveryRecord1 = NsdDiscoveryRecord()
+        val discoveryRecord2 = NsdDiscoveryRecord()
+        tryTest {
+            registerService(registrationRecord1, si1)
+
+            nsdManager.discoverServices(serviceType, NsdManager.PROTOCOL_DNS_SD,
+                    testNetwork1.network, Executor { it.run() }, discoveryRecord1)
+
+            val discoveredInfo = discoveryRecord1.waitForServiceDiscovered(
+                    serviceName, serviceType, testNetwork1.network)
+            val resolvedInfo = resolveService(discoveredInfo)
+
+            assertEquals(TEST_PORT, resolvedInfo.port)
+            assertEquals(si1.hostname, resolvedInfo.hostname)
+            assertAddressEquals(hostAddresses1, resolvedInfo.hostAddresses)
+
+            registerService(registrationRecord2, si2)
+            nsdManager.discoverServices(serviceType, NsdManager.PROTOCOL_DNS_SD,
+                    testNetwork1.network, Executor { it.run() }, discoveryRecord2)
+
+            val discoveredInfo2 = discoveryRecord2.waitForServiceDiscovered(
+                    serviceName2, serviceType, testNetwork1.network)
+            val resolvedInfo2 = resolveService(discoveredInfo2)
+
+            assertEquals(TEST_PORT + 1, resolvedInfo2.port)
+            assertEquals(si2.hostname, resolvedInfo2.hostname)
+            assertAddressEquals(hostAddresses2, resolvedInfo2.hostAddresses)
+        } cleanupStep {
+            nsdManager.stopServiceDiscovery(discoveryRecord1)
+            nsdManager.stopServiceDiscovery(discoveryRecord2)
+
+            discoveryRecord1.expectCallbackEventually<DiscoveryStopped>()
+            discoveryRecord2.expectCallbackEventually<DiscoveryStopped>()
+        } cleanup {
+            nsdManager.unregisterService(registrationRecord1)
+            nsdManager.unregisterService(registrationRecord2)
+        }
+    }
+
+    @Test
+    fun testAdvertisingAndDiscovery_multipleRegistrationsForSameCustomHost_unionOfAddressesFound() {
+        val hostAddresses1 = listOf(
+                parseNumericAddress("192.0.2.23"),
+                parseNumericAddress("2001:db8::1"),
+                parseNumericAddress("2001:db8::2"))
+        val hostAddresses2 = listOf(
+                parseNumericAddress("192.0.2.24"),
+                parseNumericAddress("2001:db8::3"))
+        val hostAddresses3 = listOf(
+                parseNumericAddress("2001:db8::3"),
+                parseNumericAddress("2001:db8::5"))
+        val si1 = NsdServiceInfo().also {
+            it.network = testNetwork1.network
+            it.hostname = customHostname
+            it.hostAddresses = hostAddresses1
+        }
+        val si2 = NsdServiceInfo().also {
+            it.network = testNetwork1.network
+            it.serviceName = serviceName
+            it.serviceType = serviceType
+            it.port = TEST_PORT
+            it.hostname = customHostname
+            it.hostAddresses = hostAddresses2
+        }
+        val si3 = NsdServiceInfo().also {
+            it.network = testNetwork1.network
+            it.serviceName = serviceName3
+            it.serviceType = serviceType
+            it.port = TEST_PORT + 1
+            it.hostname = customHostname
+            it.hostAddresses = hostAddresses3
+        }
+
+        val registrationRecord1 = NsdRegistrationRecord()
+        val registrationRecord2 = NsdRegistrationRecord()
+        val registrationRecord3 = NsdRegistrationRecord()
+
+        val discoveryRecord = NsdDiscoveryRecord()
+        tryTest {
+            registerService(registrationRecord1, si1)
+            registerService(registrationRecord2, si2)
+
+            nsdManager.discoverServices(serviceType, NsdManager.PROTOCOL_DNS_SD,
+                    testNetwork1.network, Executor { it.run() }, discoveryRecord)
+
+            val discoveredInfo1 = discoveryRecord.waitForServiceDiscovered(
+                    serviceName, serviceType, testNetwork1.network)
+            val resolvedInfo1 = resolveService(discoveredInfo1)
+
+            assertEquals(TEST_PORT, resolvedInfo1.port)
+            assertEquals(si1.hostname, resolvedInfo1.hostname)
+            assertAddressEquals(
+                    hostAddresses1 + hostAddresses2,
+                    resolvedInfo1.hostAddresses)
+
+            registerService(registrationRecord3, si3)
+
+            val discoveredInfo2 = discoveryRecord.waitForServiceDiscovered(
+                    serviceName3, serviceType, testNetwork1.network)
+            val resolvedInfo2 = resolveService(discoveredInfo2)
+
+            assertEquals(TEST_PORT + 1, resolvedInfo2.port)
+            assertEquals(si2.hostname, resolvedInfo2.hostname)
+            assertAddressEquals(
+                    hostAddresses1 + hostAddresses2 + hostAddresses3,
+                    resolvedInfo2.hostAddresses)
+        } cleanupStep {
+            nsdManager.stopServiceDiscovery(discoveryRecord)
+
+            discoveryRecord.expectCallbackEventually<DiscoveryStopped>()
+        } cleanup {
+            nsdManager.unregisterService(registrationRecord1)
+            nsdManager.unregisterService(registrationRecord2)
+            nsdManager.unregisterService(registrationRecord3)
+        }
+    }
+
+    @Test
+    fun testAdvertisingAndDiscovery_servicesWithTheSameCustomHostAddressOmitted_addressesFound() {
+        val hostAddresses = listOf(
+                parseNumericAddress("192.0.2.23"),
+                parseNumericAddress("2001:db8::1"),
+                parseNumericAddress("2001:db8::2"))
+        val si1 = NsdServiceInfo().also {
+            it.network = testNetwork1.network
+            it.serviceType = serviceType
+            it.serviceName = serviceName
+            it.port = TEST_PORT
+            it.hostname = customHostname
+            it.hostAddresses = hostAddresses
+        }
+        val si2 = NsdServiceInfo().also {
+            it.network = testNetwork1.network
+            it.serviceType = serviceType
+            it.serviceName = serviceName2
+            it.port = TEST_PORT + 1
+            it.hostname = customHostname
+        }
+
+        val registrationRecord1 = NsdRegistrationRecord()
+        val registrationRecord2 = NsdRegistrationRecord()
+
+        val discoveryRecord = NsdDiscoveryRecord()
+        tryTest {
+            registerService(registrationRecord1, si1)
+
+            nsdManager.discoverServices(serviceType, NsdManager.PROTOCOL_DNS_SD,
+                    testNetwork1.network, Executor { it.run() }, discoveryRecord)
+
+            val discoveredInfo1 = discoveryRecord.waitForServiceDiscovered(
+                    serviceName, serviceType, testNetwork1.network)
+            val resolvedInfo1 = resolveService(discoveredInfo1)
+
+            assertEquals(serviceName, discoveredInfo1.serviceName)
+            assertEquals(TEST_PORT, resolvedInfo1.port)
+            assertEquals(si1.hostname, resolvedInfo1.hostname)
+            assertAddressEquals(hostAddresses, resolvedInfo1.hostAddresses)
+
+            registerService(registrationRecord2, si2)
+
+            val discoveredInfo2 = discoveryRecord.waitForServiceDiscovered(
+                    serviceName2, serviceType, testNetwork1.network)
+            val resolvedInfo2 = resolveService(discoveredInfo2)
+
+            assertEquals(serviceName2, discoveredInfo2.serviceName)
+            assertEquals(TEST_PORT + 1, resolvedInfo2.port)
+            assertEquals(si2.hostname, resolvedInfo2.hostname)
+            assertAddressEquals(hostAddresses, resolvedInfo2.hostAddresses)
+        } cleanupStep {
+            nsdManager.stopServiceDiscovery(discoveryRecord)
+
+            discoveryRecord.expectCallback<DiscoveryStopped>()
+        } cleanup {
+            nsdManager.unregisterService(registrationRecord1)
+            nsdManager.unregisterService(registrationRecord2)
+        }
+    }
+
+    @Test
+    fun testServiceTypeClientRemovedAfterSocketDestroyed() {
+        val si = makeTestServiceInfo(testNetwork1.network)
+        // Register service on testNetwork1
+        val registrationRecord = NsdRegistrationRecord()
+        registerService(registrationRecord, si)
+        // Register multiple discovery requests.
+        val discoveryRecord1 = NsdDiscoveryRecord()
+        val discoveryRecord2 = NsdDiscoveryRecord()
+        val discoveryRecord3 = NsdDiscoveryRecord()
+        nsdManager.discoverServices("_test1._tcp", NsdManager.PROTOCOL_DNS_SD,
+                testNetwork1.network, { it.run() }, discoveryRecord1)
+        nsdManager.discoverServices("_test2._tcp", NsdManager.PROTOCOL_DNS_SD,
+                testNetwork1.network, { it.run() }, discoveryRecord2)
+        nsdManager.discoverServices(serviceType, NsdManager.PROTOCOL_DNS_SD, discoveryRecord3)
+
+        tryTest {
+            discoveryRecord1.expectCallback<DiscoveryStarted>()
+            discoveryRecord2.expectCallback<DiscoveryStarted>()
+            discoveryRecord3.expectCallback<DiscoveryStarted>()
+            val foundInfo = discoveryRecord3.waitForServiceDiscovered(
+                    serviceName, serviceType, testNetwork1.network)
+            assertEquals(testNetwork1.network, foundInfo.network)
+            // Verify that associated ServiceTypeClients has been created for testNetwork1.
+            assertTrue("No serviceTypeClients for testNetwork1.",
+                    hasServiceTypeClientsForNetwork(
+                            getServiceTypeClients(), testNetwork1.network))
+
+            // Disconnect testNetwork1
+            runAsShell(MANAGE_TEST_NETWORKS) {
+                testNetwork1.close(cm)
+            }
+
+            // Verify that no ServiceTypeClients for testNetwork1.
+            discoveryRecord3.expectCallback<ServiceLost>()
+            assertFalse("Still has serviceTypeClients for testNetwork1.",
+                    hasServiceTypeClientsForNetwork(
+                            getServiceTypeClients(), testNetwork1.network))
+        } cleanupStep {
+            nsdManager.stopServiceDiscovery(discoveryRecord1)
+            nsdManager.stopServiceDiscovery(discoveryRecord2)
+            nsdManager.stopServiceDiscovery(discoveryRecord3)
+            discoveryRecord1.expectCallback<DiscoveryStopped>()
+            discoveryRecord2.expectCallback<DiscoveryStopped>()
+            discoveryRecord3.expectCallback<DiscoveryStopped>()
+        } cleanup {
+            nsdManager.unregisterService(registrationRecord)
+            registrationRecord.expectCallback<ServiceUnregistered>()
+        }
+    }
+
+    private fun hasServiceTypeClientsForNetwork(clients: List<String>, network: Network): Boolean {
+        for (client in clients) {
+            val netid = client.substring(
+                    client.indexOf("network=") + "network=".length,
+                    client.indexOf("interfaceIndex=") - 1)
+            if (netid == network.toString()) {
+                return true
+            }
+        }
+        return false
+    }
+
+    /**
+     * Get ServiceTypeClient logs from the system dump servicediscovery section.
+     *
+     * The sample output:
+     *     ServiceTypeClient: Type{_nmt079019787._tcp.local} \
+     *         SocketKey{ network=116 interfaceIndex=68 } with 1 listeners.
+     *     ServiceTypeClient: Type{_nmt079019787._tcp.local} \
+     *         SocketKey{ network=115 interfaceIndex=67 } with 1 listeners.
+     */
+    private fun getServiceTypeClients(): List<String> {
+        return SystemUtil.runShellCommand(
+                InstrumentationRegistry.getInstrumentation(), "dumpsys servicediscovery")
+                .split("\n").mapNotNull { line ->
+                    line.indexOf("ServiceTypeClient:").let { idx ->
+                        if (idx == -1) null
+                        else line.substring(idx)
+                    }
+                }
+    }
+
     private fun buildConflictingAnnouncement(): ByteBuffer {
         /*
         Generated with:
@@ -1436,6 +2206,22 @@
         return buildMdnsPacket(mdnsPayload)
     }
 
+    private fun buildConflictingAnnouncementForCustomHost(): ByteBuffer {
+        /*
+        Generated with scapy:
+        raw(DNS(rd=0, qr=1, aa=1, qd = None, an =
+            DNSRR(rrname='NsdTestHost123456789.local', type=28, rclass=1, ttl=120,
+                    rdata='2001:db8::321')
+        )).hex()
+         */
+        val mdnsPayload = HexDump.hexStringToByteArray("000084000000000100000000144e7364" +
+                "54657374486f7374313233343536373839056c6f63616c00001c000100000078001020010db80000" +
+                "00000000000000000321")
+        replaceCustomHostnameWithTestSuffix(mdnsPayload)
+
+        return buildMdnsPacket(mdnsPayload)
+    }
+
     /**
      * Replaces occurrences of "NsdTest123456789" and "_nmt123456789" in mDNS payload with the
      * actual random name and type that are used by the test.
@@ -1452,6 +2238,19 @@
         replaceAll(packetBuffer, testPacketTypePrefix, encodedTypePrefix)
     }
 
+    /**
+     * Replaces occurrences of "NsdTestHost123456789" in mDNS payload with the
+     * actual random host name that are used by the test.
+     */
+    private fun replaceCustomHostnameWithTestSuffix(mdnsPayload: ByteArray) {
+        // Test custom hostnames have consistent length and are always ASCII
+        val testPacketName = "NsdTestHost123456789".encodeToByteArray()
+        val encodedHostname = customHostname.encodeToByteArray()
+
+        val packetBuffer = ByteBuffer.wrap(mdnsPayload)
+        replaceAll(packetBuffer, testPacketName, encodedHostname)
+    }
+
     private tailrec fun replaceAll(buffer: ByteBuffer, source: ByteArray, replacement: ByteArray) {
         assertEquals(source.size, replacement.size)
         val index = buffer.array().indexOf(source)
@@ -1550,3 +2349,9 @@
     if (this == null) return ""
     return String(this, StandardCharsets.UTF_8)
 }
+
+private fun assertAddressEquals(expected: List<InetAddress>, actual: List<InetAddress>) {
+    // No duplicate addresses in the actual address list
+    assertEquals(actual.toSet().size, actual.size)
+    assertEquals(expected.toSet(), actual.toSet())
+}
diff --git a/tests/cts/net/src/android/net/cts/OffloadServiceInfoTest.kt b/tests/cts/net/src/android/net/cts/OffloadServiceInfoTest.kt
new file mode 100644
index 0000000..36de4f2
--- /dev/null
+++ b/tests/cts/net/src/android/net/cts/OffloadServiceInfoTest.kt
@@ -0,0 +1,55 @@
+/*
+ * 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.
+ */
+
+package android.net.cts
+
+import android.net.nsd.OffloadEngine.OFFLOAD_TYPE_FILTER_QUERIES
+import android.net.nsd.OffloadServiceInfo
+import android.os.Build
+import androidx.test.filters.SmallTest
+import com.android.testutils.ConnectivityModuleTest
+import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
+import com.android.testutils.DevSdkIgnoreRunner
+import kotlin.test.assertContentEquals
+import kotlin.test.assertEquals
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/** CTS tests for {@link OffloadServiceInfo}. */
+@IgnoreUpTo(Build.VERSION_CODES.S_V2)
+@RunWith(DevSdkIgnoreRunner::class)
+@SmallTest
+@ConnectivityModuleTest
+class OffloadServiceInfoTest {
+    @Test
+    fun testCreateOffloadServiceInfo() {
+        val offloadServiceInfo = OffloadServiceInfo(
+            OffloadServiceInfo.Key("_testService", "_testType"),
+            listOf("_sub1", "_sub2"),
+            "Android.local",
+            byteArrayOf(0x1, 0x2, 0x3),
+            1 /* priority */,
+            OFFLOAD_TYPE_FILTER_QUERIES.toLong()
+        )
+
+        assertEquals(OffloadServiceInfo.Key("_testService", "_testType"), offloadServiceInfo.key)
+        assertEquals(listOf("_sub1", "_sub2"), offloadServiceInfo.subtypes)
+        assertEquals("Android.local", offloadServiceInfo.hostname)
+        assertContentEquals(byteArrayOf(0x1, 0x2, 0x3), offloadServiceInfo.offloadPayload)
+        assertEquals(1, offloadServiceInfo.priority)
+        assertEquals(OFFLOAD_TYPE_FILTER_QUERIES.toLong(), offloadServiceInfo.offloadType)
+    }
+}
diff --git a/tests/cts/net/util/Android.bp b/tests/cts/net/util/Android.bp
index fffd30f..644634b 100644
--- a/tests/cts/net/util/Android.bp
+++ b/tests/cts/net/util/Android.bp
@@ -16,12 +16,16 @@
 
 // Common utilities for cts net tests.
 package {
+    default_team: "trendy_team_fwk_core_networking",
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
 java_library {
     name: "cts-net-utils",
-    srcs: ["java/**/*.java", "java/**/*.kt"],
+    srcs: [
+        "java/**/*.java",
+        "java/**/*.kt",
+    ],
     static_libs: [
         "compatibility-device-util-axt",
         "junit",
diff --git a/tests/cts/net/util/java/android/net/cts/util/CtsNetUtils.java b/tests/cts/net/util/java/android/net/cts/util/CtsNetUtils.java
index 96330e2..3d828a4 100644
--- a/tests/cts/net/util/java/android/net/cts/util/CtsNetUtils.java
+++ b/tests/cts/net/util/java/android/net/cts/util/CtsNetUtils.java
@@ -173,21 +173,39 @@
         return cb;
     }
 
-    // Toggle WiFi twice, leaving it in the state it started in
-    public void toggleWifi() throws Exception {
-        if (mWifiManager.isWifiEnabled()) {
-            Network wifiNetwork = getWifiNetwork();
-            // Ensure system default network is WIFI because it's expected in disconnectFromWifi()
-            expectNetworkIsSystemDefault(wifiNetwork);
-            disconnectFromWifi(wifiNetwork);
-            connectToWifi();
-        } else {
-            connectToWifi();
-            Network wifiNetwork = getWifiNetwork();
-            // Ensure system default network is WIFI because it's expected in disconnectFromWifi()
-            expectNetworkIsSystemDefault(wifiNetwork);
-            disconnectFromWifi(wifiNetwork);
+    /**
+     * Toggle Wi-Fi off and on, waiting for the {@link ConnectivityManager#CONNECTIVITY_ACTION}
+     * broadcast in both cases.
+     */
+    public void reconnectWifiAndWaitForConnectivityAction() throws Exception {
+        assertTrue(mWifiManager.isWifiEnabled());
+        Network wifiNetwork = getWifiNetwork();
+        // Ensure system default network is WIFI because it's expected in disconnectFromWifi()
+        expectNetworkIsSystemDefault(wifiNetwork);
+        disconnectFromWifi(wifiNetwork, true /* expectLegacyBroadcast */);
+        connectToWifi(true /* expectLegacyBroadcast */);
+    }
+
+    /**
+     * Turn Wi-Fi off, then back on and make sure it connects, if it is supported.
+     */
+    public void reconnectWifiIfSupported() throws Exception {
+        if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WIFI)) {
+            return;
         }
+        disableWifi();
+        ensureWifiConnected();
+    }
+
+    /**
+     * Turn cell data off, then back on and make sure it connects, if it is supported.
+     */
+    public void reconnectCellIfSupported() throws Exception {
+        if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
+            return;
+        }
+        setMobileDataEnabled(false);
+        setMobileDataEnabled(true);
     }
 
     public Network expectNetworkIsSystemDefault(Network network)
diff --git a/tests/cts/netpermission/internetpermission/Android.bp b/tests/cts/netpermission/internetpermission/Android.bp
index 5314396..7d5ca2f 100644
--- a/tests/cts/netpermission/internetpermission/Android.bp
+++ b/tests/cts/netpermission/internetpermission/Android.bp
@@ -13,6 +13,7 @@
 // limitations under the License.
 
 package {
+    default_team: "trendy_team_fwk_core_networking",
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
diff --git a/tests/cts/netpermission/updatestatspermission/Android.bp b/tests/cts/netpermission/updatestatspermission/Android.bp
index 40474db..2fde1ce 100644
--- a/tests/cts/netpermission/updatestatspermission/Android.bp
+++ b/tests/cts/netpermission/updatestatspermission/Android.bp
@@ -13,6 +13,7 @@
 // limitations under the License.
 
 package {
+    default_team: "trendy_team_fwk_core_networking",
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
diff --git a/tests/cts/tethering/Android.bp b/tests/cts/tethering/Android.bp
index 4284f56..3928961 100644
--- a/tests/cts/tethering/Android.bp
+++ b/tests/cts/tethering/Android.bp
@@ -13,6 +13,7 @@
 // limitations under the License.
 
 package {
+    default_team: "trendy_team_fwk_core_networking",
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
diff --git a/tests/cts/tethering/src/android/tethering/cts/TetheringManagerTest.java b/tests/cts/tethering/src/android/tethering/cts/TetheringManagerTest.java
index 274596f..81608f7 100644
--- a/tests/cts/tethering/src/android/tethering/cts/TetheringManagerTest.java
+++ b/tests/cts/tethering/src/android/tethering/cts/TetheringManagerTest.java
@@ -71,6 +71,8 @@
 import androidx.test.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.testutils.ParcelUtils;
+
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
@@ -236,6 +238,26 @@
     }
 
     @Test
+    public void testTetheringRequestParcelable() {
+        final LinkAddress localAddr = new LinkAddress("192.168.24.5/24");
+        final LinkAddress clientAddr = new LinkAddress("192.168.24.100/24");
+        final TetheringRequest unparceled = new TetheringRequest.Builder(TETHERING_USB)
+                .setStaticIpv4Addresses(localAddr, clientAddr)
+                .setExemptFromEntitlementCheck(true)
+                .setShouldShowEntitlementUi(false).build();
+        final TetheringRequest parceled = ParcelUtils.parcelingRoundTrip(unparceled);
+        assertEquals(unparceled.getTetheringType(), parceled.getTetheringType());
+        assertEquals(unparceled.getConnectivityScope(), parceled.getConnectivityScope());
+        assertEquals(unparceled.getLocalIpv4Address(), parceled.getLocalIpv4Address());
+        assertEquals(unparceled.getClientStaticIpv4Address(),
+                parceled.getClientStaticIpv4Address());
+        assertEquals(unparceled.isExemptFromEntitlementCheck(),
+                parceled.isExemptFromEntitlementCheck());
+        assertEquals(unparceled.getShouldShowEntitlementUi(),
+                parceled.getShouldShowEntitlementUi());
+    }
+
+    @Test
     public void testRegisterTetheringEventCallback() throws Exception {
         final TestTetheringEventCallback tetherEventCallback =
                 mCtsTetheringUtils.registerTetheringEventCallback();
diff --git a/tests/deflake/Android.bp b/tests/deflake/Android.bp
index 8205f1c..726e504 100644
--- a/tests/deflake/Android.bp
+++ b/tests/deflake/Android.bp
@@ -15,6 +15,7 @@
 //
 
 package {
+    default_team: "trendy_team_fwk_core_networking",
     // See: http://go/android-license-faq
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
diff --git a/tests/integration/Android.bp b/tests/integration/Android.bp
index f705e34..349529dd 100644
--- a/tests/integration/Android.bp
+++ b/tests/integration/Android.bp
@@ -15,6 +15,7 @@
 //
 
 package {
+    default_team: "trendy_team_fwk_core_networking",
     // See: http://go/android-license-faq
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
@@ -74,7 +75,10 @@
 java_library {
     name: "frameworks-net-integration-testutils",
     defaults: ["framework-connectivity-test-defaults"],
-    srcs: ["util/**/*.java", "util/**/*.kt"],
+    srcs: [
+        "util/**/*.java",
+        "util/**/*.kt",
+    ],
     static_libs: [
         "androidx.annotation_annotation",
         "androidx.test.rules",
diff --git a/tests/integration/AndroidManifest.xml b/tests/integration/AndroidManifest.xml
index cea83c7..1821329 100644
--- a/tests/integration/AndroidManifest.xml
+++ b/tests/integration/AndroidManifest.xml
@@ -42,6 +42,9 @@
     <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"/>
     <!-- Register UidFrozenStateChangedCallback -->
     <uses-permission android:name="android.permission.PACKAGE_USAGE_STATS"/>
+    <!-- Permission required for CTS test - NetworkStatsIntegrationTest -->
+    <uses-permission android:name="android.permission.INTERNET"/>
+    <uses-permission android:name="android.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS"/>
     <application android:debuggable="true">
         <uses-library android:name="android.test.runner"/>
 
diff --git a/tests/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt b/tests/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt
index 9b1bf6e..361d68c 100644
--- a/tests/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt
+++ b/tests/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt
@@ -60,6 +60,7 @@
 import com.android.server.connectivity.MockableSystemProperties
 import com.android.server.connectivity.MultinetworkPolicyTracker
 import com.android.server.connectivity.ProxyTracker
+import com.android.server.connectivity.SatelliteAccessController
 import com.android.testutils.DevSdkIgnoreRunner
 import com.android.testutils.DeviceInfoUtils
 import com.android.testutils.RecorderCallback.CallbackEntry.LinkPropertiesChanged
@@ -86,6 +87,8 @@
 import org.mockito.Mockito.mock
 import org.mockito.MockitoAnnotations
 import org.mockito.Spy
+import java.util.function.Consumer
+import java.util.function.BiConsumer
 
 const val SERVICE_BIND_TIMEOUT_MS = 5_000L
 const val TEST_TIMEOUT_MS = 10_000L
@@ -240,15 +243,25 @@
 
         override fun makeCarrierPrivilegeAuthenticator(
             context: Context,
-            tm: TelephonyManager
+            tm: TelephonyManager,
+            requestRestrictedWifiEnabled: Boolean,
+            listener: BiConsumer<Int, Int>
         ): CarrierPrivilegeAuthenticator {
             return CarrierPrivilegeAuthenticator(context,
                 object : CarrierPrivilegeAuthenticator.Dependencies() {
                     override fun makeHandlerThread(): HandlerThread =
                         super.makeHandlerThread().also { handlerThreads.add(it) }
                 },
-                tm, TelephonyManagerShimImpl.newInstance(tm))
+                tm, TelephonyManagerShimImpl.newInstance(tm),
+                requestRestrictedWifiEnabled, listener)
         }
+
+        override fun makeSatelliteAccessController(
+            context: Context,
+            updateSatellitePreferredUid: Consumer<MutableSet<Int>>?,
+            connectivityServiceInternalHandler: Handler
+        ): SatelliteAccessController? = mock(
+            SatelliteAccessController::class.java)
     }
 
     @After
diff --git a/tests/integration/src/com/android/server/net/integrationtests/NetworkStatsIntegrationTest.kt b/tests/integration/src/com/android/server/net/integrationtests/NetworkStatsIntegrationTest.kt
new file mode 100644
index 0000000..765e56e
--- /dev/null
+++ b/tests/integration/src/com/android/server/net/integrationtests/NetworkStatsIntegrationTest.kt
@@ -0,0 +1,597 @@
+/*
+ * Copyright (C) 2023 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.net.integrationtests
+
+import android.Manifest.permission.MANAGE_TEST_NETWORKS
+import android.annotation.TargetApi
+import android.app.usage.NetworkStats
+import android.app.usage.NetworkStats.Bucket
+import android.app.usage.NetworkStats.Bucket.TAG_NONE
+import android.app.usage.NetworkStatsManager
+import android.content.Context
+import android.net.ConnectivityManager
+import android.net.ConnectivityManager.TYPE_TEST
+import android.net.InetAddresses
+import android.net.IpPrefix
+import android.net.LinkAddress
+import android.net.Network
+import android.net.NetworkCapabilities
+import android.net.NetworkRequest
+import android.net.NetworkTemplate
+import android.net.NetworkTemplate.MATCH_TEST
+import android.net.TestNetworkSpecifier
+import android.net.TrafficStats
+import android.os.Build
+import android.os.Process
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.server.net.integrationtests.NetworkStatsIntegrationTest.Direction.DOWNLOAD
+import com.android.server.net.integrationtests.NetworkStatsIntegrationTest.Direction.UPLOAD
+import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
+import com.android.testutils.DevSdkIgnoreRunner
+import com.android.testutils.PacketBridge
+import com.android.testutils.RecorderCallback.CallbackEntry.LinkPropertiesChanged
+import com.android.testutils.TestDnsServer
+import com.android.testutils.TestHttpServer
+import com.android.testutils.TestableNetworkCallback
+import com.android.testutils.runAsShell
+import fi.iki.elonen.NanoHTTPD
+import java.io.BufferedInputStream
+import java.io.BufferedOutputStream
+import java.net.HttpURLConnection
+import java.net.HttpURLConnection.HTTP_OK
+import java.net.InetSocketAddress
+import java.net.URL
+import java.nio.charset.Charset
+import kotlin.math.ceil
+import kotlin.test.assertEquals
+import kotlin.test.assertTrue
+import org.junit.After
+import org.junit.Assume.assumeTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+private const val TEST_TAG = 0xF00D
+
+@RunWith(DevSdkIgnoreRunner::class)
+@TargetApi(Build.VERSION_CODES.S)
+@IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
+class NetworkStatsIntegrationTest {
+    private val TAG = NetworkStatsIntegrationTest::class.java.simpleName
+    private val LOCAL_V6ADDR =
+        LinkAddress(InetAddresses.parseNumericAddress("2001:db8::1234"), 64)
+
+    // Remote address, both the client and server will have a hallucination that
+    // they are talking to this address.
+    private val REMOTE_V6ADDR =
+        LinkAddress(InetAddresses.parseNumericAddress("dead:beef::808:808"), 64)
+    private val REMOTE_V4ADDR =
+        LinkAddress(InetAddresses.parseNumericAddress("8.8.8.8"), 32)
+    private val DEFAULT_MTU = 1500
+    private val DEFAULT_BUFFER_SIZE = 1500 // Any size greater than or equal to mtu
+    private val CONNECTION_TIMEOUT_MILLIS = 15000
+    private val TEST_DOWNLOAD_SIZE = 10000L
+    private val TEST_UPLOAD_SIZE = 20000L
+    private val HTTP_SERVER_NAME = "test.com"
+    private val HTTP_SERVER_PORT = 8080 // Use port > 1024 to avoid restrictions on system ports
+    private val DNS_INTERNAL_SERVER_PORT = 53
+    private val DNS_EXTERNAL_SERVER_PORT = 1053
+    private val TCP_ACK_SIZE = 72
+
+    // Packet overheads that are not part of the actual data transmission, these
+    // include DNS packets, TCP handshake/termination packets, and HTTP header
+    // packets. These overheads were gathered from real samples and may not
+    // be perfectly accurate because of DNS caches and TCP retransmissions, etc.
+    private val CONSTANT_PACKET_OVERHEAD = 8
+
+    // 130 is an observed average.
+    private val CONSTANT_BYTES_OVERHEAD = 130 * CONSTANT_PACKET_OVERHEAD
+    private val TOLERANCE = 1.3
+
+    // Set up the packet bridge with two IPv6 address only test networks.
+    private val inst = InstrumentationRegistry.getInstrumentation()
+    private val context = inst.getContext()
+    private val packetBridge = runAsShell(MANAGE_TEST_NETWORKS) {
+        PacketBridge(
+            context,
+            listOf(LOCAL_V6ADDR),
+            REMOTE_V6ADDR.address,
+            listOf(
+                Pair(DNS_INTERNAL_SERVER_PORT, DNS_EXTERNAL_SERVER_PORT)
+            )
+        )
+    }
+    private val cm = context.getSystemService(ConnectivityManager::class.java)!!
+
+    // Set up DNS server for testing server and DNS64.
+    private val fakeDns = TestDnsServer(
+        packetBridge.externalNetwork,
+        InetSocketAddress(LOCAL_V6ADDR.address, DNS_EXTERNAL_SERVER_PORT)
+    ).apply {
+        start()
+        setAnswer(
+            "ipv4only.arpa",
+            listOf(IpPrefix(REMOTE_V6ADDR.address, REMOTE_V6ADDR.prefixLength).address)
+        )
+        setAnswer(HTTP_SERVER_NAME, listOf(REMOTE_V4ADDR.address))
+    }
+
+    // Start up test http server.
+    private val httpServer = TestHttpServer(
+        LOCAL_V6ADDR.address.hostAddress,
+        HTTP_SERVER_PORT
+    ).apply {
+        start()
+    }
+
+    @Before
+    fun setUp() {
+        assumeTrue(shouldRunTests())
+        packetBridge.start()
+    }
+
+    // For networkstack tests, it is not guaranteed that the tethering module will be
+    // updated at the same time. If the tethering module is not new enough, it may not contain
+    // the necessary abilities to run these tests. For example, The tests depends on test
+    // network stats being counted, which can only be achieved when they are marked as TYPE_TEST.
+    // If the tethering module does not support TYPE_TEST stats, then these tests will need
+    // to be skipped.
+    fun shouldRunTests() = cm.getNetworkInfo(packetBridge.internalNetwork)!!.type == TYPE_TEST
+
+    @After
+    fun tearDown() {
+        packetBridge.stop()
+        fakeDns.stop()
+        httpServer.stop()
+    }
+
+    private fun waitFor464XlatReady(network: Network): String {
+        val iface = cm.getLinkProperties(network)!!.interfaceName!!
+
+        // Make a network request to listen to the specific test network.
+        val nr = NetworkRequest.Builder()
+            .clearCapabilities()
+            .addTransportType(NetworkCapabilities.TRANSPORT_TEST)
+            .setNetworkSpecifier(TestNetworkSpecifier(iface))
+            .build()
+        val testCb = TestableNetworkCallback()
+        cm.registerNetworkCallback(nr, testCb)
+
+        // Wait for the stacked address to be available.
+        testCb.eventuallyExpect<LinkPropertiesChanged> {
+            it.lp.stackedLinks.getOrNull(0)?.linkAddresses?.getOrNull(0) != null
+        }
+
+        return iface
+    }
+
+    private val Network.mtu: Int get() {
+        val lp = cm.getLinkProperties(this)!!
+        val mtuStacked = if (lp.stackedLinks[0]?.mtu != 0) lp.stackedLinks[0].mtu else DEFAULT_MTU
+        val mtuInterface = if (lp.mtu != 0) lp.mtu else DEFAULT_MTU
+        return mtuInterface.coerceAtMost(mtuStacked)
+    }
+
+    /**
+     * Verify data usage download stats with test 464xlat networks.
+     *
+     * This test starts two test networks and binds them together, the internal one is for the
+     * client to make http traffic on the test network, and the external one is for the mocked
+     * http and dns server to bind to and provide responses.
+     *
+     * After Clat setup, the client will use clat v4 address to send packets to the mocked
+     * server v4 address, which will be translated into a v6 packet by the clat daemon with
+     * NAT64 prefix learned from the mocked DNS64 response. And send to the interface.
+     *
+     * While the packets are being forwarded to the external interface, the servers will see
+     * the packets originated from the mocked v6 address, and destined to a local v6 address.
+     */
+    @Test
+    fun test464XlatTcpStats() {
+        // Wait for 464Xlat to be ready.
+        val internalInterfaceName = waitFor464XlatReady(packetBridge.internalNetwork)
+        val mtu = packetBridge.internalNetwork.mtu
+
+        val snapshotBeforeTest = StatsSnapshot(context, internalInterfaceName)
+
+        // Generate the download traffic.
+        genHttpTraffic(packetBridge.internalNetwork, uploadSize = 0L, TEST_DOWNLOAD_SIZE)
+
+        // In practice, for one way 10k download payload, the download usage is about
+        // 11222~12880 bytes, with 14~17 packets. And the upload usage is about 1279~1626 bytes
+        // with 14~17 packets, which is majorly contributed by TCP ACK packets.
+        val snapshotAfterDownload = StatsSnapshot(context, internalInterfaceName)
+        val (expectedDownloadLower, expectedDownloadUpper) = getExpectedStatsBounds(
+            TEST_DOWNLOAD_SIZE,
+            mtu,
+            DOWNLOAD
+        )
+        assertOnlyNonTaggedStatsIncreases(
+            snapshotBeforeTest,
+            snapshotAfterDownload,
+            expectedDownloadLower,
+            expectedDownloadUpper
+        )
+
+        // Generate upload traffic with tag to verify tagged data accounting as well.
+        genHttpTrafficWithTag(
+            packetBridge.internalNetwork,
+            TEST_UPLOAD_SIZE,
+            downloadSize = 0L,
+            TEST_TAG
+        )
+
+        // Verify upload data usage accounting.
+        val snapshotAfterUpload = StatsSnapshot(context, internalInterfaceName)
+        val (expectedUploadLower, expectedUploadUpper) = getExpectedStatsBounds(
+            TEST_UPLOAD_SIZE,
+            mtu,
+            UPLOAD
+        )
+        assertAllStatsIncreases(
+            snapshotAfterDownload,
+            snapshotAfterUpload,
+            expectedUploadLower,
+            expectedUploadUpper
+        )
+    }
+
+    private enum class Direction {
+        DOWNLOAD,
+        UPLOAD
+    }
+
+    private fun getExpectedStatsBounds(
+        transmittedSize: Long,
+        mtu: Int,
+        direction: Direction
+    ): Pair<BareStats, BareStats> {
+        // This is already an underestimated value since the input doesn't include TCP/IP
+        // layer overhead.
+        val txBytesLower = transmittedSize
+        // Include TCP/IP header overheads and retransmissions in the upper bound.
+        val txBytesUpper = (transmittedSize * TOLERANCE).toLong()
+        val txPacketsLower = txBytesLower / mtu + (CONSTANT_PACKET_OVERHEAD / TOLERANCE).toLong()
+        val estTransmissionPacketsUpper = ceil(txBytesUpper / mtu.toDouble()).toLong()
+        val txPacketsUpper = estTransmissionPacketsUpper +
+                (CONSTANT_PACKET_OVERHEAD * TOLERANCE).toLong()
+        // Assume ACK only sent once for the entire transmission.
+        val rxPacketsLower = 1L + (CONSTANT_PACKET_OVERHEAD / TOLERANCE).toLong()
+        // Assume ACK sent for every RX packet.
+        val rxPacketsUpper = txPacketsUpper
+        val rxBytesLower = 1L * TCP_ACK_SIZE + (CONSTANT_BYTES_OVERHEAD / TOLERANCE).toLong()
+        val rxBytesUpper = estTransmissionPacketsUpper * TCP_ACK_SIZE +
+                (CONSTANT_BYTES_OVERHEAD * TOLERANCE).toLong()
+
+        return if (direction == UPLOAD) {
+            BareStats(rxBytesLower, rxPacketsLower, txBytesLower, txPacketsLower) to
+                    BareStats(rxBytesUpper, rxPacketsUpper, txBytesUpper, txPacketsUpper)
+        } else {
+            BareStats(txBytesLower, txPacketsLower, rxBytesLower, rxPacketsLower) to
+                    BareStats(txBytesUpper, txPacketsUpper, rxBytesUpper, rxPacketsUpper)
+        }
+    }
+
+    private fun genHttpTraffic(network: Network, uploadSize: Long, downloadSize: Long) =
+        genHttpTrafficWithTag(network, uploadSize, downloadSize, NetworkStats.Bucket.TAG_NONE)
+
+    private fun genHttpTrafficWithTag(
+        network: Network,
+        uploadSize: Long,
+        downloadSize: Long,
+        tag: Int
+    ) {
+        val path = "/test_upload_download"
+        val buf = ByteArray(DEFAULT_BUFFER_SIZE)
+
+        httpServer.addResponse(
+            TestHttpServer.Request(path, NanoHTTPD.Method.POST), NanoHTTPD.Response.Status.OK,
+            content = getRandomString(downloadSize)
+        )
+        var httpConnection: HttpURLConnection? = null
+        try {
+            TrafficStats.setThreadStatsTag(tag)
+            val spec = "http://$HTTP_SERVER_NAME:${httpServer.listeningPort}$path"
+            val url = URL(spec)
+            httpConnection = network.openConnection(url) as HttpURLConnection
+            httpConnection.connectTimeout = CONNECTION_TIMEOUT_MILLIS
+            httpConnection.requestMethod = "POST"
+            httpConnection.doOutput = true
+            // Tell the server that the response should not be compressed. Otherwise, the data usage
+            // accounted will be less than expected.
+            httpConnection.setRequestProperty("Accept-Encoding", "identity")
+            // Tell the server that to close connection after this request, this is needed to
+            // prevent from reusing the same socket that has different tagging requirement.
+            httpConnection.setRequestProperty("Connection", "close")
+
+            // Send http body.
+            val outputStream = BufferedOutputStream(httpConnection.outputStream)
+            outputStream.write(getRandomString(uploadSize).toByteArray(Charset.forName("UTF-8")))
+            outputStream.close()
+            assertEquals(HTTP_OK, httpConnection.responseCode)
+
+            // Receive response from the server.
+            val inputStream = BufferedInputStream(httpConnection.getInputStream())
+            var total = 0L
+            while (true) {
+                val count = inputStream.read(buf)
+                if (count == -1) break // End-of-Stream
+                total += count
+            }
+            assertEquals(downloadSize, total)
+        } finally {
+            httpConnection?.inputStream?.close()
+            TrafficStats.clearThreadStatsTag()
+        }
+    }
+
+    // NetworkStats.Bucket cannot be written. So another class is needed to
+    // perform arithmetic operations.
+    data class BareStats(
+        val rxBytes: Long,
+        val rxPackets: Long,
+        val txBytes: Long,
+        val txPackets: Long
+    ) {
+        operator fun plus(other: BareStats): BareStats {
+            return BareStats(
+                this.rxBytes + other.rxBytes, this.rxPackets + other.rxPackets,
+                this.txBytes + other.txBytes, this.txPackets + other.txPackets
+            )
+        }
+
+        operator fun minus(other: BareStats): BareStats {
+            return BareStats(
+                this.rxBytes - other.rxBytes, this.rxPackets - other.rxPackets,
+                this.txBytes - other.txBytes, this.txPackets - other.txPackets
+            )
+        }
+
+        fun reverse(): BareStats =
+            BareStats(
+                rxBytes = txBytes,
+                rxPackets = txPackets,
+                txBytes = rxBytes,
+                txPackets = rxPackets
+            )
+
+        override fun toString(): String {
+            return "BareStats{rx/txBytes=$rxBytes/$txBytes, rx/txPackets=$rxPackets/$txPackets}"
+        }
+
+        override fun equals(other: Any?): Boolean {
+            if (this === other) return true
+            if (other !is BareStats) return false
+
+            if (rxBytes != other.rxBytes) return false
+            if (rxPackets != other.rxPackets) return false
+            if (txBytes != other.txBytes) return false
+            if (txPackets != other.txPackets) return false
+
+            return true
+        }
+
+        override fun hashCode(): Int {
+            return (rxBytes * 11 + rxPackets * 13 + txBytes * 17 + txPackets * 19).toInt()
+        }
+
+        companion object {
+            val EMPTY = BareStats(0L, 0L, 0L, 0L)
+        }
+    }
+
+    data class StatsSnapshot(val context: Context, val iface: String) {
+        val statsSummary = getNetworkSummary(iface)
+        val statsUid = getUidDetail(iface, TAG_NONE)
+        val taggedSummary = getTaggedNetworkSummary(iface, TEST_TAG)
+        val taggedUid = getUidDetail(iface, TEST_TAG)
+        val trafficStatsIface = getTrafficStatsIface(iface)
+        val trafficStatsUid = getTrafficStatsUid(Process.myUid())
+
+        private fun getUidDetail(iface: String, tag: Int): BareStats {
+            return getNetworkStatsThat(iface, tag) { nsm, template ->
+                nsm.queryDetailsForUidTagState(
+                    template, Long.MIN_VALUE, Long.MAX_VALUE,
+                    Process.myUid(), tag, Bucket.STATE_ALL
+                )
+            }
+        }
+
+        private fun getNetworkSummary(iface: String): BareStats {
+            return getNetworkStatsThat(iface, TAG_NONE) { nsm, template ->
+                nsm.querySummary(template, Long.MIN_VALUE, Long.MAX_VALUE)
+            }
+        }
+
+        private fun getTaggedNetworkSummary(iface: String, tag: Int): BareStats {
+            return getNetworkStatsThat(iface, tag) { nsm, template ->
+                nsm.queryTaggedSummary(template, Long.MIN_VALUE, Long.MAX_VALUE)
+            }
+        }
+
+        private fun getNetworkStatsThat(
+            iface: String,
+            tag: Int,
+            queryApi: (nsm: NetworkStatsManager, template: NetworkTemplate) -> NetworkStats
+        ): BareStats {
+            val nsm = context.getSystemService(NetworkStatsManager::class.java)!!
+            nsm.forceUpdate()
+            val testTemplate = NetworkTemplate.Builder(MATCH_TEST)
+                .setWifiNetworkKeys(setOf(iface)).build()
+            val stats = queryApi.invoke(nsm, testTemplate)
+            val filteredBuckets =
+                stats.buckets().filter { it.uid == Process.myUid() && it.tag == tag }
+            return filteredBuckets.fold(BareStats.EMPTY) { acc, it ->
+                acc + BareStats(
+                    it.rxBytes,
+                    it.rxPackets,
+                    it.txBytes,
+                    it.txPackets
+                )
+            }
+        }
+
+        // Helper function to iterate buckets in app.usage.NetworkStats.
+        private fun NetworkStats.buckets() = object : Iterable<NetworkStats.Bucket> {
+            override fun iterator() = object : Iterator<NetworkStats.Bucket> {
+                override operator fun hasNext() = hasNextBucket()
+                override operator fun next() =
+                    NetworkStats.Bucket().also { assertTrue(getNextBucket(it)) }
+            }
+        }
+
+        private fun getTrafficStatsIface(iface: String): BareStats = BareStats(
+            TrafficStats.getRxBytes(iface),
+            TrafficStats.getRxPackets(iface),
+            TrafficStats.getTxBytes(iface),
+            TrafficStats.getTxPackets(iface)
+        )
+
+        private fun getTrafficStatsUid(uid: Int): BareStats = BareStats(
+            TrafficStats.getUidRxBytes(uid),
+            TrafficStats.getUidRxPackets(uid),
+            TrafficStats.getUidTxBytes(uid),
+            TrafficStats.getUidTxPackets(uid)
+        )
+    }
+
+    private fun assertAllStatsIncreases(
+        before: StatsSnapshot,
+        after: StatsSnapshot,
+        lower: BareStats,
+        upper: BareStats
+    ) {
+        assertNonTaggedStatsIncreases(before, after, lower, upper)
+        assertTaggedStatsIncreases(before, after, lower, upper)
+    }
+
+    private fun assertOnlyNonTaggedStatsIncreases(
+        before: StatsSnapshot,
+        after: StatsSnapshot,
+        lower: BareStats,
+        upper: BareStats
+    ) {
+        assertNonTaggedStatsIncreases(before, after, lower, upper)
+        assertTaggedStatsEquals(before, after)
+    }
+
+    private fun assertNonTaggedStatsIncreases(
+        before: StatsSnapshot,
+        after: StatsSnapshot,
+        lower: BareStats,
+        upper: BareStats
+    ) {
+        assertInRange(
+            "Unexpected iface traffic stats",
+            after.iface,
+            before.trafficStatsIface, after.trafficStatsIface,
+            lower, upper
+        )
+        // Uid traffic stats are counted in both direction because the external network
+        // traffic is also attributed to the test uid.
+        assertInRange(
+            "Unexpected uid traffic stats",
+            after.iface,
+            before.trafficStatsUid, after.trafficStatsUid,
+            lower + lower.reverse(), upper + upper.reverse()
+        )
+        assertInRange(
+            "Unexpected non-tagged summary stats",
+            after.iface,
+            before.statsSummary, after.statsSummary,
+            lower, upper
+        )
+        assertInRange(
+            "Unexpected non-tagged uid stats",
+            after.iface,
+            before.statsUid, after.statsUid,
+            lower, upper
+        )
+    }
+
+    private fun assertTaggedStatsEquals(before: StatsSnapshot, after: StatsSnapshot) {
+        // Increment of tagged data should be zero since no tagged traffic was generated.
+        assertEquals(
+            before.taggedSummary,
+            after.taggedSummary,
+            "Unexpected tagged summary stats: ${after.iface}"
+        )
+        assertEquals(
+            before.taggedUid,
+            after.taggedUid,
+            "Unexpected tagged uid stats: ${Process.myUid()} on ${after.iface}"
+        )
+    }
+
+    private fun assertTaggedStatsIncreases(
+        before: StatsSnapshot,
+        after: StatsSnapshot,
+        lower: BareStats,
+        upper: BareStats
+    ) {
+        assertInRange(
+            "Unexpected tagged summary stats",
+            after.iface,
+            before.taggedSummary, after.taggedSummary,
+            lower,
+            upper
+        )
+        assertInRange(
+            "Unexpected tagged uid stats: ${Process.myUid()}",
+            after.iface,
+            before.taggedUid, after.taggedUid,
+            lower,
+            upper
+        )
+    }
+
+    /** Verify the given BareStats is in range [lower, upper] */
+    private fun assertInRange(
+        tag: String,
+        iface: String,
+        before: BareStats,
+        after: BareStats,
+        lower: BareStats,
+        upper: BareStats
+    ) {
+        // Passing the value after operation and the value before operation to dump the actual
+        // numbers if it fails.
+        assertTrue(checkInRange(before, after, lower, upper),
+            "$tag on $iface: $after - $before is not within range [$lower, $upper]"
+        )
+    }
+
+    private fun checkInRange(
+            before: BareStats,
+            after: BareStats,
+            lower: BareStats,
+            upper: BareStats
+    ): Boolean {
+        val value = after - before
+        return value.rxBytes in lower.rxBytes..upper.rxBytes &&
+                value.rxPackets in lower.rxPackets..upper.rxPackets &&
+                value.txBytes in lower.txBytes..upper.txBytes &&
+                value.txPackets in lower.txPackets..upper.txPackets
+    }
+
+    fun getRandomString(length: Long): String {
+        val allowedChars = ('A'..'Z') + ('a'..'z') + ('0'..'9')
+        return (1..length)
+            .map { allowedChars.random() }
+            .joinToString("")
+    }
+}
diff --git a/tests/mts/Android.bp b/tests/mts/Android.bp
index 6425223..336be2e 100644
--- a/tests/mts/Android.bp
+++ b/tests/mts/Android.bp
@@ -14,6 +14,7 @@
 //
 
 package {
+    default_team: "trendy_team_fwk_core_networking",
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
@@ -38,5 +39,5 @@
         "bpf_existence_test.cpp",
     ],
     compile_multilib: "first",
-    min_sdk_version: "30",  // Ensure test runs on R and above.
+    min_sdk_version: "30", // Ensure test runs on R and above.
 }
diff --git a/tests/native/connectivity_native_test/Android.bp b/tests/native/connectivity_native_test/Android.bp
index 8825aa4..2f66d17 100644
--- a/tests/native/connectivity_native_test/Android.bp
+++ b/tests/native/connectivity_native_test/Android.bp
@@ -1,4 +1,5 @@
 package {
+    default_team: "trendy_team_fwk_core_networking",
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
diff --git a/tests/native/utilities/Android.bp b/tests/native/utilities/Android.bp
index 4706b3d..48a5414 100644
--- a/tests/native/utilities/Android.bp
+++ b/tests/native/utilities/Android.bp
@@ -14,14 +14,17 @@
 // limitations under the License.
 
 package {
+    default_team: "trendy_team_fwk_core_networking",
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
+// TODO: delete this as it is a cross-module api boundary violation
 cc_test_library {
     name: "libconnectivity_native_test_utils",
+    visibility: ["//packages/modules/DnsResolver/tests:__subpackages__"],
     defaults: [
         "netd_defaults",
-        "resolv_test_defaults"
+        "resolv_test_defaults",
     ],
     srcs: [
         "firewall.cpp",
diff --git a/tests/native/utilities/firewall.cpp b/tests/native/utilities/firewall.cpp
index 669b76a..34b4f07 100644
--- a/tests/native/utilities/firewall.cpp
+++ b/tests/native/utilities/firewall.cpp
@@ -60,10 +60,10 @@
     // iif should be non-zero if and only if match == MATCH_IIF
     if (match == IIF_MATCH && iif == 0) {
         return Errorf("Interface match {} must have nonzero interface index",
-                      static_cast<int>(match));
+                      static_cast<uint32_t>(match));
     } else if (match != IIF_MATCH && iif != 0) {
         return Errorf("Non-interface match {} must have zero interface index",
-                      static_cast<int>(match));
+                      static_cast<uint32_t>(match));
     }
 
     std::lock_guard guard(mMutex);
@@ -71,14 +71,14 @@
     if (oldMatch.ok()) {
         UidOwnerValue newMatch = {
                 .iif = iif ? iif : oldMatch.value().iif,
-                .rule = static_cast<uint8_t>(oldMatch.value().rule | match),
+                .rule = oldMatch.value().rule | match,
         };
         auto res = mUidOwnerMap.writeValue(uid, newMatch, BPF_ANY);
         if (!res.ok()) return Errorf("Failed to update rule: {}", res.error().message());
     } else {
         UidOwnerValue newMatch = {
                 .iif = iif,
-                .rule = static_cast<uint8_t>(match),
+                .rule = match,
         };
         auto res = mUidOwnerMap.writeValue(uid, newMatch, BPF_ANY);
         if (!res.ok()) return Errorf("Failed to add rule: {}", res.error().message());
@@ -93,7 +93,7 @@
 
     UidOwnerValue newMatch = {
             .iif = (match == IIF_MATCH) ? 0 : oldMatch.value().iif,
-            .rule = static_cast<uint8_t>(oldMatch.value().rule & ~match),
+            .rule = oldMatch.value().rule & ~match,
     };
     if (newMatch.rule == 0) {
         auto res = mUidOwnerMap.deleteValue(uid);
diff --git a/tests/smoketest/Android.bp b/tests/smoketest/Android.bp
index 4ab24fc..121efa1 100644
--- a/tests/smoketest/Android.bp
+++ b/tests/smoketest/Android.bp
@@ -10,6 +10,7 @@
 // TODO: remove this hack when there is a better solution for jni_libs that includes
 // dependent libraries.
 package {
+    default_team: "trendy_team_fwk_core_networking",
     // See: http://go/android-license-faq
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
diff --git a/tests/unit/Android.bp b/tests/unit/Android.bp
index 8b286a0..a5d2f4a 100644
--- a/tests/unit/Android.bp
+++ b/tests/unit/Android.bp
@@ -2,6 +2,7 @@
 // Build FrameworksNetTests package
 //########################################################################
 package {
+    default_team: "trendy_team_fwk_core_networking",
     // See: http://go/android-license-faq
     // A large-scale-change added 'default_applicable_licenses' to import
     // all of the 'license_kinds' from "Android-Apache-2.0"
@@ -57,23 +58,18 @@
 filegroup {
     name: "non-connectivity-module-test",
     srcs: [
-        "java/android/net/Ikev2VpnProfileTest.java",
         "java/android/net/IpMemoryStoreTest.java",
         "java/android/net/TelephonyNetworkSpecifierTest.java",
-        "java/android/net/VpnManagerTest.java",
         "java/android/net/ipmemorystore/*.java",
         "java/android/net/netstats/NetworkStatsDataMigrationUtilsTest.kt",
         "java/com/android/internal/net/NetworkUtilsInternalTest.java",
-        "java/com/android/internal/net/VpnProfileTest.java",
-        "java/com/android/server/VpnManagerServiceTest.java",
         "java/com/android/server/connectivity/IpConnectivityEventBuilderTest.java",
         "java/com/android/server/connectivity/IpConnectivityMetricsTest.java",
         "java/com/android/server/connectivity/MetricsTestUtil.java",
         "java/com/android/server/connectivity/MultipathPolicyTrackerTest.java",
         "java/com/android/server/connectivity/NetdEventListenerServiceTest.java",
-        "java/com/android/server/connectivity/VpnTest.java",
         "java/com/android/server/net/ipmemorystore/*.java",
-    ]
+    ],
 }
 
 // Subset of services-core used to by ConnectivityService tests to test VPN realistically.
@@ -115,7 +111,7 @@
         "service-connectivity-tiramisu-pre-jarjar",
         "services.core-vpn",
         "testables",
-        "cts-net-utils"
+        "cts-net-utils",
     ],
     libs: [
         "android.net.ipsec.ike.stubs.module_lib",
diff --git a/tests/unit/java/android/net/Ikev2VpnProfileTest.java b/tests/unit/java/android/net/Ikev2VpnProfileTest.java
deleted file mode 100644
index e12e961..0000000
--- a/tests/unit/java/android/net/Ikev2VpnProfileTest.java
+++ /dev/null
@@ -1,585 +0,0 @@
-/*
- * Copyright (C) 2019 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 android.net;
-
-import static android.net.cts.util.IkeSessionTestUtils.CHILD_PARAMS;
-import static android.net.cts.util.IkeSessionTestUtils.IKE_PARAMS_V6;
-import static android.net.cts.util.IkeSessionTestUtils.getTestIkeSessionParams;
-
-import static com.android.testutils.DevSdkIgnoreRuleKt.SC_V2;
-
-import static org.junit.Assert.assertArrayEquals;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-import android.net.ipsec.ike.IkeKeyIdIdentification;
-import android.net.ipsec.ike.IkeTunnelConnectionParams;
-import android.os.Build;
-import android.test.mock.MockContext;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.internal.net.VpnProfile;
-import com.android.internal.org.bouncycastle.x509.X509V1CertificateGenerator;
-import com.android.net.module.util.ProxyUtils;
-import com.android.testutils.DevSdkIgnoreRule;
-import com.android.testutils.DevSdkIgnoreRunner;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.math.BigInteger;
-import java.security.KeyPair;
-import java.security.KeyPairGenerator;
-import java.security.PrivateKey;
-import java.security.cert.X509Certificate;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Date;
-import java.util.List;
-import java.util.concurrent.TimeUnit;
-
-import javax.security.auth.x500.X500Principal;
-
-/** Unit tests for {@link Ikev2VpnProfile.Builder}. */
-@SmallTest
-@RunWith(DevSdkIgnoreRunner.class)
-@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
-public class Ikev2VpnProfileTest {
-    private static final String SERVER_ADDR_STRING = "1.2.3.4";
-    private static final String IDENTITY_STRING = "Identity";
-    private static final String USERNAME_STRING = "username";
-    private static final String PASSWORD_STRING = "pa55w0rd";
-    private static final String EXCL_LIST = "exclList";
-    private static final byte[] PSK_BYTES = "preSharedKey".getBytes();
-    private static final int TEST_MTU = 1300;
-
-    @Rule
-    public final DevSdkIgnoreRule ignoreRule = new DevSdkIgnoreRule();
-
-    private final MockContext mMockContext =
-            new MockContext() {
-                @Override
-                public String getOpPackageName() {
-                    return "fooPackage";
-                }
-            };
-    private final ProxyInfo mProxy = ProxyInfo.buildDirectProxy(
-            SERVER_ADDR_STRING, -1, ProxyUtils.exclusionStringAsList(EXCL_LIST));
-
-    private X509Certificate mUserCert;
-    private X509Certificate mServerRootCa;
-    private PrivateKey mPrivateKey;
-
-    @Before
-    public void setUp() throws Exception {
-        mServerRootCa = generateRandomCertAndKeyPair().cert;
-
-        final CertificateAndKey userCertKey = generateRandomCertAndKeyPair();
-        mUserCert = userCertKey.cert;
-        mPrivateKey = userCertKey.key;
-    }
-
-    private Ikev2VpnProfile.Builder getBuilderWithDefaultOptions() {
-        final Ikev2VpnProfile.Builder builder =
-                new Ikev2VpnProfile.Builder(SERVER_ADDR_STRING, IDENTITY_STRING);
-
-        builder.setBypassable(true);
-        builder.setProxy(mProxy);
-        builder.setMaxMtu(TEST_MTU);
-        builder.setMetered(true);
-
-        return builder;
-    }
-
-    @Test
-    public void testBuildValidProfileWithOptions() throws Exception {
-        final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
-
-        builder.setAuthUsernamePassword(USERNAME_STRING, PASSWORD_STRING, mServerRootCa);
-        final Ikev2VpnProfile profile = builder.build();
-        assertNotNull(profile);
-
-        // Check non-auth parameters correctly stored
-        assertEquals(SERVER_ADDR_STRING, profile.getServerAddr());
-        assertEquals(IDENTITY_STRING, profile.getUserIdentity());
-        assertEquals(mProxy, profile.getProxyInfo());
-        assertTrue(profile.isBypassable());
-        assertTrue(profile.isMetered());
-        assertEquals(TEST_MTU, profile.getMaxMtu());
-        assertEquals(Ikev2VpnProfile.DEFAULT_ALGORITHMS, profile.getAllowedAlgorithms());
-    }
-
-    @Test
-    public void testBuildUsernamePasswordProfile() throws Exception {
-        final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
-
-        builder.setAuthUsernamePassword(USERNAME_STRING, PASSWORD_STRING, mServerRootCa);
-        final Ikev2VpnProfile profile = builder.build();
-        assertNotNull(profile);
-
-        assertEquals(USERNAME_STRING, profile.getUsername());
-        assertEquals(PASSWORD_STRING, profile.getPassword());
-        assertEquals(mServerRootCa, profile.getServerRootCaCert());
-
-        assertNull(profile.getPresharedKey());
-        assertNull(profile.getRsaPrivateKey());
-        assertNull(profile.getUserCert());
-    }
-
-    @Test
-    public void testBuildDigitalSignatureProfile() throws Exception {
-        final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
-
-        builder.setAuthDigitalSignature(mUserCert, mPrivateKey, mServerRootCa);
-        final Ikev2VpnProfile profile = builder.build();
-        assertNotNull(profile);
-
-        assertEquals(profile.getUserCert(), mUserCert);
-        assertEquals(mPrivateKey, profile.getRsaPrivateKey());
-        assertEquals(profile.getServerRootCaCert(), mServerRootCa);
-
-        assertNull(profile.getPresharedKey());
-        assertNull(profile.getUsername());
-        assertNull(profile.getPassword());
-    }
-
-    @Test
-    public void testBuildPresharedKeyProfile() throws Exception {
-        final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
-
-        builder.setAuthPsk(PSK_BYTES);
-        final Ikev2VpnProfile profile = builder.build();
-        assertNotNull(profile);
-
-        assertArrayEquals(PSK_BYTES, profile.getPresharedKey());
-
-        assertNull(profile.getServerRootCaCert());
-        assertNull(profile.getUsername());
-        assertNull(profile.getPassword());
-        assertNull(profile.getRsaPrivateKey());
-        assertNull(profile.getUserCert());
-    }
-
-    @Test
-    public void testBuildWithAllowedAlgorithmsAead() throws Exception {
-        final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
-        builder.setAuthPsk(PSK_BYTES);
-
-        List<String> allowedAlgorithms =
-                Arrays.asList(
-                        IpSecAlgorithm.AUTH_CRYPT_AES_GCM,
-                        IpSecAlgorithm.AUTH_CRYPT_CHACHA20_POLY1305);
-        builder.setAllowedAlgorithms(allowedAlgorithms);
-
-        final Ikev2VpnProfile profile = builder.build();
-        assertEquals(allowedAlgorithms, profile.getAllowedAlgorithms());
-    }
-
-    @Test
-    public void testBuildWithAllowedAlgorithmsNormal() throws Exception {
-        final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
-        builder.setAuthPsk(PSK_BYTES);
-
-        List<String> allowedAlgorithms =
-                Arrays.asList(
-                        IpSecAlgorithm.AUTH_HMAC_SHA512,
-                        IpSecAlgorithm.AUTH_AES_XCBC,
-                        IpSecAlgorithm.AUTH_AES_CMAC,
-                        IpSecAlgorithm.CRYPT_AES_CBC,
-                        IpSecAlgorithm.CRYPT_AES_CTR);
-        builder.setAllowedAlgorithms(allowedAlgorithms);
-
-        final Ikev2VpnProfile profile = builder.build();
-        assertEquals(allowedAlgorithms, profile.getAllowedAlgorithms());
-    }
-
-    @Test
-    public void testSetAllowedAlgorithmsEmptyList() throws Exception {
-        final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
-
-        try {
-            builder.setAllowedAlgorithms(new ArrayList<>());
-            fail("Expected exception due to no valid algorithm set");
-        } catch (IllegalArgumentException expected) {
-        }
-    }
-
-    @Test
-    public void testSetAllowedAlgorithmsInvalidList() throws Exception {
-        final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
-        List<String> allowedAlgorithms = new ArrayList<>();
-
-        try {
-            builder.setAllowedAlgorithms(Arrays.asList(IpSecAlgorithm.AUTH_HMAC_SHA256));
-            fail("Expected exception due to missing encryption");
-        } catch (IllegalArgumentException expected) {
-        }
-
-        try {
-            builder.setAllowedAlgorithms(Arrays.asList(IpSecAlgorithm.CRYPT_AES_CBC));
-            fail("Expected exception due to missing authentication");
-        } catch (IllegalArgumentException expected) {
-        }
-    }
-
-    @Test
-    public void testSetAllowedAlgorithmsInsecureAlgorithm() throws Exception {
-        final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
-        List<String> allowedAlgorithms = new ArrayList<>();
-
-        try {
-            builder.setAllowedAlgorithms(Arrays.asList(IpSecAlgorithm.AUTH_HMAC_MD5));
-            fail("Expected exception due to insecure algorithm");
-        } catch (IllegalArgumentException expected) {
-        }
-
-        try {
-            builder.setAllowedAlgorithms(Arrays.asList(IpSecAlgorithm.AUTH_HMAC_SHA1));
-            fail("Expected exception due to insecure algorithm");
-        } catch (IllegalArgumentException expected) {
-        }
-    }
-
-    @Test
-    public void testBuildNoAuthMethodSet() throws Exception {
-        final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
-
-        try {
-            builder.build();
-            fail("Expected exception due to lack of auth method");
-        } catch (IllegalArgumentException expected) {
-        }
-    }
-
-
-    // TODO: Refer to Build.VERSION_CODES.SC_V2 when it's available in AOSP and mainline branch
-    @DevSdkIgnoreRule.IgnoreUpTo(SC_V2)
-    @Test
-    public void testBuildExcludeLocalRoutesSet() throws Exception {
-        final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
-        builder.setAuthPsk(PSK_BYTES);
-        builder.setLocalRoutesExcluded(true);
-
-        final Ikev2VpnProfile profile = builder.build();
-        assertNotNull(profile);
-        assertTrue(profile.areLocalRoutesExcluded());
-
-        builder.setBypassable(false);
-        try {
-            builder.build();
-            fail("Expected exception because excludeLocalRoutes should be set only"
-                    + " on the bypassable VPN");
-        } catch (IllegalArgumentException expected) {
-        }
-    }
-
-    @Test
-    public void testBuildInvalidMtu() throws Exception {
-        final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
-
-        try {
-            builder.setMaxMtu(500);
-            fail("Expected exception due to too-small MTU");
-        } catch (IllegalArgumentException expected) {
-        }
-    }
-
-    private void verifyVpnProfileCommon(VpnProfile profile) {
-        assertEquals(SERVER_ADDR_STRING, profile.server);
-        assertEquals(IDENTITY_STRING, profile.ipsecIdentifier);
-        assertEquals(mProxy, profile.proxy);
-        assertTrue(profile.isBypassable);
-        assertTrue(profile.isMetered);
-        assertEquals(TEST_MTU, profile.maxMtu);
-    }
-
-    @Test
-    public void testPskConvertToVpnProfile() throws Exception {
-        final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
-
-        builder.setAuthPsk(PSK_BYTES);
-        final VpnProfile profile = builder.build().toVpnProfile();
-
-        verifyVpnProfileCommon(profile);
-        assertEquals(Ikev2VpnProfile.encodeForIpsecSecret(PSK_BYTES), profile.ipsecSecret);
-
-        // Check nothing else is set
-        assertEquals("", profile.username);
-        assertEquals("", profile.password);
-        assertEquals("", profile.ipsecUserCert);
-        assertEquals("", profile.ipsecCaCert);
-    }
-
-    @Test
-    public void testUsernamePasswordConvertToVpnProfile() throws Exception {
-        final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
-
-        builder.setAuthUsernamePassword(USERNAME_STRING, PASSWORD_STRING, mServerRootCa);
-        final VpnProfile profile = builder.build().toVpnProfile();
-
-        verifyVpnProfileCommon(profile);
-        assertEquals(USERNAME_STRING, profile.username);
-        assertEquals(PASSWORD_STRING, profile.password);
-        assertEquals(Ikev2VpnProfile.certificateToPemString(mServerRootCa), profile.ipsecCaCert);
-
-        // Check nothing else is set
-        assertEquals("", profile.ipsecUserCert);
-        assertEquals("", profile.ipsecSecret);
-    }
-
-    @Test
-    public void testRsaConvertToVpnProfile() throws Exception {
-        final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
-
-        builder.setAuthDigitalSignature(mUserCert, mPrivateKey, mServerRootCa);
-        final VpnProfile profile = builder.build().toVpnProfile();
-
-        final String expectedSecret = Ikev2VpnProfile.PREFIX_INLINE
-                + Ikev2VpnProfile.encodeForIpsecSecret(mPrivateKey.getEncoded());
-        verifyVpnProfileCommon(profile);
-        assertEquals(Ikev2VpnProfile.certificateToPemString(mUserCert), profile.ipsecUserCert);
-        assertEquals(
-                expectedSecret,
-                profile.ipsecSecret);
-        assertEquals(Ikev2VpnProfile.certificateToPemString(mServerRootCa), profile.ipsecCaCert);
-
-        // Check nothing else is set
-        assertEquals("", profile.username);
-        assertEquals("", profile.password);
-    }
-
-    @Test
-    public void testPskFromVpnProfileDiscardsIrrelevantValues() throws Exception {
-        final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
-
-        builder.setAuthPsk(PSK_BYTES);
-        final VpnProfile profile = builder.build().toVpnProfile();
-        profile.username = USERNAME_STRING;
-        profile.password = PASSWORD_STRING;
-        profile.ipsecCaCert = Ikev2VpnProfile.certificateToPemString(mServerRootCa);
-        profile.ipsecUserCert = Ikev2VpnProfile.certificateToPemString(mUserCert);
-
-        final Ikev2VpnProfile result = Ikev2VpnProfile.fromVpnProfile(profile);
-        assertNull(result.getUsername());
-        assertNull(result.getPassword());
-        assertNull(result.getUserCert());
-        assertNull(result.getRsaPrivateKey());
-        assertNull(result.getServerRootCaCert());
-    }
-
-    @Test
-    public void testUsernamePasswordFromVpnProfileDiscardsIrrelevantValues() throws Exception {
-        final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
-
-        builder.setAuthUsernamePassword(USERNAME_STRING, PASSWORD_STRING, mServerRootCa);
-        final VpnProfile profile = builder.build().toVpnProfile();
-        profile.ipsecSecret = new String(PSK_BYTES);
-        profile.ipsecUserCert = Ikev2VpnProfile.certificateToPemString(mUserCert);
-
-        final Ikev2VpnProfile result = Ikev2VpnProfile.fromVpnProfile(profile);
-        assertNull(result.getPresharedKey());
-        assertNull(result.getUserCert());
-        assertNull(result.getRsaPrivateKey());
-    }
-
-    @Test
-    public void testRsaFromVpnProfileDiscardsIrrelevantValues() throws Exception {
-        final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
-
-        builder.setAuthDigitalSignature(mUserCert, mPrivateKey, mServerRootCa);
-        final VpnProfile profile = builder.build().toVpnProfile();
-        profile.username = USERNAME_STRING;
-        profile.password = PASSWORD_STRING;
-
-        final Ikev2VpnProfile result = Ikev2VpnProfile.fromVpnProfile(profile);
-        assertNull(result.getUsername());
-        assertNull(result.getPassword());
-        assertNull(result.getPresharedKey());
-    }
-
-    @Test
-    public void testPskConversionIsLossless() throws Exception {
-        final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
-
-        builder.setAuthPsk(PSK_BYTES);
-        final Ikev2VpnProfile ikeProfile = builder.build();
-
-        assertEquals(ikeProfile, Ikev2VpnProfile.fromVpnProfile(ikeProfile.toVpnProfile()));
-    }
-
-    @Test
-    public void testUsernamePasswordConversionIsLossless() throws Exception {
-        final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
-
-        builder.setAuthUsernamePassword(USERNAME_STRING, PASSWORD_STRING, mServerRootCa);
-        final Ikev2VpnProfile ikeProfile = builder.build();
-
-        assertEquals(ikeProfile, Ikev2VpnProfile.fromVpnProfile(ikeProfile.toVpnProfile()));
-    }
-
-    @Test
-    public void testRsaConversionIsLossless() throws Exception {
-        final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
-
-        builder.setAuthDigitalSignature(mUserCert, mPrivateKey, mServerRootCa);
-        final Ikev2VpnProfile ikeProfile = builder.build();
-
-        assertEquals(ikeProfile, Ikev2VpnProfile.fromVpnProfile(ikeProfile.toVpnProfile()));
-    }
-
-    @Test
-    public void testBuildWithIkeTunConnParamsConvertToVpnProfile() throws Exception {
-        // Special keyId that contains delimiter character of VpnProfile
-        final byte[] keyId = "foo\0bar".getBytes();
-        final IkeTunnelConnectionParams tunnelParams = new IkeTunnelConnectionParams(
-                getTestIkeSessionParams(true /* testIpv6 */, new IkeKeyIdIdentification(keyId)),
-                CHILD_PARAMS);
-        final Ikev2VpnProfile ikev2VpnProfile = new Ikev2VpnProfile.Builder(tunnelParams).build();
-        final VpnProfile vpnProfile = ikev2VpnProfile.toVpnProfile();
-
-        assertEquals(VpnProfile.TYPE_IKEV2_FROM_IKE_TUN_CONN_PARAMS, vpnProfile.type);
-
-        // Username, password, server, ipsecIdentifier, ipsecCaCert, ipsecSecret, ipsecUserCert and
-        // getAllowedAlgorithms should not be set if IkeTunnelConnectionParams is set.
-        assertEquals("", vpnProfile.server);
-        assertEquals("", vpnProfile.ipsecIdentifier);
-        assertEquals("", vpnProfile.username);
-        assertEquals("", vpnProfile.password);
-        assertEquals("", vpnProfile.ipsecCaCert);
-        assertEquals("", vpnProfile.ipsecSecret);
-        assertEquals("", vpnProfile.ipsecUserCert);
-        assertEquals(0, vpnProfile.getAllowedAlgorithms().size());
-
-        // IkeTunnelConnectionParams should stay the same.
-        assertEquals(tunnelParams, vpnProfile.ikeTunConnParams);
-
-        // Convert to disk-stable format and then back to Ikev2VpnProfile should be the same.
-        final VpnProfile decodedVpnProfile =
-                VpnProfile.decode(vpnProfile.key, vpnProfile.encode());
-        final Ikev2VpnProfile convertedIkev2VpnProfile =
-                Ikev2VpnProfile.fromVpnProfile(decodedVpnProfile);
-        assertEquals(ikev2VpnProfile, convertedIkev2VpnProfile);
-    }
-
-    @Test
-    public void testConversionIsLosslessWithIkeTunConnParams() throws Exception {
-        final IkeTunnelConnectionParams tunnelParams =
-                new IkeTunnelConnectionParams(IKE_PARAMS_V6, CHILD_PARAMS);
-        // Config authentication related fields is not required while building with
-        // IkeTunnelConnectionParams.
-        final Ikev2VpnProfile ikeProfile = new Ikev2VpnProfile.Builder(tunnelParams).build();
-        assertEquals(ikeProfile, Ikev2VpnProfile.fromVpnProfile(ikeProfile.toVpnProfile()));
-    }
-
-    @Test
-    public void testAutomaticNattAndIpVersionConversionIsLossless() throws Exception {
-        final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
-        builder.setAutomaticNattKeepaliveTimerEnabled(true);
-        builder.setAutomaticIpVersionSelectionEnabled(true);
-
-        builder.setAuthDigitalSignature(mUserCert, mPrivateKey, mServerRootCa);
-        final Ikev2VpnProfile ikeProfile = builder.build();
-
-        assertEquals(ikeProfile, Ikev2VpnProfile.fromVpnProfile(ikeProfile.toVpnProfile()));
-    }
-
-    @Test
-    public void testAutomaticNattAndIpVersionDefaults() throws Exception {
-        final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
-
-        builder.setAuthDigitalSignature(mUserCert, mPrivateKey, mServerRootCa);
-        final Ikev2VpnProfile ikeProfile = builder.build();
-
-        assertEquals(false, ikeProfile.isAutomaticNattKeepaliveTimerEnabled());
-        assertEquals(false, ikeProfile.isAutomaticIpVersionSelectionEnabled());
-    }
-
-    @Test
-    public void testEquals() throws Exception {
-        // Verify building without IkeTunnelConnectionParams
-        final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
-        builder.setAuthDigitalSignature(mUserCert, mPrivateKey, mServerRootCa);
-        assertEquals(builder.build(), builder.build());
-
-        // Verify building with IkeTunnelConnectionParams
-        final IkeTunnelConnectionParams tunnelParams =
-                new IkeTunnelConnectionParams(IKE_PARAMS_V6, CHILD_PARAMS);
-        final IkeTunnelConnectionParams tunnelParams2 =
-                new IkeTunnelConnectionParams(IKE_PARAMS_V6, CHILD_PARAMS);
-        assertEquals(new Ikev2VpnProfile.Builder(tunnelParams).build(),
-                new Ikev2VpnProfile.Builder(tunnelParams2).build());
-    }
-
-    @Test
-    public void testBuildProfileWithNullProxy() throws Exception {
-        final Ikev2VpnProfile ikev2VpnProfile =
-                new Ikev2VpnProfile.Builder(SERVER_ADDR_STRING, IDENTITY_STRING)
-                        .setAuthUsernamePassword(USERNAME_STRING, PASSWORD_STRING, mServerRootCa)
-                        .build();
-
-        // ProxyInfo should be null for the profile without setting ProxyInfo.
-        assertNull(ikev2VpnProfile.getProxyInfo());
-
-        // ProxyInfo should stay null after performing toVpnProfile() and fromVpnProfile()
-        final VpnProfile vpnProfile = ikev2VpnProfile.toVpnProfile();
-        assertNull(vpnProfile.proxy);
-
-        final Ikev2VpnProfile convertedIkev2VpnProfile = Ikev2VpnProfile.fromVpnProfile(vpnProfile);
-        assertNull(convertedIkev2VpnProfile.getProxyInfo());
-    }
-
-    private static class CertificateAndKey {
-        public final X509Certificate cert;
-        public final PrivateKey key;
-
-        CertificateAndKey(X509Certificate cert, PrivateKey key) {
-            this.cert = cert;
-            this.key = key;
-        }
-    }
-
-    private static CertificateAndKey generateRandomCertAndKeyPair() throws Exception {
-        final Date validityBeginDate =
-                new Date(System.currentTimeMillis() - TimeUnit.DAYS.toMillis(1L));
-        final Date validityEndDate =
-                new Date(System.currentTimeMillis() + TimeUnit.DAYS.toMillis(1L));
-
-        // Generate a keypair
-        final KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
-        keyPairGenerator.initialize(512);
-        final KeyPair keyPair = keyPairGenerator.generateKeyPair();
-
-        final X500Principal dnName = new X500Principal("CN=test.android.com");
-        final X509V1CertificateGenerator certGen = new X509V1CertificateGenerator();
-        certGen.setSerialNumber(BigInteger.valueOf(System.currentTimeMillis()));
-        certGen.setSubjectDN(dnName);
-        certGen.setIssuerDN(dnName);
-        certGen.setNotBefore(validityBeginDate);
-        certGen.setNotAfter(validityEndDate);
-        certGen.setPublicKey(keyPair.getPublic());
-        certGen.setSignatureAlgorithm("SHA256WithRSAEncryption");
-
-        final X509Certificate cert = certGen.generate(keyPair.getPrivate(), "AndroidOpenSSL");
-        return new CertificateAndKey(cert, keyPair.getPrivate());
-    }
-}
diff --git a/tests/unit/java/android/net/NetworkUtilsTest.java b/tests/unit/java/android/net/NetworkUtilsTest.java
index 2bf2211..e453c02 100644
--- a/tests/unit/java/android/net/NetworkUtilsTest.java
+++ b/tests/unit/java/android/net/NetworkUtilsTest.java
@@ -159,10 +159,9 @@
         return timeval;
     }
 
-    @Test
-    public void testSetSockOptBytes() throws ErrnoException {
-        final FileDescriptor sock = Os.socket(AF_INET6, SOCK_DGRAM, IPPROTO_ICMPV6);
-        final StructTimeval writeTimeval = StructTimeval.fromMillis(1200);
+    private void testSetSockOptBytes(FileDescriptor sock, long timeValMillis)
+            throws ErrnoException {
+        final StructTimeval writeTimeval = StructTimeval.fromMillis(timeValMillis);
         byte[] timeval = getTimevalBytes(writeTimeval);
         final StructTimeval readTimeval;
 
@@ -170,6 +169,16 @@
         readTimeval = Os.getsockoptTimeval(sock, SOL_SOCKET, SO_RCVTIMEO);
 
         assertEquals(writeTimeval, readTimeval);
+    }
+
+    @Test
+    public void testSetSockOptBytes() throws ErrnoException {
+        final FileDescriptor sock = Os.socket(AF_INET6, SOCK_DGRAM, IPPROTO_ICMPV6);
+
+        testSetSockOptBytes(sock, 3000);
+
+        testSetSockOptBytes(sock, 5000);
+
         SocketUtils.closeSocketQuietly(sock);
     }
 
diff --git a/tests/unit/java/android/net/VpnManagerTest.java b/tests/unit/java/android/net/VpnManagerTest.java
deleted file mode 100644
index 2ab4e45..0000000
--- a/tests/unit/java/android/net/VpnManagerTest.java
+++ /dev/null
@@ -1,148 +0,0 @@
-/*
- * Copyright (C) 2019 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 android.net;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assume.assumeFalse;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.eq;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.content.ComponentName;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.os.Build;
-import android.test.mock.MockContext;
-import android.util.SparseArray;
-
-import androidx.test.filters.SmallTest;
-import androidx.test.InstrumentationRegistry;
-
-import com.android.internal.net.VpnProfile;
-import com.android.internal.util.MessageUtils;
-import com.android.testutils.DevSdkIgnoreRule;
-import com.android.testutils.DevSdkIgnoreRunner;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-/** Unit tests for {@link VpnManager}. */
-@SmallTest
-@RunWith(DevSdkIgnoreRunner.class)
-@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
-public class VpnManagerTest {
-
-    private static final String PKG_NAME = "fooPackage";
-
-    private static final String SESSION_NAME_STRING = "testSession";
-    private static final String SERVER_ADDR_STRING = "1.2.3.4";
-    private static final String IDENTITY_STRING = "Identity";
-    private static final byte[] PSK_BYTES = "preSharedKey".getBytes();
-
-    private IVpnManager mMockService;
-    private VpnManager mVpnManager;
-    private final MockContext mMockContext =
-            new MockContext() {
-                @Override
-                public String getOpPackageName() {
-                    return PKG_NAME;
-                }
-            };
-
-    @Before
-    public void setUp() throws Exception {
-        assumeFalse("Skipping test because watches don't support VPN",
-            InstrumentationRegistry.getContext().getPackageManager().hasSystemFeature(
-                PackageManager.FEATURE_WATCH));
-        mMockService = mock(IVpnManager.class);
-        mVpnManager = new VpnManager(mMockContext, mMockService);
-    }
-
-    @Test
-    public void testProvisionVpnProfilePreconsented() throws Exception {
-        final PlatformVpnProfile profile = getPlatformVpnProfile();
-        when(mMockService.provisionVpnProfile(any(VpnProfile.class), eq(PKG_NAME)))
-                .thenReturn(true);
-
-        // Expect there to be no intent returned, as consent has already been granted.
-        assertNull(mVpnManager.provisionVpnProfile(profile));
-        verify(mMockService).provisionVpnProfile(eq(profile.toVpnProfile()), eq(PKG_NAME));
-    }
-
-    @Test
-    public void testProvisionVpnProfileNeedsConsent() throws Exception {
-        final PlatformVpnProfile profile = getPlatformVpnProfile();
-        when(mMockService.provisionVpnProfile(any(VpnProfile.class), eq(PKG_NAME)))
-                .thenReturn(false);
-
-        // Expect intent to be returned, as consent has not already been granted.
-        final Intent intent = mVpnManager.provisionVpnProfile(profile);
-        assertNotNull(intent);
-
-        final ComponentName expectedComponentName =
-                ComponentName.unflattenFromString(
-                        "com.android.vpndialogs/com.android.vpndialogs.PlatformVpnConfirmDialog");
-        assertEquals(expectedComponentName, intent.getComponent());
-        verify(mMockService).provisionVpnProfile(eq(profile.toVpnProfile()), eq(PKG_NAME));
-    }
-
-    @Test
-    public void testDeleteProvisionedVpnProfile() throws Exception {
-        mVpnManager.deleteProvisionedVpnProfile();
-        verify(mMockService).deleteVpnProfile(eq(PKG_NAME));
-    }
-
-    @Test
-    public void testStartProvisionedVpnProfile() throws Exception {
-        mVpnManager.startProvisionedVpnProfile();
-        verify(mMockService).startVpnProfile(eq(PKG_NAME));
-    }
-
-    @Test
-    public void testStopProvisionedVpnProfile() throws Exception {
-        mVpnManager.stopProvisionedVpnProfile();
-        verify(mMockService).stopVpnProfile(eq(PKG_NAME));
-    }
-
-    private Ikev2VpnProfile getPlatformVpnProfile() throws Exception {
-        return new Ikev2VpnProfile.Builder(SERVER_ADDR_STRING, IDENTITY_STRING)
-                .setBypassable(true)
-                .setMaxMtu(1300)
-                .setMetered(true)
-                .setAuthPsk(PSK_BYTES)
-                .build();
-    }
-
-    @Test
-    public void testVpnTypesEqual() throws Exception {
-        SparseArray<String> vmVpnTypes = MessageUtils.findMessageNames(
-                new Class[] { VpnManager.class }, new String[]{ "TYPE_VPN_" });
-        SparseArray<String> nativeVpnType = MessageUtils.findMessageNames(
-                new Class[] { NativeVpnType.class }, new String[]{ "" });
-
-        // TYPE_VPN_NONE = -1 is only defined in VpnManager.
-        assertEquals(vmVpnTypes.size() - 1, nativeVpnType.size());
-        for (int i = VpnManager.TYPE_VPN_SERVICE; i < vmVpnTypes.size(); i++) {
-            assertEquals(vmVpnTypes.get(i), "TYPE_VPN_" + nativeVpnType.get(i));
-        }
-    }
-}
diff --git a/tests/unit/java/android/net/nsd/AdvertisingRequestTest.kt b/tests/unit/java/android/net/nsd/AdvertisingRequestTest.kt
new file mode 100644
index 0000000..332f2a3
--- /dev/null
+++ b/tests/unit/java/android/net/nsd/AdvertisingRequestTest.kt
@@ -0,0 +1,106 @@
+/*
+ * 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.
+ */
+
+package android.net.nsd
+
+import android.net.nsd.AdvertisingRequest.NSD_ADVERTISING_UPDATE_ONLY
+import android.net.nsd.NsdManager.PROTOCOL_DNS_SD
+import android.os.Build
+import androidx.test.filters.SmallTest
+import com.android.testutils.ConnectivityModuleTest
+import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
+import com.android.testutils.DevSdkIgnoreRunner
+import com.android.testutils.parcelingRoundTrip
+import java.time.Duration
+import kotlin.test.assertEquals
+import kotlin.test.assertNotEquals
+import kotlin.test.assertNull
+import org.junit.Test
+import org.junit.runner.RunWith
+
+// TODO: move this class to CTS tests when AdvertisingRequest is made public
+/** Unit tests for {@link AdvertisingRequest}. */
+@IgnoreUpTo(Build.VERSION_CODES.S_V2)
+@RunWith(DevSdkIgnoreRunner::class)
+@SmallTest
+@ConnectivityModuleTest
+class AdvertisingRequestTest {
+    @Test
+    fun testParcelingIsLossLess() {
+        val info = NsdServiceInfo().apply {
+            serviceType = "_ipp._tcp"
+        }
+        val beforeParcel = AdvertisingRequest.Builder(info, PROTOCOL_DNS_SD)
+                .setAdvertisingConfig(NSD_ADVERTISING_UPDATE_ONLY)
+                .setTtl(Duration.ofSeconds(30L))
+                .build()
+
+        val afterParcel = parcelingRoundTrip(beforeParcel)
+
+        assertEquals(beforeParcel.serviceInfo.serviceType, afterParcel.serviceInfo.serviceType)
+        assertEquals(beforeParcel.advertisingConfig, afterParcel.advertisingConfig)
+    }
+
+@Test
+fun testBuilder_setNullTtl_success() {
+    val info = NsdServiceInfo().apply {
+        serviceType = "_ipp._tcp"
+    }
+    val request = AdvertisingRequest.Builder(info, PROTOCOL_DNS_SD)
+            .setTtl(null)
+            .build()
+
+    assertNull(request.ttl)
+}
+
+    @Test
+    fun testBuilder_setPropertiesSuccess() {
+        val info = NsdServiceInfo().apply {
+            serviceType = "_ipp._tcp"
+        }
+        val request = AdvertisingRequest.Builder(info, PROTOCOL_DNS_SD)
+                .setAdvertisingConfig(NSD_ADVERTISING_UPDATE_ONLY)
+                .setTtl(Duration.ofSeconds(100L))
+                .build()
+
+        assertEquals("_ipp._tcp", request.serviceInfo.serviceType)
+        assertEquals(PROTOCOL_DNS_SD, request.protocolType)
+        assertEquals(NSD_ADVERTISING_UPDATE_ONLY, request.advertisingConfig)
+        assertEquals(Duration.ofSeconds(100L), request.ttl)
+    }
+
+    @Test
+    fun testEquality() {
+        val info = NsdServiceInfo().apply {
+            serviceType = "_ipp._tcp"
+        }
+        val request1 = AdvertisingRequest.Builder(info, PROTOCOL_DNS_SD).build()
+        val request2 = AdvertisingRequest.Builder(info, PROTOCOL_DNS_SD).build()
+        val request3 = AdvertisingRequest.Builder(info, PROTOCOL_DNS_SD)
+                .setAdvertisingConfig(NSD_ADVERTISING_UPDATE_ONLY)
+                .setTtl(Duration.ofSeconds(120L))
+                .build()
+        val request4 = AdvertisingRequest.Builder(info, PROTOCOL_DNS_SD)
+                .setAdvertisingConfig(NSD_ADVERTISING_UPDATE_ONLY)
+                .setTtl(Duration.ofSeconds(120L))
+                .build()
+
+        assertEquals(request1, request2)
+        assertEquals(request3, request4)
+        assertNotEquals(request1, request3)
+        assertNotEquals(request2, request4)
+    }
+}
diff --git a/tests/unit/java/android/net/nsd/NsdManagerTest.java b/tests/unit/java/android/net/nsd/NsdManagerTest.java
index 461ead8..76a649e 100644
--- a/tests/unit/java/android/net/nsd/NsdManagerTest.java
+++ b/tests/unit/java/android/net/nsd/NsdManagerTest.java
@@ -23,6 +23,7 @@
 import static org.junit.Assert.fail;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyInt;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
@@ -52,6 +53,10 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import java.net.InetAddress;
+import java.util.List;
+import java.time.Duration;
+
 @DevSdkIgnoreRunner.MonitorThreadLeak
 @RunWith(DevSdkIgnoreRunner.class)
 @SmallTest
@@ -221,6 +226,23 @@
         verify(listener, timeout(mTimeoutMs).times(1)).onServiceRegistered(request);
     }
 
+    @Test
+    public void testRegisterServiceWithCustomTtl() throws Exception {
+        final NsdManager manager = mManager;
+        final NsdServiceInfo info = new NsdServiceInfo("another_name2", "another_type2");
+        info.setPort(2203);
+        final AdvertisingRequest request = new AdvertisingRequest.Builder(info, PROTOCOL)
+                .setTtl(Duration.ofSeconds(30)).build();
+        final NsdManager.RegistrationListener listener = mock(
+                NsdManager.RegistrationListener.class);
+
+        manager.registerService(request, Runnable::run, listener);
+
+        AdvertisingRequest capturedRequest = getAdvertisingRequest(
+                req -> verify(mServiceConn).registerService(anyInt(), req.capture()));
+        assertEquals(request, capturedRequest);
+    }
+
     private void doTestRegisterService() throws Exception {
         NsdManager manager = mManager;
 
@@ -285,6 +307,7 @@
     private void doTestDiscoverService() throws Exception {
         NsdManager manager = mManager;
 
+        DiscoveryRequest request1 = new DiscoveryRequest.Builder("a_type").build();
         NsdServiceInfo reply1 = new NsdServiceInfo("a_name", "a_type");
         NsdServiceInfo reply2 = new NsdServiceInfo("another_name", "a_type");
         NsdServiceInfo reply3 = new NsdServiceInfo("a_third_name", "a_type");
@@ -305,7 +328,7 @@
         int key2 = getRequestKey(req ->
                 verify(mServiceConn, times(2)).discoverServices(req.capture(), any()));
 
-        mCallback.onDiscoverServicesStarted(key2, reply1);
+        mCallback.onDiscoverServicesStarted(key2, request1);
         verify(listener, timeout(mTimeoutMs).times(1)).onDiscoveryStarted("a_type");
 
 
@@ -345,7 +368,7 @@
         int key3 = getRequestKey(req ->
                 verify(mServiceConn, times(3)).discoverServices(req.capture(), any()));
 
-        mCallback.onDiscoverServicesStarted(key3, reply1);
+        mCallback.onDiscoverServicesStarted(key3, request1);
         verify(listener, timeout(mTimeoutMs).times(1)).onDiscoveryStarted("a_type");
 
         // Client unregisters immediately, it fails
@@ -369,6 +392,9 @@
         NsdManager.RegistrationListener listener1 = mock(NsdManager.RegistrationListener.class);
         NsdManager.DiscoveryListener listener2 = mock(NsdManager.DiscoveryListener.class);
         NsdManager.ResolveListener listener3 = mock(NsdManager.ResolveListener.class);
+        NsdManager.RegistrationListener listener4 = mock(NsdManager.RegistrationListener.class);
+        NsdManager.RegistrationListener listener5 = mock(NsdManager.RegistrationListener.class);
+        NsdManager.RegistrationListener listener6 = mock(NsdManager.RegistrationListener.class);
 
         NsdServiceInfo invalidService = new NsdServiceInfo(null, null);
         NsdServiceInfo validService = new NsdServiceInfo("a_name", "_a_type._tcp");
@@ -378,6 +404,7 @@
                 "_a_type._tcp,_sub1,_s2");
         NsdServiceInfo otherSubtypeUpdate = new NsdServiceInfo("a_name", "_a_type._tcp,_sub1,_s3");
         NsdServiceInfo dotSyntaxSubtypeUpdate = new NsdServiceInfo("a_name", "_sub1._a_type._tcp");
+
         validService.setPort(2222);
         otherServiceWithSubtype.setPort(2222);
         validServiceDuplicate.setPort(2222);
@@ -385,6 +412,33 @@
         otherSubtypeUpdate.setPort(2222);
         dotSyntaxSubtypeUpdate.setPort(2222);
 
+        NsdServiceInfo invalidMissingHostnameWithAddresses = new NsdServiceInfo(null, null);
+        invalidMissingHostnameWithAddresses.setHostAddresses(
+                List.of(
+                        InetAddress.parseNumericAddress("192.168.82.14"),
+                        InetAddress.parseNumericAddress("2001::1")));
+
+        NsdServiceInfo validCustomHostWithAddresses = new NsdServiceInfo(null, null);
+        validCustomHostWithAddresses.setHostname("a_host");
+        validCustomHostWithAddresses.setHostAddresses(
+                List.of(
+                        InetAddress.parseNumericAddress("192.168.82.14"),
+                        InetAddress.parseNumericAddress("2001::1")));
+
+        NsdServiceInfo validServiceWithCustomHostAndAddresses =
+                new NsdServiceInfo("a_name", "_a_type._tcp");
+        validServiceWithCustomHostAndAddresses.setPort(2222);
+        validServiceWithCustomHostAndAddresses.setHostname("a_host");
+        validServiceWithCustomHostAndAddresses.setHostAddresses(
+                List.of(
+                        InetAddress.parseNumericAddress("192.168.82.14"),
+                        InetAddress.parseNumericAddress("2001::1")));
+
+        NsdServiceInfo validServiceWithCustomHostNoAddresses =
+                new NsdServiceInfo("a_name", "_a_type._tcp");
+        validServiceWithCustomHostNoAddresses.setPort(2222);
+        validServiceWithCustomHostNoAddresses.setHostname("a_host");
+
         // Service registration
         //  - invalid arguments
         mustFail(() -> { manager.unregisterService(null); });
@@ -393,6 +447,8 @@
         mustFail(() -> { manager.registerService(invalidService, PROTOCOL, listener1); });
         mustFail(() -> { manager.registerService(validService, -1, listener1); });
         mustFail(() -> { manager.registerService(validService, PROTOCOL, null); });
+        mustFail(() -> {
+            manager.registerService(invalidMissingHostnameWithAddresses, PROTOCOL, listener1); });
         manager.registerService(validService, PROTOCOL, listener1);
         //  - update without subtype is not allowed
         mustFail(() -> { manager.registerService(validServiceDuplicate, PROTOCOL, listener1); });
@@ -414,6 +470,15 @@
         // TODO: make listener immediately reusable
         //mustFail(() -> { manager.unregisterService(listener1); });
         //manager.registerService(validService, PROTOCOL, listener1);
+        //  - registering a custom host without a service is valid
+        manager.registerService(validCustomHostWithAddresses, PROTOCOL, listener4);
+        manager.unregisterService(listener4);
+        //  - registering a service with a custom host is valid
+        manager.registerService(validServiceWithCustomHostAndAddresses, PROTOCOL, listener5);
+        manager.unregisterService(listener5);
+        //  - registering a service with a custom host with no addresses is valid
+        manager.registerService(validServiceWithCustomHostNoAddresses, PROTOCOL, listener6);
+        manager.unregisterService(listener6);
 
         // Discover service
         //  - invalid arguments
@@ -455,4 +520,12 @@
         verifier.accept(captor);
         return captor.getValue();
     }
+
+    AdvertisingRequest getAdvertisingRequest(
+            ThrowingConsumer<ArgumentCaptor<AdvertisingRequest>> verifier) throws Exception {
+        final ArgumentCaptor<AdvertisingRequest> captor =
+                ArgumentCaptor.forClass(AdvertisingRequest.class);
+        verifier.accept(captor);
+        return captor.getValue();
+    }
 }
diff --git a/tests/unit/java/android/net/util/KeepaliveUtilsTest.kt b/tests/unit/java/android/net/util/KeepaliveUtilsTest.kt
index cb3a315..470274d 100644
--- a/tests/unit/java/android/net/util/KeepaliveUtilsTest.kt
+++ b/tests/unit/java/android/net/util/KeepaliveUtilsTest.kt
@@ -95,11 +95,11 @@
 
         // Check resource with invalid transport type.
         assertRunWithException(arrayOf("-1,3"))
-        assertRunWithException(arrayOf("10,3"))
+        assertRunWithException(arrayOf("11,3"))
 
         // Check valid customization generates expected array.
         val validRes = arrayOf("0,3", "1,0", "4,4")
-        val expectedValidRes = intArrayOf(3, 0, 0, 0, 4, 0, 0, 0, 0, 0)
+        val expectedValidRes = intArrayOf(3, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0)
 
         val mockContext = getMockedContextWithStringArrayRes(
                 R.array.config_networkSupportedKeepaliveCount,
diff --git a/tests/unit/java/com/android/internal/net/VpnProfileTest.java b/tests/unit/java/com/android/internal/net/VpnProfileTest.java
deleted file mode 100644
index acae7d2..0000000
--- a/tests/unit/java/com/android/internal/net/VpnProfileTest.java
+++ /dev/null
@@ -1,323 +0,0 @@
-/*
- * Copyright (C) 2019 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.internal.net;
-
-import static android.net.cts.util.IkeSessionTestUtils.CHILD_PARAMS;
-import static android.net.cts.util.IkeSessionTestUtils.IKE_PARAMS_V4;
-
-import static com.android.modules.utils.build.SdkLevel.isAtLeastT;
-import static com.android.modules.utils.build.SdkLevel.isAtLeastU;
-import static com.android.testutils.ParcelUtils.assertParcelSane;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.assertNotSame;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-
-import android.net.IpSecAlgorithm;
-import android.net.ipsec.ike.IkeTunnelConnectionParams;
-import android.os.Build;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.testutils.DevSdkIgnoreRule;
-import com.android.testutils.DevSdkIgnoreRunner;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-
-/** Unit tests for {@link VpnProfile}. */
-@SmallTest
-@RunWith(DevSdkIgnoreRunner.class)
-@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
-public class VpnProfileTest {
-    private static final String DUMMY_PROFILE_KEY = "Test";
-
-    private static final int ENCODED_INDEX_AUTH_PARAMS_INLINE = 23;
-    private static final int ENCODED_INDEX_RESTRICTED_TO_TEST_NETWORKS = 24;
-    private static final int ENCODED_INDEX_EXCLUDE_LOCAL_ROUTE = 25;
-    private static final int ENCODED_INDEX_REQUIRE_PLATFORM_VALIDATION = 26;
-    private static final int ENCODED_INDEX_IKE_TUN_CONN_PARAMS = 27;
-    private static final int ENCODED_INDEX_AUTOMATIC_NATT_KEEPALIVE_TIMER_ENABLED = 28;
-    private static final int ENCODED_INDEX_AUTOMATIC_IP_VERSION_SELECTION_ENABLED = 29;
-
-    @Test
-    public void testDefaults() throws Exception {
-        final VpnProfile p = new VpnProfile(DUMMY_PROFILE_KEY);
-
-        assertEquals(DUMMY_PROFILE_KEY, p.key);
-        assertEquals("", p.name);
-        assertEquals(VpnProfile.TYPE_PPTP, p.type);
-        assertEquals("", p.server);
-        assertEquals("", p.username);
-        assertEquals("", p.password);
-        assertEquals("", p.dnsServers);
-        assertEquals("", p.searchDomains);
-        assertEquals("", p.routes);
-        assertTrue(p.mppe);
-        assertEquals("", p.l2tpSecret);
-        assertEquals("", p.ipsecIdentifier);
-        assertEquals("", p.ipsecSecret);
-        assertEquals("", p.ipsecUserCert);
-        assertEquals("", p.ipsecCaCert);
-        assertEquals("", p.ipsecServerCert);
-        assertEquals(null, p.proxy);
-        assertTrue(p.getAllowedAlgorithms() != null && p.getAllowedAlgorithms().isEmpty());
-        assertFalse(p.isBypassable);
-        assertFalse(p.isMetered);
-        assertEquals(1360, p.maxMtu);
-        assertFalse(p.areAuthParamsInline);
-        assertFalse(p.isRestrictedToTestNetworks);
-        assertFalse(p.excludeLocalRoutes);
-        assertFalse(p.requiresInternetValidation);
-        assertFalse(p.automaticNattKeepaliveTimerEnabled);
-        assertFalse(p.automaticIpVersionSelectionEnabled);
-    }
-
-    private VpnProfile getSampleIkev2Profile(String key) {
-        final VpnProfile p = new VpnProfile(key, true /* isRestrictedToTestNetworks */,
-                false /* excludesLocalRoutes */, true /* requiresPlatformValidation */,
-                null /* ikeTunConnParams */, true /* mAutomaticNattKeepaliveTimerEnabled */,
-                true /* automaticIpVersionSelectionEnabled */);
-
-        p.name = "foo";
-        p.type = VpnProfile.TYPE_IKEV2_IPSEC_USER_PASS;
-        p.server = "bar";
-        p.username = "baz";
-        p.password = "qux";
-        p.dnsServers = "8.8.8.8";
-        p.searchDomains = "";
-        p.routes = "0.0.0.0/0";
-        p.mppe = false;
-        p.l2tpSecret = "";
-        p.ipsecIdentifier = "quux";
-        p.ipsecSecret = "quuz";
-        p.ipsecUserCert = "corge";
-        p.ipsecCaCert = "grault";
-        p.ipsecServerCert = "garply";
-        p.proxy = null;
-        p.setAllowedAlgorithms(
-                Arrays.asList(
-                        IpSecAlgorithm.AUTH_CRYPT_AES_GCM,
-                        IpSecAlgorithm.AUTH_CRYPT_CHACHA20_POLY1305,
-                        IpSecAlgorithm.AUTH_HMAC_SHA512,
-                        IpSecAlgorithm.CRYPT_AES_CBC));
-        p.isBypassable = true;
-        p.isMetered = true;
-        p.maxMtu = 1350;
-        p.areAuthParamsInline = true;
-
-        // Not saved, but also not compared.
-        p.saveLogin = true;
-
-        return p;
-    }
-
-    private VpnProfile getSampleIkev2ProfileWithIkeTunConnParams(String key) {
-        final VpnProfile p = new VpnProfile(key, true /* isRestrictedToTestNetworks */,
-                false /* excludesLocalRoutes */, true /* requiresPlatformValidation */,
-                new IkeTunnelConnectionParams(IKE_PARAMS_V4, CHILD_PARAMS),
-                true /* mAutomaticNattKeepaliveTimerEnabled */,
-                true /* automaticIpVersionSelectionEnabled */);
-
-        p.name = "foo";
-        p.server = "bar";
-        p.dnsServers = "8.8.8.8";
-        p.searchDomains = "";
-        p.routes = "0.0.0.0/0";
-        p.mppe = false;
-        p.proxy = null;
-        p.setAllowedAlgorithms(
-                Arrays.asList(
-                        IpSecAlgorithm.AUTH_CRYPT_AES_GCM,
-                        IpSecAlgorithm.AUTH_CRYPT_CHACHA20_POLY1305,
-                        IpSecAlgorithm.AUTH_HMAC_SHA512,
-                        IpSecAlgorithm.CRYPT_AES_CBC));
-        p.isBypassable = true;
-        p.isMetered = true;
-        p.maxMtu = 1350;
-        p.areAuthParamsInline = true;
-
-        // Not saved, but also not compared.
-        p.saveLogin = true;
-
-        return p;
-    }
-
-    @Test
-    public void testEquals() {
-        assertEquals(
-                getSampleIkev2Profile(DUMMY_PROFILE_KEY), getSampleIkev2Profile(DUMMY_PROFILE_KEY));
-
-        final VpnProfile modified = getSampleIkev2Profile(DUMMY_PROFILE_KEY);
-        modified.maxMtu--;
-        assertNotEquals(getSampleIkev2Profile(DUMMY_PROFILE_KEY), modified);
-    }
-
-    @Test
-    public void testParcelUnparcel() {
-        if (isAtLeastU()) {
-            // automaticNattKeepaliveTimerEnabled, automaticIpVersionSelectionEnabled added in U.
-            assertParcelSane(getSampleIkev2Profile(DUMMY_PROFILE_KEY), 28);
-            assertParcelSane(getSampleIkev2ProfileWithIkeTunConnParams(DUMMY_PROFILE_KEY), 28);
-        } else if (isAtLeastT()) {
-            // excludeLocalRoutes, requiresPlatformValidation were added in T.
-            assertParcelSane(getSampleIkev2Profile(DUMMY_PROFILE_KEY), 26);
-            assertParcelSane(getSampleIkev2ProfileWithIkeTunConnParams(DUMMY_PROFILE_KEY), 26);
-        } else {
-            assertParcelSane(getSampleIkev2Profile(DUMMY_PROFILE_KEY), 23);
-        }
-    }
-
-    @Test
-    public void testEncodeDecodeWithIkeTunConnParams() {
-        final VpnProfile profile = getSampleIkev2ProfileWithIkeTunConnParams(DUMMY_PROFILE_KEY);
-        final VpnProfile decoded = VpnProfile.decode(DUMMY_PROFILE_KEY, profile.encode());
-        assertEquals(profile, decoded);
-    }
-
-    @Test
-    public void testEncodeDecode() {
-        final VpnProfile profile = getSampleIkev2Profile(DUMMY_PROFILE_KEY);
-        final VpnProfile decoded = VpnProfile.decode(DUMMY_PROFILE_KEY, profile.encode());
-        assertEquals(profile, decoded);
-    }
-
-    @Test
-    public void testEncodeDecodeTooManyValues() {
-        final VpnProfile profile = getSampleIkev2Profile(DUMMY_PROFILE_KEY);
-        final byte[] tooManyValues =
-                (new String(profile.encode()) + VpnProfile.VALUE_DELIMITER + "invalid").getBytes();
-
-        assertNull(VpnProfile.decode(DUMMY_PROFILE_KEY, tooManyValues));
-    }
-
-    private String getEncodedDecodedIkev2ProfileMissingValues(int... missingIndices) {
-        // Sort to ensure when we remove, we can do it from greatest first.
-        Arrays.sort(missingIndices);
-
-        final String encoded = new String(getSampleIkev2Profile(DUMMY_PROFILE_KEY).encode());
-        final List<String> parts =
-                new ArrayList<>(Arrays.asList(encoded.split(VpnProfile.VALUE_DELIMITER)));
-
-        // Remove from back first to ensure indexing is consistent.
-        for (int i = missingIndices.length - 1; i >= 0; i--) {
-            parts.remove(missingIndices[i]);
-        }
-
-        return String.join(VpnProfile.VALUE_DELIMITER, parts.toArray(new String[0]));
-    }
-
-    @Test
-    public void testEncodeDecodeInvalidNumberOfValues() {
-        final String tooFewValues =
-                getEncodedDecodedIkev2ProfileMissingValues(
-                        ENCODED_INDEX_AUTH_PARAMS_INLINE,
-                        ENCODED_INDEX_RESTRICTED_TO_TEST_NETWORKS,
-                        ENCODED_INDEX_EXCLUDE_LOCAL_ROUTE,
-                        ENCODED_INDEX_REQUIRE_PLATFORM_VALIDATION,
-                        ENCODED_INDEX_IKE_TUN_CONN_PARAMS,
-                        ENCODED_INDEX_AUTOMATIC_NATT_KEEPALIVE_TIMER_ENABLED,
-                        ENCODED_INDEX_AUTOMATIC_IP_VERSION_SELECTION_ENABLED
-                        /* missingIndices */);
-
-        assertNull(VpnProfile.decode(DUMMY_PROFILE_KEY, tooFewValues.getBytes()));
-    }
-
-    private String getEncodedDecodedIkev2ProfileWithtooFewValues() {
-        return getEncodedDecodedIkev2ProfileMissingValues(
-                ENCODED_INDEX_RESTRICTED_TO_TEST_NETWORKS,
-                ENCODED_INDEX_EXCLUDE_LOCAL_ROUTE,
-                ENCODED_INDEX_REQUIRE_PLATFORM_VALIDATION,
-                ENCODED_INDEX_IKE_TUN_CONN_PARAMS,
-                ENCODED_INDEX_AUTOMATIC_NATT_KEEPALIVE_TIMER_ENABLED,
-                ENCODED_INDEX_AUTOMATIC_IP_VERSION_SELECTION_ENABLED /* missingIndices */);
-    }
-
-    @Test
-    public void testEncodeDecodeMissingIsRestrictedToTestNetworks() {
-        final String tooFewValues = getEncodedDecodedIkev2ProfileWithtooFewValues();
-
-        // Verify decoding without isRestrictedToTestNetworks defaults to false
-        final VpnProfile decoded = VpnProfile.decode(DUMMY_PROFILE_KEY, tooFewValues.getBytes());
-        assertFalse(decoded.isRestrictedToTestNetworks);
-    }
-
-    @Test
-    public void testEncodeDecodeMissingExcludeLocalRoutes() {
-        final String tooFewValues = getEncodedDecodedIkev2ProfileWithtooFewValues();
-
-        // Verify decoding without excludeLocalRoutes defaults to false
-        final VpnProfile decoded = VpnProfile.decode(DUMMY_PROFILE_KEY, tooFewValues.getBytes());
-        assertFalse(decoded.excludeLocalRoutes);
-    }
-
-    @Test
-    public void testEncodeDecodeMissingRequiresValidation() {
-        final String tooFewValues = getEncodedDecodedIkev2ProfileWithtooFewValues();
-
-        // Verify decoding without requiresValidation defaults to false
-        final VpnProfile decoded = VpnProfile.decode(DUMMY_PROFILE_KEY, tooFewValues.getBytes());
-        assertFalse(decoded.requiresInternetValidation);
-    }
-
-    @Test
-    public void testEncodeDecodeMissingAutomaticNattKeepaliveTimerEnabled() {
-        final String tooFewValues = getEncodedDecodedIkev2ProfileWithtooFewValues();
-
-        // Verify decoding without automaticNattKeepaliveTimerEnabled defaults to false
-        final VpnProfile decoded = VpnProfile.decode(DUMMY_PROFILE_KEY, tooFewValues.getBytes());
-        assertFalse(decoded.automaticNattKeepaliveTimerEnabled);
-    }
-
-    @Test
-    public void testEncodeDecodeMissingAutomaticIpVersionSelectionEnabled() {
-        final String tooFewValues = getEncodedDecodedIkev2ProfileWithtooFewValues();
-
-        // Verify decoding without automaticIpVersionSelectionEnabled defaults to false
-        final VpnProfile decoded = VpnProfile.decode(DUMMY_PROFILE_KEY, tooFewValues.getBytes());
-        assertFalse(decoded.automaticIpVersionSelectionEnabled);
-    }
-
-    @Test
-    public void testEncodeDecodeLoginsNotSaved() {
-        final VpnProfile profile = getSampleIkev2Profile(DUMMY_PROFILE_KEY);
-        profile.saveLogin = false;
-
-        final VpnProfile decoded = VpnProfile.decode(DUMMY_PROFILE_KEY, profile.encode());
-        assertNotEquals(profile, decoded);
-
-        // Add the username/password back, everything else must be equal.
-        decoded.username = profile.username;
-        decoded.password = profile.password;
-        assertEquals(profile, decoded);
-    }
-
-    @Test
-    public void testClone() {
-        final VpnProfile profile = getSampleIkev2Profile(DUMMY_PROFILE_KEY);
-        final VpnProfile clone = profile.clone();
-        assertEquals(profile, clone);
-        assertNotSame(profile, clone);
-    }
-}
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/BpfLoaderRcUtilsTest.kt b/tests/unit/java/com/android/server/BpfLoaderRcUtilsTest.kt
deleted file mode 100644
index 2cf6b17..0000000
--- a/tests/unit/java/com/android/server/BpfLoaderRcUtilsTest.kt
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Copyright (C) 2023 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
-
-import android.os.Build
-import androidx.test.filters.SmallTest
-import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
-import com.android.testutils.DevSdkIgnoreRunner
-import kotlin.test.assertEquals
-import kotlin.test.assertTrue
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@RunWith(DevSdkIgnoreRunner::class)
-@SmallTest
-@IgnoreUpTo(Build.VERSION_CODES.S)
-class BpfLoaderRcUtilsTest {
-    @Test
-    fun testLoadExistingBpfRcFile() {
-
-        val inputString = """
-            service a
-            # test comment
-            service bpfloader /system/bin/bpfloader
-                capabilities CHOWN SYS_ADMIN NET_ADMIN
-                group root graphics network_stack net_admin net_bw_acct net_bw_stats net_raw system
-                user root
-                rlimit memlock 1073741824 1073741824
-                oneshot
-                # comment 漢字
-                reboot_on_failure reboot,bpfloader-failed
-                updatable
-            
-            #test comment
-            on b 
-              oneshot 
-              # test comment
-        """.trimIndent()
-        val expectedResult = listOf(
-            "service bpfloader /system/bin/bpfloader",
-            "capabilities CHOWN SYS_ADMIN NET_ADMIN",
-            "group root graphics network_stack net_admin net_bw_acct net_bw_stats net_raw system",
-            "user root",
-            "rlimit memlock 1073741824 1073741824",
-            "oneshot",
-            "reboot_on_failure reboot,bpfloader-failed",
-            "updatable"
-        )
-
-        assertEquals(expectedResult,
-                BpfLoaderRcUtils.loadExistingBpfRcFile(inputString.byteInputStream()))
-    }
-
-    @Test
-    fun testCheckBpfRcFile() {
-        assertTrue(BpfLoaderRcUtils.checkBpfLoaderRc())
-    }
-}
diff --git a/tests/unit/java/com/android/server/ConnectivityServiceTest.java b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
index 7f821dd..8f768b2 100755
--- a/tests/unit/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
@@ -157,11 +157,11 @@
 import static android.telephony.DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH;
 import static android.telephony.DataConnectionRealTimeInfo.DC_POWER_STATE_LOW;
 
+import static com.android.server.ConnectivityService.ALLOW_SATALLITE_NETWORK_FALLBACK;
 import static com.android.server.ConnectivityService.DELAY_DESTROY_FROZEN_SOCKETS_VERSION;
 import static com.android.net.module.util.DeviceConfigUtils.TETHERING_MODULE_NAME;
 import static com.android.server.ConnectivityService.ALLOW_SYSUI_CONNECTIVITY_REPORTS;
 import static com.android.server.ConnectivityService.KEY_DESTROY_FROZEN_SOCKETS_VERSION;
-import static com.android.server.ConnectivityService.LOG_BPF_RC;
 import static com.android.server.ConnectivityService.MAX_NETWORK_REQUESTS_PER_SYSTEM_UID;
 import static com.android.server.ConnectivityService.PREFERENCE_ORDER_MOBILE_DATA_PREFERERRED;
 import static com.android.server.ConnectivityService.PREFERENCE_ORDER_OEM;
@@ -420,6 +420,7 @@
 import com.android.server.connectivity.NetworkNotificationManager.NotificationType;
 import com.android.server.connectivity.ProxyTracker;
 import com.android.server.connectivity.QosCallbackTracker;
+import com.android.server.connectivity.SatelliteAccessController;
 import com.android.server.connectivity.TcpKeepaliveController;
 import com.android.server.connectivity.UidRangeUtils;
 import com.android.server.connectivity.VpnProfileStore;
@@ -485,6 +486,7 @@
 import java.util.concurrent.TimeoutException;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.BiConsumer;
 import java.util.function.Consumer;
 import java.util.function.Predicate;
 import java.util.function.Supplier;
@@ -523,7 +525,7 @@
     // between a LOST callback that arrives immediately and a LOST callback that arrives after
     // the linger/nascent timeout. For this, our assertions should run fast enough to leave
     // less than (mService.mLingerDelayMs - TEST_CALLBACK_TIMEOUT_MS) between the time callbacks are
-    // supposedly fired, and the time we call expectCallback.
+    // supposedly fired, and the time we call expectCapChanged.
     private static final int TEST_CALLBACK_TIMEOUT_MS = 250;
     // Chosen to be less than TEST_CALLBACK_TIMEOUT_MS. This ensures that requests have time to
     // complete before callbacks are verified.
@@ -562,6 +564,7 @@
     private static final int TEST_PACKAGE_UID2 = 321;
     private static final int TEST_PACKAGE_UID3 = 456;
     private static final int NETWORK_ACTIVITY_NO_UID = -1;
+    private static final int TEST_SUBSCRIPTION_ID = 1;
 
     private static final int PACKET_WAKEUP_MARK_MASK = 0x80000000;
 
@@ -641,6 +644,7 @@
     @Mock DestroySocketsWrapper mDestroySocketsWrapper;
     @Mock SubscriptionManager mSubscriptionManager;
     @Mock KeepaliveTracker.Dependencies mMockKeepaliveTrackerDependencies;
+    @Mock SatelliteAccessController mSatelliteAccessController;
 
     // BatteryStatsManager is final and cannot be mocked with regular mockito, so just mock the
     // underlying binder calls.
@@ -2053,11 +2057,21 @@
         @Override
         public CarrierPrivilegeAuthenticator makeCarrierPrivilegeAuthenticator(
                 @NonNull final Context context,
-                @NonNull final TelephonyManager tm) {
+                @NonNull final TelephonyManager tm,
+                final boolean requestRestrictedWifiEnabled,
+                BiConsumer<Integer, Integer> listener) {
             return mDeps.isAtLeastT() ? mCarrierPrivilegeAuthenticator : null;
         }
 
         @Override
+        public SatelliteAccessController makeSatelliteAccessController(
+                @NonNull final Context context,
+                Consumer<Set<Integer>> updateSatelliteNetworkFallbackUidCallback,
+                @NonNull final Handler connectivityServiceInternalHandler) {
+            return mSatelliteAccessController;
+        }
+
+        @Override
         public boolean intentFilterEquals(final PendingIntent a, final PendingIntent b) {
             return runAsShell(GET_INTENT_SENDER_INTENT, () -> a.intentFilterEquals(b));
         }
@@ -2147,6 +2161,8 @@
                 case ConnectivityFlags.NO_REMATCH_ALL_REQUESTS_ON_REGISTER:
                 case ConnectivityFlags.CARRIER_SERVICE_CHANGED_USE_CALLBACK:
                     return true;
+                case ConnectivityFlags.REQUEST_RESTRICTED_WIFI:
+                    return true;
                 case KEY_DESTROY_FROZEN_SOCKETS_VERSION:
                     return true;
                 case DELAY_DESTROY_FROZEN_SOCKETS_VERSION:
@@ -2161,7 +2177,7 @@
             switch (name) {
                 case ALLOW_SYSUI_CONNECTIVITY_REPORTS:
                     return true;
-                case LOG_BPF_RC:
+                case ALLOW_SATALLITE_NETWORK_FALLBACK:
                     return true;
                 default:
                     return super.isFeatureNotChickenedOut(context, name);
@@ -11468,7 +11484,7 @@
         doTestInterfaceClassActivityChanged(TRANSPORT_CELLULAR);
     }
 
-    private void doTestOnNetworkActive_NewNetworkConnects(int transportType, boolean expectCallback)
+    private void doTestOnNetworkActive_NewNetworkConnects(int transportType, boolean expectCapChanged)
             throws Exception {
         final ConditionVariable onNetworkActiveCv = new ConditionVariable();
         final ConnectivityManager.OnNetworkActiveListener listener = onNetworkActiveCv::open;
@@ -11480,7 +11496,7 @@
         testAndCleanup(() -> {
             mCm.addDefaultNetworkActiveListener(listener);
             agent.connect(true);
-            if (expectCallback) {
+            if (expectCapChanged) {
                 assertTrue(onNetworkActiveCv.block(TEST_CALLBACK_TIMEOUT_MS));
             } else {
                 assertFalse(onNetworkActiveCv.block(TEST_CALLBACK_TIMEOUT_MS));
@@ -11495,7 +11511,7 @@
 
     @Test
     public void testOnNetworkActive_NewCellConnects_CallbackCalled() throws Exception {
-        doTestOnNetworkActive_NewNetworkConnects(TRANSPORT_CELLULAR, true /* expectCallback */);
+        doTestOnNetworkActive_NewNetworkConnects(TRANSPORT_CELLULAR, true /* expectCapChanged */);
     }
 
     @Test
@@ -11504,8 +11520,8 @@
         // networks that tracker adds the idle timer to. And the tracker does not set the idle timer
         // for the ethernet network.
         // So onNetworkActive is not called when the ethernet becomes the default network
-        final boolean expectCallback = mDeps.isAtLeastV();
-        doTestOnNetworkActive_NewNetworkConnects(TRANSPORT_ETHERNET, expectCallback);
+        final boolean expectCapChanged = mDeps.isAtLeastV();
+        doTestOnNetworkActive_NewNetworkConnects(TRANSPORT_ETHERNET, expectCapChanged);
     }
 
     @Test
@@ -12929,7 +12945,7 @@
         mServiceContext.setPermission(NETWORK_STACK, PERMISSION_GRANTED);
         assertTrue(
                 "NetworkStack permission not applied",
-                mService.checkConnectivityDiagnosticsPermissions(
+                mService.hasConnectivityDiagnosticsPermissions(
                         Process.myPid(), Process.myUid(), naiWithoutUid,
                         mContext.getOpPackageName()));
     }
@@ -12941,7 +12957,7 @@
         mServiceContext.setPermission(STATUS_BAR_SERVICE, PERMISSION_GRANTED);
         assertTrue(
                 "SysUi permission (STATUS_BAR_SERVICE) not applied",
-                mService.checkConnectivityDiagnosticsPermissions(
+                mService.hasConnectivityDiagnosticsPermissions(
                         Process.myPid(), Process.myUid(), naiWithoutUid,
                         mContext.getOpPackageName()));
     }
@@ -12958,7 +12974,7 @@
 
         assertFalse(
                 "Mismatched uid/package name should not pass the location permission check",
-                mService.checkConnectivityDiagnosticsPermissions(
+                mService.hasConnectivityDiagnosticsPermissions(
                         Process.myPid() + 1, wrongUid, naiWithUid, mContext.getOpPackageName()));
     }
 
@@ -12969,7 +12985,7 @@
         assertEquals(
                 "Unexpected ConnDiags permission",
                 expectPermission,
-                mService.checkConnectivityDiagnosticsPermissions(
+                mService.hasConnectivityDiagnosticsPermissions(
                         Process.myPid(), Process.myUid(), info, mContext.getOpPackageName()));
     }
 
@@ -13011,7 +13027,7 @@
         waitForIdle();
         assertTrue(
                 "Active VPN permission not applied",
-                mService.checkConnectivityDiagnosticsPermissions(
+                mService.hasConnectivityDiagnosticsPermissions(
                         Process.myPid(), Process.myUid(), naiWithoutUid,
                         mContext.getOpPackageName()));
 
@@ -13019,7 +13035,7 @@
         waitForIdle();
         assertFalse(
                 "VPN shouldn't receive callback on non-underlying network",
-                mService.checkConnectivityDiagnosticsPermissions(
+                mService.hasConnectivityDiagnosticsPermissions(
                         Process.myPid(), Process.myUid(), naiWithoutUid,
                         mContext.getOpPackageName()));
     }
@@ -13036,7 +13052,7 @@
 
         assertTrue(
                 "NetworkCapabilities administrator uid permission not applied",
-                mService.checkConnectivityDiagnosticsPermissions(
+                mService.hasConnectivityDiagnosticsPermissions(
                         Process.myPid(), Process.myUid(), naiWithUid, mContext.getOpPackageName()));
     }
 
@@ -13054,7 +13070,7 @@
         // Use wrong pid and uid
         assertFalse(
                 "Permissions allowed when they shouldn't be granted",
-                mService.checkConnectivityDiagnosticsPermissions(
+                mService.hasConnectivityDiagnosticsPermissions(
                         Process.myPid() + 1, Process.myUid() + 1, naiWithUid,
                         mContext.getOpPackageName()));
     }
@@ -17353,6 +17369,14 @@
                 .build();
     }
 
+    private NetworkRequest getRestrictedRequestForWifiWithSubIds() {
+        return new NetworkRequest.Builder()
+            .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
+            .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
+            .setSubscriptionIds(Collections.singleton(TEST_SUBSCRIPTION_ID))
+            .build();
+    }
+
     @Test
     public void testNetworkRequestWithSubIdsWithNetworkFactoryPermission() throws Exception {
         mServiceContext.setPermission(NETWORK_FACTORY, PERMISSION_GRANTED);
@@ -17386,6 +17410,141 @@
     }
 
     @Test
+    @IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
+    public void testCarrierConfigAppSendNetworkRequestForRestrictedWifi() throws Exception {
+        mServiceContext.setPermission(CONNECTIVITY_USE_RESTRICTED_NETWORKS, PERMISSION_DENIED);
+        doReturn(true).when(mCarrierPrivilegeAuthenticator)
+                .isCarrierServiceUidForNetworkCapabilities(anyInt(), any());
+        final PendingIntent pendingIntent = PendingIntent.getBroadcast(
+                mContext, 0 /* requestCode */, new Intent("a"), FLAG_IMMUTABLE);
+        final NetworkCallback networkCallback1 = new NetworkCallback();
+        final NetworkCallback networkCallback2 = new NetworkCallback();
+
+        mCm.requestNetwork(
+                getRestrictedRequestForWifiWithSubIds(), networkCallback1);
+        mCm.requestNetwork(
+                getRestrictedRequestForWifiWithSubIds(), pendingIntent);
+        mCm.registerNetworkCallback(
+                getRestrictedRequestForWifiWithSubIds(), networkCallback2);
+
+        mCm.unregisterNetworkCallback(networkCallback1);
+        mCm.releaseNetworkRequest(pendingIntent);
+        mCm.unregisterNetworkCallback(networkCallback2);
+    }
+
+    private void doTestNetworkRequestWithCarrierPrivilegesLost(
+            boolean shouldGrantRestrictedNetworkPermission,
+            int lostPrivilegeUid,
+            int lostPrivilegeSubId,
+            boolean expectUnavailable,
+            boolean expectCapChanged) throws Exception {
+        if (shouldGrantRestrictedNetworkPermission) {
+            mServiceContext.setPermission(CONNECTIVITY_USE_RESTRICTED_NETWORKS, PERMISSION_GRANTED);
+        } else {
+            mServiceContext.setPermission(CONNECTIVITY_USE_RESTRICTED_NETWORKS, PERMISSION_DENIED);
+        }
+
+        NetworkCapabilities filter =
+                getRestrictedRequestForWifiWithSubIds().networkCapabilities;
+        final HandlerThread handlerThread = new HandlerThread("testRestrictedFactoryRequests");
+        handlerThread.start();
+
+        final MockNetworkFactory testFactory = new MockNetworkFactory(handlerThread.getLooper(),
+                mServiceContext, "testFactory", filter, mCsHandlerThread);
+        testFactory.register();
+        testFactory.assertRequestCountEquals(0);
+
+        doReturn(true).when(mCarrierPrivilegeAuthenticator)
+                .isCarrierServiceUidForNetworkCapabilities(eq(Process.myUid()), any());
+        final TestNetworkCallback networkCallback = new TestNetworkCallback();
+        final NetworkRequest networkrequest =
+                getRestrictedRequestForWifiWithSubIds();
+        mCm.requestNetwork(networkrequest, networkCallback);
+        testFactory.expectRequestAdd();
+        testFactory.assertRequestCountEquals(1);
+
+        NetworkCapabilities nc = new NetworkCapabilities.Builder(filter)
+                .setAllowedUids(Set.of(Process.myUid()))
+                .build();
+        mWiFiAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI, new LinkProperties(), nc);
+        mWiFiAgent.connect(false);
+        networkCallback.expectAvailableCallbacksUnvalidated(mWiFiAgent);
+        final NetworkAgentInfo nai = mService.getNetworkAgentInfoForNetwork(
+                mWiFiAgent.getNetwork());
+
+        doReturn(false).when(mCarrierPrivilegeAuthenticator)
+                .isCarrierServiceUidForNetworkCapabilities(eq(Process.myUid()), any());
+        doReturn(TEST_SUBSCRIPTION_ID).when(mCarrierPrivilegeAuthenticator)
+                .getSubIdFromNetworkCapabilities(any());
+        mService.onCarrierPrivilegesLost(lostPrivilegeUid, lostPrivilegeSubId);
+        waitForIdle();
+
+        if (expectCapChanged) {
+            networkCallback.expect(NETWORK_CAPS_UPDATED);
+        }
+        if (expectUnavailable) {
+            networkCallback.expect(UNAVAILABLE);
+        }
+        if (!expectCapChanged && !expectUnavailable) {
+            networkCallback.assertNoCallback();
+        }
+
+        mWiFiAgent.disconnect();
+        waitForIdle();
+
+        if (expectUnavailable) {
+            testFactory.assertRequestCountEquals(0);
+        } else {
+            testFactory.assertRequestCountEquals(1);
+        }
+
+        handlerThread.quitSafely();
+        handlerThread.join();
+    }
+
+    @Test
+    @IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
+    public void testRestrictedRequestRemovedDueToCarrierPrivilegesLost() throws Exception {
+        doTestNetworkRequestWithCarrierPrivilegesLost(
+                false /* shouldGrantRestrictedNetworkPermission */,
+                Process.myUid(),
+                TEST_SUBSCRIPTION_ID,
+                true /* expectUnavailable */,
+                true /* expectCapChanged */);
+    }
+
+    @Test
+    @IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
+    public void testRequestNotRemoved_MismatchSubId() throws Exception {
+        doTestNetworkRequestWithCarrierPrivilegesLost(
+                false /* shouldGrantRestrictedNetworkPermission */,
+                Process.myUid(),
+                TEST_SUBSCRIPTION_ID + 1,
+                false /* expectUnavailable */,
+                false /* expectCapChanged */);
+    }
+    @Test
+    @IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
+    public void testRequestNotRemoved_MismatchUid() throws Exception {
+        doTestNetworkRequestWithCarrierPrivilegesLost(
+                false /* shouldGrantRestrictedNetworkPermission */,
+                Process.myUid() + 1,
+                TEST_SUBSCRIPTION_ID,
+                false /* expectUnavailable */,
+                false /* expectCapChanged */);
+    }
+
+    @Test
+    @IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
+    public void testRequestNotRemoved_HasRestrictedNetworkPermission() throws Exception {
+        doTestNetworkRequestWithCarrierPrivilegesLost(
+                true /* shouldGrantRestrictedNetworkPermission */,
+                Process.myUid(),
+                TEST_SUBSCRIPTION_ID,
+                false /* expectUnavailable */,
+                true /* expectCapChanged */);
+    }
+    @Test
     public void testAllowedUids() throws Exception {
         final int preferenceOrder =
                 ConnectivityService.PREFERENCE_ORDER_IRRELEVANT_BECAUSE_NOT_DEFAULT;
diff --git a/tests/unit/java/com/android/server/NsdServiceTest.java b/tests/unit/java/com/android/server/NsdServiceTest.java
index 87e7967..881de56 100644
--- a/tests/unit/java/com/android/server/NsdServiceTest.java
+++ b/tests/unit/java/com/android/server/NsdServiceTest.java
@@ -34,6 +34,7 @@
 import static android.net.connectivity.ConnectivityCompatChanges.RUN_NATIVE_NSD_ONLY_IF_LEGACY_APPS_T_AND_LATER;
 import static android.net.nsd.NsdManager.FAILURE_BAD_PARAMETERS;
 import static android.net.nsd.NsdManager.FAILURE_INTERNAL_ERROR;
+import static android.net.nsd.NsdManager.FAILURE_MAX_LIMIT;
 import static android.net.nsd.NsdManager.FAILURE_OPERATION_NOT_RUNNING;
 
 import static com.android.networkstack.apishim.api33.ConstantsShim.REGISTER_NSD_OFFLOAD_ENGINE;
@@ -82,6 +83,7 @@
 import android.net.mdns.aidl.IMDnsEventListener;
 import android.net.mdns.aidl.RegistrationInfo;
 import android.net.mdns.aidl.ResolutionInfo;
+import android.net.nsd.AdvertisingRequest;
 import android.net.nsd.INsdManagerCallback;
 import android.net.nsd.INsdServiceConnector;
 import android.net.nsd.MDnsManager;
@@ -100,6 +102,7 @@
 import android.os.IBinder;
 import android.os.Looper;
 import android.os.Message;
+import android.os.Process;
 import android.os.RemoteException;
 import android.util.Pair;
 
@@ -109,6 +112,7 @@
 import com.android.metrics.NetworkNsdReportedMetrics;
 import com.android.server.NsdService.Dependencies;
 import com.android.server.connectivity.mdns.MdnsAdvertiser;
+import com.android.server.connectivity.mdns.MdnsAdvertisingOptions;
 import com.android.server.connectivity.mdns.MdnsDiscoveryManager;
 import com.android.server.connectivity.mdns.MdnsInterfaceSocket;
 import com.android.server.connectivity.mdns.MdnsSearchOptions;
@@ -131,10 +135,14 @@
 import org.mockito.ArgumentCaptor;
 import org.mockito.InOrder;
 import org.mockito.Mock;
+import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
 
 import java.net.InetAddress;
 import java.net.UnknownHostException;
+import java.time.Duration;
+import java.time.Instant;
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.LinkedList;
 import java.util.List;
@@ -234,7 +242,8 @@
         doReturn(mSocketProvider).when(mDeps).makeMdnsSocketProvider(any(), any(), any(), any());
         doReturn(DEFAULT_RUNNING_APP_ACTIVE_IMPORTANCE_CUTOFF).when(mDeps).getDeviceConfigInt(
                 eq(NsdService.MDNS_CONFIG_RUNNING_APP_ACTIVE_IMPORTANCE_CUTOFF), anyInt());
-        doReturn(mAdvertiser).when(mDeps).makeMdnsAdvertiser(any(), any(), any(), any(), any());
+        doReturn(mAdvertiser).when(mDeps).makeMdnsAdvertiser(any(), any(), any(), any(), any(),
+                any());
         doReturn(mMetrics).when(mDeps).makeNetworkNsdReportedMetrics(anyInt());
         doReturn(mClock).when(mDeps).makeClock();
         doReturn(TEST_TIME_MS).when(mClock).elapsedRealtime();
@@ -256,6 +265,10 @@
             mThread.quitSafely();
             mThread.join();
         }
+
+        // Clear inline mocks as there are possible memory leaks if not done (see mockito
+        // doc for clearInlineMocks), and some tests create many of them.
+        Mockito.framework().clearInlineMocks();
     }
 
     // Native mdns provided by Netd is removed after U.
@@ -716,6 +729,86 @@
                 true /* isLegacy */, getAddrId, 10L /* durationMs */);
     }
 
+    @EnableCompatChanges(ENABLE_PLATFORM_MDNS_BACKEND)
+    @Test
+    public void testPerClientListenerLimit() throws Exception {
+        final NsdManager client1 = connectClient(mService);
+        final NsdManager client2 = connectClient(mService);
+
+        final String testType1 = "_testtype1._tcp";
+        final NsdServiceInfo testServiceInfo1 = new NsdServiceInfo("MyTestService1", testType1);
+        testServiceInfo1.setPort(12345);
+        final String testType2 = "_testtype2._tcp";
+        final NsdServiceInfo testServiceInfo2 = new NsdServiceInfo("MyTestService2", testType2);
+        testServiceInfo2.setPort(12345);
+
+        // Each client can register 200 requests (for example 100 discover and 100 register).
+        final int numEachListener = 100;
+        final ArrayList<DiscoveryListener> discListeners = new ArrayList<>(numEachListener);
+        final ArrayList<RegistrationListener> regListeners = new ArrayList<>(numEachListener);
+        for (int i = 0; i < numEachListener; i++) {
+            final DiscoveryListener discListener1 = mock(DiscoveryListener.class);
+            discListeners.add(discListener1);
+            final RegistrationListener regListener1 = mock(RegistrationListener.class);
+            regListeners.add(regListener1);
+            final DiscoveryListener discListener2 = mock(DiscoveryListener.class);
+            discListeners.add(discListener2);
+            final RegistrationListener regListener2 = mock(RegistrationListener.class);
+            regListeners.add(regListener2);
+            client1.discoverServices(testType1, NsdManager.PROTOCOL_DNS_SD,
+                    (Network) null, Runnable::run, discListener1);
+            client1.registerService(testServiceInfo1, NsdManager.PROTOCOL_DNS_SD, Runnable::run,
+                    regListener1);
+
+            client2.registerService(testServiceInfo2, NsdManager.PROTOCOL_DNS_SD, Runnable::run,
+                    regListener2);
+            client2.discoverServices(testType2, NsdManager.PROTOCOL_DNS_SD,
+                    (Network) null, Runnable::run, discListener2);
+        }
+
+        // Use a longer timeout than usual for the handler to process all the events. The
+        // registrations take about 1s on a high-end 2013 device.
+        HandlerUtils.waitForIdle(mHandler, 30_000L);
+        for (int i = 0; i < discListeners.size(); i++) {
+            // Callbacks are sent on the manager handler which is different from mHandler, so use
+            // a short timeout (each callback should come quickly after the previous one).
+            verify(discListeners.get(i), timeout(TEST_TIME_MS))
+                    .onDiscoveryStarted(i % 2 == 0 ? testType1 : testType2);
+
+            // registerService does not get a callback before probing finishes (will not happen as
+            // this is mocked)
+            verifyNoMoreInteractions(regListeners.get(i));
+        }
+
+        // The next registrations should fail
+        final DiscoveryListener failDiscListener1 = mock(DiscoveryListener.class);
+        final RegistrationListener failRegListener1 = mock(RegistrationListener.class);
+        final DiscoveryListener failDiscListener2 = mock(DiscoveryListener.class);
+        final RegistrationListener failRegListener2 = mock(RegistrationListener.class);
+
+        client1.discoverServices(testType1, NsdManager.PROTOCOL_DNS_SD,
+                (Network) null, Runnable::run, failDiscListener1);
+        verify(failDiscListener1, timeout(TEST_TIME_MS))
+                .onStartDiscoveryFailed(testType1, FAILURE_MAX_LIMIT);
+
+        client1.registerService(testServiceInfo1, NsdManager.PROTOCOL_DNS_SD, Runnable::run,
+                failRegListener1);
+        verify(failRegListener1, timeout(TEST_TIME_MS)).onRegistrationFailed(
+                argThat(a -> testServiceInfo1.getServiceName().equals(a.getServiceName())),
+                eq(FAILURE_MAX_LIMIT));
+
+        client1.discoverServices(testType2, NsdManager.PROTOCOL_DNS_SD,
+                (Network) null, Runnable::run, failDiscListener2);
+        verify(failDiscListener2, timeout(TEST_TIME_MS))
+                .onStartDiscoveryFailed(testType2, FAILURE_MAX_LIMIT);
+
+        client1.registerService(testServiceInfo2, NsdManager.PROTOCOL_DNS_SD, Runnable::run,
+                failRegListener2);
+        verify(failRegListener2, timeout(TEST_TIME_MS)).onRegistrationFailed(
+                argThat(a -> testServiceInfo2.getServiceName().equals(a.getServiceName())),
+                eq(FAILURE_MAX_LIMIT));
+    }
+
     @Test
     @DisableCompatChanges(ENABLE_PLATFORM_MDNS_BACKEND)
     @DevSdkIgnoreRule.IgnoreAfter(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
@@ -883,7 +976,8 @@
                 List.of() /* textStrings */,
                 List.of() /* textEntries */,
                 1234,
-                network);
+                network,
+                Instant.MAX /* expirationTime */);
 
         // Callbacks for query sent.
         listener.onDiscoveryQuerySent(Collections.emptyList(), 1 /* transactionId */);
@@ -913,7 +1007,8 @@
                 List.of() /* textStrings */,
                 List.of() /* textEntries */,
                 1234,
-                network);
+                network,
+                Instant.MAX /* expirationTime */);
 
         // Verify onServiceUpdated callback.
         listener.onServiceUpdated(updatedServiceInfo);
@@ -1045,7 +1140,8 @@
                 List.of(), /* textStrings */
                 List.of(), /* textEntries */
                 1234, /* interfaceIndex */
-                network);
+                network,
+                Instant.MAX /* expirationTime */);
 
         // Verify onServiceNameDiscovered callback
         listener.onServiceNameDiscovered(foundInfo, false /* isServiceFromCache */);
@@ -1066,7 +1162,8 @@
                 null, /* textStrings */
                 null, /* textEntries */
                 1234, /* interfaceIndex */
-                network);
+                network,
+                Instant.MAX /* expirationTime */);
         // Verify onServiceNameRemoved callback
         listener.onServiceNameRemoved(removedInfo);
         verify(discListener, timeout(TIMEOUT_MS)).onServiceLost(argThat(info ->
@@ -1138,7 +1235,7 @@
         verify(mAdvertiser).addOrUpdateService(anyInt(), argThat(s ->
                 "Instance".equals(s.getServiceName())
                         && SERVICE_TYPE.equals(s.getServiceType())
-                        && s.getSubtypes().equals(Set.of("_subtype"))), any());
+                        && s.getSubtypes().equals(Set.of("_subtype"))), any(), anyInt());
 
         final DiscoveryListener discListener = mock(DiscoveryListener.class);
         client.discoverServices(typeWithSubtype, PROTOCOL, network, Runnable::run, discListener);
@@ -1188,7 +1285,8 @@
                 List.of(MdnsServiceInfo.TextEntry.fromBytes(new byte[]{
                         'k', 'e', 'y', '=', (byte) 0xFF, (byte) 0xFE})) /* textEntries */,
                 1234,
-                network);
+                network,
+                Instant.ofEpochSecond(1000_000L) /* expirationTime */);
 
         // Verify onServiceFound callback
         doReturn(TEST_TIME_MS + 10L).when(mClock).elapsedRealtime();
@@ -1213,6 +1311,7 @@
         assertTrue(info.getHostAddresses().stream().anyMatch(
                 address -> address.equals(parseNumericAddress("2001:db8::2"))));
         assertEquals(network, info.getNetwork());
+        assertEquals(Instant.ofEpochSecond(1000_000L), info.getExpirationTime());
 
         // Verify the listener has been unregistered.
         verify(mDiscoveryManager, timeout(TIMEOUT_MS))
@@ -1245,7 +1344,7 @@
 
         final ArgumentCaptor<Integer> serviceIdCaptor = ArgumentCaptor.forClass(Integer.class);
         verify(mAdvertiser).addOrUpdateService(serviceIdCaptor.capture(),
-                argThat(info -> matches(info, regInfo)), any());
+                argThat(info -> matches(info, regInfo)), any(), anyInt());
 
         client.unregisterService(regListenerWithoutFeature);
         waitForIdle();
@@ -1274,7 +1373,7 @@
         service1.setHostAddresses(List.of(parseNumericAddress("2001:db8::123")));
         service1.setPort(1234);
         final NsdServiceInfo service2 = new NsdServiceInfo(SERVICE_NAME, "_type2._tcp");
-        service2.setHostAddresses(List.of(parseNumericAddress("2001:db8::123")));
+        service1.setHostAddresses(List.of(parseNumericAddress("2001:db8::123")));
         service2.setPort(1234);
 
         client.discoverServices(service1.getServiceType(),
@@ -1306,9 +1405,9 @@
 
         // The advertiser is enabled for _type2 but not _type1
         verify(mAdvertiser, never()).addOrUpdateService(anyInt(),
-                argThat(info -> matches(info, service1)), any());
+                argThat(info -> matches(info, service1)), any(), anyInt());
         verify(mAdvertiser).addOrUpdateService(anyInt(), argThat(info -> matches(info, service2)),
-                any());
+                any(), anyInt());
     }
 
     @Test
@@ -1320,7 +1419,7 @@
         // final String serviceTypeWithLocalDomain = SERVICE_TYPE + ".local";
         final ArgumentCaptor<MdnsAdvertiser.AdvertiserCallback> cbCaptor =
                 ArgumentCaptor.forClass(MdnsAdvertiser.AdvertiserCallback.class);
-        verify(mDeps).makeMdnsAdvertiser(any(), any(), cbCaptor.capture(), any(), any());
+        verify(mDeps).makeMdnsAdvertiser(any(), any(), cbCaptor.capture(), any(), any(), any());
 
         final NsdServiceInfo regInfo = new NsdServiceInfo(SERVICE_NAME, SERVICE_TYPE);
         regInfo.setHost(parseNumericAddress("192.0.2.123"));
@@ -1333,7 +1432,7 @@
         verify(mSocketProvider).startMonitoringSockets();
         final ArgumentCaptor<Integer> idCaptor = ArgumentCaptor.forClass(Integer.class);
         verify(mAdvertiser).addOrUpdateService(idCaptor.capture(), argThat(info ->
-                matches(info, regInfo)), any());
+                matches(info, regInfo)), any(), anyInt());
 
         // Verify onServiceRegistered callback
         final MdnsAdvertiser.AdvertiserCallback cb = cbCaptor.getValue();
@@ -1371,7 +1470,7 @@
         // final String serviceTypeWithLocalDomain = SERVICE_TYPE + ".local";
         final ArgumentCaptor<MdnsAdvertiser.AdvertiserCallback> cbCaptor =
                 ArgumentCaptor.forClass(MdnsAdvertiser.AdvertiserCallback.class);
-        verify(mDeps).makeMdnsAdvertiser(any(), any(), cbCaptor.capture(), any(), any());
+        verify(mDeps).makeMdnsAdvertiser(any(), any(), cbCaptor.capture(), any(), any(), any());
 
         final NsdServiceInfo regInfo = new NsdServiceInfo(SERVICE_NAME, "invalid_type");
         regInfo.setHost(parseNumericAddress("192.0.2.123"));
@@ -1381,7 +1480,7 @@
 
         client.registerService(regInfo, NsdManager.PROTOCOL_DNS_SD, Runnable::run, regListener);
         waitForIdle();
-        verify(mAdvertiser, never()).addOrUpdateService(anyInt(), any(), any());
+        verify(mAdvertiser, never()).addOrUpdateService(anyInt(), any(), any(), anyInt());
 
         verify(regListener, timeout(TIMEOUT_MS)).onRegistrationFailed(
                 argThat(info -> matches(info, regInfo)), eq(FAILURE_INTERNAL_ERROR));
@@ -1398,7 +1497,7 @@
         // final String serviceTypeWithLocalDomain = SERVICE_TYPE + ".local";
         final ArgumentCaptor<MdnsAdvertiser.AdvertiserCallback> cbCaptor =
                 ArgumentCaptor.forClass(MdnsAdvertiser.AdvertiserCallback.class);
-        verify(mDeps).makeMdnsAdvertiser(any(), any(), cbCaptor.capture(), any(), any());
+        verify(mDeps).makeMdnsAdvertiser(any(), any(), cbCaptor.capture(), any(), any(), any());
 
         final NsdServiceInfo regInfo = new NsdServiceInfo("a".repeat(70), SERVICE_TYPE);
         regInfo.setHost(parseNumericAddress("192.0.2.123"));
@@ -1410,8 +1509,12 @@
         waitForIdle();
         final ArgumentCaptor<Integer> idCaptor = ArgumentCaptor.forClass(Integer.class);
         // Service name is truncated to 63 characters
-        verify(mAdvertiser).addOrUpdateService(idCaptor.capture(),
-                argThat(info -> info.getServiceName().equals("a".repeat(63))), any());
+        verify(mAdvertiser)
+                .addOrUpdateService(
+                        idCaptor.capture(),
+                        argThat(info -> info.getServiceName().equals("a".repeat(63))),
+                        any(),
+                        anyInt());
 
         // Verify onServiceRegistered callback
         final MdnsAdvertiser.AdvertiserCallback cb = cbCaptor.getValue();
@@ -1426,6 +1529,82 @@
     }
 
     @Test
+    public void testAdvertiseCustomTtl_validTtl_success() {
+        runValidTtlAdvertisingTest(30L);
+        runValidTtlAdvertisingTest(10 * 3600L);
+    }
+
+    @Test
+    public void testAdvertiseCustomTtl_ttlSmallerThan30SecondsButClientIsSystemServer_success() {
+        when(mDeps.getCallingUid()).thenReturn(Process.SYSTEM_UID);
+
+        runValidTtlAdvertisingTest(29L);
+    }
+
+    @Test
+    public void testAdvertiseCustomTtl_ttlLargerThan10HoursButClientIsSystemServer_success() {
+        when(mDeps.getCallingUid()).thenReturn(Process.SYSTEM_UID);
+
+        runValidTtlAdvertisingTest(10 * 3600L + 1);
+        runValidTtlAdvertisingTest(0xffffffffL);
+    }
+
+    private void runValidTtlAdvertisingTest(long validTtlSeconds) {
+        setMdnsAdvertiserEnabled();
+
+        final NsdManager client = connectClient(mService);
+        final RegistrationListener regListener = mock(RegistrationListener.class);
+        final ArgumentCaptor<MdnsAdvertiser.AdvertiserCallback> cbCaptor =
+                ArgumentCaptor.forClass(MdnsAdvertiser.AdvertiserCallback.class);
+        verify(mDeps).makeMdnsAdvertiser(any(), any(), cbCaptor.capture(), any(), any(), any());
+
+        final NsdServiceInfo regInfo = new NsdServiceInfo("Service custom TTL", SERVICE_TYPE);
+        regInfo.setPort(1234);
+        final AdvertisingRequest request =
+                new AdvertisingRequest.Builder(regInfo, NsdManager.PROTOCOL_DNS_SD)
+                    .setTtl(Duration.ofSeconds(validTtlSeconds)).build();
+
+        client.registerService(request, Runnable::run, regListener);
+        waitForIdle();
+
+        final ArgumentCaptor<Integer> idCaptor = ArgumentCaptor.forClass(Integer.class);
+        final MdnsAdvertisingOptions expectedAdverstingOptions =
+                MdnsAdvertisingOptions.newBuilder().setTtl(request.getTtl()).build();
+        verify(mAdvertiser).addOrUpdateService(idCaptor.capture(), any(),
+                eq(expectedAdverstingOptions), anyInt());
+
+        // Verify onServiceRegistered callback
+        final MdnsAdvertiser.AdvertiserCallback cb = cbCaptor.getValue();
+        final int regId = idCaptor.getValue();
+        cb.onRegisterServiceSucceeded(regId, regInfo);
+
+        verify(regListener, timeout(TIMEOUT_MS)).onServiceRegistered(
+                argThat(info -> matches(info, new NsdServiceInfo(regInfo.getServiceName(), null))));
+    }
+
+    @Test
+    public void testAdvertiseCustomTtl_invalidTtl_FailsWithBadParameters() {
+        setMdnsAdvertiserEnabled();
+        final long invalidTtlSeconds = 29L;
+        final NsdManager client = connectClient(mService);
+        final RegistrationListener regListener = mock(RegistrationListener.class);
+        final ArgumentCaptor<MdnsAdvertiser.AdvertiserCallback> cbCaptor =
+                ArgumentCaptor.forClass(MdnsAdvertiser.AdvertiserCallback.class);
+        verify(mDeps).makeMdnsAdvertiser(any(), any(), cbCaptor.capture(), any(), any(), any());
+
+        final NsdServiceInfo regInfo = new NsdServiceInfo("Service custom TTL", SERVICE_TYPE);
+        regInfo.setPort(1234);
+        final AdvertisingRequest request =
+                new AdvertisingRequest.Builder(regInfo, NsdManager.PROTOCOL_DNS_SD)
+                    .setTtl(Duration.ofSeconds(invalidTtlSeconds)).build();
+        client.registerService(request, Runnable::run, regListener);
+        waitForIdle();
+
+        verify(regListener, timeout(TIMEOUT_MS))
+                .onRegistrationFailed(any(), eq(FAILURE_BAD_PARAMETERS));
+    }
+
+    @Test
     public void testStopServiceResolutionWithMdnsDiscoveryManager() {
         setMdnsDiscoveryManagerEnabled();
 
@@ -1509,7 +1688,7 @@
         client.registerService(regInfo, NsdManager.PROTOCOL_DNS_SD, Runnable::run, regListener);
         waitForIdle();
         verify(mSocketProvider).startMonitoringSockets();
-        verify(mAdvertiser).addOrUpdateService(anyInt(), any(), any());
+        verify(mAdvertiser).addOrUpdateService(anyInt(), any(), any(), anyInt());
 
         // Verify the discovery uses MdnsDiscoveryManager
         final DiscoveryListener discListener = mock(DiscoveryListener.class);
@@ -1542,7 +1721,7 @@
         client.registerService(regInfo, NsdManager.PROTOCOL_DNS_SD, Runnable::run, regListener);
         waitForIdle();
         verify(mSocketProvider).startMonitoringSockets();
-        verify(mAdvertiser).addOrUpdateService(anyInt(), any(), any());
+        verify(mAdvertiser).addOrUpdateService(anyInt(), any(), any(), anyInt());
 
         final Network wifiNetwork1 = new Network(123);
         final Network wifiNetwork2 = new Network(124);
diff --git a/tests/unit/java/com/android/server/VpnManagerServiceTest.java b/tests/unit/java/com/android/server/VpnManagerServiceTest.java
deleted file mode 100644
index bf23cd1..0000000
--- a/tests/unit/java/com/android/server/VpnManagerServiceTest.java
+++ /dev/null
@@ -1,409 +0,0 @@
-/*
- * Copyright (C) 2022 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;
-
-import static android.os.Build.VERSION_CODES.R;
-
-import static com.android.testutils.ContextUtils.mockService;
-import static com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
-import static com.android.testutils.MiscAsserts.assertThrows;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-
-import android.annotation.UserIdInt;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.net.ConnectivityManager;
-import android.net.INetd;
-import android.net.Uri;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.INetworkManagementService;
-import android.os.Looper;
-import android.os.UserHandle;
-import android.os.UserManager;
-import android.security.Credentials;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.internal.net.VpnProfile;
-import com.android.server.connectivity.Vpn;
-import com.android.server.connectivity.VpnProfileStore;
-import com.android.server.net.LockdownVpnTracker;
-import com.android.testutils.DevSdkIgnoreRule;
-import com.android.testutils.DevSdkIgnoreRunner;
-import com.android.testutils.HandlerUtils;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-import java.nio.charset.StandardCharsets;
-import java.util.List;
-
-@RunWith(DevSdkIgnoreRunner.class)
-@IgnoreUpTo(R) // VpnManagerService is not available before R
-@SmallTest
-public class VpnManagerServiceTest extends VpnTestBase {
-    private static final String CONTEXT_ATTRIBUTION_TAG = "VPN_MANAGER";
-
-    @Rule
-    public final DevSdkIgnoreRule mIgnoreRule = new DevSdkIgnoreRule();
-
-    private static final int TIMEOUT_MS = 2_000;
-
-    @Mock Context mContext;
-    @Mock Context mContextWithoutAttributionTag;
-    @Mock Context mSystemContext;
-    @Mock Context mUserAllContext;
-    private HandlerThread mHandlerThread;
-    @Mock private Vpn mVpn;
-    @Mock private INetworkManagementService mNms;
-    @Mock private ConnectivityManager mCm;
-    @Mock private UserManager mUserManager;
-    @Mock private INetd mNetd;
-    @Mock private PackageManager mPackageManager;
-    @Mock private VpnProfileStore mVpnProfileStore;
-    @Mock private LockdownVpnTracker mLockdownVpnTracker;
-
-    private VpnManagerServiceDependencies mDeps;
-    private VpnManagerService mService;
-    private BroadcastReceiver mUserPresentReceiver;
-    private BroadcastReceiver mIntentReceiver;
-    private final String mNotMyVpnPkg = "com.not.my.vpn";
-
-    class VpnManagerServiceDependencies extends VpnManagerService.Dependencies {
-        @Override
-        public HandlerThread makeHandlerThread() {
-            return mHandlerThread;
-        }
-
-        @Override
-        public INetworkManagementService getINetworkManagementService() {
-            return mNms;
-        }
-
-        @Override
-        public INetd getNetd() {
-            return mNetd;
-        }
-
-        @Override
-        public Vpn createVpn(Looper looper, Context context, INetworkManagementService nms,
-                INetd netd, @UserIdInt int userId) {
-            return mVpn;
-        }
-
-        @Override
-        public VpnProfileStore getVpnProfileStore() {
-            return mVpnProfileStore;
-        }
-
-        @Override
-        public LockdownVpnTracker createLockDownVpnTracker(Context context, Handler handler,
-                Vpn vpn, VpnProfile profile) {
-            return mLockdownVpnTracker;
-        }
-
-        @Override
-        public @UserIdInt int getMainUserId() {
-            return UserHandle.USER_SYSTEM;
-        }
-    }
-
-    @Before
-    public void setUp() throws Exception {
-        MockitoAnnotations.initMocks(this);
-
-        mHandlerThread = new HandlerThread("TestVpnManagerService");
-        mDeps = new VpnManagerServiceDependencies();
-
-        // The attribution tag is a dependency for IKE library to collect VPN metrics correctly
-        // and thus should not be changed without updating the IKE code.
-        doReturn(mContext)
-                .when(mContextWithoutAttributionTag)
-                .createAttributionContext(CONTEXT_ATTRIBUTION_TAG);
-
-        doReturn(mUserAllContext).when(mContext).createContextAsUser(UserHandle.ALL, 0);
-        doReturn(mSystemContext).when(mContext).createContextAsUser(UserHandle.SYSTEM, 0);
-        doReturn(mPackageManager).when(mContext).getPackageManager();
-        setMockedPackages(mPackageManager, sPackages);
-
-        mockService(mContext, ConnectivityManager.class, Context.CONNECTIVITY_SERVICE, mCm);
-        mockService(mContext, UserManager.class, Context.USER_SERVICE, mUserManager);
-        doReturn(SYSTEM_USER).when(mUserManager).getUserInfo(eq(SYSTEM_USER_ID));
-
-        mService = new VpnManagerService(mContextWithoutAttributionTag, mDeps);
-        mService.systemReady();
-
-        final ArgumentCaptor<BroadcastReceiver> intentReceiverCaptor =
-                ArgumentCaptor.forClass(BroadcastReceiver.class);
-        final ArgumentCaptor<BroadcastReceiver> userPresentReceiverCaptor =
-                ArgumentCaptor.forClass(BroadcastReceiver.class);
-        verify(mSystemContext).registerReceiver(
-                userPresentReceiverCaptor.capture(), any(), any(), any());
-        verify(mUserAllContext, times(2)).registerReceiver(
-                intentReceiverCaptor.capture(), any(), any(), any());
-        mUserPresentReceiver = userPresentReceiverCaptor.getValue();
-        mIntentReceiver = intentReceiverCaptor.getValue();
-
-        // Add user to create vpn in mVpn
-        onUserStarted(SYSTEM_USER_ID);
-        assertNotNull(mService.mVpns.get(SYSTEM_USER_ID));
-    }
-
-    @Test
-    public void testUpdateAppExclusionList() {
-        // Start vpn
-        mService.startVpnProfile(TEST_VPN_PKG);
-        verify(mVpn).startVpnProfile(eq(TEST_VPN_PKG));
-
-        // Remove package due to package replaced.
-        onPackageRemoved(PKGS[0], PKG_UIDS[0], true /* isReplacing */);
-        verify(mVpn, never()).refreshPlatformVpnAppExclusionList();
-
-        // Add package due to package replaced.
-        onPackageAdded(PKGS[0], PKG_UIDS[0], true /* isReplacing */);
-        verify(mVpn, never()).refreshPlatformVpnAppExclusionList();
-
-        // Remove package
-        onPackageRemoved(PKGS[0], PKG_UIDS[0], false /* isReplacing */);
-        verify(mVpn).refreshPlatformVpnAppExclusionList();
-
-        // Add the package back
-        onPackageAdded(PKGS[0], PKG_UIDS[0], false /* isReplacing */);
-        verify(mVpn, times(2)).refreshPlatformVpnAppExclusionList();
-    }
-
-    @Test
-    public void testStartVpnProfileFromDiffPackage() {
-        assertThrows(
-                SecurityException.class, () -> mService.startVpnProfile(mNotMyVpnPkg));
-    }
-
-    @Test
-    public void testStopVpnProfileFromDiffPackage() {
-        assertThrows(SecurityException.class, () -> mService.stopVpnProfile(mNotMyVpnPkg));
-    }
-
-    @Test
-    public void testGetProvisionedVpnProfileStateFromDiffPackage() {
-        assertThrows(SecurityException.class, () ->
-                mService.getProvisionedVpnProfileState(mNotMyVpnPkg));
-    }
-
-    @Test
-    public void testGetProvisionedVpnProfileState() {
-        mService.getProvisionedVpnProfileState(TEST_VPN_PKG);
-        verify(mVpn).getProvisionedVpnProfileState(TEST_VPN_PKG);
-    }
-
-    private Intent buildIntent(String action, String packageName, int userId, int uid,
-            boolean isReplacing) {
-        final Intent intent = new Intent(action);
-        intent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
-        intent.putExtra(Intent.EXTRA_UID, uid);
-        intent.putExtra(Intent.EXTRA_REPLACING, isReplacing);
-        if (packageName != null) {
-            intent.setData(Uri.fromParts("package" /* scheme */, packageName, null /* fragment */));
-        }
-
-        return intent;
-    }
-
-    private void sendIntent(Intent intent) {
-        sendIntent(mIntentReceiver, mContext, intent);
-    }
-
-    private void sendIntent(BroadcastReceiver receiver, Context context, Intent intent) {
-        final Handler h = mHandlerThread.getThreadHandler();
-
-        // Send in handler thread.
-        h.post(() -> receiver.onReceive(context, intent));
-        HandlerUtils.waitForIdle(mHandlerThread, TIMEOUT_MS);
-    }
-
-    private void onUserStarted(int userId) {
-        sendIntent(buildIntent(Intent.ACTION_USER_STARTED,
-                null /* packageName */, userId, -1 /* uid */, false /* isReplacing */));
-    }
-
-    private void onUserUnlocked(int userId) {
-        sendIntent(buildIntent(Intent.ACTION_USER_UNLOCKED,
-                null /* packageName */, userId, -1 /* uid */, false /* isReplacing */));
-    }
-
-    private void onUserStopped(int userId) {
-        sendIntent(buildIntent(Intent.ACTION_USER_STOPPED,
-                null /* packageName */, userId, -1 /* uid */, false /* isReplacing */));
-    }
-
-    private void onLockDownReset() {
-        sendIntent(buildIntent(LockdownVpnTracker.ACTION_LOCKDOWN_RESET, null /* packageName */,
-                UserHandle.USER_SYSTEM, -1 /* uid */, false /* isReplacing */));
-    }
-
-    private void onPackageAdded(String packageName, int userId, int uid, boolean isReplacing) {
-        sendIntent(buildIntent(Intent.ACTION_PACKAGE_ADDED, packageName, userId, uid, isReplacing));
-    }
-
-    private void onPackageAdded(String packageName, int uid, boolean isReplacing) {
-        onPackageAdded(packageName, UserHandle.USER_SYSTEM, uid, isReplacing);
-    }
-
-    private void onPackageRemoved(String packageName, int userId, int uid, boolean isReplacing) {
-        sendIntent(buildIntent(Intent.ACTION_PACKAGE_REMOVED, packageName, userId, uid,
-                isReplacing));
-    }
-
-    private void onPackageRemoved(String packageName, int uid, boolean isReplacing) {
-        onPackageRemoved(packageName, UserHandle.USER_SYSTEM, uid, isReplacing);
-    }
-
-    @Test
-    public void testReceiveIntentFromNonHandlerThread() {
-        assertThrows(IllegalStateException.class, () ->
-                mIntentReceiver.onReceive(mContext, buildIntent(Intent.ACTION_PACKAGE_REMOVED,
-                        PKGS[0], UserHandle.USER_SYSTEM, PKG_UIDS[0], true /* isReplacing */)));
-
-        assertThrows(IllegalStateException.class, () ->
-                mUserPresentReceiver.onReceive(mContext, new Intent(Intent.ACTION_USER_PRESENT)));
-    }
-
-    private void setupLockdownVpn(String packageName) {
-        final byte[] profileTag = packageName.getBytes(StandardCharsets.UTF_8);
-        doReturn(profileTag).when(mVpnProfileStore).get(Credentials.LOCKDOWN_VPN);
-    }
-
-    private void setupVpnProfile(String profileName) {
-        final VpnProfile profile = new VpnProfile(profileName);
-        profile.name = profileName;
-        profile.server = "192.0.2.1";
-        profile.dnsServers = "8.8.8.8";
-        profile.type = VpnProfile.TYPE_IPSEC_XAUTH_PSK;
-        final byte[] encodedProfile = profile.encode();
-        doReturn(encodedProfile).when(mVpnProfileStore).get(Credentials.VPN + profileName);
-    }
-
-    @Test
-    public void testUserPresent() {
-        // Verify that LockDownVpnTracker is not created.
-        verify(mLockdownVpnTracker, never()).init();
-
-        setupLockdownVpn(TEST_VPN_PKG);
-        setupVpnProfile(TEST_VPN_PKG);
-
-        // mUserPresentReceiver only registers ACTION_USER_PRESENT intent and does no verification
-        // on action, so an empty intent is enough.
-        sendIntent(mUserPresentReceiver, mSystemContext, new Intent());
-
-        verify(mLockdownVpnTracker).init();
-        verify(mSystemContext).unregisterReceiver(mUserPresentReceiver);
-        verify(mUserAllContext, never()).unregisterReceiver(any());
-    }
-
-    @Test
-    public void testUpdateLockdownVpn() {
-        setupLockdownVpn(TEST_VPN_PKG);
-        onUserUnlocked(SYSTEM_USER_ID);
-
-        // Will not create lockDownVpnTracker w/o valid profile configured in the keystore
-        verify(mLockdownVpnTracker, never()).init();
-
-        setupVpnProfile(TEST_VPN_PKG);
-
-        // Remove the user from mVpns
-        onUserStopped(SYSTEM_USER_ID);
-        onUserUnlocked(SYSTEM_USER_ID);
-        verify(mLockdownVpnTracker, never()).init();
-
-        // Add user back
-        onUserStarted(SYSTEM_USER_ID);
-        verify(mLockdownVpnTracker).init();
-
-        // Trigger another update. The existing LockDownVpnTracker should be shut down and
-        // initialize another one.
-        onUserUnlocked(SYSTEM_USER_ID);
-        verify(mLockdownVpnTracker).shutdown();
-        verify(mLockdownVpnTracker, times(2)).init();
-    }
-
-    @Test
-    public void testLockdownReset() {
-        // Init LockdownVpnTracker
-        setupLockdownVpn(TEST_VPN_PKG);
-        setupVpnProfile(TEST_VPN_PKG);
-        onUserUnlocked(SYSTEM_USER_ID);
-        verify(mLockdownVpnTracker).init();
-
-        onLockDownReset();
-        verify(mLockdownVpnTracker).reset();
-    }
-
-    @Test
-    public void testLockdownResetWhenLockdownVpnTrackerIsNotInit() {
-        setupLockdownVpn(TEST_VPN_PKG);
-        setupVpnProfile(TEST_VPN_PKG);
-
-        onLockDownReset();
-
-        // LockDownVpnTracker is not created. Lockdown reset will not take effect.
-        verify(mLockdownVpnTracker, never()).reset();
-    }
-
-    @Test
-    public void testIsVpnLockdownEnabled() {
-        // Vpn is created but the VPN lockdown is not enabled.
-        assertFalse(mService.isVpnLockdownEnabled(SYSTEM_USER_ID));
-
-        // Set lockdown for the SYSTEM_USER_ID VPN.
-        doReturn(true).when(mVpn).getLockdown();
-        assertTrue(mService.isVpnLockdownEnabled(SYSTEM_USER_ID));
-
-        // Even lockdown is enabled but no Vpn is created for SECONDARY_USER.
-        assertFalse(mService.isVpnLockdownEnabled(SECONDARY_USER.id));
-    }
-
-    @Test
-    public void testGetVpnLockdownAllowlist() {
-        doReturn(null).when(mVpn).getLockdownAllowlist();
-        assertNull(mService.getVpnLockdownAllowlist(SYSTEM_USER_ID));
-
-        final List<String> expected = List.of(PKGS);
-        doReturn(expected).when(mVpn).getLockdownAllowlist();
-        assertEquals(expected, mService.getVpnLockdownAllowlist(SYSTEM_USER_ID));
-
-        // Even lockdown is enabled but no Vpn is created for SECONDARY_USER.
-        assertNull(mService.getVpnLockdownAllowlist(SECONDARY_USER.id));
-    }
-}
diff --git a/tests/unit/java/com/android/server/VpnTestBase.java b/tests/unit/java/com/android/server/VpnTestBase.java
deleted file mode 100644
index 6113872..0000000
--- a/tests/unit/java/com/android/server/VpnTestBase.java
+++ /dev/null
@@ -1,97 +0,0 @@
-/*
- * Copyright (C) 2022 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;
-
-import static android.content.pm.UserInfo.FLAG_ADMIN;
-import static android.content.pm.UserInfo.FLAG_MANAGED_PROFILE;
-import static android.content.pm.UserInfo.FLAG_PRIMARY;
-import static android.content.pm.UserInfo.FLAG_RESTRICTED;
-
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.Mockito.doAnswer;
-
-import android.content.pm.PackageManager;
-import android.content.pm.UserInfo;
-import android.os.Process;
-import android.os.UserHandle;
-import android.util.ArrayMap;
-
-import java.util.Arrays;
-import java.util.List;
-import java.util.Map;
-import java.util.stream.Collectors;
-
-/** Common variables or methods shared between VpnTest and VpnManagerServiceTest. */
-public class VpnTestBase {
-    protected static final String TEST_VPN_PKG = "com.testvpn.vpn";
-    /**
-     * Names and UIDs for some fake packages. Important points:
-     *  - UID is ordered increasing.
-     *  - One pair of packages have consecutive UIDs.
-     */
-    protected static final String[] PKGS = {"com.example", "org.example", "net.example", "web.vpn"};
-    protected static final int[] PKG_UIDS = {10066, 10077, 10078, 10400};
-    // Mock packages
-    protected static final Map<String, Integer> sPackages = new ArrayMap<>();
-    static {
-        for (int i = 0; i < PKGS.length; i++) {
-            sPackages.put(PKGS[i], PKG_UIDS[i]);
-        }
-        sPackages.put(TEST_VPN_PKG, Process.myUid());
-    }
-
-    // Mock users
-    protected static final int SYSTEM_USER_ID = 0;
-    protected static final UserInfo SYSTEM_USER = new UserInfo(0, "system", UserInfo.FLAG_PRIMARY);
-    protected static final UserInfo PRIMARY_USER = new UserInfo(27, "Primary",
-            FLAG_ADMIN | FLAG_PRIMARY);
-    protected static final UserInfo SECONDARY_USER = new UserInfo(15, "Secondary", FLAG_ADMIN);
-    protected static final UserInfo RESTRICTED_PROFILE_A = new UserInfo(40, "RestrictedA",
-            FLAG_RESTRICTED);
-    protected static final UserInfo RESTRICTED_PROFILE_B = new UserInfo(42, "RestrictedB",
-            FLAG_RESTRICTED);
-    protected static final UserInfo MANAGED_PROFILE_A = new UserInfo(45, "ManagedA",
-            FLAG_MANAGED_PROFILE);
-    static {
-        RESTRICTED_PROFILE_A.restrictedProfileParentId = PRIMARY_USER.id;
-        RESTRICTED_PROFILE_B.restrictedProfileParentId = SECONDARY_USER.id;
-        MANAGED_PROFILE_A.profileGroupId = PRIMARY_USER.id;
-    }
-
-    // Populate a fake packageName-to-UID mapping.
-    protected void setMockedPackages(PackageManager mockPm, final Map<String, Integer> packages) {
-        try {
-            doAnswer(invocation -> {
-                final String appName = (String) invocation.getArguments()[0];
-                final int userId = (int) invocation.getArguments()[1];
-
-                final Integer appId = packages.get(appName);
-                if (appId == null) {
-                    throw new PackageManager.NameNotFoundException(appName);
-                }
-
-                return UserHandle.getUid(userId, appId);
-            }).when(mockPm).getPackageUidAsUser(anyString(), anyInt());
-        } catch (Exception e) {
-        }
-    }
-
-    protected List<Integer> toList(int[] arr) {
-        return Arrays.stream(arr).boxed().collect(Collectors.toList());
-    }
-}
diff --git a/tests/unit/java/com/android/server/connectivity/AutomaticOnOffKeepaliveTrackerTest.java b/tests/unit/java/com/android/server/connectivity/AutomaticOnOffKeepaliveTrackerTest.java
index 6cc301d..c53feee 100644
--- a/tests/unit/java/com/android/server/connectivity/AutomaticOnOffKeepaliveTrackerTest.java
+++ b/tests/unit/java/com/android/server/connectivity/AutomaticOnOffKeepaliveTrackerTest.java
@@ -20,10 +20,8 @@
 import static android.net.ConnectivityManager.TYPE_MOBILE;
 import static android.net.NetworkAgent.CMD_STOP_SOCKET_KEEPALIVE;
 import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
-
 import static com.android.server.connectivity.AutomaticOnOffKeepaliveTracker.METRICS_COLLECTION_DURATION_MS;
 import static com.android.testutils.HandlerUtils.visibleOnHandlerThread;
-
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
@@ -71,29 +69,16 @@
 import android.os.Message;
 import android.os.SystemClock;
 import android.telephony.SubscriptionManager;
-import android.test.suitebuilder.annotation.SmallTest;
 import android.util.Log;
-
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
-
+import androidx.test.filters.SmallTest;
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.server.connectivity.AutomaticOnOffKeepaliveTracker.AutomaticOnOffKeepalive;
 import com.android.server.connectivity.KeepaliveTracker.KeepaliveInfo;
 import com.android.testutils.DevSdkIgnoreRule;
 import com.android.testutils.DevSdkIgnoreRunner;
 import com.android.testutils.HandlerUtils;
-
-import libcore.util.HexEncoding;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
 import java.io.FileDescriptor;
 import java.io.StringWriter;
 import java.net.Inet4Address;
@@ -103,6 +88,14 @@
 import java.nio.ByteOrder;
 import java.util.ArrayList;
 import java.util.List;
+import libcore.util.HexEncoding;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
 
 @RunWith(DevSdkIgnoreRunner.class)
 @SmallTest
diff --git a/tests/unit/java/com/android/server/connectivity/CarrierPrivilegeAuthenticatorTest.java b/tests/unit/java/com/android/server/connectivity/CarrierPrivilegeAuthenticatorTest.java
index f07593e..7bd2b56 100644
--- a/tests/unit/java/com/android/server/connectivity/CarrierPrivilegeAuthenticatorTest.java
+++ b/tests/unit/java/com/android/server/connectivity/CarrierPrivilegeAuthenticatorTest.java
@@ -46,7 +46,6 @@
 import android.net.TelephonyNetworkSpecifier;
 import android.os.Build;
 import android.os.HandlerThread;
-import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 
 import com.android.net.module.util.CollectionUtils;
@@ -54,10 +53,12 @@
 import com.android.networkstack.apishim.common.TelephonyManagerShim.CarrierPrivilegesListenerShim;
 import com.android.networkstack.apishim.common.UnsupportedApiLevelException;
 import com.android.server.connectivity.CarrierPrivilegeAuthenticator.Dependencies;
+import com.android.testutils.DevSdkIgnoreRule;
 import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
 import com.android.testutils.DevSdkIgnoreRunner;
 
 import org.junit.After;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -67,6 +68,8 @@
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Map;
+import java.util.Set;
+import java.util.function.BiConsumer;
 
 /**
  * Tests for CarrierPrivilegeAuthenticatorTest.
@@ -77,6 +80,9 @@
 @RunWith(DevSdkIgnoreRunner.class)
 @IgnoreUpTo(Build.VERSION_CODES.S_V2)
 public class CarrierPrivilegeAuthenticatorTest {
+    @Rule
+    public final DevSdkIgnoreRule ignoreRule = new DevSdkIgnoreRule();
+
     private static final int SUBSCRIPTION_COUNT = 2;
     private static final int TEST_SUBSCRIPTION_ID = 1;
 
@@ -85,7 +91,9 @@
     @NonNull private final TelephonyManagerShimImpl mTelephonyManagerShim;
     @NonNull private final PackageManager mPackageManager;
     @NonNull private TestCarrierPrivilegeAuthenticator mCarrierPrivilegeAuthenticator;
+    @NonNull private final BiConsumer<Integer, Integer> mListener;
     private final int mCarrierConfigPkgUid = 12345;
+    private final boolean mUseCallbacks;
     private final String mTestPkg = "com.android.server.connectivity.test";
     private final BroadcastReceiver mMultiSimBroadcastReceiver;
     @NonNull private final HandlerThread mHandlerThread;
@@ -94,12 +102,12 @@
         TestCarrierPrivilegeAuthenticator(@NonNull final Context c,
                 @NonNull final Dependencies deps,
                 @NonNull final TelephonyManager t) {
-            super(c, deps, t, mTelephonyManagerShim);
+            super(c, deps, t, mTelephonyManagerShim, true /* requestRestrictedWifiEnabled */,
+                    mListener);
         }
         @Override
-        protected int getSlotIndex(int subId) {
-            if (SubscriptionManager.DEFAULT_SUBSCRIPTION_ID == subId) return TEST_SUBSCRIPTION_ID;
-            return subId;
+        protected int getSubId(int slotIndex) {
+            return TEST_SUBSCRIPTION_ID;
         }
     }
 
@@ -119,7 +127,9 @@
         mTelephonyManager = mock(TelephonyManager.class);
         mTelephonyManagerShim = mock(TelephonyManagerShimImpl.class);
         mPackageManager = mock(PackageManager.class);
+        mListener = mock(BiConsumer.class);
         mHandlerThread = new HandlerThread(CarrierPrivilegeAuthenticatorTest.class.getSimpleName());
+        mUseCallbacks = useCallbacks;
         final Dependencies deps = mock(Dependencies.class);
         doReturn(useCallbacks).when(deps).isFeatureEnabled(any() /* context */,
                 eq(CARRIER_SERVICE_CHANGED_USE_CALLBACK));
@@ -172,7 +182,7 @@
 
         final NetworkCapabilities.Builder ncBuilder = new NetworkCapabilities.Builder()
                 .addTransportType(TRANSPORT_CELLULAR)
-                .setNetworkSpecifier(new TelephonyNetworkSpecifier(0));
+                .setNetworkSpecifier(new TelephonyNetworkSpecifier(TEST_SUBSCRIPTION_ID));
 
         assertTrue(mCarrierPrivilegeAuthenticator.isCarrierServiceUidForNetworkCapabilities(
                 mCarrierConfigPkgUid, ncBuilder.build()));
@@ -208,7 +218,8 @@
 
         newListeners.get(0).onCarrierServiceChanged(null, mCarrierConfigPkgUid);
 
-        final TelephonyNetworkSpecifier specifier = new TelephonyNetworkSpecifier(0);
+        final TelephonyNetworkSpecifier specifier =
+                new TelephonyNetworkSpecifier(TEST_SUBSCRIPTION_ID);
         final NetworkCapabilities nc = new NetworkCapabilities.Builder()
                 .addTransportType(TRANSPORT_CELLULAR)
                 .setNetworkSpecifier(specifier)
@@ -220,10 +231,27 @@
     }
 
     @Test
+    @IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
+    public void testCarrierPrivilegesLostDueToCarrierServiceUpdate() throws Exception {
+        final CarrierPrivilegesListenerShim l = getCarrierPrivilegesListeners().get(0);
+
+        l.onCarrierServiceChanged(null, mCarrierConfigPkgUid);
+        l.onCarrierServiceChanged(null, mCarrierConfigPkgUid + 1);
+        if (mUseCallbacks) {
+            verify(mListener).accept(eq(mCarrierConfigPkgUid), eq(TEST_SUBSCRIPTION_ID));
+        }
+        l.onCarrierServiceChanged(null, mCarrierConfigPkgUid + 2);
+        if (mUseCallbacks) {
+            verify(mListener).accept(eq(mCarrierConfigPkgUid + 1), eq(TEST_SUBSCRIPTION_ID));
+        }
+    }
+
+    @Test
     public void testOnCarrierPrivilegesChanged() throws Exception {
         final CarrierPrivilegesListenerShim listener = getCarrierPrivilegesListeners().get(0);
 
-        final TelephonyNetworkSpecifier specifier = new TelephonyNetworkSpecifier(0);
+        final TelephonyNetworkSpecifier specifier =
+                new TelephonyNetworkSpecifier(TEST_SUBSCRIPTION_ID);
         final NetworkCapabilities nc = new NetworkCapabilities.Builder()
                 .addTransportType(TRANSPORT_CELLULAR)
                 .setNetworkSpecifier(specifier)
@@ -251,7 +279,7 @@
         assertFalse(mCarrierPrivilegeAuthenticator.isCarrierServiceUidForNetworkCapabilities(
                 mCarrierConfigPkgUid, ncBuilder.build()));
 
-        ncBuilder.setNetworkSpecifier(new TelephonyNetworkSpecifier(0));
+        ncBuilder.setNetworkSpecifier(new TelephonyNetworkSpecifier(TEST_SUBSCRIPTION_ID));
         assertTrue(mCarrierPrivilegeAuthenticator.isCarrierServiceUidForNetworkCapabilities(
                 mCarrierConfigPkgUid, ncBuilder.build()));
 
@@ -260,7 +288,35 @@
         ncBuilder.setNetworkSpecifier(null);
         ncBuilder.removeTransportType(TRANSPORT_CELLULAR);
         ncBuilder.addTransportType(TRANSPORT_WIFI);
-        ncBuilder.setNetworkSpecifier(new TelephonyNetworkSpecifier(0));
+        ncBuilder.setNetworkSpecifier(new TelephonyNetworkSpecifier(TEST_SUBSCRIPTION_ID));
+        assertFalse(mCarrierPrivilegeAuthenticator.isCarrierServiceUidForNetworkCapabilities(
+                mCarrierConfigPkgUid, ncBuilder.build()));
+    }
+
+    @Test
+    @IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
+    public void testNetworkCapabilitiesContainOneSubId() throws Exception {
+        final CarrierPrivilegesListenerShim listener = getCarrierPrivilegesListeners().get(0);
+        listener.onCarrierServiceChanged(null, mCarrierConfigPkgUid);
+
+        final NetworkCapabilities.Builder ncBuilder = new NetworkCapabilities.Builder();
+        ncBuilder.addTransportType(TRANSPORT_WIFI);
+        ncBuilder.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED);
+        ncBuilder.setSubscriptionIds(Set.of(TEST_SUBSCRIPTION_ID));
+        assertTrue(mCarrierPrivilegeAuthenticator.isCarrierServiceUidForNetworkCapabilities(
+                mCarrierConfigPkgUid, ncBuilder.build()));
+    }
+
+    @Test
+    @IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
+    public void testNetworkCapabilitiesContainTwoSubIds() throws Exception {
+        final CarrierPrivilegesListenerShim listener = getCarrierPrivilegesListeners().get(0);
+        listener.onCarrierServiceChanged(null, mCarrierConfigPkgUid);
+
+        final NetworkCapabilities.Builder ncBuilder = new NetworkCapabilities.Builder();
+        ncBuilder.addTransportType(TRANSPORT_WIFI);
+        ncBuilder.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED);
+        ncBuilder.setSubscriptionIds(Set.of(0, 1));
         assertFalse(mCarrierPrivilegeAuthenticator.isCarrierServiceUidForNetworkCapabilities(
                 mCarrierConfigPkgUid, ncBuilder.build()));
     }
diff --git a/tests/unit/java/com/android/server/connectivity/IpConnectivityEventBuilderTest.java b/tests/unit/java/com/android/server/connectivity/IpConnectivityEventBuilderTest.java
index 52b05aa..ab1e467 100644
--- a/tests/unit/java/com/android/server/connectivity/IpConnectivityEventBuilderTest.java
+++ b/tests/unit/java/com/android/server/connectivity/IpConnectivityEventBuilderTest.java
@@ -26,7 +26,6 @@
 import static com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.IpConnectivityLog;
 import static com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.MULTIPLE;
 import static com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.WIFI;
-
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.fail;
 
@@ -43,17 +42,14 @@
 import android.net.metrics.ValidationProbeEvent;
 import android.net.metrics.WakeupStats;
 import android.os.Build;
-import android.test.suitebuilder.annotation.SmallTest;
-
+import androidx.test.filters.SmallTest;
 import com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.IpConnectivityEvent;
 import com.android.testutils.DevSdkIgnoreRule;
 import com.android.testutils.DevSdkIgnoreRunner;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
 import java.util.Arrays;
 import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
 
 // TODO: instead of comparing textpb to textpb, parse textpb and compare proto to proto.
 @RunWith(DevSdkIgnoreRunner.class)
diff --git a/tests/unit/java/com/android/server/connectivity/IpConnectivityMetricsTest.java b/tests/unit/java/com/android/server/connectivity/IpConnectivityMetricsTest.java
index 5881a8e..91626d2 100644
--- a/tests/unit/java/com/android/server/connectivity/IpConnectivityMetricsTest.java
+++ b/tests/unit/java/com/android/server/connectivity/IpConnectivityMetricsTest.java
@@ -18,7 +18,6 @@
 
 import static android.net.metrics.INetdEventListener.EVENT_GETADDRINFO;
 import static android.net.metrics.INetdEventListener.EVENT_GETHOSTBYNAME;
-
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.fail;
@@ -50,14 +49,14 @@
 import android.os.Parcelable;
 import android.os.SystemClock;
 import android.system.OsConstants;
-import android.test.suitebuilder.annotation.SmallTest;
 import android.util.Base64;
-
+import androidx.test.filters.SmallTest;
 import com.android.internal.util.BitUtils;
 import com.android.server.connectivity.metrics.nano.IpConnectivityLogClass;
 import com.android.testutils.DevSdkIgnoreRule;
 import com.android.testutils.DevSdkIgnoreRunner;
-
+import java.io.PrintWriter;
+import java.io.StringWriter;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -65,9 +64,6 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
-import java.io.PrintWriter;
-import java.io.StringWriter;
-
 @RunWith(DevSdkIgnoreRunner.class)
 @SmallTest
 @DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
diff --git a/tests/unit/java/com/android/server/connectivity/KeepaliveStatsTrackerTest.java b/tests/unit/java/com/android/server/connectivity/KeepaliveStatsTrackerTest.java
index 1b964e2..294dacb 100644
--- a/tests/unit/java/com/android/server/connectivity/KeepaliveStatsTrackerTest.java
+++ b/tests/unit/java/com/android/server/connectivity/KeepaliveStatsTrackerTest.java
@@ -1297,8 +1297,8 @@
 
         assertTrue(mKeepaliveStatsTracker.allMetricsExpected(dailyKeepaliveInfoReported));
 
-        // Write time after 26 hours.
-        final int writeTime2 = 26 * 60 * 60 * 1000;
+        // Write time after 27 hours.
+        final int writeTime2 = 27 * 60 * 60 * 1000;
         setElapsedRealtime(writeTime2);
 
         visibleOnHandlerThread(mTestHandler, () -> mKeepaliveStatsTracker.writeAndResetMetrics());
diff --git a/tests/unit/java/com/android/server/connectivity/NetdEventListenerServiceTest.java b/tests/unit/java/com/android/server/connectivity/NetdEventListenerServiceTest.java
index d667662..89e2a51 100644
--- a/tests/unit/java/com/android/server/connectivity/NetdEventListenerServiceTest.java
+++ b/tests/unit/java/com/android/server/connectivity/NetdEventListenerServiceTest.java
@@ -18,9 +18,7 @@
 
 import static android.net.metrics.INetdEventListener.EVENT_GETADDRINFO;
 import static android.net.metrics.INetdEventListener.EVENT_GETHOSTBYNAME;
-
 import static com.android.testutils.MiscAsserts.assertStringContains;
-
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.fail;
 import static org.mockito.ArgumentMatchers.any;
@@ -34,27 +32,23 @@
 import android.net.NetworkCapabilities;
 import android.os.Build;
 import android.system.OsConstants;
-import android.test.suitebuilder.annotation.SmallTest;
 import android.util.Base64;
-
+import androidx.test.filters.SmallTest;
 import com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.IpConnectivityEvent;
 import com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.IpConnectivityLog;
 import com.android.testutils.DevSdkIgnoreRule;
 import com.android.testutils.DevSdkIgnoreRunner;
-
-import libcore.util.EmptyArray;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-
 import java.io.FileOutputStream;
 import java.io.PrintWriter;
 import java.io.StringWriter;
 import java.util.Arrays;
 import java.util.Comparator;
 import java.util.List;
+import libcore.util.EmptyArray;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
 
 @RunWith(DevSdkIgnoreRunner.class)
 @SmallTest
diff --git a/tests/unit/java/com/android/server/connectivity/SatelliteAccessControllerTest.kt b/tests/unit/java/com/android/server/connectivity/SatelliteAccessControllerTest.kt
new file mode 100644
index 0000000..193078b
--- /dev/null
+++ b/tests/unit/java/com/android/server/connectivity/SatelliteAccessControllerTest.kt
@@ -0,0 +1,222 @@
+/*
+ * 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.
+ */
+package com.android.server.connectivity
+
+import android.Manifest
+import android.app.role.OnRoleHoldersChangedListener
+import android.app.role.RoleManager
+import android.content.Context
+import android.content.pm.ApplicationInfo
+import android.content.pm.PackageManager
+import android.content.pm.UserInfo
+import android.os.Build
+import android.os.Handler
+import android.os.UserHandle
+import android.util.ArraySet
+import com.android.server.makeMockUserManager
+import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
+import com.android.testutils.DevSdkIgnoreRunner
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers.any
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.ArgumentMatchers.eq
+import org.mockito.Mockito.doReturn
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import java.util.concurrent.Executor
+import java.util.function.Consumer
+
+private const val USER = 0
+val USER_INFO = UserInfo(USER, "" /* name */, UserInfo.FLAG_PRIMARY)
+val USER_HANDLE = UserHandle(USER)
+private const val PRIMARY_USER = 0
+private const val SECONDARY_USER = 10
+private val PRIMARY_USER_HANDLE = UserHandle.of(PRIMARY_USER)
+private val SECONDARY_USER_HANDLE = UserHandle.of(SECONDARY_USER)
+// sms app names
+private const val SMS_APP1 = "sms_app_1"
+private const val SMS_APP2 = "sms_app_2"
+// sms app ids
+private const val SMS_APP_ID1 = 100
+private const val SMS_APP_ID2 = 101
+// UID for app1 and app2 on primary user
+// These app could become default sms app for user1
+private val PRIMARY_USER_SMS_APP_UID1 = UserHandle.getUid(PRIMARY_USER, SMS_APP_ID1)
+private val PRIMARY_USER_SMS_APP_UID2 = UserHandle.getUid(PRIMARY_USER, SMS_APP_ID2)
+// UID for app1 and app2 on secondary user
+// These app could become default sms app for user2
+private val SECONDARY_USER_SMS_APP_UID1 = UserHandle.getUid(SECONDARY_USER, SMS_APP_ID1)
+private val SECONDARY_USER_SMS_APP_UID2 = UserHandle.getUid(SECONDARY_USER, SMS_APP_ID2)
+
+@RunWith(DevSdkIgnoreRunner::class)
+@IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
+class SatelliteAccessControllerTest {
+    private val context = mock(Context::class.java)
+    private val mPackageManager = mock(PackageManager::class.java)
+    private val mHandler = mock(Handler::class.java)
+    private val mRoleManager =
+        mock(SatelliteAccessController.Dependencies::class.java)
+    private val mCallback = mock(Consumer::class.java) as Consumer<Set<Int>>
+    private val mSatelliteAccessController =
+        SatelliteAccessController(context, mRoleManager, mCallback, mHandler)
+    private lateinit var mRoleHolderChangedListener: OnRoleHoldersChangedListener
+    @Before
+    @Throws(PackageManager.NameNotFoundException::class)
+    fun setup() {
+        makeMockUserManager(USER_INFO, USER_HANDLE)
+        doReturn(context).`when`(context).createContextAsUser(any(), anyInt())
+        doReturn(mPackageManager).`when`(context).packageManager
+
+        doReturn(PackageManager.PERMISSION_GRANTED)
+            .`when`(mPackageManager)
+            .checkPermission(Manifest.permission.SATELLITE_COMMUNICATION, SMS_APP1)
+        doReturn(PackageManager.PERMISSION_GRANTED)
+            .`when`(mPackageManager)
+            .checkPermission(Manifest.permission.SATELLITE_COMMUNICATION, SMS_APP2)
+
+        // Initialise default message application primary user package1
+        val applicationInfo1 = ApplicationInfo()
+        applicationInfo1.uid = PRIMARY_USER_SMS_APP_UID1
+        doReturn(applicationInfo1)
+            .`when`(mPackageManager)
+            .getApplicationInfo(eq(SMS_APP1), anyInt())
+
+        // Initialise default message application primary user package2
+        val applicationInfo2 = ApplicationInfo()
+        applicationInfo2.uid = PRIMARY_USER_SMS_APP_UID2
+        doReturn(applicationInfo2)
+            .`when`(mPackageManager)
+            .getApplicationInfo(eq(SMS_APP2), anyInt())
+
+        // Get registered listener using captor
+        val listenerCaptor = ArgumentCaptor.forClass(
+            OnRoleHoldersChangedListener::class.java
+        )
+        mSatelliteAccessController.start()
+        verify(mRoleManager).addOnRoleHoldersChangedListenerAsUser(
+            any(Executor::class.java), listenerCaptor.capture(), any(UserHandle::class.java))
+        mRoleHolderChangedListener = listenerCaptor.value
+    }
+
+    @Test
+    fun test_onRoleHoldersChanged_SatelliteFallbackUid_Changed_SingleUser() {
+        doReturn(listOf<String>()).`when`(mRoleManager).getRoleHoldersAsUser(RoleManager.ROLE_SMS,
+            PRIMARY_USER_HANDLE)
+        mRoleHolderChangedListener.onRoleHoldersChanged(RoleManager.ROLE_SMS, PRIMARY_USER_HANDLE)
+        verify(mCallback, never()).accept(any())
+
+        // check DEFAULT_MESSAGING_APP1 is available as satellite network fallback uid
+        doReturn(listOf(SMS_APP1))
+            .`when`(mRoleManager).getRoleHoldersAsUser(RoleManager.ROLE_SMS, PRIMARY_USER_HANDLE)
+        mRoleHolderChangedListener.onRoleHoldersChanged(RoleManager.ROLE_SMS, PRIMARY_USER_HANDLE)
+        verify(mCallback).accept(setOf(PRIMARY_USER_SMS_APP_UID1))
+
+        // check SMS_APP2 is available as satellite network Fallback uid
+        doReturn(listOf(SMS_APP2)).`when`(mRoleManager).getRoleHoldersAsUser(RoleManager.ROLE_SMS,
+            PRIMARY_USER_HANDLE)
+        mRoleHolderChangedListener.onRoleHoldersChanged(RoleManager.ROLE_SMS, PRIMARY_USER_HANDLE)
+        verify(mCallback).accept(setOf(PRIMARY_USER_SMS_APP_UID2))
+
+        // check no uid is available as satellite network fallback uid
+        doReturn(listOf<String>()).`when`(mRoleManager).getRoleHoldersAsUser(RoleManager.ROLE_SMS,
+            PRIMARY_USER_HANDLE)
+        mRoleHolderChangedListener.onRoleHoldersChanged(RoleManager.ROLE_SMS, PRIMARY_USER_HANDLE)
+        verify(mCallback).accept(ArraySet())
+    }
+
+    @Test
+    fun test_onRoleHoldersChanged_NoSatelliteCommunicationPermission() {
+        doReturn(listOf<Any>()).`when`(mRoleManager).getRoleHoldersAsUser(RoleManager.ROLE_SMS,
+            PRIMARY_USER_HANDLE)
+        mRoleHolderChangedListener.onRoleHoldersChanged(RoleManager.ROLE_SMS, PRIMARY_USER_HANDLE)
+        verify(mCallback, never()).accept(any())
+
+        // check DEFAULT_MESSAGING_APP1 is not available as satellite network fallback uid
+        // since satellite communication permission not available.
+        doReturn(PackageManager.PERMISSION_DENIED)
+            .`when`(mPackageManager)
+            .checkPermission(Manifest.permission.SATELLITE_COMMUNICATION, SMS_APP1)
+        doReturn(listOf(SMS_APP1))
+            .`when`(mRoleManager).getRoleHoldersAsUser(RoleManager.ROLE_SMS, PRIMARY_USER_HANDLE)
+        mRoleHolderChangedListener.onRoleHoldersChanged(RoleManager.ROLE_SMS, PRIMARY_USER_HANDLE)
+        verify(mCallback, never()).accept(any())
+    }
+
+    @Test
+    fun test_onRoleHoldersChanged_RoleSms_NotAvailable() {
+        doReturn(listOf(SMS_APP1))
+            .`when`(mRoleManager).getRoleHoldersAsUser(RoleManager.ROLE_SMS, PRIMARY_USER_HANDLE)
+        mRoleHolderChangedListener.onRoleHoldersChanged(RoleManager.ROLE_BROWSER,
+            PRIMARY_USER_HANDLE)
+        verify(mCallback, never()).accept(any())
+    }
+
+    @Test
+    fun test_onRoleHoldersChanged_SatelliteNetworkFallbackUid_Changed_multiUser() {
+        doReturn(listOf<String>()).`when`(mRoleManager).getRoleHoldersAsUser(RoleManager.ROLE_SMS,
+            PRIMARY_USER_HANDLE)
+        mRoleHolderChangedListener.onRoleHoldersChanged(RoleManager.ROLE_SMS, PRIMARY_USER_HANDLE)
+        verify(mCallback, never()).accept(any())
+
+        // check SMS_APP1 is available as satellite network fallback uid at primary user
+        doReturn(listOf(SMS_APP1))
+            .`when`(mRoleManager).getRoleHoldersAsUser(RoleManager.ROLE_SMS, PRIMARY_USER_HANDLE)
+        mRoleHolderChangedListener.onRoleHoldersChanged(RoleManager.ROLE_SMS, PRIMARY_USER_HANDLE)
+        verify(mCallback).accept(setOf(PRIMARY_USER_SMS_APP_UID1))
+
+        // check SMS_APP2 is available as satellite network fallback uid at primary user
+        doReturn(listOf(SMS_APP2)).`when`(mRoleManager).getRoleHoldersAsUser(RoleManager.ROLE_SMS,
+            PRIMARY_USER_HANDLE)
+        mRoleHolderChangedListener.onRoleHoldersChanged(RoleManager.ROLE_SMS, PRIMARY_USER_HANDLE)
+        verify(mCallback).accept(setOf(PRIMARY_USER_SMS_APP_UID2))
+
+        // check SMS_APP1 is available as satellite network fallback uid at secondary user
+        val applicationInfo1 = ApplicationInfo()
+        applicationInfo1.uid = SECONDARY_USER_SMS_APP_UID1
+        doReturn(applicationInfo1).`when`(mPackageManager)
+            .getApplicationInfo(eq(SMS_APP1), anyInt())
+        doReturn(listOf(SMS_APP1)).`when`(mRoleManager).getRoleHoldersAsUser(RoleManager.ROLE_SMS,
+            SECONDARY_USER_HANDLE)
+        mRoleHolderChangedListener.onRoleHoldersChanged(RoleManager.ROLE_SMS, SECONDARY_USER_HANDLE)
+        verify(mCallback).accept(setOf(PRIMARY_USER_SMS_APP_UID2, SECONDARY_USER_SMS_APP_UID1))
+
+        // check no uid is available as satellite network fallback uid at primary user
+        doReturn(listOf<String>()).`when`(mRoleManager).getRoleHoldersAsUser(RoleManager.ROLE_SMS,
+            PRIMARY_USER_HANDLE)
+        mRoleHolderChangedListener.onRoleHoldersChanged(RoleManager.ROLE_SMS,
+            PRIMARY_USER_HANDLE)
+        verify(mCallback).accept(setOf(SECONDARY_USER_SMS_APP_UID1))
+
+        // check SMS_APP2 is available as satellite network fallback uid at secondary user
+        applicationInfo1.uid = SECONDARY_USER_SMS_APP_UID2
+        doReturn(applicationInfo1).`when`(mPackageManager)
+            .getApplicationInfo(eq(SMS_APP2), anyInt())
+        doReturn(listOf(SMS_APP2))
+            .`when`(mRoleManager).getRoleHoldersAsUser(RoleManager.ROLE_SMS, SECONDARY_USER_HANDLE)
+        mRoleHolderChangedListener.onRoleHoldersChanged(RoleManager.ROLE_SMS, SECONDARY_USER_HANDLE)
+        verify(mCallback).accept(setOf(SECONDARY_USER_SMS_APP_UID2))
+
+        // check no uid is available as satellite network fallback uid at secondary user
+        doReturn(listOf<String>()).`when`(mRoleManager).getRoleHoldersAsUser(RoleManager.ROLE_SMS,
+            SECONDARY_USER_HANDLE)
+        mRoleHolderChangedListener.onRoleHoldersChanged(RoleManager.ROLE_SMS, SECONDARY_USER_HANDLE)
+        verify(mCallback).accept(ArraySet())
+    }
+}
diff --git a/tests/unit/java/com/android/server/connectivity/VpnTest.java b/tests/unit/java/com/android/server/connectivity/VpnTest.java
deleted file mode 100644
index c9cece0..0000000
--- a/tests/unit/java/com/android/server/connectivity/VpnTest.java
+++ /dev/null
@@ -1,3298 +0,0 @@
-/*
- * Copyright (C) 2016 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;
-
-import static android.Manifest.permission.BIND_VPN_SERVICE;
-import static android.Manifest.permission.CONTROL_VPN;
-import static android.content.pm.PackageManager.PERMISSION_DENIED;
-import static android.content.pm.PackageManager.PERMISSION_GRANTED;
-import static android.net.ConnectivityDiagnosticsManager.ConnectivityDiagnosticsCallback;
-import static android.net.ConnectivityDiagnosticsManager.DataStallReport;
-import static android.net.ConnectivityManager.NetworkCallback;
-import static android.net.INetd.IF_STATE_DOWN;
-import static android.net.INetd.IF_STATE_UP;
-import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
-import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED;
-import static android.net.NetworkCapabilities.NET_CAPABILITY_TEMPORARILY_NOT_METERED;
-import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
-import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
-import static android.net.RouteInfo.RTN_UNREACHABLE;
-import static android.net.VpnManager.TYPE_VPN_PLATFORM;
-import static android.net.cts.util.IkeSessionTestUtils.CHILD_PARAMS;
-import static android.net.cts.util.IkeSessionTestUtils.TEST_IDENTITY;
-import static android.net.cts.util.IkeSessionTestUtils.TEST_KEEPALIVE_TIMEOUT_UNSET;
-import static android.net.cts.util.IkeSessionTestUtils.getTestIkeSessionParams;
-import static android.net.ipsec.ike.IkeSessionConfiguration.EXTENSION_TYPE_MOBIKE;
-import static android.net.ipsec.ike.IkeSessionParams.ESP_ENCAP_TYPE_AUTO;
-import static android.net.ipsec.ike.IkeSessionParams.ESP_ENCAP_TYPE_NONE;
-import static android.net.ipsec.ike.IkeSessionParams.ESP_ENCAP_TYPE_UDP;
-import static android.net.ipsec.ike.IkeSessionParams.ESP_IP_VERSION_AUTO;
-import static android.net.ipsec.ike.IkeSessionParams.ESP_IP_VERSION_IPV4;
-import static android.net.ipsec.ike.IkeSessionParams.ESP_IP_VERSION_IPV6;
-import static android.os.UserHandle.PER_USER_RANGE;
-import static android.telephony.CarrierConfigManager.KEY_CARRIER_CONFIG_APPLIED_BOOL;
-import static android.telephony.CarrierConfigManager.KEY_MIN_UDP_PORT_4500_NAT_TIMEOUT_SEC_INT;
-import static android.telephony.CarrierConfigManager.KEY_PREFERRED_IKE_PROTOCOL_INT;
-
-import static com.android.net.module.util.NetworkStackConstants.IPV6_MIN_MTU;
-import static com.android.server.connectivity.Vpn.AUTOMATIC_KEEPALIVE_DELAY_SECONDS;
-import static com.android.server.connectivity.Vpn.DEFAULT_LONG_LIVED_TCP_CONNS_EXPENSIVE_TIMEOUT_SEC;
-import static com.android.server.connectivity.Vpn.DEFAULT_UDP_PORT_4500_NAT_TIMEOUT_SEC_INT;
-import static com.android.server.connectivity.Vpn.PREFERRED_IKE_PROTOCOL_AUTO;
-import static com.android.server.connectivity.Vpn.PREFERRED_IKE_PROTOCOL_IPV4_UDP;
-import static com.android.server.connectivity.Vpn.PREFERRED_IKE_PROTOCOL_IPV6_ESP;
-import static com.android.server.connectivity.Vpn.PREFERRED_IKE_PROTOCOL_IPV6_UDP;
-import static com.android.testutils.HandlerUtils.waitForIdleSerialExecutor;
-import static com.android.testutils.MiscAsserts.assertThrows;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.anyLong;
-import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.ArgumentMatchers.argThat;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.ArgumentMatchers.longThat;
-import static org.mockito.Mockito.after;
-import static org.mockito.Mockito.atLeast;
-import static org.mockito.Mockito.atLeastOnce;
-import static org.mockito.Mockito.clearInvocations;
-import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.doCallRealMethod;
-import static org.mockito.Mockito.doNothing;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.doThrow;
-import static org.mockito.Mockito.inOrder;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.reset;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.timeout;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.annotation.NonNull;
-import android.annotation.UserIdInt;
-import android.app.AppOpsManager;
-import android.app.NotificationManager;
-import android.app.PendingIntent;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.content.pm.ServiceInfo;
-import android.content.pm.UserInfo;
-import android.content.res.Resources;
-import android.net.ConnectivityDiagnosticsManager;
-import android.net.ConnectivityManager;
-import android.net.INetd;
-import android.net.Ikev2VpnProfile;
-import android.net.InetAddresses;
-import android.net.InterfaceConfigurationParcel;
-import android.net.IpPrefix;
-import android.net.IpSecConfig;
-import android.net.IpSecManager;
-import android.net.IpSecTransform;
-import android.net.IpSecTunnelInterfaceResponse;
-import android.net.LinkAddress;
-import android.net.LinkProperties;
-import android.net.Network;
-import android.net.NetworkAgent;
-import android.net.NetworkAgentConfig;
-import android.net.NetworkCapabilities;
-import android.net.NetworkInfo.DetailedState;
-import android.net.RouteInfo;
-import android.net.TelephonyNetworkSpecifier;
-import android.net.UidRangeParcel;
-import android.net.VpnManager;
-import android.net.VpnProfileState;
-import android.net.VpnService;
-import android.net.VpnTransportInfo;
-import android.net.ipsec.ike.ChildSessionCallback;
-import android.net.ipsec.ike.ChildSessionConfiguration;
-import android.net.ipsec.ike.IkeFqdnIdentification;
-import android.net.ipsec.ike.IkeSessionCallback;
-import android.net.ipsec.ike.IkeSessionConfiguration;
-import android.net.ipsec.ike.IkeSessionConnectionInfo;
-import android.net.ipsec.ike.IkeSessionParams;
-import android.net.ipsec.ike.IkeTrafficSelector;
-import android.net.ipsec.ike.IkeTunnelConnectionParams;
-import android.net.ipsec.ike.exceptions.IkeException;
-import android.net.ipsec.ike.exceptions.IkeNetworkLostException;
-import android.net.ipsec.ike.exceptions.IkeNonProtocolException;
-import android.net.ipsec.ike.exceptions.IkeProtocolException;
-import android.net.ipsec.ike.exceptions.IkeTimeoutException;
-import android.net.vcn.VcnTransportInfo;
-import android.net.wifi.WifiInfo;
-import android.os.Build.VERSION_CODES;
-import android.os.Bundle;
-import android.os.INetworkManagementService;
-import android.os.ParcelFileDescriptor;
-import android.os.PersistableBundle;
-import android.os.PowerWhitelistManager;
-import android.os.Process;
-import android.os.UserHandle;
-import android.os.UserManager;
-import android.os.test.TestLooper;
-import android.provider.Settings;
-import android.security.Credentials;
-import android.telephony.CarrierConfigManager;
-import android.telephony.SubscriptionInfo;
-import android.telephony.SubscriptionManager;
-import android.telephony.TelephonyManager;
-import android.util.ArrayMap;
-import android.util.ArraySet;
-import android.util.Pair;
-import android.util.Range;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
-
-import com.android.internal.R;
-import com.android.internal.net.LegacyVpnInfo;
-import com.android.internal.net.VpnConfig;
-import com.android.internal.net.VpnProfile;
-import com.android.internal.util.HexDump;
-import com.android.internal.util.IndentingPrintWriter;
-import com.android.server.DeviceIdleInternal;
-import com.android.server.IpSecService;
-import com.android.server.VpnTestBase;
-import com.android.server.vcn.util.PersistableBundleUtils;
-import com.android.testutils.DevSdkIgnoreRule;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.AdditionalAnswers;
-import org.mockito.Answers;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Captor;
-import org.mockito.InOrder;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-import java.io.FileDescriptor;
-import java.io.IOException;
-import java.io.StringWriter;
-import java.net.Inet4Address;
-import java.net.Inet6Address;
-import java.net.InetAddress;
-import java.net.UnknownHostException;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.SortedSet;
-import java.util.TreeSet;
-import java.util.concurrent.ScheduledFuture;
-import java.util.concurrent.ScheduledThreadPoolExecutor;
-import java.util.concurrent.TimeUnit;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-/**
- * Tests for {@link Vpn}.
- *
- * Build, install and run with:
- *  runtest frameworks-net -c com.android.server.connectivity.VpnTest
- */
-@RunWith(AndroidJUnit4.class)
-@SmallTest
-public class VpnTest extends VpnTestBase {
-    private static final String TAG = "VpnTest";
-
-    @Rule
-    public final DevSdkIgnoreRule mIgnoreRule = new DevSdkIgnoreRule();
-
-    static final Network EGRESS_NETWORK = new Network(101);
-    static final String EGRESS_IFACE = "wlan0";
-    private static final String TEST_VPN_CLIENT = "2.4.6.8";
-    private static final String TEST_VPN_SERVER = "1.2.3.4";
-    private static final String TEST_VPN_IDENTITY = "identity";
-    private static final byte[] TEST_VPN_PSK = "psk".getBytes();
-
-    private static final int IP4_PREFIX_LEN = 32;
-    private static final int IP6_PREFIX_LEN = 64;
-    private static final int MIN_PORT = 0;
-    private static final int MAX_PORT = 65535;
-
-    private static final InetAddress TEST_VPN_CLIENT_IP =
-            InetAddresses.parseNumericAddress(TEST_VPN_CLIENT);
-    private static final InetAddress TEST_VPN_SERVER_IP =
-            InetAddresses.parseNumericAddress(TEST_VPN_SERVER);
-    private static final InetAddress TEST_VPN_CLIENT_IP_2 =
-            InetAddresses.parseNumericAddress("192.0.2.200");
-    private static final InetAddress TEST_VPN_SERVER_IP_2 =
-            InetAddresses.parseNumericAddress("192.0.2.201");
-    private static final InetAddress TEST_VPN_INTERNAL_IP =
-            InetAddresses.parseNumericAddress("198.51.100.10");
-    private static final InetAddress TEST_VPN_INTERNAL_IP6 =
-            InetAddresses.parseNumericAddress("2001:db8::1");
-    private static final InetAddress TEST_VPN_INTERNAL_DNS =
-            InetAddresses.parseNumericAddress("8.8.8.8");
-    private static final InetAddress TEST_VPN_INTERNAL_DNS6 =
-            InetAddresses.parseNumericAddress("2001:4860:4860::8888");
-
-    private static final IkeTrafficSelector IN_TS =
-            new IkeTrafficSelector(MIN_PORT, MAX_PORT, TEST_VPN_INTERNAL_IP, TEST_VPN_INTERNAL_IP);
-    private static final IkeTrafficSelector IN_TS6 =
-            new IkeTrafficSelector(
-                    MIN_PORT, MAX_PORT, TEST_VPN_INTERNAL_IP6, TEST_VPN_INTERNAL_IP6);
-    private static final IkeTrafficSelector OUT_TS =
-            new IkeTrafficSelector(MIN_PORT, MAX_PORT,
-                    InetAddresses.parseNumericAddress("0.0.0.0"),
-                    InetAddresses.parseNumericAddress("255.255.255.255"));
-    private static final IkeTrafficSelector OUT_TS6 =
-            new IkeTrafficSelector(
-                    MIN_PORT,
-                    MAX_PORT,
-                    InetAddresses.parseNumericAddress("::"),
-                    InetAddresses.parseNumericAddress("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"));
-
-    private static final Network TEST_NETWORK = new Network(Integer.MAX_VALUE);
-    private static final Network TEST_NETWORK_2 = new Network(Integer.MAX_VALUE - 1);
-    private static final String TEST_IFACE_NAME = "TEST_IFACE";
-    private static final int TEST_TUNNEL_RESOURCE_ID = 0x2345;
-    private static final long TEST_TIMEOUT_MS = 500L;
-    private static final long TIMEOUT_CROSSTHREAD_MS = 20_000L;
-    private static final String PRIMARY_USER_APP_EXCLUDE_KEY =
-            "VPNAPPEXCLUDED_27_com.testvpn.vpn";
-    static final String PKGS_BYTES = getPackageByteString(List.of(PKGS));
-    private static final Range<Integer> PRIMARY_USER_RANGE = uidRangeForUser(PRIMARY_USER.id);
-    private static final int TEST_KEEPALIVE_TIMER = 800;
-    private static final int TEST_SUB_ID = 1234;
-    private static final String TEST_MCCMNC = "12345";
-
-    @Mock(answer = Answers.RETURNS_DEEP_STUBS) private Context mContext;
-    @Mock private UserManager mUserManager;
-    @Mock private PackageManager mPackageManager;
-    @Mock private INetworkManagementService mNetService;
-    @Mock private INetd mNetd;
-    @Mock private AppOpsManager mAppOps;
-    @Mock private NotificationManager mNotificationManager;
-    @Mock private Vpn.SystemServices mSystemServices;
-    @Mock private Vpn.IkeSessionWrapper mIkeSessionWrapper;
-    @Mock private Vpn.Ikev2SessionCreator mIkev2SessionCreator;
-    @Mock private Vpn.VpnNetworkAgentWrapper mMockNetworkAgent;
-    @Mock private ConnectivityManager mConnectivityManager;
-    @Mock private ConnectivityDiagnosticsManager mCdm;
-    @Mock private TelephonyManager mTelephonyManager;
-    @Mock private TelephonyManager mTmPerSub;
-    @Mock private CarrierConfigManager mConfigManager;
-    @Mock private SubscriptionManager mSubscriptionManager;
-    @Mock private IpSecService mIpSecService;
-    @Mock private VpnProfileStore mVpnProfileStore;
-    private final TestExecutor mExecutor;
-    @Mock DeviceIdleInternal mDeviceIdleInternal;
-    private final VpnProfile mVpnProfile;
-
-    @Captor private ArgumentCaptor<Collection<Range<Integer>>> mUidRangesCaptor;
-
-    private IpSecManager mIpSecManager;
-    private TestDeps mTestDeps;
-
-    public static class TestExecutor extends ScheduledThreadPoolExecutor {
-        public static final long REAL_DELAY = -1;
-
-        // For the purposes of the test, run all scheduled tasks after 10ms to save
-        // execution time, unless overridden by the specific test. Set to REAL_DELAY
-        // to actually wait for the delay specified by the real call to schedule().
-        public long delayMs = 10;
-        // If this is true, execute() will call the runnable inline. This is useful because
-        // super.execute() calls schedule(), which messes with checks that scheduled() is
-        // called a given number of times.
-        public boolean executeDirect = false;
-
-        public TestExecutor() {
-            super(1);
-        }
-
-        @Override
-        public void execute(final Runnable command) {
-            // See |executeDirect| for why this is necessary.
-            if (executeDirect) {
-                command.run();
-            } else {
-                super.execute(command);
-            }
-        }
-
-        @Override
-        public ScheduledFuture<?> schedule(final Runnable command, final long delay,
-                TimeUnit unit) {
-            if (0 == delay || delayMs == REAL_DELAY) {
-                // super.execute() calls schedule() with 0, so use the real delay if it's 0.
-                return super.schedule(command, delay, unit);
-            } else {
-                return super.schedule(command, delayMs, TimeUnit.MILLISECONDS);
-            }
-        }
-    }
-
-    public VpnTest() throws Exception {
-        // Build an actual VPN profile that is capable of being converted to and from an
-        // Ikev2VpnProfile
-        final Ikev2VpnProfile.Builder builder =
-                new Ikev2VpnProfile.Builder(TEST_VPN_SERVER, TEST_VPN_IDENTITY);
-        builder.setAuthPsk(TEST_VPN_PSK);
-        builder.setBypassable(true /* isBypassable */);
-        mExecutor = spy(new TestExecutor());
-        mVpnProfile = builder.build().toVpnProfile();
-    }
-
-    @Before
-    public void setUp() throws Exception {
-        MockitoAnnotations.initMocks(this);
-
-        mIpSecManager = new IpSecManager(mContext, mIpSecService);
-        mTestDeps = spy(new TestDeps());
-        doReturn(IPV6_MIN_MTU)
-                .when(mTestDeps)
-                .calculateVpnMtu(any(), anyInt(), anyInt(), anyBoolean());
-        doReturn(1500).when(mTestDeps).getJavaNetworkInterfaceMtu(any(), anyInt());
-
-        when(mContext.getPackageManager()).thenReturn(mPackageManager);
-        setMockedPackages(sPackages);
-
-        when(mContext.getPackageName()).thenReturn(TEST_VPN_PKG);
-        when(mContext.getOpPackageName()).thenReturn(TEST_VPN_PKG);
-        mockService(UserManager.class, Context.USER_SERVICE, mUserManager);
-        mockService(AppOpsManager.class, Context.APP_OPS_SERVICE, mAppOps);
-        mockService(NotificationManager.class, Context.NOTIFICATION_SERVICE, mNotificationManager);
-        mockService(ConnectivityManager.class, Context.CONNECTIVITY_SERVICE, mConnectivityManager);
-        mockService(IpSecManager.class, Context.IPSEC_SERVICE, mIpSecManager);
-        mockService(ConnectivityDiagnosticsManager.class, Context.CONNECTIVITY_DIAGNOSTICS_SERVICE,
-                mCdm);
-        mockService(TelephonyManager.class, Context.TELEPHONY_SERVICE, mTelephonyManager);
-        mockService(CarrierConfigManager.class, Context.CARRIER_CONFIG_SERVICE, mConfigManager);
-        mockService(SubscriptionManager.class, Context.TELEPHONY_SUBSCRIPTION_SERVICE,
-                mSubscriptionManager);
-        doReturn(mTmPerSub).when(mTelephonyManager).createForSubscriptionId(anyInt());
-        when(mContext.getString(R.string.config_customVpnAlwaysOnDisconnectedDialogComponent))
-                .thenReturn(Resources.getSystem().getString(
-                        R.string.config_customVpnAlwaysOnDisconnectedDialogComponent));
-        when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_IPSEC_TUNNELS))
-                .thenReturn(true);
-
-        // Used by {@link Notification.Builder}
-        ApplicationInfo applicationInfo = new ApplicationInfo();
-        applicationInfo.targetSdkVersion = VERSION_CODES.CUR_DEVELOPMENT;
-        when(mContext.getApplicationInfo()).thenReturn(applicationInfo);
-        when(mPackageManager.getApplicationInfoAsUser(anyString(), anyInt(), anyInt()))
-                .thenReturn(applicationInfo);
-
-        doNothing().when(mNetService).registerObserver(any());
-
-        // Deny all appops by default.
-        when(mAppOps.noteOpNoThrow(anyString(), anyInt(), anyString(), any(), any()))
-                .thenReturn(AppOpsManager.MODE_IGNORED);
-
-        // Setup IpSecService
-        final IpSecTunnelInterfaceResponse tunnelResp =
-                new IpSecTunnelInterfaceResponse(
-                        IpSecManager.Status.OK, TEST_TUNNEL_RESOURCE_ID, TEST_IFACE_NAME);
-        when(mIpSecService.createTunnelInterface(any(), any(), any(), any(), any()))
-                .thenReturn(tunnelResp);
-        doReturn(new LinkProperties()).when(mConnectivityManager).getLinkProperties(any());
-
-        // The unit test should know what kind of permission it needs and set the permission by
-        // itself, so set the default value of Context#checkCallingOrSelfPermission to
-        // PERMISSION_DENIED.
-        doReturn(PERMISSION_DENIED).when(mContext).checkCallingOrSelfPermission(any());
-
-        // Set up mIkev2SessionCreator and mExecutor
-        resetIkev2SessionCreator(mIkeSessionWrapper);
-    }
-
-    private void resetIkev2SessionCreator(Vpn.IkeSessionWrapper ikeSession) {
-        reset(mIkev2SessionCreator);
-        when(mIkev2SessionCreator.createIkeSession(any(), any(), any(), any(), any(), any()))
-                .thenReturn(ikeSession);
-    }
-
-    private <T> void mockService(Class<T> clazz, String name, T service) {
-        doReturn(service).when(mContext).getSystemService(name);
-        doReturn(name).when(mContext).getSystemServiceName(clazz);
-        if (mContext.getSystemService(clazz).getClass().equals(Object.class)) {
-            // Test is using mockito-extended (mContext uses Answers.RETURNS_DEEP_STUBS and returned
-            // a mock object on a final method)
-            doCallRealMethod().when(mContext).getSystemService(clazz);
-        }
-    }
-
-    private Set<Range<Integer>> rangeSet(Range<Integer> ... ranges) {
-        final Set<Range<Integer>> range = new ArraySet<>();
-        for (Range<Integer> r : ranges) range.add(r);
-
-        return range;
-    }
-
-    private static Range<Integer> uidRangeForUser(int userId) {
-        return new Range<Integer>(userId * PER_USER_RANGE, (userId + 1) * PER_USER_RANGE - 1);
-    }
-
-    private Range<Integer> uidRange(int start, int stop) {
-        return new Range<Integer>(start, stop);
-    }
-
-    private static String getPackageByteString(List<String> packages) {
-        try {
-            return HexDump.toHexString(
-                    PersistableBundleUtils.toDiskStableBytes(PersistableBundleUtils.fromList(
-                            packages, PersistableBundleUtils.STRING_SERIALIZER)),
-                        true /* upperCase */);
-        } catch (IOException e) {
-            return null;
-        }
-    }
-
-    @Test
-    public void testRestrictedProfilesAreAddedToVpn() {
-        setMockedUsers(PRIMARY_USER, SECONDARY_USER, RESTRICTED_PROFILE_A, RESTRICTED_PROFILE_B);
-
-        final Vpn vpn = createVpn(PRIMARY_USER.id);
-
-        // Assume the user can have restricted profiles.
-        doReturn(true).when(mUserManager).canHaveRestrictedProfile();
-        final Set<Range<Integer>> ranges =
-                vpn.createUserAndRestrictedProfilesRanges(PRIMARY_USER.id, null, null);
-
-        assertEquals(rangeSet(PRIMARY_USER_RANGE, uidRangeForUser(RESTRICTED_PROFILE_A.id)),
-                 ranges);
-    }
-
-    @Test
-    public void testManagedProfilesAreNotAddedToVpn() {
-        setMockedUsers(PRIMARY_USER, MANAGED_PROFILE_A);
-
-        final Vpn vpn = createVpn(PRIMARY_USER.id);
-        final Set<Range<Integer>> ranges = vpn.createUserAndRestrictedProfilesRanges(
-                PRIMARY_USER.id, null, null);
-
-        assertEquals(rangeSet(PRIMARY_USER_RANGE), ranges);
-    }
-
-    @Test
-    public void testAddUserToVpnOnlyAddsOneUser() {
-        setMockedUsers(PRIMARY_USER, RESTRICTED_PROFILE_A, MANAGED_PROFILE_A);
-
-        final Vpn vpn = createVpn(PRIMARY_USER.id);
-        final Set<Range<Integer>> ranges = new ArraySet<>();
-        vpn.addUserToRanges(ranges, PRIMARY_USER.id, null, null);
-
-        assertEquals(rangeSet(PRIMARY_USER_RANGE), ranges);
-    }
-
-    @Test
-    public void testUidAllowAndDenylist() throws Exception {
-        final Vpn vpn = createVpn(PRIMARY_USER.id);
-        final Range<Integer> user = PRIMARY_USER_RANGE;
-        final int userStart = user.getLower();
-        final int userStop = user.getUpper();
-        final String[] packages = {PKGS[0], PKGS[1], PKGS[2]};
-
-        // Allowed list
-        final Set<Range<Integer>> allow = vpn.createUserAndRestrictedProfilesRanges(PRIMARY_USER.id,
-                Arrays.asList(packages), null /* disallowedApplications */);
-        assertEquals(rangeSet(
-                uidRange(userStart + PKG_UIDS[0], userStart + PKG_UIDS[0]),
-                uidRange(userStart + PKG_UIDS[1], userStart + PKG_UIDS[2]),
-                uidRange(Process.toSdkSandboxUid(userStart + PKG_UIDS[0]),
-                         Process.toSdkSandboxUid(userStart + PKG_UIDS[0])),
-                uidRange(Process.toSdkSandboxUid(userStart + PKG_UIDS[1]),
-                         Process.toSdkSandboxUid(userStart + PKG_UIDS[2]))),
-                allow);
-
-        // Denied list
-        final Set<Range<Integer>> disallow =
-                vpn.createUserAndRestrictedProfilesRanges(PRIMARY_USER.id,
-                        null /* allowedApplications */, Arrays.asList(packages));
-        assertEquals(rangeSet(
-                uidRange(userStart, userStart + PKG_UIDS[0] - 1),
-                uidRange(userStart + PKG_UIDS[0] + 1, userStart + PKG_UIDS[1] - 1),
-                /* Empty range between UIDS[1] and UIDS[2], should be excluded, */
-                uidRange(userStart + PKG_UIDS[2] + 1,
-                         Process.toSdkSandboxUid(userStart + PKG_UIDS[0] - 1)),
-                uidRange(Process.toSdkSandboxUid(userStart + PKG_UIDS[0] + 1),
-                         Process.toSdkSandboxUid(userStart + PKG_UIDS[1] - 1)),
-                uidRange(Process.toSdkSandboxUid(userStart + PKG_UIDS[2] + 1), userStop)),
-                disallow);
-    }
-
-    private void verifyPowerSaveTempWhitelistApp(String packageName) {
-        verify(mDeviceIdleInternal, timeout(TEST_TIMEOUT_MS)).addPowerSaveTempWhitelistApp(
-                anyInt(), eq(packageName), anyLong(), anyInt(), eq(false),
-                eq(PowerWhitelistManager.REASON_VPN), eq("VpnManager event"));
-    }
-
-    @Test
-    public void testGetAlwaysAndOnGetLockDown() throws Exception {
-        final Vpn vpn = createVpn(PRIMARY_USER.id);
-
-        // Default state.
-        assertFalse(vpn.getAlwaysOn());
-        assertFalse(vpn.getLockdown());
-
-        // Set always-on without lockdown.
-        assertTrue(vpn.setAlwaysOnPackage(PKGS[1], false, Collections.emptyList()));
-        assertTrue(vpn.getAlwaysOn());
-        assertFalse(vpn.getLockdown());
-
-        // Set always-on with lockdown.
-        assertTrue(vpn.setAlwaysOnPackage(PKGS[1], true, Collections.emptyList()));
-        assertTrue(vpn.getAlwaysOn());
-        assertTrue(vpn.getLockdown());
-
-        // Remove always-on configuration.
-        assertTrue(vpn.setAlwaysOnPackage(null, false, Collections.emptyList()));
-        assertFalse(vpn.getAlwaysOn());
-        assertFalse(vpn.getLockdown());
-    }
-
-    @Test
-    public void testAlwaysOnWithoutLockdown() throws Exception {
-        final Vpn vpn = createVpn(PRIMARY_USER.id);
-        assertTrue(vpn.setAlwaysOnPackage(
-                PKGS[1], false /* lockdown */, null /* lockdownAllowlist */));
-        verify(mConnectivityManager, never()).setRequireVpnForUids(anyBoolean(), any());
-
-        assertTrue(vpn.setAlwaysOnPackage(
-                null /* packageName */, false /* lockdown */, null /* lockdownAllowlist */));
-        verify(mConnectivityManager, never()).setRequireVpnForUids(anyBoolean(), any());
-    }
-
-    @Test
-    public void testLockdownChangingPackage() throws Exception {
-        final Vpn vpn = createVpn(PRIMARY_USER.id);
-        final Range<Integer> user = PRIMARY_USER_RANGE;
-        final int userStart = user.getLower();
-        final int userStop = user.getUpper();
-        // Set always-on without lockdown.
-        assertTrue(vpn.setAlwaysOnPackage(PKGS[1], false, null));
-
-        // Set always-on with lockdown.
-        assertTrue(vpn.setAlwaysOnPackage(PKGS[1], true, null));
-        verify(mConnectivityManager).setRequireVpnForUids(true, toRanges(new UidRangeParcel[] {
-                new UidRangeParcel(userStart, userStart + PKG_UIDS[1] - 1),
-                new UidRangeParcel(userStart + PKG_UIDS[1] + 1,
-                                   Process.toSdkSandboxUid(userStart + PKG_UIDS[1] - 1)),
-                new UidRangeParcel(Process.toSdkSandboxUid(userStart + PKG_UIDS[1] + 1), userStop)
-        }));
-
-        // Switch to another app.
-        assertTrue(vpn.setAlwaysOnPackage(PKGS[3], true, null));
-        verify(mConnectivityManager).setRequireVpnForUids(false, toRanges(new UidRangeParcel[] {
-                new UidRangeParcel(userStart, userStart + PKG_UIDS[1] - 1),
-                new UidRangeParcel(userStart + PKG_UIDS[1] + 1,
-                                   Process.toSdkSandboxUid(userStart + PKG_UIDS[1] - 1)),
-                new UidRangeParcel(Process.toSdkSandboxUid(userStart + PKG_UIDS[1] + 1), userStop)
-        }));
-        verify(mConnectivityManager).setRequireVpnForUids(true, toRanges(new UidRangeParcel[] {
-                new UidRangeParcel(userStart, userStart + PKG_UIDS[3] - 1),
-                new UidRangeParcel(userStart + PKG_UIDS[3] + 1,
-                                   Process.toSdkSandboxUid(userStart + PKG_UIDS[3] - 1)),
-                new UidRangeParcel(Process.toSdkSandboxUid(userStart + PKG_UIDS[3] + 1), userStop)
-        }));
-    }
-
-    @Test
-    public void testLockdownAllowlist() throws Exception {
-        final Vpn vpn = createVpn(PRIMARY_USER.id);
-        final Range<Integer> user = PRIMARY_USER_RANGE;
-        final int userStart = user.getLower();
-        final int userStop = user.getUpper();
-        // Set always-on with lockdown and allow app PKGS[2] from lockdown.
-        assertTrue(vpn.setAlwaysOnPackage(
-                PKGS[1], true, Collections.singletonList(PKGS[2])));
-        verify(mConnectivityManager).setRequireVpnForUids(true, toRanges(new UidRangeParcel[]  {
-                new UidRangeParcel(userStart, userStart + PKG_UIDS[1] - 1),
-                new UidRangeParcel(userStart + PKG_UIDS[2] + 1,
-                                   Process.toSdkSandboxUid(userStart + PKG_UIDS[1]) - 1),
-                new UidRangeParcel(Process.toSdkSandboxUid(userStart + PKG_UIDS[2] + 1), userStop)
-        }));
-        // Change allowed app list to PKGS[3].
-        assertTrue(vpn.setAlwaysOnPackage(
-                PKGS[1], true, Collections.singletonList(PKGS[3])));
-        verify(mConnectivityManager).setRequireVpnForUids(false, toRanges(new UidRangeParcel[] {
-                new UidRangeParcel(userStart + PKG_UIDS[2] + 1,
-                                   Process.toSdkSandboxUid(userStart + PKG_UIDS[1] - 1)),
-                new UidRangeParcel(Process.toSdkSandboxUid(userStart + PKG_UIDS[2] + 1), userStop)
-        }));
-        verify(mConnectivityManager).setRequireVpnForUids(true, toRanges(new UidRangeParcel[] {
-                new UidRangeParcel(userStart + PKG_UIDS[1] + 1, userStart + PKG_UIDS[3] - 1),
-                new UidRangeParcel(userStart + PKG_UIDS[3] + 1,
-                                   Process.toSdkSandboxUid(userStart + PKG_UIDS[1] - 1)),
-                new UidRangeParcel(Process.toSdkSandboxUid(userStart + PKG_UIDS[1] + 1),
-                                   Process.toSdkSandboxUid(userStart + PKG_UIDS[3] - 1)),
-                new UidRangeParcel(Process.toSdkSandboxUid(userStart + PKG_UIDS[3] + 1), userStop)
-        }));
-
-        // Change the VPN app.
-        assertTrue(vpn.setAlwaysOnPackage(
-                PKGS[0], true, Collections.singletonList(PKGS[3])));
-        verify(mConnectivityManager).setRequireVpnForUids(false, toRanges(new UidRangeParcel[] {
-                new UidRangeParcel(userStart, userStart + PKG_UIDS[1] - 1),
-                new UidRangeParcel(userStart + PKG_UIDS[1] + 1, userStart + PKG_UIDS[3] - 1),
-                new UidRangeParcel(userStart + PKG_UIDS[3] + 1,
-                                   Process.toSdkSandboxUid(userStart + PKG_UIDS[1] - 1)),
-                new UidRangeParcel(Process.toSdkSandboxUid(userStart + PKG_UIDS[1] + 1),
-                                   Process.toSdkSandboxUid(userStart + PKG_UIDS[3] - 1))
-        }));
-        verify(mConnectivityManager).setRequireVpnForUids(true, toRanges(new UidRangeParcel[] {
-                new UidRangeParcel(userStart, userStart + PKG_UIDS[0] - 1),
-                new UidRangeParcel(userStart + PKG_UIDS[0] + 1, userStart + PKG_UIDS[3] - 1),
-                new UidRangeParcel(userStart + PKG_UIDS[3] + 1,
-                                   Process.toSdkSandboxUid(userStart + PKG_UIDS[0] - 1)),
-                new UidRangeParcel(Process.toSdkSandboxUid(userStart + PKG_UIDS[0] + 1),
-                                   Process.toSdkSandboxUid(userStart + PKG_UIDS[3] - 1))
-        }));
-
-        // Remove the list of allowed packages.
-        assertTrue(vpn.setAlwaysOnPackage(PKGS[0], true, null));
-        verify(mConnectivityManager).setRequireVpnForUids(false, toRanges(new UidRangeParcel[] {
-                new UidRangeParcel(userStart + PKG_UIDS[0] + 1, userStart + PKG_UIDS[3] - 1),
-                new UidRangeParcel(userStart + PKG_UIDS[3] + 1,
-                                   Process.toSdkSandboxUid(userStart + PKG_UIDS[0] - 1)),
-                new UidRangeParcel(Process.toSdkSandboxUid(userStart + PKG_UIDS[0] + 1),
-                                   Process.toSdkSandboxUid(userStart + PKG_UIDS[3] - 1)),
-                new UidRangeParcel(Process.toSdkSandboxUid(userStart + PKG_UIDS[3] + 1), userStop)
-        }));
-        verify(mConnectivityManager).setRequireVpnForUids(true, toRanges(new UidRangeParcel[] {
-                new UidRangeParcel(userStart + PKG_UIDS[0] + 1,
-                                   Process.toSdkSandboxUid(userStart + PKG_UIDS[0] - 1)),
-                new UidRangeParcel(Process.toSdkSandboxUid(userStart + PKG_UIDS[0] + 1), userStop),
-        }));
-
-        // Add the list of allowed packages.
-        assertTrue(vpn.setAlwaysOnPackage(
-                PKGS[0], true, Collections.singletonList(PKGS[1])));
-        verify(mConnectivityManager).setRequireVpnForUids(false, toRanges(new UidRangeParcel[] {
-                new UidRangeParcel(userStart + PKG_UIDS[0] + 1,
-                                   Process.toSdkSandboxUid(userStart + PKG_UIDS[0] - 1)),
-                new UidRangeParcel(Process.toSdkSandboxUid(userStart + PKG_UIDS[0] + 1), userStop),
-        }));
-        verify(mConnectivityManager).setRequireVpnForUids(true, toRanges(new UidRangeParcel[] {
-                new UidRangeParcel(userStart + PKG_UIDS[0] + 1, userStart + PKG_UIDS[1] - 1),
-                new UidRangeParcel(userStart + PKG_UIDS[1] + 1,
-                                   Process.toSdkSandboxUid(userStart + PKG_UIDS[0] - 1)),
-                new UidRangeParcel(Process.toSdkSandboxUid(userStart + PKG_UIDS[0] + 1),
-                                   Process.toSdkSandboxUid(userStart + PKG_UIDS[1] - 1)),
-                new UidRangeParcel(Process.toSdkSandboxUid(userStart + PKG_UIDS[1] + 1), userStop)
-        }));
-
-        // Try allowing a package with a comma, should be rejected.
-        assertFalse(vpn.setAlwaysOnPackage(
-                PKGS[0], true, Collections.singletonList("a.b,c.d")));
-
-        // Pass a non-existent packages in the allowlist, they (and only they) should be ignored.
-        // allowed package should change from PGKS[1] to PKGS[2].
-        assertTrue(vpn.setAlwaysOnPackage(
-                PKGS[0], true, Arrays.asList("com.foo.app", PKGS[2], "com.bar.app")));
-        verify(mConnectivityManager).setRequireVpnForUids(false, toRanges(new UidRangeParcel[] {
-                new UidRangeParcel(userStart + PKG_UIDS[0] + 1, userStart + PKG_UIDS[1] - 1),
-                new UidRangeParcel(userStart + PKG_UIDS[1] + 1,
-                                   Process.toSdkSandboxUid(userStart + PKG_UIDS[0] - 1)),
-                new UidRangeParcel(Process.toSdkSandboxUid(userStart + PKG_UIDS[0] + 1),
-                                   Process.toSdkSandboxUid(userStart + PKG_UIDS[1] - 1)),
-                new UidRangeParcel(Process.toSdkSandboxUid(userStart + PKG_UIDS[1] + 1), userStop)
-        }));
-        verify(mConnectivityManager).setRequireVpnForUids(true, toRanges(new UidRangeParcel[] {
-                new UidRangeParcel(userStart + PKG_UIDS[0] + 1, userStart + PKG_UIDS[2] - 1),
-                new UidRangeParcel(userStart + PKG_UIDS[2] + 1,
-                                   Process.toSdkSandboxUid(userStart + PKG_UIDS[0] - 1)),
-                new UidRangeParcel(Process.toSdkSandboxUid(userStart + PKG_UIDS[0] + 1),
-                                   Process.toSdkSandboxUid(userStart + PKG_UIDS[2] - 1)),
-                new UidRangeParcel(Process.toSdkSandboxUid(userStart + PKG_UIDS[2] + 1), userStop)
-        }));
-    }
-
-    @Test
-    public void testLockdownSystemUser() throws Exception {
-        final Vpn vpn = createVpn(SYSTEM_USER_ID);
-
-        // Uid 0 is always excluded and PKG_UIDS[1] is the uid of the VPN.
-        final List<Integer> excludedUids = new ArrayList<>(List.of(0, PKG_UIDS[1]));
-        final List<Range<Integer>> ranges = makeVpnUidRange(SYSTEM_USER_ID, excludedUids);
-
-        // Set always-on with lockdown.
-        assertTrue(vpn.setAlwaysOnPackage(
-                PKGS[1], true /* lockdown */, null /* lockdownAllowlist */));
-        verify(mConnectivityManager).setRequireVpnForUids(true, ranges);
-
-        // Disable always-on with lockdown.
-        assertTrue(vpn.setAlwaysOnPackage(
-                null /* packageName */, false /* lockdown */, null /* lockdownAllowlist */));
-        verify(mConnectivityManager).setRequireVpnForUids(false, ranges);
-
-        // Set always-on with lockdown and allow the app PKGS[2].
-        excludedUids.add(PKG_UIDS[2]);
-        final List<Range<Integer>> ranges2 = makeVpnUidRange(SYSTEM_USER_ID, excludedUids);
-        assertTrue(vpn.setAlwaysOnPackage(
-                PKGS[1], true /* lockdown */, Collections.singletonList(PKGS[2])));
-        verify(mConnectivityManager).setRequireVpnForUids(true, ranges2);
-
-        // Disable always-on with lockdown.
-        assertTrue(vpn.setAlwaysOnPackage(
-                null /* packageName */, false /* lockdown */, null /* lockdownAllowlist */));
-        verify(mConnectivityManager).setRequireVpnForUids(false, ranges2);
-    }
-
-    @Test
-    public void testLockdownRuleRepeatability() throws Exception {
-        final Vpn vpn = createVpn(PRIMARY_USER.id);
-        final UidRangeParcel[] primaryUserRangeParcel = new UidRangeParcel[] {
-                new UidRangeParcel(PRIMARY_USER_RANGE.getLower(), PRIMARY_USER_RANGE.getUpper())};
-        // Given legacy lockdown is already enabled,
-        vpn.setLockdown(true);
-        verify(mConnectivityManager, times(1)).setRequireVpnForUids(true,
-                toRanges(primaryUserRangeParcel));
-
-        // Enabling legacy lockdown twice should do nothing.
-        vpn.setLockdown(true);
-        verify(mConnectivityManager, times(1)).setRequireVpnForUids(anyBoolean(), any());
-
-        // And disabling should remove the rules exactly once.
-        vpn.setLockdown(false);
-        verify(mConnectivityManager, times(1)).setRequireVpnForUids(false,
-                toRanges(primaryUserRangeParcel));
-
-        // Removing the lockdown again should have no effect.
-        vpn.setLockdown(false);
-        verify(mConnectivityManager, times(2)).setRequireVpnForUids(anyBoolean(), any());
-    }
-
-    private ArrayList<Range<Integer>> toRanges(UidRangeParcel[] ranges) {
-        ArrayList<Range<Integer>> rangesArray = new ArrayList<>(ranges.length);
-        for (int i = 0; i < ranges.length; i++) {
-            rangesArray.add(new Range<>(ranges[i].start, ranges[i].stop));
-        }
-        return rangesArray;
-    }
-
-    @Test
-    public void testLockdownRuleReversibility() throws Exception {
-        doReturn(PERMISSION_GRANTED).when(mContext).checkCallingOrSelfPermission(CONTROL_VPN);
-        final Vpn vpn = createVpn(PRIMARY_USER.id);
-        final UidRangeParcel[] entireUser = {
-            new UidRangeParcel(PRIMARY_USER_RANGE.getLower(), PRIMARY_USER_RANGE.getUpper())
-        };
-        final UidRangeParcel[] exceptPkg0 = {
-            new UidRangeParcel(entireUser[0].start, entireUser[0].start + PKG_UIDS[0] - 1),
-            new UidRangeParcel(entireUser[0].start + PKG_UIDS[0] + 1,
-                               Process.toSdkSandboxUid(entireUser[0].start + PKG_UIDS[0] - 1)),
-            new UidRangeParcel(Process.toSdkSandboxUid(entireUser[0].start + PKG_UIDS[0] + 1),
-                               entireUser[0].stop),
-        };
-
-        final InOrder order = inOrder(mConnectivityManager);
-
-        // Given lockdown is enabled with no package (legacy VPN),
-        vpn.setLockdown(true);
-        order.verify(mConnectivityManager).setRequireVpnForUids(true, toRanges(entireUser));
-
-        // When a new VPN package is set the rules should change to cover that package.
-        vpn.prepare(null, PKGS[0], VpnManager.TYPE_VPN_SERVICE);
-        order.verify(mConnectivityManager).setRequireVpnForUids(false, toRanges(entireUser));
-        order.verify(mConnectivityManager).setRequireVpnForUids(true, toRanges(exceptPkg0));
-
-        // When that VPN package is unset, everything should be undone again in reverse.
-        vpn.prepare(null, VpnConfig.LEGACY_VPN, VpnManager.TYPE_VPN_SERVICE);
-        order.verify(mConnectivityManager).setRequireVpnForUids(false, toRanges(exceptPkg0));
-        order.verify(mConnectivityManager).setRequireVpnForUids(true, toRanges(entireUser));
-    }
-
-    @Test
-    public void testOnUserAddedAndRemoved_restrictedUser() throws Exception {
-        final InOrder order = inOrder(mMockNetworkAgent);
-        final Vpn vpn = createVpn(PRIMARY_USER.id);
-        final Set<Range<Integer>> initialRange = rangeSet(PRIMARY_USER_RANGE);
-        // Note since mVpnProfile is a Ikev2VpnProfile, this starts an IkeV2VpnRunner.
-        startLegacyVpn(vpn, mVpnProfile);
-        // Set an initial Uid range and mock the network agent
-        vpn.mNetworkCapabilities.setUids(initialRange);
-        vpn.mNetworkAgent = mMockNetworkAgent;
-
-        // Add the restricted user
-        setMockedUsers(PRIMARY_USER, RESTRICTED_PROFILE_A);
-        vpn.onUserAdded(RESTRICTED_PROFILE_A.id);
-        // Expect restricted user range to be added to the NetworkCapabilities.
-        final Set<Range<Integer>> expectRestrictedRange =
-                rangeSet(PRIMARY_USER_RANGE, uidRangeForUser(RESTRICTED_PROFILE_A.id));
-        assertEquals(expectRestrictedRange, vpn.mNetworkCapabilities.getUids());
-        order.verify(mMockNetworkAgent).doSendNetworkCapabilities(
-                argThat(nc -> expectRestrictedRange.equals(nc.getUids())));
-
-        // Remove the restricted user
-        vpn.onUserRemoved(RESTRICTED_PROFILE_A.id);
-        // Expect restricted user range to be removed from the NetworkCapabilities.
-        assertEquals(initialRange, vpn.mNetworkCapabilities.getUids());
-        order.verify(mMockNetworkAgent).doSendNetworkCapabilities(
-                argThat(nc -> initialRange.equals(nc.getUids())));
-    }
-
-    @Test
-    public void testOnUserAddedAndRemoved_restrictedUserLockdown() throws Exception {
-        final UidRangeParcel[] primaryUserRangeParcel = new UidRangeParcel[] {
-                new UidRangeParcel(PRIMARY_USER_RANGE.getLower(), PRIMARY_USER_RANGE.getUpper())};
-        final Range<Integer> restrictedUserRange = uidRangeForUser(RESTRICTED_PROFILE_A.id);
-        final UidRangeParcel[] restrictedUserRangeParcel = new UidRangeParcel[] {
-                new UidRangeParcel(restrictedUserRange.getLower(), restrictedUserRange.getUpper())};
-        final Vpn vpn = createVpn(PRIMARY_USER.id);
-
-        // Set lockdown calls setRequireVpnForUids
-        vpn.setLockdown(true);
-        verify(mConnectivityManager).setRequireVpnForUids(true, toRanges(primaryUserRangeParcel));
-
-        // Add the restricted user
-        doReturn(true).when(mUserManager).canHaveRestrictedProfile();
-        setMockedUsers(PRIMARY_USER, RESTRICTED_PROFILE_A);
-        vpn.onUserAdded(RESTRICTED_PROFILE_A.id);
-
-        // Expect restricted user range to be added.
-        verify(mConnectivityManager).setRequireVpnForUids(true,
-                toRanges(restrictedUserRangeParcel));
-
-        // Mark as partial indicates that the user is removed, mUserManager.getAliveUsers() does not
-        // return the restricted user but it is still returned in mUserManager.getUserInfo().
-        RESTRICTED_PROFILE_A.partial = true;
-        // Remove the restricted user
-        vpn.onUserRemoved(RESTRICTED_PROFILE_A.id);
-        verify(mConnectivityManager).setRequireVpnForUids(false,
-                toRanges(restrictedUserRangeParcel));
-        // reset to avoid affecting other tests since RESTRICTED_PROFILE_A is static.
-        RESTRICTED_PROFILE_A.partial = false;
-    }
-
-    @Test
-    public void testOnUserAddedAndRemoved_restrictedUserAlwaysOn() throws Exception {
-        final Vpn vpn = createVpn(PRIMARY_USER.id);
-
-        // setAlwaysOnPackage() calls setRequireVpnForUids()
-        assertTrue(vpn.setAlwaysOnPackage(
-                PKGS[0], true /* lockdown */, null /* lockdownAllowlist */));
-        final List<Integer> excludedUids = List.of(PKG_UIDS[0]);
-        final List<Range<Integer>> primaryRanges =
-                makeVpnUidRange(PRIMARY_USER.id, excludedUids);
-        verify(mConnectivityManager).setRequireVpnForUids(true, primaryRanges);
-
-        // Add the restricted user
-        doReturn(true).when(mUserManager).canHaveRestrictedProfile();
-        setMockedUsers(PRIMARY_USER, RESTRICTED_PROFILE_A);
-        vpn.onUserAdded(RESTRICTED_PROFILE_A.id);
-
-        final List<Range<Integer>> restrictedRanges =
-                makeVpnUidRange(RESTRICTED_PROFILE_A.id, excludedUids);
-        // Expect restricted user range to be added.
-        verify(mConnectivityManager).setRequireVpnForUids(true, restrictedRanges);
-
-        // Mark as partial indicates that the user is removed, mUserManager.getAliveUsers() does not
-        // return the restricted user but it is still returned in mUserManager.getUserInfo().
-        RESTRICTED_PROFILE_A.partial = true;
-        // Remove the restricted user
-        vpn.onUserRemoved(RESTRICTED_PROFILE_A.id);
-        verify(mConnectivityManager).setRequireVpnForUids(false, restrictedRanges);
-
-        // reset to avoid affecting other tests since RESTRICTED_PROFILE_A is static.
-        RESTRICTED_PROFILE_A.partial = false;
-    }
-
-    @Test
-    public void testPrepare_throwSecurityExceptionWhenGivenPackageDoesNotBelongToTheCaller()
-            throws Exception {
-        mTestDeps.mIgnoreCallingUidChecks = false;
-        final Vpn vpn = createVpn();
-        assertThrows(SecurityException.class,
-                () -> vpn.prepare("com.not.vpn.owner", null, VpnManager.TYPE_VPN_SERVICE));
-        assertThrows(SecurityException.class,
-                () -> vpn.prepare(null, "com.not.vpn.owner", VpnManager.TYPE_VPN_SERVICE));
-        assertThrows(SecurityException.class,
-                () -> vpn.prepare("com.not.vpn.owner1", "com.not.vpn.owner2",
-                        VpnManager.TYPE_VPN_SERVICE));
-    }
-
-    @Test
-    public void testPrepare_bothOldPackageAndNewPackageAreNull() throws Exception {
-        final Vpn vpn = createVpn();
-        assertTrue(vpn.prepare(null, null, VpnManager.TYPE_VPN_SERVICE));
-
-    }
-
-    @Test
-    public void testPrepare_legacyVpnWithoutControlVpn()
-            throws Exception {
-        doThrow(new SecurityException("no CONTROL_VPN")).when(mContext)
-                .enforceCallingOrSelfPermission(eq(CONTROL_VPN), any());
-        final Vpn vpn = createVpn();
-        assertThrows(SecurityException.class,
-                () -> vpn.prepare(null, VpnConfig.LEGACY_VPN, VpnManager.TYPE_VPN_SERVICE));
-
-        // CONTROL_VPN can be held by the caller or another system server process - both are
-        // allowed. Just checking for `enforceCallingPermission` may not be sufficient.
-        verify(mContext, never()).enforceCallingPermission(eq(CONTROL_VPN), any());
-    }
-
-    @Test
-    public void testPrepare_legacyVpnWithControlVpn()
-            throws Exception {
-        doNothing().when(mContext).enforceCallingOrSelfPermission(eq(CONTROL_VPN), any());
-        final Vpn vpn = createVpn();
-        assertTrue(vpn.prepare(null, VpnConfig.LEGACY_VPN, VpnManager.TYPE_VPN_SERVICE));
-
-        // CONTROL_VPN can be held by the caller or another system server process - both are
-        // allowed. Just checking for `enforceCallingPermission` may not be sufficient.
-        verify(mContext, never()).enforceCallingPermission(eq(CONTROL_VPN), any());
-    }
-
-    @Test
-    public void testIsAlwaysOnPackageSupported() throws Exception {
-        final Vpn vpn = createVpn(PRIMARY_USER.id);
-
-        ApplicationInfo appInfo = new ApplicationInfo();
-        when(mPackageManager.getApplicationInfoAsUser(eq(PKGS[0]), anyInt(), eq(PRIMARY_USER.id)))
-                .thenReturn(appInfo);
-
-        ServiceInfo svcInfo = new ServiceInfo();
-        ResolveInfo resInfo = new ResolveInfo();
-        resInfo.serviceInfo = svcInfo;
-        when(mPackageManager.queryIntentServicesAsUser(any(), eq(PackageManager.GET_META_DATA),
-                eq(PRIMARY_USER.id)))
-                .thenReturn(Collections.singletonList(resInfo));
-
-        // null package name should return false
-        assertFalse(vpn.isAlwaysOnPackageSupported(null));
-
-        // Pre-N apps are not supported
-        appInfo.targetSdkVersion = VERSION_CODES.M;
-        assertFalse(vpn.isAlwaysOnPackageSupported(PKGS[0]));
-
-        // N+ apps are supported by default
-        appInfo.targetSdkVersion = VERSION_CODES.N;
-        assertTrue(vpn.isAlwaysOnPackageSupported(PKGS[0]));
-
-        // Apps that opt out explicitly are not supported
-        appInfo.targetSdkVersion = VERSION_CODES.CUR_DEVELOPMENT;
-        Bundle metaData = new Bundle();
-        metaData.putBoolean(VpnService.SERVICE_META_DATA_SUPPORTS_ALWAYS_ON, false);
-        svcInfo.metaData = metaData;
-        assertFalse(vpn.isAlwaysOnPackageSupported(PKGS[0]));
-    }
-
-    @Test
-    public void testNotificationShownForAlwaysOnApp() throws Exception {
-        final UserHandle userHandle = UserHandle.of(PRIMARY_USER.id);
-        final Vpn vpn = createVpn(PRIMARY_USER.id);
-        setMockedUsers(PRIMARY_USER);
-
-        final InOrder order = inOrder(mNotificationManager);
-
-        // Don't show a notification for regular disconnected states.
-        vpn.updateState(DetailedState.DISCONNECTED, TAG);
-        order.verify(mNotificationManager, atLeastOnce()).cancel(anyString(), anyInt());
-
-        // Start showing a notification for disconnected once always-on.
-        vpn.setAlwaysOnPackage(PKGS[0], false, null);
-        order.verify(mNotificationManager).notify(anyString(), anyInt(), any());
-
-        // Stop showing the notification once connected.
-        vpn.updateState(DetailedState.CONNECTED, TAG);
-        order.verify(mNotificationManager).cancel(anyString(), anyInt());
-
-        // Show the notification if we disconnect again.
-        vpn.updateState(DetailedState.DISCONNECTED, TAG);
-        order.verify(mNotificationManager).notify(anyString(), anyInt(), any());
-
-        // Notification should be cleared after unsetting always-on package.
-        vpn.setAlwaysOnPackage(null, false, null);
-        order.verify(mNotificationManager).cancel(anyString(), anyInt());
-    }
-
-    /**
-     * The profile name should NOT change between releases for backwards compatibility
-     *
-     * <p>If this is changed between releases, the {@link Vpn#getVpnProfilePrivileged()} method MUST
-     * be updated to ensure backward compatibility.
-     */
-    @Test
-    public void testGetProfileNameForPackage() throws Exception {
-        final Vpn vpn = createVpn(PRIMARY_USER.id);
-        setMockedUsers(PRIMARY_USER);
-
-        final String expected = Credentials.PLATFORM_VPN + PRIMARY_USER.id + "_" + TEST_VPN_PKG;
-        assertEquals(expected, vpn.getProfileNameForPackage(TEST_VPN_PKG));
-    }
-
-    private Vpn createVpn(String... grantedOps) throws Exception {
-        return createVpn(PRIMARY_USER, grantedOps);
-    }
-
-    private Vpn createVpn(UserInfo user, String... grantedOps) throws Exception {
-        final Vpn vpn = createVpn(user.id);
-        setMockedUsers(user);
-
-        for (final String opStr : grantedOps) {
-            when(mAppOps.noteOpNoThrow(opStr, Process.myUid(), TEST_VPN_PKG,
-                    null /* attributionTag */, null /* message */))
-                    .thenReturn(AppOpsManager.MODE_ALLOWED);
-        }
-
-        return vpn;
-    }
-
-    private void checkProvisionVpnProfile(Vpn vpn, boolean expectedResult, String... checkedOps) {
-        assertEquals(expectedResult, vpn.provisionVpnProfile(TEST_VPN_PKG, mVpnProfile));
-
-        // The profile should always be stored, whether or not consent has been previously granted.
-        verify(mVpnProfileStore)
-                .put(
-                        eq(vpn.getProfileNameForPackage(TEST_VPN_PKG)),
-                        eq(mVpnProfile.encode()));
-
-        for (final String checkedOpStr : checkedOps) {
-            verify(mAppOps).noteOpNoThrow(checkedOpStr, Process.myUid(), TEST_VPN_PKG,
-                    null /* attributionTag */, null /* message */);
-        }
-    }
-
-    @Test
-    public void testProvisionVpnProfileNoIpsecTunnels() throws Exception {
-        when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_IPSEC_TUNNELS))
-                .thenReturn(false);
-        final Vpn vpn = createVpn(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN);
-
-        try {
-            checkProvisionVpnProfile(
-                    vpn, true /* expectedResult */, AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN);
-            fail("Expected exception due to missing feature");
-        } catch (UnsupportedOperationException expected) {
-        }
-    }
-
-    private String startVpnForVerifyAppExclusionList(Vpn vpn) throws Exception {
-        when(mVpnProfileStore.get(vpn.getProfileNameForPackage(TEST_VPN_PKG)))
-                .thenReturn(mVpnProfile.encode());
-        when(mVpnProfileStore.get(PRIMARY_USER_APP_EXCLUDE_KEY))
-                .thenReturn(HexDump.hexStringToByteArray(PKGS_BYTES));
-        final String sessionKey = vpn.startVpnProfile(TEST_VPN_PKG);
-        final Set<Range<Integer>> uidRanges = vpn.createUserAndRestrictedProfilesRanges(
-                PRIMARY_USER.id, null /* allowedApplications */, Arrays.asList(PKGS));
-        verify(mConnectivityManager).setVpnDefaultForUids(eq(sessionKey), eq(uidRanges));
-        clearInvocations(mConnectivityManager);
-        verify(mVpnProfileStore).get(eq(vpn.getProfileNameForPackage(TEST_VPN_PKG)));
-        vpn.mNetworkAgent = mMockNetworkAgent;
-
-        return sessionKey;
-    }
-
-    private Vpn prepareVpnForVerifyAppExclusionList() throws Exception {
-        final Vpn vpn = createVpn(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN);
-        startVpnForVerifyAppExclusionList(vpn);
-
-        return vpn;
-    }
-
-    @Test
-    public void testSetAndGetAppExclusionList() throws Exception {
-        final Vpn vpn = createVpn(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN);
-        final String sessionKey = startVpnForVerifyAppExclusionList(vpn);
-        verify(mVpnProfileStore, never()).put(eq(PRIMARY_USER_APP_EXCLUDE_KEY), any());
-        vpn.setAppExclusionList(TEST_VPN_PKG, Arrays.asList(PKGS));
-        verify(mVpnProfileStore)
-                .put(eq(PRIMARY_USER_APP_EXCLUDE_KEY),
-                     eq(HexDump.hexStringToByteArray(PKGS_BYTES)));
-        final Set<Range<Integer>> uidRanges = vpn.createUserAndRestrictedProfilesRanges(
-                PRIMARY_USER.id, null /* allowedApplications */, Arrays.asList(PKGS));
-        verify(mConnectivityManager).setVpnDefaultForUids(eq(sessionKey), eq(uidRanges));
-        assertEquals(uidRanges, vpn.mNetworkCapabilities.getUids());
-        assertEquals(Arrays.asList(PKGS), vpn.getAppExclusionList(TEST_VPN_PKG));
-    }
-
-    @Test
-    public void testRefreshPlatformVpnAppExclusionList_updatesExcludedUids() throws Exception {
-        final Vpn vpn = createVpn(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN);
-        final String sessionKey = startVpnForVerifyAppExclusionList(vpn);
-        vpn.setAppExclusionList(TEST_VPN_PKG, Arrays.asList(PKGS));
-        final Set<Range<Integer>> uidRanges = vpn.createUserAndRestrictedProfilesRanges(
-                PRIMARY_USER.id, null /* allowedApplications */, Arrays.asList(PKGS));
-        verify(mConnectivityManager).setVpnDefaultForUids(eq(sessionKey), eq(uidRanges));
-        verify(mMockNetworkAgent).doSendNetworkCapabilities(any());
-        assertEquals(Arrays.asList(PKGS), vpn.getAppExclusionList(TEST_VPN_PKG));
-
-        reset(mMockNetworkAgent);
-
-        // Remove one of the package
-        List<Integer> newExcludedUids = toList(PKG_UIDS);
-        newExcludedUids.remove((Integer) PKG_UIDS[0]);
-        Set<Range<Integer>> newUidRanges = makeVpnUidRangeSet(PRIMARY_USER.id, newExcludedUids);
-        sPackages.remove(PKGS[0]);
-        vpn.refreshPlatformVpnAppExclusionList();
-
-        // List in keystore is not changed, but UID for the removed packages is no longer exempted.
-        assertEquals(Arrays.asList(PKGS), vpn.getAppExclusionList(TEST_VPN_PKG));
-        assertEquals(newUidRanges, vpn.mNetworkCapabilities.getUids());
-        ArgumentCaptor<NetworkCapabilities> ncCaptor =
-                ArgumentCaptor.forClass(NetworkCapabilities.class);
-        verify(mMockNetworkAgent).doSendNetworkCapabilities(ncCaptor.capture());
-        assertEquals(newUidRanges, ncCaptor.getValue().getUids());
-        verify(mConnectivityManager).setVpnDefaultForUids(eq(sessionKey), eq(newUidRanges));
-
-        reset(mMockNetworkAgent);
-
-        // Add the package back
-        newExcludedUids.add(PKG_UIDS[0]);
-        newUidRanges = makeVpnUidRangeSet(PRIMARY_USER.id, newExcludedUids);
-        sPackages.put(PKGS[0], PKG_UIDS[0]);
-        vpn.refreshPlatformVpnAppExclusionList();
-
-        // List in keystore is not changed and the uid list should be updated in the net cap.
-        assertEquals(Arrays.asList(PKGS), vpn.getAppExclusionList(TEST_VPN_PKG));
-        assertEquals(newUidRanges, vpn.mNetworkCapabilities.getUids());
-        verify(mMockNetworkAgent).doSendNetworkCapabilities(ncCaptor.capture());
-        assertEquals(newUidRanges, ncCaptor.getValue().getUids());
-
-        // The uidRange is the same as the original setAppExclusionList so this is the second call
-        verify(mConnectivityManager, times(2))
-                .setVpnDefaultForUids(eq(sessionKey), eq(newUidRanges));
-    }
-
-    private List<Range<Integer>> makeVpnUidRange(int userId, List<Integer> excludedAppIdList) {
-        final SortedSet<Integer> list = new TreeSet<>();
-
-        final int userBase = userId * UserHandle.PER_USER_RANGE;
-        for (int appId : excludedAppIdList) {
-            final int uid = UserHandle.getUid(userId, appId);
-            list.add(uid);
-            if (Process.isApplicationUid(uid)) {
-                list.add(Process.toSdkSandboxUid(uid)); // Add Sdk Sandbox UID
-            }
-        }
-
-        final int minUid = userBase;
-        final int maxUid = userBase + UserHandle.PER_USER_RANGE - 1;
-        final List<Range<Integer>> ranges = new ArrayList<>();
-
-        // Iterate the list to create the ranges between each uid.
-        int start = minUid;
-        for (int uid : list) {
-            if (uid == start) {
-                start++;
-            } else {
-                ranges.add(new Range<>(start, uid - 1));
-                start = uid + 1;
-            }
-        }
-
-        // Create the range between last uid and max uid.
-        if (start <= maxUid) {
-            ranges.add(new Range<>(start, maxUid));
-        }
-
-        return ranges;
-    }
-
-    private Set<Range<Integer>> makeVpnUidRangeSet(int userId, List<Integer> excludedAppIdList) {
-        return new ArraySet<>(makeVpnUidRange(userId, excludedAppIdList));
-    }
-
-    @Test
-    public void testSetAndGetAppExclusionListRestrictedUser() throws Exception {
-        final Vpn vpn = prepareVpnForVerifyAppExclusionList();
-
-        // Mock it to restricted profile
-        when(mUserManager.getUserInfo(anyInt())).thenReturn(RESTRICTED_PROFILE_A);
-
-        // Restricted users cannot configure VPNs
-        assertThrows(SecurityException.class,
-                () -> vpn.setAppExclusionList(TEST_VPN_PKG, new ArrayList<>()));
-
-        assertEquals(Arrays.asList(PKGS), vpn.getAppExclusionList(TEST_VPN_PKG));
-    }
-
-    @Test
-    public void testProvisionVpnProfilePreconsented() throws Exception {
-        final Vpn vpn = createVpn(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN);
-
-        checkProvisionVpnProfile(
-                vpn, true /* expectedResult */, AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN);
-    }
-
-    @Test
-    public void testProvisionVpnProfileNotPreconsented() throws Exception {
-        final Vpn vpn = createVpn();
-
-        // Expect that both the ACTIVATE_VPN and ACTIVATE_PLATFORM_VPN were tried, but the caller
-        // had neither.
-        checkProvisionVpnProfile(vpn, false /* expectedResult */,
-                AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN, AppOpsManager.OPSTR_ACTIVATE_VPN);
-    }
-
-    @Test
-    public void testProvisionVpnProfileVpnServicePreconsented() throws Exception {
-        final Vpn vpn = createVpn(AppOpsManager.OPSTR_ACTIVATE_VPN);
-
-        checkProvisionVpnProfile(vpn, true /* expectedResult */, AppOpsManager.OPSTR_ACTIVATE_VPN);
-    }
-
-    @Test
-    public void testProvisionVpnProfileTooLarge() throws Exception {
-        final Vpn vpn = createVpn(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN);
-
-        final VpnProfile bigProfile = new VpnProfile("");
-        bigProfile.name = new String(new byte[Vpn.MAX_VPN_PROFILE_SIZE_BYTES + 1]);
-
-        try {
-            vpn.provisionVpnProfile(TEST_VPN_PKG, bigProfile);
-            fail("Expected IAE due to profile size");
-        } catch (IllegalArgumentException expected) {
-        }
-    }
-
-    @Test
-    public void testProvisionVpnProfileRestrictedUser() throws Exception {
-        final Vpn vpn =
-                createVpn(
-                        RESTRICTED_PROFILE_A, AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN);
-
-        try {
-            vpn.provisionVpnProfile(TEST_VPN_PKG, mVpnProfile);
-            fail("Expected SecurityException due to restricted user");
-        } catch (SecurityException expected) {
-        }
-    }
-
-    @Test
-    public void testDeleteVpnProfile() throws Exception {
-        final Vpn vpn = createVpn();
-
-        vpn.deleteVpnProfile(TEST_VPN_PKG);
-
-        verify(mVpnProfileStore)
-                .remove(eq(vpn.getProfileNameForPackage(TEST_VPN_PKG)));
-    }
-
-    @Test
-    public void testDeleteVpnProfileRestrictedUser() throws Exception {
-        final Vpn vpn =
-                createVpn(
-                        RESTRICTED_PROFILE_A, AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN);
-
-        try {
-            vpn.deleteVpnProfile(TEST_VPN_PKG);
-            fail("Expected SecurityException due to restricted user");
-        } catch (SecurityException expected) {
-        }
-    }
-
-    @Test
-    public void testGetVpnProfilePrivileged() throws Exception {
-        final Vpn vpn = createVpn();
-
-        when(mVpnProfileStore.get(vpn.getProfileNameForPackage(TEST_VPN_PKG)))
-                .thenReturn(new VpnProfile("").encode());
-
-        vpn.getVpnProfilePrivileged(TEST_VPN_PKG);
-
-        verify(mVpnProfileStore).get(eq(vpn.getProfileNameForPackage(TEST_VPN_PKG)));
-    }
-
-    private void verifyPlatformVpnIsActivated(String packageName) {
-        verify(mAppOps).noteOpNoThrow(
-                eq(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN),
-                eq(Process.myUid()),
-                eq(packageName),
-                eq(null) /* attributionTag */,
-                eq(null) /* message */);
-        verify(mAppOps).startOp(
-                eq(AppOpsManager.OPSTR_ESTABLISH_VPN_MANAGER),
-                eq(UserHandle.getUid(PRIMARY_USER.id, Process.myUid())),
-                eq(packageName),
-                eq(null) /* attributionTag */,
-                eq(null) /* message */);
-    }
-
-    private void verifyPlatformVpnIsDeactivated(String packageName) {
-        // Add a small delay to double confirm that finishOp is only called once.
-        verify(mAppOps, after(100)).finishOp(
-                eq(AppOpsManager.OPSTR_ESTABLISH_VPN_MANAGER),
-                eq(UserHandle.getUid(PRIMARY_USER.id, Process.myUid())),
-                eq(packageName),
-                eq(null) /* attributionTag */);
-    }
-
-    @Test
-    public void testStartVpnProfile() throws Exception {
-        final Vpn vpn = createVpn(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN);
-
-        when(mVpnProfileStore.get(vpn.getProfileNameForPackage(TEST_VPN_PKG)))
-                .thenReturn(mVpnProfile.encode());
-
-        vpn.startVpnProfile(TEST_VPN_PKG);
-
-        verify(mVpnProfileStore).get(eq(vpn.getProfileNameForPackage(TEST_VPN_PKG)));
-        verifyPlatformVpnIsActivated(TEST_VPN_PKG);
-    }
-
-    @Test
-    public void testStartVpnProfileVpnServicePreconsented() throws Exception {
-        final Vpn vpn = createVpn(AppOpsManager.OPSTR_ACTIVATE_VPN);
-
-        when(mVpnProfileStore.get(vpn.getProfileNameForPackage(TEST_VPN_PKG)))
-                .thenReturn(mVpnProfile.encode());
-
-        vpn.startVpnProfile(TEST_VPN_PKG);
-
-        // Verify that the ACTIVATE_VPN appop was checked, but no error was thrown.
-        verify(mAppOps).noteOpNoThrow(AppOpsManager.OPSTR_ACTIVATE_VPN, Process.myUid(),
-                TEST_VPN_PKG, null /* attributionTag */, null /* message */);
-    }
-
-    @Test
-    public void testStartVpnProfileNotConsented() throws Exception {
-        final Vpn vpn = createVpn();
-
-        try {
-            vpn.startVpnProfile(TEST_VPN_PKG);
-            fail("Expected failure due to no user consent");
-        } catch (SecurityException expected) {
-        }
-
-        // Verify both appops were checked.
-        verify(mAppOps)
-                .noteOpNoThrow(
-                        eq(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN),
-                        eq(Process.myUid()),
-                        eq(TEST_VPN_PKG),
-                        eq(null) /* attributionTag */,
-                        eq(null) /* message */);
-        verify(mAppOps).noteOpNoThrow(AppOpsManager.OPSTR_ACTIVATE_VPN, Process.myUid(),
-                TEST_VPN_PKG, null /* attributionTag */, null /* message */);
-
-        // Keystore should never have been accessed.
-        verify(mVpnProfileStore, never()).get(any());
-    }
-
-    @Test
-    public void testStartVpnProfileMissingProfile() throws Exception {
-        final Vpn vpn = createVpn(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN);
-
-        when(mVpnProfileStore.get(vpn.getProfileNameForPackage(TEST_VPN_PKG))).thenReturn(null);
-
-        try {
-            vpn.startVpnProfile(TEST_VPN_PKG);
-            fail("Expected failure due to missing profile");
-        } catch (IllegalArgumentException expected) {
-        }
-
-        verify(mVpnProfileStore).get(vpn.getProfileNameForPackage(TEST_VPN_PKG));
-        verify(mAppOps)
-                .noteOpNoThrow(
-                        eq(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN),
-                        eq(Process.myUid()),
-                        eq(TEST_VPN_PKG),
-                        eq(null) /* attributionTag */,
-                        eq(null) /* message */);
-    }
-
-    @Test
-    public void testStartVpnProfileRestrictedUser() throws Exception {
-        final Vpn vpn = createVpn(RESTRICTED_PROFILE_A, AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN);
-
-        try {
-            vpn.startVpnProfile(TEST_VPN_PKG);
-            fail("Expected SecurityException due to restricted user");
-        } catch (SecurityException expected) {
-        }
-    }
-
-    @Test
-    public void testStopVpnProfileRestrictedUser() throws Exception {
-        final Vpn vpn = createVpn(RESTRICTED_PROFILE_A, AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN);
-
-        try {
-            vpn.stopVpnProfile(TEST_VPN_PKG);
-            fail("Expected SecurityException due to restricted user");
-        } catch (SecurityException expected) {
-        }
-    }
-
-    @Test
-    public void testStartOpAndFinishOpWillBeCalledWhenPlatformVpnIsOnAndOff() throws Exception {
-        final Vpn vpn = createVpn(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN);
-        when(mVpnProfileStore.get(vpn.getProfileNameForPackage(TEST_VPN_PKG)))
-                .thenReturn(mVpnProfile.encode());
-        vpn.startVpnProfile(TEST_VPN_PKG);
-        verifyPlatformVpnIsActivated(TEST_VPN_PKG);
-        // Add a small delay to make sure that startOp is only called once.
-        verify(mAppOps, after(100).times(1)).startOp(
-                eq(AppOpsManager.OPSTR_ESTABLISH_VPN_MANAGER),
-                eq(UserHandle.getUid(PRIMARY_USER.id, Process.myUid())),
-                eq(TEST_VPN_PKG),
-                eq(null) /* attributionTag */,
-                eq(null) /* message */);
-        // Check that the startOp is not called with OPSTR_ESTABLISH_VPN_SERVICE.
-        verify(mAppOps, never()).startOp(
-                eq(AppOpsManager.OPSTR_ESTABLISH_VPN_SERVICE),
-                eq(UserHandle.getUid(PRIMARY_USER.id, Process.myUid())),
-                eq(TEST_VPN_PKG),
-                eq(null) /* attributionTag */,
-                eq(null) /* message */);
-        vpn.stopVpnProfile(TEST_VPN_PKG);
-        verifyPlatformVpnIsDeactivated(TEST_VPN_PKG);
-    }
-
-    @Test
-    public void testStartOpWithSeamlessHandover() throws Exception {
-        // Create with SYSTEM_USER so that establish() will match the user ID when checking
-        // against Binder.getCallerUid
-        final Vpn vpn = createVpn(SYSTEM_USER, AppOpsManager.OPSTR_ACTIVATE_VPN);
-        assertTrue(vpn.prepare(TEST_VPN_PKG, null, VpnManager.TYPE_VPN_SERVICE));
-        final VpnConfig config = new VpnConfig();
-        config.user = "VpnTest";
-        config.addresses.add(new LinkAddress("192.0.2.2/32"));
-        config.mtu = 1450;
-        final ResolveInfo resolveInfo = new ResolveInfo();
-        final ServiceInfo serviceInfo = new ServiceInfo();
-        serviceInfo.permission = BIND_VPN_SERVICE;
-        resolveInfo.serviceInfo = serviceInfo;
-        when(mPackageManager.resolveService(any(), anyInt())).thenReturn(resolveInfo);
-        when(mContext.bindServiceAsUser(any(), any(), anyInt(), any())).thenReturn(true);
-        vpn.establish(config);
-        verify(mAppOps, times(1)).startOp(
-                eq(AppOpsManager.OPSTR_ESTABLISH_VPN_SERVICE),
-                eq(Process.myUid()),
-                eq(TEST_VPN_PKG),
-                eq(null) /* attributionTag */,
-                eq(null) /* message */);
-        // Call establish() twice with the same config, it should match seamless handover case and
-        // startOp() shouldn't be called again.
-        vpn.establish(config);
-        verify(mAppOps, times(1)).startOp(
-                eq(AppOpsManager.OPSTR_ESTABLISH_VPN_SERVICE),
-                eq(Process.myUid()),
-                eq(TEST_VPN_PKG),
-                eq(null) /* attributionTag */,
-                eq(null) /* message */);
-    }
-
-    private void verifyVpnManagerEvent(String sessionKey, String category, int errorClass,
-            int errorCode, String[] packageName, @NonNull VpnProfileState... profileState) {
-        final Context userContext =
-                mContext.createContextAsUser(UserHandle.of(PRIMARY_USER.id), 0 /* flags */);
-        final ArgumentCaptor<Intent> intentArgumentCaptor = ArgumentCaptor.forClass(Intent.class);
-
-        final int verifyTimes = profileState.length;
-        verify(userContext, timeout(TEST_TIMEOUT_MS).times(verifyTimes))
-                .startService(intentArgumentCaptor.capture());
-
-        for (int i = 0; i < verifyTimes; i++) {
-            final Intent intent = intentArgumentCaptor.getAllValues().get(i);
-            assertEquals(packageName[i], intent.getPackage());
-            assertEquals(sessionKey, intent.getStringExtra(VpnManager.EXTRA_SESSION_KEY));
-            final Set<String> categories = intent.getCategories();
-            assertTrue(categories.contains(category));
-            assertEquals(1, categories.size());
-            assertEquals(errorClass,
-                    intent.getIntExtra(VpnManager.EXTRA_ERROR_CLASS, -1 /* defaultValue */));
-            assertEquals(errorCode,
-                    intent.getIntExtra(VpnManager.EXTRA_ERROR_CODE, -1 /* defaultValue */));
-            // CATEGORY_EVENT_DEACTIVATED_BY_USER & CATEGORY_EVENT_ALWAYS_ON_STATE_CHANGED won't
-            // send NetworkCapabilities & LinkProperties to VPN app.
-            // For ERROR_CODE_NETWORK_LOST, the NetworkCapabilities & LinkProperties of underlying
-            // network will be cleared. So the VPN app will receive null for those 2 extra values.
-            if (category.equals(VpnManager.CATEGORY_EVENT_DEACTIVATED_BY_USER)
-                    || category.equals(VpnManager.CATEGORY_EVENT_ALWAYS_ON_STATE_CHANGED)
-                    || errorCode == VpnManager.ERROR_CODE_NETWORK_LOST) {
-                assertNull(intent.getParcelableExtra(
-                        VpnManager.EXTRA_UNDERLYING_NETWORK_CAPABILITIES));
-                assertNull(intent.getParcelableExtra(VpnManager.EXTRA_UNDERLYING_LINK_PROPERTIES));
-            } else {
-                assertNotNull(intent.getParcelableExtra(
-                        VpnManager.EXTRA_UNDERLYING_NETWORK_CAPABILITIES));
-                assertNotNull(intent.getParcelableExtra(
-                        VpnManager.EXTRA_UNDERLYING_LINK_PROPERTIES));
-            }
-
-            assertEquals(profileState[i], intent.getParcelableExtra(
-                    VpnManager.EXTRA_VPN_PROFILE_STATE, VpnProfileState.class));
-        }
-        reset(userContext);
-    }
-
-    private void verifyDeactivatedByUser(String sessionKey, String[] packageName) {
-        // CATEGORY_EVENT_DEACTIVATED_BY_USER is not an error event, so both of errorClass and
-        // errorCode won't be set.
-        verifyVpnManagerEvent(sessionKey, VpnManager.CATEGORY_EVENT_DEACTIVATED_BY_USER,
-                -1 /* errorClass */, -1 /* errorCode */, packageName,
-                // VPN NetworkAgnet does not switch to CONNECTED in the test, and the state is not
-                // important here. Verify that the state as it is, i.e. CONNECTING state.
-                new VpnProfileState(VpnProfileState.STATE_CONNECTING,
-                        sessionKey, false /* alwaysOn */, false /* lockdown */));
-    }
-
-    private void verifyAlwaysOnStateChanged(String[] packageName, VpnProfileState... profileState) {
-        verifyVpnManagerEvent(null /* sessionKey */,
-                VpnManager.CATEGORY_EVENT_ALWAYS_ON_STATE_CHANGED, -1 /* errorClass */,
-                -1 /* errorCode */, packageName, profileState);
-    }
-
-    @Test
-    public void testVpnManagerEventForUserDeactivated() throws Exception {
-        // For security reasons, Vpn#prepare() will check that oldPackage and newPackage are either
-        // null or the package of the caller. This test will call Vpn#prepare() to pretend the old
-        // VPN is replaced by a new one. But only Settings can change to some other packages, and
-        // this is checked with CONTROL_VPN so simulate holding CONTROL_VPN in order to pass the
-        // security checks.
-        doReturn(PERMISSION_GRANTED).when(mContext).checkCallingOrSelfPermission(CONTROL_VPN);
-        final Vpn vpn = createVpn(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN);
-        when(mVpnProfileStore.get(vpn.getProfileNameForPackage(TEST_VPN_PKG)))
-                .thenReturn(mVpnProfile.encode());
-
-        // Test the case that the user deactivates the vpn in vpn app.
-        final String sessionKey1 = vpn.startVpnProfile(TEST_VPN_PKG);
-        verifyPlatformVpnIsActivated(TEST_VPN_PKG);
-        vpn.stopVpnProfile(TEST_VPN_PKG);
-        verifyPlatformVpnIsDeactivated(TEST_VPN_PKG);
-        verifyPowerSaveTempWhitelistApp(TEST_VPN_PKG);
-        reset(mDeviceIdleInternal);
-        verifyDeactivatedByUser(sessionKey1, new String[] {TEST_VPN_PKG});
-        reset(mAppOps);
-
-        // Test the case that the user chooses another vpn and the original one is replaced.
-        final String sessionKey2 = vpn.startVpnProfile(TEST_VPN_PKG);
-        verifyPlatformVpnIsActivated(TEST_VPN_PKG);
-        vpn.prepare(TEST_VPN_PKG, "com.new.vpn" /* newPackage */, TYPE_VPN_PLATFORM);
-        verifyPlatformVpnIsDeactivated(TEST_VPN_PKG);
-        verifyPowerSaveTempWhitelistApp(TEST_VPN_PKG);
-        reset(mDeviceIdleInternal);
-        verifyDeactivatedByUser(sessionKey2, new String[] {TEST_VPN_PKG});
-    }
-
-    @Test
-    public void testVpnManagerEventForAlwaysOnChanged() throws Exception {
-        // Calling setAlwaysOnPackage() needs to hold CONTROL_VPN.
-        doReturn(PERMISSION_GRANTED).when(mContext).checkCallingOrSelfPermission(CONTROL_VPN);
-        final Vpn vpn = createVpn(PRIMARY_USER.id);
-        // Enable VPN always-on for PKGS[1].
-        assertTrue(vpn.setAlwaysOnPackage(PKGS[1], false /* lockdown */,
-                null /* lockdownAllowlist */));
-        verifyPowerSaveTempWhitelistApp(PKGS[1]);
-        reset(mDeviceIdleInternal);
-        verifyAlwaysOnStateChanged(new String[] {PKGS[1]},
-                new VpnProfileState(VpnProfileState.STATE_DISCONNECTED,
-                        null /* sessionKey */, true /* alwaysOn */, false /* lockdown */));
-
-        // Enable VPN lockdown for PKGS[1].
-        assertTrue(vpn.setAlwaysOnPackage(PKGS[1], true /* lockdown */,
-                null /* lockdownAllowlist */));
-        verifyPowerSaveTempWhitelistApp(PKGS[1]);
-        reset(mDeviceIdleInternal);
-        verifyAlwaysOnStateChanged(new String[] {PKGS[1]},
-                new VpnProfileState(VpnProfileState.STATE_DISCONNECTED,
-                        null /* sessionKey */, true /* alwaysOn */, true /* lockdown */));
-
-        // Disable VPN lockdown for PKGS[1].
-        assertTrue(vpn.setAlwaysOnPackage(PKGS[1], false /* lockdown */,
-                null /* lockdownAllowlist */));
-        verifyPowerSaveTempWhitelistApp(PKGS[1]);
-        reset(mDeviceIdleInternal);
-        verifyAlwaysOnStateChanged(new String[] {PKGS[1]},
-                new VpnProfileState(VpnProfileState.STATE_DISCONNECTED,
-                        null /* sessionKey */, true /* alwaysOn */, false /* lockdown */));
-
-        // Disable VPN always-on.
-        assertTrue(vpn.setAlwaysOnPackage(null, false /* lockdown */,
-                null /* lockdownAllowlist */));
-        verifyPowerSaveTempWhitelistApp(PKGS[1]);
-        reset(mDeviceIdleInternal);
-        verifyAlwaysOnStateChanged(new String[] {PKGS[1]},
-                new VpnProfileState(VpnProfileState.STATE_DISCONNECTED,
-                        null /* sessionKey */, false /* alwaysOn */, false /* lockdown */));
-
-        // Enable VPN always-on for PKGS[1] again.
-        assertTrue(vpn.setAlwaysOnPackage(PKGS[1], false /* lockdown */,
-                null /* lockdownAllowlist */));
-        verifyPowerSaveTempWhitelistApp(PKGS[1]);
-        reset(mDeviceIdleInternal);
-        verifyAlwaysOnStateChanged(new String[] {PKGS[1]},
-                new VpnProfileState(VpnProfileState.STATE_DISCONNECTED,
-                        null /* sessionKey */, true /* alwaysOn */, false /* lockdown */));
-
-        // Enable VPN always-on for PKGS[2].
-        assertTrue(vpn.setAlwaysOnPackage(PKGS[2], false /* lockdown */,
-                null /* lockdownAllowlist */));
-        verifyPowerSaveTempWhitelistApp(PKGS[2]);
-        reset(mDeviceIdleInternal);
-        // PKGS[1] is replaced with PKGS[2].
-        // Pass 2 VpnProfileState objects to verifyVpnManagerEvent(), the first one is sent to
-        // PKGS[1] to notify PKGS[1] that the VPN always-on is disabled, the second one is sent to
-        // PKGS[2] to notify PKGS[2] that the VPN always-on is enabled.
-        verifyAlwaysOnStateChanged(new String[] {PKGS[1], PKGS[2]},
-                new VpnProfileState(VpnProfileState.STATE_DISCONNECTED,
-                        null /* sessionKey */, false /* alwaysOn */, false /* lockdown */),
-                new VpnProfileState(VpnProfileState.STATE_DISCONNECTED,
-                        null /* sessionKey */, true /* alwaysOn */, false /* lockdown */));
-    }
-
-    @Test
-    public void testReconnectVpnManagerVpnWithAlwaysOnEnabled() throws Exception {
-        final Vpn vpn = createVpn(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN);
-        when(mVpnProfileStore.get(vpn.getProfileNameForPackage(TEST_VPN_PKG)))
-                .thenReturn(mVpnProfile.encode());
-        vpn.startVpnProfile(TEST_VPN_PKG);
-        verifyPlatformVpnIsActivated(TEST_VPN_PKG);
-
-        // Enable VPN always-on for TEST_VPN_PKG.
-        assertTrue(vpn.setAlwaysOnPackage(TEST_VPN_PKG, false /* lockdown */,
-                null /* lockdownAllowlist */));
-
-        // Reset to verify next startVpnProfile.
-        reset(mAppOps);
-
-        vpn.stopVpnProfile(TEST_VPN_PKG);
-
-        // Reconnect the vpn with different package will cause exception.
-        assertThrows(SecurityException.class, () -> vpn.startVpnProfile(PKGS[0]));
-
-        // Reconnect the vpn again with the vpn always on package w/o exception.
-        vpn.startVpnProfile(TEST_VPN_PKG);
-        verifyPlatformVpnIsActivated(TEST_VPN_PKG);
-    }
-
-    @Test
-    public void testLockdown_enableDisableWhileConnected() throws Exception {
-        final PlatformVpnSnapshot vpnSnapShot = verifySetupPlatformVpn(
-                createIkeConfig(createIkeConnectInfo(), true /* isMobikeEnabled */));
-
-        final InOrder order = inOrder(mTestDeps);
-        order.verify(mTestDeps, timeout(TIMEOUT_CROSSTHREAD_MS))
-                .newNetworkAgent(any(), any(), any(), any(), any(), any(),
-                        argThat(config -> config.allowBypass), any(), any());
-
-        // Make VPN lockdown.
-        assertTrue(vpnSnapShot.vpn.setAlwaysOnPackage(TEST_VPN_PKG, true /* lockdown */,
-                null /* lockdownAllowlist */));
-
-        order.verify(mTestDeps, timeout(TIMEOUT_CROSSTHREAD_MS))
-                .newNetworkAgent(any(), any(), any(), any(), any(), any(),
-                argThat(config -> !config.allowBypass), any(), any());
-
-        // Disable lockdown.
-        assertTrue(vpnSnapShot.vpn.setAlwaysOnPackage(TEST_VPN_PKG, false /* lockdown */,
-                null /* lockdownAllowlist */));
-
-        order.verify(mTestDeps, timeout(TIMEOUT_CROSSTHREAD_MS))
-                .newNetworkAgent(any(), any(), any(), any(), any(), any(),
-                        argThat(config -> config.allowBypass), any(), any());
-    }
-
-    @Test
-    public void testSetPackageAuthorizationVpnService() throws Exception {
-        final Vpn vpn = createVpn();
-
-        assertTrue(vpn.setPackageAuthorization(TEST_VPN_PKG, VpnManager.TYPE_VPN_SERVICE));
-        verify(mAppOps)
-                .setMode(
-                        eq(AppOpsManager.OPSTR_ACTIVATE_VPN),
-                        eq(UserHandle.getUid(PRIMARY_USER.id, Process.myUid())),
-                        eq(TEST_VPN_PKG),
-                        eq(AppOpsManager.MODE_ALLOWED));
-    }
-
-    @Test
-    public void testSetPackageAuthorizationPlatformVpn() throws Exception {
-        final Vpn vpn = createVpn();
-
-        assertTrue(vpn.setPackageAuthorization(TEST_VPN_PKG, TYPE_VPN_PLATFORM));
-        verify(mAppOps)
-                .setMode(
-                        eq(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN),
-                        eq(UserHandle.getUid(PRIMARY_USER.id, Process.myUid())),
-                        eq(TEST_VPN_PKG),
-                        eq(AppOpsManager.MODE_ALLOWED));
-    }
-
-    @Test
-    public void testSetPackageAuthorizationRevokeAuthorization() throws Exception {
-        final Vpn vpn = createVpn();
-
-        assertTrue(vpn.setPackageAuthorization(TEST_VPN_PKG, VpnManager.TYPE_VPN_NONE));
-        verify(mAppOps)
-                .setMode(
-                        eq(AppOpsManager.OPSTR_ACTIVATE_VPN),
-                        eq(UserHandle.getUid(PRIMARY_USER.id, Process.myUid())),
-                        eq(TEST_VPN_PKG),
-                        eq(AppOpsManager.MODE_IGNORED));
-        verify(mAppOps)
-                .setMode(
-                        eq(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN),
-                        eq(UserHandle.getUid(PRIMARY_USER.id, Process.myUid())),
-                        eq(TEST_VPN_PKG),
-                        eq(AppOpsManager.MODE_IGNORED));
-    }
-
-    private NetworkCallback triggerOnAvailableAndGetCallback() throws Exception {
-        return triggerOnAvailableAndGetCallback(new NetworkCapabilities.Builder().build());
-    }
-
-    private NetworkCallback triggerOnAvailableAndGetCallback(
-            @NonNull final NetworkCapabilities caps) throws Exception {
-        final ArgumentCaptor<NetworkCallback> networkCallbackCaptor =
-                ArgumentCaptor.forClass(NetworkCallback.class);
-        verify(mConnectivityManager, timeout(TEST_TIMEOUT_MS))
-                .registerSystemDefaultNetworkCallback(networkCallbackCaptor.capture(), any());
-
-        // onAvailable() will trigger onDefaultNetworkChanged(), so NetdUtils#setInterfaceUp will be
-        // invoked. Set the return value of INetd#interfaceGetCfg to prevent NullPointerException.
-        final InterfaceConfigurationParcel config = new InterfaceConfigurationParcel();
-        config.flags = new String[] {IF_STATE_DOWN};
-        when(mNetd.interfaceGetCfg(anyString())).thenReturn(config);
-        final NetworkCallback cb = networkCallbackCaptor.getValue();
-        cb.onAvailable(TEST_NETWORK);
-        // Trigger onCapabilitiesChanged() and onLinkPropertiesChanged() so the test can verify that
-        // if NetworkCapabilities and LinkProperties of underlying network will be sent/cleared or
-        // not.
-        // See verifyVpnManagerEvent().
-        cb.onCapabilitiesChanged(TEST_NETWORK, caps);
-        cb.onLinkPropertiesChanged(TEST_NETWORK, new LinkProperties());
-        return cb;
-    }
-
-    private void verifyInterfaceSetCfgWithFlags(String flag) throws Exception {
-        // Add a timeout for waiting for interfaceSetCfg to be called.
-        verify(mNetd, timeout(TEST_TIMEOUT_MS)).interfaceSetCfg(argThat(
-                config -> Arrays.asList(config.flags).contains(flag)));
-    }
-
-    private void doTestPlatformVpnWithException(IkeException exception,
-            String category, int errorType, int errorCode) throws Exception {
-        final ArgumentCaptor<IkeSessionCallback> captor =
-                ArgumentCaptor.forClass(IkeSessionCallback.class);
-
-        final Vpn vpn = createVpn(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN);
-        when(mVpnProfileStore.get(vpn.getProfileNameForPackage(TEST_VPN_PKG)))
-                .thenReturn(mVpnProfile.encode());
-
-        doReturn(new NetworkCapabilities()).when(mConnectivityManager)
-                .getRedactedNetworkCapabilitiesForPackage(any(), anyInt(), anyString());
-        doReturn(new LinkProperties()).when(mConnectivityManager)
-                .getRedactedLinkPropertiesForPackage(any(), anyInt(), anyString());
-
-        final String sessionKey = vpn.startVpnProfile(TEST_VPN_PKG);
-        final Set<Range<Integer>> uidRanges = rangeSet(PRIMARY_USER_RANGE);
-        // This is triggered by Ikev2VpnRunner constructor.
-        verify(mConnectivityManager, times(1)).setVpnDefaultForUids(eq(sessionKey), eq(uidRanges));
-        final NetworkCallback cb = triggerOnAvailableAndGetCallback();
-
-        verifyInterfaceSetCfgWithFlags(IF_STATE_UP);
-
-        // Wait for createIkeSession() to be called before proceeding in order to ensure consistent
-        // state
-        verify(mIkev2SessionCreator, timeout(TEST_TIMEOUT_MS))
-                .createIkeSession(any(), any(), any(), any(), captor.capture(), any());
-        // This is triggered by Vpn#startOrMigrateIkeSession().
-        verify(mConnectivityManager, times(2)).setVpnDefaultForUids(eq(sessionKey), eq(uidRanges));
-        reset(mIkev2SessionCreator);
-        // For network lost case, the process should be triggered by calling onLost(), which is the
-        // same process with the real case.
-        if (errorCode == VpnManager.ERROR_CODE_NETWORK_LOST) {
-            cb.onLost(TEST_NETWORK);
-            verify(mExecutor, atLeastOnce()).schedule(any(Runnable.class), anyLong(), any());
-        } else {
-            final IkeSessionCallback ikeCb = captor.getValue();
-            mExecutor.execute(() -> ikeCb.onClosedWithException(exception));
-        }
-
-        verifyPowerSaveTempWhitelistApp(TEST_VPN_PKG);
-        reset(mDeviceIdleInternal);
-        verifyVpnManagerEvent(sessionKey, category, errorType, errorCode,
-                // VPN NetworkAgnet does not switch to CONNECTED in the test, and the state is not
-                // important here. Verify that the state as it is, i.e. CONNECTING state.
-                new String[] {TEST_VPN_PKG}, new VpnProfileState(VpnProfileState.STATE_CONNECTING,
-                        sessionKey, false /* alwaysOn */, false /* lockdown */));
-        if (errorType == VpnManager.ERROR_CLASS_NOT_RECOVERABLE) {
-            verify(mConnectivityManager).setVpnDefaultForUids(eq(sessionKey),
-                    eq(Collections.EMPTY_LIST));
-            verify(mConnectivityManager, timeout(TEST_TIMEOUT_MS))
-                    .unregisterNetworkCallback(eq(cb));
-        } else if (errorType == VpnManager.ERROR_CLASS_RECOVERABLE
-                // Vpn won't retry when there is no usable underlying network.
-                && errorCode != VpnManager.ERROR_CODE_NETWORK_LOST) {
-            int retryIndex = 0;
-            // First failure occurred above.
-            final IkeSessionCallback retryCb = verifyRetryAndGetNewIkeCb(retryIndex++);
-            // Trigger 2 more failures to let the retry delay increase to 5s.
-            mExecutor.execute(() -> retryCb.onClosedWithException(exception));
-            final IkeSessionCallback retryCb2 = verifyRetryAndGetNewIkeCb(retryIndex++);
-            mExecutor.execute(() -> retryCb2.onClosedWithException(exception));
-            final IkeSessionCallback retryCb3 = verifyRetryAndGetNewIkeCb(retryIndex++);
-
-            // setVpnDefaultForUids may be called again but the uidRanges should not change.
-            verify(mConnectivityManager, atLeast(2)).setVpnDefaultForUids(eq(sessionKey),
-                    mUidRangesCaptor.capture());
-            final List<Collection<Range<Integer>>> capturedUidRanges =
-                    mUidRangesCaptor.getAllValues();
-            for (int i = 2; i < capturedUidRanges.size(); i++) {
-                // Assert equals no order.
-                assertTrue(
-                        "uid ranges should not be modified. Expected: " + uidRanges
-                                + ", actual: " + capturedUidRanges.get(i),
-                        capturedUidRanges.get(i).containsAll(uidRanges)
-                                && capturedUidRanges.get(i).size() == uidRanges.size());
-            }
-
-            // A fourth failure will cause the retry delay to be greater than 5s.
-            mExecutor.execute(() -> retryCb3.onClosedWithException(exception));
-            verifyRetryAndGetNewIkeCb(retryIndex++);
-
-            // The VPN network preference will be cleared when the retry delay is greater than 5s.
-            verify(mConnectivityManager).setVpnDefaultForUids(eq(sessionKey),
-                    eq(Collections.EMPTY_LIST));
-        }
-    }
-
-    private IkeSessionCallback verifyRetryAndGetNewIkeCb(int retryIndex) {
-        final ArgumentCaptor<IkeSessionCallback> ikeCbCaptor =
-                ArgumentCaptor.forClass(IkeSessionCallback.class);
-
-        // Verify retry is scheduled
-        final long expectedDelayMs = mTestDeps.getNextRetryDelayMs(retryIndex);
-        verify(mExecutor, timeout(TEST_TIMEOUT_MS)).schedule(any(Runnable.class),
-                eq(expectedDelayMs), eq(TimeUnit.MILLISECONDS));
-
-        verify(mIkev2SessionCreator, timeout(TEST_TIMEOUT_MS + expectedDelayMs))
-                .createIkeSession(any(), any(), any(), any(), ikeCbCaptor.capture(), any());
-
-        // Forget the mIkev2SessionCreator#createIkeSession call and mExecutor#schedule call
-        // for the next retry verification
-        resetIkev2SessionCreator(mIkeSessionWrapper);
-
-        return ikeCbCaptor.getValue();
-    }
-
-    @Test
-    public void testStartPlatformVpnAuthenticationFailed() throws Exception {
-        final IkeProtocolException exception = mock(IkeProtocolException.class);
-        final int errorCode = IkeProtocolException.ERROR_TYPE_AUTHENTICATION_FAILED;
-        when(exception.getErrorType()).thenReturn(errorCode);
-        doTestPlatformVpnWithException(exception,
-                VpnManager.CATEGORY_EVENT_IKE_ERROR, VpnManager.ERROR_CLASS_NOT_RECOVERABLE,
-                errorCode);
-    }
-
-    @Test
-    public void testStartPlatformVpnFailedWithRecoverableError() throws Exception {
-        final IkeProtocolException exception = mock(IkeProtocolException.class);
-        final int errorCode = IkeProtocolException.ERROR_TYPE_TEMPORARY_FAILURE;
-        when(exception.getErrorType()).thenReturn(errorCode);
-        doTestPlatformVpnWithException(exception,
-                VpnManager.CATEGORY_EVENT_IKE_ERROR, VpnManager.ERROR_CLASS_RECOVERABLE, errorCode);
-    }
-
-    @Test
-    public void testStartPlatformVpnFailedWithUnknownHostException() throws Exception {
-        final IkeNonProtocolException exception = mock(IkeNonProtocolException.class);
-        final UnknownHostException unknownHostException = new UnknownHostException();
-        final int errorCode = VpnManager.ERROR_CODE_NETWORK_UNKNOWN_HOST;
-        when(exception.getCause()).thenReturn(unknownHostException);
-        doTestPlatformVpnWithException(exception,
-                VpnManager.CATEGORY_EVENT_NETWORK_ERROR, VpnManager.ERROR_CLASS_RECOVERABLE,
-                errorCode);
-    }
-
-    @Test
-    public void testStartPlatformVpnFailedWithIkeTimeoutException() throws Exception {
-        final IkeNonProtocolException exception = mock(IkeNonProtocolException.class);
-        final IkeTimeoutException ikeTimeoutException =
-                new IkeTimeoutException("IkeTimeoutException");
-        final int errorCode = VpnManager.ERROR_CODE_NETWORK_PROTOCOL_TIMEOUT;
-        when(exception.getCause()).thenReturn(ikeTimeoutException);
-        doTestPlatformVpnWithException(exception,
-                VpnManager.CATEGORY_EVENT_NETWORK_ERROR, VpnManager.ERROR_CLASS_RECOVERABLE,
-                errorCode);
-    }
-
-    @Test
-    public void testStartPlatformVpnFailedWithIkeNetworkLostException() throws Exception {
-        final IkeNetworkLostException exception = new IkeNetworkLostException(
-                new Network(100));
-        doTestPlatformVpnWithException(exception,
-                VpnManager.CATEGORY_EVENT_NETWORK_ERROR, VpnManager.ERROR_CLASS_RECOVERABLE,
-                VpnManager.ERROR_CODE_NETWORK_LOST);
-    }
-
-    @Test
-    public void testStartPlatformVpnFailedWithIOException() throws Exception {
-        final IkeNonProtocolException exception = mock(IkeNonProtocolException.class);
-        final IOException ioException = new IOException();
-        final int errorCode = VpnManager.ERROR_CODE_NETWORK_IO;
-        when(exception.getCause()).thenReturn(ioException);
-        doTestPlatformVpnWithException(exception,
-                VpnManager.CATEGORY_EVENT_NETWORK_ERROR, VpnManager.ERROR_CLASS_RECOVERABLE,
-                errorCode);
-    }
-
-    @Test
-    public void testStartPlatformVpnIllegalArgumentExceptionInSetup() throws Exception {
-        when(mIkev2SessionCreator.createIkeSession(any(), any(), any(), any(), any(), any()))
-                .thenThrow(new IllegalArgumentException());
-        final Vpn vpn = startLegacyVpn(createVpn(PRIMARY_USER.id), mVpnProfile);
-        final NetworkCallback cb = triggerOnAvailableAndGetCallback();
-
-        verifyInterfaceSetCfgWithFlags(IF_STATE_UP);
-
-        // Wait for createIkeSession() to be called before proceeding in order to ensure consistent
-        // state
-        verify(mConnectivityManager, timeout(TEST_TIMEOUT_MS)).unregisterNetworkCallback(eq(cb));
-        assertEquals(LegacyVpnInfo.STATE_FAILED, vpn.getLegacyVpnInfo().state);
-    }
-
-    @Test
-    public void testVpnManagerEventWillNotBeSentToSettingsVpn() throws Exception {
-        startLegacyVpn(createVpn(PRIMARY_USER.id), mVpnProfile);
-        triggerOnAvailableAndGetCallback();
-
-        verifyInterfaceSetCfgWithFlags(IF_STATE_UP);
-
-        final IkeNonProtocolException exception = mock(IkeNonProtocolException.class);
-        final IkeTimeoutException ikeTimeoutException =
-                new IkeTimeoutException("IkeTimeoutException");
-        when(exception.getCause()).thenReturn(ikeTimeoutException);
-
-        final ArgumentCaptor<IkeSessionCallback> captor =
-                ArgumentCaptor.forClass(IkeSessionCallback.class);
-        verify(mIkev2SessionCreator, timeout(TEST_TIMEOUT_MS))
-                .createIkeSession(any(), any(), any(), any(), captor.capture(), any());
-        final IkeSessionCallback ikeCb = captor.getValue();
-        ikeCb.onClosedWithException(exception);
-
-        final Context userContext =
-                mContext.createContextAsUser(UserHandle.of(PRIMARY_USER.id), 0 /* flags */);
-        verify(userContext, never()).startService(any());
-    }
-
-    private void setAndVerifyAlwaysOnPackage(Vpn vpn, int uid, boolean lockdownEnabled) {
-        assertTrue(vpn.setAlwaysOnPackage(TEST_VPN_PKG, lockdownEnabled, null));
-
-        verify(mVpnProfileStore).get(eq(vpn.getProfileNameForPackage(TEST_VPN_PKG)));
-        verify(mAppOps).setMode(
-                eq(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN), eq(uid), eq(TEST_VPN_PKG),
-                eq(AppOpsManager.MODE_ALLOWED));
-
-        verify(mSystemServices).settingsSecurePutStringForUser(
-                eq(Settings.Secure.ALWAYS_ON_VPN_APP), eq(TEST_VPN_PKG), eq(PRIMARY_USER.id));
-        verify(mSystemServices).settingsSecurePutIntForUser(
-                eq(Settings.Secure.ALWAYS_ON_VPN_LOCKDOWN), eq(lockdownEnabled ? 1 : 0),
-                eq(PRIMARY_USER.id));
-        verify(mSystemServices).settingsSecurePutStringForUser(
-                eq(Settings.Secure.ALWAYS_ON_VPN_LOCKDOWN_WHITELIST), eq(""), eq(PRIMARY_USER.id));
-    }
-
-    @Test
-    public void testSetAndStartAlwaysOnVpn() throws Exception {
-        final Vpn vpn = createVpn(PRIMARY_USER.id);
-        setMockedUsers(PRIMARY_USER);
-
-        // UID checks must return a different UID; otherwise it'll be treated as already prepared.
-        final int uid = Process.myUid() + 1;
-        when(mPackageManager.getPackageUidAsUser(eq(TEST_VPN_PKG), anyInt()))
-                .thenReturn(uid);
-        when(mVpnProfileStore.get(vpn.getProfileNameForPackage(TEST_VPN_PKG)))
-                .thenReturn(mVpnProfile.encode());
-
-        setAndVerifyAlwaysOnPackage(vpn, uid, false);
-        assertTrue(vpn.startAlwaysOnVpn());
-
-        // TODO: Test the Ikev2VpnRunner started up properly. Relies on utility methods added in
-        // a subsequent CL.
-    }
-
-    private Vpn startLegacyVpn(final Vpn vpn, final VpnProfile vpnProfile) throws Exception {
-        setMockedUsers(PRIMARY_USER);
-        vpn.startLegacyVpn(vpnProfile);
-        return vpn;
-    }
-
-    private IkeSessionConnectionInfo createIkeConnectInfo() {
-        return new IkeSessionConnectionInfo(TEST_VPN_CLIENT_IP, TEST_VPN_SERVER_IP, TEST_NETWORK);
-    }
-
-    private IkeSessionConnectionInfo createIkeConnectInfo_2() {
-        return new IkeSessionConnectionInfo(
-                TEST_VPN_CLIENT_IP_2, TEST_VPN_SERVER_IP_2, TEST_NETWORK_2);
-    }
-
-    private IkeSessionConfiguration createIkeConfig(
-            IkeSessionConnectionInfo ikeConnectInfo, boolean isMobikeEnabled) {
-        final IkeSessionConfiguration.Builder builder =
-                new IkeSessionConfiguration.Builder(ikeConnectInfo);
-
-        if (isMobikeEnabled) {
-            builder.addIkeExtension(EXTENSION_TYPE_MOBIKE);
-        }
-
-        return builder.build();
-    }
-
-    private ChildSessionConfiguration createChildConfig() {
-        return new ChildSessionConfiguration.Builder(
-                        Arrays.asList(IN_TS, IN_TS6), Arrays.asList(OUT_TS, OUT_TS6))
-                .addInternalAddress(new LinkAddress(TEST_VPN_INTERNAL_IP, IP4_PREFIX_LEN))
-                .addInternalAddress(new LinkAddress(TEST_VPN_INTERNAL_IP6, IP6_PREFIX_LEN))
-                .addInternalDnsServer(TEST_VPN_INTERNAL_DNS)
-                .addInternalDnsServer(TEST_VPN_INTERNAL_DNS6)
-                .build();
-    }
-
-    private IpSecTransform createIpSecTransform() {
-        return new IpSecTransform(mContext, new IpSecConfig());
-    }
-
-    private void verifyApplyTunnelModeTransforms(int expectedTimes) throws Exception {
-        verify(mIpSecService, times(expectedTimes)).applyTunnelModeTransform(
-                eq(TEST_TUNNEL_RESOURCE_ID), eq(IpSecManager.DIRECTION_IN),
-                anyInt(), anyString());
-        verify(mIpSecService, times(expectedTimes)).applyTunnelModeTransform(
-                eq(TEST_TUNNEL_RESOURCE_ID), eq(IpSecManager.DIRECTION_OUT),
-                anyInt(), anyString());
-    }
-
-    private Pair<IkeSessionCallback, ChildSessionCallback> verifyCreateIkeAndCaptureCbs()
-            throws Exception {
-        final ArgumentCaptor<IkeSessionCallback> ikeCbCaptor =
-                ArgumentCaptor.forClass(IkeSessionCallback.class);
-        final ArgumentCaptor<ChildSessionCallback> childCbCaptor =
-                ArgumentCaptor.forClass(ChildSessionCallback.class);
-
-        verify(mIkev2SessionCreator, timeout(TEST_TIMEOUT_MS)).createIkeSession(
-                any(), any(), any(), any(), ikeCbCaptor.capture(), childCbCaptor.capture());
-
-        return new Pair<>(ikeCbCaptor.getValue(), childCbCaptor.getValue());
-    }
-
-    private static class PlatformVpnSnapshot {
-        public final Vpn vpn;
-        public final NetworkCallback nwCb;
-        public final IkeSessionCallback ikeCb;
-        public final ChildSessionCallback childCb;
-
-        PlatformVpnSnapshot(Vpn vpn, NetworkCallback nwCb,
-                IkeSessionCallback ikeCb, ChildSessionCallback childCb) {
-            this.vpn = vpn;
-            this.nwCb = nwCb;
-            this.ikeCb = ikeCb;
-            this.childCb = childCb;
-        }
-    }
-
-    private PlatformVpnSnapshot verifySetupPlatformVpn(IkeSessionConfiguration ikeConfig)
-            throws Exception {
-        return verifySetupPlatformVpn(ikeConfig, true);
-    }
-
-    private PlatformVpnSnapshot verifySetupPlatformVpn(
-            IkeSessionConfiguration ikeConfig, boolean mtuSupportsIpv6) throws Exception {
-        return verifySetupPlatformVpn(mVpnProfile, ikeConfig, mtuSupportsIpv6);
-    }
-
-    private PlatformVpnSnapshot verifySetupPlatformVpn(VpnProfile vpnProfile,
-            IkeSessionConfiguration ikeConfig, boolean mtuSupportsIpv6) throws Exception {
-        return verifySetupPlatformVpn(vpnProfile, ikeConfig,
-                new NetworkCapabilities.Builder().build() /* underlying network caps */,
-                mtuSupportsIpv6, false /* areLongLivedTcpConnectionsExpensive */);
-    }
-
-    private PlatformVpnSnapshot verifySetupPlatformVpn(VpnProfile vpnProfile,
-            IkeSessionConfiguration ikeConfig,
-            @NonNull final NetworkCapabilities underlyingNetworkCaps,
-            boolean mtuSupportsIpv6,
-            boolean areLongLivedTcpConnectionsExpensive) throws Exception {
-        if (!mtuSupportsIpv6) {
-            doReturn(IPV6_MIN_MTU - 1).when(mTestDeps).calculateVpnMtu(any(), anyInt(), anyInt(),
-                    anyBoolean());
-        }
-
-        doReturn(mMockNetworkAgent).when(mTestDeps)
-                .newNetworkAgent(
-                        any(), any(), anyString(), any(), any(), any(), any(), any(), any());
-        doReturn(TEST_NETWORK).when(mMockNetworkAgent).getNetwork();
-
-        final Vpn vpn = createVpn(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN);
-        when(mVpnProfileStore.get(vpn.getProfileNameForPackage(TEST_VPN_PKG)))
-                .thenReturn(vpnProfile.encode());
-
-        final String sessionKey = vpn.startVpnProfile(TEST_VPN_PKG);
-        final Set<Range<Integer>> uidRanges = Collections.singleton(PRIMARY_USER_RANGE);
-        verify(mConnectivityManager).setVpnDefaultForUids(eq(sessionKey), eq(uidRanges));
-        final NetworkCallback nwCb = triggerOnAvailableAndGetCallback(underlyingNetworkCaps);
-        // There are 4 interactions with the executor.
-        // - Network available
-        // - LP change
-        // - NC change
-        // - schedule() calls in scheduleStartIkeSession()
-        // The first 3 calls are triggered from Executor.execute(). The execute() will also call to
-        // schedule() with 0 delay. Verify the exact interaction here so that it won't cause flakes
-        // in the follow-up flow.
-        verify(mExecutor, timeout(TEST_TIMEOUT_MS).times(4))
-                .schedule(any(Runnable.class), anyLong(), any());
-        reset(mExecutor);
-
-        // Mock the setup procedure by firing callbacks
-        final Pair<IkeSessionCallback, ChildSessionCallback> cbPair =
-                verifyCreateIkeAndCaptureCbs();
-        final IkeSessionCallback ikeCb = cbPair.first;
-        final ChildSessionCallback childCb = cbPair.second;
-
-        ikeCb.onOpened(ikeConfig);
-        childCb.onIpSecTransformCreated(createIpSecTransform(), IpSecManager.DIRECTION_IN);
-        childCb.onIpSecTransformCreated(createIpSecTransform(), IpSecManager.DIRECTION_OUT);
-        childCb.onOpened(createChildConfig());
-
-        // Verification VPN setup
-        verifyApplyTunnelModeTransforms(1);
-
-        ArgumentCaptor<LinkProperties> lpCaptor = ArgumentCaptor.forClass(LinkProperties.class);
-        ArgumentCaptor<NetworkCapabilities> ncCaptor =
-                ArgumentCaptor.forClass(NetworkCapabilities.class);
-        ArgumentCaptor<NetworkAgentConfig> nacCaptor =
-                ArgumentCaptor.forClass(NetworkAgentConfig.class);
-        verify(mTestDeps).newNetworkAgent(
-                any(), any(), anyString(), ncCaptor.capture(), lpCaptor.capture(),
-                any(), nacCaptor.capture(), any(), any());
-        verify(mIkeSessionWrapper).setUnderpinnedNetwork(TEST_NETWORK);
-        // Check LinkProperties
-        final LinkProperties lp = lpCaptor.getValue();
-        final List<RouteInfo> expectedRoutes =
-                new ArrayList<>(
-                        Arrays.asList(
-                                new RouteInfo(
-                                        new IpPrefix(Inet4Address.ANY, 0),
-                                        null /* gateway */,
-                                        TEST_IFACE_NAME,
-                                        RouteInfo.RTN_UNICAST)));
-        final List<LinkAddress> expectedAddresses =
-                new ArrayList<>(
-                        Arrays.asList(new LinkAddress(TEST_VPN_INTERNAL_IP, IP4_PREFIX_LEN)));
-        final List<InetAddress> expectedDns = new ArrayList<>(Arrays.asList(TEST_VPN_INTERNAL_DNS));
-
-        if (mtuSupportsIpv6) {
-            expectedRoutes.add(
-                    new RouteInfo(
-                            new IpPrefix(Inet6Address.ANY, 0),
-                            null /* gateway */,
-                            TEST_IFACE_NAME,
-                            RouteInfo.RTN_UNICAST));
-            expectedAddresses.add(new LinkAddress(TEST_VPN_INTERNAL_IP6, IP6_PREFIX_LEN));
-            expectedDns.add(TEST_VPN_INTERNAL_DNS6);
-        } else {
-            expectedRoutes.add(
-                    new RouteInfo(
-                            new IpPrefix(Inet6Address.ANY, 0),
-                            null /* gateway */,
-                            TEST_IFACE_NAME,
-                            RTN_UNREACHABLE));
-        }
-
-        assertEquals(expectedRoutes, lp.getRoutes());
-        assertEquals(expectedAddresses, lp.getLinkAddresses());
-        assertEquals(expectedDns, lp.getDnsServers());
-
-        // Check NetworkCapabilities
-        assertEquals(Arrays.asList(TEST_NETWORK), ncCaptor.getValue().getUnderlyingNetworks());
-
-        // Check if allowBypass is set or not.
-        assertTrue(nacCaptor.getValue().isBypassableVpn());
-        // Check if extra info for VPN is set.
-        assertTrue(nacCaptor.getValue().getLegacyExtraInfo().contains(TEST_VPN_PKG));
-        final VpnTransportInfo info = (VpnTransportInfo) ncCaptor.getValue().getTransportInfo();
-        assertTrue(info.isBypassable());
-        assertEquals(areLongLivedTcpConnectionsExpensive,
-                info.areLongLivedTcpConnectionsExpensive());
-        return new PlatformVpnSnapshot(vpn, nwCb, ikeCb, childCb);
-    }
-
-    @Test
-    public void testStartPlatformVpn() throws Exception {
-        final PlatformVpnSnapshot vpnSnapShot = verifySetupPlatformVpn(
-                createIkeConfig(createIkeConnectInfo(), true /* isMobikeEnabled */));
-        vpnSnapShot.vpn.mVpnRunner.exitVpnRunner();
-        verify(mConnectivityManager).setVpnDefaultForUids(anyString(), eq(Collections.EMPTY_LIST));
-    }
-
-    @Test
-    public void testMigrateIkeSession_FromIkeTunnConnParams_AutoTimerNoTimer() throws Exception {
-        doTestMigrateIkeSession_FromIkeTunnConnParams(
-                false /* isAutomaticIpVersionSelectionEnabled */,
-                true /* isAutomaticNattKeepaliveTimerEnabled */,
-                TEST_KEEPALIVE_TIMEOUT_UNSET /* keepaliveInProfile */,
-                ESP_IP_VERSION_AUTO /* ipVersionInProfile */,
-                ESP_ENCAP_TYPE_AUTO /* encapTypeInProfile */);
-    }
-
-    @Test
-    public void testMigrateIkeSession_FromIkeTunnConnParams_AutoTimerTimerSet() throws Exception {
-        doTestMigrateIkeSession_FromIkeTunnConnParams(
-                false /* isAutomaticIpVersionSelectionEnabled */,
-                true /* isAutomaticNattKeepaliveTimerEnabled */,
-                TEST_KEEPALIVE_TIMER /* keepaliveInProfile */,
-                ESP_IP_VERSION_AUTO /* ipVersionInProfile */,
-                ESP_ENCAP_TYPE_AUTO /* encapTypeInProfile */);
-    }
-
-    @Test
-    public void testMigrateIkeSession_FromIkeTunnConnParams_AutoIp() throws Exception {
-        doTestMigrateIkeSession_FromIkeTunnConnParams(
-                true /* isAutomaticIpVersionSelectionEnabled */,
-                false /* isAutomaticNattKeepaliveTimerEnabled */,
-                TEST_KEEPALIVE_TIMEOUT_UNSET /* keepaliveInProfile */,
-                ESP_IP_VERSION_AUTO /* ipVersionInProfile */,
-                ESP_ENCAP_TYPE_AUTO /* encapTypeInProfile */);
-    }
-
-    @Test
-    public void testMigrateIkeSession_FromIkeTunnConnParams_AssignedIpProtocol() throws Exception {
-        doTestMigrateIkeSession_FromIkeTunnConnParams(
-                false /* isAutomaticIpVersionSelectionEnabled */,
-                false /* isAutomaticNattKeepaliveTimerEnabled */,
-                TEST_KEEPALIVE_TIMEOUT_UNSET /* keepaliveInProfile */,
-                ESP_IP_VERSION_IPV4 /* ipVersionInProfile */,
-                ESP_ENCAP_TYPE_UDP /* encapTypeInProfile */);
-    }
-
-    @Test
-    public void testMigrateIkeSession_FromNotIkeTunnConnParams_AutoTimer() throws Exception {
-        doTestMigrateIkeSession_FromNotIkeTunnConnParams(
-                false /* isAutomaticIpVersionSelectionEnabled */,
-                true /* isAutomaticNattKeepaliveTimerEnabled */);
-    }
-
-    @Test
-    public void testMigrateIkeSession_FromNotIkeTunnConnParams_AutoIp() throws Exception {
-        doTestMigrateIkeSession_FromNotIkeTunnConnParams(
-                true /* isAutomaticIpVersionSelectionEnabled */,
-                false /* isAutomaticNattKeepaliveTimerEnabled */);
-    }
-
-    private void doTestMigrateIkeSession_FromNotIkeTunnConnParams(
-            boolean isAutomaticIpVersionSelectionEnabled,
-            boolean isAutomaticNattKeepaliveTimerEnabled) throws Exception {
-        final Ikev2VpnProfile ikeProfile =
-                new Ikev2VpnProfile.Builder(TEST_VPN_SERVER, TEST_VPN_IDENTITY)
-                        .setAuthPsk(TEST_VPN_PSK)
-                        .setBypassable(true /* isBypassable */)
-                        .setAutomaticNattKeepaliveTimerEnabled(isAutomaticNattKeepaliveTimerEnabled)
-                        .setAutomaticIpVersionSelectionEnabled(isAutomaticIpVersionSelectionEnabled)
-                        .build();
-
-        final int expectedKeepalive = isAutomaticNattKeepaliveTimerEnabled
-                ? AUTOMATIC_KEEPALIVE_DELAY_SECONDS
-                : DEFAULT_UDP_PORT_4500_NAT_TIMEOUT_SEC_INT;
-        doTestMigrateIkeSession(ikeProfile.toVpnProfile(),
-                expectedKeepalive,
-                ESP_IP_VERSION_AUTO /* expectedIpVersion */,
-                ESP_ENCAP_TYPE_AUTO /* expectedEncapType */,
-                new NetworkCapabilities.Builder().build());
-    }
-
-    private Ikev2VpnProfile makeIkeV2VpnProfile(
-            boolean isAutomaticIpVersionSelectionEnabled,
-            boolean isAutomaticNattKeepaliveTimerEnabled,
-            int keepaliveInProfile,
-            int ipVersionInProfile,
-            int encapTypeInProfile) {
-        // TODO: Update helper function in IkeSessionTestUtils to support building IkeSessionParams
-        // with IP version and encap type when mainline-prod branch support these two APIs.
-        final IkeSessionParams params = getTestIkeSessionParams(true /* testIpv6 */,
-                new IkeFqdnIdentification(TEST_IDENTITY), keepaliveInProfile);
-        final IkeSessionParams ikeSessionParams = new IkeSessionParams.Builder(params)
-                .setIpVersion(ipVersionInProfile)
-                .setEncapType(encapTypeInProfile)
-                .build();
-
-        final IkeTunnelConnectionParams tunnelParams =
-                new IkeTunnelConnectionParams(ikeSessionParams, CHILD_PARAMS);
-        return new Ikev2VpnProfile.Builder(tunnelParams)
-                .setBypassable(true)
-                .setAutomaticNattKeepaliveTimerEnabled(isAutomaticNattKeepaliveTimerEnabled)
-                .setAutomaticIpVersionSelectionEnabled(isAutomaticIpVersionSelectionEnabled)
-                .build();
-    }
-
-    private void doTestMigrateIkeSession_FromIkeTunnConnParams(
-            boolean isAutomaticIpVersionSelectionEnabled,
-            boolean isAutomaticNattKeepaliveTimerEnabled,
-            int keepaliveInProfile,
-            int ipVersionInProfile,
-            int encapTypeInProfile) throws Exception {
-        doTestMigrateIkeSession_FromIkeTunnConnParams(isAutomaticIpVersionSelectionEnabled,
-                isAutomaticNattKeepaliveTimerEnabled, keepaliveInProfile, ipVersionInProfile,
-                encapTypeInProfile, new NetworkCapabilities.Builder().build());
-    }
-
-    private void doTestMigrateIkeSession_FromIkeTunnConnParams(
-            boolean isAutomaticIpVersionSelectionEnabled,
-            boolean isAutomaticNattKeepaliveTimerEnabled,
-            int keepaliveInProfile,
-            int ipVersionInProfile,
-            int encapTypeInProfile,
-            @NonNull final NetworkCapabilities nc) throws Exception {
-        final Ikev2VpnProfile ikeProfile = makeIkeV2VpnProfile(
-                isAutomaticIpVersionSelectionEnabled,
-                isAutomaticNattKeepaliveTimerEnabled,
-                keepaliveInProfile,
-                ipVersionInProfile,
-                encapTypeInProfile);
-
-        final IkeSessionParams ikeSessionParams =
-                ikeProfile.getIkeTunnelConnectionParams().getIkeSessionParams();
-        final int expectedKeepalive = isAutomaticNattKeepaliveTimerEnabled
-                ? AUTOMATIC_KEEPALIVE_DELAY_SECONDS
-                : ikeSessionParams.getNattKeepAliveDelaySeconds();
-        final int expectedIpVersion = isAutomaticIpVersionSelectionEnabled
-                ? ESP_IP_VERSION_AUTO
-                : ikeSessionParams.getIpVersion();
-        final int expectedEncapType = isAutomaticIpVersionSelectionEnabled
-                ? ESP_ENCAP_TYPE_AUTO
-                : ikeSessionParams.getEncapType();
-        doTestMigrateIkeSession(ikeProfile.toVpnProfile(), expectedKeepalive,
-                expectedIpVersion, expectedEncapType, nc);
-    }
-
-    @Test
-    public void doTestMigrateIkeSession_Vcn() throws Exception {
-        final int expectedKeepalive = 2097; // Any unlikely number will do
-        final NetworkCapabilities vcnNc = new NetworkCapabilities.Builder()
-                .addTransportType(TRANSPORT_CELLULAR)
-                .setTransportInfo(new VcnTransportInfo(TEST_SUB_ID, expectedKeepalive))
-                .build();
-        final Ikev2VpnProfile ikev2VpnProfile = makeIkeV2VpnProfile(
-                true /* isAutomaticIpVersionSelectionEnabled */,
-                true /* isAutomaticNattKeepaliveTimerEnabled */,
-                234 /* keepaliveInProfile */, // Should be ignored, any value will do
-                ESP_IP_VERSION_IPV4, // Should be ignored
-                ESP_ENCAP_TYPE_UDP // Should be ignored
-        );
-        doTestMigrateIkeSession(
-                ikev2VpnProfile.toVpnProfile(),
-                expectedKeepalive,
-                ESP_IP_VERSION_AUTO /* expectedIpVersion */,
-                ESP_ENCAP_TYPE_AUTO /* expectedEncapType */,
-                vcnNc);
-    }
-
-    private void doTestMigrateIkeSession(
-            @NonNull final VpnProfile profile,
-            final int expectedKeepalive,
-            final int expectedIpVersion,
-            final int expectedEncapType,
-            @NonNull final NetworkCapabilities caps) throws Exception {
-        final PlatformVpnSnapshot vpnSnapShot =
-                verifySetupPlatformVpn(profile,
-                        createIkeConfig(createIkeConnectInfo(), true /* isMobikeEnabled */),
-                        caps /* underlying network capabilities */,
-                        false /* mtuSupportsIpv6 */,
-                        expectedKeepalive < DEFAULT_LONG_LIVED_TCP_CONNS_EXPENSIVE_TIMEOUT_SEC);
-        // Simulate a new network coming up
-        vpnSnapShot.nwCb.onAvailable(TEST_NETWORK_2);
-        verify(mIkeSessionWrapper, never()).setNetwork(any(), anyInt(), anyInt(), anyInt());
-
-        vpnSnapShot.nwCb.onCapabilitiesChanged(TEST_NETWORK_2, caps);
-        // Verify MOBIKE is triggered
-        verify(mIkeSessionWrapper, timeout(TEST_TIMEOUT_MS)).setNetwork(TEST_NETWORK_2,
-                expectedIpVersion, expectedEncapType, expectedKeepalive);
-
-        vpnSnapShot.vpn.mVpnRunner.exitVpnRunner();
-    }
-
-    @Test
-    public void testLinkPropertiesUpdateTriggerReevaluation() throws Exception {
-        final boolean hasV6 = true;
-
-        mockCarrierConfig(TEST_SUB_ID, TelephonyManager.SIM_STATE_LOADED, TEST_KEEPALIVE_TIMER,
-                PREFERRED_IKE_PROTOCOL_IPV6_ESP);
-        final IkeSessionParams params = getTestIkeSessionParams(hasV6,
-                new IkeFqdnIdentification(TEST_IDENTITY), TEST_KEEPALIVE_TIMER);
-        final IkeTunnelConnectionParams tunnelParams =
-                new IkeTunnelConnectionParams(params, CHILD_PARAMS);
-        final Ikev2VpnProfile ikeProfile = new Ikev2VpnProfile.Builder(tunnelParams)
-                .setBypassable(true)
-                .setAutomaticNattKeepaliveTimerEnabled(false)
-                .setAutomaticIpVersionSelectionEnabled(true)
-                .build();
-        final PlatformVpnSnapshot vpnSnapShot =
-                verifySetupPlatformVpn(ikeProfile.toVpnProfile(),
-                        createIkeConfig(createIkeConnectInfo(), true /* isMobikeEnabled */),
-                        new NetworkCapabilities.Builder().build() /* underlying network caps */,
-                        hasV6 /* mtuSupportsIpv6 */,
-                        false /* areLongLivedTcpConnectionsExpensive */);
-        reset(mExecutor);
-
-        // Simulate a new network coming up
-        final LinkProperties lp = new LinkProperties();
-        lp.addLinkAddress(new LinkAddress("192.0.2.2/32"));
-
-        // Have the executor use the real delay to make sure schedule() was called only
-        // once for all calls. Also, arrange for execute() not to call schedule() to avoid
-        // messing with the checks for schedule().
-        mExecutor.delayMs = TestExecutor.REAL_DELAY;
-        mExecutor.executeDirect = true;
-        vpnSnapShot.nwCb.onAvailable(TEST_NETWORK_2);
-        vpnSnapShot.nwCb.onCapabilitiesChanged(
-                TEST_NETWORK_2, new NetworkCapabilities.Builder().build());
-        vpnSnapShot.nwCb.onLinkPropertiesChanged(TEST_NETWORK_2, new LinkProperties(lp));
-        verify(mExecutor).schedule(any(Runnable.class), longThat(it -> it > 0), any());
-        reset(mExecutor);
-
-        final InOrder order = inOrder(mIkeSessionWrapper);
-
-        // Verify the network is started
-        order.verify(mIkeSessionWrapper, timeout(TIMEOUT_CROSSTHREAD_MS)).setNetwork(TEST_NETWORK_2,
-                ESP_IP_VERSION_AUTO, ESP_ENCAP_TYPE_AUTO, TEST_KEEPALIVE_TIMER);
-
-        // Send the same properties, check that no migration is scheduled
-        vpnSnapShot.nwCb.onLinkPropertiesChanged(TEST_NETWORK_2, new LinkProperties(lp));
-        verify(mExecutor, never()).schedule(any(Runnable.class), anyLong(), any());
-
-        // Add v6 address, verify MOBIKE is triggered
-        lp.addLinkAddress(new LinkAddress("2001:db8::1/64"));
-        vpnSnapShot.nwCb.onLinkPropertiesChanged(TEST_NETWORK_2, new LinkProperties(lp));
-        order.verify(mIkeSessionWrapper, timeout(TIMEOUT_CROSSTHREAD_MS)).setNetwork(TEST_NETWORK_2,
-                ESP_IP_VERSION_AUTO, ESP_ENCAP_TYPE_AUTO, TEST_KEEPALIVE_TIMER);
-
-        // Add another v4 address, verify MOBIKE is triggered
-        final LinkProperties stacked = new LinkProperties();
-        stacked.setInterfaceName("v4-" + lp.getInterfaceName());
-        stacked.addLinkAddress(new LinkAddress("192.168.0.1/32"));
-        lp.addStackedLink(stacked);
-        vpnSnapShot.nwCb.onLinkPropertiesChanged(TEST_NETWORK_2, new LinkProperties(lp));
-        order.verify(mIkeSessionWrapper, timeout(TIMEOUT_CROSSTHREAD_MS)).setNetwork(TEST_NETWORK_2,
-                ESP_IP_VERSION_AUTO, ESP_ENCAP_TYPE_AUTO, TEST_KEEPALIVE_TIMER);
-
-        vpnSnapShot.vpn.mVpnRunner.exitVpnRunner();
-    }
-
-    private void mockCarrierConfig(int subId, int simStatus, int keepaliveTimer, int ikeProtocol) {
-        final SubscriptionInfo subscriptionInfo = mock(SubscriptionInfo.class);
-        doReturn(subId).when(subscriptionInfo).getSubscriptionId();
-        doReturn(List.of(subscriptionInfo)).when(mSubscriptionManager)
-                .getActiveSubscriptionInfoList();
-
-        doReturn(simStatus).when(mTmPerSub).getSimApplicationState();
-        doReturn(TEST_MCCMNC).when(mTmPerSub).getSimOperator(subId);
-
-        final PersistableBundle persistableBundle = new PersistableBundle();
-        persistableBundle.putInt(KEY_MIN_UDP_PORT_4500_NAT_TIMEOUT_SEC_INT, keepaliveTimer);
-        persistableBundle.putInt(KEY_PREFERRED_IKE_PROTOCOL_INT, ikeProtocol);
-        // For CarrierConfigManager.isConfigForIdentifiedCarrier check
-        persistableBundle.putBoolean(KEY_CARRIER_CONFIG_APPLIED_BOOL, true);
-        doReturn(persistableBundle).when(mConfigManager).getConfigForSubId(subId);
-    }
-
-    private CarrierConfigManager.CarrierConfigChangeListener getCarrierConfigListener() {
-        final ArgumentCaptor<CarrierConfigManager.CarrierConfigChangeListener> listenerCaptor =
-                ArgumentCaptor.forClass(CarrierConfigManager.CarrierConfigChangeListener.class);
-
-        verify(mConfigManager).registerCarrierConfigChangeListener(any(), listenerCaptor.capture());
-
-        return listenerCaptor.getValue();
-    }
-
-    @Test
-    public void testNattKeepaliveTimerFromCarrierConfig_noSubId() throws Exception {
-        doTestReadCarrierConfig(new NetworkCapabilities(),
-                TelephonyManager.SIM_STATE_LOADED,
-                PREFERRED_IKE_PROTOCOL_IPV4_UDP,
-                AUTOMATIC_KEEPALIVE_DELAY_SECONDS /* expectedKeepaliveTimer */,
-                ESP_IP_VERSION_AUTO /* expectedIpVersion */,
-                ESP_ENCAP_TYPE_AUTO /* expectedEncapType */,
-                false /* expectedReadFromCarrierConfig*/,
-                true /* areLongLivedTcpConnectionsExpensive */);
-    }
-
-    @Test
-    public void testNattKeepaliveTimerFromCarrierConfig_simAbsent() throws Exception {
-        doTestReadCarrierConfig(new NetworkCapabilities.Builder().build(),
-                TelephonyManager.SIM_STATE_ABSENT,
-                PREFERRED_IKE_PROTOCOL_IPV4_UDP,
-                AUTOMATIC_KEEPALIVE_DELAY_SECONDS /* expectedKeepaliveTimer */,
-                ESP_IP_VERSION_AUTO /* expectedIpVersion */,
-                ESP_ENCAP_TYPE_AUTO /* expectedEncapType */,
-                false /* expectedReadFromCarrierConfig*/,
-                true /* areLongLivedTcpConnectionsExpensive */);
-    }
-
-    @Test
-    public void testNattKeepaliveTimerFromCarrierConfig() throws Exception {
-        doTestReadCarrierConfig(createTestCellNc(),
-                TelephonyManager.SIM_STATE_LOADED,
-                PREFERRED_IKE_PROTOCOL_AUTO,
-                TEST_KEEPALIVE_TIMER /* expectedKeepaliveTimer */,
-                ESP_IP_VERSION_AUTO /* expectedIpVersion */,
-                ESP_ENCAP_TYPE_AUTO /* expectedEncapType */,
-                true /* expectedReadFromCarrierConfig*/,
-                false /* areLongLivedTcpConnectionsExpensive */);
-    }
-
-    @Test
-    public void testNattKeepaliveTimerFromCarrierConfig_NotCell() throws Exception {
-        final NetworkCapabilities nc = new NetworkCapabilities.Builder()
-                .addTransportType(TRANSPORT_WIFI)
-                .setTransportInfo(new WifiInfo.Builder().build())
-                .build();
-        doTestReadCarrierConfig(nc,
-                TelephonyManager.SIM_STATE_LOADED,
-                PREFERRED_IKE_PROTOCOL_IPV4_UDP,
-                AUTOMATIC_KEEPALIVE_DELAY_SECONDS /* expectedKeepaliveTimer */,
-                ESP_IP_VERSION_AUTO /* expectedIpVersion */,
-                ESP_ENCAP_TYPE_AUTO /* expectedEncapType */,
-                false /* expectedReadFromCarrierConfig*/,
-                true /* areLongLivedTcpConnectionsExpensive */);
-    }
-
-    @Test
-    public void testPreferredIpProtocolFromCarrierConfig_v4UDP() throws Exception {
-        doTestReadCarrierConfig(createTestCellNc(),
-                TelephonyManager.SIM_STATE_LOADED,
-                PREFERRED_IKE_PROTOCOL_IPV4_UDP,
-                TEST_KEEPALIVE_TIMER /* expectedKeepaliveTimer */,
-                ESP_IP_VERSION_IPV4 /* expectedIpVersion */,
-                ESP_ENCAP_TYPE_UDP /* expectedEncapType */,
-                true /* expectedReadFromCarrierConfig*/,
-                false /* areLongLivedTcpConnectionsExpensive */);
-    }
-
-    @Test
-    public void testPreferredIpProtocolFromCarrierConfig_v6ESP() throws Exception {
-        doTestReadCarrierConfig(createTestCellNc(),
-                TelephonyManager.SIM_STATE_LOADED,
-                PREFERRED_IKE_PROTOCOL_IPV6_ESP,
-                TEST_KEEPALIVE_TIMER /* expectedKeepaliveTimer */,
-                ESP_IP_VERSION_IPV6 /* expectedIpVersion */,
-                ESP_ENCAP_TYPE_NONE /* expectedEncapType */,
-                true /* expectedReadFromCarrierConfig*/,
-                false /* areLongLivedTcpConnectionsExpensive */);
-    }
-
-    @Test
-    public void testPreferredIpProtocolFromCarrierConfig_v6UDP() throws Exception {
-        doTestReadCarrierConfig(createTestCellNc(),
-                TelephonyManager.SIM_STATE_LOADED,
-                PREFERRED_IKE_PROTOCOL_IPV6_UDP,
-                TEST_KEEPALIVE_TIMER /* expectedKeepaliveTimer */,
-                ESP_IP_VERSION_IPV6 /* expectedIpVersion */,
-                ESP_ENCAP_TYPE_UDP /* expectedEncapType */,
-                true /* expectedReadFromCarrierConfig*/,
-                false /* areLongLivedTcpConnectionsExpensive */);
-    }
-
-    private NetworkCapabilities createTestCellNc() {
-        return new NetworkCapabilities.Builder()
-                .addTransportType(TRANSPORT_CELLULAR)
-                .setNetworkSpecifier(new TelephonyNetworkSpecifier.Builder()
-                        .setSubscriptionId(TEST_SUB_ID)
-                        .build())
-                .build();
-    }
-
-    private void doTestReadCarrierConfig(NetworkCapabilities nc, int simState, int preferredIpProto,
-            int expectedKeepaliveTimer, int expectedIpVersion, int expectedEncapType,
-            boolean expectedReadFromCarrierConfig,
-            boolean areLongLivedTcpConnectionsExpensive)
-            throws Exception {
-        final Ikev2VpnProfile ikeProfile =
-                new Ikev2VpnProfile.Builder(TEST_VPN_SERVER, TEST_VPN_IDENTITY)
-                        .setAuthPsk(TEST_VPN_PSK)
-                        .setBypassable(true /* isBypassable */)
-                        .setAutomaticNattKeepaliveTimerEnabled(true)
-                        .setAutomaticIpVersionSelectionEnabled(true)
-                        .build();
-
-        final PlatformVpnSnapshot vpnSnapShot =
-                verifySetupPlatformVpn(ikeProfile.toVpnProfile(),
-                        createIkeConfig(createIkeConnectInfo(), true /* isMobikeEnabled */),
-                        new NetworkCapabilities.Builder().build() /* underlying network caps */,
-                        false /* mtuSupportsIpv6 */,
-                        true /* areLongLivedTcpConnectionsExpensive */);
-
-        final CarrierConfigManager.CarrierConfigChangeListener listener =
-                getCarrierConfigListener();
-
-        // Simulate a new network coming up
-        vpnSnapShot.nwCb.onAvailable(TEST_NETWORK_2);
-        // Migration will not be started until receiving network capabilities change.
-        verify(mIkeSessionWrapper, never()).setNetwork(any(), anyInt(), anyInt(), anyInt());
-
-        reset(mIkeSessionWrapper);
-        mockCarrierConfig(TEST_SUB_ID, simState, TEST_KEEPALIVE_TIMER, preferredIpProto);
-        vpnSnapShot.nwCb.onCapabilitiesChanged(TEST_NETWORK_2, nc);
-        verify(mIkeSessionWrapper, timeout(TEST_TIMEOUT_MS)).setNetwork(TEST_NETWORK_2,
-                expectedIpVersion, expectedEncapType, expectedKeepaliveTimer);
-        if (expectedReadFromCarrierConfig) {
-            final ArgumentCaptor<NetworkCapabilities> ncCaptor =
-                    ArgumentCaptor.forClass(NetworkCapabilities.class);
-            verify(mMockNetworkAgent, timeout(TEST_TIMEOUT_MS))
-                    .doSendNetworkCapabilities(ncCaptor.capture());
-
-            final VpnTransportInfo info =
-                    (VpnTransportInfo) ncCaptor.getValue().getTransportInfo();
-            assertEquals(areLongLivedTcpConnectionsExpensive,
-                    info.areLongLivedTcpConnectionsExpensive());
-        } else {
-            verify(mMockNetworkAgent, never()).doSendNetworkCapabilities(any());
-        }
-
-        reset(mExecutor);
-        reset(mIkeSessionWrapper);
-        reset(mMockNetworkAgent);
-
-        // Trigger carrier config change
-        listener.onCarrierConfigChanged(1 /* logicalSlotIndex */, TEST_SUB_ID,
-                -1 /* carrierId */, -1 /* specificCarrierId */);
-        verify(mIkeSessionWrapper).setNetwork(TEST_NETWORK_2,
-                expectedIpVersion, expectedEncapType, expectedKeepaliveTimer);
-        // Expect no NetworkCapabilities change.
-        // Call to doSendNetworkCapabilities() will not be triggered.
-        verify(mMockNetworkAgent, never()).doSendNetworkCapabilities(any());
-    }
-
-    @Test
-    public void testStartPlatformVpn_mtuDoesNotSupportIpv6() throws Exception {
-        final PlatformVpnSnapshot vpnSnapShot =
-                verifySetupPlatformVpn(
-                        createIkeConfig(createIkeConnectInfo(), true /* isMobikeEnabled */),
-                        false /* mtuSupportsIpv6 */);
-        vpnSnapShot.vpn.mVpnRunner.exitVpnRunner();
-    }
-
-    @Test
-    public void testStartPlatformVpn_underlyingNetworkNotChange() throws Exception {
-        final PlatformVpnSnapshot vpnSnapShot = verifySetupPlatformVpn(
-                createIkeConfig(createIkeConnectInfo(), true /* isMobikeEnabled */));
-        // Trigger update on the same network should not cause underlying network change in NC of
-        // the VPN network
-        vpnSnapShot.nwCb.onAvailable(TEST_NETWORK);
-        vpnSnapShot.nwCb.onCapabilitiesChanged(TEST_NETWORK,
-                new NetworkCapabilities.Builder()
-                        .setSubscriptionIds(Set.of(TEST_SUB_ID))
-                        .build());
-        // Verify setNetwork() called but no underlying network update
-        verify(mIkeSessionWrapper, timeout(TEST_TIMEOUT_MS)).setNetwork(eq(TEST_NETWORK),
-                eq(ESP_IP_VERSION_AUTO) /* ipVersion */,
-                eq(ESP_ENCAP_TYPE_AUTO) /* encapType */,
-                eq(DEFAULT_UDP_PORT_4500_NAT_TIMEOUT_SEC_INT) /* keepaliveDelay */);
-        verify(mMockNetworkAgent, never())
-                .doSetUnderlyingNetworks(any());
-
-        vpnSnapShot.nwCb.onAvailable(TEST_NETWORK_2);
-        vpnSnapShot.nwCb.onCapabilitiesChanged(TEST_NETWORK_2,
-                new NetworkCapabilities.Builder().build());
-
-        // A new network should trigger both setNetwork() and a underlying network update.
-        verify(mIkeSessionWrapper, timeout(TEST_TIMEOUT_MS)).setNetwork(eq(TEST_NETWORK_2),
-                eq(ESP_IP_VERSION_AUTO) /* ipVersion */,
-                eq(ESP_ENCAP_TYPE_AUTO) /* encapType */,
-                eq(DEFAULT_UDP_PORT_4500_NAT_TIMEOUT_SEC_INT) /* keepaliveDelay */);
-        verify(mMockNetworkAgent).doSetUnderlyingNetworks(
-                Collections.singletonList(TEST_NETWORK_2));
-
-        vpnSnapShot.vpn.mVpnRunner.exitVpnRunner();
-    }
-
-    @Test
-    public void testStartPlatformVpnMobility_mobikeEnabled() throws Exception {
-        final PlatformVpnSnapshot vpnSnapShot = verifySetupPlatformVpn(
-                createIkeConfig(createIkeConnectInfo(), true /* isMobikeEnabled */));
-
-        // Set new MTU on a different network
-        final int newMtu = IPV6_MIN_MTU + 1;
-        doReturn(newMtu).when(mTestDeps).calculateVpnMtu(any(), anyInt(), anyInt(), anyBoolean());
-
-        // Mock network loss and verify a cleanup task is scheduled
-        vpnSnapShot.nwCb.onLost(TEST_NETWORK);
-        verify(mExecutor, atLeastOnce()).schedule(any(Runnable.class), anyLong(), any());
-
-        // Mock new network comes up and the cleanup task is cancelled
-        vpnSnapShot.nwCb.onAvailable(TEST_NETWORK_2);
-        verify(mIkeSessionWrapper, never()).setNetwork(any(), anyInt(), anyInt(), anyInt());
-
-        vpnSnapShot.nwCb.onCapabilitiesChanged(TEST_NETWORK_2,
-                new NetworkCapabilities.Builder().build());
-        // Verify MOBIKE is triggered
-        verify(mIkeSessionWrapper, timeout(TEST_TIMEOUT_MS)).setNetwork(eq(TEST_NETWORK_2),
-                eq(ESP_IP_VERSION_AUTO) /* ipVersion */,
-                eq(ESP_ENCAP_TYPE_AUTO) /* encapType */,
-                eq(DEFAULT_UDP_PORT_4500_NAT_TIMEOUT_SEC_INT) /* keepaliveDelay */);
-        // Verify mNetworkCapabilities is updated
-        assertEquals(
-                Collections.singletonList(TEST_NETWORK_2),
-                vpnSnapShot.vpn.mNetworkCapabilities.getUnderlyingNetworks());
-        verify(mMockNetworkAgent)
-                .doSetUnderlyingNetworks(Collections.singletonList(TEST_NETWORK_2));
-
-        // Mock the MOBIKE procedure
-        vpnSnapShot.ikeCb.onIkeSessionConnectionInfoChanged(createIkeConnectInfo_2());
-        vpnSnapShot.childCb.onIpSecTransformsMigrated(
-                createIpSecTransform(), createIpSecTransform());
-
-        verify(mIpSecService).setNetworkForTunnelInterface(
-                eq(TEST_TUNNEL_RESOURCE_ID), eq(TEST_NETWORK_2), anyString());
-
-        // Expect 2 times: one for initial setup and one for MOBIKE
-        verifyApplyTunnelModeTransforms(2);
-
-        // Verify mNetworkAgent is updated
-        verify(mMockNetworkAgent).doSendLinkProperties(argThat(lp -> lp.getMtu() == newMtu));
-        verify(mMockNetworkAgent, never()).unregister();
-        // No further doSetUnderlyingNetworks interaction. The interaction count should stay one.
-        verify(mMockNetworkAgent, times(1)).doSetUnderlyingNetworks(any());
-        vpnSnapShot.vpn.mVpnRunner.exitVpnRunner();
-    }
-
-    @Test
-    public void testStartPlatformVpnMobility_mobikeEnabledMtuDoesNotSupportIpv6() throws Exception {
-        final PlatformVpnSnapshot vpnSnapShot =
-                verifySetupPlatformVpn(
-                        createIkeConfig(createIkeConnectInfo(), true /* isMobikeEnabled */));
-
-        // Set MTU below 1280
-        final int newMtu = IPV6_MIN_MTU - 1;
-        doReturn(newMtu).when(mTestDeps).calculateVpnMtu(any(), anyInt(), anyInt(), anyBoolean());
-
-        // Mock new network available & MOBIKE procedures
-        vpnSnapShot.nwCb.onAvailable(TEST_NETWORK_2);
-        vpnSnapShot.nwCb.onCapabilitiesChanged(TEST_NETWORK_2,
-                new NetworkCapabilities.Builder().build());
-        // Verify mNetworkCapabilities is updated
-        verify(mMockNetworkAgent, timeout(TEST_TIMEOUT_MS))
-                .doSetUnderlyingNetworks(Collections.singletonList(TEST_NETWORK_2));
-        assertEquals(
-                Collections.singletonList(TEST_NETWORK_2),
-                vpnSnapShot.vpn.mNetworkCapabilities.getUnderlyingNetworks());
-
-        vpnSnapShot.ikeCb.onIkeSessionConnectionInfoChanged(createIkeConnectInfo_2());
-        vpnSnapShot.childCb.onIpSecTransformsMigrated(
-                createIpSecTransform(), createIpSecTransform());
-
-        // Verify removal of IPv6 addresses and routes triggers a network agent restart
-        final ArgumentCaptor<LinkProperties> lpCaptor =
-                ArgumentCaptor.forClass(LinkProperties.class);
-        verify(mTestDeps, times(2))
-                .newNetworkAgent(any(), any(), anyString(), any(), lpCaptor.capture(), any(), any(),
-                        any(), any());
-        verify(mMockNetworkAgent).unregister();
-        // mMockNetworkAgent is an old NetworkAgent, so it won't update LinkProperties after
-        // unregistering.
-        verify(mMockNetworkAgent, never()).doSendLinkProperties(any());
-
-        final LinkProperties lp = lpCaptor.getValue();
-
-        for (LinkAddress addr : lp.getLinkAddresses()) {
-            if (addr.isIpv6()) {
-                fail("IPv6 address found on VPN with MTU < IPv6 minimum MTU");
-            }
-        }
-
-        for (InetAddress dnsAddr : lp.getDnsServers()) {
-            if (dnsAddr instanceof Inet6Address) {
-                fail("IPv6 DNS server found on VPN with MTU < IPv6 minimum MTU");
-            }
-        }
-
-        for (RouteInfo routeInfo : lp.getRoutes()) {
-            if (routeInfo.getDestinationLinkAddress().isIpv6()
-                    && !routeInfo.isIPv6UnreachableDefault()) {
-                fail("IPv6 route found on VPN with MTU < IPv6 minimum MTU");
-            }
-        }
-
-        assertEquals(newMtu, lp.getMtu());
-
-        vpnSnapShot.vpn.mVpnRunner.exitVpnRunner();
-    }
-
-    @Test
-    public void testStartPlatformVpnReestablishes_mobikeDisabled() throws Exception {
-        final PlatformVpnSnapshot vpnSnapShot = verifySetupPlatformVpn(
-                createIkeConfig(createIkeConnectInfo(), false /* isMobikeEnabled */));
-
-        // Forget the first IKE creation to be prepared to capture callbacks of the second
-        // IKE session
-        resetIkev2SessionCreator(mock(Vpn.IkeSessionWrapper.class));
-
-        // Mock network switch
-        vpnSnapShot.nwCb.onLost(TEST_NETWORK);
-        vpnSnapShot.nwCb.onAvailable(TEST_NETWORK_2);
-        // The old IKE Session will not be killed until receiving network capabilities change.
-        verify(mIkeSessionWrapper, never()).kill();
-
-        vpnSnapShot.nwCb.onCapabilitiesChanged(
-                TEST_NETWORK_2, new NetworkCapabilities.Builder().build());
-        // Verify the old IKE Session is killed
-        verify(mIkeSessionWrapper, timeout(TEST_TIMEOUT_MS)).kill();
-
-        // Capture callbacks of the new IKE Session
-        final Pair<IkeSessionCallback, ChildSessionCallback> cbPair =
-                verifyCreateIkeAndCaptureCbs();
-        final IkeSessionCallback ikeCb = cbPair.first;
-        final ChildSessionCallback childCb = cbPair.second;
-
-        // Mock the IKE Session setup
-        ikeCb.onOpened(createIkeConfig(createIkeConnectInfo_2(), false /* isMobikeEnabled */));
-
-        childCb.onIpSecTransformCreated(createIpSecTransform(), IpSecManager.DIRECTION_IN);
-        childCb.onIpSecTransformCreated(createIpSecTransform(), IpSecManager.DIRECTION_OUT);
-        childCb.onOpened(createChildConfig());
-
-        // Expect 2 times since there have been two Session setups
-        verifyApplyTunnelModeTransforms(2);
-
-        // Verify mNetworkCapabilities and mNetworkAgent are updated
-        assertEquals(
-                Collections.singletonList(TEST_NETWORK_2),
-                vpnSnapShot.vpn.mNetworkCapabilities.getUnderlyingNetworks());
-        verify(mMockNetworkAgent)
-                .doSetUnderlyingNetworks(Collections.singletonList(TEST_NETWORK_2));
-
-        vpnSnapShot.vpn.mVpnRunner.exitVpnRunner();
-    }
-
-    private String getDump(@NonNull final Vpn vpn) {
-        final StringWriter sw = new StringWriter();
-        final IndentingPrintWriter writer = new IndentingPrintWriter(sw, "");
-        vpn.dump(writer);
-        writer.flush();
-        return sw.toString();
-    }
-
-    private int countMatches(@NonNull final Pattern regexp, @NonNull final String string) {
-        final Matcher m = regexp.matcher(string);
-        int i = 0;
-        while (m.find()) ++i;
-        return i;
-    }
-
-    @Test
-    public void testNCEventChanges() throws Exception {
-        final NetworkCapabilities.Builder ncBuilder = new NetworkCapabilities.Builder()
-                .addTransportType(TRANSPORT_CELLULAR)
-                .addCapability(NET_CAPABILITY_INTERNET)
-                .addCapability(NET_CAPABILITY_NOT_RESTRICTED)
-                .setLinkDownstreamBandwidthKbps(1000)
-                .setLinkUpstreamBandwidthKbps(500);
-
-        final Ikev2VpnProfile ikeProfile =
-                new Ikev2VpnProfile.Builder(TEST_VPN_SERVER, TEST_VPN_IDENTITY)
-                        .setAuthPsk(TEST_VPN_PSK)
-                        .setBypassable(true /* isBypassable */)
-                        .setAutomaticNattKeepaliveTimerEnabled(true)
-                        .setAutomaticIpVersionSelectionEnabled(true)
-                        .build();
-
-        final PlatformVpnSnapshot vpnSnapShot =
-                verifySetupPlatformVpn(ikeProfile.toVpnProfile(),
-                        createIkeConfig(createIkeConnectInfo(), true /* isMobikeEnabled */),
-                        ncBuilder.build(), false /* mtuSupportsIpv6 */,
-                        true /* areLongLivedTcpConnectionsExpensive */);
-
-        // Calls to onCapabilitiesChanged will be thrown to the executor for execution ; by
-        // default this will incur a 10ms delay before it's executed, messing with the timing
-        // of the log and having the checks for counts in equals() below flake.
-        mExecutor.executeDirect = true;
-
-        // First nc changed triggered by verifySetupPlatformVpn
-        final Pattern pattern = Pattern.compile("Cap changed from", Pattern.MULTILINE);
-        final String stage1 = getDump(vpnSnapShot.vpn);
-        assertEquals(1, countMatches(pattern, stage1));
-
-        vpnSnapShot.nwCb.onCapabilitiesChanged(TEST_NETWORK, ncBuilder.build());
-        final String stage2 = getDump(vpnSnapShot.vpn);
-        // Was the same caps, there should still be only 1 match
-        assertEquals(1, countMatches(pattern, stage2));
-
-        ncBuilder.setLinkDownstreamBandwidthKbps(1200)
-                .setLinkUpstreamBandwidthKbps(300);
-        vpnSnapShot.nwCb.onCapabilitiesChanged(TEST_NETWORK, ncBuilder.build());
-        final String stage3 = getDump(vpnSnapShot.vpn);
-        // Was not an important change, should not be logged, still only 1 match
-        assertEquals(1, countMatches(pattern, stage3));
-
-        ncBuilder.addCapability(NET_CAPABILITY_TEMPORARILY_NOT_METERED);
-        vpnSnapShot.nwCb.onCapabilitiesChanged(TEST_NETWORK, ncBuilder.build());
-        final String stage4 = getDump(vpnSnapShot.vpn);
-        // Change to caps is important, should cause a new match
-        assertEquals(2, countMatches(pattern, stage4));
-
-        ncBuilder.removeCapability(NET_CAPABILITY_TEMPORARILY_NOT_METERED);
-        ncBuilder.setLinkDownstreamBandwidthKbps(600);
-        vpnSnapShot.nwCb.onCapabilitiesChanged(TEST_NETWORK, ncBuilder.build());
-        final String stage5 = getDump(vpnSnapShot.vpn);
-        // Change to caps is important, should cause a new match even with the unimportant change
-        assertEquals(3, countMatches(pattern, stage5));
-    }
-    // TODO : beef up event logs tests
-
-    private void verifyHandlingNetworkLoss(PlatformVpnSnapshot vpnSnapShot) throws Exception {
-        // Forget the #sendLinkProperties during first setup.
-        reset(mMockNetworkAgent);
-
-        // Mock network loss
-        vpnSnapShot.nwCb.onLost(TEST_NETWORK);
-
-        // Mock the grace period expires
-        verify(mExecutor, atLeastOnce()).schedule(any(Runnable.class), anyLong(), any());
-
-        final ArgumentCaptor<LinkProperties> lpCaptor =
-                ArgumentCaptor.forClass(LinkProperties.class);
-        verify(mMockNetworkAgent, timeout(TEST_TIMEOUT_MS))
-                .doSendLinkProperties(lpCaptor.capture());
-        final LinkProperties lp = lpCaptor.getValue();
-
-        assertNull(lp.getInterfaceName());
-        final List<RouteInfo> expectedRoutes = Arrays.asList(
-                new RouteInfo(new IpPrefix(Inet4Address.ANY, 0), null /* gateway */,
-                        null /* iface */, RTN_UNREACHABLE),
-                new RouteInfo(new IpPrefix(Inet6Address.ANY, 0), null /* gateway */,
-                        null /* iface */, RTN_UNREACHABLE));
-        assertEquals(expectedRoutes, lp.getRoutes());
-
-        verify(mMockNetworkAgent, timeout(TEST_TIMEOUT_MS)).unregister();
-    }
-
-    @Test
-    public void testStartPlatformVpnHandlesNetworkLoss_mobikeEnabled() throws Exception {
-        final PlatformVpnSnapshot vpnSnapShot = verifySetupPlatformVpn(
-                createIkeConfig(createIkeConnectInfo(), true /* isMobikeEnabled */));
-        verifyHandlingNetworkLoss(vpnSnapShot);
-    }
-
-    @Test
-    public void testStartPlatformVpnHandlesNetworkLoss_mobikeDisabled() throws Exception {
-        final PlatformVpnSnapshot vpnSnapShot = verifySetupPlatformVpn(
-                createIkeConfig(createIkeConnectInfo(), false /* isMobikeEnabled */));
-        verifyHandlingNetworkLoss(vpnSnapShot);
-    }
-
-    private ConnectivityDiagnosticsCallback getConnectivityDiagCallback() {
-        final ArgumentCaptor<ConnectivityDiagnosticsCallback> cdcCaptor =
-                ArgumentCaptor.forClass(ConnectivityDiagnosticsCallback.class);
-        verify(mCdm).registerConnectivityDiagnosticsCallback(
-                any(), any(), cdcCaptor.capture());
-        return cdcCaptor.getValue();
-    }
-
-    private DataStallReport createDataStallReport() {
-        return new DataStallReport(TEST_NETWORK, 1234 /* reportTimestamp */,
-                1 /* detectionMethod */, new LinkProperties(), new NetworkCapabilities(),
-                new PersistableBundle());
-    }
-
-    private void verifyMobikeTriggered(List<Network> expected, int retryIndex) {
-        // Verify retry is scheduled
-        final long expectedDelayMs = mTestDeps.getValidationFailRecoveryMs(retryIndex);
-        final ArgumentCaptor<Long> delayCaptor = ArgumentCaptor.forClass(Long.class);
-        verify(mExecutor, times(retryIndex + 1)).schedule(
-                any(Runnable.class), delayCaptor.capture(), eq(TimeUnit.MILLISECONDS));
-        final List<Long> delays = delayCaptor.getAllValues();
-        assertEquals(expectedDelayMs, (long) delays.get(delays.size() - 1));
-
-        final ArgumentCaptor<Network> networkCaptor = ArgumentCaptor.forClass(Network.class);
-        verify(mIkeSessionWrapper, timeout(TEST_TIMEOUT_MS + expectedDelayMs))
-                .setNetwork(networkCaptor.capture(), anyInt() /* ipVersion */,
-                        anyInt() /* encapType */, anyInt() /* keepaliveDelay */);
-        assertEquals(expected, Collections.singletonList(networkCaptor.getValue()));
-    }
-
-    @Test
-    public void testDataStallInIkev2VpnMobikeDisabled() throws Exception {
-        final PlatformVpnSnapshot vpnSnapShot = verifySetupPlatformVpn(
-                createIkeConfig(createIkeConnectInfo(), false /* isMobikeEnabled */));
-
-        doReturn(TEST_NETWORK).when(mMockNetworkAgent).getNetwork();
-        ((Vpn.IkeV2VpnRunner) vpnSnapShot.vpn.mVpnRunner).onValidationStatus(
-                NetworkAgent.VALIDATION_STATUS_NOT_VALID);
-
-        // Should not trigger MOBIKE if MOBIKE is not enabled
-        verify(mIkeSessionWrapper, never()).setNetwork(any() /* network */,
-                anyInt() /* ipVersion */, anyInt() /* encapType */, anyInt() /* keepaliveDelay */);
-    }
-
-    @Test
-    public void testDataStallInIkev2VpnRecoveredByMobike() throws Exception {
-        final PlatformVpnSnapshot vpnSnapShot = verifySetupPlatformVpn(
-                createIkeConfig(createIkeConnectInfo(), true /* isMobikeEnabled */));
-
-        doReturn(TEST_NETWORK).when(mMockNetworkAgent).getNetwork();
-        ((Vpn.IkeV2VpnRunner) vpnSnapShot.vpn.mVpnRunner).onValidationStatus(
-                NetworkAgent.VALIDATION_STATUS_NOT_VALID);
-        // Verify MOBIKE is triggered
-        verifyMobikeTriggered(vpnSnapShot.vpn.mNetworkCapabilities.getUnderlyingNetworks(),
-                0 /* retryIndex */);
-        // Validation failure on VPN network should trigger a re-evaluation request for the
-        // underlying network.
-        verify(mConnectivityManager).reportNetworkConnectivity(TEST_NETWORK, false);
-
-        reset(mIkev2SessionCreator);
-        reset(mExecutor);
-
-        // Send validation status update.
-        // Recovered and get network validated. It should not trigger the ike session reset.
-        ((Vpn.IkeV2VpnRunner) vpnSnapShot.vpn.mVpnRunner).onValidationStatus(
-                NetworkAgent.VALIDATION_STATUS_VALID);
-        // Verify that the retry count is reset. The mValidationFailRetryCount will not be reset
-        // until the executor finishes the execute() call, so wait until the all tasks are executed.
-        waitForIdleSerialExecutor(mExecutor, TEST_TIMEOUT_MS);
-        assertEquals(0,
-                ((Vpn.IkeV2VpnRunner) vpnSnapShot.vpn.mVpnRunner).mValidationFailRetryCount);
-        verify(mIkev2SessionCreator, never()).createIkeSession(
-                any(), any(), any(), any(), any(), any());
-
-        reset(mIkeSessionWrapper);
-        reset(mExecutor);
-
-        // Another validation fail should trigger another reportNetworkConnectivity
-        ((Vpn.IkeV2VpnRunner) vpnSnapShot.vpn.mVpnRunner).onValidationStatus(
-                NetworkAgent.VALIDATION_STATUS_NOT_VALID);
-        verifyMobikeTriggered(vpnSnapShot.vpn.mNetworkCapabilities.getUnderlyingNetworks(),
-                0 /* retryIndex */);
-        verify(mConnectivityManager, times(2)).reportNetworkConnectivity(TEST_NETWORK, false);
-    }
-
-    @Test
-    public void testDataStallInIkev2VpnNotRecoveredByMobike() throws Exception {
-        final PlatformVpnSnapshot vpnSnapShot = verifySetupPlatformVpn(
-                createIkeConfig(createIkeConnectInfo(), true /* isMobikeEnabled */));
-
-        int retry = 0;
-        doReturn(TEST_NETWORK).when(mMockNetworkAgent).getNetwork();
-        ((Vpn.IkeV2VpnRunner) vpnSnapShot.vpn.mVpnRunner).onValidationStatus(
-                NetworkAgent.VALIDATION_STATUS_NOT_VALID);
-        verifyMobikeTriggered(vpnSnapShot.vpn.mNetworkCapabilities.getUnderlyingNetworks(),
-                retry++);
-        // Validation failure on VPN network should trigger a re-evaluation request for the
-        // underlying network.
-        verify(mConnectivityManager).reportNetworkConnectivity(TEST_NETWORK, false);
-        reset(mIkev2SessionCreator);
-
-        // Second validation status update.
-        ((Vpn.IkeV2VpnRunner) vpnSnapShot.vpn.mVpnRunner).onValidationStatus(
-                NetworkAgent.VALIDATION_STATUS_NOT_VALID);
-        verifyMobikeTriggered(vpnSnapShot.vpn.mNetworkCapabilities.getUnderlyingNetworks(),
-                retry++);
-        // Call to reportNetworkConnectivity should only happen once. No further interaction.
-        verify(mConnectivityManager, times(1)).reportNetworkConnectivity(TEST_NETWORK, false);
-
-        // Use real delay to verify reset session will not be performed if there is an existing
-        // recovery for resetting the session.
-        mExecutor.delayMs = TestExecutor.REAL_DELAY;
-        mExecutor.executeDirect = true;
-        // Send validation status update should result in ike session reset.
-        ((Vpn.IkeV2VpnRunner) vpnSnapShot.vpn.mVpnRunner).onValidationStatus(
-                NetworkAgent.VALIDATION_STATUS_NOT_VALID);
-
-        // Verify session reset is scheduled
-        long expectedDelay = mTestDeps.getValidationFailRecoveryMs(retry++);
-        final ArgumentCaptor<Long> delayCaptor = ArgumentCaptor.forClass(Long.class);
-        verify(mExecutor, times(retry)).schedule(any(Runnable.class), delayCaptor.capture(),
-                eq(TimeUnit.MILLISECONDS));
-        final List<Long> delays = delayCaptor.getAllValues();
-        assertEquals(expectedDelay, (long) delays.get(delays.size() - 1));
-        // Call to reportNetworkConnectivity should only happen once. No further interaction.
-        verify(mConnectivityManager, times(1)).reportNetworkConnectivity(TEST_NETWORK, false);
-
-        // Another invalid status reported should not trigger other scheduled recovery.
-        expectedDelay = mTestDeps.getValidationFailRecoveryMs(retry++);
-        ((Vpn.IkeV2VpnRunner) vpnSnapShot.vpn.mVpnRunner).onValidationStatus(
-                NetworkAgent.VALIDATION_STATUS_NOT_VALID);
-        verify(mExecutor, never()).schedule(
-                any(Runnable.class), eq(expectedDelay), eq(TimeUnit.MILLISECONDS));
-
-        // Verify that session being reset
-        verify(mIkev2SessionCreator, timeout(TEST_TIMEOUT_MS + expectedDelay))
-                .createIkeSession(any(), any(), any(), any(), any(), any());
-        // Call to reportNetworkConnectivity should only happen once. No further interaction.
-        verify(mConnectivityManager, times(1)).reportNetworkConnectivity(TEST_NETWORK, false);
-    }
-
-    @Test
-    public void testStartLegacyVpnType() throws Exception {
-        setMockedUsers(PRIMARY_USER);
-        final Vpn vpn = createVpn(PRIMARY_USER.id);
-        final VpnProfile profile = new VpnProfile("testProfile" /* key */);
-
-        profile.type = VpnProfile.TYPE_PPTP;
-        assertThrows(UnsupportedOperationException.class, () -> startLegacyVpn(vpn, profile));
-        profile.type = VpnProfile.TYPE_L2TP_IPSEC_PSK;
-        assertThrows(UnsupportedOperationException.class, () -> startLegacyVpn(vpn, profile));
-    }
-
-    @Test
-    public void testStartLegacyVpnModifyProfile_TypePSK() throws Exception {
-        setMockedUsers(PRIMARY_USER);
-        final Vpn vpn = createVpn(PRIMARY_USER.id);
-        final Ikev2VpnProfile ikev2VpnProfile =
-                new Ikev2VpnProfile.Builder(TEST_VPN_SERVER, TEST_VPN_IDENTITY)
-                        .setAuthPsk(TEST_VPN_PSK)
-                        .build();
-        final VpnProfile profile = ikev2VpnProfile.toVpnProfile();
-
-        startLegacyVpn(vpn, profile);
-        assertEquals(profile, ikev2VpnProfile.toVpnProfile());
-    }
-
-    private void assertTransportInfoMatches(NetworkCapabilities nc, int type) {
-        assertNotNull(nc);
-        VpnTransportInfo ti = (VpnTransportInfo) nc.getTransportInfo();
-        assertNotNull(ti);
-        assertEquals(type, ti.getType());
-    }
-
-    // Make it public and un-final so as to spy it
-    public class TestDeps extends Vpn.Dependencies {
-        TestDeps() {}
-
-        @Override
-        public boolean isCallerSystem() {
-            return true;
-        }
-
-        @Override
-        public PendingIntent getIntentForStatusPanel(Context context) {
-            return null;
-        }
-
-        @Override
-        public ParcelFileDescriptor adoptFd(Vpn vpn, int mtu) {
-            return new ParcelFileDescriptor(new FileDescriptor());
-        }
-
-        @Override
-        public int jniCreate(Vpn vpn, int mtu) {
-            // Pick a random positive number as fd to return.
-            return 345;
-        }
-
-        @Override
-        public String jniGetName(Vpn vpn, int fd) {
-            return TEST_IFACE_NAME;
-        }
-
-        @Override
-        public int jniSetAddresses(Vpn vpn, String interfaze, String addresses) {
-            if (addresses == null) return 0;
-            // Return the number of addresses.
-            return addresses.split(" ").length;
-        }
-
-        @Override
-        public void setBlocking(FileDescriptor fd, boolean blocking) {}
-
-        @Override
-        public DeviceIdleInternal getDeviceIdleInternal() {
-            return mDeviceIdleInternal;
-        }
-
-        @Override
-        public long getValidationFailRecoveryMs(int retryCount) {
-            // Simply return retryCount as the delay seconds for retrying.
-            return retryCount * 100L;
-        }
-
-        @Override
-        public ScheduledThreadPoolExecutor newScheduledThreadPoolExecutor() {
-            return mExecutor;
-        }
-
-        public boolean mIgnoreCallingUidChecks = true;
-        @Override
-        public void verifyCallingUidAndPackage(Context context, String packageName, int userId) {
-            if (!mIgnoreCallingUidChecks) {
-                super.verifyCallingUidAndPackage(context, packageName, userId);
-            }
-        }
-    }
-
-    /**
-     * Mock some methods of vpn object.
-     */
-    private Vpn createVpn(@UserIdInt int userId) {
-        final Context asUserContext = mock(Context.class, AdditionalAnswers.delegatesTo(mContext));
-        doReturn(UserHandle.of(userId)).when(asUserContext).getUser();
-        when(mContext.createContextAsUser(eq(UserHandle.of(userId)), anyInt()))
-                .thenReturn(asUserContext);
-        final TestLooper testLooper = new TestLooper();
-        final Vpn vpn = new Vpn(testLooper.getLooper(), mContext, mTestDeps, mNetService,
-                mNetd, userId, mVpnProfileStore, mSystemServices, mIkev2SessionCreator);
-        verify(mConnectivityManager, times(1)).registerNetworkProvider(argThat(
-                provider -> provider.getName().contains("VpnNetworkProvider")
-        ));
-        return vpn;
-    }
-
-    /**
-     * Populate {@link #mUserManager} with a list of fake users.
-     */
-    private void setMockedUsers(UserInfo... users) {
-        final Map<Integer, UserInfo> userMap = new ArrayMap<>();
-        for (UserInfo user : users) {
-            userMap.put(user.id, user);
-        }
-
-        /**
-         * @see UserManagerService#getUsers(boolean)
-         */
-        doAnswer(invocation -> {
-            final ArrayList<UserInfo> result = new ArrayList<>(users.length);
-            for (UserInfo ui : users) {
-                if (ui.isEnabled() && !ui.partial) {
-                    result.add(ui);
-                }
-            }
-            return result;
-        }).when(mUserManager).getAliveUsers();
-
-        doAnswer(invocation -> {
-            final int id = (int) invocation.getArguments()[0];
-            return userMap.get(id);
-        }).when(mUserManager).getUserInfo(anyInt());
-    }
-
-    /**
-     * Populate {@link #mPackageManager} with a fake packageName-to-UID mapping.
-     */
-    private void setMockedPackages(final Map<String, Integer> packages) {
-        try {
-            doAnswer(invocation -> {
-                final String appName = (String) invocation.getArguments()[0];
-                final int userId = (int) invocation.getArguments()[1];
-                Integer appId = packages.get(appName);
-                if (appId == null) throw new PackageManager.NameNotFoundException(appName);
-                return UserHandle.getUid(userId, appId);
-            }).when(mPackageManager).getPackageUidAsUser(anyString(), anyInt());
-        } catch (Exception e) {
-        }
-    }
-}
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsAdvertiserTest.kt b/tests/unit/java/com/android/server/connectivity/mdns/MdnsAdvertiserTest.kt
index 121f844..b8ebf0f 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsAdvertiserTest.kt
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsAdvertiserTest.kt
@@ -16,6 +16,8 @@
 
 package com.android.server.connectivity.mdns
 
+import android.content.Context
+import android.content.res.Resources
 import android.net.InetAddresses.parseNumericAddress
 import android.net.LinkAddress
 import android.net.Network
@@ -26,13 +28,17 @@
 import android.os.Build
 import android.os.Handler
 import android.os.HandlerThread
+import com.android.connectivity.resources.R
 import com.android.net.module.util.SharedLog
+import com.android.server.connectivity.ConnectivityResources
 import com.android.server.connectivity.mdns.MdnsAdvertiser.AdvertiserCallback
+import com.android.server.connectivity.mdns.MdnsInterfaceAdvertiser.CONFLICT_SERVICE
 import com.android.server.connectivity.mdns.MdnsSocketProvider.SocketCallback
 import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
 import com.android.testutils.DevSdkIgnoreRunner
 import com.android.testutils.waitForIdle
 import java.net.NetworkInterface
+import java.time.Duration
 import java.util.Objects
 import java.util.concurrent.CompletableFuture
 import java.util.concurrent.TimeUnit
@@ -55,9 +61,10 @@
 
 private const val SERVICE_ID_1 = 1
 private const val SERVICE_ID_2 = 2
-private const val LONG_SERVICE_ID_1 = 3
-private const val LONG_SERVICE_ID_2 = 4
-private const val CASE_INSENSITIVE_TEST_SERVICE_ID = 5
+private const val SERVICE_ID_3 = 3
+private const val LONG_SERVICE_ID_1 = 4
+private const val LONG_SERVICE_ID_2 = 5
+private const val CASE_INSENSITIVE_TEST_SERVICE_ID = 6
 private const val TIMEOUT_MS = 10_000L
 private val TEST_ADDR = parseNumericAddress("2001:db8::123")
 private val TEST_ADDR2 = parseNumericAddress("2001:db8::124")
@@ -71,6 +78,7 @@
 private const val TEST_SUBTYPE2 = "_subtype2"
 private val TEST_INTERFACE1 = "test_iface1"
 private val TEST_INTERFACE2 = "test_iface2"
+private val TEST_CLIENT_UID_1 = 10010
 private val TEST_OFFLOAD_PACKET1 = byteArrayOf(0x01, 0x02, 0x03)
 private val TEST_OFFLOAD_PACKET2 = byteArrayOf(0x02, 0x03, 0x04)
 private val DEFAULT_ADVERTISING_OPTION = MdnsAdvertisingOptions.getDefaultOptions()
@@ -150,6 +158,12 @@
     OffloadEngine.OFFLOAD_TYPE_REPLY.toLong()
 )
 
+private val SERVICES_PRIORITY_LIST = arrayOf(
+    "0:_advertisertest._tcp",
+    "5:_prioritytest._udp",
+    "5:_otherprioritytest._tcp"
+)
+
 @RunWith(DevSdkIgnoreRunner::class)
 @IgnoreUpTo(Build.VERSION_CODES.S_V2)
 class MdnsAdvertiserTest {
@@ -164,6 +178,8 @@
     private val mockInterfaceAdvertiser1 = mock(MdnsInterfaceAdvertiser::class.java)
     private val mockInterfaceAdvertiser2 = mock(MdnsInterfaceAdvertiser::class.java)
     private val mockDeps = mock(MdnsAdvertiser.Dependencies::class.java)
+    private val context = mock(Context::class.java)
+    private val resources = mock(Resources::class.java)
     private val flags = MdnsFeatureFlags.newBuilder().setIsMdnsOffloadFeatureEnabled(true).build()
 
     @Before
@@ -184,12 +200,21 @@
         doReturn(TEST_INTERFACE2).`when`(mockInterfaceAdvertiser2).socketInterfaceName
         doReturn(TEST_OFFLOAD_PACKET1).`when`(mockInterfaceAdvertiser1).getRawOffloadPayload(
             SERVICE_ID_1)
+        doReturn(TEST_OFFLOAD_PACKET1).`when`(mockInterfaceAdvertiser1).getRawOffloadPayload(
+            SERVICE_ID_2)
+        doReturn(TEST_OFFLOAD_PACKET1).`when`(mockInterfaceAdvertiser1).getRawOffloadPayload(
+            SERVICE_ID_3)
         doReturn(TEST_OFFLOAD_PACKET1).`when`(mockInterfaceAdvertiser2).getRawOffloadPayload(
             SERVICE_ID_1)
+        doReturn(resources).`when`(context).getResources()
+        doReturn(SERVICES_PRIORITY_LIST).`when`(resources).getStringArray(
+            R.array.config_nsdOffloadServicesPriority)
+        ConnectivityResources.setResourcesContextForTest(context)
     }
 
     @After
     fun tearDown() {
+        ConnectivityResources.setResourcesContextForTest(null)
         thread.quitSafely()
         thread.join()
     }
@@ -203,9 +228,9 @@
     @Test
     fun testAddService_OneNetwork() {
         val advertiser =
-            MdnsAdvertiser(thread.looper, socketProvider, cb, mockDeps, sharedlog, flags)
+            MdnsAdvertiser(thread.looper, socketProvider, cb, mockDeps, sharedlog, flags, context)
         postSync { advertiser.addOrUpdateService(SERVICE_ID_1, SERVICE_1,
-                DEFAULT_ADVERTISING_OPTION) }
+                DEFAULT_ADVERTISING_OPTION, TEST_CLIENT_UID_1) }
 
         val socketCbCaptor = ArgumentCaptor.forClass(SocketCallback::class.java)
         verify(socketProvider).requestSocket(eq(TEST_NETWORK_1), socketCbCaptor.capture())
@@ -232,7 +257,10 @@
         verify(cb).onOffloadStartOrUpdate(eq(TEST_INTERFACE1), eq(OFFLOAD_SERVICEINFO_NO_SUBTYPE))
 
         // Service is conflicted.
-        postSync { intAdvCbCaptor.value.onServiceConflict(mockInterfaceAdvertiser1, SERVICE_ID_1) }
+        postSync {
+            intAdvCbCaptor.value
+                    .onServiceConflict(mockInterfaceAdvertiser1, SERVICE_ID_1, CONFLICT_SERVICE)
+        }
 
         // Verify the metrics data
         doReturn(25).`when`(mockInterfaceAdvertiser1).getServiceRepliedRequestsCount(SERVICE_ID_1)
@@ -265,9 +293,9 @@
     @Test
     fun testAddService_AllNetworksWithSubType() {
         val advertiser =
-            MdnsAdvertiser(thread.looper, socketProvider, cb, mockDeps, sharedlog, flags)
+            MdnsAdvertiser(thread.looper, socketProvider, cb, mockDeps, sharedlog, flags, context)
         postSync { advertiser.addOrUpdateService(SERVICE_ID_1, ALL_NETWORKS_SERVICE_SUBTYPE,
-                DEFAULT_ADVERTISING_OPTION) }
+                DEFAULT_ADVERTISING_OPTION, TEST_CLIENT_UID_1) }
 
         val socketCbCaptor = ArgumentCaptor.forClass(SocketCallback::class.java)
         verify(socketProvider).requestSocket(eq(ALL_NETWORKS_SERVICE_SUBTYPE.network),
@@ -286,9 +314,9 @@
                 eq(thread.looper), any(), intAdvCbCaptor2.capture(), eq(TEST_HOSTNAME), any(), any()
         )
         verify(mockInterfaceAdvertiser1).addService(
-                anyInt(), eq(ALL_NETWORKS_SERVICE_SUBTYPE))
+                anyInt(), eq(ALL_NETWORKS_SERVICE_SUBTYPE), any())
         verify(mockInterfaceAdvertiser2).addService(
-                anyInt(), eq(ALL_NETWORKS_SERVICE_SUBTYPE))
+                anyInt(), eq(ALL_NETWORKS_SERVICE_SUBTYPE), any())
 
         doReturn(false).`when`(mockInterfaceAdvertiser1).isProbing(SERVICE_ID_1)
         postSync { intAdvCbCaptor1.value.onServiceProbingSucceeded(
@@ -305,9 +333,18 @@
                 argThat { it.matches(ALL_NETWORKS_SERVICE_SUBTYPE) })
 
         // Services are conflicted.
-        postSync { intAdvCbCaptor1.value.onServiceConflict(mockInterfaceAdvertiser1, SERVICE_ID_1) }
-        postSync { intAdvCbCaptor1.value.onServiceConflict(mockInterfaceAdvertiser1, SERVICE_ID_1) }
-        postSync { intAdvCbCaptor2.value.onServiceConflict(mockInterfaceAdvertiser2, SERVICE_ID_1) }
+        postSync {
+            intAdvCbCaptor1.value
+                    .onServiceConflict(mockInterfaceAdvertiser1, SERVICE_ID_1, CONFLICT_SERVICE)
+        }
+        postSync {
+            intAdvCbCaptor1.value
+                    .onServiceConflict(mockInterfaceAdvertiser1, SERVICE_ID_1, CONFLICT_SERVICE)
+        }
+        postSync {
+            intAdvCbCaptor2.value
+                    .onServiceConflict(mockInterfaceAdvertiser2, SERVICE_ID_1, CONFLICT_SERVICE)
+        }
 
         // Verify the metrics data
         doReturn(10).`when`(mockInterfaceAdvertiser1).getServiceRepliedRequestsCount(SERVICE_ID_1)
@@ -335,11 +372,70 @@
     }
 
     @Test
+    fun testAddService_OffloadPriority() {
+        val advertiser =
+            MdnsAdvertiser(thread.looper, socketProvider, cb, mockDeps, sharedlog, flags, context)
+        postSync {
+            advertiser.addOrUpdateService(SERVICE_ID_1, SERVICE_1, DEFAULT_ADVERTISING_OPTION,
+                    TEST_CLIENT_UID_1)
+            advertiser.addOrUpdateService(SERVICE_ID_2,
+                NsdServiceInfo("TestService2", "_PRIORITYTEST._udp").apply {
+                    port = 12345
+                    hostAddresses = listOf(TEST_ADDR)
+                }, DEFAULT_ADVERTISING_OPTION, TEST_CLIENT_UID_1)
+            advertiser.addOrUpdateService(
+                SERVICE_ID_3,
+                NsdServiceInfo("TestService3", "_notprioritized._tcp").apply {
+                    port = 12345
+                    hostAddresses = listOf(TEST_ADDR)
+                }, DEFAULT_ADVERTISING_OPTION, TEST_CLIENT_UID_1)
+        }
+
+        val socketCbCaptor = ArgumentCaptor.forClass(SocketCallback::class.java)
+        verify(socketProvider).requestSocket(eq(SERVICE_1.network), socketCbCaptor.capture())
+
+        val socketCb = socketCbCaptor.value
+        postSync { socketCb.onSocketCreated(TEST_SOCKETKEY_1, mockSocket1, listOf(TEST_LINKADDR)) }
+
+        val intAdvCbCaptor1 = ArgumentCaptor.forClass(MdnsInterfaceAdvertiser.Callback::class.java)
+        verify(mockDeps).makeAdvertiser(eq(mockSocket1), eq(listOf(TEST_LINKADDR)),
+            eq(thread.looper), any(), intAdvCbCaptor1.capture(), eq(TEST_HOSTNAME), any(), any()
+        )
+
+        doReturn(false).`when`(mockInterfaceAdvertiser1).isProbing(SERVICE_ID_1)
+        doReturn(false).`when`(mockInterfaceAdvertiser1).isProbing(SERVICE_ID_2)
+        doReturn(false).`when`(mockInterfaceAdvertiser1).isProbing(SERVICE_ID_3)
+        postSync {
+            intAdvCbCaptor1.value.onServiceProbingSucceeded(mockInterfaceAdvertiser1, SERVICE_ID_1)
+            intAdvCbCaptor1.value.onServiceProbingSucceeded(mockInterfaceAdvertiser1, SERVICE_ID_2)
+            intAdvCbCaptor1.value.onServiceProbingSucceeded(mockInterfaceAdvertiser1, SERVICE_ID_3)
+        }
+
+        verify(cb).onOffloadStartOrUpdate(eq(TEST_INTERFACE1), eq(OFFLOAD_SERVICEINFO_NO_SUBTYPE))
+        verify(cb).onOffloadStartOrUpdate(eq(TEST_INTERFACE1), eq(OffloadServiceInfo(
+            OffloadServiceInfo.Key("TestService2", "_PRIORITYTEST._udp"),
+            emptyList() /* subtypes */,
+            "Android_test.local",
+            TEST_OFFLOAD_PACKET1,
+            5, /* priority */
+            OffloadEngine.OFFLOAD_TYPE_REPLY.toLong()
+        )))
+        verify(cb).onOffloadStartOrUpdate(eq(TEST_INTERFACE1), eq(OffloadServiceInfo(
+            OffloadServiceInfo.Key("TestService3", "_notprioritized._tcp"),
+            emptyList() /* subtypes */,
+            "Android_test.local",
+            TEST_OFFLOAD_PACKET1,
+            Integer.MAX_VALUE, /* priority */
+            OffloadEngine.OFFLOAD_TYPE_REPLY.toLong()
+        )))
+    }
+
+    @Test
     fun testAddService_Conflicts() {
         val advertiser =
-            MdnsAdvertiser(thread.looper, socketProvider, cb, mockDeps, sharedlog, flags)
+            MdnsAdvertiser(thread.looper, socketProvider, cb, mockDeps, sharedlog, flags, context)
         postSync { advertiser.addOrUpdateService(SERVICE_ID_1, SERVICE_1,
-                DEFAULT_ADVERTISING_OPTION) }
+                DEFAULT_ADVERTISING_OPTION, TEST_CLIENT_UID_1) }
 
         val oneNetSocketCbCaptor = ArgumentCaptor.forClass(SocketCallback::class.java)
         verify(socketProvider).requestSocket(eq(TEST_NETWORK_1), oneNetSocketCbCaptor.capture())
@@ -347,18 +443,18 @@
 
         // Register a service with the same name on all networks (name conflict)
         postSync { advertiser.addOrUpdateService(SERVICE_ID_2, ALL_NETWORKS_SERVICE,
-                DEFAULT_ADVERTISING_OPTION) }
+                DEFAULT_ADVERTISING_OPTION, TEST_CLIENT_UID_1) }
         val allNetSocketCbCaptor = ArgumentCaptor.forClass(SocketCallback::class.java)
         verify(socketProvider).requestSocket(eq(null), allNetSocketCbCaptor.capture())
         val allNetSocketCb = allNetSocketCbCaptor.value
 
         postSync { advertiser.addOrUpdateService(LONG_SERVICE_ID_1, LONG_SERVICE_1,
-                DEFAULT_ADVERTISING_OPTION) }
+                DEFAULT_ADVERTISING_OPTION, TEST_CLIENT_UID_1) }
         postSync { advertiser.addOrUpdateService(LONG_SERVICE_ID_2, LONG_ALL_NETWORKS_SERVICE,
-                DEFAULT_ADVERTISING_OPTION) }
+                DEFAULT_ADVERTISING_OPTION, TEST_CLIENT_UID_1) }
 
         postSync { advertiser.addOrUpdateService(CASE_INSENSITIVE_TEST_SERVICE_ID,
-                ALL_NETWORKS_SERVICE_2, DEFAULT_ADVERTISING_OPTION) }
+                ALL_NETWORKS_SERVICE_2, DEFAULT_ADVERTISING_OPTION, TEST_CLIENT_UID_1) }
 
         // Callbacks for matching network and all networks both get the socket
         postSync {
@@ -394,15 +490,15 @@
                 eq(thread.looper), any(), intAdvCbCaptor.capture(), eq(TEST_HOSTNAME), any(), any()
         )
         verify(mockInterfaceAdvertiser1).addService(eq(SERVICE_ID_1),
-                argThat { it.matches(SERVICE_1) })
+                argThat { it.matches(SERVICE_1) }, any())
         verify(mockInterfaceAdvertiser1).addService(eq(SERVICE_ID_2),
-                argThat { it.matches(expectedRenamed) })
+                argThat { it.matches(expectedRenamed) }, any())
         verify(mockInterfaceAdvertiser1).addService(eq(LONG_SERVICE_ID_1),
-                argThat { it.matches(LONG_SERVICE_1) })
+                argThat { it.matches(LONG_SERVICE_1) }, any())
         verify(mockInterfaceAdvertiser1).addService(eq(LONG_SERVICE_ID_2),
-            argThat { it.matches(expectedLongRenamed) })
+            argThat { it.matches(expectedLongRenamed) }, any())
         verify(mockInterfaceAdvertiser1).addService(eq(CASE_INSENSITIVE_TEST_SERVICE_ID),
-            argThat { it.matches(expectedCaseInsensitiveRenamed) })
+            argThat { it.matches(expectedCaseInsensitiveRenamed) }, any())
 
         doReturn(false).`when`(mockInterfaceAdvertiser1).isProbing(SERVICE_ID_1)
         postSync { intAdvCbCaptor.value.onServiceProbingSucceeded(
@@ -425,9 +521,10 @@
     @Test
     fun testAddOrUpdateService_Updates() {
         val advertiser =
-                MdnsAdvertiser(thread.looper, socketProvider, cb, mockDeps, sharedlog, flags)
+                MdnsAdvertiser(thread.looper, socketProvider, cb, mockDeps, sharedlog, flags,
+                    context)
         postSync { advertiser.addOrUpdateService(SERVICE_ID_1, ALL_NETWORKS_SERVICE,
-                DEFAULT_ADVERTISING_OPTION) }
+                DEFAULT_ADVERTISING_OPTION, TEST_CLIENT_UID_1) }
 
         val socketCbCaptor = ArgumentCaptor.forClass(SocketCallback::class.java)
         verify(socketProvider).requestSocket(eq(null), socketCbCaptor.capture())
@@ -436,37 +533,55 @@
         postSync { socketCb.onSocketCreated(TEST_SOCKETKEY_1, mockSocket1, listOf(TEST_LINKADDR)) }
 
         verify(mockInterfaceAdvertiser1).addService(eq(SERVICE_ID_1),
-                argThat { it.matches(ALL_NETWORKS_SERVICE) })
+                argThat { it.matches(ALL_NETWORKS_SERVICE) }, any())
 
         val updateOptions = MdnsAdvertisingOptions.newBuilder().setIsOnlyUpdate(true).build()
 
         // Update with serviceId that is not registered yet should fail
         postSync { advertiser.addOrUpdateService(SERVICE_ID_2, ALL_NETWORKS_SERVICE_SUBTYPE,
-                updateOptions) }
+                updateOptions, TEST_CLIENT_UID_1) }
         verify(cb).onRegisterServiceFailed(SERVICE_ID_2, NsdManager.FAILURE_INTERNAL_ERROR)
 
         // Update service with different NsdServiceInfo should fail
-        postSync { advertiser.addOrUpdateService(SERVICE_ID_1, SERVICE_1_SUBTYPE, updateOptions) }
+        postSync { advertiser.addOrUpdateService(SERVICE_ID_1, SERVICE_1_SUBTYPE, updateOptions,
+                TEST_CLIENT_UID_1) }
         verify(cb).onRegisterServiceFailed(SERVICE_ID_1, NsdManager.FAILURE_INTERNAL_ERROR)
 
         // Update service with same NsdServiceInfo but different subType should succeed
         postSync { advertiser.addOrUpdateService(SERVICE_ID_1, ALL_NETWORKS_SERVICE_SUBTYPE,
-                updateOptions) }
+                updateOptions, TEST_CLIENT_UID_1) }
         verify(mockInterfaceAdvertiser1).updateService(eq(SERVICE_ID_1), eq(setOf(TEST_SUBTYPE)))
 
         // Newly created MdnsInterfaceAdvertiser will get addService() call.
         postSync { socketCb.onSocketCreated(TEST_SOCKETKEY_2, mockSocket2, listOf(TEST_LINKADDR2)) }
         verify(mockInterfaceAdvertiser2).addService(eq(SERVICE_ID_1),
-                argThat { it.matches(ALL_NETWORKS_SERVICE_SUBTYPE) })
+                argThat { it.matches(ALL_NETWORKS_SERVICE_SUBTYPE) }, any())
+    }
+
+    @Test
+    fun testAddOrUpdateService_customTtl_registeredSuccess() {
+        val advertiser = MdnsAdvertiser(
+                thread.looper, socketProvider, cb, mockDeps, sharedlog, flags, context)
+        val updateOptions =
+                MdnsAdvertisingOptions.newBuilder().setTtl(Duration.ofSeconds(30)).build()
+
+        postSync { advertiser.addOrUpdateService(SERVICE_ID_1, ALL_NETWORKS_SERVICE,
+                updateOptions, TEST_CLIENT_UID_1) }
+
+        val socketCbCaptor = ArgumentCaptor.forClass(SocketCallback::class.java)
+        verify(socketProvider).requestSocket(eq(null), socketCbCaptor.capture())
+        val socketCb = socketCbCaptor.value
+        postSync { socketCb.onSocketCreated(TEST_SOCKETKEY_1, mockSocket1, listOf(TEST_LINKADDR)) }
+        verify(mockInterfaceAdvertiser1).addService(eq(SERVICE_ID_1), any(), eq(updateOptions))
     }
 
     @Test
     fun testRemoveService_whenAllServiceRemoved_thenUpdateHostName() {
         val advertiser =
-            MdnsAdvertiser(thread.looper, socketProvider, cb, mockDeps, sharedlog, flags)
+            MdnsAdvertiser(thread.looper, socketProvider, cb, mockDeps, sharedlog, flags, context)
         verify(mockDeps, times(1)).generateHostname()
         postSync { advertiser.addOrUpdateService(SERVICE_ID_1, SERVICE_1,
-                DEFAULT_ADVERTISING_OPTION) }
+                DEFAULT_ADVERTISING_OPTION, TEST_CLIENT_UID_1) }
         postSync { advertiser.removeService(SERVICE_ID_1) }
         verify(mockDeps, times(2)).generateHostname()
     }
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsAnnouncerTest.kt b/tests/unit/java/com/android/server/connectivity/mdns/MdnsAnnouncerTest.kt
index 2797462..27242f1 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsAnnouncerTest.kt
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsAnnouncerTest.kt
@@ -55,6 +55,7 @@
     private val socket = mock(MdnsInterfaceSocket::class.java)
     private val sharedLog = mock(SharedLog::class.java)
     private val buffer = ByteArray(1500)
+    private val flags = MdnsFeatureFlags.newBuilder().build()
 
     @Before
     fun setUp() {
@@ -83,7 +84,7 @@
     @Test
     fun testAnnounce() {
         val replySender = MdnsReplySender(
-                thread.looper, socket, buffer, sharedLog, true /* enableDebugLog */)
+                thread.looper, socket, buffer, sharedLog, true /* enableDebugLog */, flags)
         @Suppress("UNCHECKED_CAST")
         val cb = mock(MdnsPacketRepeater.PacketRepeaterCallback::class.java)
                 as MdnsPacketRepeater.PacketRepeaterCallback<BaseAnnouncementInfo>
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsInterfaceAdvertiserTest.kt b/tests/unit/java/com/android/server/connectivity/mdns/MdnsInterfaceAdvertiserTest.kt
index ee0bd1a..28608bb 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsInterfaceAdvertiserTest.kt
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsInterfaceAdvertiserTest.kt
@@ -18,6 +18,7 @@
 
 import android.net.InetAddresses.parseNumericAddress
 import android.net.LinkAddress
+import android.net.nsd.NsdManager
 import android.net.nsd.NsdServiceInfo
 import android.os.Build
 import android.os.HandlerThread
@@ -26,6 +27,7 @@
 import com.android.server.connectivity.mdns.MdnsAnnouncer.AnnouncementInfo
 import com.android.server.connectivity.mdns.MdnsAnnouncer.BaseAnnouncementInfo
 import com.android.server.connectivity.mdns.MdnsAnnouncer.ExitAnnouncementInfo
+import com.android.server.connectivity.mdns.MdnsInterfaceAdvertiser.CONFLICT_SERVICE
 import com.android.server.connectivity.mdns.MdnsInterfaceAdvertiser.EXIT_ANNOUNCEMENT_DELAY_MS
 import com.android.server.connectivity.mdns.MdnsPacketRepeater.PacketRepeaterCallback
 import com.android.server.connectivity.mdns.MdnsProber.ProbingInfo
@@ -45,6 +47,7 @@
 import org.mockito.Mockito.any
 import org.mockito.Mockito.anyInt
 import org.mockito.Mockito.anyString
+import org.mockito.Mockito.argThat
 import org.mockito.Mockito.doAnswer
 import org.mockito.Mockito.doReturn
 import org.mockito.Mockito.eq
@@ -87,7 +90,8 @@
     private val announcer = mock(MdnsAnnouncer::class.java)
     private val prober = mock(MdnsProber::class.java)
     private val sharedlog = SharedLog("MdnsInterfaceAdvertiserTest")
-    private val flags = MdnsFeatureFlags.newBuilder().build()
+    private val flags = MdnsFeatureFlags.newBuilder()
+            .setIsKnownAnswerSuppressionEnabled(true).build()
     @Suppress("UNCHECKED_CAST")
     private val probeCbCaptor = ArgumentCaptor.forClass(PacketRepeaterCallback::class.java)
             as ArgumentCaptor<PacketRepeaterCallback<ProbingInfo>>
@@ -118,7 +122,8 @@
     @Before
     fun setUp() {
         doReturn(repository).`when`(deps).makeRecordRepository(any(), eq(TEST_HOSTNAME), any())
-        doReturn(replySender).`when`(deps).makeReplySender(anyString(), any(), any(), any(), any())
+        doReturn(replySender).`when`(deps).makeReplySender(
+                anyString(), any(), any(), any(), any(), any())
         doReturn(announcer).`when`(deps).makeMdnsAnnouncer(anyString(), any(), any(), any(), any())
         doReturn(prober).`when`(deps).makeMdnsProber(anyString(), any(), any(), any(), any())
 
@@ -127,7 +132,7 @@
             knownServices.add(inv.getArgument(0))
 
             -1
-        }.`when`(repository).addService(anyInt(), any())
+        }.`when`(repository).addService(anyInt(), any(), any())
         doAnswer { inv ->
             knownServices.remove(inv.getArgument(0))
             null
@@ -200,7 +205,8 @@
     fun testReplyToQuery() {
         addServiceAndFinishProbing(TEST_SERVICE_ID_1, TEST_SERVICE_1)
 
-        val testReply = MdnsReplyInfo(emptyList(), emptyList(), 0, InetSocketAddress(0))
+        val testReply = MdnsReplyInfo(emptyList(), emptyList(), 0, InetSocketAddress(0),
+                InetSocketAddress(0), emptyList())
         doReturn(testReply).`when`(repository).getReply(any(), any())
 
         // Query obtained with:
@@ -235,9 +241,116 @@
     }
 
     @Test
+    fun testReplyToQuery_TruncatedBitSet() {
+        addServiceAndFinishProbing(TEST_SERVICE_ID_1, TEST_SERVICE_1)
+        val src = InetSocketAddress(parseNumericAddress("2001:db8::456"), MdnsConstants.MDNS_PORT)
+        val testReply = MdnsReplyInfo(emptyList(), emptyList(), 400L, InetSocketAddress(0), src,
+                emptyList())
+        val knownAnswersReply = MdnsReplyInfo(emptyList(), emptyList(), 400L, InetSocketAddress(0),
+                src, emptyList())
+        val knownAnswersReply2 = MdnsReplyInfo(emptyList(), emptyList(), 0L, InetSocketAddress(0),
+                src, emptyList())
+        doReturn(testReply).`when`(repository).getReply(
+                argThat { pkg -> pkg.questions.size != 0 && pkg.answers.size == 0 &&
+                        (pkg.flags and MdnsConstants.FLAG_TRUNCATED) != 0},
+                eq(src))
+        doReturn(knownAnswersReply).`when`(repository).getReply(
+                argThat { pkg -> pkg.questions.size == 0 && pkg.answers.size != 0 &&
+                        (pkg.flags and MdnsConstants.FLAG_TRUNCATED) != 0},
+                eq(src))
+        doReturn(knownAnswersReply2).`when`(repository).getReply(
+                argThat { pkg -> pkg.questions.size == 0 && pkg.answers.size != 0 &&
+                        (pkg.flags and MdnsConstants.FLAG_TRUNCATED) == 0},
+                eq(src))
+
+        // Query obtained with:
+        // scapy.raw(scapy.DNS(
+        //  tc = 1, qd = scapy.DNSQR(qtype='PTR', qname='_testservice._tcp.local'))
+        // ).hex().upper()
+        val query = HexDump.hexStringToByteArray(
+                "0000030000010000000000000C5F7465737473657276696365045F746370056C6F63616C00000C0001"
+        )
+
+        packetHandler.handlePacket(query, query.size, src)
+
+        val packetCaptor = ArgumentCaptor.forClass(MdnsPacket::class.java)
+        verify(repository).getReply(packetCaptor.capture(), eq(src))
+
+        packetCaptor.value.let {
+            assertTrue((it.flags and MdnsConstants.FLAG_TRUNCATED) != 0)
+            assertEquals(1, it.questions.size)
+            assertEquals(0, it.answers.size)
+            assertEquals(0, it.authorityRecords.size)
+            assertEquals(0, it.additionalRecords.size)
+
+            assertTrue(it.questions[0] is MdnsPointerRecord)
+            assertContentEquals(arrayOf("_testservice", "_tcp", "local"), it.questions[0].name)
+        }
+
+        verify(replySender).queueReply(testReply)
+
+        // Known-Answer packet with truncated bit set obtained with:
+        // scapy.raw(scapy.DNS(
+        //   tc = 1, qd = None, an = scapy.DNSRR(type='PTR', rrname='_testtype._tcp.local',
+        //   rdata='othertestservice._testtype._tcp.local', rclass='IN', ttl=4500))
+        // ).hex().upper()
+        val knownAnswers = HexDump.hexStringToByteArray(
+                "000003000000000100000000095F7465737474797065045F746370056C6F63616C00000C0001000" +
+                        "011940027106F746865727465737473657276696365095F7465737474797065045F7463" +
+                        "70056C6F63616C00"
+        )
+
+        packetHandler.handlePacket(knownAnswers, knownAnswers.size, src)
+
+        verify(repository, times(2)).getReply(packetCaptor.capture(), eq(src))
+
+        packetCaptor.value.let {
+            assertTrue((it.flags and MdnsConstants.FLAG_TRUNCATED) != 0)
+            assertEquals(0, it.questions.size)
+            assertEquals(1, it.answers.size)
+            assertEquals(0, it.authorityRecords.size)
+            assertEquals(0, it.additionalRecords.size)
+
+            assertTrue(it.answers[0] is MdnsPointerRecord)
+            assertContentEquals(arrayOf("_testtype", "_tcp", "local"), it.answers[0].name)
+        }
+
+        verify(replySender).queueReply(knownAnswersReply)
+
+        // Known-Answer packet obtained with:
+        // scapy.raw(scapy.DNS(
+        //   qd = None, an = scapy.DNSRR(type='PTR', rrname='_testtype._tcp.local',
+        //   rdata='testservice._testtype._tcp.local', rclass='IN', ttl=4500))
+        // ).hex().upper()
+        val knownAnswers2 = HexDump.hexStringToByteArray(
+                "000001000000000100000000095F7465737474797065045F746370056C6F63616C00000C0001000" +
+                        "0119400220B7465737473657276696365095F7465737474797065045F746370056C6F63" +
+                        "616C00"
+        )
+
+        packetHandler.handlePacket(knownAnswers2, knownAnswers2.size, src)
+
+        verify(repository, times(3)).getReply(packetCaptor.capture(), eq(src))
+
+        packetCaptor.value.let {
+            assertTrue((it.flags and MdnsConstants.FLAG_TRUNCATED) == 0)
+            assertEquals(0, it.questions.size)
+            assertEquals(1, it.answers.size)
+            assertEquals(0, it.authorityRecords.size)
+            assertEquals(0, it.additionalRecords.size)
+
+            assertTrue(it.answers[0] is MdnsPointerRecord)
+            assertContentEquals(arrayOf("_testtype", "_tcp", "local"), it.answers[0].name)
+        }
+
+        verify(replySender).queueReply(knownAnswersReply2)
+    }
+
+    @Test
     fun testConflict() {
         addServiceAndFinishProbing(TEST_SERVICE_ID_1, TEST_SERVICE_1)
-        doReturn(setOf(TEST_SERVICE_ID_1)).`when`(repository).getConflictingServices(any())
+        doReturn(mapOf(TEST_SERVICE_ID_1 to CONFLICT_SERVICE))
+                .`when`(repository).getConflictingServices(any())
 
         // Reply obtained with:
         // scapy.raw(scapy.DNS(
@@ -263,7 +376,7 @@
         }
 
         thread.waitForIdle(TIMEOUT_MS)
-        verify(cb).onServiceConflict(advertiser, TEST_SERVICE_ID_1)
+        verify(cb).onServiceConflict(advertiser, TEST_SERVICE_ID_1, CONFLICT_SERVICE)
     }
 
     @Test
@@ -290,9 +403,10 @@
     @Test
     fun testReplaceExitingService() {
         doReturn(TEST_SERVICE_ID_DUPLICATE).`when`(repository)
-                .addService(eq(TEST_SERVICE_ID_DUPLICATE), any())
-        advertiser.addService(TEST_SERVICE_ID_DUPLICATE, TEST_SERVICE_1_SUBTYPE)
-        verify(repository).addService(eq(TEST_SERVICE_ID_DUPLICATE), any())
+                .addService(eq(TEST_SERVICE_ID_DUPLICATE), any(), any())
+        advertiser.addService(TEST_SERVICE_ID_DUPLICATE, TEST_SERVICE_1_SUBTYPE,
+                MdnsAdvertisingOptions.getDefaultOptions())
+        verify(repository).addService(eq(TEST_SERVICE_ID_DUPLICATE), any(), any())
         verify(announcer).stop(TEST_SERVICE_ID_DUPLICATE)
         verify(prober).startProbing(any())
     }
@@ -300,7 +414,7 @@
     @Test
     fun testUpdateExistingService() {
         doReturn(TEST_SERVICE_ID_DUPLICATE).`when`(repository)
-                .addService(eq(TEST_SERVICE_ID_DUPLICATE), any())
+                .addService(eq(TEST_SERVICE_ID_DUPLICATE), any(), any())
         val subTypes = setOf("_sub")
         advertiser.updateService(TEST_SERVICE_ID_DUPLICATE, subTypes)
         verify(repository).updateService(eq(TEST_SERVICE_ID_DUPLICATE), any())
@@ -314,8 +428,8 @@
         doReturn(serviceId).`when`(testProbingInfo).serviceId
         doReturn(testProbingInfo).`when`(repository).setServiceProbing(serviceId)
 
-        advertiser.addService(serviceId, serviceInfo)
-        verify(repository).addService(serviceId, serviceInfo)
+        advertiser.addService(serviceId, serviceInfo, MdnsAdvertisingOptions.getDefaultOptions())
+        verify(repository).addService(serviceId, serviceInfo, null /* ttl */)
         verify(prober).startProbing(testProbingInfo)
 
         // Simulate probing success: continues to announcing
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsMultinetworkSocketClientTest.java b/tests/unit/java/com/android/server/connectivity/mdns/MdnsMultinetworkSocketClientTest.java
index ad30ce0..9474464 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsMultinetworkSocketClientTest.java
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsMultinetworkSocketClientTest.java
@@ -101,11 +101,17 @@
 
     private SocketCallback expectSocketCallback(MdnsServiceBrowserListener listener,
             Network requestedNetwork) {
+        return expectSocketCallback(listener, requestedNetwork, mSocketCreationCallback,
+                1 /* requestSocketCount */);
+    }
+
+    private SocketCallback expectSocketCallback(MdnsServiceBrowserListener listener,
+                Network requestedNetwork, SocketCreationCallback callback, int requestSocketCount) {
         final ArgumentCaptor<SocketCallback> callbackCaptor =
                 ArgumentCaptor.forClass(SocketCallback.class);
         mHandler.post(() -> mSocketClient.notifyNetworkRequested(
-                listener, requestedNetwork, mSocketCreationCallback));
-        verify(mProvider, timeout(DEFAULT_TIMEOUT))
+                listener, requestedNetwork, callback));
+        verify(mProvider, timeout(DEFAULT_TIMEOUT).times(requestSocketCount))
                 .requestSocket(eq(requestedNetwork), callbackCaptor.capture());
         return callbackCaptor.getValue();
     }
@@ -365,4 +371,40 @@
         callback.onInterfaceDestroyed(otherSocketKey, otherSocket);
         verify(mSocketCreationCallback).onSocketDestroyed(otherSocketKey);
     }
+
+    @Test
+    public void testSocketDestroyed_MultipleCallbacks() {
+        final MdnsInterfaceSocket socket2 = mock(MdnsInterfaceSocket.class);
+        final SocketKey socketKey2 = new SocketKey(1001 /* interfaceIndex */);
+        final SocketCreationCallback creationCallback1 = mock(SocketCreationCallback.class);
+        final SocketCreationCallback creationCallback2 = mock(SocketCreationCallback.class);
+        final SocketCreationCallback creationCallback3 = mock(SocketCreationCallback.class);
+        final SocketCallback callback1 = expectSocketCallback(
+                mock(MdnsServiceBrowserListener.class), mNetwork, creationCallback1,
+                1 /* requestSocketCount */);
+        final SocketCallback callback2 = expectSocketCallback(
+                mock(MdnsServiceBrowserListener.class), mNetwork, creationCallback2,
+                2 /* requestSocketCount */);
+        final SocketCallback callback3 = expectSocketCallback(
+                mock(MdnsServiceBrowserListener.class), null /* requestedNetwork */,
+                creationCallback3, 1 /* requestSocketCount */);
+
+        doReturn(createEmptyNetworkInterface()).when(mSocket).getInterface();
+        callback1.onSocketCreated(mSocketKey, mSocket, List.of());
+        callback2.onSocketCreated(mSocketKey, mSocket, List.of());
+        callback3.onSocketCreated(mSocketKey, mSocket, List.of());
+        callback3.onSocketCreated(socketKey2, socket2, List.of());
+        verify(creationCallback1).onSocketCreated(mSocketKey);
+        verify(creationCallback2).onSocketCreated(mSocketKey);
+        verify(creationCallback3).onSocketCreated(mSocketKey);
+        verify(creationCallback3).onSocketCreated(socketKey2);
+
+        callback1.onInterfaceDestroyed(mSocketKey, mSocket);
+        callback2.onInterfaceDestroyed(mSocketKey, mSocket);
+        callback3.onInterfaceDestroyed(mSocketKey, mSocket);
+        verify(creationCallback1).onSocketDestroyed(mSocketKey);
+        verify(creationCallback2).onSocketDestroyed(mSocketKey);
+        verify(creationCallback3).onSocketDestroyed(mSocketKey);
+        verify(creationCallback3, never()).onSocketDestroyed(socketKey2);
+    }
 }
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsPacketReaderTests.java b/tests/unit/java/com/android/server/connectivity/mdns/MdnsPacketReaderTests.java
index 37588b5..0168b61 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsPacketReaderTests.java
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsPacketReaderTests.java
@@ -19,8 +19,10 @@
 import static com.android.testutils.DevSdkIgnoreRuleKt.SC_V2;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThrows;
 import static org.junit.Assert.fail;
 
+import com.android.net.module.util.HexDump;
 import com.android.testutils.DevSdkIgnoreRule;
 import com.android.testutils.DevSdkIgnoreRunner;
 
@@ -83,4 +85,17 @@
         }
         assertEquals(data.length, packetReader.getRemaining());
     }
+
+    @Test
+    public void testInfinitePtrLoop() {
+        // Fake mdns response packet label portion which has infinite ptr loop.
+        final byte[] infinitePtrLoopData = HexDump.hexStringToByteArray(
+                "054C4142454C" // label "LABEL"
+                        + "0454455354" // label "TEST"
+                        + "C006"); // PTR to second label.
+        MdnsPacketReader packetReader = new MdnsPacketReader(
+                infinitePtrLoopData, infinitePtrLoopData.length,
+                MdnsFeatureFlags.newBuilder().setIsLabelCountLimitEnabled(true).build());
+        assertThrows(IOException.class, packetReader::readLabels);
+    }
 }
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsPacketTest.kt b/tests/unit/java/com/android/server/connectivity/mdns/MdnsPacketTest.kt
index 0877b68..fc4796b 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsPacketTest.kt
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsPacketTest.kt
@@ -21,6 +21,7 @@
 import com.android.testutils.DevSdkIgnoreRunner
 import kotlin.test.assertContentEquals
 import kotlin.test.assertEquals
+import kotlin.test.assertFailsWith
 import kotlin.test.assertTrue
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -71,4 +72,17 @@
         assertEquals(InetAddresses.parseNumericAddress("2001:db8::789"),
                 (packet.authorityRecords[3] as MdnsInetAddressRecord).inet6Address!!)
     }
+
+    @Test
+    fun testParseQueryWithLabelLoop_ThrowsParseException() {
+        val packetWithErrorHex = "000084000000000100000000054C4142454C0454455354C006000C800100000" +
+                "07800140454455354056C6F63616C00"
+
+        val bytes = HexDump.hexStringToByteArray(packetWithErrorHex)
+        val reader = MdnsPacketReader(
+                bytes, bytes.size, makeFlags(isLabelCountLimitEnabled = true))
+        assertFailsWith<MdnsPacket.ParseException> {
+            MdnsPacket.parse(reader)
+        }
+    }
 }
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsProberTest.kt b/tests/unit/java/com/android/server/connectivity/mdns/MdnsProberTest.kt
index 5b7c0ba..9befbc1 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsProberTest.kt
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsProberTest.kt
@@ -61,6 +61,7 @@
     private val cb = mock(MdnsPacketRepeater.PacketRepeaterCallback::class.java)
         as MdnsPacketRepeater.PacketRepeaterCallback<ProbingInfo>
     private val buffer = ByteArray(1500)
+    private val flags = MdnsFeatureFlags.newBuilder().build()
 
     @Before
     fun setUp() {
@@ -120,7 +121,7 @@
     @Test
     fun testProbe() {
         val replySender = MdnsReplySender(
-                thread.looper, socket, buffer, sharedLog, true /* enableDebugLog */)
+                thread.looper, socket, buffer, sharedLog, true /* enableDebugLog */, flags)
         val prober = TestProber(thread.looper, replySender, cb, sharedLog)
         val probeInfo = TestProbeInfo(
                 listOf(makeServiceRecord(TEST_SERVICE_NAME_1, 37890)))
@@ -145,7 +146,7 @@
     @Test
     fun testProbeMultipleRecords() {
         val replySender = MdnsReplySender(
-                thread.looper, socket, buffer, sharedLog, true /* enableDebugLog */)
+                thread.looper, socket, buffer, sharedLog, true /* enableDebugLog */, flags)
         val prober = TestProber(thread.looper, replySender, cb, sharedLog)
         val probeInfo = TestProbeInfo(listOf(
                 makeServiceRecord(TEST_SERVICE_NAME_1, 37890),
@@ -184,7 +185,7 @@
     @Test
     fun testStopProbing() {
         val replySender = MdnsReplySender(
-                thread.looper, socket, buffer, sharedLog, true /* enableDebugLog */)
+                thread.looper, socket, buffer, sharedLog, true /* enableDebugLog */, flags)
         val prober = TestProber(thread.looper, replySender, cb, sharedLog)
         val probeInfo = TestProbeInfo(
                 listOf(makeServiceRecord(TEST_SERVICE_NAME_1, 37890)),
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsRecordRepositoryTest.kt b/tests/unit/java/com/android/server/connectivity/mdns/MdnsRecordRepositoryTest.kt
index 1edc806..8d1dff6 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsRecordRepositoryTest.kt
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsRecordRepositoryTest.kt
@@ -22,6 +22,8 @@
 import android.os.Build
 import android.os.HandlerThread
 import com.android.server.connectivity.mdns.MdnsAnnouncer.AnnouncementInfo
+import com.android.server.connectivity.mdns.MdnsInterfaceAdvertiser.CONFLICT_HOST
+import com.android.server.connectivity.mdns.MdnsInterfaceAdvertiser.CONFLICT_SERVICE
 import com.android.server.connectivity.mdns.MdnsRecord.TYPE_A
 import com.android.server.connectivity.mdns.MdnsRecord.TYPE_AAAA
 import com.android.server.connectivity.mdns.MdnsRecord.TYPE_PTR
@@ -52,6 +54,9 @@
 private const val TEST_SERVICE_ID_1 = 42
 private const val TEST_SERVICE_ID_2 = 43
 private const val TEST_SERVICE_ID_3 = 44
+private const val TEST_CUSTOM_HOST_ID_1 = 45
+private const val TEST_CUSTOM_HOST_ID_2 = 46
+private const val TEST_SERVICE_CUSTOM_HOST_ID_1 = 48
 private const val TEST_PORT = 12345
 private const val TEST_SUBTYPE = "_subtype"
 private const val TEST_SUBTYPE2 = "_subtype2"
@@ -86,6 +91,26 @@
     port = TEST_PORT
 }
 
+private val TEST_CUSTOM_HOST_1 = NsdServiceInfo().apply {
+    hostname = "TestHost"
+    hostAddresses = listOf(parseNumericAddress("2001:db8::1"), parseNumericAddress("2001:db8::2"))
+}
+
+private val TEST_CUSTOM_HOST_1_NAME = arrayOf("TestHost", "local")
+
+private val TEST_CUSTOM_HOST_2 = NsdServiceInfo().apply {
+    hostname = "OtherTestHost"
+    hostAddresses = listOf(parseNumericAddress("2001:db8::3"), parseNumericAddress("2001:db8::4"))
+}
+
+private val TEST_SERVICE_CUSTOM_HOST_1 = NsdServiceInfo().apply {
+    hostname = "TestHost"
+    hostAddresses = listOf(parseNumericAddress("2001:db8::1"))
+    serviceType = "_testservice._tcp"
+    serviceName = "TestService"
+    port = TEST_PORT
+}
+
 @RunWith(DevSdkIgnoreRunner::class)
 @DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.S_V2)
 class MdnsRecordRepositoryTest {
@@ -120,7 +145,8 @@
     fun testAddServiceAndProbe() {
         val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, makeFlags())
         assertEquals(0, repository.servicesCount)
-        assertEquals(-1, repository.addService(TEST_SERVICE_ID_1, TEST_SERVICE_1))
+        assertEquals(-1,
+                repository.addService(TEST_SERVICE_ID_1, TEST_SERVICE_1, null /* ttl */))
         assertEquals(1, repository.servicesCount)
 
         val probingInfo = repository.setServiceProbing(TEST_SERVICE_ID_1)
@@ -155,10 +181,10 @@
         val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, makeFlags())
         repository.initWithService(TEST_SERVICE_ID_1, TEST_SERVICE_1)
         assertFailsWith(NameConflictException::class) {
-            repository.addService(TEST_SERVICE_ID_2, TEST_SERVICE_1)
+            repository.addService(TEST_SERVICE_ID_2, TEST_SERVICE_1, null /* ttl */)
         }
         assertFailsWith(NameConflictException::class) {
-            repository.addService(TEST_SERVICE_ID_3, TEST_SERVICE_3)
+            repository.addService(TEST_SERVICE_ID_3, TEST_SERVICE_3, null /* ttl */)
         }
     }
 
@@ -175,8 +201,8 @@
 
         val queriedName = arrayOf(TEST_SUBTYPE, "_sub", "_testservice", "_tcp", "local")
         val questions = listOf(MdnsPointerRecord(queriedName, false /* isUnicast */))
-        val query = MdnsPacket(0 /* flags */, questions, listOf() /* answers */,
-                listOf() /* authorityRecords */, listOf() /* additionalRecords */)
+        val query = MdnsPacket(0 /* flags */, questions, emptyList() /* answers */,
+                emptyList() /* authorityRecords */, emptyList() /* additionalRecords */)
         val src = InetSocketAddress(parseNumericAddress("192.0.2.123"), 5353)
         val reply = repository.getReply(query, src)
 
@@ -199,9 +225,9 @@
     @Test
     fun testInvalidReuseOfServiceId() {
         val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, makeFlags())
-        repository.addService(TEST_SERVICE_ID_1, TEST_SERVICE_1)
+        repository.addService(TEST_SERVICE_ID_1, TEST_SERVICE_1, null /* ttl */)
         assertFailsWith(IllegalArgumentException::class) {
-            repository.addService(TEST_SERVICE_ID_1, TEST_SERVICE_2)
+            repository.addService(TEST_SERVICE_ID_1, TEST_SERVICE_2, null /* ttl */)
         }
     }
 
@@ -210,7 +236,7 @@
         val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, makeFlags())
         assertFalse(repository.hasActiveService(TEST_SERVICE_ID_1))
 
-        repository.addService(TEST_SERVICE_ID_1, TEST_SERVICE_1)
+        repository.addService(TEST_SERVICE_ID_1, TEST_SERVICE_1, null /* ttl */)
         assertTrue(repository.hasActiveService(TEST_SERVICE_ID_1))
 
         val probingInfo = repository.setServiceProbing(TEST_SERVICE_ID_1)
@@ -302,7 +328,7 @@
         repository.exitService(TEST_SERVICE_ID_1)
 
         assertEquals(TEST_SERVICE_ID_1,
-                repository.addService(TEST_SERVICE_ID_2, TEST_SERVICE_1))
+                repository.addService(TEST_SERVICE_ID_2, TEST_SERVICE_1, null /* ttl */))
         assertEquals(1, repository.servicesCount)
 
         repository.removeService(TEST_SERVICE_ID_2)
@@ -510,8 +536,8 @@
         val questionsCaseInSensitive = listOf(
                 MdnsPointerRecord(arrayOf("_TESTSERVICE", "_TCP", "local"), false /* isUnicast */))
         val queryCaseInsensitive = MdnsPacket(0 /* flags */, questionsCaseInSensitive,
-            listOf() /* answers */, listOf() /* authorityRecords */,
-            listOf() /* additionalRecords */)
+            emptyList() /* answers */, emptyList() /* authorityRecords */,
+            emptyList() /* additionalRecords */)
         val src = InetSocketAddress(parseNumericAddress("192.0.2.123"), 5353)
         val replyCaseInsensitive = repository.getReply(queryCaseInsensitive, src)
         assertNotNull(replyCaseInsensitive)
@@ -524,8 +550,8 @@
      */
     private fun makeQuery(vararg queries: Pair<Int, Array<String>>): MdnsPacket {
         val questions = queries.map { (type, name) -> makeQuestionRecord(name, type) }
-        return MdnsPacket(0 /* flags */, questions, listOf() /* answers */,
-            listOf() /* authorityRecords */, listOf() /* additionalRecords */)
+        return MdnsPacket(0 /* flags */, questions, emptyList() /* answers */,
+                emptyList() /* authorityRecords */, emptyList() /* additionalRecords */)
     }
 
     private fun makeQuestionRecord(name: Array<String>, type: Int): MdnsRecord {
@@ -554,7 +580,7 @@
                     arrayOf("_testservice", "_tcp", "local"), 0L, false, LONG_TTL, serviceName)),
             reply.answers)
         assertEquals(listOf(
-                MdnsTextRecord(serviceName, 0L, true, LONG_TTL, listOf()),
+                MdnsTextRecord(serviceName, 0L, true, LONG_TTL, emptyList()),
                 MdnsServiceRecord(serviceName, 0L, true, SHORT_TTL, 0, 0, TEST_PORT, TEST_HOSTNAME),
                 MdnsInetAddressRecord(
                     TEST_HOSTNAME, 0L, true, SHORT_TTL, TEST_ADDRESSES[0].address),
@@ -569,6 +595,92 @@
             ), reply.additionalAnswers)
     }
 
+
+    @Test
+    fun testGetReply_ptrQuestionForServiceWithCustomHost_customHostUsedInAdditionalAnswers() {
+        val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, makeFlags())
+        repository.initWithService(TEST_SERVICE_CUSTOM_HOST_ID_1, TEST_SERVICE_CUSTOM_HOST_1,
+                setOf(TEST_SUBTYPE, TEST_SUBTYPE2))
+        val src = InetSocketAddress(parseNumericAddress("fe80::1234"), 5353)
+        val serviceName = arrayOf("TestService", "_testservice", "_tcp", "local")
+
+        val query = makeQuery(TYPE_PTR to arrayOf("_testservice", "_tcp", "local"))
+        val reply = repository.getReply(query, src)
+
+        assertNotNull(reply)
+        assertEquals(listOf(
+                MdnsPointerRecord(
+                        arrayOf("_testservice", "_tcp", "local"),
+                        0L, false, LONG_TTL, serviceName)),
+                reply.answers)
+        assertEquals(listOf(
+                MdnsTextRecord(serviceName, 0L, true, LONG_TTL, listOf()),
+                MdnsServiceRecord(serviceName, 0L, true, SHORT_TTL,
+                        0, 0, TEST_PORT, TEST_CUSTOM_HOST_1_NAME),
+                MdnsInetAddressRecord(
+                        TEST_CUSTOM_HOST_1_NAME, 0L, true, SHORT_TTL,
+                        parseNumericAddress("2001:db8::1")),
+                MdnsNsecRecord(serviceName, 0L, true, LONG_TTL, serviceName /* nextDomain */,
+                        intArrayOf(TYPE_TXT, TYPE_SRV)),
+                MdnsNsecRecord(TEST_CUSTOM_HOST_1_NAME, 0L, true, SHORT_TTL,
+                        TEST_CUSTOM_HOST_1_NAME /* nextDomain */,
+                        intArrayOf(TYPE_AAAA)),
+        ), reply.additionalAnswers)
+    }
+
+    @Test
+    fun testGetReply_ptrQuestionForServicesWithSameCustomHost_customHostUsedInAdditionalAnswers() {
+        val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, makeFlags())
+        val serviceWithCustomHost1 = NsdServiceInfo().apply {
+            hostname = "TestHost"
+            hostAddresses = listOf(
+                    parseNumericAddress("2001:db8::1"),
+                    parseNumericAddress("192.0.2.1"))
+            serviceType = "_testservice._tcp"
+            serviceName = "TestService1"
+            port = TEST_PORT
+        }
+        val serviceWithCustomHost2 = NsdServiceInfo().apply {
+            hostname = "TestHost"
+            hostAddresses = listOf(
+                    parseNumericAddress("2001:db8::1"),
+                    parseNumericAddress("2001:db8::3"))
+        }
+        repository.addServiceAndFinishProbing(TEST_SERVICE_ID_1, serviceWithCustomHost1)
+        repository.addServiceAndFinishProbing(TEST_SERVICE_ID_2, serviceWithCustomHost2)
+        val src = InetSocketAddress(parseNumericAddress("fe80::1234"), 5353)
+        val serviceName = arrayOf("TestService1", "_testservice", "_tcp", "local")
+
+        val query = makeQuery(TYPE_PTR to arrayOf("_testservice", "_tcp", "local"))
+        val reply = repository.getReply(query, src)
+
+        assertNotNull(reply)
+        assertEquals(listOf(
+                MdnsPointerRecord(
+                        arrayOf("_testservice", "_tcp", "local"),
+                        0L, false, LONG_TTL, serviceName)),
+                reply.answers)
+        assertEquals(listOf(
+                MdnsTextRecord(serviceName, 0L, true, LONG_TTL, listOf()),
+                MdnsServiceRecord(serviceName, 0L, true, SHORT_TTL,
+                        0, 0, TEST_PORT, TEST_CUSTOM_HOST_1_NAME),
+                MdnsInetAddressRecord(
+                        TEST_CUSTOM_HOST_1_NAME, 0L, true, SHORT_TTL,
+                        parseNumericAddress("2001:db8::1")),
+                MdnsInetAddressRecord(
+                        TEST_CUSTOM_HOST_1_NAME, 0L, true, SHORT_TTL,
+                        parseNumericAddress("192.0.2.1")),
+                MdnsInetAddressRecord(
+                        TEST_CUSTOM_HOST_1_NAME, 0L, true, SHORT_TTL,
+                        parseNumericAddress("2001:db8::3")),
+                MdnsNsecRecord(serviceName, 0L, true, LONG_TTL, serviceName /* nextDomain */,
+                        intArrayOf(TYPE_TXT, TYPE_SRV)),
+                MdnsNsecRecord(TEST_CUSTOM_HOST_1_NAME, 0L, true, SHORT_TTL,
+                        TEST_CUSTOM_HOST_1_NAME /* nextDomain */,
+                        intArrayOf(TYPE_A, TYPE_AAAA)),
+        ), reply.additionalAnswers)
+    }
+
     @Test
     fun testGetReply_singleSubtypePtrQuestion_returnsSrvTxtAddressNsecRecords() {
         val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, makeFlags())
@@ -587,7 +699,7 @@
                     LONG_TTL, serviceName)),
             reply.answers)
         assertEquals(listOf(
-                MdnsTextRecord(serviceName, 0L, true, LONG_TTL, listOf()),
+                MdnsTextRecord(serviceName, 0L, true, LONG_TTL, emptyList()),
                 MdnsServiceRecord(serviceName, 0L, true, SHORT_TTL, 0, 0, TEST_PORT, TEST_HOSTNAME),
                 MdnsInetAddressRecord(
                     TEST_HOSTNAME, 0L, true, SHORT_TTL, TEST_ADDRESSES[0].address),
@@ -620,7 +732,7 @@
                     arrayOf("_testservice", "_tcp", "local"), 0L, false, LONG_TTL, serviceName)),
             reply.answers)
         assertEquals(listOf(
-                MdnsTextRecord(serviceName, 0L, true, LONG_TTL, listOf()),
+                MdnsTextRecord(serviceName, 0L, true, LONG_TTL, emptyList()),
                 MdnsServiceRecord(serviceName, 0L, true, SHORT_TTL, 0, 0, TEST_PORT, TEST_HOSTNAME),
                 MdnsInetAddressRecord(
                     TEST_HOSTNAME, 0L, true, SHORT_TTL, TEST_ADDRESSES[0].address),
@@ -656,7 +768,7 @@
                     0L, false, LONG_TTL, serviceName)),
             reply.answers)
         assertEquals(listOf(
-                MdnsTextRecord(serviceName, 0L, true, LONG_TTL, listOf()),
+                MdnsTextRecord(serviceName, 0L, true, LONG_TTL, emptyList()),
                 MdnsServiceRecord(serviceName, 0L, true, SHORT_TTL, 0, 0, TEST_PORT, TEST_HOSTNAME),
                 MdnsInetAddressRecord(
                     TEST_HOSTNAME, 0L, true, SHORT_TTL, TEST_ADDRESSES[0].address),
@@ -682,7 +794,7 @@
         val reply = repository.getReply(query, src)
 
         assertNotNull(reply)
-        assertEquals(listOf(MdnsTextRecord(serviceName, 0L, true, LONG_TTL, listOf())),
+        assertEquals(listOf(MdnsTextRecord(serviceName, 0L, true, LONG_TTL, emptyList())),
                 reply.answers)
         // No NSEC records because the reply doesn't include the SRV record
         assertTrue(reply.additionalAnswers.isEmpty())
@@ -708,6 +820,91 @@
     }
 
     @Test
+    fun testGetReply_AAAAQuestionForCustomHost_returnsAAAARecords() {
+        val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, makeFlags())
+        repository.initWithService(
+                TEST_CUSTOM_HOST_ID_1, TEST_CUSTOM_HOST_1, subtypes = setOf(),
+                listOf(LinkAddress(parseNumericAddress("192.0.2.111"), 24)))
+        repository.addService(TEST_CUSTOM_HOST_ID_2, TEST_CUSTOM_HOST_2, null /* ttl */)
+        val src = InetSocketAddress(parseNumericAddress("fe80::123"), 5353)
+
+        val query = makeQuery(TYPE_AAAA to TEST_CUSTOM_HOST_1_NAME)
+        val reply = repository.getReply(query, src)
+
+        assertNotNull(reply)
+        assertEquals(listOf(
+                MdnsInetAddressRecord(TEST_CUSTOM_HOST_1_NAME,
+                        0, false, LONG_TTL, parseNumericAddress("2001:db8::1")),
+                MdnsInetAddressRecord(TEST_CUSTOM_HOST_1_NAME,
+                        0, false, LONG_TTL, parseNumericAddress("2001:db8::2"))),
+                reply.answers)
+        assertEquals(
+                listOf(MdnsNsecRecord(TEST_CUSTOM_HOST_1_NAME,
+                        0L, true, SHORT_TTL,
+                        TEST_CUSTOM_HOST_1_NAME /* nextDomain */,
+                        intArrayOf(TYPE_AAAA))),
+                reply.additionalAnswers)
+    }
+
+
+    @Test
+    fun testGetReply_AAAAQuestionForCustomHostInMultipleRegistrations_returnsAAAARecords() {
+        val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, makeFlags())
+
+        repository.addServiceAndFinishProbing(TEST_CUSTOM_HOST_ID_1, NsdServiceInfo().apply {
+            hostname = "TestHost"
+            hostAddresses = listOf(
+                    parseNumericAddress("2001:db8::1"),
+                    parseNumericAddress("2001:db8::2"))
+        })
+        repository.addServiceAndFinishProbing(TEST_CUSTOM_HOST_ID_2, NsdServiceInfo().apply {
+            hostname = "TestHost"
+            hostAddresses = listOf(
+                    parseNumericAddress("2001:db8::1"),
+                    parseNumericAddress("2001:db8::3"))
+        })
+        val src = InetSocketAddress(parseNumericAddress("fe80::123"), 5353)
+
+        val query = makeQuery(TYPE_AAAA to TEST_CUSTOM_HOST_1_NAME)
+        val reply = repository.getReply(query, src)
+
+        assertNotNull(reply)
+        assertEquals(listOf(
+                MdnsInetAddressRecord(TEST_CUSTOM_HOST_1_NAME,
+                        0, false, LONG_TTL, parseNumericAddress("2001:db8::1")),
+                MdnsInetAddressRecord(TEST_CUSTOM_HOST_1_NAME,
+                        0, false, LONG_TTL, parseNumericAddress("2001:db8::2")),
+                MdnsInetAddressRecord(TEST_CUSTOM_HOST_1_NAME,
+                        0, false, LONG_TTL, parseNumericAddress("2001:db8::3"))),
+                reply.answers)
+        assertEquals(
+                listOf(MdnsNsecRecord(TEST_CUSTOM_HOST_1_NAME,
+                        0L, true, SHORT_TTL,
+                        TEST_CUSTOM_HOST_1_NAME /* nextDomain */,
+                        intArrayOf(TYPE_AAAA))),
+                reply.additionalAnswers)
+    }
+
+    @Test
+    fun testGetReply_customHostRemoved_noAnswerToAAAAQuestion() {
+        val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, makeFlags())
+        repository.initWithService(
+                TEST_CUSTOM_HOST_ID_1, TEST_CUSTOM_HOST_1, subtypes = setOf(),
+                listOf(LinkAddress(parseNumericAddress("192.0.2.111"), 24)))
+        repository.addService(
+                TEST_SERVICE_CUSTOM_HOST_ID_1, TEST_SERVICE_CUSTOM_HOST_1, null /* ttl */)
+        repository.removeService(TEST_CUSTOM_HOST_ID_1)
+        repository.removeService(TEST_SERVICE_CUSTOM_HOST_ID_1)
+
+        val src = InetSocketAddress(parseNumericAddress("fe80::123"), 5353)
+
+        val query = makeQuery(TYPE_AAAA to TEST_CUSTOM_HOST_1_NAME)
+        val reply = repository.getReply(query, src)
+
+        assertNull(reply)
+    }
+
+    @Test
     fun testGetReply_ptrAndSrvQuestions_doesNotReturnSrvRecordInAdditionalAnswerSection() {
         val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, makeFlags())
         repository.initWithService(TEST_SERVICE_ID_1, TEST_SERVICE_1, setOf(TEST_SUBTYPE))
@@ -747,7 +944,7 @@
         assertNotNull(reply)
         assertEquals(listOf(
                 MdnsServiceRecord(serviceName, 0L, true, SHORT_TTL, 0, 0, TEST_PORT, TEST_HOSTNAME),
-                MdnsTextRecord(serviceName, 0L, true, LONG_TTL, listOf()),
+                MdnsTextRecord(serviceName, 0L, true, LONG_TTL, emptyList()),
                 MdnsInetAddressRecord(
                         TEST_HOSTNAME, 0L, true, SHORT_TTL, TEST_ADDRESSES[0].address),
                 MdnsInetAddressRecord(
@@ -794,8 +991,8 @@
     @Test
     fun testGetConflictingServices() {
         val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, makeFlags())
-        repository.addService(TEST_SERVICE_ID_1, TEST_SERVICE_1)
-        repository.addService(TEST_SERVICE_ID_2, TEST_SERVICE_2)
+        repository.addService(TEST_SERVICE_ID_1, TEST_SERVICE_1, null /* ttl */)
+        repository.addService(TEST_SERVICE_ID_2, TEST_SERVICE_2, null /* ttl */)
 
         val packet = MdnsPacket(
                 0 /* flags */,
@@ -815,15 +1012,18 @@
                 emptyList() /* authorityRecords */,
                 emptyList() /* additionalRecords */)
 
-        assertEquals(setOf(TEST_SERVICE_ID_1, TEST_SERVICE_ID_2),
+        assertEquals(
+                mapOf(
+                        TEST_SERVICE_ID_1 to CONFLICT_SERVICE,
+                        TEST_SERVICE_ID_2 to CONFLICT_SERVICE),
                 repository.getConflictingServices(packet))
     }
 
     @Test
     fun testGetConflictingServicesCaseInsensitive() {
         val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, makeFlags())
-        repository.addService(TEST_SERVICE_ID_1, TEST_SERVICE_1)
-        repository.addService(TEST_SERVICE_ID_2, TEST_SERVICE_2)
+        repository.addService(TEST_SERVICE_ID_1, TEST_SERVICE_1, null /* ttl */)
+        repository.addService(TEST_SERVICE_ID_2, TEST_SERVICE_2, null /* ttl */)
 
         val packet = MdnsPacket(
             0 /* flags */,
@@ -843,15 +1043,138 @@
             emptyList() /* authorityRecords */,
             emptyList() /* additionalRecords */)
 
-        assertEquals(setOf(TEST_SERVICE_ID_1, TEST_SERVICE_ID_2),
-            repository.getConflictingServices(packet))
+        assertEquals(
+                mapOf(TEST_SERVICE_ID_1 to CONFLICT_SERVICE,
+                        TEST_SERVICE_ID_2 to CONFLICT_SERVICE),
+                repository.getConflictingServices(packet))
+    }
+
+    @Test
+    fun testGetConflictingServices_customHosts_differentAddresses() {
+        val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, makeFlags())
+        repository.addService(TEST_CUSTOM_HOST_ID_1, TEST_CUSTOM_HOST_1, null /* ttl */)
+        repository.addService(TEST_CUSTOM_HOST_ID_2, TEST_CUSTOM_HOST_2, null /* ttl */)
+
+        val packet = MdnsPacket(
+                0, /* flags */
+                emptyList(), /* questions */
+                listOf(
+                        MdnsInetAddressRecord(arrayOf("TestHost", "local"),
+                                0L /* receiptTimeMillis */, true /* cacheFlush */,
+                                0L /* ttlMillis */, parseNumericAddress("2001:db8::5")),
+                        MdnsInetAddressRecord(arrayOf("TestHost", "local"),
+                                0L /* receiptTimeMillis */, true /* cacheFlush */,
+                                0L /* ttlMillis */, parseNumericAddress("2001:db8::6")),
+                ) /* answers */,
+                emptyList() /* authorityRecords */,
+                emptyList() /* additionalRecords */)
+
+        assertEquals(mapOf(TEST_CUSTOM_HOST_ID_1 to CONFLICT_HOST),
+                repository.getConflictingServices(packet))
+    }
+
+    @Test
+    fun testGetConflictingServices_customHosts_moreAddressesThanUs_conflict() {
+        val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, makeFlags())
+        repository.addService(TEST_CUSTOM_HOST_ID_1, TEST_CUSTOM_HOST_1, null /* ttl */)
+        repository.addService(TEST_CUSTOM_HOST_ID_2, TEST_CUSTOM_HOST_2, null /* ttl */)
+
+        val packet = MdnsPacket(
+                0, /* flags */
+                emptyList(), /* questions */
+                listOf(
+                        MdnsInetAddressRecord(arrayOf("TestHost", "local"),
+                                0L /* receiptTimeMillis */, true /* cacheFlush */,
+                                0L /* ttlMillis */, parseNumericAddress("2001:db8::1")),
+                        MdnsInetAddressRecord(arrayOf("TestHost", "local"),
+                                0L /* receiptTimeMillis */, true /* cacheFlush */,
+                                0L /* ttlMillis */, parseNumericAddress("2001:db8::2")),
+                        MdnsInetAddressRecord(arrayOf("TestHost", "local"),
+                                0L /* receiptTimeMillis */, true /* cacheFlush */,
+                                0L /* ttlMillis */, parseNumericAddress("2001:db8::3")),
+                ) /* answers */,
+                emptyList() /* authorityRecords */,
+                emptyList() /* additionalRecords */)
+
+        assertEquals(mapOf(TEST_CUSTOM_HOST_ID_1 to CONFLICT_HOST),
+                repository.getConflictingServices(packet))
+    }
+
+    @Test
+    fun testGetConflictingServices_customHostsReplyHasFewerAddressesThanUs_noConflict() {
+        val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, makeFlags())
+        repository.addService(TEST_CUSTOM_HOST_ID_1, TEST_CUSTOM_HOST_1, null /* ttl */)
+        repository.addService(TEST_CUSTOM_HOST_ID_2, TEST_CUSTOM_HOST_2, null /* ttl */)
+
+        val packet = MdnsPacket(
+                0, /* flags */
+                emptyList(), /* questions */
+                listOf(
+                        MdnsInetAddressRecord(arrayOf("TestHost", "local"),
+                                0L /* receiptTimeMillis */, true /* cacheFlush */,
+                                0L /* ttlMillis */, parseNumericAddress("2001:db8::2")),
+                ) /* answers */,
+                emptyList() /* authorityRecords */,
+                emptyList() /* additionalRecords */)
+
+        assertEquals(emptyMap(),
+                repository.getConflictingServices(packet))
+    }
+
+    @Test
+    fun testGetConflictingServices_customHostsReplyHasIdenticalHosts_noConflict() {
+        val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, makeFlags())
+        repository.addService(TEST_CUSTOM_HOST_ID_1, TEST_CUSTOM_HOST_1, null /* ttl */)
+        repository.addService(TEST_CUSTOM_HOST_ID_2, TEST_CUSTOM_HOST_2, null /* ttl */)
+
+        val packet = MdnsPacket(
+                0, /* flags */
+                emptyList(), /* questions */
+                listOf(
+                        MdnsInetAddressRecord(arrayOf("TestHost", "local"),
+                                0L /* receiptTimeMillis */, true /* cacheFlush */,
+                                0L /* ttlMillis */, parseNumericAddress("2001:db8::1")),
+                        MdnsInetAddressRecord(arrayOf("TestHost", "local"),
+                                0L /* receiptTimeMillis */, true /* cacheFlush */,
+                                0L /* ttlMillis */, parseNumericAddress("2001:db8::2")),
+                ) /* answers */,
+                emptyList() /* authorityRecords */,
+                emptyList() /* additionalRecords */)
+
+        assertEquals(emptyMap(),
+                repository.getConflictingServices(packet))
+    }
+
+
+    @Test
+    fun testGetConflictingServices_customHostsCaseInsensitiveReplyHasIdenticalHosts_noConflict() {
+        val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, makeFlags())
+        repository.addService(TEST_CUSTOM_HOST_ID_1, TEST_CUSTOM_HOST_1, null /* ttl */)
+        repository.addService(TEST_CUSTOM_HOST_ID_2, TEST_CUSTOM_HOST_2, null /* ttl */)
+
+        val packet = MdnsPacket(
+                0, /* flags */
+                emptyList(), /* questions */
+                listOf(
+                        MdnsInetAddressRecord(arrayOf("TESTHOST", "local"),
+                                0L /* receiptTimeMillis */, true /* cacheFlush */,
+                                0L /* ttlMillis */, parseNumericAddress("2001:db8::1")),
+                        MdnsInetAddressRecord(arrayOf("testhost", "local"),
+                                0L /* receiptTimeMillis */, true /* cacheFlush */,
+                                0L /* ttlMillis */, parseNumericAddress("2001:db8::2")),
+                ) /* answers */,
+                emptyList() /* authorityRecords */,
+                emptyList() /* additionalRecords */)
+
+        assertEquals(emptyMap(),
+                repository.getConflictingServices(packet))
     }
 
     @Test
     fun testGetConflictingServices_IdenticalService() {
         val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, makeFlags())
-        repository.addService(TEST_SERVICE_ID_1, TEST_SERVICE_1)
-        repository.addService(TEST_SERVICE_ID_2, TEST_SERVICE_2)
+        repository.addService(TEST_SERVICE_ID_1, TEST_SERVICE_1, null /* ttl */)
+        repository.addService(TEST_SERVICE_ID_2, TEST_SERVICE_2, null /* ttl */)
 
         val otherTtlMillis = 1234L
         val packet = MdnsPacket(
@@ -873,14 +1196,14 @@
                 emptyList() /* additionalRecords */)
 
         // Above records are identical to the actual registrations: no conflict
-        assertEquals(emptySet(), repository.getConflictingServices(packet))
+        assertEquals(emptyMap(), repository.getConflictingServices(packet))
     }
 
     @Test
     fun testGetConflictingServicesCaseInsensitive_IdenticalService() {
         val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, makeFlags())
-        repository.addService(TEST_SERVICE_ID_1, TEST_SERVICE_1)
-        repository.addService(TEST_SERVICE_ID_2, TEST_SERVICE_2)
+        repository.addService(TEST_SERVICE_ID_1, TEST_SERVICE_1, null /* ttl */)
+        repository.addService(TEST_SERVICE_ID_2, TEST_SERVICE_2, null /* ttl */)
 
         val otherTtlMillis = 1234L
         val packet = MdnsPacket(
@@ -902,7 +1225,7 @@
                 emptyList() /* additionalRecords */)
 
         // Above records are identical to the actual registrations: no conflict
-        assertEquals(emptySet(), repository.getConflictingServices(packet))
+        assertEquals(emptyMap(), repository.getConflictingServices(packet))
     }
 
     @Test
@@ -915,8 +1238,8 @@
 
         val questions = listOf(
                 MdnsPointerRecord(arrayOf("_testservice", "_tcp", "local"), false /* isUnicast */))
-        val query = MdnsPacket(0 /* flags */, questions, listOf() /* answers */,
-                listOf() /* authorityRecords */, listOf() /* additionalRecords */)
+        val query = MdnsPacket(0 /* flags */, questions, emptyList() /* answers */,
+                emptyList() /* authorityRecords */, emptyList() /* additionalRecords */)
         val src = InetSocketAddress(parseNumericAddress("192.0.2.123"), 5353)
 
         // Reply to the question and verify there is one packet replied.
@@ -935,7 +1258,8 @@
             makeFlags(includeInetAddressesInProbing = true))
         repository.updateAddresses(TEST_ADDRESSES)
         assertEquals(0, repository.servicesCount)
-        assertEquals(-1, repository.addService(TEST_SERVICE_ID_1, TEST_SERVICE_1))
+        assertEquals(-1,
+                repository.addService(TEST_SERVICE_ID_1, TEST_SERVICE_1, null /* ttl */))
         assertEquals(1, repository.servicesCount)
 
         val probingInfo = repository.setServiceProbing(TEST_SERVICE_ID_1)
@@ -994,18 +1318,17 @@
             questions: List<MdnsRecord>,
             knownAnswers: List<MdnsRecord>,
             replyAnswers: List<MdnsRecord>,
-            additionalAnswers: List<MdnsRecord>,
-            expectReply: Boolean
+            additionalAnswers: List<MdnsRecord>
     ) {
         val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME,
             makeFlags(isKnownAnswerSuppressionEnabled = true))
         repository.initWithService(TEST_SERVICE_ID_1, TEST_SERVICE_1)
         val query = MdnsPacket(0 /* flags */, questions, knownAnswers,
-                listOf() /* authorityRecords */, listOf() /* additionalRecords */)
+                emptyList() /* authorityRecords */, emptyList() /* additionalRecords */)
         val src = InetSocketAddress(parseNumericAddress("192.0.2.123"), 5353)
         val reply = repository.getReply(query, src)
 
-        if (!expectReply) {
+        if (replyAnswers.isEmpty() || additionalAnswers.isEmpty()) {
             assertNull(reply)
             return
         }
@@ -1016,6 +1339,7 @@
         assertEquals(MdnsConstants.MDNS_PORT, reply.destination.port)
         assertEquals(replyAnswers, reply.answers)
         assertEquals(additionalAnswers, reply.additionalAnswers)
+        assertEquals(knownAnswers, reply.knownAnswers)
     }
 
     @Test
@@ -1028,8 +1352,8 @@
                 false /* cacheFlush */,
                 LONG_TTL,
                 arrayOf("MyTestService", "_testservice", "_tcp", "local")))
-        doGetReplyWithAnswersTest(questions, knownAnswers, listOf() /* replyAnswers */,
-                listOf() /* additionalAnswers */, false /* expectReply */)
+        doGetReplyWithAnswersTest(questions, knownAnswers, emptyList() /* replyAnswers */,
+                emptyList() /* additionalAnswers */)
     }
 
     @Test
@@ -1055,7 +1379,7 @@
                         0L /* receiptTimeMillis */,
                         true /* cacheFlush */,
                         LONG_TTL,
-                        listOf() /* entries */),
+                        emptyList() /* entries */),
                 MdnsServiceRecord(
                         serviceName,
                         0L /* receiptTimeMillis */,
@@ -1097,8 +1421,7 @@
                         SHORT_TTL,
                         TEST_HOSTNAME /* nextDomain */,
                         intArrayOf(MdnsRecord.TYPE_A, MdnsRecord.TYPE_AAAA)))
-        doGetReplyWithAnswersTest(questions, knownAnswers, replyAnswers, additionalAnswers,
-                true /* expectReply */)
+        doGetReplyWithAnswersTest(questions, knownAnswers, replyAnswers, additionalAnswers)
     }
 
     @Test
@@ -1124,7 +1447,7 @@
                         0L /* receiptTimeMillis */,
                         true /* cacheFlush */,
                         LONG_TTL,
-                        listOf() /* entries */),
+                        emptyList() /* entries */),
                 MdnsServiceRecord(
                         serviceName,
                         0L /* receiptTimeMillis */,
@@ -1166,8 +1489,7 @@
                         SHORT_TTL,
                         TEST_HOSTNAME /* nextDomain */,
                         intArrayOf(MdnsRecord.TYPE_A, MdnsRecord.TYPE_AAAA)))
-        doGetReplyWithAnswersTest(questions, knownAnswers, replyAnswers, additionalAnswers,
-                true /* expectReply */)
+        doGetReplyWithAnswersTest(questions, knownAnswers, replyAnswers, additionalAnswers)
     }
 
     @Test
@@ -1218,8 +1540,7 @@
                         SHORT_TTL,
                         TEST_HOSTNAME /* nextDomain */,
                         intArrayOf(MdnsRecord.TYPE_A, MdnsRecord.TYPE_AAAA)))
-        doGetReplyWithAnswersTest(questions, knownAnswers, replyAnswers, additionalAnswers,
-                true /* expectReply */)
+        doGetReplyWithAnswersTest(questions, knownAnswers, replyAnswers, additionalAnswers)
     }
 
     @Test
@@ -1248,10 +1569,8 @@
                 TEST_HOSTNAME
             )
         )
-        doGetReplyWithAnswersTest(
-            questions, knownAnswers, listOf() /* replyAnswers */,
-            listOf() /* additionalAnswers */, false /* expectReply */
-        )
+        doGetReplyWithAnswersTest(questions, knownAnswers, emptyList() /* replyAnswers */,
+                emptyList() /* additionalAnswers */)
     }
 
     @Test
@@ -1263,8 +1582,8 @@
         val questions = listOf(
             MdnsPointerRecord(arrayOf("_testservice", "_tcp", "local"), true /* isUnicast */),
             MdnsPointerRecord(arrayOf("_otherservice", "_tcp", "local"), true /* isUnicast */))
-        val query = MdnsPacket(0 /* flags */, questions, listOf() /* answers */,
-            listOf() /* authorityRecords */, listOf() /* additionalRecords */)
+        val query = MdnsPacket(0 /* flags */, questions, emptyList() /* answers */,
+                emptyList() /* authorityRecords */, emptyList() /* additionalRecords */)
         val src = InetSocketAddress(parseNumericAddress("2001:db8::123"), 5353)
 
         // Reply to the question and verify it is sent to the source.
@@ -1287,8 +1606,8 @@
         val questions = listOf(
             MdnsPointerRecord(arrayOf("_testservice", "_tcp", "local"), true /* isUnicast */),
             MdnsPointerRecord(arrayOf("_otherservice", "_tcp", "local"), false /* isUnicast */))
-        val query = MdnsPacket(0 /* flags */, questions, listOf() /* answers */,
-            listOf() /* authorityRecords */, listOf() /* additionalRecords */)
+        val query = MdnsPacket(0 /* flags */, questions, emptyList() /* answers */,
+                emptyList() /* authorityRecords */, emptyList() /* additionalRecords */)
         val src = InetSocketAddress(parseNumericAddress("2001:db8::123"), 5353)
 
         // Reply to the question and verify it is sent multicast.
@@ -1306,8 +1625,8 @@
         val questions = listOf(
             MdnsPointerRecord(arrayOf("_otherservice", "_tcp", "local"), true /* isUnicast */),
             MdnsPointerRecord(arrayOf("_testservice", "_tcp", "local"), false /* isUnicast */))
-        val query = MdnsPacket(0 /* flags */, questions, listOf() /* answers */,
-            listOf() /* authorityRecords */, listOf() /* additionalRecords */)
+        val query = MdnsPacket(0 /* flags */, questions, emptyList() /* answers */,
+                emptyList() /* authorityRecords */, emptyList() /* additionalRecords */)
         val src = InetSocketAddress(parseNumericAddress("2001:db8::123"), 5353)
 
         // Reply to the question and verify it is sent multicast.
@@ -1325,8 +1644,8 @@
         // The service is known and requests unicast reply, but the feature is disabled
         val questions = listOf(
             MdnsPointerRecord(arrayOf("_testservice", "_tcp", "local"), true /* isUnicast */))
-        val query = MdnsPacket(0 /* flags */, questions, listOf() /* answers */,
-            listOf() /* authorityRecords */, listOf() /* additionalRecords */)
+        val query = MdnsPacket(0 /* flags */, questions, emptyList() /* answers */,
+                emptyList() /* authorityRecords */, emptyList() /* additionalRecords */)
         val src = InetSocketAddress(parseNumericAddress("2001:db8::123"), 5353)
 
         // Reply to the question and verify it is sent multicast.
@@ -1334,6 +1653,28 @@
         assertNotNull(reply)
         assertEquals(MdnsConstants.getMdnsIPv6Address(), reply.destination.address)
     }
+
+    @Test
+    fun testGetReply_OnlyKnownAnswers() {
+        val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME,
+                makeFlags(isKnownAnswerSuppressionEnabled = true))
+        repository.initWithService(TEST_SERVICE_ID_1, TEST_SERVICE_1)
+        val knownAnswers = listOf(MdnsPointerRecord(
+                arrayOf("_testservice", "_tcp", "local"),
+                0L /* receiptTimeMillis */,
+                false /* cacheFlush */,
+                LONG_TTL - 1000L,
+                arrayOf("MyTestService", "_testservice", "_tcp", "local")))
+        val query = MdnsPacket(MdnsConstants.FLAG_TRUNCATED /* flags */, emptyList(),
+                knownAnswers, emptyList() /* authorityRecords */,
+                emptyList() /* additionalRecords */)
+        val src = InetSocketAddress(parseNumericAddress("192.0.2.123"), 5353)
+        val reply = repository.getReply(query, src)
+        assertNotNull(reply)
+        assertEquals(0, reply.answers.size)
+        assertEquals(0, reply.additionalAnswers.size)
+        assertEquals(knownAnswers, reply.knownAnswers)
+    }
 }
 
 private fun MdnsRecordRepository.initWithService(
@@ -1351,7 +1692,7 @@
     serviceId: Int,
     serviceInfo: NsdServiceInfo
 ): AnnouncementInfo {
-    addService(serviceId, serviceInfo)
+    addService(serviceId, serviceInfo, null /* ttl */)
     val probingInfo = setServiceProbing(serviceId)
     assertNotNull(probingInfo)
     return onProbingSucceeded(probingInfo)
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsReplySenderTest.kt b/tests/unit/java/com/android/server/connectivity/mdns/MdnsReplySenderTest.kt
index 9e2933f..9bd0530 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsReplySenderTest.kt
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsReplySenderTest.kt
@@ -24,21 +24,28 @@
 import android.os.Message
 import com.android.net.module.util.SharedLog
 import com.android.server.connectivity.mdns.MdnsConstants.IPV4_SOCKET_ADDR
+import com.android.server.connectivity.mdns.MdnsConstants.IPV6_SOCKET_ADDR
+import com.android.server.connectivity.mdns.MdnsReplySender.getReplyDestination
 import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
 import com.android.testutils.DevSdkIgnoreRunner
+import java.net.DatagramPacket
 import java.net.InetSocketAddress
 import java.util.concurrent.CompletableFuture
 import java.util.concurrent.TimeUnit
+import kotlin.test.assertEquals
 import org.junit.After
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.ArgumentCaptor
+import org.mockito.Mockito.any
+import org.mockito.Mockito.anyLong
 import org.mockito.Mockito.argThat
 import org.mockito.Mockito.doReturn
 import org.mockito.Mockito.eq
 import org.mockito.Mockito.mock
 import org.mockito.Mockito.timeout
+import org.mockito.Mockito.times
 import org.mockito.Mockito.verify
 
 private const val TEST_PORT = 12345
@@ -50,8 +57,12 @@
 @IgnoreUpTo(Build.VERSION_CODES.S_V2)
 class MdnsReplySenderTest {
     private val serviceName = arrayOf("MyTestService", "_testservice", "_tcp", "local")
+    private val otherServiceName = arrayOf("OtherTestService", "_testservice", "_tcp", "local")
     private val serviceType = arrayOf("_testservice", "_tcp", "local")
+    private val source = InetSocketAddress(
+            InetAddresses.parseNumericAddress("192.0.2.1"), TEST_PORT)
     private val hostname = arrayOf("Android_000102030405060708090A0B0C0D0E0F", "local")
+    private val otherHostname = arrayOf("Android_0F0E0D0C0B0A09080706050403020100", "local")
     private val hostAddresses = listOf(
             LinkAddress(InetAddresses.parseNumericAddress("192.0.2.111"), 24),
             LinkAddress(InetAddresses.parseNumericAddress("2001:db8::111"), 64),
@@ -59,9 +70,12 @@
     private val answers = listOf(
             MdnsPointerRecord(serviceType, 0L /* receiptTimeMillis */, false /* cacheFlush */,
                     LONG_TTL, serviceName))
+    private val otherAnswers = listOf(
+            MdnsPointerRecord(serviceType, 0L /* receiptTimeMillis */, false /* cacheFlush */,
+                    LONG_TTL, otherServiceName))
     private val additionalAnswers = listOf(
             MdnsTextRecord(serviceName, 0L /* receiptTimeMillis */, true /* cacheFlush */, LONG_TTL,
-                    listOf() /* entries */),
+                    emptyList() /* entries */),
             MdnsServiceRecord(serviceName, 0L /* receiptTimeMillis */, true /* cacheFlush */,
                     SHORT_TTL, 0 /* servicePriority */, 0 /* serviceWeight */, TEST_PORT, hostname),
             MdnsInetAddressRecord(hostname, 0L /* receiptTimeMillis */, true /* cacheFlush */,
@@ -75,15 +89,30 @@
                     intArrayOf(MdnsRecord.TYPE_TXT, MdnsRecord.TYPE_SRV)),
             MdnsNsecRecord(hostname, 0L /* receiptTimeMillis */, true /* cacheFlush */, SHORT_TTL,
                     hostname /* nextDomain */, intArrayOf(MdnsRecord.TYPE_A, MdnsRecord.TYPE_AAAA)))
+    private val otherAdditionalAnswers = listOf(
+            MdnsTextRecord(otherServiceName, 0L /* receiptTimeMillis */, true /* cacheFlush */,
+                    LONG_TTL, emptyList() /* entries */),
+            MdnsServiceRecord(otherServiceName, 0L /* receiptTimeMillis */, true /* cacheFlush */,
+                    SHORT_TTL, 0 /* servicePriority */, 0 /* serviceWeight */, TEST_PORT,
+                    otherHostname),
+            MdnsInetAddressRecord(otherHostname, 0L /* receiptTimeMillis */, true /* cacheFlush */,
+                    SHORT_TTL, hostAddresses[0].address),
+            MdnsInetAddressRecord(otherHostname, 0L /* receiptTimeMillis */, true /* cacheFlush */,
+                    SHORT_TTL, hostAddresses[1].address),
+            MdnsInetAddressRecord(otherHostname, 0L /* receiptTimeMillis */, true /* cacheFlush */,
+                    SHORT_TTL, hostAddresses[2].address),
+            MdnsNsecRecord(otherServiceName, 0L /* receiptTimeMillis */, true /* cacheFlush */,
+                    LONG_TTL, otherServiceName /* nextDomain */,
+                    intArrayOf(MdnsRecord.TYPE_TXT, MdnsRecord.TYPE_SRV)),
+            MdnsNsecRecord(otherHostname, 0L /* receiptTimeMillis */, true /* cacheFlush */,
+                    SHORT_TTL, otherHostname /* nextDomain */,
+                    intArrayOf(MdnsRecord.TYPE_A, MdnsRecord.TYPE_AAAA)))
     private val thread = HandlerThread(MdnsReplySenderTest::class.simpleName)
     private val socket = mock(MdnsInterfaceSocket::class.java)
     private val buffer = ByteArray(1500)
     private val sharedLog = SharedLog(MdnsReplySenderTest::class.simpleName)
     private val deps = mock(MdnsReplySender.Dependencies::class.java)
     private val handler by lazy { Handler(thread.looper) }
-    private val replySender by lazy {
-        MdnsReplySender(thread.looper, socket, buffer, sharedLog, false /* enableDebugLog */, deps)
-    }
 
     @Before
     fun setUp() {
@@ -106,37 +135,180 @@
         return future.get(DEFAULT_TIMEOUT_MS, TimeUnit.MILLISECONDS)
     }
 
-    private fun sendNow(packet: MdnsPacket, destination: InetSocketAddress):
-            Unit = runningOnHandlerAndReturn { replySender.sendNow(packet, destination) }
+    private fun sendNow(sender: MdnsReplySender, packet: MdnsPacket, dest: InetSocketAddress):
+            Unit = runningOnHandlerAndReturn { sender.sendNow(packet, dest) }
 
-    private fun queueReply(reply: MdnsReplyInfo):
-            Unit = runningOnHandlerAndReturn { replySender.queueReply(reply) }
+    private fun queueReply(sender: MdnsReplySender, reply: MdnsReplyInfo):
+            Unit = runningOnHandlerAndReturn { sender.queueReply(reply) }
+
+    private fun buildFlags(enableKAS: Boolean): MdnsFeatureFlags {
+        return MdnsFeatureFlags.newBuilder()
+                .setIsKnownAnswerSuppressionEnabled(enableKAS).build()
+    }
+
+    private fun createSender(enableKAS: Boolean): MdnsReplySender =
+            MdnsReplySender(thread.looper, socket, buffer, sharedLog, false /* enableDebugLog */,
+                    deps, buildFlags(enableKAS))
 
     @Test
     fun testSendNow() {
+        val replySender = createSender(enableKAS = false)
         val packet = MdnsPacket(0x8400,
-                listOf() /* questions */,
+                emptyList() /* questions */,
                 answers,
-                listOf() /* authorityRecords */,
+                emptyList() /* authorityRecords */,
                 additionalAnswers)
-        sendNow(packet, IPV4_SOCKET_ADDR)
+        sendNow(replySender, packet, IPV4_SOCKET_ADDR)
         verify(socket).send(argThat{ it.socketAddress.equals(IPV4_SOCKET_ADDR) })
     }
 
+    private fun verifyMessageQueued(
+            sender: MdnsReplySender,
+            replies: List<MdnsReplyInfo>
+    ): Pair<Handler, Message> {
+        val handlerCaptor = ArgumentCaptor.forClass(Handler::class.java)
+        val messageCaptor = ArgumentCaptor.forClass(Message::class.java)
+        for (reply in replies) {
+            queueReply(sender, reply)
+            verify(deps).sendMessageDelayed(
+                    handlerCaptor.capture(), messageCaptor.capture(), eq(reply.sendDelayMs))
+        }
+        return Pair(handlerCaptor.value, messageCaptor.value)
+    }
+
+    private fun verifyReplySent(
+            realHandler: Handler,
+            delayMessage: Message,
+            remainingAnswers: List<MdnsRecord>
+    ) {
+        val datagramPacketCaptor = ArgumentCaptor.forClass(DatagramPacket::class.java)
+        realHandler.sendMessage(delayMessage)
+        verify(socket, timeout(DEFAULT_TIMEOUT_MS)).send(datagramPacketCaptor.capture())
+
+        val dPacket = datagramPacketCaptor.value
+        val mdnsPacket = MdnsPacket.parse(MdnsPacketReader(
+                dPacket.data, dPacket.length, buildFlags(enableKAS = false)))
+        assertEquals(mdnsPacket.answers.toSet(), remainingAnswers.toSet())
+    }
+
     @Test
     fun testQueueReply() {
+        val replySender = createSender(enableKAS = false)
         val reply = MdnsReplyInfo(answers, additionalAnswers, 20L /* sendDelayMs */,
-                IPV4_SOCKET_ADDR)
-        val handlerCaptor = ArgumentCaptor.forClass(Handler::class.java)
-        val messageCaptor = ArgumentCaptor.forClass(Message::class.java)
-        queueReply(reply)
-        verify(deps).sendMessageDelayed(handlerCaptor.capture(), messageCaptor.capture(), eq(20L))
+                IPV4_SOCKET_ADDR, source, emptyList())
+        val (handler, message) = verifyMessageQueued(replySender, listOf(reply))
+        verifyReplySent(handler, message, answers)
+    }
 
-        val realHandler = handlerCaptor.value
-        val delayMessage = messageCaptor.value
-        realHandler.sendMessage(delayMessage)
-        verify(socket, timeout(DEFAULT_TIMEOUT_MS)).send(argThat{
-            it.socketAddress.equals(IPV4_SOCKET_ADDR)
-        })
+    @Test
+    fun testQueueReply_KnownAnswerSuppressionEnabled() {
+        val replySender = createSender(enableKAS = true)
+        val reply = MdnsReplyInfo(answers, additionalAnswers, 20L /* sendDelayMs */,
+                IPV4_SOCKET_ADDR, source, emptyList())
+        val (handler, message) = verifyMessageQueued(replySender, listOf(reply))
+        verifyReplySent(handler, message, answers)
+    }
+
+    @Test
+    fun testQueueReply_MultiplePacket() {
+        val replySender = createSender(enableKAS = true)
+        val reply = MdnsReplyInfo(answers, additionalAnswers, 400L /* sendDelayMs */,
+                IPV4_SOCKET_ADDR, source, emptyList())
+        verifyMessageQueued(replySender, listOf(reply))
+
+        // Receive a known-answer packet and verify no message queued.
+        val knownAnswersReply = MdnsReplyInfo(emptyList(), emptyList(), 0L /* sendDelayMs */,
+                IPV4_SOCKET_ADDR, source, answers)
+        queueReply(replySender, knownAnswersReply)
+        verify(deps, times(1)).sendMessageDelayed(any(), any(), anyLong())
+    }
+
+    @Test
+    fun testQueueReply_MultiplePacket_LostSubsequentPacket() {
+        val replySender = createSender(enableKAS = true)
+        val reply = MdnsReplyInfo(answers, additionalAnswers, 400L /* sendDelayMs */,
+                IPV4_SOCKET_ADDR, source, emptyList())
+        val (handler, message) = verifyMessageQueued(replySender, listOf(reply))
+
+        // No subsequent packets
+        verifyReplySent(handler, message, answers)
+    }
+
+    @Test
+    fun testQueueReply_MultiplePacket_OtherKnownAnswer() {
+        val replySender = createSender(enableKAS = true)
+        val reply = MdnsReplyInfo(answers, additionalAnswers, 400L /* sendDelayMs */,
+                IPV4_SOCKET_ADDR, source, emptyList())
+        // Other known-answer service
+        val otherKnownAnswersReply = MdnsReplyInfo(emptyList(), emptyList(), 0L /* sendDelayMs */,
+                IPV4_SOCKET_ADDR, source, otherAnswers)
+        val (handler, message) = verifyMessageQueued(
+                replySender, listOf(reply, otherKnownAnswersReply))
+        verifyReplySent(handler, message, answers)
+    }
+
+    @Test
+    fun testQueueReply_MultiplePacket_TwoKnownAnswerPackets() {
+        val replySender = createSender(enableKAS = true)
+        val reply = MdnsReplyInfo(answers, additionalAnswers, 400L /* sendDelayMs */,
+                IPV4_SOCKET_ADDR, source, emptyList())
+        val firstKnownAnswerReply = MdnsReplyInfo(emptyList(), emptyList(), 401L /* sendDelayMs */,
+                IPV4_SOCKET_ADDR, source, otherAnswers)
+        verifyMessageQueued(replySender, listOf(reply, firstKnownAnswerReply))
+
+        // Second known-answer service
+        val secondKnownAnswerReply = MdnsReplyInfo(emptyList(), emptyList(), 0L /* sendDelayMs */,
+                IPV4_SOCKET_ADDR, source, answers)
+        queueReply(replySender, secondKnownAnswerReply)
+
+        // Verify that no reply is queued, as all answers are known.
+        verify(deps, times(2)).sendMessageDelayed(any(), any(), anyLong())
+    }
+
+    @Test
+    fun testQueueReply_MultiplePacket_LostSecondaryPacket() {
+        val replySender = createSender(enableKAS = true)
+        val reply = MdnsReplyInfo(answers, additionalAnswers, 400L /* sendDelayMs */,
+                IPV4_SOCKET_ADDR, source, emptyList())
+        val firstKnownAnswerReply = MdnsReplyInfo(emptyList(), emptyList(), 401L /* sendDelayMs */,
+                IPV4_SOCKET_ADDR, source, otherAnswers)
+        val (handler, message) = verifyMessageQueued(
+                replySender, listOf(reply, firstKnownAnswerReply))
+
+        // Second known-answer service lost
+        verifyReplySent(handler, message, answers)
+    }
+
+    @Test
+    fun testQueueReply_MultiplePacket_WithMultipleQuestions() {
+        val replySender = createSender(enableKAS = true)
+        val twoAnswers = listOf(
+                MdnsPointerRecord(serviceType, 0L /* receiptTimeMillis */, false /* cacheFlush */,
+                        LONG_TTL, serviceName),
+                MdnsServiceRecord(otherServiceName, 0L /* receiptTimeMillis */,
+                        true /* cacheFlush */, SHORT_TTL, 0 /* servicePriority */,
+                        0 /* serviceWeight */, TEST_PORT, otherHostname))
+        val reply = MdnsReplyInfo(twoAnswers, additionalAnswers, 400L /* sendDelayMs */,
+                IPV4_SOCKET_ADDR, source, emptyList())
+        val knownAnswersReply = MdnsReplyInfo(otherAnswers, otherAdditionalAnswers,
+                20L /* sendDelayMs */, IPV4_SOCKET_ADDR, source, answers)
+        val (handler, message) = verifyMessageQueued(replySender, listOf(reply, knownAnswersReply))
+
+        val remainingAnswers = listOf(
+                MdnsPointerRecord(serviceType, 0L /* receiptTimeMillis */, false /* cacheFlush */,
+                        LONG_TTL, otherServiceName),
+                MdnsServiceRecord(otherServiceName, 0L /* receiptTimeMillis */,
+                        true /* cacheFlush */, SHORT_TTL, 0 /* servicePriority */,
+                        0 /* serviceWeight */, TEST_PORT, otherHostname))
+        verifyReplySent(handler, message, remainingAnswers)
+    }
+
+    @Test
+    fun testGetReplyDestination() {
+        assertEquals(IPV4_SOCKET_ADDR, getReplyDestination(IPV4_SOCKET_ADDR, IPV4_SOCKET_ADDR))
+        assertEquals(IPV6_SOCKET_ADDR, getReplyDestination(IPV6_SOCKET_ADDR, IPV6_SOCKET_ADDR))
+        assertEquals(IPV4_SOCKET_ADDR, getReplyDestination(source, IPV4_SOCKET_ADDR))
+        assertEquals(IPV6_SOCKET_ADDR, getReplyDestination(source, IPV6_SOCKET_ADDR))
+        assertEquals(source, getReplyDestination(source, source))
     }
 }
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceInfoTest.java b/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceInfoTest.java
index e7d7a98..8740e80 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceInfoTest.java
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceInfoTest.java
@@ -35,6 +35,7 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.time.Instant;
 import java.util.List;
 import java.util.Map;
 
@@ -202,7 +203,8 @@
                         List.of(),
                         /* textEntries= */ null,
                         /* interfaceIndex= */ 20,
-                        network);
+                        network,
+                        Instant.MAX /* expirationTime */);
 
         assertEquals(network, info2.getNetwork());
     }
@@ -225,7 +227,8 @@
                                 MdnsServiceInfo.TextEntry.fromString("mn=Google Nest Hub Max"),
                                 MdnsServiceInfo.TextEntry.fromString("test=")),
                         20 /* interfaceIndex */,
-                        new Network(123));
+                        new Network(123),
+                        Instant.MAX /* expirationTime */);
 
         beforeParcel.writeToParcel(parcel, 0);
         parcel.setDataPosition(0);
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 7a2e4bf..09236b1 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceTypeClientTests.java
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceTypeClientTests.java
@@ -16,7 +16,13 @@
 
 package com.android.server.connectivity.mdns;
 
+import static com.android.server.connectivity.mdns.MdnsSearchOptions.ACTIVE_QUERY_MODE;
+import static com.android.server.connectivity.mdns.MdnsSearchOptions.AGGRESSIVE_QUERY_MODE;
+import static com.android.server.connectivity.mdns.MdnsSearchOptions.PASSIVE_QUERY_MODE;
 import static com.android.server.connectivity.mdns.MdnsServiceTypeClient.EVENT_START_QUERYTASK;
+import static com.android.server.connectivity.mdns.QueryTaskConfig.INITIAL_AGGRESSIVE_TIME_BETWEEN_BURSTS_MS;
+import static com.android.server.connectivity.mdns.QueryTaskConfig.MAX_TIME_BETWEEN_AGGRESSIVE_BURSTS_MS;
+import static com.android.server.connectivity.mdns.QueryTaskConfig.TIME_BETWEEN_RETRANSMISSION_QUERIES_IN_BURST_MS;
 import static com.android.testutils.DevSdkIgnoreRuleKt.SC_V2;
 
 import static org.junit.Assert.assertArrayEquals;
@@ -37,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;
@@ -145,8 +152,8 @@
         MockitoAnnotations.initMocks(this);
         doReturn(TEST_ELAPSED_REALTIME).when(mockDecoderClock).elapsedRealtime();
 
-        expectedIPv4Packets = new DatagramPacket[16];
-        expectedIPv6Packets = new DatagramPacket[16];
+        expectedIPv4Packets = new DatagramPacket[24];
+        expectedIPv6Packets = new DatagramPacket[24];
         socketKey = new SocketKey(mockNetwork, INTERFACE_INDEX);
 
         for (int i = 0; i < expectedIPv4Packets.length; ++i) {
@@ -171,7 +178,15 @@
                 .thenReturn(expectedIPv4Packets[12])
                 .thenReturn(expectedIPv4Packets[13])
                 .thenReturn(expectedIPv4Packets[14])
-                .thenReturn(expectedIPv4Packets[15]);
+                .thenReturn(expectedIPv4Packets[15])
+                .thenReturn(expectedIPv4Packets[16])
+                .thenReturn(expectedIPv4Packets[17])
+                .thenReturn(expectedIPv4Packets[18])
+                .thenReturn(expectedIPv4Packets[19])
+                .thenReturn(expectedIPv4Packets[20])
+                .thenReturn(expectedIPv4Packets[21])
+                .thenReturn(expectedIPv4Packets[22])
+                .thenReturn(expectedIPv4Packets[23]);
 
         when(mockPacketWriter.getPacket(IPV6_ADDRESS))
                 .thenReturn(expectedIPv6Packets[0])
@@ -189,7 +204,15 @@
                 .thenReturn(expectedIPv6Packets[12])
                 .thenReturn(expectedIPv6Packets[13])
                 .thenReturn(expectedIPv6Packets[14])
-                .thenReturn(expectedIPv6Packets[15]);
+                .thenReturn(expectedIPv6Packets[15])
+                .thenReturn(expectedIPv6Packets[16])
+                .thenReturn(expectedIPv6Packets[17])
+                .thenReturn(expectedIPv6Packets[18])
+                .thenReturn(expectedIPv6Packets[19])
+                .thenReturn(expectedIPv6Packets[20])
+                .thenReturn(expectedIPv6Packets[21])
+                .thenReturn(expectedIPv6Packets[22])
+                .thenReturn(expectedIPv6Packets[23]);
 
         thread = new HandlerThread("MdnsServiceTypeClientTests");
         thread.start();
@@ -219,15 +242,22 @@
             return true;
         }).when(mockDeps).sendMessage(any(Handler.class), any(Message.class));
 
-        client =
-                new MdnsServiceTypeClient(SERVICE_TYPE, mockSocketClient, currentThreadExecutor,
-                        mockDecoderClock, socketKey, mockSharedLog, thread.getLooper(), mockDeps,
-                        serviceCache) {
-                    @Override
-                    MdnsPacketWriter createMdnsPacketWriter() {
-                        return mockPacketWriter;
-                    }
-                };
+        client = makeMdnsServiceTypeClient(mockPacketWriter);
+    }
+
+    private MdnsServiceTypeClient makeMdnsServiceTypeClient(
+            @Nullable MdnsPacketWriter packetWriter) {
+        return new MdnsServiceTypeClient(SERVICE_TYPE, mockSocketClient, currentThreadExecutor,
+                mockDecoderClock, socketKey, mockSharedLog, thread.getLooper(), mockDeps,
+                serviceCache) {
+            @Override
+            MdnsPacketWriter createMdnsPacketWriter() {
+                if (packetWriter == null) {
+                    return super.createMdnsPacketWriter();
+                }
+                return packetWriter;
+            }
+        };
     }
 
     @After
@@ -267,8 +297,8 @@
 
     @Test
     public void sendQueries_activeScanMode() {
-        MdnsSearchOptions searchOptions =
-                MdnsSearchOptions.newBuilder().addSubtype(SUBTYPE).setIsPassiveMode(false).build();
+        MdnsSearchOptions searchOptions = MdnsSearchOptions.newBuilder()
+                .addSubtype(SUBTYPE).setQueryMode(ACTIVE_QUERY_MODE).build();
         startSendAndReceive(mockListenerOne, searchOptions);
         // Always try to remove the task.
         verify(mockDeps, times(1)).removeMessages(any(), eq(EVENT_START_QUERYTASK));
@@ -319,8 +349,8 @@
 
     @Test
     public void sendQueries_reentry_activeScanMode() {
-        MdnsSearchOptions searchOptions =
-                MdnsSearchOptions.newBuilder().addSubtype(SUBTYPE).setIsPassiveMode(false).build();
+        MdnsSearchOptions searchOptions = MdnsSearchOptions.newBuilder()
+                .addSubtype(SUBTYPE).setQueryMode(ACTIVE_QUERY_MODE).build();
         startSendAndReceive(mockListenerOne, searchOptions);
         // Always try to remove the task.
         verify(mockDeps, times(1)).removeMessages(any(), eq(EVENT_START_QUERYTASK));
@@ -333,7 +363,7 @@
                 MdnsSearchOptions.newBuilder()
                         .addSubtype(SUBTYPE)
                         .addSubtype("_subtype2")
-                        .setIsPassiveMode(false)
+                        .setQueryMode(ACTIVE_QUERY_MODE)
                         .build();
         startSendAndReceive(mockListenerOne, searchOptions);
         // The previous scheduled task should be canceled.
@@ -353,8 +383,8 @@
 
     @Test
     public void sendQueries_passiveScanMode() {
-        MdnsSearchOptions searchOptions =
-                MdnsSearchOptions.newBuilder().addSubtype(SUBTYPE).setIsPassiveMode(true).build();
+        MdnsSearchOptions searchOptions = MdnsSearchOptions.newBuilder()
+                .addSubtype(SUBTYPE).setQueryMode(PASSIVE_QUERY_MODE).build();
         startSendAndReceive(mockListenerOne, searchOptions);
         // Always try to remove the task.
         verify(mockDeps, times(1)).removeMessages(any(), eq(EVENT_START_QUERYTASK));
@@ -380,8 +410,10 @@
     @Test
     public void sendQueries_activeScanWithQueryBackoff() {
         MdnsSearchOptions searchOptions =
-                MdnsSearchOptions.newBuilder().addSubtype(SUBTYPE).setIsPassiveMode(
-                        false).setNumOfQueriesBeforeBackoff(11).build();
+                MdnsSearchOptions.newBuilder()
+                        .addSubtype(SUBTYPE)
+                        .setQueryMode(ACTIVE_QUERY_MODE)
+                        .setNumOfQueriesBeforeBackoff(11).build();
         startSendAndReceive(mockListenerOne, searchOptions);
         // Always try to remove the task.
         verify(mockDeps, times(1)).removeMessages(any(), eq(EVENT_START_QUERYTASK));
@@ -439,8 +471,10 @@
     @Test
     public void sendQueries_passiveScanWithQueryBackoff() {
         MdnsSearchOptions searchOptions =
-                MdnsSearchOptions.newBuilder().addSubtype(SUBTYPE).setIsPassiveMode(
-                        true).setNumOfQueriesBeforeBackoff(3).build();
+                MdnsSearchOptions.newBuilder()
+                        .addSubtype(SUBTYPE)
+                        .setQueryMode(PASSIVE_QUERY_MODE)
+                        .setNumOfQueriesBeforeBackoff(3).build();
         startSendAndReceive(mockListenerOne, searchOptions);
         // Always try to remove the task.
         verify(mockDeps, times(1)).removeMessages(any(), eq(EVENT_START_QUERYTASK));
@@ -497,8 +531,8 @@
 
     @Test
     public void sendQueries_reentry_passiveScanMode() {
-        MdnsSearchOptions searchOptions =
-                MdnsSearchOptions.newBuilder().addSubtype(SUBTYPE).setIsPassiveMode(true).build();
+        MdnsSearchOptions searchOptions = MdnsSearchOptions.newBuilder()
+                .addSubtype(SUBTYPE).setQueryMode(PASSIVE_QUERY_MODE).build();
         startSendAndReceive(mockListenerOne, searchOptions);
         // Always try to remove the task.
         verify(mockDeps, times(1)).removeMessages(any(), eq(EVENT_START_QUERYTASK));
@@ -511,7 +545,7 @@
                 MdnsSearchOptions.newBuilder()
                         .addSubtype(SUBTYPE)
                         .addSubtype("_subtype2")
-                        .setIsPassiveMode(true)
+                        .setQueryMode(PASSIVE_QUERY_MODE)
                         .build();
         startSendAndReceive(mockListenerOne, searchOptions);
         // The previous scheduled task should be canceled.
@@ -533,16 +567,15 @@
     @Ignore("MdnsConfigs is not configurable currently.")
     public void testQueryTaskConfig_alwaysAskForUnicastResponse() {
         //MdnsConfigsFlagsImpl.alwaysAskForUnicastResponseInEachBurst.override(true);
-        MdnsSearchOptions searchOptions =
-                MdnsSearchOptions.newBuilder().addSubtype(SUBTYPE).setIsPassiveMode(false).build();
+        MdnsSearchOptions searchOptions = MdnsSearchOptions.newBuilder()
+                .addSubtype(SUBTYPE).setQueryMode(ACTIVE_QUERY_MODE).build();
         QueryTaskConfig config = new QueryTaskConfig(
-                searchOptions.getSubtypes(), searchOptions.isPassiveMode(),
+                searchOptions.getQueryMode(),
                 false /* onlyUseIpv6OnIpv6OnlyNetworks */, 3 /* numOfQueriesBeforeBackoff */,
                 socketKey);
 
         // This is the first query. We will ask for unicast response.
         assertTrue(config.expectUnicastResponse);
-        assertEquals(config.subtypes, searchOptions.getSubtypes());
         assertEquals(config.transactionId, 1);
 
         // For the rest of queries in this burst, we will NOT ask for unicast response.
@@ -550,7 +583,6 @@
             int oldTransactionId = config.transactionId;
             config = config.getConfigForNextRun();
             assertFalse(config.expectUnicastResponse);
-            assertEquals(config.subtypes, searchOptions.getSubtypes());
             assertEquals(config.transactionId, oldTransactionId + 1);
         }
 
@@ -558,22 +590,20 @@
         int oldTransactionId = config.transactionId;
         config = config.getConfigForNextRun();
         assertTrue(config.expectUnicastResponse);
-        assertEquals(config.subtypes, searchOptions.getSubtypes());
         assertEquals(config.transactionId, oldTransactionId + 1);
     }
 
     @Test
     public void testQueryTaskConfig_askForUnicastInFirstQuery() {
-        MdnsSearchOptions searchOptions =
-                MdnsSearchOptions.newBuilder().addSubtype(SUBTYPE).setIsPassiveMode(false).build();
+        MdnsSearchOptions searchOptions = MdnsSearchOptions.newBuilder()
+                .addSubtype(SUBTYPE).setQueryMode(ACTIVE_QUERY_MODE).build();
         QueryTaskConfig config = new QueryTaskConfig(
-                searchOptions.getSubtypes(), searchOptions.isPassiveMode(),
+                searchOptions.getQueryMode(),
                 false /* onlyUseIpv6OnIpv6OnlyNetworks */, 3 /* numOfQueriesBeforeBackoff */,
                 socketKey);
 
         // This is the first query. We will ask for unicast response.
         assertTrue(config.expectUnicastResponse);
-        assertEquals(config.subtypes, searchOptions.getSubtypes());
         assertEquals(config.transactionId, 1);
 
         // For the rest of queries in this burst, we will NOT ask for unicast response.
@@ -581,7 +611,6 @@
             int oldTransactionId = config.transactionId;
             config = config.getConfigForNextRun();
             assertFalse(config.expectUnicastResponse);
-            assertEquals(config.subtypes, searchOptions.getSubtypes());
             assertEquals(config.transactionId, oldTransactionId + 1);
         }
 
@@ -589,14 +618,13 @@
         int oldTransactionId = config.transactionId;
         config = config.getConfigForNextRun();
         assertFalse(config.expectUnicastResponse);
-        assertEquals(config.subtypes, searchOptions.getSubtypes());
         assertEquals(config.transactionId, oldTransactionId + 1);
     }
 
     @Test
     public void testIfPreviousTaskIsCanceledWhenNewSessionStarts() {
-        MdnsSearchOptions searchOptions =
-                MdnsSearchOptions.newBuilder().addSubtype(SUBTYPE).setIsPassiveMode(true).build();
+        MdnsSearchOptions searchOptions = MdnsSearchOptions.newBuilder()
+                .addSubtype(SUBTYPE).setQueryMode(PASSIVE_QUERY_MODE).build();
         startSendAndReceive(mockListenerOne, searchOptions);
         Runnable firstMdnsTask = currentThreadExecutor.getAndClearSubmittedRunnable();
 
@@ -605,7 +633,7 @@
                 MdnsSearchOptions.newBuilder()
                         .addSubtype(SUBTYPE)
                         .addSubtype("_subtype2")
-                        .setIsPassiveMode(true)
+                        .setQueryMode(PASSIVE_QUERY_MODE)
                         .build();
         startSendAndReceive(mockListenerOne, searchOptions);
 
@@ -624,8 +652,8 @@
     @Ignore("MdnsConfigs is not configurable currently.")
     public void testIfPreviousTaskIsCanceledWhenSessionStops() {
         //MdnsConfigsFlagsImpl.shouldCancelScanTaskWhenFutureIsNull.override(true);
-        MdnsSearchOptions searchOptions =
-                MdnsSearchOptions.newBuilder().addSubtype(SUBTYPE).setIsPassiveMode(true).build();
+        MdnsSearchOptions searchOptions = MdnsSearchOptions.newBuilder()
+                .addSubtype(SUBTYPE).setQueryMode(PASSIVE_QUERY_MODE).build();
         startSendAndReceive(mockListenerOne, searchOptions);
         // Change the sutypes and start a new session.
         stopSendAndReceive(mockListenerOne);
@@ -667,6 +695,81 @@
                 any(), any(), eq(MdnsConfigs.timeBetweenQueriesInBurstMs()));
     }
 
+    @Test
+    public void testCombinedSubtypesQueriedWithMultipleListeners() throws Exception {
+        client = makeMdnsServiceTypeClient(/* packetWriter= */ null);
+        final MdnsSearchOptions searchOptions1 = MdnsSearchOptions.newBuilder()
+                .addSubtype("subtype1").build();
+        final MdnsSearchOptions searchOptions2 = MdnsSearchOptions.newBuilder()
+                .addSubtype("subtype2").build();
+        startSendAndReceive(mockListenerOne, searchOptions1);
+        currentThreadExecutor.getAndClearSubmittedRunnable().run();
+
+        InOrder inOrder = inOrder(mockListenerOne, mockSocketClient, mockDeps);
+
+        // Verify the query asks for subtype1
+        final ArgumentCaptor<DatagramPacket> subtype1QueryCaptor =
+                ArgumentCaptor.forClass(DatagramPacket.class);
+        currentThreadExecutor.getAndClearLastScheduledRunnable().run();
+        // Send twice for IPv4 and IPv6
+        inOrder.verify(mockSocketClient, times(2)).sendPacketRequestingUnicastResponse(
+                subtype1QueryCaptor.capture(),
+                eq(socketKey), eq(false));
+
+        final MdnsPacket subtype1Query = MdnsPacket.parse(
+                new MdnsPacketReader(subtype1QueryCaptor.getValue()));
+
+        assertEquals(2, subtype1Query.questions.size());
+        assertTrue(hasQuestion(subtype1Query, MdnsRecord.TYPE_PTR, SERVICE_TYPE_LABELS));
+        assertTrue(hasQuestion(subtype1Query, MdnsRecord.TYPE_PTR,
+                getServiceTypeWithSubtype("_subtype1")));
+
+        // Add subtype2
+        startSendAndReceive(mockListenerTwo, searchOptions2);
+        inOrder.verify(mockDeps).removeMessages(any(), eq(EVENT_START_QUERYTASK));
+        currentThreadExecutor.getAndClearLastScheduledRunnable().run();
+
+        final ArgumentCaptor<DatagramPacket> combinedSubtypesQueryCaptor =
+                ArgumentCaptor.forClass(DatagramPacket.class);
+        inOrder.verify(mockSocketClient, times(2)).sendPacketRequestingUnicastResponse(
+                combinedSubtypesQueryCaptor.capture(),
+                eq(socketKey), eq(false));
+        // The next query must have been scheduled
+        inOrder.verify(mockDeps).sendMessageDelayed(any(), any(), anyLong());
+
+        final MdnsPacket combinedSubtypesQuery = MdnsPacket.parse(
+                new MdnsPacketReader(combinedSubtypesQueryCaptor.getValue()));
+
+        assertEquals(3, combinedSubtypesQuery.questions.size());
+        assertTrue(hasQuestion(combinedSubtypesQuery, MdnsRecord.TYPE_PTR, SERVICE_TYPE_LABELS));
+        assertTrue(hasQuestion(combinedSubtypesQuery, MdnsRecord.TYPE_PTR,
+                getServiceTypeWithSubtype("_subtype1")));
+        assertTrue(hasQuestion(combinedSubtypesQuery, MdnsRecord.TYPE_PTR,
+                getServiceTypeWithSubtype("_subtype2")));
+
+        // Remove subtype1
+        stopSendAndReceive(mockListenerOne);
+
+        // Queries are not rescheduled, but the next query is affected
+        dispatchMessage();
+        currentThreadExecutor.getAndClearLastScheduledRunnable().run();
+
+        final ArgumentCaptor<DatagramPacket> subtype2QueryCaptor =
+                ArgumentCaptor.forClass(DatagramPacket.class);
+        // Send twice for IPv4 and IPv6
+        inOrder.verify(mockSocketClient, times(2)).sendPacketRequestingMulticastResponse(
+                subtype2QueryCaptor.capture(),
+                eq(socketKey), eq(false));
+
+        final MdnsPacket subtype2Query = MdnsPacket.parse(
+                new MdnsPacketReader(subtype2QueryCaptor.getValue()));
+
+        assertEquals(2, subtype2Query.questions.size());
+        assertTrue(hasQuestion(subtype2Query, MdnsRecord.TYPE_PTR, SERVICE_TYPE_LABELS));
+        assertTrue(hasQuestion(subtype2Query, MdnsRecord.TYPE_PTR,
+                getServiceTypeWithSubtype("_subtype2")));
+    }
+
     private static void verifyServiceInfo(MdnsServiceInfo serviceInfo, String serviceName,
             String[] serviceType, List<String> ipv4Addresses, List<String> ipv6Addresses, int port,
             List<String> subTypes, Map<String, String> attributes, SocketKey socketKey) {
@@ -919,15 +1022,7 @@
     public void processResponse_searchOptionsEnableServiceRemoval_shouldRemove()
             throws Exception {
         final String serviceInstanceName = "service-instance-1";
-        client =
-                new MdnsServiceTypeClient(SERVICE_TYPE, mockSocketClient, currentThreadExecutor,
-                        mockDecoderClock, socketKey, mockSharedLog, thread.getLooper(), mockDeps,
-                        serviceCache) {
-                    @Override
-                    MdnsPacketWriter createMdnsPacketWriter() {
-                        return mockPacketWriter;
-                    }
-                };
+        client = makeMdnsServiceTypeClient(mockPacketWriter);
         MdnsSearchOptions searchOptions = MdnsSearchOptions.newBuilder()
                 .setRemoveExpiredService(true)
                 .setNumOfQueriesBeforeBackoff(Integer.MAX_VALUE)
@@ -965,15 +1060,7 @@
     public void processResponse_searchOptionsNotEnableServiceRemoval_shouldNotRemove()
             throws Exception {
         final String serviceInstanceName = "service-instance-1";
-        client =
-                new MdnsServiceTypeClient(SERVICE_TYPE, mockSocketClient, currentThreadExecutor,
-                        mockDecoderClock, socketKey, mockSharedLog, thread.getLooper(), mockDeps,
-                        serviceCache) {
-                    @Override
-                    MdnsPacketWriter createMdnsPacketWriter() {
-                        return mockPacketWriter;
-                    }
-                };
+        client = makeMdnsServiceTypeClient(mockPacketWriter);
         startSendAndReceive(mockListenerOne, MdnsSearchOptions.getDefaultOptions());
         Runnable firstMdnsTask = currentThreadExecutor.getAndClearSubmittedRunnable();
 
@@ -999,15 +1086,7 @@
             throws Exception {
         //MdnsConfigsFlagsImpl.removeServiceAfterTtlExpires.override(true);
         final String serviceInstanceName = "service-instance-1";
-        client =
-                new MdnsServiceTypeClient(SERVICE_TYPE, mockSocketClient, currentThreadExecutor,
-                        mockDecoderClock, socketKey, mockSharedLog, thread.getLooper(), mockDeps,
-                        serviceCache) {
-                    @Override
-                    MdnsPacketWriter createMdnsPacketWriter() {
-                        return mockPacketWriter;
-                    }
-                };
+        client = makeMdnsServiceTypeClient(mockPacketWriter);
         startSendAndReceive(mockListenerOne, MdnsSearchOptions.getDefaultOptions());
         Runnable firstMdnsTask = currentThreadExecutor.getAndClearSubmittedRunnable();
 
@@ -1122,19 +1201,21 @@
 
     @Test
     public void testProcessResponse_Resolve() throws Exception {
-        client = new MdnsServiceTypeClient(SERVICE_TYPE, mockSocketClient, currentThreadExecutor,
-                mockDecoderClock, socketKey, mockSharedLog, thread.getLooper(), mockDeps,
-                serviceCache);
+        client = makeMdnsServiceTypeClient(/* packetWriter= */ null);
 
         final String instanceName = "service-instance";
         final String[] hostname = new String[] { "testhost "};
         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
@@ -1147,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(
@@ -1170,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();
@@ -1179,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(
@@ -1200,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,
@@ -1217,9 +1317,7 @@
 
     @Test
     public void testRenewTxtSrvInResolve() throws Exception {
-        client = new MdnsServiceTypeClient(SERVICE_TYPE, mockSocketClient, currentThreadExecutor,
-                mockDecoderClock, socketKey, mockSharedLog, thread.getLooper(), mockDeps,
-                serviceCache);
+        client = makeMdnsServiceTypeClient(/* packetWriter= */ null);
 
         final String instanceName = "service-instance";
         final String[] hostname = new String[] { "testhost "};
@@ -1333,9 +1431,7 @@
 
     @Test
     public void testProcessResponse_ResolveExcludesOtherServices() {
-        client = new MdnsServiceTypeClient(SERVICE_TYPE, mockSocketClient, currentThreadExecutor,
-                mockDecoderClock, socketKey, mockSharedLog, thread.getLooper(), mockDeps,
-                serviceCache);
+        client = makeMdnsServiceTypeClient(/* packetWriter= */ null);
 
         final String requestedInstance = "instance1";
         final String otherInstance = "instance2";
@@ -1403,9 +1499,7 @@
 
     @Test
     public void testProcessResponse_SubtypeDiscoveryLimitedToSubtype() {
-        client = new MdnsServiceTypeClient(SERVICE_TYPE, mockSocketClient, currentThreadExecutor,
-                mockDecoderClock, socketKey, mockSharedLog, thread.getLooper(), mockDeps,
-                serviceCache);
+        client = makeMdnsServiceTypeClient(/* packetWriter= */ null);
 
         final String matchingInstance = "instance1";
         final String subtype = "_subtype";
@@ -1492,10 +1586,91 @@
     }
 
     @Test
+    public void testProcessResponse_SubtypeChange() {
+        client = makeMdnsServiceTypeClient(/* packetWriter= */ null);
+
+        final String matchingInstance = "instance1";
+        final String subtype = "_subtype";
+        final String ipV4Address = "192.0.2.0";
+        final String ipV6Address = "2001:db8::";
+
+        final MdnsSearchOptions options = MdnsSearchOptions.newBuilder()
+                .addSubtype("othersub").build();
+
+        startSendAndReceive(mockListenerOne, options);
+
+        // Complete response from instanceName
+        final MdnsPacket packetWithoutSubtype = createResponse(
+                matchingInstance, ipV4Address, 5353, SERVICE_TYPE_LABELS,
+                Collections.emptyMap() /* textAttributes */, TEST_TTL);
+        final MdnsPointerRecord originalPtr = (MdnsPointerRecord) CollectionUtils.findFirst(
+                packetWithoutSubtype.answers, r -> r instanceof MdnsPointerRecord);
+
+        // Add a subtype PTR record
+        final ArrayList<MdnsRecord> newAnswers = new ArrayList<>(packetWithoutSubtype.answers);
+        newAnswers.add(new MdnsPointerRecord(
+                // PTR should be _subtype._sub._type._tcp.local -> instance1._type._tcp.local
+                Stream.concat(Stream.of(subtype, "_sub"), Arrays.stream(SERVICE_TYPE_LABELS))
+                        .toArray(String[]::new),
+                originalPtr.getReceiptTime(), originalPtr.getCacheFlush(), originalPtr.getTtl(),
+                originalPtr.getPointer()));
+        processResponse(new MdnsPacket(
+                packetWithoutSubtype.flags,
+                packetWithoutSubtype.questions,
+                newAnswers,
+                packetWithoutSubtype.authorityRecords,
+                packetWithoutSubtype.additionalRecords), socketKey);
+
+        // The subtype does not match
+        final InOrder inOrder = inOrder(mockListenerOne);
+        inOrder.verify(mockListenerOne, never()).onServiceNameDiscovered(any(), anyBoolean());
+
+        // Add another matching subtype
+        newAnswers.add(new MdnsPointerRecord(
+                // PTR should be _subtype._sub._type._tcp.local -> instance1._type._tcp.local
+                Stream.concat(Stream.of("_othersub", "_sub"), Arrays.stream(SERVICE_TYPE_LABELS))
+                        .toArray(String[]::new),
+                originalPtr.getReceiptTime(), originalPtr.getCacheFlush(), originalPtr.getTtl(),
+                originalPtr.getPointer()));
+        processResponse(new MdnsPacket(
+                packetWithoutSubtype.flags,
+                packetWithoutSubtype.questions,
+                newAnswers,
+                packetWithoutSubtype.authorityRecords,
+                packetWithoutSubtype.additionalRecords), socketKey);
+
+        final ArgumentMatcher<MdnsServiceInfo> subtypeInstanceMatcher = info ->
+                info.getServiceInstanceName().equals(matchingInstance)
+                        && info.getSubtypes().equals(List.of("_subtype", "_othersub"));
+
+        // Service found callbacks are sent now
+        inOrder.verify(mockListenerOne).onServiceNameDiscovered(
+                argThat(subtypeInstanceMatcher), eq(false) /* isServiceFromCache */);
+        inOrder.verify(mockListenerOne).onServiceFound(
+                argThat(subtypeInstanceMatcher), eq(false) /* isServiceFromCache */);
+
+        // Address update: update callbacks are sent
+        processResponse(createResponse(
+                matchingInstance, ipV6Address, 5353, SERVICE_TYPE_LABELS,
+                Collections.emptyMap(), TEST_TTL), socketKey);
+
+        inOrder.verify(mockListenerOne).onServiceUpdated(argThat(info ->
+                subtypeInstanceMatcher.matches(info)
+                        && info.getIpv4Addresses().equals(List.of(ipV4Address))
+                        && info.getIpv6Addresses().equals(List.of(ipV6Address))));
+
+        // Goodbye: service removed callbacks are sent
+        processResponse(createResponse(
+                matchingInstance, ipV6Address, 5353, SERVICE_TYPE_LABELS,
+                Collections.emptyMap(), 0L /* ttl */), socketKey);
+
+        inOrder.verify(mockListenerOne).onServiceRemoved(matchServiceName(matchingInstance));
+        inOrder.verify(mockListenerOne).onServiceNameRemoved(matchServiceName(matchingInstance));
+    }
+
+    @Test
     public void testNotifySocketDestroyed() throws Exception {
-        client = new MdnsServiceTypeClient(SERVICE_TYPE, mockSocketClient, currentThreadExecutor,
-                mockDecoderClock, socketKey, mockSharedLog, thread.getLooper(), mockDeps,
-                serviceCache);
+        client = makeMdnsServiceTypeClient(/* packetWriter= */ null);
 
         final String requestedInstance = "instance1";
         final String otherInstance = "instance2";
@@ -1666,6 +1841,111 @@
                 socketKey);
     }
 
+    @Test
+    public void sendQueries_aggressiveScanMode() {
+        final MdnsSearchOptions searchOptions = MdnsSearchOptions.newBuilder()
+                .addSubtype(SUBTYPE).setQueryMode(AGGRESSIVE_QUERY_MODE).build();
+        startSendAndReceive(mockListenerOne, searchOptions);
+        // Always try to remove the task.
+        verify(mockDeps, times(1)).removeMessages(any(), eq(EVENT_START_QUERYTASK));
+
+        int burstCounter = 0;
+        int betweenBurstTime = 0;
+        for (int i = 0; i < expectedIPv4Packets.length; i += 3) {
+            verifyAndSendQuery(i, betweenBurstTime, /* expectsUnicastResponse= */ true);
+            verifyAndSendQuery(i + 1, /* timeInMs= */ 0, /* expectsUnicastResponse= */ false);
+            verifyAndSendQuery(i + 2, TIME_BETWEEN_RETRANSMISSION_QUERIES_IN_BURST_MS,
+                    /* expectsUnicastResponse= */ false);
+            betweenBurstTime = Math.min(
+                    INITIAL_AGGRESSIVE_TIME_BETWEEN_BURSTS_MS * (int) Math.pow(2, burstCounter),
+                    MAX_TIME_BETWEEN_AGGRESSIVE_BURSTS_MS);
+            burstCounter++;
+        }
+        // Verify that Task is not removed before stopSendAndReceive was called.
+        verify(mockDeps, times(1)).removeMessages(any(), eq(EVENT_START_QUERYTASK));
+
+        // Stop sending packets.
+        stopSendAndReceive(mockListenerOne);
+        verify(mockDeps, times(2)).removeMessages(any(), eq(EVENT_START_QUERYTASK));
+    }
+
+    @Test
+    public void sendQueries_reentry_aggressiveScanMode() {
+        final MdnsSearchOptions searchOptions = MdnsSearchOptions.newBuilder()
+                .addSubtype(SUBTYPE).setQueryMode(AGGRESSIVE_QUERY_MODE).build();
+        startSendAndReceive(mockListenerOne, searchOptions);
+        // Always try to remove the task.
+        verify(mockDeps, times(1)).removeMessages(any(), eq(EVENT_START_QUERYTASK));
+
+        // First burst, first query is sent.
+        verifyAndSendQuery(0, /* timeInMs= */ 0, /* expectsUnicastResponse= */ true);
+
+        // After the first query is sent, change the subtypes, and restart.
+        final MdnsSearchOptions searchOptions2 = MdnsSearchOptions.newBuilder().addSubtype(SUBTYPE)
+                .addSubtype("_subtype2").setQueryMode(AGGRESSIVE_QUERY_MODE).build();
+        startSendAndReceive(mockListenerOne, searchOptions2);
+        // The previous scheduled task should be canceled.
+        verify(mockDeps, times(2)).removeMessages(any(), eq(EVENT_START_QUERYTASK));
+
+        // Queries should continue to be sent.
+        verifyAndSendQuery(1, /* timeInMs= */ 0, /* expectsUnicastResponse= */ true);
+        verifyAndSendQuery(2, /* timeInMs= */ 0, /* expectsUnicastResponse= */ false);
+        verifyAndSendQuery(3, TIME_BETWEEN_RETRANSMISSION_QUERIES_IN_BURST_MS,
+                /* expectsUnicastResponse= */ false);
+
+        // Stop sending packets.
+        stopSendAndReceive(mockListenerOne);
+        verify(mockDeps, times(3)).removeMessages(any(), eq(EVENT_START_QUERYTASK));
+    }
+
+    @Test
+    public void sendQueries_blendScanWithQueryBackoff() {
+        final int numOfQueriesBeforeBackoff = 11;
+        final MdnsSearchOptions searchOptions = MdnsSearchOptions.newBuilder()
+                .addSubtype(SUBTYPE)
+                .setQueryMode(AGGRESSIVE_QUERY_MODE)
+                .setNumOfQueriesBeforeBackoff(numOfQueriesBeforeBackoff)
+                .build();
+        startSendAndReceive(mockListenerOne, searchOptions);
+        // Always try to remove the task.
+        verify(mockDeps, times(1)).removeMessages(any(), eq(EVENT_START_QUERYTASK));
+
+        int burstCounter = 0;
+        int betweenBurstTime = 0;
+        for (int i = 0; i < numOfQueriesBeforeBackoff; i += 3) {
+            verifyAndSendQuery(i, betweenBurstTime, /* expectsUnicastResponse= */ true);
+            verifyAndSendQuery(i + 1, /* timeInMs= */ 0, /* expectsUnicastResponse= */ false);
+            verifyAndSendQuery(i + 2, TIME_BETWEEN_RETRANSMISSION_QUERIES_IN_BURST_MS,
+                    /* expectsUnicastResponse= */ false);
+            betweenBurstTime = Math.min(
+                    INITIAL_AGGRESSIVE_TIME_BETWEEN_BURSTS_MS * (int) Math.pow(2, burstCounter),
+                    MAX_TIME_BETWEEN_AGGRESSIVE_BURSTS_MS);
+            burstCounter++;
+        }
+        // In backoff mode, the current scheduled task will be canceled and reschedule if the
+        // 0.8 * smallestRemainingTtl is larger than time to next run.
+        long currentTime = TEST_TTL / 2 + TEST_ELAPSED_REALTIME;
+        doReturn(currentTime).when(mockDecoderClock).elapsedRealtime();
+        doReturn(true).when(mockDeps).hasMessages(any(), eq(EVENT_START_QUERYTASK));
+        processResponse(createResponse(
+                "service-instance-1", "192.0.2.123", 5353,
+                SERVICE_TYPE_LABELS,
+                Collections.emptyMap(), TEST_TTL), socketKey);
+        verify(mockDeps, times(2)).removeMessages(any(), eq(EVENT_START_QUERYTASK));
+        assertNotNull(delayMessage);
+        verifyAndSendQuery(12 /* index */, (long) (TEST_TTL / 2 * 0.8) /* timeInMs */,
+                true /* expectsUnicastResponse */, true /* multipleSocketDiscovery */,
+                14 /* scheduledCount */);
+        currentTime += (long) (TEST_TTL / 2 * 0.8);
+        doReturn(currentTime).when(mockDecoderClock).elapsedRealtime();
+        verifyAndSendQuery(13 /* index */, 0 /* timeInMs */,
+                false /* expectsUnicastResponse */, true /* multipleSocketDiscovery */,
+                15 /* scheduledCount */);
+        verifyAndSendQuery(14 /* index */, TIME_BETWEEN_RETRANSMISSION_QUERIES_IN_BURST_MS,
+                false /* expectsUnicastResponse */, true /* multipleSocketDiscovery */,
+                16 /* scheduledCount */);
+    }
+
     private static MdnsServiceInfo matchServiceName(String name) {
         return argThat(info -> info.getServiceInstanceName().equals(name));
     }
@@ -1712,6 +1992,11 @@
                 Arrays.stream(SERVICE_TYPE_LABELS)).toArray(String[]::new);
     }
 
+    private static String[] getServiceTypeWithSubtype(String subtype) {
+        return Stream.concat(Stream.of(subtype, "_sub"),
+                Arrays.stream(SERVICE_TYPE_LABELS)).toArray(String[]::new);
+    }
+
     private static boolean hasQuestion(MdnsPacket packet, int type) {
         return hasQuestion(packet, type, null);
     }
diff --git a/tests/unit/java/com/android/server/connectivityservice/CSCaptivePortalAppTest.kt b/tests/unit/java/com/android/server/connectivityservice/CSCaptivePortalAppTest.kt
new file mode 100644
index 0000000..be2b29c
--- /dev/null
+++ b/tests/unit/java/com/android/server/connectivityservice/CSCaptivePortalAppTest.kt
@@ -0,0 +1,127 @@
+/*
+ * 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.
+ */
+
+package com.android.server
+
+import android.Manifest.permission.NETWORK_STACK
+import android.content.Intent
+import android.content.pm.PackageManager.PERMISSION_DENIED
+import android.content.pm.PackageManager.PERMISSION_GRANTED
+import android.net.ConnectivityManager.ACTION_CAPTIVE_PORTAL_SIGN_IN
+import android.net.ConnectivityManager.EXTRA_CAPTIVE_PORTAL
+import android.net.IpPrefix
+import android.net.LinkAddress
+import android.net.LinkProperties
+import android.net.NetworkCapabilities
+import android.net.NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL
+import android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET
+import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED
+import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING
+import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED
+import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED
+import android.net.NetworkCapabilities.TRANSPORT_WIFI
+import android.net.NetworkStack
+import android.net.CaptivePortal
+import android.net.NetworkRequest
+import android.net.NetworkScore
+import android.net.NetworkScore.KEEP_CONNECTED_FOR_TEST
+import android.net.RouteInfo
+import android.os.Build
+import android.os.Bundle
+import androidx.test.filters.SmallTest
+import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
+import com.android.testutils.DevSdkIgnoreRunner
+import com.android.testutils.assertThrows
+import com.android.testutils.TestableNetworkCallback
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.verify
+import kotlin.test.assertEquals
+
+// This allows keeping all the networks connected without having to file individual requests
+// for them.
+private fun keepScore() = FromS(
+        NetworkScore.Builder().setKeepConnectedReason(KEEP_CONNECTED_FOR_TEST).build()
+)
+
+private fun nc(transport: Int, vararg caps: Int) = NetworkCapabilities.Builder().apply {
+    addTransportType(transport)
+    caps.forEach {
+        addCapability(it)
+    }
+    // Useful capabilities for everybody
+    addCapability(NET_CAPABILITY_NOT_RESTRICTED)
+    addCapability(NET_CAPABILITY_NOT_SUSPENDED)
+    addCapability(NET_CAPABILITY_NOT_ROAMING)
+    addCapability(NET_CAPABILITY_NOT_VCN_MANAGED)
+}.build()
+
+private fun lp(iface: String) = LinkProperties().apply {
+    interfaceName = iface
+    addLinkAddress(LinkAddress(LOCAL_IPV4_ADDRESS, 32))
+    addRoute(RouteInfo(IpPrefix("0.0.0.0/0"), null, null))
+}
+
+@DevSdkIgnoreRunner.MonitorThreadLeak
+@RunWith(DevSdkIgnoreRunner::class)
+@SmallTest
+@IgnoreUpTo(Build.VERSION_CODES.R)
+class CSCaptivePortalAppTest : CSTest() {
+    private val WIFI_IFACE = "wifi0"
+    private val TEST_REDIRECT_URL = "http://example.com/firstPath"
+    private val TIMEOUT_MS = 2_000L
+
+    @Test
+    fun testCaptivePortalApp_Reevaluate_Nopermission() {
+        val captivePortalCallback = TestableNetworkCallback()
+        val captivePortalRequest = NetworkRequest.Builder()
+                .addCapability(NET_CAPABILITY_CAPTIVE_PORTAL).build()
+        cm.registerNetworkCallback(captivePortalRequest, captivePortalCallback)
+        val wifiAgent = createWifiAgent()
+        wifiAgent.connectWithCaptivePortal(TEST_REDIRECT_URL)
+        captivePortalCallback.expectAvailableCallbacksUnvalidated(wifiAgent)
+        val signInIntent = startCaptivePortalApp(wifiAgent)
+        // Remove the granted permissions
+        context.setPermission(NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK,
+                PERMISSION_DENIED)
+        context.setPermission(NETWORK_STACK, PERMISSION_DENIED)
+        val captivePortal: CaptivePortal? = signInIntent.getParcelableExtra(EXTRA_CAPTIVE_PORTAL)
+        assertThrows(SecurityException::class.java, { captivePortal?.reevaluateNetwork() })
+    }
+
+    private fun createWifiAgent(): CSAgentWrapper {
+        return Agent(score = keepScore(), lp = lp(WIFI_IFACE),
+                nc = nc(TRANSPORT_WIFI, NET_CAPABILITY_INTERNET))
+    }
+
+    private fun startCaptivePortalApp(networkAgent: CSAgentWrapper): Intent {
+        val network = networkAgent.network
+        cm.startCaptivePortalApp(network)
+        waitForIdle()
+        verify(networkAgent.networkMonitor).launchCaptivePortalApp()
+
+        val testBundle = Bundle()
+        val testKey = "testkey"
+        val testValue = "testvalue"
+        testBundle.putString(testKey, testValue)
+        context.setPermission(NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, PERMISSION_GRANTED)
+        cm.startCaptivePortalApp(network, testBundle)
+        val signInIntent: Intent = context.expectStartActivityIntent(TIMEOUT_MS)
+        assertEquals(ACTION_CAPTIVE_PORTAL_SIGN_IN, signInIntent.getAction())
+        assertEquals(testValue, signInIntent.getStringExtra(testKey))
+        return signInIntent
+    }
+}
diff --git a/tests/unit/java/com/android/server/connectivityservice/CSSatelliteNetworkFallbackTest.kt b/tests/unit/java/com/android/server/connectivityservice/CSSatelliteNetworkFallbackTest.kt
new file mode 100644
index 0000000..9024641
--- /dev/null
+++ b/tests/unit/java/com/android/server/connectivityservice/CSSatelliteNetworkFallbackTest.kt
@@ -0,0 +1,202 @@
+/*
+ * Copyright (C) 2023 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
+
+import android.net.IpPrefix
+import android.net.INetd
+import android.net.LinkAddress
+import android.net.LinkProperties
+import android.net.NativeNetworkConfig
+import android.net.NativeNetworkType
+import android.net.NetworkCapabilities
+import android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET
+import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED
+import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING
+import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED
+import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED
+import android.net.NetworkScore
+import android.net.NetworkCapabilities.TRANSPORT_SATELLITE
+import android.net.NetworkScore.KEEP_CONNECTED_FOR_TEST
+import android.net.RouteInfo
+import android.net.UidRange
+import android.net.UidRangeParcel
+import android.net.VpnManager
+import android.net.netd.aidl.NativeUidRangeConfig
+import android.os.Build
+import android.os.UserHandle
+import android.util.ArraySet
+import com.android.net.module.util.CollectionUtils
+import com.android.server.ConnectivityService.PREFERENCE_ORDER_SATELLITE_FALLBACK
+import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
+import com.android.testutils.DevSdkIgnoreRunner
+import com.android.testutils.visibleOnHandlerThread
+import org.junit.Assert
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.any
+import org.mockito.Mockito.inOrder
+import org.mockito.Mockito.never
+import kotlin.test.assertEquals
+import kotlin.test.assertTrue
+
+private const val SECONDARY_USER = 10
+private val SECONDARY_USER_HANDLE = UserHandle(SECONDARY_USER)
+private const val TEST_PACKAGE_UID = 123
+private const val TEST_PACKAGE_UID2 = 321
+
+@DevSdkIgnoreRunner.MonitorThreadLeak
+@RunWith(DevSdkIgnoreRunner::class)
+@IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
+class CSSatelliteNetworkPreferredTest : CSTest() {
+    /**
+     * Test createMultiLayerNrisFromSatelliteNetworkPreferredUids returns correct
+     * NetworkRequestInfo.
+     */
+    @Test
+    fun testCreateMultiLayerNrisFromSatelliteNetworkPreferredUids() {
+        // Verify that empty uid set should not create any NRI for it.
+        val nrisNoUid = service.createMultiLayerNrisFromSatelliteNetworkFallbackUids(emptySet())
+        Assert.assertEquals(0, nrisNoUid.size.toLong())
+        val uid1 = PRIMARY_USER_HANDLE.getUid(TEST_PACKAGE_UID)
+        val uid2 = PRIMARY_USER_HANDLE.getUid(TEST_PACKAGE_UID2)
+        val uid3 = SECONDARY_USER_HANDLE.getUid(TEST_PACKAGE_UID)
+        assertCreateMultiLayerNrisFromSatelliteNetworkPreferredUids(mutableSetOf(uid1))
+        assertCreateMultiLayerNrisFromSatelliteNetworkPreferredUids(mutableSetOf(uid1, uid3))
+        assertCreateMultiLayerNrisFromSatelliteNetworkPreferredUids(mutableSetOf(uid1, uid2))
+    }
+
+    /**
+     * Test that SATELLITE_NETWORK_PREFERENCE_UIDS changes will send correct net id and uid ranges
+     * to netd.
+     */
+    @Test
+    fun testSatelliteNetworkPreferredUidsChanged() {
+        val netdInOrder = inOrder(netd)
+
+        val satelliteAgent = createSatelliteAgent("satellite0")
+        satelliteAgent.connect()
+
+        val satelliteNetId = satelliteAgent.network.netId
+        netdInOrder.verify(netd).networkCreate(
+            nativeNetworkConfigPhysical(satelliteNetId, INetd.PERMISSION_NONE))
+
+        val uid1 = PRIMARY_USER_HANDLE.getUid(TEST_PACKAGE_UID)
+        val uid2 = PRIMARY_USER_HANDLE.getUid(TEST_PACKAGE_UID2)
+        val uid3 = SECONDARY_USER_HANDLE.getUid(TEST_PACKAGE_UID)
+
+        // Initial satellite network preferred uids status.
+        setAndUpdateSatelliteNetworkPreferredUids(setOf())
+        netdInOrder.verify(netd, never()).networkAddUidRangesParcel(any())
+        netdInOrder.verify(netd, never()).networkRemoveUidRangesParcel(any())
+
+        // Set SATELLITE_NETWORK_PREFERENCE_UIDS setting and verify that net id and uid ranges
+        // send to netd
+        var uids = mutableSetOf(uid1, uid2, uid3)
+        val uidRanges1 = toUidRangeStableParcels(uidRangesForUids(uids))
+        val config1 = NativeUidRangeConfig(
+            satelliteNetId, uidRanges1,
+            PREFERENCE_ORDER_SATELLITE_FALLBACK
+        )
+        setAndUpdateSatelliteNetworkPreferredUids(uids)
+        netdInOrder.verify(netd).networkAddUidRangesParcel(config1)
+        netdInOrder.verify(netd, never()).networkRemoveUidRangesParcel(any())
+
+        // Set SATELLITE_NETWORK_PREFERENCE_UIDS setting again and verify that old rules are removed
+        // and new rules are added.
+        uids = mutableSetOf(uid1)
+        val uidRanges2: Array<UidRangeParcel?> = toUidRangeStableParcels(uidRangesForUids(uids))
+        val config2 = NativeUidRangeConfig(
+            satelliteNetId, uidRanges2,
+            PREFERENCE_ORDER_SATELLITE_FALLBACK
+        )
+        setAndUpdateSatelliteNetworkPreferredUids(uids)
+        netdInOrder.verify(netd).networkRemoveUidRangesParcel(config1)
+        netdInOrder.verify(netd).networkAddUidRangesParcel(config2)
+    }
+
+    private fun assertCreateMultiLayerNrisFromSatelliteNetworkPreferredUids(uids: Set<Int>) {
+        val nris: Set<ConnectivityService.NetworkRequestInfo> =
+            service.createMultiLayerNrisFromSatelliteNetworkFallbackUids(uids)
+        val nri = nris.iterator().next()
+        // Verify that one NRI is created with multilayer requests. Because one NRI can contain
+        // multiple uid ranges, so it only need create one NRI here.
+        assertEquals(1, nris.size.toLong())
+        assertTrue(nri.isMultilayerRequest)
+        assertEquals(nri.uids, uidRangesForUids(uids))
+        assertEquals(PREFERENCE_ORDER_SATELLITE_FALLBACK, nri.mPreferenceOrder)
+    }
+
+    private fun setAndUpdateSatelliteNetworkPreferredUids(uids: Set<Int>) {
+        visibleOnHandlerThread(csHandler) {
+            deps.satelliteNetworkFallbackUidUpdate!!.accept(uids)
+        }
+    }
+
+    private fun nativeNetworkConfigPhysical(netId: Int, permission: Int) =
+        NativeNetworkConfig(netId, NativeNetworkType.PHYSICAL, permission,
+            false /* secure */, VpnManager.TYPE_VPN_NONE, false /* excludeLocalRoutes */)
+
+    private fun createSatelliteAgent(name: String): CSAgentWrapper {
+        return Agent(score = keepScore(), lp = lp(name),
+            nc = nc(TRANSPORT_SATELLITE, NET_CAPABILITY_INTERNET)
+        )
+    }
+
+    private fun toUidRangeStableParcels(ranges: Set<UidRange>): Array<UidRangeParcel?> {
+        val stableRanges = arrayOfNulls<UidRangeParcel>(ranges.size)
+        for ((index, range) in ranges.withIndex()) {
+            stableRanges[index] = UidRangeParcel(range.start, range.stop)
+        }
+        return stableRanges
+    }
+
+    private fun uidRangesForUids(vararg uids: Int): Set<UidRange> {
+        val ranges = ArraySet<UidRange>()
+        for (uid in uids) {
+            ranges.add(UidRange(uid, uid))
+        }
+        return ranges
+    }
+
+    private fun uidRangesForUids(uids: Collection<Int>): Set<UidRange> {
+        return uidRangesForUids(*CollectionUtils.toIntArray(uids))
+    }
+
+    private fun nc(transport: Int, vararg caps: Int) = NetworkCapabilities.Builder().apply {
+        addTransportType(transport)
+        caps.forEach {
+            addCapability(it)
+        }
+        // Useful capabilities for everybody
+        addCapability(NET_CAPABILITY_NOT_RESTRICTED)
+        addCapability(NET_CAPABILITY_NOT_SUSPENDED)
+        addCapability(NET_CAPABILITY_NOT_ROAMING)
+        addCapability(NET_CAPABILITY_NOT_VCN_MANAGED)
+    }.build()
+
+    private fun lp(iface: String) = LinkProperties().apply {
+        interfaceName = iface
+        addLinkAddress(LinkAddress(LOCAL_IPV4_ADDRESS, 32))
+        addRoute(RouteInfo(IpPrefix("0.0.0.0/0"), null, null))
+    }
+
+    // This allows keeping all the networks connected without having to file individual requests
+    // for them.
+    private fun keepScore() = FromS(
+        NetworkScore.Builder().setKeepConnectedReason(KEEP_CONNECTED_FOR_TEST).build()
+    )
+}
diff --git a/tests/unit/java/com/android/server/connectivityservice/base/CSAgentWrapper.kt b/tests/unit/java/com/android/server/connectivityservice/base/CSAgentWrapper.kt
index d41c742..d7343b1 100644
--- a/tests/unit/java/com/android/server/connectivityservice/base/CSAgentWrapper.kt
+++ b/tests/unit/java/com/android/server/connectivityservice/base/CSAgentWrapper.kt
@@ -19,6 +19,8 @@
 import android.content.Context
 import android.net.ConnectivityManager
 import android.net.INetworkMonitor
+import android.net.INetworkMonitor.NETWORK_VALIDATION_PROBE_DNS
+import android.net.INetworkMonitor.NETWORK_VALIDATION_PROBE_HTTP
 import android.net.INetworkMonitorCallbacks
 import android.net.LinkProperties
 import android.net.LocalNetworkConfig
@@ -75,10 +77,15 @@
 ) : TestableNetworkCallback.HasNetwork {
     private val TAG = "CSAgent${nextAgentId()}"
     private val VALIDATION_RESULT_INVALID = 0
+    private val NO_PROBE_RESULT = 0
     private val VALIDATION_TIMESTAMP = 1234L
     private val agent: NetworkAgent
     private val nmCallbacks: INetworkMonitorCallbacks
     val networkMonitor = mock<INetworkMonitor>()
+    private var nmValidationRedirectUrl: String? = null
+    private var nmValidationResult = NO_PROBE_RESULT
+    private var nmProbesCompleted = NO_PROBE_RESULT
+    private var nmProbesSucceeded = NO_PROBE_RESULT
 
     override val network: Network get() = agent.network!!
 
@@ -120,10 +127,10 @@
         }
         nmCallbacks.notifyProbeStatusChanged(0 /* completed */, 0 /* succeeded */)
         val p = NetworkTestResultParcelable()
-        p.result = VALIDATION_RESULT_INVALID
-        p.probesAttempted = 0
-        p.probesSucceeded = 0
-        p.redirectUrl = null
+        p.result = nmValidationResult
+        p.probesAttempted = nmProbesCompleted
+        p.probesSucceeded = nmProbesSucceeded
+        p.redirectUrl = nmValidationRedirectUrl
         p.timestampMillis = VALIDATION_TIMESTAMP
         nmCallbacks.notifyNetworkTestedWithExtras(p)
     }
@@ -171,4 +178,26 @@
 
     fun sendLocalNetworkConfig(lnc: LocalNetworkConfig) = agent.sendLocalNetworkConfig(lnc)
     fun sendNetworkCapabilities(nc: NetworkCapabilities) = agent.sendNetworkCapabilities(nc)
+
+    fun connectWithCaptivePortal(redirectUrl: String) {
+        setCaptivePortal(redirectUrl)
+        connect()
+    }
+
+    fun setProbesStatus(probesCompleted: Int, probesSucceeded: Int) {
+        nmProbesCompleted = probesCompleted
+        nmProbesSucceeded = probesSucceeded
+    }
+
+    fun setCaptivePortal(redirectUrl: String) {
+        nmValidationResult = VALIDATION_RESULT_INVALID
+        nmValidationRedirectUrl = redirectUrl
+        // Suppose the portal is found when NetworkMonitor probes NETWORK_VALIDATION_PROBE_HTTP
+        // in the beginning. Because NETWORK_VALIDATION_PROBE_HTTP is the decisive probe for captive
+        // portal, considering the NETWORK_VALIDATION_PROBE_HTTPS hasn't probed yet and set only
+        // DNS and HTTP probes completed.
+        setProbesStatus(
+            NETWORK_VALIDATION_PROBE_DNS or NETWORK_VALIDATION_PROBE_HTTP /* probesCompleted */,
+            VALIDATION_RESULT_INVALID /* probesSucceeded */)
+    }
 }
diff --git a/tests/unit/java/com/android/server/connectivityservice/base/CSTest.kt b/tests/unit/java/com/android/server/connectivityservice/base/CSTest.kt
index 0708669..3b83c41 100644
--- a/tests/unit/java/com/android/server/connectivityservice/base/CSTest.kt
+++ b/tests/unit/java/com/android/server/connectivityservice/base/CSTest.kt
@@ -16,6 +16,7 @@
 
 package com.android.server
 
+import android.app.AlarmManager
 import android.content.BroadcastReceiver
 import android.content.Context
 import android.content.Intent
@@ -46,8 +47,10 @@
 import android.os.Bundle
 import android.os.Handler
 import android.os.HandlerThread
+import android.os.Process
 import android.os.UserHandle
 import android.os.UserManager
+import android.permission.PermissionManager.PermissionResult
 import android.telephony.TelephonyManager
 import android.testing.TestableContext
 import android.util.ArraySet
@@ -66,19 +69,25 @@
 import com.android.server.connectivity.MultinetworkPolicyTrackerTestDependencies
 import com.android.server.connectivity.NetworkRequestStateStatsMetrics
 import com.android.server.connectivity.ProxyTracker
-import com.android.server.connectivity.RoutingCoordinatorService
+import com.android.server.connectivity.SatelliteAccessController
 import com.android.testutils.visibleOnHandlerThread
 import com.android.testutils.waitForIdle
 import java.util.concurrent.Executors
+import java.util.concurrent.LinkedBlockingQueue
+import java.util.concurrent.TimeUnit
+import java.util.function.Consumer
+import java.util.function.BiConsumer
+import kotlin.test.assertNotNull
 import kotlin.test.assertNull
 import kotlin.test.fail
 import org.junit.After
+import org.junit.Before
 import org.mockito.AdditionalAnswers.delegatesTo
 import org.mockito.Mockito.doAnswer
 import org.mockito.Mockito.doReturn
 import org.mockito.Mockito.mock
 
-internal const val HANDLER_TIMEOUT_MS = 2_000
+internal const val HANDLER_TIMEOUT_MS = 2_000L
 internal const val BROADCAST_TIMEOUT_MS = 3_000L
 internal const val TEST_PACKAGE_NAME = "com.android.test.package"
 internal const val WIFI_WOL_IFNAME = "test_wlan_wol"
@@ -133,10 +142,11 @@
     // permissions using static contexts.
     val enabledFeatures = HashMap<String, Boolean>().also {
         it[ConnectivityFlags.NO_REMATCH_ALL_REQUESTS_ON_REGISTER] = true
+        it[ConnectivityFlags.REQUEST_RESTRICTED_WIFI] = true
         it[ConnectivityService.KEY_DESTROY_FROZEN_SOCKETS_VERSION] = true
         it[ConnectivityService.DELAY_DESTROY_FROZEN_SOCKETS_VERSION] = true
         it[ConnectivityService.ALLOW_SYSUI_CONNECTIVITY_REPORTS] = true
-        it[ConnectivityService.LOG_BPF_RC] = true
+        it[ConnectivityService.ALLOW_SATALLITE_NETWORK_FALLBACK] = true
     }
     fun enableFeature(f: String) = enabledFeatures.set(f, true)
     fun disableFeature(f: String) = enabledFeatures.set(f, false)
@@ -163,8 +173,6 @@
     val clatCoordinator = mock<ClatCoordinator>()
     val networkRequestStateStatsMetrics = mock<NetworkRequestStateStatsMetrics>()
     val proxyTracker = ProxyTracker(context, mock<Handler>(), 16 /* EVENT_PROXY_HAS_CHANGED */)
-    val alrmHandlerThread = HandlerThread("TestAlarmManager").also { it.start() }
-    val alarmManager = makeMockAlarmManager(alrmHandlerThread)
     val systemConfigManager = makeMockSystemConfigManager()
     val batteryStats = mock<IBatteryStats>()
     val batteryManager = BatteryStatsManager(batteryStats)
@@ -173,18 +181,34 @@
     }
 
     val multicastRoutingCoordinatorService = mock<MulticastRoutingCoordinatorService>()
+    val satelliteAccessController = mock<SatelliteAccessController>()
 
     val deps = CSDeps()
-    val service = makeConnectivityService(context, netd, deps).also { it.systemReadyInternal() }
-    val cm = ConnectivityManager(context, service)
-    val csHandler = Handler(csHandlerThread.looper)
+
+    // Initializations that start threads are done from setUp to avoid thread leak
+    lateinit var alarmHandlerThread: HandlerThread
+    lateinit var alarmManager: AlarmManager
+    lateinit var service: ConnectivityService
+    lateinit var cm: ConnectivityManager
+    lateinit var csHandler: Handler
+
+    @Before
+    fun setUp() {
+        alarmHandlerThread = HandlerThread("TestAlarmManager").also { it.start() }
+        alarmManager = makeMockAlarmManager(alarmHandlerThread)
+        service = makeConnectivityService(context, netd, deps).also { it.systemReadyInternal() }
+        cm = ConnectivityManager(context, service)
+        // csHandler initialization must be after makeConnectivityService since ConnectivityService
+        // constructor starts csHandlerThread
+        csHandler = Handler(csHandlerThread.looper)
+    }
 
     @After
     fun tearDown() {
         csHandlerThread.quitSafely()
         csHandlerThread.join()
-        alrmHandlerThread.quitSafely()
-        alrmHandlerThread.join()
+        alarmHandlerThread.quitSafely()
+        alarmHandlerThread.join()
     }
 
     inner class CSDeps : ConnectivityService.Dependencies() {
@@ -200,9 +224,21 @@
 
         override fun makeCarrierPrivilegeAuthenticator(
                 context: Context,
-                tm: TelephonyManager
+                tm: TelephonyManager,
+                requestRestrictedWifiEnabled: Boolean,
+                listener: BiConsumer<Int, Int>
         ) = if (SdkLevel.isAtLeastT()) mock<CarrierPrivilegeAuthenticator>() else null
 
+        var satelliteNetworkFallbackUidUpdate: Consumer<Set<Int>>? = null
+        override fun makeSatelliteAccessController(
+            context: Context,
+            updateSatelliteNetworkFallackUid: Consumer<Set<Int>>?,
+            csHandlerThread: Handler
+        ): SatelliteAccessController? {
+            satelliteNetworkFallbackUidUpdate = updateSatelliteNetworkFallackUid
+            return satelliteAccessController
+        }
+
         private inner class AOOKTDeps(c: Context) : AutomaticOnOffKeepaliveTracker.Dependencies(c) {
             override fun isTetheringFeatureNotChickenedOut(name: String): Boolean {
                 return isFeatureEnabled(context, name)
@@ -268,13 +304,65 @@
         val pacProxyManager = mock<PacProxyManager>()
         val networkPolicyManager = mock<NetworkPolicyManager>()
 
+        // Map of permission name -> PermissionManager.Permission_{GRANTED|DENIED} constant
+        // For permissions granted across the board, the key is only the permission name.
+        // For permissions only granted to a combination of uid/pid, the key
+        // is "<permission name>,<pid>,<uid>". PID+UID permissions have priority over generic ones.
+        private val mMockedPermissions: HashMap<String, Int> = HashMap()
+        private val mStartedActivities = LinkedBlockingQueue<Intent>()
         override fun getPackageManager() = this@CSTest.packageManager
         override fun getContentResolver() = this@CSTest.contentResolver
 
-        // TODO : buff up the capabilities of this permission scheme to allow checking for
-        // permission rejections
-        override fun checkPermission(permission: String, pid: Int, uid: Int) = PERMISSION_GRANTED
-        override fun checkCallingOrSelfPermission(permission: String) = PERMISSION_GRANTED
+        // If the permission result does not set in the mMockedPermissions, it will be
+        // considered as PERMISSION_GRANTED as existing design to prevent breaking other tests.
+        override fun checkPermission(permission: String, pid: Int, uid: Int) =
+            checkMockedPermission(permission, pid, uid, PERMISSION_GRANTED)
+
+        override fun enforceCallingOrSelfPermission(permission: String, message: String?) {
+            // If the permission result does not set in the mMockedPermissions, it will be
+            // considered as PERMISSION_GRANTED as existing design to prevent breaking other tests.
+            val granted = checkMockedPermission(permission, Process.myPid(), Process.myUid(),
+                PERMISSION_GRANTED)
+            if (!granted.equals(PERMISSION_GRANTED)) {
+                throw SecurityException("[Test] permission denied: " + permission)
+            }
+        }
+
+        // If the permission result does not set in the mMockedPermissions, it will be
+        // considered as PERMISSION_GRANTED as existing design to prevent breaking other tests.
+        override fun checkCallingOrSelfPermission(permission: String) =
+            checkMockedPermission(permission, Process.myPid(), Process.myUid(), PERMISSION_GRANTED)
+
+        private fun checkMockedPermission(permission: String, pid: Int, uid: Int, default: Int):
+                Int {
+            val processSpecificKey = "$permission,$pid,$uid"
+            return mMockedPermissions[processSpecificKey]
+                    ?: mMockedPermissions[permission] ?: default
+        }
+
+        /**
+         * Mock checks for the specified permission, and have them behave as per `granted` or
+         * `denied`.
+         *
+         * This will apply to all calls no matter what the checked UID and PID are.
+         *
+         * @param granted One of {@link PackageManager#PermissionResult}.
+         */
+        fun setPermission(permission: String, @PermissionResult granted: Int) {
+            mMockedPermissions.put(permission, granted)
+        }
+
+        /**
+         * Mock checks for the specified permission, and have them behave as per `granted` or
+         * `denied`.
+         *
+         * This will only apply to the passed UID and PID.
+         *
+         * @param granted One of {@link PackageManager#PermissionResult}.
+         */
+        fun setPermission(permission: String, pid: Int, uid: Int, @PermissionResult granted: Int) {
+            mMockedPermissions.put("$permission,$pid,$uid", granted)
+        }
 
         // Necessary for MultinetworkPolicyTracker, which tries to register a receiver for
         // all users. The test can't do that since it doesn't hold INTERACT_ACROSS_USERS.
@@ -332,6 +420,16 @@
         ) {
             orderedBroadcastAsUserHistory.add(intent)
         }
+
+        override fun startActivityAsUser(intent: Intent, handle: UserHandle) {
+            mStartedActivities.put(intent)
+        }
+
+        fun expectStartActivityIntent(timeoutMs: Long = HANDLER_TIMEOUT_MS): Intent {
+            val intent = mStartedActivities.poll(timeoutMs, TimeUnit.MILLISECONDS)
+            assertNotNull(intent, "Did not receive sign-in intent after " + timeoutMs + "ms")
+            return intent
+        }
     }
 
     // Utility methods for subclasses to use
diff --git a/tests/unit/java/com/android/server/net/BpfInterfaceMapHelperTest.java b/tests/unit/java/com/android/server/net/BpfInterfaceMapHelperTest.java
new file mode 100644
index 0000000..7b3bea3
--- /dev/null
+++ b/tests/unit/java/com/android/server/net/BpfInterfaceMapHelperTest.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2022 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.net;
+
+import static android.system.OsConstants.EPERM;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.spy;
+
+import android.os.Build;
+import android.system.ErrnoException;
+import android.util.IndentingPrintWriter;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.net.module.util.BaseNetdUnsolicitedEventListener;
+import com.android.net.module.util.IBpfMap;
+import com.android.net.module.util.Struct.S32;
+import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRunner;
+import com.android.testutils.TestBpfMap;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.MockitoAnnotations;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+
+@SmallTest
+@RunWith(DevSdkIgnoreRunner.class)
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.S_V2)
+public final class BpfInterfaceMapHelperTest {
+    private static final int TEST_INDEX = 1;
+    private static final int TEST_INDEX2 = 2;
+    private static final String TEST_INTERFACE_NAME = "test1";
+    private static final String TEST_INTERFACE_NAME2 = "test2";
+
+    private BaseNetdUnsolicitedEventListener mListener;
+    private BpfInterfaceMapHelper mUpdater;
+    private IBpfMap<S32, InterfaceMapValue> mBpfMap =
+            spy(new TestBpfMap<>(S32.class, InterfaceMapValue.class));
+
+    private class TestDependencies extends BpfInterfaceMapHelper.Dependencies {
+        @Override
+        public IBpfMap<S32, InterfaceMapValue> getInterfaceMap() {
+            return mBpfMap;
+        }
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        mUpdater = new BpfInterfaceMapHelper(new TestDependencies());
+    }
+
+    @Test
+    public void testGetIfNameByIndex() throws Exception {
+        mBpfMap.updateEntry(new S32(TEST_INDEX), new InterfaceMapValue(TEST_INTERFACE_NAME));
+        assertEquals(TEST_INTERFACE_NAME, mUpdater.getIfNameByIndex(TEST_INDEX));
+    }
+
+    @Test
+    public void testGetIfNameByIndexNoEntry() {
+        assertNull(mUpdater.getIfNameByIndex(TEST_INDEX));
+    }
+
+    @Test
+    public void testGetIfNameByIndexException() throws Exception {
+        doThrow(new ErrnoException("", EPERM)).when(mBpfMap).getValue(new S32(TEST_INDEX));
+        assertNull(mUpdater.getIfNameByIndex(TEST_INDEX));
+    }
+
+    private void assertDumpContains(final String dump, final String message) {
+        assertTrue(String.format("dump(%s) does not contain '%s'", dump, message),
+                dump.contains(message));
+    }
+
+    private String getDump() {
+        final StringWriter sw = new StringWriter();
+        mUpdater.dump(new IndentingPrintWriter(new PrintWriter(sw), " "));
+        return sw.toString();
+    }
+
+    @Test
+    public void testDump() throws ErrnoException {
+        mBpfMap.updateEntry(new S32(TEST_INDEX), new InterfaceMapValue(TEST_INTERFACE_NAME));
+        mBpfMap.updateEntry(new S32(TEST_INDEX2), new InterfaceMapValue(TEST_INTERFACE_NAME2));
+
+        final String dump = getDump();
+        assertDumpContains(dump, "IfaceIndexNameMap: OK");
+        assertDumpContains(dump, "ifaceIndex=1 ifaceName=test1");
+        assertDumpContains(dump, "ifaceIndex=2 ifaceName=test2");
+    }
+}
diff --git a/tests/unit/java/com/android/server/net/BpfInterfaceMapUpdaterTest.java b/tests/unit/java/com/android/server/net/BpfInterfaceMapUpdaterTest.java
deleted file mode 100644
index c730856..0000000
--- a/tests/unit/java/com/android/server/net/BpfInterfaceMapUpdaterTest.java
+++ /dev/null
@@ -1,174 +0,0 @@
-/*
- * Copyright (C) 2022 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.net;
-
-import static android.system.OsConstants.EPERM;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.Matchers.eq;
-import static org.mockito.Mockito.doThrow;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyNoMoreInteractions;
-import static org.mockito.Mockito.when;
-
-import android.content.Context;
-import android.net.INetd;
-import android.net.MacAddress;
-import android.os.Build;
-import android.os.Handler;
-import android.os.test.TestLooper;
-import android.system.ErrnoException;
-import android.util.IndentingPrintWriter;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.net.module.util.BaseNetdUnsolicitedEventListener;
-import com.android.net.module.util.IBpfMap;
-import com.android.net.module.util.InterfaceParams;
-import com.android.net.module.util.Struct.S32;
-import com.android.testutils.DevSdkIgnoreRule;
-import com.android.testutils.DevSdkIgnoreRunner;
-import com.android.testutils.TestBpfMap;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-import java.io.PrintWriter;
-import java.io.StringWriter;
-
-@SmallTest
-@RunWith(DevSdkIgnoreRunner.class)
-@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.S_V2)
-public final class BpfInterfaceMapUpdaterTest {
-    private static final int TEST_INDEX = 1;
-    private static final int TEST_INDEX2 = 2;
-    private static final String TEST_INTERFACE_NAME = "test1";
-    private static final String TEST_INTERFACE_NAME2 = "test2";
-
-    private final TestLooper mLooper = new TestLooper();
-    private BaseNetdUnsolicitedEventListener mListener;
-    private BpfInterfaceMapUpdater mUpdater;
-    private IBpfMap<S32, InterfaceMapValue> mBpfMap =
-            spy(new TestBpfMap<>(S32.class, InterfaceMapValue.class));
-    @Mock private INetd mNetd;
-    @Mock private Context mContext;
-
-    private class TestDependencies extends BpfInterfaceMapUpdater.Dependencies {
-        @Override
-        public IBpfMap<S32, InterfaceMapValue> getInterfaceMap() {
-            return mBpfMap;
-        }
-
-        @Override
-        public InterfaceParams getInterfaceParams(String ifaceName) {
-            if (ifaceName.equals(TEST_INTERFACE_NAME)) {
-                return new InterfaceParams(TEST_INTERFACE_NAME, TEST_INDEX,
-                        MacAddress.ALL_ZEROS_ADDRESS);
-            } else if (ifaceName.equals(TEST_INTERFACE_NAME2)) {
-                return new InterfaceParams(TEST_INTERFACE_NAME2, TEST_INDEX2,
-                        MacAddress.ALL_ZEROS_ADDRESS);
-            }
-
-            return null;
-        }
-
-        @Override
-        public INetd getINetd(Context ctx) {
-            return mNetd;
-        }
-    }
-
-    @Before
-    public void setUp() throws Exception {
-        MockitoAnnotations.initMocks(this);
-        when(mNetd.interfaceGetList()).thenReturn(new String[] {TEST_INTERFACE_NAME});
-        mUpdater = new BpfInterfaceMapUpdater(mContext, new Handler(mLooper.getLooper()),
-                new TestDependencies());
-    }
-
-    private void verifyStartUpdater() throws Exception {
-        mUpdater.start();
-        mLooper.dispatchAll();
-        final ArgumentCaptor<BaseNetdUnsolicitedEventListener> listenerCaptor =
-                ArgumentCaptor.forClass(BaseNetdUnsolicitedEventListener.class);
-        verify(mNetd).registerUnsolicitedEventListener(listenerCaptor.capture());
-        mListener = listenerCaptor.getValue();
-        verify(mBpfMap).updateEntry(eq(new S32(TEST_INDEX)),
-                eq(new InterfaceMapValue(TEST_INTERFACE_NAME)));
-    }
-
-    @Test
-    public void testUpdateInterfaceMap() throws Exception {
-        verifyStartUpdater();
-
-        mListener.onInterfaceAdded(TEST_INTERFACE_NAME2);
-        mLooper.dispatchAll();
-        verify(mBpfMap).updateEntry(eq(new S32(TEST_INDEX2)),
-                eq(new InterfaceMapValue(TEST_INTERFACE_NAME2)));
-
-        // Check that when onInterfaceRemoved is called, nothing happens.
-        mListener.onInterfaceRemoved(TEST_INTERFACE_NAME);
-        mLooper.dispatchAll();
-        verifyNoMoreInteractions(mBpfMap);
-    }
-
-    @Test
-    public void testGetIfNameByIndex() throws Exception {
-        mBpfMap.updateEntry(new S32(TEST_INDEX), new InterfaceMapValue(TEST_INTERFACE_NAME));
-        assertEquals(TEST_INTERFACE_NAME, mUpdater.getIfNameByIndex(TEST_INDEX));
-    }
-
-    @Test
-    public void testGetIfNameByIndexNoEntry() {
-        assertNull(mUpdater.getIfNameByIndex(TEST_INDEX));
-    }
-
-    @Test
-    public void testGetIfNameByIndexException() throws Exception {
-        doThrow(new ErrnoException("", EPERM)).when(mBpfMap).getValue(new S32(TEST_INDEX));
-        assertNull(mUpdater.getIfNameByIndex(TEST_INDEX));
-    }
-
-    private void assertDumpContains(final String dump, final String message) {
-        assertTrue(String.format("dump(%s) does not contain '%s'", dump, message),
-                dump.contains(message));
-    }
-
-    private String getDump() {
-        final StringWriter sw = new StringWriter();
-        mUpdater.dump(new IndentingPrintWriter(new PrintWriter(sw), " "));
-        return sw.toString();
-    }
-
-    @Test
-    public void testDump() throws ErrnoException {
-        mBpfMap.updateEntry(new S32(TEST_INDEX), new InterfaceMapValue(TEST_INTERFACE_NAME));
-        mBpfMap.updateEntry(new S32(TEST_INDEX2), new InterfaceMapValue(TEST_INTERFACE_NAME2));
-
-        final String dump = getDump();
-        assertDumpContains(dump, "IfaceIndexNameMap: OK");
-        assertDumpContains(dump, "ifaceIndex=1 ifaceName=test1");
-        assertDumpContains(dump, "ifaceIndex=2 ifaceName=test2");
-    }
-}
diff --git a/tests/unit/java/com/android/server/net/NetworkStatsObserversTest.java b/tests/unit/java/com/android/server/net/NetworkStatsObserversTest.java
index e62ac74..0bbc34c 100644
--- a/tests/unit/java/com/android/server/net/NetworkStatsObserversTest.java
+++ b/tests/unit/java/com/android/server/net/NetworkStatsObserversTest.java
@@ -47,7 +47,6 @@
 import android.net.NetworkTemplate;
 import android.os.HandlerThread;
 import android.os.IBinder;
-import android.os.Looper;
 import android.os.Process;
 import android.os.UserHandle;
 import android.telephony.TelephonyManager;
@@ -127,13 +126,7 @@
 
         mObserverHandlerThread = new HandlerThread("NetworkStatsObserversTest");
         mObserverHandlerThread.start();
-        final Looper observerLooper = mObserverHandlerThread.getLooper();
-        mStatsObservers = new NetworkStatsObservers() {
-            @Override
-            protected Looper getHandlerLooperLocked() {
-                return observerLooper;
-            }
-        };
+        mStatsObservers = new NetworkStatsObservers(mObserverHandlerThread.getLooper());
 
         mActiveIfaces = new ArrayMap<>();
         mActiveUidIfaces = new ArrayMap<>();
diff --git a/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java b/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
index a5fee5b..3d7ad66 100644
--- a/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
+++ b/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
@@ -123,7 +123,6 @@
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.IBinder;
-import android.os.Looper;
 import android.os.PowerManager;
 import android.os.SimpleClock;
 import android.provider.Settings;
@@ -254,7 +253,7 @@
     private @Mock AlarmManager mAlarmManager;
     @Mock
     private NetworkStatsSubscriptionsMonitor mNetworkStatsSubscriptionsMonitor;
-    private @Mock BpfInterfaceMapUpdater mBpfInterfaceMapUpdater;
+    private @Mock BpfInterfaceMapHelper mBpfInterfaceMapHelper;
     private HandlerThread mHandlerThread;
     @Mock
     private LocationPermissionChecker mLocationPermissionChecker;
@@ -293,7 +292,6 @@
     private String mCompareStatsResult = null;
     private @Mock Resources mResources;
     private Boolean mIsDebuggable;
-    private HandlerThread mObserverHandlerThread;
     final TestDependencies mDeps = new TestDependencies();
 
     private class MockContext extends BroadcastInterceptingContext {
@@ -377,21 +375,8 @@
                 powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
 
         mHandlerThread = new HandlerThread("NetworkStatsServiceTest-HandlerThread");
-        // Create a separate thread for observers to run on. This thread cannot be the same
-        // as the handler thread, because the observer callback is fired on this thread, and
-        // it should not be blocked by client code. Additionally, creating the observers
-        // object requires a looper, which can only be obtained after a thread has been started.
-        mObserverHandlerThread = new HandlerThread("NetworkStatsServiceTest-ObserversThread");
-        mObserverHandlerThread.start();
-        final Looper observerLooper = mObserverHandlerThread.getLooper();
-        final NetworkStatsObservers statsObservers = new NetworkStatsObservers() {
-            @Override
-            protected Looper getHandlerLooperLocked() {
-                return observerLooper;
-            }
-        };
         mService = new NetworkStatsService(mServiceContext, mNetd, mAlarmManager, wakeLock,
-                mClock, mSettings, mStatsFactory, statsObservers, mDeps);
+                mClock, mSettings, mStatsFactory, mDeps);
 
         mElapsedRealtime = 0L;
 
@@ -519,9 +504,8 @@
         }
 
         @Override
-        public BpfInterfaceMapUpdater makeBpfInterfaceMapUpdater(
-                @NonNull Context ctx, @NonNull Handler handler) {
-            return mBpfInterfaceMapUpdater;
+        public BpfInterfaceMapHelper makeBpfInterfaceMapHelper() {
+            return mBpfInterfaceMapHelper;
         }
 
         @Override
@@ -590,10 +574,6 @@
             mHandlerThread.quitSafely();
             mHandlerThread.join();
         }
-        if (mObserverHandlerThread != null) {
-            mObserverHandlerThread.quitSafely();
-            mObserverHandlerThread.join();
-        }
     }
 
     private void initWifiStats(NetworkStateSnapshot snapshot) throws Exception {
@@ -2764,13 +2744,13 @@
 
     @Test
     public void testDumpStatsMap() throws ErrnoException {
-        doReturn("wlan0").when(mBpfInterfaceMapUpdater).getIfNameByIndex(10 /* index */);
+        doReturn("wlan0").when(mBpfInterfaceMapHelper).getIfNameByIndex(10 /* index */);
         doTestDumpStatsMap("wlan0");
     }
 
     @Test
     public void testDumpStatsMapUnknownInterface() throws ErrnoException {
-        doReturn(null).when(mBpfInterfaceMapUpdater).getIfNameByIndex(10 /* index */);
+        doReturn(null).when(mBpfInterfaceMapHelper).getIfNameByIndex(10 /* index */);
         doTestDumpStatsMap("unknown");
     }
 
@@ -2785,13 +2765,13 @@
 
     @Test
     public void testDumpIfaceStatsMap() throws Exception {
-        doReturn("wlan0").when(mBpfInterfaceMapUpdater).getIfNameByIndex(10 /* index */);
+        doReturn("wlan0").when(mBpfInterfaceMapHelper).getIfNameByIndex(10 /* index */);
         doTestDumpIfaceStatsMap("wlan0");
     }
 
     @Test
     public void testDumpIfaceStatsMapUnknownInterface() throws Exception {
-        doReturn(null).when(mBpfInterfaceMapUpdater).getIfNameByIndex(10 /* index */);
+        doReturn(null).when(mBpfInterfaceMapHelper).getIfNameByIndex(10 /* index */);
         doTestDumpIfaceStatsMap("unknown");
     }
 
diff --git a/tests/unit/java/com/android/server/net/SkDestroyListenerTest.kt b/tests/unit/java/com/android/server/net/SkDestroyListenerTest.kt
new file mode 100644
index 0000000..18785e5
--- /dev/null
+++ b/tests/unit/java/com/android/server/net/SkDestroyListenerTest.kt
@@ -0,0 +1,63 @@
+/*
+ * 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.
+ */
+
+package com.android.server.net
+
+import android.os.Handler
+import android.os.HandlerThread
+import com.android.net.module.util.SharedLog
+import com.android.testutils.DevSdkIgnoreRunner
+import java.io.PrintWriter
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.any
+import org.mockito.Mock
+import org.mockito.Mockito.doReturn
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@DevSdkIgnoreRunner.MonitorThreadLeak
+@RunWith(DevSdkIgnoreRunner::class)
+class SkDestroyListenerTest {
+    @Mock lateinit var sharedLog: SharedLog
+    val handlerThread = HandlerThread("SkDestroyListenerTest")
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        handlerThread.start()
+    }
+
+    @After
+    fun tearDown() {
+        handlerThread.quitSafely()
+        handlerThread.join()
+    }
+
+    @Test
+    fun testDump() {
+        doReturn(sharedLog).`when`(sharedLog).forSubComponent(any())
+
+        val handler = Handler(handlerThread.looper)
+        val skDestroylistener = SkDestroyListener(null /* cookieTagMap */, handler, sharedLog)
+        val pw = PrintWriter(System.out)
+        skDestroylistener.dump(pw)
+
+        verify(sharedLog).reverseDump(pw)
+    }
+}
diff --git a/tests/unit/java/com/android/server/net/TrafficStatsRateLimitCacheTest.kt b/tests/unit/java/com/android/server/net/TrafficStatsRateLimitCacheTest.kt
new file mode 100644
index 0000000..27e6f96
--- /dev/null
+++ b/tests/unit/java/com/android/server/net/TrafficStatsRateLimitCacheTest.kt
@@ -0,0 +1,89 @@
+/*
+ * 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.
+ */
+
+package com.android.server.net
+
+import android.net.NetworkStats
+import com.android.testutils.DevSdkIgnoreRunner
+import java.time.Clock
+import kotlin.test.assertEquals
+import kotlin.test.assertNull
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.`when`
+
+@RunWith(DevSdkIgnoreRunner::class)
+class TrafficStatsRateLimitCacheTest {
+    companion object {
+        private const val expiryDurationMs = 1000L
+    }
+
+    private val clock = mock(Clock::class.java)
+    private val entry = mock(NetworkStats.Entry::class.java)
+    private val cache = TrafficStatsRateLimitCache(clock, expiryDurationMs)
+
+    @Test
+    fun testGet_returnsEntryIfNotExpired() {
+        cache.put("iface", 2, entry)
+        `when`(clock.millis()).thenReturn(500L) // Set clock to before expiry
+        val result = cache.get("iface", 2)
+        assertEquals(entry, result)
+    }
+
+    @Test
+    fun testGet_returnsNullIfExpired() {
+        cache.put("iface", 2, entry)
+        `when`(clock.millis()).thenReturn(2000L) // Set clock to after expiry
+        assertNull(cache.get("iface", 2))
+    }
+
+    @Test
+    fun testGet_returnsNullForNonExistentKey() {
+        val result = cache.get("otherIface", 99)
+        assertNull(result)
+    }
+
+    @Test
+    fun testPutAndGet_retrievesCorrectEntryForDifferentKeys() {
+        val entry1 = mock(NetworkStats.Entry::class.java)
+        val entry2 = mock(NetworkStats.Entry::class.java)
+
+        cache.put("iface1", 2, entry1)
+        cache.put("iface2", 4, entry2)
+
+        assertEquals(entry1, cache.get("iface1", 2))
+        assertEquals(entry2, cache.get("iface2", 4))
+    }
+
+    @Test
+    fun testPut_overridesExistingEntry() {
+        val entry1 = mock(NetworkStats.Entry::class.java)
+        val entry2 = mock(NetworkStats.Entry::class.java)
+
+        cache.put("iface", 2, entry1)
+        cache.put("iface", 2, entry2) // Put with the same key
+
+        assertEquals(entry2, cache.get("iface", 2))
+    }
+
+    @Test
+    fun testClear() {
+        cache.put("iface", 2, entry)
+        cache.clear()
+        assertNull(cache.get("iface", 2))
+    }
+}
diff --git a/tests/unit/jni/Android.bp b/tests/unit/jni/Android.bp
index 616da81..57a157d 100644
--- a/tests/unit/jni/Android.bp
+++ b/tests/unit/jni/Android.bp
@@ -1,4 +1,5 @@
 package {
+    default_team: "trendy_team_fwk_core_networking",
     // See: http://go/android-license-faq
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
diff --git a/tests/unit/vpn-jarjar-rules.txt b/tests/unit/vpn-jarjar-rules.txt
index 1a6bddc..f74eab8 100644
--- a/tests/unit/vpn-jarjar-rules.txt
+++ b/tests/unit/vpn-jarjar-rules.txt
@@ -1,4 +1,2 @@
 # Only keep classes imported by ConnectivityServiceTest
-keep com.android.server.connectivity.Vpn
 keep com.android.server.connectivity.VpnProfileStore
-keep com.android.server.net.LockdownVpnTracker
diff --git a/thread/apex/Android.bp b/thread/apex/Android.bp
index 28854f2..edf000a 100644
--- a/thread/apex/Android.bp
+++ b/thread/apex/Android.bp
@@ -15,6 +15,7 @@
 //
 
 package {
+    default_team: "trendy_team_fwk_thread_network",
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
diff --git a/thread/apex/ot-daemon.34rc b/thread/apex/ot-daemon.34rc
index 1eb1294..86f6b69 100644
--- a/thread/apex/ot-daemon.34rc
+++ b/thread/apex/ot-daemon.34rc
@@ -21,4 +21,5 @@
     user thread_network
     group thread_network inet system
     seclabel u:r:ot_daemon:s0
+    socket ot-daemon/thread-wpan.sock stream 0660 thread_network thread_network
     override
diff --git a/thread/demoapp/Android.bp b/thread/demoapp/Android.bp
index da7a5f8..fcfd469 100644
--- a/thread/demoapp/Android.bp
+++ b/thread/demoapp/Android.bp
@@ -13,6 +13,7 @@
 // limitations under the License.
 
 package {
+    default_team: "trendy_team_fwk_thread_network",
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
diff --git a/thread/flags/thread_base.aconfig b/thread/flags/thread_base.aconfig
deleted file mode 100644
index f73ea6b..0000000
--- a/thread/flags/thread_base.aconfig
+++ /dev/null
@@ -1,15 +0,0 @@
-package: "com.android.net.thread.flags"
-
-flag {
-    name: "thread_enabled"
-    namespace: "thread_network"
-    description: "Controls whether the Android Thread feature is enabled"
-    bug: "301473012"
-}
-
-flag {
-    name: "thread_user_restriction_enabled"
-    namespace: "thread_network"
-    description: "Controls whether user restriction on thread networks is enabled"
-    bug: "307679182"
-}
diff --git a/thread/framework/Android.bp b/thread/framework/Android.bp
index cc598d8..846253c 100644
--- a/thread/framework/Android.bp
+++ b/thread/framework/Android.bp
@@ -15,6 +15,7 @@
 //
 
 package {
+    default_team: "trendy_team_fwk_thread_network",
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
diff --git a/thread/framework/java/android/net/thread/IStateCallback.aidl b/thread/framework/java/android/net/thread/IStateCallback.aidl
index d7cbda9..9d0a571 100644
--- a/thread/framework/java/android/net/thread/IStateCallback.aidl
+++ b/thread/framework/java/android/net/thread/IStateCallback.aidl
@@ -22,4 +22,5 @@
 oneway interface IStateCallback {
     void onDeviceRoleChanged(int deviceRole);
     void onPartitionIdChanged(long partitionId);
+    void onThreadEnableStateChanged(int enabledState);
 }
diff --git a/thread/framework/java/android/net/thread/IThreadNetworkController.aidl b/thread/framework/java/android/net/thread/IThreadNetworkController.aidl
index a9da8d6..485e25d 100644
--- a/thread/framework/java/android/net/thread/IThreadNetworkController.aidl
+++ b/thread/framework/java/android/net/thread/IThreadNetworkController.aidl
@@ -42,4 +42,6 @@
 
     int getThreadVersion();
     void createRandomizedDataset(String networkName, IActiveOperationalDatasetReceiver receiver);
+
+    void setEnabled(boolean enabled, in IOperationReceiver receiver);
 }
diff --git a/thread/framework/java/android/net/thread/ThreadNetworkController.java b/thread/framework/java/android/net/thread/ThreadNetworkController.java
index 7242ed7..db761a3 100644
--- a/thread/framework/java/android/net/thread/ThreadNetworkController.java
+++ b/thread/framework/java/android/net/thread/ThreadNetworkController.java
@@ -68,6 +68,15 @@
     /** The device is a Thread Leader. */
     public static final int DEVICE_ROLE_LEADER = 4;
 
+    /** The Thread radio is disabled. */
+    public static final int STATE_DISABLED = 0;
+
+    /** The Thread radio is enabled. */
+    public static final int STATE_ENABLED = 1;
+
+    /** The Thread radio is being disabled. */
+    public static final int STATE_DISABLING = 2;
+
     /** @hide */
     @Retention(RetentionPolicy.SOURCE)
     @IntDef({
@@ -79,6 +88,13 @@
     })
     public @interface DeviceRole {}
 
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(
+            prefix = {"STATE_"},
+            value = {STATE_DISABLED, STATE_ENABLED, STATE_DISABLING})
+    public @interface EnabledState {}
+
     /** Thread standard version 1.3. */
     public static final int THREAD_VERSION_1_3 = 4;
 
@@ -106,6 +122,40 @@
         mControllerService = controllerService;
     }
 
+    /**
+     * Enables/Disables the radio of this ThreadNetworkController. The requested enabled state will
+     * be persistent and survives device reboots.
+     *
+     * <p>When Thread is in {@code STATE_DISABLED}, {@link ThreadNetworkController} APIs which
+     * require the Thread radio will fail with error code {@link
+     * ThreadNetworkException#ERROR_THREAD_DISABLED}. When Thread is in {@code STATE_DISABLING},
+     * {@link ThreadNetworkController} APIs that return a {@link ThreadNetworkException} will fail
+     * with error code {@link ThreadNetworkException#ERROR_BUSY}.
+     *
+     * <p>On success, {@link OutcomeReceiver#onResult} of {@code receiver} is called. It indicates
+     * the operation has completed. But there maybe subsequent calls to update the enabled state,
+     * callers of this method should use {@link #registerStateCallback} to subscribe to the Thread
+     * enabled state changes.
+     *
+     * <p>On failure, {@link OutcomeReceiver#onError} of {@code receiver} will be invoked with a
+     * specific error in {@link ThreadNetworkException#ERROR_}.
+     *
+     * @param enabled {@code true} for enabling Thread
+     * @param executor the executor to execute {@code receiver}
+     * @param receiver the receiver to receive result of this operation
+     */
+    @RequiresPermission("android.permission.THREAD_NETWORK_PRIVILEGED")
+    public void setEnabled(
+            boolean enabled,
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull OutcomeReceiver<Void, ThreadNetworkException> receiver) {
+        try {
+            mControllerService.setEnabled(enabled, new OperationReceiverProxy(executor, receiver));
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
     /** Returns the Thread version this device is operating on. */
     @ThreadVersion
     public int getThreadVersion() {
@@ -170,6 +220,16 @@
          * @param partitionId the new Thread partition ID
          */
         default void onPartitionIdChanged(long partitionId) {}
+
+        /**
+         * The Thread enabled state has changed.
+         *
+         * <p>The Thread enabled state can be set with {@link setEnabled}, it may also be updated by
+         * airplane mode or admin control.
+         *
+         * @param enabledState the new Thread enabled state
+         */
+        default void onThreadEnableStateChanged(@EnabledState int enabledState) {}
     }
 
     private static final class StateCallbackProxy extends IStateCallback.Stub {
@@ -200,6 +260,16 @@
                 Binder.restoreCallingIdentity(identity);
             }
         }
+
+        @Override
+        public void onThreadEnableStateChanged(@EnabledState int enabled) {
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                mExecutor.execute(() -> mCallback.onThreadEnableStateChanged(enabled));
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
     }
 
     /**
diff --git a/thread/framework/java/android/net/thread/ThreadNetworkException.java b/thread/framework/java/android/net/thread/ThreadNetworkException.java
index c5e1e97..4def0fb 100644
--- a/thread/framework/java/android/net/thread/ThreadNetworkException.java
+++ b/thread/framework/java/android/net/thread/ThreadNetworkException.java
@@ -47,6 +47,8 @@
         ERROR_REJECTED_BY_PEER,
         ERROR_RESPONSE_BAD_FORMAT,
         ERROR_RESOURCE_EXHAUSTED,
+        ERROR_UNKNOWN,
+        ERROR_THREAD_DISABLED,
     })
     public @interface ErrorCode {}
 
@@ -87,8 +89,9 @@
 
     /**
      * The operation failed because required preconditions were not satisfied. For example, trying
-     * to schedule a network migration when this device is not attached will receive this error. The
-     * caller should not retry the same operation before the precondition is satisfied.
+     * to schedule a network migration when this device is not attached will receive this error or
+     * enable Thread while User Resitration has disabled it. The caller should not retry the same
+     * operation before the precondition is satisfied.
      */
     public static final int ERROR_FAILED_PRECONDITION = 6;
 
@@ -122,11 +125,42 @@
      */
     public static final int ERROR_RESOURCE_EXHAUSTED = 10;
 
+    /**
+     * The operation failed because of an unknown error in the system. This typically indicates that
+     * the caller doesn't understand error codes added in newer Android versions.
+     */
+    public static final int ERROR_UNKNOWN = 11;
+
+    /**
+     * The operation failed because the Thread radio is disabled by {@link
+     * ThreadNetworkController#setEnabled}, airplane mode or device admin. The caller should retry
+     * only after Thread is enabled.
+     */
+    public static final int ERROR_THREAD_DISABLED = 12;
+
+    private static final int ERROR_MIN = ERROR_INTERNAL_ERROR;
+    private static final int ERROR_MAX = ERROR_THREAD_DISABLED;
+
     private final int mErrorCode;
 
-    /** Creates a new {@link ThreadNetworkException} object with given error code and message. */
-    public ThreadNetworkException(@ErrorCode int errorCode, @NonNull String errorMessage) {
-        super(requireNonNull(errorMessage, "errorMessage cannot be null"));
+    /**
+     * Creates a new {@link ThreadNetworkException} object with given error code and message.
+     *
+     * @throws IllegalArgumentException if {@code errorCode} is not a value in {@link #ERROR_}
+     * @throws NullPointerException if {@code message} is {@code null}
+     */
+    public ThreadNetworkException(@ErrorCode int errorCode, @NonNull String message) {
+        super(requireNonNull(message, "message cannot be null"));
+        if (errorCode < ERROR_MIN || errorCode > ERROR_MAX) {
+            throw new IllegalArgumentException(
+                    "errorCode cannot be "
+                            + errorCode
+                            + " (allowedRange = ["
+                            + ERROR_MIN
+                            + ", "
+                            + ERROR_MAX
+                            + "])");
+        }
         this.mErrorCode = errorCode;
     }
 
diff --git a/thread/framework/java/android/net/thread/ThreadNetworkManager.java b/thread/framework/java/android/net/thread/ThreadNetworkManager.java
index 28012a7..150b759 100644
--- a/thread/framework/java/android/net/thread/ThreadNetworkManager.java
+++ b/thread/framework/java/android/net/thread/ThreadNetworkManager.java
@@ -79,6 +79,17 @@
     public static final String PERMISSION_THREAD_NETWORK_PRIVILEGED =
             "android.permission.THREAD_NETWORK_PRIVILEGED";
 
+    /**
+     * This user restriction specifies if Thread network is disallowed on the device. If Thread
+     * network is disallowed it cannot be turned on via Settings.
+     *
+     * <p>this is a mirror of {@link UserManager#DISALLOW_THREAD_NETWORK} which is not available on
+     * Android U devices.
+     *
+     * @hide
+     */
+    public static final String DISALLOW_THREAD_NETWORK = "no_thread_network";
+
     @NonNull private final Context mContext;
     @NonNull private final List<ThreadNetworkController> mUnmodifiableControllerServices;
 
diff --git a/thread/scripts/make-pretty.sh b/thread/scripts/make-pretty.sh
index e4bd459..c176bfa 100755
--- a/thread/scripts/make-pretty.sh
+++ b/thread/scripts/make-pretty.sh
@@ -3,5 +3,7 @@
 SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
 
 GOOGLE_JAVA_FORMAT=$SCRIPT_DIR/../../../../../prebuilts/tools/common/google-java-format/google-java-format
+ANDROID_BP_FORMAT=$SCRIPT_DIR/../../../../../prebuilts/build-tools/linux-x86/bin/bpfmt
 
 $GOOGLE_JAVA_FORMAT --aosp -i $(find $SCRIPT_DIR/../ -name "*.java")
+$ANDROID_BP_FORMAT -w $(find $SCRIPT_DIR/../ -name "*.bp")
diff --git a/thread/service/Android.bp b/thread/service/Android.bp
index 69295cc..6e2fac1 100644
--- a/thread/service/Android.bp
+++ b/thread/service/Android.bp
@@ -15,6 +15,7 @@
 //
 
 package {
+    default_team: "trendy_team_fwk_thread_network",
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
@@ -47,9 +48,6 @@
         "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/ActiveOperationalDatasetReceiverWrapper.java b/thread/service/java/com/android/server/thread/ActiveOperationalDatasetReceiverWrapper.java
new file mode 100644
index 0000000..e3b4e1a
--- /dev/null
+++ b/thread/service/java/com/android/server/thread/ActiveOperationalDatasetReceiverWrapper.java
@@ -0,0 +1,87 @@
+/*
+ * 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.
+ */
+
+package com.android.server.thread;
+
+import static android.net.thread.ThreadNetworkException.ERROR_UNAVAILABLE;
+
+import android.net.thread.ActiveOperationalDataset;
+import android.net.thread.IActiveOperationalDatasetReceiver;
+import android.os.RemoteException;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * A {@link IActiveOperationalDatasetReceiver} wrapper which makes it easier to invoke the
+ * callbacks.
+ */
+final class ActiveOperationalDatasetReceiverWrapper {
+    private final IActiveOperationalDatasetReceiver mReceiver;
+
+    private static final Object sPendingReceiversLock = new Object();
+
+    @GuardedBy("sPendingReceiversLock")
+    private static final Set<ActiveOperationalDatasetReceiverWrapper> sPendingReceivers =
+            new HashSet<>();
+
+    public ActiveOperationalDatasetReceiverWrapper(IActiveOperationalDatasetReceiver receiver) {
+        this.mReceiver = receiver;
+
+        synchronized (sPendingReceiversLock) {
+            sPendingReceivers.add(this);
+        }
+    }
+
+    public static void onOtDaemonDied() {
+        synchronized (sPendingReceiversLock) {
+            for (ActiveOperationalDatasetReceiverWrapper receiver : sPendingReceivers) {
+                try {
+                    receiver.mReceiver.onError(ERROR_UNAVAILABLE, "Thread daemon died");
+                } catch (RemoteException e) {
+                    // The client is dead, do nothing
+                }
+            }
+            sPendingReceivers.clear();
+        }
+    }
+
+    public void onSuccess(ActiveOperationalDataset dataset) {
+        synchronized (sPendingReceiversLock) {
+            sPendingReceivers.remove(this);
+        }
+
+        try {
+            mReceiver.onSuccess(dataset);
+        } catch (RemoteException e) {
+            // The client is dead, do nothing
+        }
+    }
+
+    public void onError(int errorCode, String errorMessage) {
+        synchronized (sPendingReceiversLock) {
+            sPendingReceivers.remove(this);
+        }
+
+        try {
+            mReceiver.onError(errorCode, errorMessage);
+        } catch (RemoteException e) {
+            // The client is dead, do nothing
+        }
+    }
+}
diff --git a/thread/service/java/com/android/server/thread/NsdPublisher.java b/thread/service/java/com/android/server/thread/NsdPublisher.java
new file mode 100644
index 0000000..440c2c3
--- /dev/null
+++ b/thread/service/java/com/android/server/thread/NsdPublisher.java
@@ -0,0 +1,361 @@
+/*
+ * 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.
+ */
+
+package com.android.server.thread;
+
+import static android.net.nsd.NsdManager.PROTOCOL_DNS_SD;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.net.InetAddresses;
+import android.net.nsd.NsdManager;
+import android.net.nsd.NsdServiceInfo;
+import android.os.Handler;
+import android.os.RemoteException;
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.thread.openthread.DnsTxtAttribute;
+import com.android.server.thread.openthread.INsdPublisher;
+import com.android.server.thread.openthread.INsdStatusReceiver;
+
+import java.net.InetAddress;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Deque;
+import java.util.HashSet;
+import java.util.List;
+import java.util.concurrent.Executor;
+
+/**
+ * Implementation of {@link INsdPublisher}.
+ *
+ * <p>This class provides API for service registration and discovery over mDNS. This class is a
+ * proxy between ot-daemon and NsdManager.
+ *
+ * <p>All the data members of this class MUST be accessed in the {@code mHandler}'s Thread except
+ * {@code mHandler} itself.
+ *
+ * <p>TODO: b/323300118 - Remove the following mechanism when the race condition in NsdManager is
+ * fixed.
+ *
+ * <p>There's always only one running registration job at any timepoint. All other pending jobs are
+ * queued in {@code mRegistrationJobs}. When a registration job is complete (i.e. the according
+ * method in {@link NsdManager.RegistrationListener} is called), it will start the next registration
+ * job in the queue.
+ */
+public final class NsdPublisher extends INsdPublisher.Stub {
+    // TODO: b/321883491 - specify network for mDNS operations
+    private static final String TAG = NsdPublisher.class.getSimpleName();
+    private final NsdManager mNsdManager;
+    private final Handler mHandler;
+    private final Executor mExecutor;
+    private final SparseArray<RegistrationListener> mRegistrationListeners = new SparseArray<>(0);
+    private final Deque<Runnable> mRegistrationJobs = new ArrayDeque<>();
+
+    @VisibleForTesting
+    public NsdPublisher(NsdManager nsdManager, Handler handler) {
+        mNsdManager = nsdManager;
+        mHandler = handler;
+        mExecutor = runnable -> mHandler.post(runnable);
+    }
+
+    public static NsdPublisher newInstance(Context context, Handler handler) {
+        return new NsdPublisher(context.getSystemService(NsdManager.class), handler);
+    }
+
+    @Override
+    public void registerService(
+            String hostname,
+            String name,
+            String type,
+            List<String> subTypeList,
+            int port,
+            List<DnsTxtAttribute> txt,
+            INsdStatusReceiver receiver,
+            int listenerId) {
+        postRegistrationJob(
+                () -> {
+                    NsdServiceInfo serviceInfo =
+                            buildServiceInfoForService(
+                                    hostname, name, type, subTypeList, port, txt);
+                    registerInternal(serviceInfo, receiver, listenerId, "service");
+                });
+    }
+
+    private static NsdServiceInfo buildServiceInfoForService(
+            String hostname,
+            String name,
+            String type,
+            List<String> subTypeList,
+            int port,
+            List<DnsTxtAttribute> txt) {
+        NsdServiceInfo serviceInfo = new NsdServiceInfo();
+
+        serviceInfo.setServiceName(name);
+        if (!TextUtils.isEmpty(hostname)) {
+            serviceInfo.setHostname(hostname);
+        }
+        serviceInfo.setServiceType(type);
+        serviceInfo.setPort(port);
+        serviceInfo.setSubtypes(new HashSet<>(subTypeList));
+        for (DnsTxtAttribute attribute : txt) {
+            serviceInfo.setAttribute(attribute.name, attribute.value);
+        }
+
+        return serviceInfo;
+    }
+
+    @Override
+    public void registerHost(
+            String name, List<String> addresses, INsdStatusReceiver receiver, int listenerId) {
+        postRegistrationJob(
+                () -> {
+                    NsdServiceInfo serviceInfo = buildServiceInfoForHost(name, addresses);
+                    registerInternal(serviceInfo, receiver, listenerId, "host");
+                });
+    }
+
+    private static NsdServiceInfo buildServiceInfoForHost(
+            String name, List<String> addressStrings) {
+        NsdServiceInfo serviceInfo = new NsdServiceInfo();
+
+        serviceInfo.setHostname(name);
+        ArrayList<InetAddress> addresses = new ArrayList<>(addressStrings.size());
+        for (String addressString : addressStrings) {
+            addresses.add(InetAddresses.parseNumericAddress(addressString));
+        }
+        serviceInfo.setHostAddresses(addresses);
+
+        return serviceInfo;
+    }
+
+    private void registerInternal(
+            NsdServiceInfo serviceInfo,
+            INsdStatusReceiver receiver,
+            int listenerId,
+            String registrationType) {
+        checkOnHandlerThread();
+        Log.i(
+                TAG,
+                "Registering "
+                        + registrationType
+                        + ". Listener ID: "
+                        + listenerId
+                        + ", serviceInfo: "
+                        + serviceInfo);
+        RegistrationListener listener = new RegistrationListener(serviceInfo, listenerId, receiver);
+        mRegistrationListeners.append(listenerId, listener);
+        try {
+            mNsdManager.registerService(serviceInfo, PROTOCOL_DNS_SD, mExecutor, listener);
+        } catch (IllegalArgumentException e) {
+            Log.i(TAG, "Failed to register service. serviceInfo: " + serviceInfo, e);
+            listener.onRegistrationFailed(serviceInfo, NsdManager.FAILURE_INTERNAL_ERROR);
+        }
+    }
+
+    public void unregister(INsdStatusReceiver receiver, int listenerId) {
+        postRegistrationJob(() -> unregisterInternal(receiver, listenerId));
+    }
+
+    public void unregisterInternal(INsdStatusReceiver receiver, int listenerId) {
+        checkOnHandlerThread();
+        RegistrationListener registrationListener = mRegistrationListeners.get(listenerId);
+        if (registrationListener == null) {
+            Log.w(
+                    TAG,
+                    "Failed to unregister service."
+                            + " Listener ID: "
+                            + listenerId
+                            + " The registrationListener is empty.");
+
+            return;
+        }
+        Log.i(
+                TAG,
+                "Unregistering service."
+                        + " Listener ID: "
+                        + listenerId
+                        + " serviceInfo: "
+                        + registrationListener.mServiceInfo);
+        registrationListener.addUnregistrationReceiver(receiver);
+        mNsdManager.unregisterService(registrationListener);
+    }
+
+    private void checkOnHandlerThread() {
+        if (mHandler.getLooper().getThread() != Thread.currentThread()) {
+            throw new IllegalStateException(
+                    "Not running on handler Thread: " + Thread.currentThread().getName());
+        }
+    }
+
+    /** On ot-daemon died, unregister all registrations. */
+    public void onOtDaemonDied() {
+        checkOnHandlerThread();
+        for (int i = 0; i < mRegistrationListeners.size(); ++i) {
+            try {
+                mNsdManager.unregisterService(mRegistrationListeners.valueAt(i));
+            } catch (IllegalArgumentException e) {
+                Log.i(
+                        TAG,
+                        "Failed to unregister."
+                                + " Listener ID: "
+                                + mRegistrationListeners.keyAt(i)
+                                + " serviceInfo: "
+                                + mRegistrationListeners.valueAt(i).mServiceInfo,
+                        e);
+            }
+        }
+        mRegistrationListeners.clear();
+    }
+
+    // TODO: b/323300118 - Remove this mechanism when the race condition in NsdManager is fixed.
+    /** Fetch the first job from the queue and run it. See the class doc for more details. */
+    private void peekAndRun() {
+        if (mRegistrationJobs.isEmpty()) {
+            return;
+        }
+        Runnable job = mRegistrationJobs.getFirst();
+        job.run();
+    }
+
+    // TODO: b/323300118 - Remove this mechanism when the race condition in NsdManager is fixed.
+    /**
+     * Pop the first job from the queue and run the next job. See the class doc for more details.
+     */
+    private void popAndRunNext() {
+        if (mRegistrationJobs.isEmpty()) {
+            Log.i(TAG, "No registration jobs when trying to pop and run next.");
+            return;
+        }
+        mRegistrationJobs.removeFirst();
+        peekAndRun();
+    }
+
+    private void postRegistrationJob(Runnable registrationJob) {
+        mHandler.post(
+                () -> {
+                    mRegistrationJobs.addLast(registrationJob);
+                    if (mRegistrationJobs.size() == 1) {
+                        peekAndRun();
+                    }
+                });
+    }
+
+    private final class RegistrationListener implements NsdManager.RegistrationListener {
+        private final NsdServiceInfo mServiceInfo;
+        private final int mListenerId;
+        private final INsdStatusReceiver mRegistrationReceiver;
+        private final List<INsdStatusReceiver> mUnregistrationReceivers;
+
+        RegistrationListener(
+                @NonNull NsdServiceInfo serviceInfo,
+                int listenerId,
+                @NonNull INsdStatusReceiver registrationReceiver) {
+            mServiceInfo = serviceInfo;
+            mListenerId = listenerId;
+            mRegistrationReceiver = registrationReceiver;
+            mUnregistrationReceivers = new ArrayList<>();
+        }
+
+        void addUnregistrationReceiver(@NonNull INsdStatusReceiver unregistrationReceiver) {
+            mUnregistrationReceivers.add(unregistrationReceiver);
+        }
+
+        @Override
+        public void onRegistrationFailed(NsdServiceInfo serviceInfo, int errorCode) {
+            checkOnHandlerThread();
+            mRegistrationListeners.remove(mListenerId);
+            Log.i(
+                    TAG,
+                    "Failed to register listener ID: "
+                            + mListenerId
+                            + " error code: "
+                            + errorCode
+                            + " serviceInfo: "
+                            + serviceInfo);
+            try {
+                mRegistrationReceiver.onError(errorCode);
+            } catch (RemoteException ignored) {
+                // do nothing if the client is dead
+            }
+            popAndRunNext();
+        }
+
+        @Override
+        public void onUnregistrationFailed(NsdServiceInfo serviceInfo, int errorCode) {
+            checkOnHandlerThread();
+            for (INsdStatusReceiver receiver : mUnregistrationReceivers) {
+                Log.i(
+                        TAG,
+                        "Failed to unregister."
+                                + "Listener ID: "
+                                + mListenerId
+                                + ", error code: "
+                                + errorCode
+                                + ", serviceInfo: "
+                                + serviceInfo);
+                try {
+                    receiver.onError(errorCode);
+                } catch (RemoteException ignored) {
+                    // do nothing if the client is dead
+                }
+            }
+            popAndRunNext();
+        }
+
+        @Override
+        public void onServiceRegistered(NsdServiceInfo serviceInfo) {
+            checkOnHandlerThread();
+            Log.i(
+                    TAG,
+                    "Registered successfully. "
+                            + "Listener ID: "
+                            + mListenerId
+                            + ", serviceInfo: "
+                            + serviceInfo);
+            try {
+                mRegistrationReceiver.onSuccess();
+            } catch (RemoteException ignored) {
+                // do nothing if the client is dead
+            }
+            popAndRunNext();
+        }
+
+        @Override
+        public void onServiceUnregistered(NsdServiceInfo serviceInfo) {
+            checkOnHandlerThread();
+            for (INsdStatusReceiver receiver : mUnregistrationReceivers) {
+                Log.i(
+                        TAG,
+                        "Unregistered successfully. "
+                                + "Listener ID: "
+                                + mListenerId
+                                + ", serviceInfo: "
+                                + serviceInfo);
+                try {
+                    receiver.onSuccess();
+                } catch (RemoteException ignored) {
+                    // do nothing if the client is dead
+                }
+            }
+            mRegistrationListeners.remove(mListenerId);
+            popAndRunNext();
+        }
+    }
+}
diff --git a/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java b/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java
index 1c51c42..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;
@@ -27,6 +26,9 @@
 import static android.net.thread.ActiveOperationalDataset.MESH_LOCAL_PREFIX_FIRST_BYTE;
 import static android.net.thread.ActiveOperationalDataset.SecurityPolicy.DEFAULT_ROTATION_TIME_HOURS;
 import static android.net.thread.ThreadNetworkController.DEVICE_ROLE_DETACHED;
+import static android.net.thread.ThreadNetworkController.STATE_DISABLED;
+import static android.net.thread.ThreadNetworkController.STATE_DISABLING;
+import static android.net.thread.ThreadNetworkController.STATE_ENABLED;
 import static android.net.thread.ThreadNetworkController.THREAD_VERSION_1_3;
 import static android.net.thread.ThreadNetworkException.ERROR_ABORTED;
 import static android.net.thread.ThreadNetworkException.ERROR_BUSY;
@@ -35,20 +37,26 @@
 import static android.net.thread.ThreadNetworkException.ERROR_REJECTED_BY_PEER;
 import static android.net.thread.ThreadNetworkException.ERROR_RESOURCE_EXHAUSTED;
 import static android.net.thread.ThreadNetworkException.ERROR_RESPONSE_BAD_FORMAT;
+import static android.net.thread.ThreadNetworkException.ERROR_THREAD_DISABLED;
 import static android.net.thread.ThreadNetworkException.ERROR_TIMEOUT;
 import static android.net.thread.ThreadNetworkException.ERROR_UNSUPPORTED_CHANNEL;
+import static android.net.thread.ThreadNetworkManager.DISALLOW_THREAD_NETWORK;
 import static android.net.thread.ThreadNetworkManager.PERMISSION_THREAD_NETWORK_PRIVILEGED;
 
 import static com.android.server.thread.openthread.IOtDaemon.ErrorCode.OT_ERROR_ABORT;
 import static com.android.server.thread.openthread.IOtDaemon.ErrorCode.OT_ERROR_BUSY;
-import static com.android.server.thread.openthread.IOtDaemon.ErrorCode.OT_ERROR_DETACHED;
+import static com.android.server.thread.openthread.IOtDaemon.ErrorCode.OT_ERROR_FAILED_PRECONDITION;
 import static com.android.server.thread.openthread.IOtDaemon.ErrorCode.OT_ERROR_INVALID_STATE;
 import static com.android.server.thread.openthread.IOtDaemon.ErrorCode.OT_ERROR_NO_BUFS;
 import static com.android.server.thread.openthread.IOtDaemon.ErrorCode.OT_ERROR_PARSE;
 import static com.android.server.thread.openthread.IOtDaemon.ErrorCode.OT_ERROR_REASSEMBLY_TIMEOUT;
 import static com.android.server.thread.openthread.IOtDaemon.ErrorCode.OT_ERROR_REJECTED;
 import static com.android.server.thread.openthread.IOtDaemon.ErrorCode.OT_ERROR_RESPONSE_TIMEOUT;
+import static com.android.server.thread.openthread.IOtDaemon.ErrorCode.OT_ERROR_THREAD_DISABLED;
 import static com.android.server.thread.openthread.IOtDaemon.ErrorCode.OT_ERROR_UNSUPPORTED_CHANNEL;
+import static com.android.server.thread.openthread.IOtDaemon.OT_STATE_DISABLED;
+import static com.android.server.thread.openthread.IOtDaemon.OT_STATE_DISABLING;
+import static com.android.server.thread.openthread.IOtDaemon.OT_STATE_ENABLED;
 import static com.android.server.thread.openthread.IOtDaemon.TUN_IF_NAME;
 
 import android.Manifest.permission;
@@ -56,9 +64,12 @@
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
 import android.annotation.TargetApi;
+import android.content.BroadcastReceiver;
 import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
 import android.net.ConnectivityManager;
-import android.net.IpPrefix;
+import android.net.InetAddresses;
 import android.net.LinkAddress;
 import android.net.LinkProperties;
 import android.net.LocalNetworkConfig;
@@ -71,7 +82,6 @@
 import android.net.NetworkProvider;
 import android.net.NetworkRequest;
 import android.net.NetworkScore;
-import android.net.RouteInfo;
 import android.net.TestNetworkSpecifier;
 import android.net.thread.ActiveOperationalDataset;
 import android.net.thread.ActiveOperationalDataset.SecurityPolicy;
@@ -92,12 +102,15 @@
 import android.os.Looper;
 import android.os.RemoteException;
 import android.os.SystemClock;
+import android.os.UserManager;
 import android.util.Log;
 import android.util.SparseArray;
 
 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;
 import com.android.server.thread.openthread.IOtDaemonCallback;
 import com.android.server.thread.openthread.IOtStatusReceiver;
@@ -111,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;
@@ -143,12 +157,9 @@
     private final ConnectivityManager mConnectivityManager;
     private final TunInterfaceController mTunIfController;
     private final InfraInterfaceController mInfraIfController;
-    private final LinkProperties mLinkProperties = new LinkProperties();
+    private final NsdPublisher mNsdPublisher;
     private final OtDaemonCallbackProxy mOtDaemonCallbackProxy = new OtDaemonCallbackProxy();
 
-    // TODO(b/308310823): read supported channel from Thread dameon
-    private final int mSupportedChannelMask = 0x07FFF800; // from channel 11 to 26
-
     @Nullable private IOtDaemon mOtDaemon;
     @Nullable private NetworkAgent mNetworkAgent;
     @Nullable private NetworkAgent mTestNetworkAgent;
@@ -160,6 +171,9 @@
     private UpstreamNetworkCallback mUpstreamNetworkCallback;
     private TestNetworkSpecifier mUpstreamTestNetworkSpecifier;
     private final HashMap<Network, String> mNetworkToInterface;
+    private final ThreadPersistentSettings mPersistentSettings;
+    private final UserManager mUserManager;
+    private boolean mUserRestricted;
 
     private BorderRouterConfigurationParcel mBorderRouterConfig;
 
@@ -171,7 +185,10 @@
             Supplier<IOtDaemon> otDaemonSupplier,
             ConnectivityManager connectivityManager,
             TunInterfaceController tunIfController,
-            InfraInterfaceController infraIfController) {
+            InfraInterfaceController infraIfController,
+            ThreadPersistentSettings persistentSettings,
+            NsdPublisher nsdPublisher,
+            UserManager userManager) {
         mContext = context;
         mHandler = handler;
         mNetworkProvider = networkProvider;
@@ -182,22 +199,30 @@
         mUpstreamNetworkRequest = newUpstreamNetworkRequest();
         mNetworkToInterface = new HashMap<Network, String>();
         mBorderRouterConfig = new BorderRouterConfigurationParcel();
+        mPersistentSettings = persistentSettings;
+        mNsdPublisher = nsdPublisher;
+        mUserManager = userManager;
     }
 
-    public static ThreadNetworkControllerService newInstance(Context context) {
+    public static ThreadNetworkControllerService newInstance(
+            Context context, ThreadPersistentSettings persistentSettings) {
         HandlerThread handlerThread = new HandlerThread("ThreadHandlerThread");
         handlerThread.start();
+        Handler handler = new Handler(handlerThread.getLooper());
         NetworkProvider networkProvider =
                 new NetworkProvider(context, handlerThread.getLooper(), "ThreadNetworkProvider");
 
         return new ThreadNetworkControllerService(
                 context,
-                new Handler(handlerThread.getLooper()),
+                handler,
                 networkProvider,
                 () -> IOtDaemon.Stub.asInterface(ServiceManagerWrapper.waitForService("ot_daemon")),
                 context.getSystemService(ConnectivityManager.class),
                 new TunInterfaceController(TUN_IF_NAME),
-                new InfraInterfaceController());
+                new InfraInterfaceController(),
+                persistentSettings,
+                NsdPublisher.newInstance(context, handler),
+                context.getSystemService(UserManager.class));
     }
 
     private static Inet6Address bytesToInet6Address(byte[] addressBytes) {
@@ -273,19 +298,23 @@
         if (otDaemon == null) {
             throw new RemoteException("Internal error: failed to start OT daemon");
         }
-        otDaemon.initialize(mTunIfController.getTunFd());
+        otDaemon.initialize(mTunIfController.getTunFd(), isEnabled(), mNsdPublisher);
         otDaemon.registerStateCallback(mOtDaemonCallbackProxy, -1);
         otDaemon.asBinder().linkToDeath(() -> mHandler.post(this::onOtDaemonDied), 0);
         mOtDaemon = otDaemon;
         return mOtDaemon;
     }
 
-    // TODO(b/309792480): restarts the OT daemon service
     private void onOtDaemonDied() {
-        Log.w(TAG, "OT daemon became dead, clean up...");
+        checkOnHandlerThread();
+        Log.w(TAG, "OT daemon is dead, clean up and restart it...");
+
         OperationReceiverWrapper.onOtDaemonDied();
         mOtDaemonCallbackProxy.onOtDaemonDied();
+        mTunIfController.onOtDaemonDied();
+        mNsdPublisher.onOtDaemonDied();
         mOtDaemon = null;
+        initializeOtDaemon();
     }
 
     public void initialize() {
@@ -298,16 +327,111 @@
                         throw new IllegalStateException(
                                 "Failed to create Thread tunnel interface", e);
                     }
-                    mLinkProperties.setInterfaceName(TUN_IF_NAME);
-                    mLinkProperties.setMtu(TunInterfaceController.MTU);
                     mConnectivityManager.registerNetworkProvider(mNetworkProvider);
                     requestUpstreamNetwork();
                     requestThreadNetwork();
-
+                    mUserRestricted = isThreadUserRestricted();
+                    registerUserRestrictionsReceiver();
                     initializeOtDaemon();
                 });
     }
 
+    public void setEnabled(boolean isEnabled, @NonNull IOperationReceiver receiver) {
+        enforceAllPermissionsGranted(PERMISSION_THREAD_NETWORK_PRIVILEGED);
+
+        mHandler.post(
+                () ->
+                        setEnabledInternal(
+                                isEnabled,
+                                true /* persist */,
+                                new OperationReceiverWrapper(receiver)));
+    }
+
+    private void setEnabledInternal(
+            boolean isEnabled, boolean persist, @NonNull OperationReceiverWrapper receiver) {
+        if (isEnabled && isThreadUserRestricted()) {
+            receiver.onError(
+                    ERROR_FAILED_PRECONDITION,
+                    "Cannot enable Thread: forbidden by user restriction");
+            return;
+        }
+
+        if (persist) {
+            // The persistent setting keeps the desired enabled state, thus it's set regardless
+            // the otDaemon set enabled state operation succeeded or not, so that it can recover
+            // to the desired value after reboot.
+            mPersistentSettings.put(ThreadPersistentSettings.THREAD_ENABLED.key, isEnabled);
+        }
+
+        try {
+            getOtDaemon().setThreadEnabled(isEnabled, newOtStatusReceiver(receiver));
+        } catch (RemoteException e) {
+            Log.e(TAG, "otDaemon.setThreadEnabled failed", e);
+            receiver.onError(ERROR_INTERNAL_ERROR, "Thread stack error");
+        }
+    }
+
+    private void registerUserRestrictionsReceiver() {
+        mContext.registerReceiver(
+                new BroadcastReceiver() {
+                    @Override
+                    public void onReceive(Context context, Intent intent) {
+                        onUserRestrictionsChanged(isThreadUserRestricted());
+                    }
+                },
+                new IntentFilter(UserManager.ACTION_USER_RESTRICTIONS_CHANGED),
+                null /* broadcastPermission */,
+                mHandler);
+    }
+
+    private void onUserRestrictionsChanged(boolean newUserRestrictedState) {
+        checkOnHandlerThread();
+        if (mUserRestricted == newUserRestrictedState) {
+            return;
+        }
+        Log.i(
+                TAG,
+                "Thread user restriction changed: "
+                        + mUserRestricted
+                        + " -> "
+                        + newUserRestrictedState);
+        mUserRestricted = newUserRestrictedState;
+
+        final boolean isEnabled = isEnabled();
+        final IOperationReceiver receiver =
+                new IOperationReceiver.Stub() {
+                    @Override
+                    public void onSuccess() {
+                        Log.d(
+                                TAG,
+                                (isEnabled ? "Enabled" : "Disabled")
+                                        + " Thread due to user restriction change");
+                    }
+
+                    @Override
+                    public void onError(int otError, String messages) {
+                        Log.e(
+                                TAG,
+                                "Failed to "
+                                        + (isEnabled ? "enable" : "disable")
+                                        + " Thread for user restriction change");
+                    }
+                };
+        // Do not save the user restriction state to persistent settings so that the user
+        // configuration won't be overwritten
+        setEnabledInternal(isEnabled, false /* persist */, new OperationReceiverWrapper(receiver));
+    }
+
+    /** Returns {@code true} if Thread is set enabled. */
+    private boolean isEnabled() {
+        return !mUserRestricted && mPersistentSettings.get(ThreadPersistentSettings.THREAD_ENABLED);
+    }
+
+    /** Returns {@code true} if Thread has been restricted for the user. */
+    private boolean isThreadUserRestricted() {
+        return mUserManager.hasUserRestriction(DISALLOW_THREAD_NETWORK);
+    }
+
     private void requestUpstreamNetwork() {
         if (mUpstreamNetworkCallback != null) {
             throw new AssertionError("The upstream network request is already there.");
@@ -330,25 +454,31 @@
         @Override
         public void onAvailable(@NonNull Network network) {
             checkOnHandlerThread();
-            Log.i(TAG, "onAvailable: " + network);
+            Log.i(TAG, "Upstream network available: " + network);
         }
 
         @Override
         public void onLost(@NonNull Network network) {
             checkOnHandlerThread();
-            Log.i(TAG, "onLost: " + network);
+            Log.i(TAG, "Upstream network lost: " + network);
+
+            // TODO: disable border routing when upsteam network disconnected
         }
 
         @Override
         public void onLinkPropertiesChanged(
                 @NonNull Network network, @NonNull LinkProperties linkProperties) {
             checkOnHandlerThread();
-            Log.i(
-                    TAG,
-                    String.format(
-                            "onLinkPropertiesChanged: {network: %s, interface: %s}",
-                            network, linkProperties.getInterfaceName()));
-            mNetworkToInterface.put(network, linkProperties.getInterfaceName());
+
+            String existingIfName = mNetworkToInterface.get(network);
+            String newIfName = linkProperties.getInterfaceName();
+            if (Objects.equals(existingIfName, newIfName)) {
+                return;
+            }
+            Log.i(TAG, "Upstream network changed: " + existingIfName + " -> " + newIfName);
+            mNetworkToInterface.put(network, newIfName);
+
+            // TODO: disable border routing if netIfName is null
             if (network.equals(mUpstreamNetwork)) {
                 enableBorderRouting(mNetworkToInterface.get(mUpstreamNetwork));
             }
@@ -359,14 +489,20 @@
         @Override
         public void onAvailable(@NonNull Network network) {
             checkOnHandlerThread();
-            Log.i(TAG, "onAvailable: Thread network Available");
+            Log.i(TAG, "Thread network available: " + network);
         }
 
         @Override
         public void onLocalNetworkInfoChanged(
                 @NonNull Network network, @NonNull LocalNetworkInfo localNetworkInfo) {
             checkOnHandlerThread();
-            Log.i(TAG, "onLocalNetworkInfoChanged: " + localNetworkInfo);
+            Log.i(
+                    TAG,
+                    "LocalNetworkInfo of Thread network changed: {threadNetwork: "
+                            + network
+                            + ", localNetworkInfo: "
+                            + localNetworkInfo
+                            + "}");
             if (localNetworkInfo.getUpstreamNetwork() == null) {
                 mUpstreamNetwork = null;
                 return;
@@ -418,7 +554,7 @@
                 mHandler.getLooper(),
                 TAG,
                 netCaps,
-                mLinkProperties,
+                mTunIfController.getLinkProperties(),
                 newLocalNetworkConfig(),
                 score,
                 new NetworkAgentConfig.Builder().build(),
@@ -449,46 +585,6 @@
         mNetworkAgent = null;
     }
 
-    private void updateTunInterfaceAddress(LinkAddress linkAddress, boolean isAdded) {
-        try {
-            if (isAdded) {
-                mTunIfController.addAddress(linkAddress);
-            } else {
-                mTunIfController.removeAddress(linkAddress);
-            }
-        } catch (IOException e) {
-            Log.e(
-                    TAG,
-                    String.format(
-                            "Failed to %s Thread tun interface address %s",
-                            (isAdded ? "add" : "remove"), linkAddress),
-                    e);
-        }
-    }
-
-    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
-        // registered
-        if (mNetworkAgent != null) {
-            mNetworkAgent.sendLinkProperties(mLinkProperties);
-        }
-    }
-
     @Override
     public int getThreadVersion() {
         return THREAD_VERSION_1_3;
@@ -497,26 +593,51 @@
     @Override
     public void createRandomizedDataset(
             String networkName, IActiveOperationalDatasetReceiver receiver) {
-        mHandler.post(
-                () -> {
-                    ActiveOperationalDataset dataset =
-                            createRandomizedDatasetInternal(
-                                    networkName,
-                                    mSupportedChannelMask,
-                                    Instant.now(),
-                                    new Random(),
-                                    new SecureRandom());
-                    try {
-                        receiver.onSuccess(dataset);
-                    } catch (RemoteException e) {
-                        // The client is dead, do nothing
-                    }
-                });
+        ActiveOperationalDatasetReceiverWrapper receiverWrapper =
+                new ActiveOperationalDatasetReceiverWrapper(receiver);
+        mHandler.post(() -> createRandomizedDatasetInternal(networkName, receiverWrapper));
     }
 
-    private static ActiveOperationalDataset createRandomizedDatasetInternal(
+    private void createRandomizedDatasetInternal(
+            String networkName, @NonNull ActiveOperationalDatasetReceiverWrapper receiver) {
+        checkOnHandlerThread();
+
+        try {
+            getOtDaemon().getChannelMasks(newChannelMasksReceiver(networkName, receiver));
+        } catch (RemoteException e) {
+            Log.e(TAG, "otDaemon.getChannelMasks failed", e);
+            receiver.onError(ERROR_INTERNAL_ERROR, "Thread stack error");
+        }
+    }
+
+    private IChannelMasksReceiver newChannelMasksReceiver(
+            String networkName, ActiveOperationalDatasetReceiverWrapper receiver) {
+        return new IChannelMasksReceiver.Stub() {
+            @Override
+            public void onSuccess(int supportedChannelMask, int preferredChannelMask) {
+                ActiveOperationalDataset dataset =
+                        createRandomizedDataset(
+                                networkName,
+                                supportedChannelMask,
+                                preferredChannelMask,
+                                Instant.now(),
+                                new Random(),
+                                new SecureRandom());
+
+                receiver.onSuccess(dataset);
+            }
+
+            @Override
+            public void onError(int errorCode, String errorMessage) {
+                receiver.onError(otErrorToAndroidError(errorCode), errorMessage);
+            }
+        };
+    }
+
+    private static ActiveOperationalDataset createRandomizedDataset(
             String networkName,
             int supportedChannelMask,
+            int preferredChannelMask,
             Instant now,
             Random random,
             SecureRandom secureRandom) {
@@ -526,6 +647,7 @@
 
         final SparseArray<byte[]> channelMask = new SparseArray<>(1);
         channelMask.put(CHANNEL_PAGE_24_GHZ, channelMaskToByteArray(supportedChannelMask));
+        final int channel = selectChannel(supportedChannelMask, preferredChannelMask, random);
 
         final byte[] securityFlags = new byte[] {(byte) 0xff, (byte) 0xf8};
 
@@ -536,7 +658,7 @@
                 .setExtendedPanId(newRandomBytes(random, LENGTH_EXTENDED_PAN_ID))
                 .setPanId(panId)
                 .setNetworkName(networkName)
-                .setChannel(CHANNEL_PAGE_24_GHZ, selectRandomChannel(supportedChannelMask, random))
+                .setChannel(CHANNEL_PAGE_24_GHZ, channel)
                 .setChannelMask(channelMask)
                 .setPskc(newRandomBytes(secureRandom, LENGTH_PSKC))
                 .setNetworkKey(newRandomBytes(secureRandom, LENGTH_NETWORK_KEY))
@@ -545,6 +667,18 @@
                 .build();
     }
 
+    private static int selectChannel(
+            int supportedChannelMask, int preferredChannelMask, Random random) {
+        // If the preferred channel mask is not empty, select a random channel from it, otherwise
+        // choose one from the supported channel mask.
+        preferredChannelMask = preferredChannelMask & supportedChannelMask;
+        if (preferredChannelMask == 0) {
+            preferredChannelMask = supportedChannelMask;
+        }
+
+        return selectRandomChannel(preferredChannelMask, random);
+    }
+
     private static byte[] newRandomBytes(Random random, int length) {
         byte[] result = new byte[length];
         random.nextBytes(result);
@@ -644,9 +778,6 @@
                 return ERROR_ABORTED;
             case OT_ERROR_BUSY:
                 return ERROR_BUSY;
-            case OT_ERROR_DETACHED:
-            case OT_ERROR_INVALID_STATE:
-                return ERROR_FAILED_PRECONDITION;
             case OT_ERROR_NO_BUFS:
                 return ERROR_RESOURCE_EXHAUSTED;
             case OT_ERROR_PARSE:
@@ -658,6 +789,11 @@
                 return ERROR_REJECTED_BY_PEER;
             case OT_ERROR_UNSUPPORTED_CHANNEL:
                 return ERROR_UNSUPPORTED_CHANNEL;
+            case OT_ERROR_THREAD_DISABLED:
+                return ERROR_THREAD_DISABLED;
+            case OT_ERROR_FAILED_PRECONDITION:
+                return ERROR_FAILED_PRECONDITION;
+            case OT_ERROR_INVALID_STATE:
             default:
                 return ERROR_INTERNAL_ERROR;
         }
@@ -792,7 +928,7 @@
                 && infraIfName.equals(mBorderRouterConfig.infraInterfaceName)) {
             return;
         }
-        Log.i(TAG, "enableBorderRouting on AIL: " + infraIfName);
+        Log.i(TAG, "Enable border routing on AIL: " + infraIfName);
         try {
             mBorderRouterConfig.infraInterfaceName = infraIfName;
             mBorderRouterConfig.infraInterfaceIcmp6Socket =
@@ -823,7 +959,7 @@
     private void handleThreadInterfaceStateChanged(boolean isUp) {
         try {
             mTunIfController.setInterfaceUp(isUp);
-            Log.d(TAG, "Thread network interface becomes " + (isUp ? "up" : "down"));
+            Log.i(TAG, "Thread TUN interface becomes " + (isUp ? "up" : "down"));
         } catch (IOException e) {
             Log.e(TAG, "Failed to handle Thread interface state changes", e);
         }
@@ -831,13 +967,13 @@
 
     private void handleDeviceRoleChanged(@DeviceRole int deviceRole) {
         if (ThreadNetworkController.isAttached(deviceRole)) {
-            Log.d(TAG, "Attached to the Thread network");
+            Log.i(TAG, "Attached to the Thread network");
 
             // This is an idempotent method which can be called for multiple times when the device
             // is already attached (e.g. going from Child to Router)
             registerThreadNetwork();
         } else {
-            Log.d(TAG, "Detached from the Thread network");
+            Log.i(TAG, "Detached from the Thread network");
 
             // This is an idempotent method which can be called for multiple times when the device
             // is already detached or stopped
@@ -854,15 +990,17 @@
         }
 
         LinkAddress linkAddress = newLinkAddress(addressInfo);
-        Log.d(TAG, (isAdded ? "Adding" : "Removing") + " address " + linkAddress);
+        if (isAdded) {
+            mTunIfController.addAddress(linkAddress);
+        } else {
+            mTunIfController.removeAddress(linkAddress);
+        }
 
-        updateTunInterfaceAddress(linkAddress, isAdded);
-        updateNetworkLinkProperties(linkAddress, isAdded);
-    }
-
-    private boolean isMulticastForwardingEnabled() {
-        return !(mUpstreamMulticastRoutingConfig.getForwardingMode() == FORWARD_NONE
-                && mDownstreamMulticastRoutingConfig.getForwardingMode() == FORWARD_NONE);
+        // The OT daemon can send link property updates before the networkAgent is
+        // registered
+        if (mNetworkAgent != null) {
+            mNetworkAgent.sendLinkProperties(mTunIfController.getLinkProperties());
+        }
     }
 
     private void sendLocalNetworkConfig() {
@@ -874,73 +1012,44 @@
         Log.d(TAG, "Sent localNetworkConfig: " + localNetworkConfig);
     }
 
-    private void handleMulticastForwardingStateChanged(boolean isEnabled) {
-        if (isMulticastForwardingEnabled() == isEnabled) {
-            return;
-        }
-        if (isEnabled) {
+    private void handleMulticastForwardingChanged(BackboneRouterState state) {
+        MulticastRoutingConfig upstreamMulticastRoutingConfig;
+        MulticastRoutingConfig downstreamMulticastRoutingConfig;
+
+        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();
-        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) {
+    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 {
@@ -1001,6 +1110,15 @@
             }
         }
 
+        private void notifyThreadEnabledUpdated(IStateCallback callback, int enabledState) {
+            try {
+                callback.onThreadEnableStateChanged(enabledState);
+                Log.i(TAG, "onThreadEnableStateChanged " + enabledState);
+            } catch (RemoteException ignored) {
+                // do nothing if the client is dead
+            }
+        }
+
         public void unregisterStateCallback(IStateCallback callback) {
             checkOnHandlerThread();
             if (!mStateCallbacks.containsKey(callback)) {
@@ -1065,6 +1183,31 @@
         }
 
         @Override
+        public void onThreadEnabledChanged(int state) {
+            mHandler.post(() -> onThreadEnabledChangedInternal(state));
+        }
+
+        private void onThreadEnabledChangedInternal(int state) {
+            checkOnHandlerThread();
+            for (IStateCallback callback : mStateCallbacks.keySet()) {
+                notifyThreadEnabledUpdated(callback, otStateToAndroidState(state));
+            }
+        }
+
+        private static int otStateToAndroidState(int state) {
+            switch (state) {
+                case OT_STATE_ENABLED:
+                    return STATE_ENABLED;
+                case OT_STATE_DISABLED:
+                    return STATE_DISABLED;
+                case OT_STATE_DISABLING:
+                    return STATE_DISABLING;
+                default:
+                    throw new IllegalArgumentException("Unknown ot state " + state);
+            }
+        }
+
+        @Override
         public void onStateChanged(OtDaemonState newState, long listenerId) {
             mHandler.post(() -> onStateChangedInternal(newState, listenerId));
         }
@@ -1074,7 +1217,6 @@
             onInterfaceStateChanged(newState.isInterfaceUp);
             onDeviceRoleChanged(newState.deviceRole, listenerId);
             onPartitionIdChanged(newState.partitionId, listenerId);
-            onMulticastForwardingStateChanged(newState.multicastForwardingEnabled);
             mState = newState;
 
             ActiveOperationalDataset newActiveDataset;
@@ -1183,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/service/java/com/android/server/thread/ThreadNetworkCountryCode.java b/thread/service/java/com/android/server/thread/ThreadNetworkCountryCode.java
index b7b6233..ffa7b44 100644
--- a/thread/service/java/com/android/server/thread/ThreadNetworkCountryCode.java
+++ b/thread/service/java/com/android/server/thread/ThreadNetworkCountryCode.java
@@ -31,6 +31,7 @@
 import android.net.wifi.WifiManager;
 import android.net.wifi.WifiManager.ActiveCountryCodeChangedCallback;
 import android.os.Build;
+import android.sysprop.ThreadNetworkProperties;
 import android.telephony.SubscriptionInfo;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
@@ -53,8 +54,8 @@
 
 /**
  * Provide functions for making changes to Thread Network country code. This Country Code is from
- * location, WiFi or telephony configuration. This class sends Country Code to Thread Network native
- * layer.
+ * location, WiFi, telephony or OEM configuration. This class sends Country Code to Thread Network
+ * native layer.
  *
  * <p>This class is thread-safe.
  */
@@ -77,6 +78,7 @@
             value = {
                 COUNTRY_CODE_SOURCE_DEFAULT,
                 COUNTRY_CODE_SOURCE_LOCATION,
+                COUNTRY_CODE_SOURCE_OEM,
                 COUNTRY_CODE_SOURCE_OVERRIDE,
                 COUNTRY_CODE_SOURCE_TELEPHONY,
                 COUNTRY_CODE_SOURCE_TELEPHONY_LAST,
@@ -86,6 +88,7 @@
 
     private static final String COUNTRY_CODE_SOURCE_DEFAULT = "Default";
     private static final String COUNTRY_CODE_SOURCE_LOCATION = "Location";
+    private static final String COUNTRY_CODE_SOURCE_OEM = "Oem";
     private static final String COUNTRY_CODE_SOURCE_OVERRIDE = "Override";
     private static final String COUNTRY_CODE_SOURCE_TELEPHONY = "Telephony";
     private static final String COUNTRY_CODE_SOURCE_TELEPHONY_LAST = "TelephonyLast";
@@ -111,6 +114,7 @@
     @Nullable private CountryCodeInfo mWifiCountryCodeInfo;
     @Nullable private CountryCodeInfo mTelephonyCountryCodeInfo;
     @Nullable private CountryCodeInfo mTelephonyLastCountryCodeInfo;
+    @Nullable private CountryCodeInfo mOemCountryCodeInfo;
 
     /** Container class to store Thread country code information. */
     private static final class CountryCodeInfo {
@@ -118,13 +122,35 @@
         @CountryCodeSource private String mSource;
         private final Instant mUpdatedTimestamp;
 
+        /**
+         * Constructs a new {@code CountryCodeInfo} from the given country code, country code source
+         * and country coode created time.
+         *
+         * @param countryCode a String representation of the country code as defined in ISO 3166.
+         * @param countryCodeSource a String representation of country code source.
+         * @param instant a Instant representation of the time when the country code was created.
+         * @throws IllegalArgumentException if {@code countryCode} contains invalid country code.
+         */
         public CountryCodeInfo(
                 String countryCode, @CountryCodeSource String countryCodeSource, Instant instant) {
+            if (!isValidCountryCode(countryCode)) {
+                throw new IllegalArgumentException("Country code is invalid: " + countryCode);
+            }
+
             mCountryCode = countryCode;
             mSource = countryCodeSource;
             mUpdatedTimestamp = instant;
         }
 
+        /**
+         * Constructs a new {@code CountryCodeInfo} from the given country code, country code
+         * source. The updated timestamp of the country code will be set to the time when {@code
+         * CountryCodeInfo} was constructed.
+         *
+         * @param countryCode a String representation of the country code as defined in ISO 3166.
+         * @param countryCodeSource a String representation of country code source.
+         * @throws IllegalArgumentException if {@code countryCode} contains invalid country code.
+         */
         public CountryCodeInfo(String countryCode, @CountryCodeSource String countryCodeSource) {
             this(countryCode, countryCodeSource, Instant.now());
         }
@@ -188,7 +214,8 @@
             WifiManager wifiManager,
             Context context,
             TelephonyManager telephonyManager,
-            SubscriptionManager subscriptionManager) {
+            SubscriptionManager subscriptionManager,
+            @Nullable String oemCountryCode) {
         mLocationManager = locationManager;
         mThreadNetworkControllerService = threadNetworkControllerService;
         mGeocoder = geocoder;
@@ -197,6 +224,10 @@
         mContext = context;
         mTelephonyManager = telephonyManager;
         mSubscriptionManager = subscriptionManager;
+
+        if (oemCountryCode != null) {
+            mOemCountryCodeInfo = new CountryCodeInfo(oemCountryCode, COUNTRY_CODE_SOURCE_OEM);
+        }
     }
 
     public static ThreadNetworkCountryCode newInstance(
@@ -209,7 +240,8 @@
                 context.getSystemService(WifiManager.class),
                 context,
                 context.getSystemService(TelephonyManager.class),
-                context.getSystemService(SubscriptionManager.class));
+                context.getSystemService(SubscriptionManager.class),
+                ThreadNetworkProperties.country_code().orElse(null));
     }
 
     /** Sets up this country code module to listen to location country code changes. */
@@ -278,7 +310,14 @@
         public void onActiveCountryCodeChanged(String countryCode) {
             Log.d(TAG, "Wifi country code is changed to " + countryCode);
             synchronized ("ThreadNetworkCountryCode.this") {
-                mWifiCountryCodeInfo = new CountryCodeInfo(countryCode, COUNTRY_CODE_SOURCE_WIFI);
+                if (isValidCountryCode(countryCode)) {
+                    mWifiCountryCodeInfo =
+                            new CountryCodeInfo(countryCode, COUNTRY_CODE_SOURCE_WIFI);
+                } else {
+                    Log.w(TAG, "WiFi country code " + countryCode + " is invalid");
+                    mWifiCountryCodeInfo = null;
+                }
+
                 updateCountryCode(false /* forceUpdate */);
             }
         }
@@ -418,6 +457,9 @@
      *       number will be used.
      *   <li>5. Location country code - Country code retrieved from LocationManager passive location
      *       provider.
+     *   <li>6. OEM country code - Country code retrieved from the system property
+     *       `threadnetwork.country_code`.
+     *   <li>7. Default country code `WW`.
      * </ul>
      *
      * @return the selected country code information.
@@ -443,6 +485,10 @@
             return mLocationCountryCodeInfo;
         }
 
+        if (mOemCountryCodeInfo != null) {
+            return mOemCountryCodeInfo;
+        }
+
         return DEFAULT_COUNTRY_CODE_INFO;
     }
 
@@ -531,13 +577,14 @@
     /** Dumps the current state of this ThreadNetworkCountryCode object. */
     public synchronized void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         pw.println("---- Dump of ThreadNetworkCountryCode begin ----");
-        pw.println("mOverrideCountryCodeInfo: " + mOverrideCountryCodeInfo);
+        pw.println("mOverrideCountryCodeInfo        : " + mOverrideCountryCodeInfo);
         pw.println("mTelephonyCountryCodeSlotInfoMap: " + mTelephonyCountryCodeSlotInfoMap);
-        pw.println("mTelephonyCountryCodeInfo: " + mTelephonyCountryCodeInfo);
-        pw.println("mWifiCountryCodeInfo: " + mWifiCountryCodeInfo);
-        pw.println("mTelephonyLastCountryCodeInfo: " + mTelephonyLastCountryCodeInfo);
-        pw.println("mLocationCountryCodeInfo: " + mLocationCountryCodeInfo);
-        pw.println("mCurrentCountryCodeInfo: " + mCurrentCountryCodeInfo);
+        pw.println("mTelephonyCountryCodeInfo       : " + mTelephonyCountryCodeInfo);
+        pw.println("mWifiCountryCodeInfo            : " + mWifiCountryCodeInfo);
+        pw.println("mTelephonyLastCountryCodeInfo   : " + mTelephonyLastCountryCodeInfo);
+        pw.println("mLocationCountryCodeInfo        : " + mLocationCountryCodeInfo);
+        pw.println("mOemCountryCodeInfo             : " + mOemCountryCodeInfo);
+        pw.println("mCurrentCountryCodeInfo         : " + mCurrentCountryCodeInfo);
         pw.println("---- Dump of ThreadNetworkCountryCode end ------");
     }
 }
diff --git a/thread/service/java/com/android/server/thread/ThreadNetworkService.java b/thread/service/java/com/android/server/thread/ThreadNetworkService.java
index 53f2d4f..5664922 100644
--- a/thread/service/java/com/android/server/thread/ThreadNetworkService.java
+++ b/thread/service/java/com/android/server/thread/ThreadNetworkService.java
@@ -40,11 +40,13 @@
     private final Context mContext;
     @Nullable private ThreadNetworkCountryCode mCountryCode;
     @Nullable private ThreadNetworkControllerService mControllerService;
+    private final ThreadPersistentSettings mPersistentSettings;
     @Nullable private ThreadNetworkShellCommand mShellCommand;
 
     /** Creates a new {@link ThreadNetworkService} object. */
     public ThreadNetworkService(Context context) {
         mContext = context;
+        mPersistentSettings = ThreadPersistentSettings.newInstance(context);
     }
 
     /**
@@ -54,7 +56,9 @@
      */
     public void onBootPhase(int phase) {
         if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
-            mControllerService = ThreadNetworkControllerService.newInstance(mContext);
+            mPersistentSettings.initialize();
+            mControllerService =
+                    ThreadNetworkControllerService.newInstance(mContext, mPersistentSettings);
             mControllerService.initialize();
         } else if (phase == SystemService.PHASE_BOOT_COMPLETED) {
             // Country code initialization is delayed to the BOOT_COMPLETED phase because it will
diff --git a/thread/service/java/com/android/server/thread/ThreadPersistentSettings.java b/thread/service/java/com/android/server/thread/ThreadPersistentSettings.java
index d32f0bf..aba4193 100644
--- a/thread/service/java/com/android/server/thread/ThreadPersistentSettings.java
+++ b/thread/service/java/com/android/server/thread/ThreadPersistentSettings.java
@@ -16,15 +16,23 @@
 
 package com.android.server.thread;
 
+import static com.android.net.module.util.DeviceConfigUtils.TETHERING_MODULE_NAME;
+
 import android.annotation.Nullable;
+import android.content.ApexEnvironment;
+import android.content.Context;
 import android.os.PersistableBundle;
 import android.util.AtomicFile;
 import android.util.Log;
 
+import com.android.connectivity.resources.R;
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.connectivity.ConnectivityResources;
 
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
+import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileNotFoundException;
 import java.io.FileOutputStream;
@@ -39,7 +47,7 @@
 public class ThreadPersistentSettings {
     private static final String TAG = "ThreadPersistentSettings";
     /** File name used for storing settings. */
-    public static final String FILE_NAME = "ThreadPersistentSettings.xml";
+    private static final String FILE_NAME = "ThreadPersistentSettings.xml";
     /** Current config store data version. This will be incremented for any additions. */
     private static final int CURRENT_SETTINGS_STORE_DATA_VERSION = 1;
     /**
@@ -62,16 +70,29 @@
     @GuardedBy("mLock")
     private final PersistableBundle mSettings = new PersistableBundle();
 
-    public ThreadPersistentSettings(AtomicFile atomicFile) {
+    private final ConnectivityResources mResources;
+
+    public static ThreadPersistentSettings newInstance(Context context) {
+        return new ThreadPersistentSettings(
+                new AtomicFile(new File(getOrCreateThreadNetworkDir(), FILE_NAME)),
+                new ConnectivityResources(context));
+    }
+
+    @VisibleForTesting
+    ThreadPersistentSettings(AtomicFile atomicFile, ConnectivityResources resources) {
         mAtomicFile = atomicFile;
+        mResources = resources;
     }
 
     /** Initialize the settings by reading from the settings file. */
     public void initialize() {
         readFromStoreFile();
         synchronized (mLock) {
-            if (mSettings.isEmpty()) {
-                put(THREAD_ENABLED.key, THREAD_ENABLED.defaultValue);
+            if (!mSettings.containsKey(THREAD_ENABLED.key)) {
+                Log.i(TAG, "\"thread_enabled\" is missing in settings file, using default value");
+                put(
+                        THREAD_ENABLED.key,
+                        mResources.get().getBoolean(R.bool.config_thread_default_enabled));
             }
         }
     }
@@ -240,4 +261,19 @@
             throw e;
         }
     }
+
+    /** Get device protected storage dir for the tethering apex. */
+    private static File getOrCreateThreadNetworkDir() {
+        final File threadnetworkDir;
+        final File apexDataDir =
+                ApexEnvironment.getApexEnvironment(TETHERING_MODULE_NAME)
+                        .getDeviceProtectedDataDir();
+        threadnetworkDir = new File(apexDataDir, "thread");
+
+        if (threadnetworkDir.exists() || threadnetworkDir.mkdirs()) {
+            return threadnetworkDir;
+        }
+        throw new IllegalStateException(
+                "Cannot write into thread network data directory: " + threadnetworkDir);
+    }
 }
diff --git a/thread/service/java/com/android/server/thread/TunInterfaceController.java b/thread/service/java/com/android/server/thread/TunInterfaceController.java
index 7223b2a..b29a54f 100644
--- a/thread/service/java/com/android/server/thread/TunInterfaceController.java
+++ b/thread/service/java/com/android/server/thread/TunInterfaceController.java
@@ -17,7 +17,10 @@
 package com.android.server.thread;
 
 import android.annotation.Nullable;
+import android.net.IpPrefix;
 import android.net.LinkAddress;
+import android.net.LinkProperties;
+import android.net.RouteInfo;
 import android.net.util.SocketUtils;
 import android.os.ParcelFileDescriptor;
 import android.os.SystemClock;
@@ -31,6 +34,7 @@
 
 import java.io.FileDescriptor;
 import java.io.IOException;
+import java.io.InterruptedIOException;
 
 /** Controller for virtual/tunnel network interfaces. */
 public class TunInterfaceController {
@@ -43,13 +47,21 @@
     }
 
     private final String mIfName;
+    private final LinkProperties mLinkProperties = new LinkProperties();
     private ParcelFileDescriptor mParcelTunFd;
     private FileDescriptor mNetlinkSocket;
     private static int sNetlinkSeqNo = 0;
 
     /** Creates a new {@link TunInterfaceController} instance for given interface. */
     public TunInterfaceController(String interfaceName) {
-        this.mIfName = interfaceName;
+        mIfName = interfaceName;
+        mLinkProperties.setInterfaceName(mIfName);
+        mLinkProperties.setMtu(MTU);
+    }
+
+    /** Returns link properties of the Thread TUN interface. */
+    public LinkProperties getLinkProperties() {
+        return mLinkProperties;
     }
 
     /**
@@ -87,13 +99,18 @@
 
     /** Sets the interface up or down according to {@code isUp}. */
     public void setInterfaceUp(boolean isUp) throws IOException {
+        if (!isUp) {
+            for (LinkAddress address : mLinkProperties.getAllLinkAddresses()) {
+                removeAddress(address);
+            }
+        }
         nativeSetInterfaceUp(mIfName, isUp);
     }
 
     private native void nativeSetInterfaceUp(String interfaceName, boolean isUp) throws IOException;
 
     /** Adds a new address to the interface. */
-    public void addAddress(LinkAddress address) throws IOException {
+    public void addAddress(LinkAddress address) {
         Log.d(TAG, "Adding address " + address + " with flags: " + address.getFlags());
 
         long validLifetimeSeconds;
@@ -121,7 +138,7 @@
 
         byte[] message =
                 RtNetlinkAddressMessage.newRtmNewAddressMessage(
-                        sNetlinkSeqNo,
+                        sNetlinkSeqNo++,
                         address.getAddress(),
                         (short) address.getPrefixLength(),
                         address.getFlags(),
@@ -131,13 +148,51 @@
                         preferredLifetimeSeconds);
         try {
             Os.write(mNetlinkSocket, message, 0, message.length);
-        } catch (ErrnoException e) {
-            throw new IOException("Failed to send netlink message", e);
+        } catch (ErrnoException | InterruptedIOException e) {
+            Log.e(TAG, "Failed to add address " + address, e);
+            return;
         }
+        mLinkProperties.addLinkAddress(address);
+        mLinkProperties.addRoute(getRouteForAddress(address));
     }
 
     /** Removes an address from the interface. */
-    public void removeAddress(LinkAddress address) throws IOException {
-        // TODO(b/263222068): remove address with netlink
+    public void removeAddress(LinkAddress address) {
+        Log.d(TAG, "Removing address " + address);
+        byte[] message =
+                RtNetlinkAddressMessage.newRtmDelAddressMessage(
+                        sNetlinkSeqNo++,
+                        address.getAddress(),
+                        (short) address.getPrefixLength(),
+                        Os.if_nametoindex(mIfName));
+
+        // Intentionally update the mLinkProperties before send netlink message because the
+        // address is already removed from ot-daemon and apps can't reach to the address even
+        // when the netlink request below fails
+        mLinkProperties.removeLinkAddress(address);
+        mLinkProperties.removeRoute(getRouteForAddress(address));
+        try {
+            Os.write(mNetlinkSocket, message, 0, message.length);
+        } catch (ErrnoException | InterruptedIOException e) {
+            Log.e(TAG, "Failed to remove address " + address, e);
+        }
+    }
+
+    private RouteInfo getRouteForAddress(LinkAddress linkAddress) {
+        return new RouteInfo(
+                new IpPrefix(linkAddress.getAddress(), linkAddress.getPrefixLength()),
+                null,
+                mIfName,
+                RouteInfo.RTN_UNICAST,
+                MTU);
+    }
+
+    /** Called by {@link ThreadNetworkControllerService} to do clean up when ot-daemon is dead. */
+    public void onOtDaemonDied() {
+        try {
+            setInterfaceUp(false);
+        } catch (IOException e) {
+            Log.e(TAG, "Failed to set Thread TUN interface down");
+        }
     }
 }
diff --git a/thread/service/proguard.flags b/thread/service/proguard.flags
deleted file mode 100644
index 5028982..0000000
--- a/thread/service/proguard.flags
+++ /dev/null
@@ -1,4 +0,0 @@
-# Ensure the callback methods are not stripped
--keepclassmembers class **.ThreadNetworkControllerService$ThreadNetworkCallback {
-    *;
-}
diff --git a/thread/tests/cts/Android.bp b/thread/tests/cts/Android.bp
index 3cf31e5..5890d26 100644
--- a/thread/tests/cts/Android.bp
+++ b/thread/tests/cts/Android.bp
@@ -15,12 +15,13 @@
 //
 
 package {
+    default_team: "trendy_team_fwk_thread_network",
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
+// TODO: add this test to the CTS test suite
 android_test {
     name: "CtsThreadNetworkTestCases",
-    defaults: ["cts_defaults"],
     min_sdk_version: "33",
     sdk_version: "test_current",
     manifest: "AndroidManifest.xml",
@@ -29,8 +30,8 @@
         "src/**/*.java",
     ],
     test_suites: [
-        "cts",
         "general-tests",
+        "mcts-tethering",
         "mts-tethering",
     ],
     static_libs: [
@@ -40,14 +41,16 @@
         "guava",
         "guava-android-testlib",
         "net-tests-utils",
+        "ThreadNetworkTestUtils",
         "truth",
     ],
     libs: [
         "android.test.base",
         "android.test.runner",
-        "framework-connectivity-module-api-stubs-including-flagged"
+        "framework-connectivity-module-api-stubs-including-flagged",
     ],
     // Test coverage system runs on different devices. Need to
     // compile for all architectures.
     compile_multilib: "both",
+    platform_apis: true,
 }
diff --git a/thread/tests/cts/AndroidManifest.xml b/thread/tests/cts/AndroidManifest.xml
index 4370fe3..1541bf5 100644
--- a/thread/tests/cts/AndroidManifest.xml
+++ b/thread/tests/cts/AndroidManifest.xml
@@ -19,6 +19,9 @@
     xmlns:android="http://schemas.android.com/apk/res/android"
     package="android.net.thread.cts">
 
+    <uses-permission android:name="android.permission.INTERNET"/>
+    <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE"/>
+
     <application android:debuggable="true">
         <uses-library android:name="android.test.runner" />
     </application>
diff --git a/thread/tests/cts/src/android/net/thread/cts/ThreadNetworkControllerTest.java b/thread/tests/cts/src/android/net/thread/cts/ThreadNetworkControllerTest.java
index 7a6c9aa..36ce4d5 100644
--- a/thread/tests/cts/src/android/net/thread/cts/ThreadNetworkControllerTest.java
+++ b/thread/tests/cts/src/android/net/thread/cts/ThreadNetworkControllerTest.java
@@ -16,11 +16,19 @@
 
 package android.net.thread.cts;
 
+import static android.Manifest.permission.ACCESS_NETWORK_STATE;
+import static android.net.thread.ThreadNetworkController.DEVICE_ROLE_CHILD;
+import static android.net.thread.ThreadNetworkController.DEVICE_ROLE_LEADER;
+import static android.net.thread.ThreadNetworkController.DEVICE_ROLE_ROUTER;
 import static android.net.thread.ThreadNetworkController.DEVICE_ROLE_STOPPED;
+import static android.net.thread.ThreadNetworkController.STATE_DISABLED;
+import static android.net.thread.ThreadNetworkController.STATE_DISABLING;
+import static android.net.thread.ThreadNetworkController.STATE_ENABLED;
 import static android.net.thread.ThreadNetworkController.THREAD_VERSION_1_3;
 import static android.net.thread.ThreadNetworkException.ERROR_ABORTED;
 import static android.net.thread.ThreadNetworkException.ERROR_FAILED_PRECONDITION;
 import static android.net.thread.ThreadNetworkException.ERROR_REJECTED_BY_PEER;
+import static android.net.thread.ThreadNetworkException.ERROR_THREAD_DISABLED;
 
 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
 
@@ -29,17 +37,20 @@
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.util.concurrent.MoreExecutors.directExecutor;
 
+import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.fail;
 import static org.junit.Assume.assumeNotNull;
 
 import static java.util.concurrent.TimeUnit.MILLISECONDS;
 
-import android.Manifest.permission;
 import android.content.Context;
 import android.net.ConnectivityManager;
 import android.net.Network;
 import android.net.NetworkCapabilities;
 import android.net.NetworkRequest;
+import android.net.nsd.NsdManager;
+import android.net.nsd.NsdServiceInfo;
 import android.net.thread.ActiveOperationalDataset;
 import android.net.thread.OperationalDatasetTimestamp;
 import android.net.thread.PendingOperationalDataset;
@@ -48,17 +59,20 @@
 import android.net.thread.ThreadNetworkController.StateCallback;
 import android.net.thread.ThreadNetworkException;
 import android.net.thread.ThreadNetworkManager;
+import android.net.thread.utils.TapTestNetworkTracker;
 import android.os.Build;
+import android.os.HandlerThread;
 import android.os.OutcomeReceiver;
 
+import androidx.annotation.NonNull;
 import androidx.test.core.app.ApplicationProvider;
 import androidx.test.filters.LargeTest;
 
+import com.android.net.module.util.ArrayTrackRecord;
 import com.android.testutils.DevSdkIgnoreRule;
 import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
 import com.android.testutils.DevSdkIgnoreRunner;
-
-import com.google.common.util.concurrent.SettableFuture;
+import com.android.testutils.FunctionalUtils.ThrowingRunnable;
 
 import org.junit.After;
 import org.junit.Before;
@@ -66,14 +80,22 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.nio.charset.StandardCharsets;
 import java.time.Duration;
 import java.time.Instant;
+import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Map;
+import java.util.Random;
 import java.util.Set;
+import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
+import java.util.concurrent.TimeoutException;
+import java.util.function.Predicate;
 
 /** CTS tests for {@link ThreadNetworkController}. */
 @LargeTest
@@ -81,52 +103,721 @@
 @IgnoreUpTo(Build.VERSION_CODES.TIRAMISU) // Thread is available on only U+
 public class ThreadNetworkControllerTest {
     private static final int JOIN_TIMEOUT_MILLIS = 30 * 1000;
+    private static final int LEAVE_TIMEOUT_MILLIS = 2_000;
+    private static final int MIGRATION_TIMEOUT_MILLIS = 40 * 1_000;
     private static final int NETWORK_CALLBACK_TIMEOUT_MILLIS = 10 * 1000;
-    private static final int CALLBACK_TIMEOUT_MILLIS = 1000;
-    private static final String PERMISSION_THREAD_NETWORK_PRIVILEGED =
+    private static final int CALLBACK_TIMEOUT_MILLIS = 1_000;
+    private static final int ENABLED_TIMEOUT_MILLIS = 2_000;
+    private static final int SERVICE_DISCOVERY_TIMEOUT_MILLIS = 30_000;
+    private static final String MESHCOP_SERVICE_TYPE = "_meshcop._udp";
+    private static final String THREAD_NETWORK_PRIVILEGED =
             "android.permission.THREAD_NETWORK_PRIVILEGED";
 
     @Rule public DevSdkIgnoreRule mIgnoreRule = new DevSdkIgnoreRule();
 
     private final Context mContext = ApplicationProvider.getApplicationContext();
     private ExecutorService mExecutor;
-    private ThreadNetworkManager mManager;
+    private ThreadNetworkController mController;
+    private NsdManager mNsdManager;
 
     private Set<String> mGrantedPermissions;
+    private HandlerThread mHandlerThread;
+    private TapTestNetworkTracker mTestNetworkTracker;
 
     @Before
-    public void setUp() {
-        mExecutor = Executors.newSingleThreadExecutor();
-        mManager = mContext.getSystemService(ThreadNetworkManager.class);
-        mGrantedPermissions = new HashSet<String>();
+    public void setUp() throws Exception {
+        ThreadNetworkManager manager = mContext.getSystemService(ThreadNetworkManager.class);
+        if (manager != null) {
+            mController = manager.getAllThreadNetworkControllers().get(0);
+        }
 
         // TODO: we will also need it in tearDown(), it's better to have a Rule to skip
         // tests if a feature is not available.
-        assumeNotNull(mManager);
+        assumeNotNull(mController);
+
+        mGrantedPermissions = new HashSet<String>();
+        mExecutor = Executors.newSingleThreadExecutor();
+        mNsdManager = mContext.getSystemService(NsdManager.class);
+        mHandlerThread = new HandlerThread(this.getClass().getSimpleName());
+        mHandlerThread.start();
+
+        setEnabledAndWait(mController, true);
     }
 
     @After
     public void tearDown() throws Exception {
-        if (mManager != null) {
-            leaveAndWait();
+        if (mController == null) {
+            return;
+        }
+        dropAllPermissions();
+        leaveAndWait(mController);
+        tearDownTestNetwork();
+    }
+
+    @Test
+    public void getThreadVersion_returnsAtLeastThreadVersion1P3() {
+        assertThat(mController.getThreadVersion()).isAtLeast(THREAD_VERSION_1_3);
+    }
+
+    @Test
+    public void registerStateCallback_permissionsGranted_returnsCurrentStates() throws Exception {
+        CompletableFuture<Integer> deviceRole = new CompletableFuture<>();
+        StateCallback callback = deviceRole::complete;
+
+        try {
+            runAsShell(
+                    ACCESS_NETWORK_STATE,
+                    () -> mController.registerStateCallback(mExecutor, callback));
+
+            assertThat(deviceRole.get(CALLBACK_TIMEOUT_MILLIS, MILLISECONDS))
+                    .isEqualTo(DEVICE_ROLE_STOPPED);
+        } finally {
+            runAsShell(ACCESS_NETWORK_STATE, () -> mController.unregisterStateCallback(callback));
+        }
+    }
+
+    @Test
+    public void registerStateCallback_returnsUpdatedEnabledStates() throws Exception {
+        CompletableFuture<Void> setFuture1 = new CompletableFuture<>();
+        CompletableFuture<Void> setFuture2 = new CompletableFuture<>();
+        EnabledStateListener listener = new EnabledStateListener(mController);
+
+        try {
+            runAsShell(
+                    THREAD_NETWORK_PRIVILEGED,
+                    () -> {
+                        mController.setEnabled(false, mExecutor, newOutcomeReceiver(setFuture1));
+                    });
+            setFuture1.get(ENABLED_TIMEOUT_MILLIS, MILLISECONDS);
+
+            runAsShell(
+                    THREAD_NETWORK_PRIVILEGED,
+                    () -> {
+                        mController.setEnabled(true, mExecutor, newOutcomeReceiver(setFuture2));
+                    });
+            setFuture2.get(ENABLED_TIMEOUT_MILLIS, MILLISECONDS);
+
+            listener.expectThreadEnabledState(STATE_ENABLED);
+            listener.expectThreadEnabledState(STATE_DISABLING);
+            listener.expectThreadEnabledState(STATE_DISABLED);
+            listener.expectThreadEnabledState(STATE_ENABLED);
+        } finally {
+            listener.unregisterStateCallback();
+        }
+    }
+
+    @Test
+    public void registerStateCallback_noPermissions_throwsSecurityException() throws Exception {
+        dropAllPermissions();
+
+        assertThrows(
+                SecurityException.class,
+                () -> mController.registerStateCallback(mExecutor, role -> {}));
+    }
+
+    @Test
+    public void registerStateCallback_alreadyRegistered_throwsIllegalArgumentException()
+            throws Exception {
+        grantPermissions(ACCESS_NETWORK_STATE);
+        CompletableFuture<Integer> deviceRole = new CompletableFuture<>();
+        StateCallback callback = role -> deviceRole.complete(role);
+
+        mController.registerStateCallback(mExecutor, callback);
+
+        assertThrows(
+                IllegalArgumentException.class,
+                () -> mController.registerStateCallback(mExecutor, callback));
+    }
+
+    @Test
+    public void unregisterStateCallback_noPermissions_throwsSecurityException() throws Exception {
+        CompletableFuture<Integer> deviceRole = new CompletableFuture<>();
+        StateCallback callback = role -> deviceRole.complete(role);
+        runAsShell(
+                ACCESS_NETWORK_STATE, () -> mController.registerStateCallback(mExecutor, callback));
+
+        try {
             dropAllPermissions();
+            assertThrows(
+                    SecurityException.class, () -> mController.unregisterStateCallback(callback));
+        } finally {
+            runAsShell(ACCESS_NETWORK_STATE, () -> mController.unregisterStateCallback(callback));
         }
     }
 
-    private List<ThreadNetworkController> getAllControllers() {
-        return mManager.getAllThreadNetworkControllers();
+    @Test
+    public void unregisterStateCallback_callbackRegistered_success() throws Exception {
+        grantPermissions(ACCESS_NETWORK_STATE);
+        CompletableFuture<Integer> deviceRole = new CompletableFuture<>();
+        StateCallback callback = role -> deviceRole.complete(role);
+
+        assertDoesNotThrow(() -> mController.registerStateCallback(mExecutor, callback));
+        mController.unregisterStateCallback(callback);
     }
 
-    private void leaveAndWait() throws Exception {
-        grantPermissions(PERMISSION_THREAD_NETWORK_PRIVILEGED);
+    @Test
+    public void unregisterStateCallback_callbackNotRegistered_throwsIllegalArgumentException()
+            throws Exception {
+        CompletableFuture<Integer> deviceRole = new CompletableFuture<>();
+        StateCallback callback = role -> deviceRole.complete(role);
 
-        for (ThreadNetworkController controller : getAllControllers()) {
-            SettableFuture<Void> future = SettableFuture.create();
-            controller.leave(mExecutor, future::set);
-            future.get();
+        assertThrows(
+                IllegalArgumentException.class,
+                () -> mController.unregisterStateCallback(callback));
+    }
+
+    @Test
+    public void unregisterStateCallback_alreadyUnregistered_throwsIllegalArgumentException()
+            throws Exception {
+        grantPermissions(ACCESS_NETWORK_STATE);
+        CompletableFuture<Integer> deviceRole = new CompletableFuture<>();
+        StateCallback callback = deviceRole::complete;
+        mController.registerStateCallback(mExecutor, callback);
+        mController.unregisterStateCallback(callback);
+
+        assertThrows(
+                IllegalArgumentException.class,
+                () -> mController.unregisterStateCallback(callback));
+    }
+
+    @Test
+    public void registerOperationalDatasetCallback_permissionsGranted_returnsCurrentStates()
+            throws Exception {
+        grantPermissions(ACCESS_NETWORK_STATE, THREAD_NETWORK_PRIVILEGED);
+        CompletableFuture<ActiveOperationalDataset> activeFuture = new CompletableFuture<>();
+        CompletableFuture<PendingOperationalDataset> pendingFuture = new CompletableFuture<>();
+        var callback = newDatasetCallback(activeFuture, pendingFuture);
+
+        try {
+            mController.registerOperationalDatasetCallback(mExecutor, callback);
+
+            assertThat(activeFuture.get(CALLBACK_TIMEOUT_MILLIS, MILLISECONDS)).isNull();
+            assertThat(pendingFuture.get(CALLBACK_TIMEOUT_MILLIS, MILLISECONDS)).isNull();
+        } finally {
+            mController.unregisterOperationalDatasetCallback(callback);
         }
     }
 
+    @Test
+    public void registerOperationalDatasetCallback_noPermissions_throwsSecurityException()
+            throws Exception {
+        dropAllPermissions();
+        CompletableFuture<ActiveOperationalDataset> activeFuture = new CompletableFuture<>();
+        CompletableFuture<PendingOperationalDataset> pendingFuture = new CompletableFuture<>();
+        var callback = newDatasetCallback(activeFuture, pendingFuture);
+
+        assertThrows(
+                SecurityException.class,
+                () -> mController.registerOperationalDatasetCallback(mExecutor, callback));
+    }
+
+    @Test
+    public void unregisterOperationalDatasetCallback_callbackRegistered_success() throws Exception {
+        grantPermissions(ACCESS_NETWORK_STATE, THREAD_NETWORK_PRIVILEGED);
+        CompletableFuture<ActiveOperationalDataset> activeFuture = new CompletableFuture<>();
+        CompletableFuture<PendingOperationalDataset> pendingFuture = new CompletableFuture<>();
+        var callback = newDatasetCallback(activeFuture, pendingFuture);
+        mController.registerOperationalDatasetCallback(mExecutor, callback);
+
+        assertDoesNotThrow(() -> mController.unregisterOperationalDatasetCallback(callback));
+    }
+
+    @Test
+    public void unregisterOperationalDatasetCallback_noPermissions_throwsSecurityException()
+            throws Exception {
+        CompletableFuture<ActiveOperationalDataset> activeFuture = new CompletableFuture<>();
+        CompletableFuture<PendingOperationalDataset> pendingFuture = new CompletableFuture<>();
+        var callback = newDatasetCallback(activeFuture, pendingFuture);
+        runAsShell(
+                ACCESS_NETWORK_STATE,
+                THREAD_NETWORK_PRIVILEGED,
+                () -> mController.registerOperationalDatasetCallback(mExecutor, callback));
+
+        try {
+            dropAllPermissions();
+            assertThrows(
+                    SecurityException.class,
+                    () -> mController.unregisterOperationalDatasetCallback(callback));
+        } finally {
+            runAsShell(
+                    ACCESS_NETWORK_STATE,
+                    THREAD_NETWORK_PRIVILEGED,
+                    () -> mController.unregisterOperationalDatasetCallback(callback));
+        }
+    }
+
+    private static <V> OutcomeReceiver<V, ThreadNetworkException> newOutcomeReceiver(
+            CompletableFuture<V> future) {
+        return new OutcomeReceiver<V, ThreadNetworkException>() {
+            @Override
+            public void onResult(V result) {
+                future.complete(result);
+            }
+
+            @Override
+            public void onError(ThreadNetworkException e) {
+                future.completeExceptionally(e);
+            }
+        };
+    }
+
+    @Test
+    public void join_withPrivilegedPermission_success() throws Exception {
+        ActiveOperationalDataset activeDataset = newRandomizedDataset("TestNet", mController);
+        CompletableFuture<Void> joinFuture = new CompletableFuture<>();
+
+        runAsShell(
+                THREAD_NETWORK_PRIVILEGED,
+                () -> mController.join(activeDataset, mExecutor, newOutcomeReceiver(joinFuture)));
+        joinFuture.get(JOIN_TIMEOUT_MILLIS, MILLISECONDS);
+
+        assertThat(isAttached(mController)).isTrue();
+        assertThat(getActiveOperationalDataset(mController)).isEqualTo(activeDataset);
+    }
+
+    @Test
+    public void join_withoutPrivilegedPermission_throwsSecurityException() throws Exception {
+        dropAllPermissions();
+        ActiveOperationalDataset activeDataset = newRandomizedDataset("TestNet", mController);
+
+        assertThrows(
+                SecurityException.class, () -> mController.join(activeDataset, mExecutor, v -> {}));
+    }
+
+    @Test
+    public void join_threadDisabled_failsWithErrorThreadDisabled() throws Exception {
+        setEnabledAndWait(mController, false);
+        ActiveOperationalDataset activeDataset = newRandomizedDataset("TestNet", mController);
+        CompletableFuture<Void> joinFuture = new CompletableFuture<>();
+
+        runAsShell(
+                THREAD_NETWORK_PRIVILEGED,
+                () -> mController.join(activeDataset, mExecutor, newOutcomeReceiver(joinFuture)));
+
+        var thrown =
+                assertThrows(
+                        ExecutionException.class,
+                        () -> joinFuture.get(JOIN_TIMEOUT_MILLIS, MILLISECONDS));
+        var threadException = (ThreadNetworkException) thrown.getCause();
+        assertThat(threadException.getErrorCode()).isEqualTo(ERROR_THREAD_DISABLED);
+    }
+
+    @Test
+    public void join_concurrentRequests_firstOneIsAborted() throws Exception {
+        final byte[] KEY_1 = new byte[] {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1};
+        final byte[] KEY_2 = new byte[] {2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2};
+        ActiveOperationalDataset activeDataset1 =
+                new ActiveOperationalDataset.Builder(newRandomizedDataset("TestNet", mController))
+                        .setNetworkKey(KEY_1)
+                        .build();
+        ActiveOperationalDataset activeDataset2 =
+                new ActiveOperationalDataset.Builder(activeDataset1).setNetworkKey(KEY_2).build();
+        CompletableFuture<Void> joinFuture1 = new CompletableFuture<>();
+        CompletableFuture<Void> joinFuture2 = new CompletableFuture<>();
+
+        runAsShell(
+                THREAD_NETWORK_PRIVILEGED,
+                () -> {
+                    mController.join(activeDataset1, mExecutor, newOutcomeReceiver(joinFuture1));
+                    mController.join(activeDataset2, mExecutor, newOutcomeReceiver(joinFuture2));
+                });
+
+        var thrown =
+                assertThrows(
+                        ExecutionException.class,
+                        () -> joinFuture1.get(JOIN_TIMEOUT_MILLIS, MILLISECONDS));
+        var threadException = (ThreadNetworkException) thrown.getCause();
+        assertThat(threadException.getErrorCode()).isEqualTo(ERROR_ABORTED);
+        joinFuture2.get(JOIN_TIMEOUT_MILLIS, MILLISECONDS);
+        assertThat(isAttached(mController)).isTrue();
+        assertThat(getActiveOperationalDataset(mController)).isEqualTo(activeDataset2);
+    }
+
+    @Test
+    public void leave_withPrivilegedPermission_success() throws Exception {
+        CompletableFuture<Void> leaveFuture = new CompletableFuture<>();
+        joinRandomizedDatasetAndWait(mController);
+
+        runAsShell(
+                THREAD_NETWORK_PRIVILEGED,
+                () -> mController.leave(mExecutor, newOutcomeReceiver(leaveFuture)));
+        leaveFuture.get(LEAVE_TIMEOUT_MILLIS, MILLISECONDS);
+
+        assertThat(getDeviceRole(mController)).isEqualTo(DEVICE_ROLE_STOPPED);
+    }
+
+    @Test
+    public void leave_withoutPrivilegedPermission_throwsSecurityException() {
+        dropAllPermissions();
+
+        assertThrows(SecurityException.class, () -> mController.leave(mExecutor, v -> {}));
+    }
+
+    @Test
+    public void leave_threadDisabled_success() throws Exception {
+        setEnabledAndWait(mController, false);
+        CompletableFuture<Void> leaveFuture = new CompletableFuture<>();
+
+        leave(mController, newOutcomeReceiver(leaveFuture));
+        leaveFuture.get(LEAVE_TIMEOUT_MILLIS, MILLISECONDS);
+
+        assertThat(getDeviceRole(mController)).isEqualTo(DEVICE_ROLE_STOPPED);
+    }
+
+    @Test
+    public void leave_concurrentRequests_bothSuccess() throws Exception {
+        CompletableFuture<Void> leaveFuture1 = new CompletableFuture<>();
+        CompletableFuture<Void> leaveFuture2 = new CompletableFuture<>();
+        joinRandomizedDatasetAndWait(mController);
+
+        runAsShell(
+                THREAD_NETWORK_PRIVILEGED,
+                () -> {
+                    mController.leave(mExecutor, newOutcomeReceiver(leaveFuture1));
+                    mController.leave(mExecutor, newOutcomeReceiver(leaveFuture2));
+                });
+
+        leaveFuture1.get(LEAVE_TIMEOUT_MILLIS, MILLISECONDS);
+        leaveFuture2.get(LEAVE_TIMEOUT_MILLIS, MILLISECONDS);
+        assertThat(getDeviceRole(mController)).isEqualTo(DEVICE_ROLE_STOPPED);
+    }
+
+    @Test
+    public void scheduleMigration_withPrivilegedPermission_newDatasetApplied() throws Exception {
+        grantPermissions(ACCESS_NETWORK_STATE, THREAD_NETWORK_PRIVILEGED);
+        ActiveOperationalDataset activeDataset1 =
+                new ActiveOperationalDataset.Builder(newRandomizedDataset("TestNet", mController))
+                        .setActiveTimestamp(new OperationalDatasetTimestamp(1L, 0, false))
+                        .setExtendedPanId(new byte[] {1, 1, 1, 1, 1, 1, 1, 1})
+                        .build();
+        ActiveOperationalDataset activeDataset2 =
+                new ActiveOperationalDataset.Builder(activeDataset1)
+                        .setActiveTimestamp(new OperationalDatasetTimestamp(2L, 0, false))
+                        .setNetworkName("ThreadNet2")
+                        .build();
+        PendingOperationalDataset pendingDataset2 =
+                new PendingOperationalDataset(
+                        activeDataset2,
+                        OperationalDatasetTimestamp.fromInstant(Instant.now()),
+                        Duration.ofSeconds(30));
+        CompletableFuture<Void> joinFuture = new CompletableFuture<>();
+        CompletableFuture<Void> migrateFuture = new CompletableFuture<>();
+        mController.join(activeDataset1, mExecutor, newOutcomeReceiver(joinFuture));
+        joinFuture.get(JOIN_TIMEOUT_MILLIS, MILLISECONDS);
+
+        mController.scheduleMigration(
+                pendingDataset2, mExecutor, newOutcomeReceiver(migrateFuture));
+        migrateFuture.get(CALLBACK_TIMEOUT_MILLIS, MILLISECONDS);
+
+        CompletableFuture<Boolean> dataset2IsApplied = new CompletableFuture<>();
+        CompletableFuture<Boolean> pendingDatasetIsRemoved = new CompletableFuture<>();
+        OperationalDatasetCallback datasetCallback =
+                new OperationalDatasetCallback() {
+                    @Override
+                    public void onActiveOperationalDatasetChanged(
+                            ActiveOperationalDataset activeDataset) {
+                        if (activeDataset.equals(activeDataset2)) {
+                            dataset2IsApplied.complete(true);
+                        }
+                    }
+
+                    @Override
+                    public void onPendingOperationalDatasetChanged(
+                            PendingOperationalDataset pendingDataset) {
+                        if (pendingDataset == null) {
+                            pendingDatasetIsRemoved.complete(true);
+                        }
+                    }
+                };
+        mController.registerOperationalDatasetCallback(directExecutor(), datasetCallback);
+        try {
+            assertThat(dataset2IsApplied.get(MIGRATION_TIMEOUT_MILLIS, MILLISECONDS)).isTrue();
+            assertThat(pendingDatasetIsRemoved.get(CALLBACK_TIMEOUT_MILLIS, MILLISECONDS)).isTrue();
+        } finally {
+            mController.unregisterOperationalDatasetCallback(datasetCallback);
+        }
+    }
+
+    @Test
+    public void scheduleMigration_whenNotAttached_failWithPreconditionError() throws Exception {
+        grantPermissions(ACCESS_NETWORK_STATE, THREAD_NETWORK_PRIVILEGED);
+        PendingOperationalDataset pendingDataset =
+                new PendingOperationalDataset(
+                        newRandomizedDataset("TestNet", mController),
+                        OperationalDatasetTimestamp.fromInstant(Instant.now()),
+                        Duration.ofSeconds(30));
+        CompletableFuture<Void> migrateFuture = new CompletableFuture<>();
+
+        mController.scheduleMigration(pendingDataset, mExecutor, newOutcomeReceiver(migrateFuture));
+
+        ThreadNetworkException thrown =
+                (ThreadNetworkException)
+                        assertThrows(ExecutionException.class, migrateFuture::get).getCause();
+        assertThat(thrown.getErrorCode()).isEqualTo(ERROR_FAILED_PRECONDITION);
+    }
+
+    @Test
+    public void scheduleMigration_secondRequestHasSmallerTimestamp_rejectedByLeader()
+            throws Exception {
+        grantPermissions(ACCESS_NETWORK_STATE, THREAD_NETWORK_PRIVILEGED);
+        final ActiveOperationalDataset activeDataset =
+                new ActiveOperationalDataset.Builder(newRandomizedDataset("testNet", mController))
+                        .setActiveTimestamp(new OperationalDatasetTimestamp(1L, 0, false))
+                        .build();
+        ActiveOperationalDataset activeDataset1 =
+                new ActiveOperationalDataset.Builder(activeDataset)
+                        .setActiveTimestamp(new OperationalDatasetTimestamp(2L, 0, false))
+                        .setNetworkName("testNet1")
+                        .build();
+        PendingOperationalDataset pendingDataset1 =
+                new PendingOperationalDataset(
+                        activeDataset1,
+                        new OperationalDatasetTimestamp(100, 0, false),
+                        Duration.ofSeconds(30));
+        ActiveOperationalDataset activeDataset2 =
+                new ActiveOperationalDataset.Builder(activeDataset)
+                        .setActiveTimestamp(new OperationalDatasetTimestamp(3L, 0, false))
+                        .setNetworkName("testNet2")
+                        .build();
+        PendingOperationalDataset pendingDataset2 =
+                new PendingOperationalDataset(
+                        activeDataset2,
+                        new OperationalDatasetTimestamp(20, 0, false),
+                        Duration.ofSeconds(30));
+        CompletableFuture<Void> joinFuture = new CompletableFuture<>();
+        CompletableFuture<Void> migrateFuture1 = new CompletableFuture<>();
+        CompletableFuture<Void> migrateFuture2 = new CompletableFuture<>();
+        mController.join(activeDataset, mExecutor, newOutcomeReceiver(joinFuture));
+        joinFuture.get(JOIN_TIMEOUT_MILLIS, MILLISECONDS);
+
+        mController.scheduleMigration(
+                pendingDataset1, mExecutor, newOutcomeReceiver(migrateFuture1));
+        migrateFuture1.get(CALLBACK_TIMEOUT_MILLIS, MILLISECONDS);
+        mController.scheduleMigration(
+                pendingDataset2, mExecutor, newOutcomeReceiver(migrateFuture2));
+
+        ThreadNetworkException thrown =
+                (ThreadNetworkException)
+                        assertThrows(ExecutionException.class, migrateFuture2::get).getCause();
+        assertThat(thrown.getErrorCode()).isEqualTo(ERROR_REJECTED_BY_PEER);
+    }
+
+    @Test
+    public void scheduleMigration_secondRequestHasLargerTimestamp_newDatasetApplied()
+            throws Exception {
+        grantPermissions(ACCESS_NETWORK_STATE, THREAD_NETWORK_PRIVILEGED);
+        final ActiveOperationalDataset activeDataset =
+                new ActiveOperationalDataset.Builder(newRandomizedDataset("validName", mController))
+                        .setActiveTimestamp(new OperationalDatasetTimestamp(1L, 0, false))
+                        .build();
+        ActiveOperationalDataset activeDataset1 =
+                new ActiveOperationalDataset.Builder(activeDataset)
+                        .setActiveTimestamp(new OperationalDatasetTimestamp(2L, 0, false))
+                        .setNetworkName("testNet1")
+                        .build();
+        PendingOperationalDataset pendingDataset1 =
+                new PendingOperationalDataset(
+                        activeDataset1,
+                        new OperationalDatasetTimestamp(100, 0, false),
+                        Duration.ofSeconds(30));
+        ActiveOperationalDataset activeDataset2 =
+                new ActiveOperationalDataset.Builder(activeDataset)
+                        .setActiveTimestamp(new OperationalDatasetTimestamp(3L, 0, false))
+                        .setNetworkName("testNet2")
+                        .build();
+        PendingOperationalDataset pendingDataset2 =
+                new PendingOperationalDataset(
+                        activeDataset2,
+                        new OperationalDatasetTimestamp(200, 0, false),
+                        Duration.ofSeconds(30));
+        CompletableFuture<Void> joinFuture = new CompletableFuture<>();
+        CompletableFuture<Void> migrateFuture1 = new CompletableFuture<>();
+        CompletableFuture<Void> migrateFuture2 = new CompletableFuture<>();
+        mController.join(activeDataset, mExecutor, newOutcomeReceiver(joinFuture));
+        joinFuture.get(JOIN_TIMEOUT_MILLIS, MILLISECONDS);
+
+        mController.scheduleMigration(
+                pendingDataset1, mExecutor, newOutcomeReceiver(migrateFuture1));
+        migrateFuture1.get(CALLBACK_TIMEOUT_MILLIS, MILLISECONDS);
+        mController.scheduleMigration(
+                pendingDataset2, mExecutor, newOutcomeReceiver(migrateFuture2));
+        migrateFuture2.get(CALLBACK_TIMEOUT_MILLIS, MILLISECONDS);
+
+        CompletableFuture<Boolean> dataset2IsApplied = new CompletableFuture<>();
+        CompletableFuture<Boolean> pendingDatasetIsRemoved = new CompletableFuture<>();
+        OperationalDatasetCallback datasetCallback =
+                new OperationalDatasetCallback() {
+                    @Override
+                    public void onActiveOperationalDatasetChanged(
+                            ActiveOperationalDataset activeDataset) {
+                        if (activeDataset.equals(activeDataset2)) {
+                            dataset2IsApplied.complete(true);
+                        }
+                    }
+
+                    @Override
+                    public void onPendingOperationalDatasetChanged(
+                            PendingOperationalDataset pendingDataset) {
+                        if (pendingDataset == null) {
+                            pendingDatasetIsRemoved.complete(true);
+                        }
+                    }
+                };
+        mController.registerOperationalDatasetCallback(directExecutor(), datasetCallback);
+        try {
+            assertThat(dataset2IsApplied.get(MIGRATION_TIMEOUT_MILLIS, MILLISECONDS)).isTrue();
+            assertThat(pendingDatasetIsRemoved.get(CALLBACK_TIMEOUT_MILLIS, MILLISECONDS)).isTrue();
+        } finally {
+            mController.unregisterOperationalDatasetCallback(datasetCallback);
+        }
+    }
+
+    @Test
+    public void scheduleMigration_threadDisabled_failsWithErrorThreadDisabled() throws Exception {
+        ActiveOperationalDataset activeDataset = newRandomizedDataset("TestNet", mController);
+        PendingOperationalDataset pendingDataset =
+                new PendingOperationalDataset(
+                        activeDataset,
+                        OperationalDatasetTimestamp.fromInstant(Instant.now()),
+                        Duration.ofSeconds(30));
+        joinRandomizedDatasetAndWait(mController);
+        CompletableFuture<Void> migrationFuture = new CompletableFuture<>();
+
+        setEnabledAndWait(mController, false);
+
+        scheduleMigration(mController, pendingDataset, newOutcomeReceiver(migrationFuture));
+
+        ThreadNetworkException thrown =
+                (ThreadNetworkException)
+                        assertThrows(ExecutionException.class, migrationFuture::get).getCause();
+        assertThat(thrown.getErrorCode()).isEqualTo(ERROR_THREAD_DISABLED);
+    }
+
+    @Test
+    public void createRandomizedDataset_wrongNetworkNameLength_throwsIllegalArgumentException() {
+        assertThrows(
+                IllegalArgumentException.class,
+                () -> mController.createRandomizedDataset("", mExecutor, dataset -> {}));
+
+        assertThrows(
+                IllegalArgumentException.class,
+                () ->
+                        mController.createRandomizedDataset(
+                                "ANetNameIs17Bytes", mExecutor, dataset -> {}));
+    }
+
+    @Test
+    public void createRandomizedDataset_validNetworkName_success() throws Exception {
+        ActiveOperationalDataset dataset = newRandomizedDataset("validName", mController);
+
+        assertThat(dataset.getNetworkName()).isEqualTo("validName");
+        assertThat(dataset.getPanId()).isLessThan(0xffff);
+        assertThat(dataset.getChannelMask().size()).isAtLeast(1);
+        assertThat(dataset.getExtendedPanId()).hasLength(8);
+        assertThat(dataset.getNetworkKey()).hasLength(16);
+        assertThat(dataset.getPskc()).hasLength(16);
+        assertThat(dataset.getMeshLocalPrefix().getPrefixLength()).isEqualTo(64);
+        assertThat(dataset.getMeshLocalPrefix().getRawAddress()[0]).isEqualTo((byte) 0xfd);
+    }
+
+    @Test
+    public void setEnabled_permissionsGranted_succeeds() throws Exception {
+        CompletableFuture<Void> setFuture1 = new CompletableFuture<>();
+        CompletableFuture<Void> setFuture2 = new CompletableFuture<>();
+
+        runAsShell(
+                THREAD_NETWORK_PRIVILEGED,
+                () -> mController.setEnabled(false, mExecutor, newOutcomeReceiver(setFuture1)));
+        setFuture1.get(ENABLED_TIMEOUT_MILLIS, MILLISECONDS);
+        waitForEnabledState(mController, booleanToEnabledState(false));
+
+        runAsShell(
+                THREAD_NETWORK_PRIVILEGED,
+                () -> mController.setEnabled(true, mExecutor, newOutcomeReceiver(setFuture2)));
+        setFuture2.get(ENABLED_TIMEOUT_MILLIS, MILLISECONDS);
+        waitForEnabledState(mController, booleanToEnabledState(true));
+    }
+
+    @Test
+    public void setEnabled_noPermissions_throwsSecurityException() throws Exception {
+        CompletableFuture<Void> setFuture = new CompletableFuture<>();
+        assertThrows(
+                SecurityException.class,
+                () -> mController.setEnabled(false, mExecutor, newOutcomeReceiver(setFuture)));
+    }
+
+    @Test
+    public void setEnabled_disable_leavesThreadNetwork() throws Exception {
+        joinRandomizedDatasetAndWait(mController);
+        setEnabledAndWait(mController, false);
+        assertThat(getDeviceRole(mController)).isEqualTo(DEVICE_ROLE_STOPPED);
+    }
+
+    @Test
+    public void setEnabled_toggleAfterJoin_joinsThreadNetworkAgain() throws Exception {
+        joinRandomizedDatasetAndWait(mController);
+
+        setEnabledAndWait(mController, false);
+        assertThat(getDeviceRole(mController)).isEqualTo(DEVICE_ROLE_STOPPED);
+        setEnabledAndWait(mController, true);
+
+        runAsShell(ACCESS_NETWORK_STATE, () -> waitForAttachedState(mController));
+    }
+
+    @Test
+    public void setEnabled_enableFollowedByDisable_allSucceed() throws Exception {
+        joinRandomizedDatasetAndWait(mController);
+        CompletableFuture<Void> setFuture1 = new CompletableFuture<>();
+        CompletableFuture<Void> setFuture2 = new CompletableFuture<>();
+        EnabledStateListener listener = new EnabledStateListener(mController);
+        listener.expectThreadEnabledState(STATE_ENABLED);
+
+        runAsShell(
+                THREAD_NETWORK_PRIVILEGED,
+                () -> {
+                    mController.setEnabled(true, mExecutor, newOutcomeReceiver(setFuture1));
+                    mController.setEnabled(false, mExecutor, newOutcomeReceiver(setFuture2));
+                });
+        setFuture1.get(ENABLED_TIMEOUT_MILLIS, MILLISECONDS);
+        setFuture2.get(ENABLED_TIMEOUT_MILLIS, MILLISECONDS);
+
+        listener.expectThreadEnabledState(STATE_DISABLING);
+        listener.expectThreadEnabledState(STATE_DISABLED);
+        assertThat(getDeviceRole(mController)).isEqualTo(DEVICE_ROLE_STOPPED);
+        // FIXME: this is not called when a exception is thrown after the creation of `listener`
+        listener.unregisterStateCallback();
+    }
+
+    // TODO (b/322437869): add test case to verify when Thread is in DISABLING state, any commands
+    // (join/leave/scheduleMigration/setEnabled) fail with ERROR_BUSY. This is not currently tested
+    // because DISABLING has very short lifecycle, it's not possible to guarantee the command can be
+    // sent before state changes to DISABLED.
+
+    @Test
+    public void threadNetworkCallback_deviceAttached_threadNetworkIsAvailable() throws Exception {
+        CompletableFuture<Network> networkFuture = new CompletableFuture<>();
+        ConnectivityManager cm = mContext.getSystemService(ConnectivityManager.class);
+        NetworkRequest networkRequest =
+                new NetworkRequest.Builder()
+                        .addTransportType(NetworkCapabilities.TRANSPORT_THREAD)
+                        .build();
+        ConnectivityManager.NetworkCallback networkCallback =
+                new ConnectivityManager.NetworkCallback() {
+                    @Override
+                    public void onAvailable(Network network) {
+                        networkFuture.complete(network);
+                    }
+                };
+
+        joinRandomizedDatasetAndWait(mController);
+        runAsShell(
+                ACCESS_NETWORK_STATE,
+                () -> cm.registerNetworkCallback(networkRequest, networkCallback));
+
+        assertThat(isAttached(mController)).isTrue();
+        assertThat(networkFuture.get(NETWORK_CALLBACK_TIMEOUT_MILLIS, MILLISECONDS)).isNotNull();
+    }
+
     private void grantPermissions(String... permissions) {
         for (String permission : permissions) {
             mGrantedPermissions.add(permission);
@@ -136,14 +827,79 @@
         getInstrumentation().getUiAutomation().adoptShellPermissionIdentity(allPermissions);
     }
 
+    @Test
+    public void meshcopService_threadEnabledButNotJoined_discoveredButNoNetwork() throws Exception {
+        setUpTestNetwork();
+
+        setEnabledAndWait(mController, true);
+        leaveAndWait(mController);
+
+        NsdServiceInfo serviceInfo =
+                expectServiceResolved(
+                        MESHCOP_SERVICE_TYPE,
+                        SERVICE_DISCOVERY_TIMEOUT_MILLIS,
+                        s -> s.getAttributes().get("at") == null);
+
+        Map<String, byte[]> txtMap = serviceInfo.getAttributes();
+
+        assertThat(txtMap.get("rv")).isNotNull();
+        assertThat(txtMap.get("tv")).isNotNull();
+        assertThat(txtMap.get("sb")).isNotNull();
+    }
+
+    @Test
+    public void meshcopService_joinedNetwork_discoveredHasNetwork() throws Exception {
+        setUpTestNetwork();
+
+        String networkName = "TestNet" + new Random().nextInt(10_000);
+        joinRandomizedDatasetAndWait(mController, networkName);
+
+        Predicate<NsdServiceInfo> predicate =
+                serviceInfo ->
+                        serviceInfo.getAttributes().get("at") != null
+                                && Arrays.equals(
+                                        serviceInfo.getAttributes().get("nn"),
+                                        networkName.getBytes(StandardCharsets.UTF_8));
+
+        NsdServiceInfo resolvedService =
+                expectServiceResolved(
+                        MESHCOP_SERVICE_TYPE, SERVICE_DISCOVERY_TIMEOUT_MILLIS, predicate);
+
+        Map<String, byte[]> txtMap = resolvedService.getAttributes();
+        assertThat(txtMap.get("rv")).isNotNull();
+        assertThat(txtMap.get("tv")).isNotNull();
+        assertThat(txtMap.get("sb")).isNotNull();
+        assertThat(txtMap.get("id").length).isEqualTo(16);
+    }
+
+    @Test
+    public void meshcopService_threadDisabled_notDiscovered() throws Exception {
+        setUpTestNetwork();
+
+        CompletableFuture<NsdServiceInfo> serviceLostFuture = new CompletableFuture<>();
+        NsdManager.DiscoveryListener listener =
+                discoverForServiceLost(MESHCOP_SERVICE_TYPE, serviceLostFuture);
+        setEnabledAndWait(mController, false);
+        try {
+            serviceLostFuture.get(SERVICE_DISCOVERY_TIMEOUT_MILLIS, MILLISECONDS);
+        } catch (InterruptedException | ExecutionException | TimeoutException ignored) {
+            // It's fine if the service lost event didn't show up. The service may not ever be
+            // advertised.
+        } finally {
+            mNsdManager.stopServiceDiscovery(listener);
+        }
+
+        assertThrows(TimeoutException.class, () -> discoverService(MESHCOP_SERVICE_TYPE));
+    }
+
     private static void dropAllPermissions() {
         getInstrumentation().getUiAutomation().dropShellPermissionIdentity();
     }
 
     private static ActiveOperationalDataset newRandomizedDataset(
             String networkName, ThreadNetworkController controller) throws Exception {
-        SettableFuture<ActiveOperationalDataset> future = SettableFuture.create();
-        controller.createRandomizedDataset(networkName, directExecutor(), future::set);
+        CompletableFuture<ActiveOperationalDataset> future = new CompletableFuture<>();
+        controller.createRandomizedDataset(networkName, directExecutor(), future::complete);
         return future.get(CALLBACK_TIMEOUT_MILLIS, MILLISECONDS);
     }
 
@@ -152,642 +908,302 @@
     }
 
     private static int getDeviceRole(ThreadNetworkController controller) throws Exception {
-        SettableFuture<Integer> future = SettableFuture.create();
-        StateCallback callback = future::set;
-        controller.registerStateCallback(directExecutor(), callback);
-        int role = future.get(CALLBACK_TIMEOUT_MILLIS, MILLISECONDS);
-        controller.unregisterStateCallback(callback);
-        return role;
+        CompletableFuture<Integer> future = new CompletableFuture<>();
+        StateCallback callback = future::complete;
+        runAsShell(
+                ACCESS_NETWORK_STATE,
+                () -> controller.registerStateCallback(directExecutor(), callback));
+        try {
+            return future.get(CALLBACK_TIMEOUT_MILLIS, MILLISECONDS);
+        } finally {
+            runAsShell(ACCESS_NETWORK_STATE, () -> controller.unregisterStateCallback(callback));
+        }
+    }
+
+    private static int waitForAttachedState(ThreadNetworkController controller) throws Exception {
+        List<Integer> attachedRoles = new ArrayList<>();
+        attachedRoles.add(DEVICE_ROLE_CHILD);
+        attachedRoles.add(DEVICE_ROLE_ROUTER);
+        attachedRoles.add(DEVICE_ROLE_LEADER);
+        return waitForStateAnyOf(controller, attachedRoles);
     }
 
     private static int waitForStateAnyOf(
             ThreadNetworkController controller, List<Integer> deviceRoles) throws Exception {
-        SettableFuture<Integer> future = SettableFuture.create();
+        CompletableFuture<Integer> future = new CompletableFuture<>();
         StateCallback callback =
                 newRole -> {
                     if (deviceRoles.contains(newRole)) {
-                        future.set(newRole);
+                        future.complete(newRole);
                     }
                 };
         controller.registerStateCallback(directExecutor(), callback);
-        int role = future.get();
+        int role = future.get(JOIN_TIMEOUT_MILLIS, MILLISECONDS);
         controller.unregisterStateCallback(callback);
         return role;
     }
 
+    private static void waitForEnabledState(ThreadNetworkController controller, int state)
+            throws Exception {
+        CompletableFuture<Integer> future = new CompletableFuture<>();
+        StateCallback callback =
+                new ThreadNetworkController.StateCallback() {
+                    @Override
+                    public void onDeviceRoleChanged(int r) {}
+
+                    @Override
+                    public void onThreadEnableStateChanged(int enabled) {
+                        if (enabled == state) {
+                            future.complete(enabled);
+                        }
+                    }
+                };
+        runAsShell(
+                ACCESS_NETWORK_STATE,
+                () -> controller.registerStateCallback(directExecutor(), callback));
+        future.get(ENABLED_TIMEOUT_MILLIS, MILLISECONDS);
+        runAsShell(ACCESS_NETWORK_STATE, () -> controller.unregisterStateCallback(callback));
+    }
+
+    private void leave(
+            ThreadNetworkController controller,
+            OutcomeReceiver<Void, ThreadNetworkException> receiver) {
+        runAsShell(THREAD_NETWORK_PRIVILEGED, () -> controller.leave(mExecutor, receiver));
+    }
+
+    private void leaveAndWait(ThreadNetworkController controller) throws Exception {
+        CompletableFuture<Void> future = new CompletableFuture<>();
+        leave(controller, future::complete);
+        future.get(LEAVE_TIMEOUT_MILLIS, MILLISECONDS);
+    }
+
+    private void scheduleMigration(
+            ThreadNetworkController controller,
+            PendingOperationalDataset pendingDataset,
+            OutcomeReceiver<Void, ThreadNetworkException> receiver) {
+        runAsShell(
+                THREAD_NETWORK_PRIVILEGED,
+                () -> controller.scheduleMigration(pendingDataset, mExecutor, receiver));
+    }
+
+    private class EnabledStateListener {
+        private ArrayTrackRecord<Integer> mEnabledStates = new ArrayTrackRecord<>();
+        private final ArrayTrackRecord<Integer>.ReadHead mReadHead = mEnabledStates.newReadHead();
+        ThreadNetworkController mController;
+        StateCallback mCallback =
+                new ThreadNetworkController.StateCallback() {
+                    @Override
+                    public void onDeviceRoleChanged(int r) {}
+
+                    @Override
+                    public void onThreadEnableStateChanged(int enabled) {
+                        mEnabledStates.add(enabled);
+                    }
+                };
+
+        EnabledStateListener(ThreadNetworkController controller) {
+            this.mController = controller;
+            runAsShell(
+                    ACCESS_NETWORK_STATE,
+                    () -> controller.registerStateCallback(mExecutor, mCallback));
+        }
+
+        public void expectThreadEnabledState(int enabled) {
+            assertNotNull(mReadHead.poll(ENABLED_TIMEOUT_MILLIS, e -> (e == enabled)));
+        }
+
+        public void unregisterStateCallback() {
+            runAsShell(ACCESS_NETWORK_STATE, () -> mController.unregisterStateCallback(mCallback));
+        }
+    }
+
+    private int booleanToEnabledState(boolean enabled) {
+        return enabled ? STATE_ENABLED : STATE_DISABLED;
+    }
+
+    private void setEnabledAndWait(ThreadNetworkController controller, boolean enabled)
+            throws Exception {
+        CompletableFuture<Void> setFuture = new CompletableFuture<>();
+        runAsShell(
+                THREAD_NETWORK_PRIVILEGED,
+                () -> controller.setEnabled(enabled, mExecutor, newOutcomeReceiver(setFuture)));
+        setFuture.get(ENABLED_TIMEOUT_MILLIS, MILLISECONDS);
+        waitForEnabledState(controller, booleanToEnabledState(enabled));
+    }
+
+    private CompletableFuture joinRandomizedDataset(
+            ThreadNetworkController controller, String networkName) throws Exception {
+        ActiveOperationalDataset activeDataset = newRandomizedDataset(networkName, controller);
+        CompletableFuture<Void> joinFuture = new CompletableFuture<>();
+        runAsShell(
+                THREAD_NETWORK_PRIVILEGED,
+                () -> controller.join(activeDataset, mExecutor, newOutcomeReceiver(joinFuture)));
+        return joinFuture;
+    }
+
+    private void joinRandomizedDatasetAndWait(ThreadNetworkController controller) throws Exception {
+        joinRandomizedDatasetAndWait(controller, "TestNet");
+    }
+
+    private void joinRandomizedDatasetAndWait(
+            ThreadNetworkController controller, String networkName) throws Exception {
+        CompletableFuture<Void> joinFuture = joinRandomizedDataset(controller, networkName);
+        joinFuture.get(JOIN_TIMEOUT_MILLIS, MILLISECONDS);
+        assertThat(isAttached(controller)).isTrue();
+    }
+
     private static ActiveOperationalDataset getActiveOperationalDataset(
             ThreadNetworkController controller) throws Exception {
-        SettableFuture<ActiveOperationalDataset> future = SettableFuture.create();
-        OperationalDatasetCallback callback = future::set;
-        controller.registerOperationalDatasetCallback(directExecutor(), callback);
-        ActiveOperationalDataset dataset = future.get(CALLBACK_TIMEOUT_MILLIS, MILLISECONDS);
-        controller.unregisterOperationalDatasetCallback(callback);
-        return dataset;
+        CompletableFuture<ActiveOperationalDataset> future = new CompletableFuture<>();
+        OperationalDatasetCallback callback = future::complete;
+        runAsShell(
+                ACCESS_NETWORK_STATE,
+                THREAD_NETWORK_PRIVILEGED,
+                () -> controller.registerOperationalDatasetCallback(directExecutor(), callback));
+        try {
+            return future.get(CALLBACK_TIMEOUT_MILLIS, MILLISECONDS);
+        } finally {
+            runAsShell(
+                    ACCESS_NETWORK_STATE,
+                    THREAD_NETWORK_PRIVILEGED,
+                    () -> controller.unregisterOperationalDatasetCallback(callback));
+        }
     }
 
     private static PendingOperationalDataset getPendingOperationalDataset(
             ThreadNetworkController controller) throws Exception {
-        SettableFuture<ActiveOperationalDataset> activeFuture = SettableFuture.create();
-        SettableFuture<PendingOperationalDataset> pendingFuture = SettableFuture.create();
+        CompletableFuture<ActiveOperationalDataset> activeFuture = new CompletableFuture<>();
+        CompletableFuture<PendingOperationalDataset> pendingFuture = new CompletableFuture<>();
         controller.registerOperationalDatasetCallback(
                 directExecutor(), newDatasetCallback(activeFuture, pendingFuture));
-        return pendingFuture.get();
+        return pendingFuture.get(CALLBACK_TIMEOUT_MILLIS, MILLISECONDS);
     }
 
     private static OperationalDatasetCallback newDatasetCallback(
-            SettableFuture<ActiveOperationalDataset> activeFuture,
-            SettableFuture<PendingOperationalDataset> pendingFuture) {
+            CompletableFuture<ActiveOperationalDataset> activeFuture,
+            CompletableFuture<PendingOperationalDataset> pendingFuture) {
         return new OperationalDatasetCallback() {
             @Override
             public void onActiveOperationalDatasetChanged(
                     ActiveOperationalDataset activeOpDataset) {
-                activeFuture.set(activeOpDataset);
+                activeFuture.complete(activeOpDataset);
             }
 
             @Override
             public void onPendingOperationalDatasetChanged(
                     PendingOperationalDataset pendingOpDataset) {
-                pendingFuture.set(pendingOpDataset);
+                pendingFuture.complete(pendingOpDataset);
             }
         };
     }
 
-    @Test
-    public void getThreadVersion_returnsAtLeastThreadVersion1P3() {
-        for (ThreadNetworkController controller : getAllControllers()) {
-            assertThat(controller.getThreadVersion()).isAtLeast(THREAD_VERSION_1_3);
+    private static void assertDoesNotThrow(ThrowingRunnable runnable) {
+        try {
+            runnable.run();
+        } catch (Throwable e) {
+            fail("Should not have thrown " + e);
         }
     }
 
-    @Test
-    public void registerStateCallback_permissionsGranted_returnsCurrentStates() throws Exception {
-        grantPermissions(permission.ACCESS_NETWORK_STATE);
-
-        for (ThreadNetworkController controller : getAllControllers()) {
-            SettableFuture<Integer> deviceRole = SettableFuture.create();
-            StateCallback callback = deviceRole::set;
-
-            try {
-                controller.registerStateCallback(mExecutor, callback);
-
-                assertThat(deviceRole.get()).isEqualTo(DEVICE_ROLE_STOPPED);
-            } finally {
-                controller.unregisterStateCallback(callback);
-            }
-        }
-    }
-
-    @Test
-    public void registerStateCallback_noPermissions_throwsSecurityException() throws Exception {
-        dropAllPermissions();
-
-        for (ThreadNetworkController controller : getAllControllers()) {
-            assertThrows(
-                    SecurityException.class,
-                    () -> controller.registerStateCallback(mExecutor, role -> {}));
-        }
-    }
-
-    @Test
-    public void registerStateCallback_alreadyRegistered_throwsIllegalArgumentException()
-            throws Exception {
-        grantPermissions(permission.ACCESS_NETWORK_STATE);
-
-        for (ThreadNetworkController controller : getAllControllers()) {
-            SettableFuture<Integer> deviceRole = SettableFuture.create();
-            StateCallback callback = role -> deviceRole.set(role);
-            controller.registerStateCallback(mExecutor, callback);
-
-            assertThrows(
-                    IllegalArgumentException.class,
-                    () -> controller.registerStateCallback(mExecutor, callback));
-        }
-    }
-
-    @Test
-    public void unregisterStateCallback_noPermissions_throwsSecurityException() throws Exception {
-        for (ThreadNetworkController controller : getAllControllers()) {
-            SettableFuture<Integer> deviceRole = SettableFuture.create();
-            StateCallback callback = role -> deviceRole.set(role);
-            grantPermissions(permission.ACCESS_NETWORK_STATE);
-            controller.registerStateCallback(mExecutor, callback);
-
-            try {
-                dropAllPermissions();
-                assertThrows(
-                        SecurityException.class,
-                        () -> controller.unregisterStateCallback(callback));
-            } finally {
-                grantPermissions(permission.ACCESS_NETWORK_STATE);
-                controller.unregisterStateCallback(callback);
-            }
-        }
-    }
-
-    @Test
-    public void unregisterStateCallback_callbackRegistered_success() throws Exception {
-        grantPermissions(permission.ACCESS_NETWORK_STATE);
-        for (ThreadNetworkController controller : getAllControllers()) {
-            SettableFuture<Integer> deviceRole = SettableFuture.create();
-            StateCallback callback = role -> deviceRole.set(role);
-            controller.registerStateCallback(mExecutor, callback);
-
-            controller.unregisterStateCallback(callback);
-        }
-    }
-
-    @Test
-    public void unregisterStateCallback_callbackNotRegistered_throwsIllegalArgumentException()
-            throws Exception {
-        for (ThreadNetworkController controller : getAllControllers()) {
-            SettableFuture<Integer> deviceRole = SettableFuture.create();
-            StateCallback callback = role -> deviceRole.set(role);
-
-            assertThrows(
-                    IllegalArgumentException.class,
-                    () -> controller.unregisterStateCallback(callback));
-        }
-    }
-
-    @Test
-    public void unregisterStateCallback_alreadyUnregistered_throwsIllegalArgumentException()
-            throws Exception {
-        grantPermissions(permission.ACCESS_NETWORK_STATE);
-        for (ThreadNetworkController controller : getAllControllers()) {
-            SettableFuture<Integer> deviceRole = SettableFuture.create();
-            StateCallback callback = deviceRole::set;
-            controller.registerStateCallback(mExecutor, callback);
-            controller.unregisterStateCallback(callback);
-
-            assertThrows(
-                    IllegalArgumentException.class,
-                    () -> controller.unregisterStateCallback(callback));
-        }
-    }
-
-    @Test
-    public void registerOperationalDatasetCallback_permissionsGranted_returnsCurrentStates()
-            throws Exception {
-        grantPermissions(permission.ACCESS_NETWORK_STATE, PERMISSION_THREAD_NETWORK_PRIVILEGED);
-
-        for (ThreadNetworkController controller : getAllControllers()) {
-            SettableFuture<ActiveOperationalDataset> activeFuture = SettableFuture.create();
-            SettableFuture<PendingOperationalDataset> pendingFuture = SettableFuture.create();
-            var callback = newDatasetCallback(activeFuture, pendingFuture);
-
-            try {
-                controller.registerOperationalDatasetCallback(mExecutor, callback);
-
-                assertThat(activeFuture.get()).isNull();
-                assertThat(pendingFuture.get()).isNull();
-            } finally {
-                controller.unregisterOperationalDatasetCallback(callback);
-            }
-        }
-    }
-
-    @Test
-    public void registerOperationalDatasetCallback_noPermissions_throwsSecurityException()
-            throws Exception {
-        dropAllPermissions();
-
-        for (ThreadNetworkController controller : getAllControllers()) {
-            SettableFuture<ActiveOperationalDataset> activeFuture = SettableFuture.create();
-            SettableFuture<PendingOperationalDataset> pendingFuture = SettableFuture.create();
-            var callback = newDatasetCallback(activeFuture, pendingFuture);
-
-            assertThrows(
-                    SecurityException.class,
-                    () -> controller.registerOperationalDatasetCallback(mExecutor, callback));
-        }
-    }
-
-    @Test
-    public void unregisterOperationalDatasetCallback_callbackRegistered_success() throws Exception {
-        grantPermissions(permission.ACCESS_NETWORK_STATE, PERMISSION_THREAD_NETWORK_PRIVILEGED);
-        for (ThreadNetworkController controller : getAllControllers()) {
-            SettableFuture<ActiveOperationalDataset> activeFuture = SettableFuture.create();
-            SettableFuture<PendingOperationalDataset> pendingFuture = SettableFuture.create();
-            var callback = newDatasetCallback(activeFuture, pendingFuture);
-            controller.registerOperationalDatasetCallback(mExecutor, callback);
-
-            controller.unregisterOperationalDatasetCallback(callback);
-        }
-    }
-
-    @Test
-    public void unregisterOperationalDatasetCallback_noPermissions_throwsSecurityException()
-            throws Exception {
-        dropAllPermissions();
-
-        for (ThreadNetworkController controller : getAllControllers()) {
-            SettableFuture<ActiveOperationalDataset> activeFuture = SettableFuture.create();
-            SettableFuture<PendingOperationalDataset> pendingFuture = SettableFuture.create();
-            var callback = newDatasetCallback(activeFuture, pendingFuture);
-            grantPermissions(permission.ACCESS_NETWORK_STATE, PERMISSION_THREAD_NETWORK_PRIVILEGED);
-            controller.registerOperationalDatasetCallback(mExecutor, callback);
-
-            try {
-                dropAllPermissions();
-                assertThrows(
-                        SecurityException.class,
-                        () -> controller.unregisterOperationalDatasetCallback(callback));
-            } finally {
-                grantPermissions(
-                        permission.ACCESS_NETWORK_STATE, PERMISSION_THREAD_NETWORK_PRIVILEGED);
-                controller.unregisterOperationalDatasetCallback(callback);
-            }
-        }
-    }
-
-    private static <V> OutcomeReceiver<V, ThreadNetworkException> newOutcomeReceiver(
-            SettableFuture<V> future) {
-        return new OutcomeReceiver<V, ThreadNetworkException>() {
-            @Override
-            public void onResult(V result) {
-                future.set(result);
-            }
-
-            @Override
-            public void onError(ThreadNetworkException e) {
-                future.setException(e);
-            }
-        };
-    }
-
-    @Test
-    public void join_withPrivilegedPermission_success() throws Exception {
-        grantPermissions(PERMISSION_THREAD_NETWORK_PRIVILEGED);
-
-        for (ThreadNetworkController controller : getAllControllers()) {
-            ActiveOperationalDataset activeDataset = newRandomizedDataset("TestNet", controller);
-            SettableFuture<Void> joinFuture = SettableFuture.create();
-
-            controller.join(activeDataset, mExecutor, newOutcomeReceiver(joinFuture));
-            joinFuture.get();
-
-            grantPermissions(permission.ACCESS_NETWORK_STATE);
-            assertThat(isAttached(controller)).isTrue();
-            assertThat(getActiveOperationalDataset(controller)).isEqualTo(activeDataset);
-        }
-    }
-
-    @Test
-    public void join_withoutPrivilegedPermission_throwsSecurityException() throws Exception {
-        dropAllPermissions();
-
-        for (ThreadNetworkController controller : getAllControllers()) {
-            ActiveOperationalDataset activeDataset = newRandomizedDataset("TestNet", controller);
-
-            assertThrows(
-                    SecurityException.class,
-                    () -> controller.join(activeDataset, mExecutor, v -> {}));
-        }
-    }
-
-    @Test
-    public void join_concurrentRequests_firstOneIsAborted() throws Exception {
-        grantPermissions(PERMISSION_THREAD_NETWORK_PRIVILEGED);
-
-        final byte[] KEY_1 = new byte[] {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1};
-        final byte[] KEY_2 = new byte[] {2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2};
-        for (ThreadNetworkController controller : getAllControllers()) {
-            ActiveOperationalDataset activeDataset1 =
-                    new ActiveOperationalDataset.Builder(
-                                    newRandomizedDataset("TestNet", controller))
-                            .setNetworkKey(KEY_1)
-                            .build();
-            ActiveOperationalDataset activeDataset2 =
-                    new ActiveOperationalDataset.Builder(activeDataset1)
-                            .setNetworkKey(KEY_2)
-                            .build();
-            SettableFuture<Void> joinFuture1 = SettableFuture.create();
-            SettableFuture<Void> joinFuture2 = SettableFuture.create();
-
-            controller.join(activeDataset1, mExecutor, newOutcomeReceiver(joinFuture1));
-            controller.join(activeDataset2, mExecutor, newOutcomeReceiver(joinFuture2));
-
-            ThreadNetworkException thrown =
-                    (ThreadNetworkException)
-                            assertThrows(ExecutionException.class, joinFuture1::get).getCause();
-            assertThat(thrown.getErrorCode()).isEqualTo(ERROR_ABORTED);
-            joinFuture2.get();
-            grantPermissions(permission.ACCESS_NETWORK_STATE);
-            assertThat(isAttached(controller)).isTrue();
-            assertThat(getActiveOperationalDataset(controller)).isEqualTo(activeDataset2);
-        }
-    }
-
-    @Test
-    public void leave_withPrivilegedPermission_success() throws Exception {
-        grantPermissions(PERMISSION_THREAD_NETWORK_PRIVILEGED);
-
-        for (ThreadNetworkController controller : getAllControllers()) {
-            ActiveOperationalDataset activeDataset = newRandomizedDataset("TestNet", controller);
-            SettableFuture<Void> joinFuture = SettableFuture.create();
-            SettableFuture<Void> leaveFuture = SettableFuture.create();
-            controller.join(activeDataset, mExecutor, newOutcomeReceiver(joinFuture));
-            joinFuture.get();
-
-            controller.leave(mExecutor, newOutcomeReceiver(leaveFuture));
-            leaveFuture.get();
-
-            grantPermissions(permission.ACCESS_NETWORK_STATE);
-            assertThat(getDeviceRole(controller)).isEqualTo(DEVICE_ROLE_STOPPED);
-        }
-    }
-
-    @Test
-    public void leave_withoutPrivilegedPermission_throwsSecurityException() {
-        dropAllPermissions();
-
-        for (ThreadNetworkController controller : getAllControllers()) {
-            assertThrows(SecurityException.class, () -> controller.leave(mExecutor, v -> {}));
-        }
-    }
-
-    @Test
-    public void leave_concurrentRequests_bothSuccess() throws Exception {
-        grantPermissions(PERMISSION_THREAD_NETWORK_PRIVILEGED);
-
-        for (ThreadNetworkController controller : getAllControllers()) {
-            ActiveOperationalDataset activeDataset = newRandomizedDataset("TestNet", controller);
-            SettableFuture<Void> joinFuture = SettableFuture.create();
-            SettableFuture<Void> leaveFuture1 = SettableFuture.create();
-            SettableFuture<Void> leaveFuture2 = SettableFuture.create();
-            controller.join(activeDataset, mExecutor, newOutcomeReceiver(joinFuture));
-            joinFuture.get();
-
-            controller.leave(mExecutor, newOutcomeReceiver(leaveFuture1));
-            controller.leave(mExecutor, newOutcomeReceiver(leaveFuture2));
-
-            leaveFuture1.get();
-            leaveFuture2.get();
-            grantPermissions(permission.ACCESS_NETWORK_STATE);
-            assertThat(getDeviceRole(controller)).isEqualTo(DEVICE_ROLE_STOPPED);
-        }
-    }
-
-    @Test
-    public void scheduleMigration_withPrivilegedPermission_newDatasetApplied() throws Exception {
-        grantPermissions(permission.ACCESS_NETWORK_STATE, PERMISSION_THREAD_NETWORK_PRIVILEGED);
-
-        for (ThreadNetworkController controller : getAllControllers()) {
-            ActiveOperationalDataset activeDataset1 =
-                    new ActiveOperationalDataset.Builder(
-                                    newRandomizedDataset("TestNet", controller))
-                            .setActiveTimestamp(new OperationalDatasetTimestamp(1L, 0, false))
-                            .setExtendedPanId(new byte[] {1, 1, 1, 1, 1, 1, 1, 1})
-                            .build();
-            ActiveOperationalDataset activeDataset2 =
-                    new ActiveOperationalDataset.Builder(activeDataset1)
-                            .setActiveTimestamp(new OperationalDatasetTimestamp(2L, 0, false))
-                            .setNetworkName("ThreadNet2")
-                            .build();
-            PendingOperationalDataset pendingDataset2 =
-                    new PendingOperationalDataset(
-                            activeDataset2,
-                            OperationalDatasetTimestamp.fromInstant(Instant.now()),
-                            Duration.ofSeconds(30));
-            SettableFuture<Void> joinFuture = SettableFuture.create();
-            SettableFuture<Void> migrateFuture = SettableFuture.create();
-            controller.join(activeDataset1, mExecutor, newOutcomeReceiver(joinFuture));
-            joinFuture.get();
-
-            controller.scheduleMigration(
-                    pendingDataset2, mExecutor, newOutcomeReceiver(migrateFuture));
-            migrateFuture.get();
-
-            SettableFuture<Boolean> dataset2IsApplied = SettableFuture.create();
-            SettableFuture<Boolean> pendingDatasetIsRemoved = SettableFuture.create();
-            OperationalDatasetCallback datasetCallback =
-                    new OperationalDatasetCallback() {
-                        @Override
-                        public void onActiveOperationalDatasetChanged(
-                                ActiveOperationalDataset activeDataset) {
-                            if (activeDataset.equals(activeDataset2)) {
-                                dataset2IsApplied.set(true);
-                            }
-                        }
-
-                        @Override
-                        public void onPendingOperationalDatasetChanged(
-                                PendingOperationalDataset pendingDataset) {
-                            if (pendingDataset == null) {
-                                pendingDatasetIsRemoved.set(true);
-                            }
-                        }
-                    };
-            controller.registerOperationalDatasetCallback(directExecutor(), datasetCallback);
-            assertThat(dataset2IsApplied.get()).isTrue();
-            assertThat(pendingDatasetIsRemoved.get()).isTrue();
-            controller.unregisterOperationalDatasetCallback(datasetCallback);
-        }
-    }
-
-    @Test
-    public void scheduleMigration_whenNotAttached_failWithPreconditionError() throws Exception {
-        grantPermissions(permission.ACCESS_NETWORK_STATE, PERMISSION_THREAD_NETWORK_PRIVILEGED);
-
-        for (ThreadNetworkController controller : getAllControllers()) {
-            PendingOperationalDataset pendingDataset =
-                    new PendingOperationalDataset(
-                            newRandomizedDataset("TestNet", controller),
-                            OperationalDatasetTimestamp.fromInstant(Instant.now()),
-                            Duration.ofSeconds(30));
-            SettableFuture<Void> migrateFuture = SettableFuture.create();
-
-            controller.scheduleMigration(
-                    pendingDataset, mExecutor, newOutcomeReceiver(migrateFuture));
-
-            ThreadNetworkException thrown =
-                    (ThreadNetworkException)
-                            assertThrows(ExecutionException.class, migrateFuture::get).getCause();
-            assertThat(thrown.getErrorCode()).isEqualTo(ERROR_FAILED_PRECONDITION);
-        }
-    }
-
-    @Test
-    public void scheduleMigration_secondRequestHasSmallerTimestamp_rejectedByLeader()
-            throws Exception {
-        grantPermissions(permission.ACCESS_NETWORK_STATE, PERMISSION_THREAD_NETWORK_PRIVILEGED);
-
-        for (ThreadNetworkController controller : getAllControllers()) {
-            final ActiveOperationalDataset activeDataset =
-                    new ActiveOperationalDataset.Builder(
-                                    newRandomizedDataset("testNet", controller))
-                            .setActiveTimestamp(new OperationalDatasetTimestamp(1L, 0, false))
-                            .build();
-            ActiveOperationalDataset activeDataset1 =
-                    new ActiveOperationalDataset.Builder(activeDataset)
-                            .setActiveTimestamp(new OperationalDatasetTimestamp(2L, 0, false))
-                            .setNetworkName("testNet1")
-                            .build();
-            PendingOperationalDataset pendingDataset1 =
-                    new PendingOperationalDataset(
-                            activeDataset1,
-                            new OperationalDatasetTimestamp(100, 0, false),
-                            Duration.ofSeconds(30));
-            ActiveOperationalDataset activeDataset2 =
-                    new ActiveOperationalDataset.Builder(activeDataset)
-                            .setActiveTimestamp(new OperationalDatasetTimestamp(3L, 0, false))
-                            .setNetworkName("testNet2")
-                            .build();
-            PendingOperationalDataset pendingDataset2 =
-                    new PendingOperationalDataset(
-                            activeDataset2,
-                            new OperationalDatasetTimestamp(20, 0, false),
-                            Duration.ofSeconds(30));
-            SettableFuture<Void> joinFuture = SettableFuture.create();
-            SettableFuture<Void> migrateFuture1 = SettableFuture.create();
-            SettableFuture<Void> migrateFuture2 = SettableFuture.create();
-            controller.join(activeDataset, mExecutor, newOutcomeReceiver(joinFuture));
-            joinFuture.get();
-
-            controller.scheduleMigration(
-                    pendingDataset1, mExecutor, newOutcomeReceiver(migrateFuture1));
-            migrateFuture1.get();
-            controller.scheduleMigration(
-                    pendingDataset2, mExecutor, newOutcomeReceiver(migrateFuture2));
-
-            ThreadNetworkException thrown =
-                    (ThreadNetworkException)
-                            assertThrows(ExecutionException.class, migrateFuture2::get).getCause();
-            assertThat(thrown.getErrorCode()).isEqualTo(ERROR_REJECTED_BY_PEER);
-        }
-    }
-
-    @Test
-    public void scheduleMigration_secondRequestHasLargerTimestamp_newDatasetApplied()
-            throws Exception {
-        grantPermissions(permission.ACCESS_NETWORK_STATE, PERMISSION_THREAD_NETWORK_PRIVILEGED);
-
-        for (ThreadNetworkController controller : getAllControllers()) {
-            final ActiveOperationalDataset activeDataset =
-                    new ActiveOperationalDataset.Builder(
-                                    newRandomizedDataset("validName", controller))
-                            .setActiveTimestamp(new OperationalDatasetTimestamp(1L, 0, false))
-                            .build();
-            ActiveOperationalDataset activeDataset1 =
-                    new ActiveOperationalDataset.Builder(activeDataset)
-                            .setActiveTimestamp(new OperationalDatasetTimestamp(2L, 0, false))
-                            .setNetworkName("testNet1")
-                            .build();
-            PendingOperationalDataset pendingDataset1 =
-                    new PendingOperationalDataset(
-                            activeDataset1,
-                            new OperationalDatasetTimestamp(100, 0, false),
-                            Duration.ofSeconds(30));
-            ActiveOperationalDataset activeDataset2 =
-                    new ActiveOperationalDataset.Builder(activeDataset)
-                            .setActiveTimestamp(new OperationalDatasetTimestamp(3L, 0, false))
-                            .setNetworkName("testNet2")
-                            .build();
-            PendingOperationalDataset pendingDataset2 =
-                    new PendingOperationalDataset(
-                            activeDataset2,
-                            new OperationalDatasetTimestamp(200, 0, false),
-                            Duration.ofSeconds(30));
-            SettableFuture<Void> joinFuture = SettableFuture.create();
-            SettableFuture<Void> migrateFuture1 = SettableFuture.create();
-            SettableFuture<Void> migrateFuture2 = SettableFuture.create();
-            controller.join(activeDataset, mExecutor, newOutcomeReceiver(joinFuture));
-            joinFuture.get();
-
-            controller.scheduleMigration(
-                    pendingDataset1, mExecutor, newOutcomeReceiver(migrateFuture1));
-            migrateFuture1.get();
-            controller.scheduleMigration(
-                    pendingDataset2, mExecutor, newOutcomeReceiver(migrateFuture2));
-            migrateFuture2.get();
-
-            SettableFuture<Boolean> dataset2IsApplied = SettableFuture.create();
-            SettableFuture<Boolean> pendingDatasetIsRemoved = SettableFuture.create();
-            OperationalDatasetCallback datasetCallback =
-                    new OperationalDatasetCallback() {
-                        @Override
-                        public void onActiveOperationalDatasetChanged(
-                                ActiveOperationalDataset activeDataset) {
-                            if (activeDataset.equals(activeDataset2)) {
-                                dataset2IsApplied.set(true);
-                            }
-                        }
-
-                        @Override
-                        public void onPendingOperationalDatasetChanged(
-                                PendingOperationalDataset pendingDataset) {
-                            if (pendingDataset == null) {
-                                pendingDatasetIsRemoved.set(true);
-                            }
-                        }
-                    };
-            controller.registerOperationalDatasetCallback(directExecutor(), datasetCallback);
-            assertThat(dataset2IsApplied.get()).isTrue();
-            assertThat(pendingDatasetIsRemoved.get()).isTrue();
-            controller.unregisterOperationalDatasetCallback(datasetCallback);
-        }
-    }
-
-    @Test
-    public void createRandomizedDataset_wrongNetworkNameLength_throwsIllegalArgumentException() {
-        for (ThreadNetworkController controller : getAllControllers()) {
-            assertThrows(
-                    IllegalArgumentException.class,
-                    () -> controller.createRandomizedDataset("", mExecutor, dataset -> {}));
-
-            assertThrows(
-                    IllegalArgumentException.class,
-                    () ->
-                            controller.createRandomizedDataset(
-                                    "ANetNameIs17Bytes", mExecutor, dataset -> {}));
-        }
-    }
-
-    @Test
-    public void createRandomizedDataset_validNetworkName_success() throws Exception {
-        for (ThreadNetworkController controller : getAllControllers()) {
-            ActiveOperationalDataset dataset = newRandomizedDataset("validName", controller);
-
-            assertThat(dataset.getNetworkName()).isEqualTo("validName");
-            assertThat(dataset.getPanId()).isLessThan(0xffff);
-            assertThat(dataset.getChannelMask().size()).isAtLeast(1);
-            assertThat(dataset.getExtendedPanId()).hasLength(8);
-            assertThat(dataset.getNetworkKey()).hasLength(16);
-            assertThat(dataset.getPskc()).hasLength(16);
-            assertThat(dataset.getMeshLocalPrefix().getPrefixLength()).isEqualTo(64);
-            assertThat(dataset.getMeshLocalPrefix().getRawAddress()[0]).isEqualTo((byte) 0xfd);
-        }
-    }
-
-    @Test
-    public void threadNetworkCallback_deviceAttached_threadNetworkIsAvailable() throws Exception {
-        ThreadNetworkController controller = mManager.getAllThreadNetworkControllers().get(0);
-        ActiveOperationalDataset activeDataset = newRandomizedDataset("TestNet", controller);
-        SettableFuture<Void> joinFuture = SettableFuture.create();
-        SettableFuture<Network> networkFuture = SettableFuture.create();
-        ConnectivityManager cm = mContext.getSystemService(ConnectivityManager.class);
-        NetworkRequest networkRequest =
-                new NetworkRequest.Builder()
-                        .addTransportType(NetworkCapabilities.TRANSPORT_THREAD)
-                        .build();
-        ConnectivityManager.NetworkCallback networkCallback =
-                new ConnectivityManager.NetworkCallback() {
+    // Return the first discovered service instance.
+    private NsdServiceInfo discoverService(String serviceType) throws Exception {
+        CompletableFuture<NsdServiceInfo> serviceInfoFuture = new CompletableFuture<>();
+        NsdManager.DiscoveryListener listener =
+                new DefaultDiscoveryListener() {
                     @Override
-                    public void onAvailable(Network network) {
-                        networkFuture.set(network);
+                    public void onServiceFound(NsdServiceInfo serviceInfo) {
+                        serviceInfoFuture.complete(serviceInfo);
                     }
                 };
+        mNsdManager.discoverServices(serviceType, NsdManager.PROTOCOL_DNS_SD, listener);
+        try {
+            serviceInfoFuture.get(SERVICE_DISCOVERY_TIMEOUT_MILLIS, MILLISECONDS);
+        } finally {
+            mNsdManager.stopServiceDiscovery(listener);
+        }
 
-        runAsShell(
-                PERMISSION_THREAD_NETWORK_PRIVILEGED,
-                () -> controller.join(activeDataset, mExecutor, newOutcomeReceiver(joinFuture)));
-        runAsShell(
-                permission.ACCESS_NETWORK_STATE,
-                () -> cm.registerNetworkCallback(networkRequest, networkCallback));
+        return serviceInfoFuture.get();
+    }
 
-        joinFuture.get(JOIN_TIMEOUT_MILLIS, MILLISECONDS);
-        runAsShell(
-                permission.ACCESS_NETWORK_STATE, () -> assertThat(isAttached(controller)).isTrue());
-        assertThat(networkFuture.get(NETWORK_CALLBACK_TIMEOUT_MILLIS, MILLISECONDS)).isNotNull();
+    private NsdManager.DiscoveryListener discoverForServiceLost(
+            String serviceType, CompletableFuture<NsdServiceInfo> serviceInfoFuture) {
+        NsdManager.DiscoveryListener listener =
+                new DefaultDiscoveryListener() {
+                    @Override
+                    public void onServiceLost(NsdServiceInfo serviceInfo) {
+                        serviceInfoFuture.complete(serviceInfo);
+                    }
+                };
+        mNsdManager.discoverServices(serviceType, NsdManager.PROTOCOL_DNS_SD, listener);
+        return listener;
+    }
+
+    private NsdServiceInfo expectServiceResolved(
+            String serviceType, int timeoutMilliseconds, Predicate<NsdServiceInfo> predicate)
+            throws Exception {
+        NsdServiceInfo discoveredServiceInfo = discoverService(serviceType);
+        CompletableFuture<NsdServiceInfo> future = new CompletableFuture<>();
+        NsdManager.ServiceInfoCallback callback =
+                new DefaultServiceInfoCallback() {
+                    @Override
+                    public void onServiceUpdated(@NonNull NsdServiceInfo serviceInfo) {
+                        if (predicate.test(serviceInfo)) {
+                            future.complete(serviceInfo);
+                        }
+                    }
+                };
+        mNsdManager.registerServiceInfoCallback(discoveredServiceInfo, mExecutor, callback);
+        try {
+            return future.get(timeoutMilliseconds, MILLISECONDS);
+        } finally {
+            mNsdManager.unregisterServiceInfoCallback(callback);
+        }
+    }
+
+    private void setUpTestNetwork() {
+        assertThat(mTestNetworkTracker).isNull();
+        mTestNetworkTracker = new TapTestNetworkTracker(mContext, mHandlerThread.getLooper());
+    }
+
+    private void tearDownTestNetwork() throws InterruptedException {
+        if (mTestNetworkTracker != null) {
+            mTestNetworkTracker.tearDown();
+        }
+        mHandlerThread.quitSafely();
+        mHandlerThread.join();
+    }
+
+    private static class DefaultDiscoveryListener implements NsdManager.DiscoveryListener {
+        @Override
+        public void onStartDiscoveryFailed(String serviceType, int errorCode) {}
+
+        @Override
+        public void onStopDiscoveryFailed(String serviceType, int errorCode) {}
+
+        @Override
+        public void onDiscoveryStarted(String serviceType) {}
+
+        @Override
+        public void onDiscoveryStopped(String serviceType) {}
+
+        @Override
+        public void onServiceFound(NsdServiceInfo serviceInfo) {}
+
+        @Override
+        public void onServiceLost(NsdServiceInfo serviceInfo) {}
+    }
+
+    private static class DefaultServiceInfoCallback implements NsdManager.ServiceInfoCallback {
+        @Override
+        public void onServiceInfoCallbackRegistrationFailed(int errorCode) {}
+
+        @Override
+        public void onServiceUpdated(@NonNull NsdServiceInfo serviceInfo) {}
+
+        @Override
+        public void onServiceLost() {}
+
+        @Override
+        public void onServiceInfoCallbackUnregistered() {}
     }
 }
diff --git a/thread/tests/cts/src/android/net/thread/cts/ThreadNetworkExceptionTest.java b/thread/tests/cts/src/android/net/thread/cts/ThreadNetworkExceptionTest.java
new file mode 100644
index 0000000..7d9ae81
--- /dev/null
+++ b/thread/tests/cts/src/android/net/thread/cts/ThreadNetworkExceptionTest.java
@@ -0,0 +1,65 @@
+/*
+ * 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.
+ */
+
+package android.net.thread.cts;
+
+import static android.net.thread.ThreadNetworkException.ERROR_INTERNAL_ERROR;
+import static android.net.thread.ThreadNetworkException.ERROR_THREAD_DISABLED;
+import static android.net.thread.ThreadNetworkException.ERROR_UNKNOWN;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertThrows;
+
+import android.net.thread.ThreadNetworkException;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/** CTS tests for {@link ThreadNetworkException}. */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public final class ThreadNetworkExceptionTest {
+    @Test
+    public void constructor_validValues_valuesAreConnectlySet() throws Exception {
+        ThreadNetworkException errorThreadDisabled =
+                new ThreadNetworkException(ERROR_THREAD_DISABLED, "Thread disabled error!");
+        ThreadNetworkException errorInternalError =
+                new ThreadNetworkException(ERROR_INTERNAL_ERROR, "internal error!");
+
+        assertThat(errorThreadDisabled.getErrorCode()).isEqualTo(ERROR_THREAD_DISABLED);
+        assertThat(errorThreadDisabled.getMessage()).isEqualTo("Thread disabled error!");
+        assertThat(errorInternalError.getErrorCode()).isEqualTo(ERROR_INTERNAL_ERROR);
+        assertThat(errorInternalError.getMessage()).isEqualTo("internal error!");
+    }
+
+    @Test
+    public void constructor_nullMessage_throwsNullPointerException() throws Exception {
+        assertThrows(
+                NullPointerException.class,
+                () -> new ThreadNetworkException(ERROR_UNKNOWN, null /* message */));
+    }
+
+    @Test
+    public void constructor_tooSmallErrorCode_throwsIllegalArgumentException() throws Exception {
+        assertThrows(IllegalArgumentException.class, () -> new ThreadNetworkException(0, "0"));
+        // TODO: add argument check for too large error code when mainline CTS is ready. This was
+        // not added here for CTS forward copatibility.
+    }
+}
diff --git a/thread/tests/integration/Android.bp b/thread/tests/integration/Android.bp
index 405fb76..9677ec5 100644
--- a/thread/tests/integration/Android.bp
+++ b/thread/tests/integration/Android.bp
@@ -15,6 +15,7 @@
 //
 
 package {
+    default_team: "trendy_team_fwk_thread_network",
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
@@ -23,12 +24,15 @@
     min_sdk_version: "30",
     static_libs: [
         "androidx.test.rules",
+        "compatibility-device-util-axt",
         "guava",
         "mockito-target-minus-junit4",
         "net-tests-utils",
         "net-utils-device-common",
         "net-utils-device-common-bpf",
         "testables",
+        "ThreadNetworkTestUtils",
+        "truth",
     ],
     libs: [
         "android.test.runner",
@@ -41,11 +45,13 @@
     name: "ThreadNetworkIntegrationTests",
     platform_apis: true,
     manifest: "AndroidManifest.xml",
+    test_config: "AndroidTest.xml",
     defaults: [
         "framework-connectivity-test-defaults",
-        "ThreadNetworkIntegrationTestsDefaults"
+        "ThreadNetworkIntegrationTestsDefaults",
     ],
     test_suites: [
+        "mts-tethering",
         "general-tests",
     ],
     srcs: [
diff --git a/thread/tests/integration/AndroidTest.xml b/thread/tests/integration/AndroidTest.xml
new file mode 100644
index 0000000..152c1c3
--- /dev/null
+++ b/thread/tests/integration/AndroidTest.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    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.
+ -->
+
+<configuration description="Config for Thread integration tests">
+    <option name="test-tag" value="ThreadNetworkIntegrationTests" />
+    <option name="test-suite-tag" value="apct" />
+
+    <!--
+        Only run tests if the device under test is SDK version 34 (Android 14) or above.
+    -->
+    <object type="module_controller"
+            class="com.android.tradefed.testtype.suite.module.Sdk34ModuleController" />
+
+    <!-- Run tests in MTS only if the Tethering Mainline module is installed. -->
+    <object type="module_controller"
+            class="com.android.tradefed.testtype.suite.module.MainlineTestModuleController">
+        <option name="mainline-module-package-name" value="com.google.android.tethering" />
+    </object>
+
+    <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer" />
+
+    <!-- Install test -->
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="test-file-name" value="ThreadNetworkIntegrationTests.apk" />
+        <option name="check-min-sdk" value="true" />
+        <option name="cleanup-apks" value="true" />
+    </target_preparer>
+
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="com.android.thread.tests.integration" />
+    </test>
+</configuration>
diff --git a/thread/tests/integration/src/android/net/thread/BorderRoutingTest.java b/thread/tests/integration/src/android/net/thread/BorderRoutingTest.java
index 25f5bd3..ee21405 100644
--- a/thread/tests/integration/src/android/net/thread/BorderRoutingTest.java
+++ b/thread/tests/integration/src/android/net/thread/BorderRoutingTest.java
@@ -18,28 +18,44 @@
 
 import static android.Manifest.permission.MANAGE_TEST_NETWORKS;
 import static android.Manifest.permission.NETWORK_SETTINGS;
-import static android.net.thread.IntegrationTestUtils.isExpectedIcmpv6Packet;
-import static android.net.thread.IntegrationTestUtils.isSimulatedThreadRadioSupported;
-import static android.net.thread.IntegrationTestUtils.newPacketReader;
-import static android.net.thread.IntegrationTestUtils.readPacketFrom;
-import static android.net.thread.IntegrationTestUtils.waitFor;
-import static android.net.thread.IntegrationTestUtils.waitForStateAnyOf;
-import static android.net.thread.ThreadNetworkController.DEVICE_ROLE_LEADER;
 import static android.net.thread.ThreadNetworkManager.PERMISSION_THREAD_NETWORK_PRIVILEGED;
+import static android.net.thread.utils.IntegrationTestUtils.JOIN_TIMEOUT;
+import static android.net.thread.utils.IntegrationTestUtils.RESTART_JOIN_TIMEOUT;
+import static android.net.thread.utils.IntegrationTestUtils.isExpectedIcmpv6Packet;
+import static android.net.thread.utils.IntegrationTestUtils.isFromIpv6Source;
+import static android.net.thread.utils.IntegrationTestUtils.isInMulticastGroup;
+import static android.net.thread.utils.IntegrationTestUtils.isMulticastRoutingSupported;
+import static android.net.thread.utils.IntegrationTestUtils.isSimulatedThreadRadioSupported;
+import static android.net.thread.utils.IntegrationTestUtils.isToIpv6Destination;
+import static android.net.thread.utils.IntegrationTestUtils.newPacketReader;
+import static android.net.thread.utils.IntegrationTestUtils.pollForPacket;
+import static android.net.thread.utils.IntegrationTestUtils.sendUdpMessage;
+import static android.net.thread.utils.IntegrationTestUtils.waitFor;
 
 import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ECHO_REPLY_TYPE;
+import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ECHO_REQUEST_TYPE;
 import static com.android.testutils.TestNetworkTrackerKt.initTestNetwork;
 import static com.android.testutils.TestPermissionUtil.runAsShell;
 
 import static com.google.common.io.BaseEncoding.base16;
+import static com.google.common.util.concurrent.MoreExecutors.directExecutor;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assume.assumeNotNull;
 import static org.junit.Assume.assumeTrue;
 
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+
 import android.content.Context;
+import android.net.InetAddresses;
 import android.net.LinkProperties;
 import android.net.MacAddress;
+import android.net.thread.utils.FullThreadDevice;
+import android.net.thread.utils.InfraNetworkDevice;
+import android.net.thread.utils.OtDaemonController;
 import android.os.Handler;
 import android.os.HandlerThread;
 
@@ -50,17 +66,19 @@
 import com.android.testutils.TapPacketReader;
 import com.android.testutils.TestNetworkTracker;
 
-import com.google.common.util.concurrent.MoreExecutors;
-
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
 import java.net.Inet6Address;
+import java.time.Duration;
+import java.util.ArrayList;
 import java.util.List;
+import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
+import java.util.function.Predicate;
 
 /** Integration test cases for Thread Border Routing feature. */
 @RunWith(AndroidJUnit4.class)
@@ -68,12 +86,22 @@
 public class BorderRoutingTest {
     private static final String TAG = BorderRoutingTest.class.getSimpleName();
     private final Context mContext = ApplicationProvider.getApplicationContext();
-    private final ThreadNetworkManager mThreadNetworkManager =
-            mContext.getSystemService(ThreadNetworkManager.class);
-    private ThreadNetworkController mThreadNetworkController;
+    private ThreadNetworkController mController;
+    private OtDaemonController mOtCtl;
     private HandlerThread mHandlerThread;
     private Handler mHandler;
     private TestNetworkTracker mInfraNetworkTracker;
+    private List<FullThreadDevice> mFtds;
+    private TapPacketReader mInfraNetworkReader;
+    private InfraNetworkDevice mInfraDevice;
+
+    private static final int NUM_FTD = 2;
+    private static final Inet6Address GROUP_ADDR_SCOPE_5 =
+            (Inet6Address) InetAddresses.parseNumericAddress("ff05::1234");
+    private static final Inet6Address GROUP_ADDR_SCOPE_4 =
+            (Inet6Address) InetAddresses.parseNumericAddress("ff04::1234");
+    private static final Inet6Address GROUP_ADDR_SCOPE_3 =
+            (Inet6Address) InetAddresses.parseNumericAddress("ff03::1234");
 
     // A valid Thread Active Operational Dataset generated from OpenThread CLI "dataset init new".
     private static final byte[] DEFAULT_DATASET_TLVS =
@@ -88,12 +116,469 @@
 
     @Before
     public void setUp() throws Exception {
+        assumeTrue(isSimulatedThreadRadioSupported());
+        final ThreadNetworkManager manager = mContext.getSystemService(ThreadNetworkManager.class);
+        if (manager != null) {
+            mController = manager.getAllThreadNetworkControllers().get(0);
+        }
+
+        // Run the tests on only devices where the Thread feature is available
+        assumeNotNull(mController);
+
+        // TODO: b/323301831 - This is a workaround to avoid unnecessary delay to re-form a network
+        mOtCtl = new OtDaemonController();
+        mOtCtl.factoryReset();
+
         mHandlerThread = new HandlerThread(getClass().getSimpleName());
         mHandlerThread.start();
         mHandler = new Handler(mHandlerThread.getLooper());
-        var threadControllers = mThreadNetworkManager.getAllThreadNetworkControllers();
-        assertEquals(threadControllers.size(), 1);
-        mThreadNetworkController = threadControllers.get(0);
+        mFtds = new ArrayList<>();
+
+        setUpInfraNetwork();
+
+        // BR forms a network.
+        startBrLeader();
+
+        // Creates a infra network device.
+        mInfraNetworkReader = newPacketReader(mInfraNetworkTracker.getTestIface(), mHandler);
+        startInfraDevice();
+
+        // Create Ftds
+        for (int i = 0; i < NUM_FTD; ++i) {
+            mFtds.add(new FullThreadDevice(15 + i /* node ID */));
+        }
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        if (mController == null) {
+            return;
+        }
+
+        runAsShell(
+                PERMISSION_THREAD_NETWORK_PRIVILEGED,
+                NETWORK_SETTINGS,
+                () -> {
+                    CountDownLatch latch = new CountDownLatch(2);
+                    mController.setTestNetworkAsUpstream(
+                            null, directExecutor(), v -> latch.countDown());
+                    mController.leave(directExecutor(), v -> latch.countDown());
+                    latch.await(10, TimeUnit.SECONDS);
+                });
+        tearDownInfraNetwork();
+
+        mHandlerThread.quitSafely();
+        mHandlerThread.join();
+
+        for (var ftd : mFtds) {
+            ftd.destroy();
+        }
+        mFtds.clear();
+    }
+
+    @Test
+    public void unicastRouting_infraDevicePingTheadDeviceOmr_replyReceived() throws Exception {
+        /*
+         * <pre>
+         * Topology:
+         *                 infra network                       Thread
+         * infra device -------------------- Border Router -------------- Full Thread device
+         *                                   (Cuttlefish)
+         * </pre>
+         */
+
+        // Let ftd join the network.
+        FullThreadDevice ftd = mFtds.get(0);
+        startFtdChild(ftd);
+
+        // Infra device sends an echo request to FTD's OMR.
+        mInfraDevice.sendEchoRequest(ftd.getOmrAddress());
+
+        // Infra device receives an echo reply sent by FTD.
+        assertNotNull(pollForPacketOnInfraNetwork(ICMPV6_ECHO_REPLY_TYPE, null /* srcAddress */));
+    }
+
+    @Test
+    public void unicastRouting_borderRouterSendsUdpToThreadDevice_datagramReceived()
+            throws Exception {
+        assumeTrue(isSimulatedThreadRadioSupported());
+
+        /*
+         * <pre>
+         * Topology:
+         *                   Thread
+         * Border Router -------------- Full Thread device
+         *  (Cuttlefish)
+         * </pre>
+         */
+
+        // BR forms a network.
+        CompletableFuture<Void> joinFuture = new CompletableFuture<>();
+        runAsShell(
+                PERMISSION_THREAD_NETWORK_PRIVILEGED,
+                () -> mController.join(DEFAULT_DATASET, directExecutor(), joinFuture::complete));
+        joinFuture.get(RESTART_JOIN_TIMEOUT.toMillis(), MILLISECONDS);
+
+        // Creates a Full Thread Device (FTD) and lets it join the network.
+        FullThreadDevice ftd = mFtds.get(0);
+        startFtdChild(ftd);
+        Inet6Address ftdOmr = ftd.getOmrAddress();
+        Inet6Address ftdMlEid = ftd.getMlEid();
+        assertNotNull(ftdMlEid);
+
+        ftd.udpBind(ftdOmr, 12345);
+        sendUdpMessage(ftdOmr, 12345, "aaaaaaaa");
+        assertEquals("aaaaaaaa", ftd.udpReceive());
+
+        ftd.udpBind(ftdMlEid, 12345);
+        sendUdpMessage(ftdMlEid, 12345, "bbbbbbbb");
+        assertEquals("bbbbbbbb", ftd.udpReceive());
+    }
+
+    @Test
+    public void multicastRouting_ftdSubscribedMulticastAddress_infraLinkJoinsMulticastGroup()
+            throws Exception {
+        assumeTrue(isMulticastRoutingSupported());
+        /*
+         * <pre>
+         * Topology:
+         *                 infra network                       Thread
+         * infra device -------------------- Border Router -------------- Full Thread device
+         *                                   (Cuttlefish)
+         * </pre>
+         */
+
+        FullThreadDevice ftd = mFtds.get(0);
+        startFtdChild(ftd);
+
+        ftd.subscribeMulticastAddress(GROUP_ADDR_SCOPE_5);
+
+        assertInfraLinkMemberOfGroup(GROUP_ADDR_SCOPE_5);
+    }
+
+    @Test
+    public void
+            multicastRouting_ftdSubscribedScope3MulticastAddress_infraLinkNotJoinMulticastGroup()
+                    throws Exception {
+        assumeTrue(isMulticastRoutingSupported());
+        /*
+         * <pre>
+         * Topology:
+         *                 infra network                       Thread
+         * infra device -------------------- Border Router -------------- Full Thread device
+         *                                   (Cuttlefish)
+         * </pre>
+         */
+
+        FullThreadDevice ftd = mFtds.get(0);
+        startFtdChild(ftd);
+
+        ftd.subscribeMulticastAddress(GROUP_ADDR_SCOPE_3);
+
+        assertInfraLinkNotMemberOfGroup(GROUP_ADDR_SCOPE_3);
+    }
+
+    @Test
+    public void multicastRouting_ftdSubscribedMulticastAddress_canPingfromInfraLink()
+            throws Exception {
+        assumeTrue(isMulticastRoutingSupported());
+        /*
+         * <pre>
+         * Topology:
+         *                 infra network                       Thread
+         * infra device -------------------- Border Router -------------- Full Thread device
+         *                                   (Cuttlefish)
+         * </pre>
+         */
+
+        FullThreadDevice ftd = mFtds.get(0);
+        startFtdChild(ftd);
+        subscribeMulticastAddressAndWait(ftd, GROUP_ADDR_SCOPE_5);
+
+        mInfraDevice.sendEchoRequest(GROUP_ADDR_SCOPE_5);
+
+        assertNotNull(pollForPacketOnInfraNetwork(ICMPV6_ECHO_REPLY_TYPE, ftd.getOmrAddress()));
+    }
+
+    @Test
+    public void multicastRouting_inboundForwarding_afterBrRejoinFtdRepliesSubscribedAddress()
+            throws Exception {
+        assumeTrue(isMulticastRoutingSupported());
+
+        // 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(isMulticastRoutingSupported());
+        /*
+         * <pre>
+         * Topology:
+         *                 infra network                       Thread
+         * infra device -------------------- Border Router -------------- Full Thread device
+         *                                   (Cuttlefish)
+         * </pre>
+         */
+
+        FullThreadDevice ftd = mFtds.get(0);
+        startFtdChild(ftd);
+        ftd.subscribeMulticastAddress(GROUP_ADDR_SCOPE_3);
+
+        mInfraDevice.sendEchoRequest(GROUP_ADDR_SCOPE_3);
+
+        assertNull(pollForPacketOnInfraNetwork(ICMPV6_ECHO_REPLY_TYPE, ftd.getOmrAddress()));
+    }
+
+    @Test
+    public void multicastRouting_ftdNotSubscribedMulticastAddress_cannotPingFromInfraDevice()
+            throws Exception {
+        assumeTrue(isMulticastRoutingSupported());
+        /*
+         * <pre>
+         * Topology:
+         *                 infra network                       Thread
+         * infra device -------------------- Border Router -------------- Full Thread device
+         *                                   (Cuttlefish)
+         * </pre>
+         */
+
+        FullThreadDevice ftd = mFtds.get(0);
+        startFtdChild(ftd);
+
+        mInfraDevice.sendEchoRequest(GROUP_ADDR_SCOPE_4);
+
+        assertNull(pollForPacketOnInfraNetwork(ICMPV6_ECHO_REPLY_TYPE, ftd.getOmrAddress()));
+    }
+
+    @Test
+    public void multicastRouting_multipleFtdsSubscribedDifferentAddresses_canPingFromInfraDevice()
+            throws Exception {
+        assumeTrue(isMulticastRoutingSupported());
+        /*
+         * <pre>
+         * Topology:
+         *                 infra network                       Thread
+         * infra device -------------------- Border Router -------------- Full Thread device 1
+         *                                   (Cuttlefish)
+         *                                         |
+         *                                         | Thread
+         *                                         |
+         *                                  Full Thread device 2
+         * </pre>
+         */
+
+        FullThreadDevice ftd1 = mFtds.get(0);
+        startFtdChild(ftd1);
+        subscribeMulticastAddressAndWait(ftd1, GROUP_ADDR_SCOPE_5);
+
+        FullThreadDevice ftd2 = mFtds.get(1);
+        startFtdChild(ftd2);
+        subscribeMulticastAddressAndWait(ftd2, GROUP_ADDR_SCOPE_4);
+
+        mInfraDevice.sendEchoRequest(GROUP_ADDR_SCOPE_5);
+
+        assertNotNull(pollForPacketOnInfraNetwork(ICMPV6_ECHO_REPLY_TYPE, ftd1.getOmrAddress()));
+
+        // Verify ping reply from ftd1 and ftd2 separately as the order of replies can't be
+        // predicted.
+        mInfraDevice.sendEchoRequest(GROUP_ADDR_SCOPE_4);
+
+        assertNotNull(pollForPacketOnInfraNetwork(ICMPV6_ECHO_REPLY_TYPE, ftd2.getOmrAddress()));
+    }
+
+    @Test
+    public void multicastRouting_multipleFtdsSubscribedSameAddress_canPingFromInfraDevice()
+            throws Exception {
+        assumeTrue(isMulticastRoutingSupported());
+        /*
+         * <pre>
+         * Topology:
+         *                 infra network                       Thread
+         * infra device -------------------- Border Router -------------- Full Thread device 1
+         *                                   (Cuttlefish)
+         *                                         |
+         *                                         | Thread
+         *                                         |
+         *                                  Full Thread device 2
+         * </pre>
+         */
+
+        FullThreadDevice ftd1 = mFtds.get(0);
+        startFtdChild(ftd1);
+        subscribeMulticastAddressAndWait(ftd1, GROUP_ADDR_SCOPE_5);
+
+        FullThreadDevice ftd2 = mFtds.get(1);
+        startFtdChild(ftd2);
+        subscribeMulticastAddressAndWait(ftd2, GROUP_ADDR_SCOPE_5);
+
+        mInfraDevice.sendEchoRequest(GROUP_ADDR_SCOPE_5);
+
+        assertNotNull(pollForPacketOnInfraNetwork(ICMPV6_ECHO_REPLY_TYPE, ftd1.getOmrAddress()));
+
+        // Send the request twice as the order of replies from ftd1 and ftd2 are not guaranteed
+        mInfraDevice.sendEchoRequest(GROUP_ADDR_SCOPE_5);
+
+        assertNotNull(pollForPacketOnInfraNetwork(ICMPV6_ECHO_REPLY_TYPE, ftd2.getOmrAddress()));
+    }
+
+    @Test
+    public void multicastRouting_outboundForwarding_scopeLargerThan3IsForwarded() throws Exception {
+        assumeTrue(isMulticastRoutingSupported());
+        /*
+         * <pre>
+         * Topology:
+         *                 infra network                       Thread
+         * infra device -------------------- Border Router -------------- Full Thread device
+         *                                   (Cuttlefish)
+         * </pre>
+         */
+
+        FullThreadDevice ftd = mFtds.get(0);
+        startFtdChild(ftd);
+        Inet6Address ftdOmr = ftd.getOmrAddress();
+
+        ftd.ping(GROUP_ADDR_SCOPE_5);
+        ftd.ping(GROUP_ADDR_SCOPE_4);
+
+        assertNotNull(
+                pollForPacketOnInfraNetwork(ICMPV6_ECHO_REQUEST_TYPE, ftdOmr, GROUP_ADDR_SCOPE_5));
+        assertNotNull(
+                pollForPacketOnInfraNetwork(ICMPV6_ECHO_REQUEST_TYPE, ftdOmr, GROUP_ADDR_SCOPE_4));
+    }
+
+    @Test
+    public void multicastRouting_outboundForwarding_scopeSmallerThan4IsNotForwarded()
+            throws Exception {
+        assumeTrue(isMulticastRoutingSupported());
+        /*
+         * <pre>
+         * Topology:
+         *                 infra network                       Thread
+         * infra device -------------------- Border Router -------------- Full Thread device
+         *                                   (Cuttlefish)
+         * </pre>
+         */
+
+        FullThreadDevice ftd = mFtds.get(0);
+        startFtdChild(ftd);
+
+        ftd.ping(GROUP_ADDR_SCOPE_3);
+
+        assertNull(
+                pollForPacketOnInfraNetwork(
+                        ICMPV6_ECHO_REQUEST_TYPE, ftd.getOmrAddress(), GROUP_ADDR_SCOPE_3));
+    }
+
+    @Test
+    public void multicastRouting_outboundForwarding_llaToScope4IsNotForwarded() throws Exception {
+        assumeTrue(isMulticastRoutingSupported());
+        /*
+         * <pre>
+         * Topology:
+         *                 infra network                       Thread
+         * infra device -------------------- Border Router -------------- Full Thread device
+         *                                   (Cuttlefish)
+         * </pre>
+         */
+
+        FullThreadDevice ftd = mFtds.get(0);
+        startFtdChild(ftd);
+        Inet6Address ftdLla = ftd.getLinkLocalAddress();
+        assertNotNull(ftdLla);
+
+        ftd.ping(GROUP_ADDR_SCOPE_4, ftdLla, 100 /* size */, 1 /* count */);
+
+        assertNull(
+                pollForPacketOnInfraNetwork(ICMPV6_ECHO_REQUEST_TYPE, ftdLla, GROUP_ADDR_SCOPE_4));
+    }
+
+    @Test
+    public void multicastRouting_outboundForwarding_mlaToScope4IsNotForwarded() throws Exception {
+        assumeTrue(isMulticastRoutingSupported());
+        /*
+         * <pre>
+         * Topology:
+         *                 infra network                       Thread
+         * infra device -------------------- Border Router -------------- Full Thread device
+         *                                   (Cuttlefish)
+         * </pre>
+         */
+
+        FullThreadDevice ftd = mFtds.get(0);
+        startFtdChild(ftd);
+        List<Inet6Address> ftdMlas = ftd.getMeshLocalAddresses();
+        assertFalse(ftdMlas.isEmpty());
+
+        for (Inet6Address ftdMla : ftdMlas) {
+            ftd.ping(GROUP_ADDR_SCOPE_4, ftdMla, 100 /* size */, 1 /* count */);
+
+            assertNull(
+                    pollForPacketOnInfraNetwork(
+                            ICMPV6_ECHO_REQUEST_TYPE, ftdMla, GROUP_ADDR_SCOPE_4));
+        }
+    }
+
+    @Test
+    public void multicastRouting_infraNetworkSwitch_ftdRepliesToSubscribedAddress()
+            throws Exception {
+        assumeTrue(isMulticastRoutingSupported());
+        /*
+         * <pre>
+         * Topology:
+         *                 infra network                       Thread
+         * infra device -------------------- Border Router -------------- Full Thread device
+         *                                   (Cuttlefish)
+         * </pre>
+         */
+
+        FullThreadDevice ftd = mFtds.get(0);
+        startFtdChild(ftd);
+        subscribeMulticastAddressAndWait(ftd, GROUP_ADDR_SCOPE_5);
+        Inet6Address ftdOmr = ftd.getOmrAddress();
+
+        // Destroy infra link and re-create
+        tearDownInfraNetwork();
+        setUpInfraNetwork();
+        mInfraNetworkReader = newPacketReader(mInfraNetworkTracker.getTestIface(), mHandler);
+        startInfraDevice();
+
+        mInfraDevice.sendEchoRequest(GROUP_ADDR_SCOPE_5);
+
+        assertNotNull(pollForPacketOnInfraNetwork(ICMPV6_ECHO_REPLY_TYPE, ftdOmr));
+    }
+
+    @Test
+    public void multicastRouting_infraNetworkSwitch_outboundPacketIsForwarded() throws Exception {
+        assumeTrue(isMulticastRoutingSupported());
+        /*
+         * <pre>
+         * Topology:
+         *                 infra network                       Thread
+         * infra device -------------------- Border Router -------------- Full Thread device
+         *                                   (Cuttlefish)
+         * </pre>
+         */
+
+        FullThreadDevice ftd = mFtds.get(0);
+        startFtdChild(ftd);
+        Inet6Address ftdOmr = ftd.getOmrAddress();
+
+        // Destroy infra link and re-create
+        tearDownInfraNetwork();
+        setUpInfraNetwork();
+        mInfraNetworkReader = newPacketReader(mInfraNetworkTracker.getTestIface(), mHandler);
+        startInfraDevice();
+
+        ftd.ping(GROUP_ADDR_SCOPE_4);
+
+        assertNotNull(
+                pollForPacketOnInfraNetwork(ICMPV6_ECHO_REQUEST_TYPE, ftdOmr, GROUP_ADDR_SCOPE_4));
+    }
+
+    private void setUpInfraNetwork() {
         mInfraNetworkTracker =
                 runAsShell(
                         MANAGE_TEST_NETWORKS,
@@ -104,83 +589,80 @@
                 PERMISSION_THREAD_NETWORK_PRIVILEGED,
                 NETWORK_SETTINGS,
                 () -> {
-                    CountDownLatch latch = new CountDownLatch(1);
-                    mThreadNetworkController.setTestNetworkAsUpstream(
+                    CompletableFuture<Void> future = new CompletableFuture<>();
+                    mController.setTestNetworkAsUpstream(
                             mInfraNetworkTracker.getTestIface().getInterfaceName(),
-                            MoreExecutors.directExecutor(),
-                            v -> {
-                                latch.countDown();
-                            });
-                    latch.await();
+                            directExecutor(),
+                            future::complete);
+                    future.get(5, TimeUnit.SECONDS);
                 });
     }
 
-    @After
-    public void tearDown() throws Exception {
-        runAsShell(
-                PERMISSION_THREAD_NETWORK_PRIVILEGED,
-                NETWORK_SETTINGS,
-                () -> {
-                    CountDownLatch latch = new CountDownLatch(2);
-                    mThreadNetworkController.setTestNetworkAsUpstream(
-                            null, MoreExecutors.directExecutor(), v -> latch.countDown());
-                    mThreadNetworkController.leave(
-                            MoreExecutors.directExecutor(), v -> latch.countDown());
-                    latch.await(10, TimeUnit.SECONDS);
-                });
+    private void tearDownInfraNetwork() {
         runAsShell(MANAGE_TEST_NETWORKS, () -> mInfraNetworkTracker.teardown());
-
-        mHandlerThread.quitSafely();
-        mHandlerThread.join();
     }
 
-    @Test
-    public void unicastRouting_infraDevicePingTheadDeviceOmr_replyReceived() throws Exception {
-        assumeTrue(isSimulatedThreadRadioSupported());
-
-        /*
-         * <pre>
-         * Topology:
-         *                 infra network                       Thread
-         * infra device -------------------- Border Router -------------- Full Thread device
-         *                                   (Cuttlefish)
-         * </pre>
-         */
-
-        // BR forms a network.
+    private void startBrLeader() throws Exception {
+        CompletableFuture<Void> joinFuture = new CompletableFuture<>();
         runAsShell(
                 PERMISSION_THREAD_NETWORK_PRIVILEGED,
-                () -> {
-                    mThreadNetworkController.join(
-                            DEFAULT_DATASET, MoreExecutors.directExecutor(), result -> {});
-                });
-        waitForStateAnyOf(
-                mThreadNetworkController, List.of(DEVICE_ROLE_LEADER), 30 /* timeoutSeconds */);
+                () -> mController.join(DEFAULT_DATASET, directExecutor(), joinFuture::complete));
+        joinFuture.get(RESTART_JOIN_TIMEOUT.toSeconds(), TimeUnit.SECONDS);
+    }
 
-        // Creates a Full Thread Device (FTD) and lets it join the network.
-        FullThreadDevice ftd = new FullThreadDevice(5 /* node ID */);
+    private void startFtdChild(FullThreadDevice ftd) throws Exception {
         ftd.factoryReset();
         ftd.joinNetwork(DEFAULT_DATASET);
-        ftd.waitForStateAnyOf(List.of("router", "child"), 10 /* timeoutSeconds */);
-        waitFor(() -> ftd.getOmrAddress() != null, 60 /* timeoutSeconds */);
+        ftd.waitForStateAnyOf(List.of("router", "child"), JOIN_TIMEOUT);
+        waitFor(() -> ftd.getOmrAddress() != null, Duration.ofSeconds(60));
         Inet6Address ftdOmr = ftd.getOmrAddress();
         assertNotNull(ftdOmr);
+    }
 
-        // Creates a infra network device.
-        TapPacketReader infraNetworkReader =
-                newPacketReader(mInfraNetworkTracker.getTestIface(), mHandler);
-        InfraNetworkDevice infraDevice =
-                new InfraNetworkDevice(MacAddress.fromString("1:2:3:4:5:6"), infraNetworkReader);
-        infraDevice.runSlaac(60 /* timeoutSeconds */);
-        assertNotNull(infraDevice.ipv6Addr);
+    private void startInfraDevice() throws Exception {
+        mInfraDevice =
+                new InfraNetworkDevice(MacAddress.fromString("1:2:3:4:5:6"), mInfraNetworkReader);
+        mInfraDevice.runSlaac(Duration.ofSeconds(60));
+        assertNotNull(mInfraDevice.ipv6Addr);
+    }
 
-        // Infra device sends an echo request to FTD's OMR.
-        infraDevice.sendEchoRequest(ftdOmr);
+    private void assertInfraLinkMemberOfGroup(Inet6Address address) throws Exception {
+        waitFor(
+                () ->
+                        isInMulticastGroup(
+                                mInfraNetworkTracker.getTestIface().getInterfaceName(), address),
+                Duration.ofSeconds(3));
+    }
 
-        // Infra device receives an echo reply sent by FTD.
-        assertNotNull(
-                readPacketFrom(
-                        infraNetworkReader,
-                        p -> isExpectedIcmpv6Packet(p, ICMPV6_ECHO_REPLY_TYPE)));
+    private void assertInfraLinkNotMemberOfGroup(Inet6Address address) throws Exception {
+        waitFor(
+                () ->
+                        !isInMulticastGroup(
+                                mInfraNetworkTracker.getTestIface().getInterfaceName(), address),
+                Duration.ofSeconds(3));
+    }
+
+    private void subscribeMulticastAddressAndWait(FullThreadDevice ftd, Inet6Address address)
+            throws Exception {
+        ftd.subscribeMulticastAddress(address);
+
+        assertInfraLinkMemberOfGroup(address);
+    }
+
+    private byte[] pollForPacketOnInfraNetwork(int type, Inet6Address srcAddress) {
+        return pollForPacketOnInfraNetwork(type, srcAddress, null);
+    }
+
+    private byte[] pollForPacketOnInfraNetwork(
+            int type, Inet6Address srcAddress, Inet6Address destAddress) {
+        Predicate<byte[]> filter;
+        filter =
+                p ->
+                        (isExpectedIcmpv6Packet(p, type)
+                                && (srcAddress == null ? true : isFromIpv6Source(p, srcAddress))
+                                && (destAddress == null
+                                        ? true
+                                        : isToIpv6Destination(p, destAddress)));
+        return pollForPacket(mInfraNetworkReader, filter);
     }
 }
diff --git a/thread/tests/integration/src/android/net/thread/FullThreadDevice.java b/thread/tests/integration/src/android/net/thread/FullThreadDevice.java
deleted file mode 100644
index 01638f3..0000000
--- a/thread/tests/integration/src/android/net/thread/FullThreadDevice.java
+++ /dev/null
@@ -1,180 +0,0 @@
-/*
- * Copyright (C) 2023 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 android.net.thread;
-
-import static android.net.thread.IntegrationTestUtils.waitFor;
-
-import static com.google.common.io.BaseEncoding.base16;
-
-import static org.junit.Assert.fail;
-
-import android.net.InetAddresses;
-import android.net.IpPrefix;
-
-import java.io.BufferedReader;
-import java.io.BufferedWriter;
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.io.OutputStreamWriter;
-import java.net.Inet6Address;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.TimeoutException;
-
-/**
- * A class that launches and controls a simulation Full Thread Device (FTD).
- *
- * <p>This class launches an `ot-cli-ftd` process and communicates with it via command line input
- * and output. See <a
- * href="https://github.com/openthread/openthread/blob/main/src/cli/README.md">this page</a> for
- * available commands.
- */
-public final class FullThreadDevice {
-    private final Process mProcess;
-    private final BufferedReader mReader;
-    private final BufferedWriter mWriter;
-
-    private ActiveOperationalDataset mActiveOperationalDataset;
-
-    /**
-     * Constructs a {@link FullThreadDevice} for the given node ID.
-     *
-     * <p>It launches an `ot-cli-ftd` process using the given node ID. The node ID is an integer in
-     * range [1, OPENTHREAD_SIMULATION_MAX_NETWORK_SIZE]. `OPENTHREAD_SIMULATION_MAX_NETWORK_SIZE`
-     * is defined in `external/openthread/examples/platforms/simulation/platform-config.h`.
-     *
-     * @param nodeId the node ID for the simulation Full Thread Device.
-     * @throws IllegalStateException the node ID is already occupied by another simulation Thread
-     *     device.
-     */
-    public FullThreadDevice(int nodeId) {
-        try {
-            mProcess = Runtime.getRuntime().exec("/system/bin/ot-cli-ftd " + nodeId);
-        } catch (IOException e) {
-            throw new IllegalStateException("Failed to start ot-cli-ftd (id=" + nodeId + ")", e);
-        }
-        mReader = new BufferedReader(new InputStreamReader(mProcess.getInputStream()));
-        mWriter = new BufferedWriter(new OutputStreamWriter(mProcess.getOutputStream()));
-        mActiveOperationalDataset = null;
-    }
-
-    /**
-     * Returns an OMR (Off-Mesh-Routable) address on this device if any.
-     *
-     * <p>This methods goes through all unicast addresses on the device and returns the first
-     * address which is neither link-local nor mesh-local.
-     */
-    public Inet6Address getOmrAddress() {
-        List<String> addresses = executeCommand("ipaddr");
-        IpPrefix meshLocalPrefix = mActiveOperationalDataset.getMeshLocalPrefix();
-        for (String address : addresses) {
-            if (address.startsWith("fe80:")) {
-                continue;
-            }
-            Inet6Address addr = (Inet6Address) InetAddresses.parseNumericAddress(address);
-            if (!meshLocalPrefix.contains(addr)) {
-                return addr;
-            }
-        }
-        return null;
-    }
-
-    /**
-     * Joins the Thread network using the given {@link ActiveOperationalDataset}.
-     *
-     * @param dataset the Active Operational Dataset
-     */
-    public void joinNetwork(ActiveOperationalDataset dataset) {
-        mActiveOperationalDataset = dataset;
-        executeCommand("dataset set active " + base16().lowerCase().encode(dataset.toThreadTlvs()));
-        executeCommand("ifconfig up");
-        executeCommand("thread start");
-    }
-
-    /** Stops the Thread network radio. */
-    public void stopThreadRadio() {
-        executeCommand("thread stop");
-        executeCommand("ifconfig down");
-    }
-
-    /**
-     * Waits for the Thread device to enter the any state of the given {@link List<String>}.
-     *
-     * @param states the list of states to wait for. Valid states are "disabled", "detached",
-     *     "child", "router" and "leader".
-     * @param timeoutSeconds the number of seconds to wait for.
-     */
-    public void waitForStateAnyOf(List<String> states, int timeoutSeconds) throws TimeoutException {
-        waitFor(() -> states.contains(getState()), timeoutSeconds);
-    }
-
-    /**
-     * Gets the state of the Thread device.
-     *
-     * @return a string representing the state.
-     */
-    public String getState() {
-        return executeCommand("state").get(0);
-    }
-
-    /** Runs the "factoryreset" command on the device. */
-    public void factoryReset() {
-        try {
-            mWriter.write("factoryreset\n");
-            mWriter.flush();
-            // fill the input buffer to avoid truncating next command
-            for (int i = 0; i < 1000; ++i) {
-                mWriter.write("\n");
-            }
-            mWriter.flush();
-        } catch (IOException e) {
-            throw new IllegalStateException("Failed to run factoryreset on ot-cli-ftd", e);
-        }
-    }
-
-    private List<String> executeCommand(String command) {
-        try {
-            mWriter.write(command + "\n");
-            mWriter.flush();
-        } catch (IOException e) {
-            throw new IllegalStateException(
-                    "Failed to write the command " + command + " to ot-cli-ftd", e);
-        }
-        try {
-            return readUntilDone();
-        } catch (IOException e) {
-            throw new IllegalStateException(
-                    "Failed to read the ot-cli-ftd output of command: " + command, e);
-        }
-    }
-
-    private List<String> readUntilDone() throws IOException {
-        ArrayList<String> result = new ArrayList<>();
-        String line;
-        while ((line = mReader.readLine()) != null) {
-            if (line.equals("Done")) {
-                break;
-            }
-            if (line.startsWith("Error:")) {
-                fail("ot-cli-ftd reported an error: " + line);
-            }
-            if (!line.startsWith("> ")) {
-                result.add(line);
-            }
-        }
-        return result;
-    }
-}
diff --git a/thread/tests/integration/src/android/net/thread/IntegrationTestUtils.java b/thread/tests/integration/src/android/net/thread/IntegrationTestUtils.java
deleted file mode 100644
index c465d57..0000000
--- a/thread/tests/integration/src/android/net/thread/IntegrationTestUtils.java
+++ /dev/null
@@ -1,228 +0,0 @@
-/*
- * Copyright (C) 2023 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 android.net.thread;
-
-import static android.system.OsConstants.IPPROTO_ICMPV6;
-
-import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ND_OPTION_PIO;
-import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ROUTER_ADVERTISEMENT;
-
-import static com.google.common.util.concurrent.MoreExecutors.directExecutor;
-
-import android.net.TestNetworkInterface;
-import android.os.Handler;
-import android.os.SystemClock;
-import android.os.SystemProperties;
-
-import com.android.net.module.util.Struct;
-import com.android.net.module.util.structs.Icmpv6Header;
-import com.android.net.module.util.structs.Ipv6Header;
-import com.android.net.module.util.structs.PrefixInformationOption;
-import com.android.net.module.util.structs.RaHeader;
-import com.android.testutils.HandlerUtils;
-import com.android.testutils.TapPacketReader;
-
-import com.google.common.util.concurrent.SettableFuture;
-
-import java.io.FileDescriptor;
-import java.nio.ByteBuffer;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
-import java.util.function.Predicate;
-import java.util.function.Supplier;
-
-/** Static utility methods relating to Thread integration tests. */
-public final class IntegrationTestUtils {
-    private IntegrationTestUtils() {}
-
-    /** Returns whether the device supports simulated Thread radio. */
-    public static boolean isSimulatedThreadRadioSupported() {
-        // The integration test uses SIMULATION Thread radio so that it only supports CuttleFish.
-        return SystemProperties.get("ro.product.model").startsWith("Cuttlefish");
-    }
-
-    /**
-     * Waits for the given {@link Supplier} to be true until given timeout.
-     *
-     * <p>It checks the condition once every second.
-     *
-     * @param condition the condition to check.
-     * @param timeoutSeconds the number of seconds to wait for.
-     * @throws TimeoutException if the condition is not met after the timeout.
-     */
-    public static void waitFor(Supplier<Boolean> condition, int timeoutSeconds)
-            throws TimeoutException {
-        waitFor(condition, timeoutSeconds, 1);
-    }
-
-    /**
-     * Waits for the given {@link Supplier} to be true until given timeout.
-     *
-     * <p>It checks the condition once every {@code intervalSeconds}.
-     *
-     * @param condition the condition to check.
-     * @param timeoutSeconds the number of seconds to wait for.
-     * @param intervalSeconds the period to check the {@code condition}.
-     * @throws TimeoutException if the condition is still not met when the timeout expires.
-     */
-    public static void waitFor(Supplier<Boolean> condition, int timeoutSeconds, int intervalSeconds)
-            throws TimeoutException {
-        for (int i = 0; i < timeoutSeconds; i += intervalSeconds) {
-            if (condition.get()) {
-                return;
-            }
-            SystemClock.sleep(intervalSeconds * 1000L);
-        }
-        if (condition.get()) {
-            return;
-        }
-        throw new TimeoutException(
-                String.format(
-                        "The condition failed to become true in %d seconds.", timeoutSeconds));
-    }
-
-    /**
-     * Creates a {@link TapPacketReader} given the {@link TestNetworkInterface} and {@link Handler}.
-     *
-     * @param testNetworkInterface the TUN interface of the test network.
-     * @param handler the handler to process the packets.
-     * @return the {@link TapPacketReader}.
-     */
-    public static TapPacketReader newPacketReader(
-            TestNetworkInterface testNetworkInterface, Handler handler) {
-        FileDescriptor fd = testNetworkInterface.getFileDescriptor().getFileDescriptor();
-        final TapPacketReader reader =
-                new TapPacketReader(handler, fd, testNetworkInterface.getMtu());
-        handler.post(() -> reader.start());
-        HandlerUtils.waitForIdle(handler, 5000 /* timeout in milliseconds */);
-        return reader;
-    }
-
-    /**
-     * Waits for the Thread module to enter any state of the given {@code deviceRoles}.
-     *
-     * @param controller the {@link ThreadNetworkController}.
-     * @param deviceRoles the desired device roles. See also {@link
-     *     ThreadNetworkController.DeviceRole}.
-     * @param timeoutSeconds the number of seconds ot wait for.
-     * @return the {@link ThreadNetworkController.DeviceRole} after waiting.
-     * @throws TimeoutException if the device hasn't become any of expected roles until the timeout
-     *     expires.
-     */
-    public static int waitForStateAnyOf(
-            ThreadNetworkController controller, List<Integer> deviceRoles, int timeoutSeconds)
-            throws TimeoutException {
-        SettableFuture<Integer> future = SettableFuture.create();
-        ThreadNetworkController.StateCallback callback =
-                newRole -> {
-                    if (deviceRoles.contains(newRole)) {
-                        future.set(newRole);
-                    }
-                };
-        controller.registerStateCallback(directExecutor(), callback);
-        try {
-            int role = future.get(timeoutSeconds, TimeUnit.SECONDS);
-            controller.unregisterStateCallback(callback);
-            return role;
-        } catch (InterruptedException | ExecutionException e) {
-            throw new TimeoutException(
-                    String.format(
-                            "The device didn't become an expected role in %d seconds.",
-                            timeoutSeconds));
-        }
-    }
-
-    /**
-     * Reads a packet from a given {@link TapPacketReader} that satisfies the {@code filter}.
-     *
-     * @param packetReader a TUN packet reader.
-     * @param filter the filter to be applied on the packet.
-     * @return the first IPv6 packet that satisfies the {@code filter}. If it has waited for more
-     *     than 3000ms to read the next packet, the method will return null.
-     */
-    public static byte[] readPacketFrom(TapPacketReader packetReader, Predicate<byte[]> filter) {
-        byte[] packet;
-        while ((packet = packetReader.poll(3000 /* timeoutMs */)) != null) {
-            if (filter.test(packet)) return packet;
-        }
-        return null;
-    }
-
-    /** Returns {@code true} if {@code packet} is an ICMPv6 packet of given {@code type}. */
-    public static boolean isExpectedIcmpv6Packet(byte[] packet, int type) {
-        if (packet == null) {
-            return false;
-        }
-        ByteBuffer buf = ByteBuffer.wrap(packet);
-        try {
-            if (Struct.parse(Ipv6Header.class, buf).nextHeader != (byte) IPPROTO_ICMPV6) {
-                return false;
-            }
-            return Struct.parse(Icmpv6Header.class, buf).type == (short) type;
-        } catch (IllegalArgumentException ignored) {
-            // It's fine that the passed in packet is malformed because it's could be sent
-            // by anybody.
-        }
-        return false;
-    }
-
-    /** Returns the Prefix Information Options (PIO) extracted from an ICMPv6 RA message. */
-    public static List<PrefixInformationOption> getRaPios(byte[] raMsg) {
-        final ArrayList<PrefixInformationOption> pioList = new ArrayList<>();
-
-        if (raMsg == null) {
-            return pioList;
-        }
-
-        final ByteBuffer buf = ByteBuffer.wrap(raMsg);
-        final Ipv6Header ipv6Header = Struct.parse(Ipv6Header.class, buf);
-        if (ipv6Header.nextHeader != (byte) IPPROTO_ICMPV6) {
-            return pioList;
-        }
-
-        final Icmpv6Header icmpv6Header = Struct.parse(Icmpv6Header.class, buf);
-        if (icmpv6Header.type != (short) ICMPV6_ROUTER_ADVERTISEMENT) {
-            return pioList;
-        }
-
-        Struct.parse(RaHeader.class, buf);
-        while (buf.position() < raMsg.length) {
-            final int currentPos = buf.position();
-            final int type = Byte.toUnsignedInt(buf.get());
-            final int length = Byte.toUnsignedInt(buf.get());
-            if (type == ICMPV6_ND_OPTION_PIO) {
-                final ByteBuffer pioBuf =
-                        ByteBuffer.wrap(
-                                buf.array(),
-                                currentPos,
-                                Struct.getSize(PrefixInformationOption.class));
-                final PrefixInformationOption pio =
-                        Struct.parse(PrefixInformationOption.class, pioBuf);
-                pioList.add(pio);
-
-                // Move ByteBuffer position to the next option.
-                buf.position(currentPos + Struct.getSize(PrefixInformationOption.class));
-            } else {
-                // The length is in units of 8 octets.
-                buf.position(currentPos + (length * 8));
-            }
-        }
-        return pioList;
-    }
-}
diff --git a/thread/tests/integration/src/android/net/thread/ServiceDiscoveryTest.java b/thread/tests/integration/src/android/net/thread/ServiceDiscoveryTest.java
new file mode 100644
index 0000000..3493d9f
--- /dev/null
+++ b/thread/tests/integration/src/android/net/thread/ServiceDiscoveryTest.java
@@ -0,0 +1,352 @@
+/*
+ * 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.
+ */
+
+package android.net.thread;
+
+import static android.Manifest.permission.NETWORK_SETTINGS;
+import static android.net.InetAddresses.parseNumericAddress;
+import static android.net.thread.ThreadNetworkManager.PERMISSION_THREAD_NETWORK_PRIVILEGED;
+import static android.net.thread.utils.IntegrationTestUtils.JOIN_TIMEOUT;
+import static android.net.thread.utils.IntegrationTestUtils.RESTART_JOIN_TIMEOUT;
+import static android.net.thread.utils.IntegrationTestUtils.SERVICE_DISCOVERY_TIMEOUT;
+import static android.net.thread.utils.IntegrationTestUtils.discoverForServiceLost;
+import static android.net.thread.utils.IntegrationTestUtils.discoverService;
+import static android.net.thread.utils.IntegrationTestUtils.isSimulatedThreadRadioSupported;
+import static android.net.thread.utils.IntegrationTestUtils.resolveService;
+import static android.net.thread.utils.IntegrationTestUtils.resolveServiceUntil;
+import static android.net.thread.utils.IntegrationTestUtils.waitFor;
+
+import static com.android.testutils.TestPermissionUtil.runAsShell;
+
+import static com.google.common.io.BaseEncoding.base16;
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.util.concurrent.MoreExecutors.directExecutor;
+
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assume.assumeNotNull;
+import static org.junit.Assume.assumeTrue;
+
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+import static java.util.concurrent.TimeUnit.SECONDS;
+
+import android.content.Context;
+import android.net.nsd.NsdManager;
+import android.net.nsd.NsdServiceInfo;
+import android.net.thread.utils.FullThreadDevice;
+import android.net.thread.utils.TapTestNetworkTracker;
+import android.os.HandlerThread;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.filters.LargeTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.google.common.truth.Correspondence;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.net.Inet6Address;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Random;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeoutException;
+
+/** Integration test cases for Service Discovery feature. */
+@RunWith(AndroidJUnit4.class)
+@LargeTest
+@Ignore("TODO: b/328527773 - enable the test when it's stable")
+public class ServiceDiscoveryTest {
+    private static final String TAG = ServiceDiscoveryTest.class.getSimpleName();
+    private static final int NUM_FTD = 3;
+    private final Context mContext = ApplicationProvider.getApplicationContext();
+
+    private HandlerThread mHandlerThread;
+    private ThreadNetworkController mController;
+    private NsdManager mNsdManager;
+    private TapTestNetworkTracker mTestNetworkTracker;
+    private List<FullThreadDevice> mFtds;
+
+    // A valid Thread Active Operational Dataset generated from OpenThread CLI "dataset init new".
+    private static final byte[] DEFAULT_DATASET_TLVS =
+            base16().decode(
+                            "0E080000000000010000000300001335060004001FFFE002"
+                                    + "08ACC214689BC40BDF0708FD64DB1225F47E0B0510F26B31"
+                                    + "53760F519A63BAFDDFFC80D2AF030F4F70656E5468726561"
+                                    + "642D643961300102D9A00410A245479C836D551B9CA557F7"
+                                    + "B9D351B40C0402A0FFF8");
+    private static final ActiveOperationalDataset DEFAULT_DATASET =
+            ActiveOperationalDataset.fromThreadTlvs(DEFAULT_DATASET_TLVS);
+
+    private static final Correspondence<byte[], byte[]> BYTE_ARRAY_EQUALITY =
+            Correspondence.from(Arrays::equals, "is equivalent to");
+
+    @Before
+    public void setUp() throws Exception {
+        final ThreadNetworkManager manager = mContext.getSystemService(ThreadNetworkManager.class);
+        if (manager != null) {
+            mController = manager.getAllThreadNetworkControllers().get(0);
+        }
+
+        // Run the tests on only devices where the Thread feature is available.
+        assumeNotNull(mController);
+
+        // Run the tests only when the device uses simulated Thread radio.
+        assumeTrue(isSimulatedThreadRadioSupported());
+
+        // BR forms a network.
+        CompletableFuture<Void> joinFuture = new CompletableFuture<>();
+        runAsShell(
+                PERMISSION_THREAD_NETWORK_PRIVILEGED,
+                () -> mController.join(DEFAULT_DATASET, directExecutor(), joinFuture::complete));
+        joinFuture.get(RESTART_JOIN_TIMEOUT.toMillis(), MILLISECONDS);
+
+        mNsdManager = mContext.getSystemService(NsdManager.class);
+
+        mHandlerThread = new HandlerThread(TAG);
+        mHandlerThread.start();
+
+        mTestNetworkTracker = new TapTestNetworkTracker(mContext, mHandlerThread.getLooper());
+        assertThat(mTestNetworkTracker).isNotNull();
+        runAsShell(
+                PERMISSION_THREAD_NETWORK_PRIVILEGED,
+                NETWORK_SETTINGS,
+                () -> {
+                    CompletableFuture<Void> future = new CompletableFuture<>();
+                    mController.setTestNetworkAsUpstream(
+                            mTestNetworkTracker.getInterfaceName(),
+                            directExecutor(),
+                            v -> future.complete(null));
+                    future.get(5, SECONDS);
+                });
+        // Create the FTDs in setUp() so that the FTDs can be safely released in tearDown().
+        // Don't create new FTDs in test cases.
+        mFtds = new ArrayList<>();
+        for (int i = 0; i < NUM_FTD; ++i) {
+            FullThreadDevice ftd = new FullThreadDevice(10 + i /* node ID */);
+            ftd.autoStartSrpClient();
+            mFtds.add(ftd);
+        }
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        if (mController == null) {
+            return;
+        }
+        if (!isSimulatedThreadRadioSupported()) {
+            return;
+        }
+        for (FullThreadDevice ftd : mFtds) {
+            // Clear registered SRP hosts and services
+            if (ftd.isSrpHostRegistered()) {
+                ftd.removeSrpHost();
+            }
+            ftd.destroy();
+        }
+        if (mTestNetworkTracker != null) {
+            mTestNetworkTracker.tearDown();
+        }
+        if (mHandlerThread != null) {
+            mHandlerThread.quitSafely();
+            mHandlerThread.join();
+        }
+        runAsShell(
+                PERMISSION_THREAD_NETWORK_PRIVILEGED,
+                NETWORK_SETTINGS,
+                () -> {
+                    CompletableFuture<Void> setUpstreamFuture = new CompletableFuture<>();
+                    CompletableFuture<Void> leaveFuture = new CompletableFuture<>();
+                    mController.setTestNetworkAsUpstream(
+                            null, directExecutor(), v -> setUpstreamFuture.complete(null));
+                    mController.leave(directExecutor(), v -> leaveFuture.complete(null));
+                    setUpstreamFuture.get(5, SECONDS);
+                    leaveFuture.get(5, SECONDS);
+                });
+    }
+
+    @Test
+    public void advertisingProxy_multipleSrpClientsRegisterServices_servicesResolvableByMdns()
+            throws Exception {
+        /*
+         * <pre>
+         * Topology:
+         *                    Thread
+         *  Border Router -------------- Full Thread device 1
+         *  (Cuttlefish)         |
+         *                       +------ Full Thread device 2
+         *                       |
+         *                       +------ Full Thread device 3
+         * </pre>
+         */
+
+        // Creates Full Thread Devices (FTD) and let them join the network.
+        for (FullThreadDevice ftd : mFtds) {
+            ftd.joinNetwork(DEFAULT_DATASET);
+            ftd.waitForStateAnyOf(List.of("router", "child"), JOIN_TIMEOUT);
+        }
+
+        int randomId = new Random().nextInt(10_000);
+
+        String serviceNamePrefix = "service-" + randomId + "-";
+        String serviceTypePrefix = "_test" + randomId;
+        String hostnamePrefix = "host-" + randomId + "-";
+
+        // For every FTD, let it register an SRP service.
+        for (int i = 0; i < mFtds.size(); ++i) {
+            FullThreadDevice ftd = mFtds.get(i);
+            ftd.setSrpHostname(hostnamePrefix + i);
+            ftd.setSrpHostAddresses(List.of(ftd.getOmrAddress(), ftd.getMlEid()));
+            ftd.addSrpService(
+                    serviceNamePrefix + i,
+                    serviceTypePrefix + i + "._tcp",
+                    List.of("_sub1", "_sub2"),
+                    12345 /* port */,
+                    Map.of("key1", bytes(0x01, 0x02), "key2", bytes(i)));
+        }
+
+        // Check the advertised services are discoverable and resolvable by NsdManager
+        for (int i = 0; i < mFtds.size(); ++i) {
+            NsdServiceInfo discoveredService =
+                    discoverService(mNsdManager, serviceTypePrefix + i + "._tcp");
+            assertThat(discoveredService).isNotNull();
+            NsdServiceInfo resolvedService = resolveService(mNsdManager, discoveredService);
+            assertThat(resolvedService.getServiceName()).isEqualTo(serviceNamePrefix + i);
+            assertThat(resolvedService.getServiceType()).isEqualTo(serviceTypePrefix + i + "._tcp");
+            assertThat(resolvedService.getPort()).isEqualTo(12345);
+            assertThat(resolvedService.getAttributes())
+                    .comparingValuesUsing(BYTE_ARRAY_EQUALITY)
+                    .containsExactly("key1", bytes(0x01, 0x02), "key2", bytes(i));
+            assertThat(resolvedService.getHostname()).isEqualTo(hostnamePrefix + i);
+            assertThat(resolvedService.getHostAddresses())
+                    .containsExactly(mFtds.get(i).getOmrAddress());
+        }
+    }
+
+    @Test
+    public void advertisingProxy_srpClientUpdatesService_updatedServiceResolvableByMdns()
+            throws Exception {
+        /*
+         * <pre>
+         * Topology:
+         *                    Thread
+         *  Border Router -------------- Full Thread device
+         *  (Cuttlefish)
+         * </pre>
+         */
+
+        // Creates a Full Thread Devices (FTD) and let it join the network.
+        FullThreadDevice ftd = mFtds.get(0);
+        ftd.joinNetwork(DEFAULT_DATASET);
+        ftd.waitForStateAnyOf(List.of("router", "child"), JOIN_TIMEOUT);
+        ftd.setSrpHostname("my-host");
+        ftd.setSrpHostAddresses(List.of((Inet6Address) parseNumericAddress("2001:db8::1")));
+        ftd.addSrpService(
+                "my-service",
+                "_test._tcp",
+                Collections.emptyList() /* subtypes */,
+                12345 /* port */,
+                Map.of("key1", bytes(0x01, 0x02), "key2", bytes(0x03)));
+
+        // Update the host addresses
+        ftd.setSrpHostAddresses(
+                List.of(
+                        (Inet6Address) parseNumericAddress("2001:db8::1"),
+                        (Inet6Address) parseNumericAddress("2001:db8::2")));
+        // Update the service
+        ftd.updateSrpService(
+                "my-service", "_test._tcp", List.of("_sub3"), 11111, Map.of("key1", bytes(0x04)));
+        waitFor(ftd::isSrpHostRegistered, SERVICE_DISCOVERY_TIMEOUT);
+
+        // Check the advertised service is discoverable and resolvable by NsdManager
+        NsdServiceInfo discoveredService = discoverService(mNsdManager, "_test._tcp");
+        assertThat(discoveredService).isNotNull();
+        NsdServiceInfo resolvedService =
+                resolveServiceUntil(
+                        mNsdManager,
+                        discoveredService,
+                        s -> s.getPort() == 11111 && s.getHostAddresses().size() == 2);
+        assertThat(resolvedService.getServiceName()).isEqualTo("my-service");
+        assertThat(resolvedService.getServiceType()).isEqualTo("_test._tcp");
+        assertThat(resolvedService.getPort()).isEqualTo(11111);
+        assertThat(resolvedService.getAttributes())
+                .comparingValuesUsing(BYTE_ARRAY_EQUALITY)
+                .containsExactly("key1", bytes(0x04));
+        assertThat(resolvedService.getHostname()).isEqualTo("my-host");
+        assertThat(resolvedService.getHostAddresses())
+                .containsExactly(
+                        parseNumericAddress("2001:db8::1"), parseNumericAddress("2001:db8::2"));
+    }
+
+    @Test
+    public void advertisingProxy_srpClientUnregistersService_serviceIsNotDiscoverableByMdns()
+            throws Exception {
+        /*
+         * <pre>
+         * Topology:
+         *                    Thread
+         *  Border Router -------------- Full Thread device
+         *  (Cuttlefish)
+         * </pre>
+         */
+
+        // Creates a Full Thread Devices (FTD) and let it join the network.
+        FullThreadDevice ftd = mFtds.get(0);
+        ftd.joinNetwork(DEFAULT_DATASET);
+        ftd.waitForStateAnyOf(List.of("router", "child"), JOIN_TIMEOUT);
+        ftd.setSrpHostname("my-host");
+        ftd.setSrpHostAddresses(
+                List.of(
+                        (Inet6Address) parseNumericAddress("2001:db8::1"),
+                        (Inet6Address) parseNumericAddress("2001:db8::2")));
+        ftd.addSrpService(
+                "my-service",
+                "_test._udp",
+                List.of("_sub1"),
+                12345 /* port */,
+                Map.of("key1", bytes(0x01, 0x02), "key2", bytes(0x03)));
+        // Wait for the service to be discoverable by NsdManager.
+        assertThat(discoverService(mNsdManager, "_test._udp")).isNotNull();
+
+        // Unregister the service.
+        CompletableFuture<NsdServiceInfo> serviceLostFuture = new CompletableFuture<>();
+        NsdManager.DiscoveryListener listener =
+                discoverForServiceLost(mNsdManager, "_test._udp", serviceLostFuture);
+        ftd.removeSrpService("my-service", "_test._udp", true /* notifyServer */);
+
+        // Verify the service becomes lost.
+        try {
+            serviceLostFuture.get(SERVICE_DISCOVERY_TIMEOUT.toMillis(), MILLISECONDS);
+        } finally {
+            mNsdManager.stopServiceDiscovery(listener);
+        }
+        assertThrows(TimeoutException.class, () -> discoverService(mNsdManager, "_test._udp"));
+    }
+
+    private static byte[] bytes(int... byteInts) {
+        byte[] bytes = new byte[byteInts.length];
+        for (int i = 0; i < byteInts.length; ++i) {
+            bytes[i] = (byte) byteInts[i];
+        }
+        return bytes;
+    }
+}
diff --git a/thread/tests/integration/src/android/net/thread/ThreadIntegrationTest.java b/thread/tests/integration/src/android/net/thread/ThreadIntegrationTest.java
new file mode 100644
index 0000000..580a83a
--- /dev/null
+++ b/thread/tests/integration/src/android/net/thread/ThreadIntegrationTest.java
@@ -0,0 +1,201 @@
+/*
+ * 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.
+ */
+
+package android.net.thread;
+
+import static android.Manifest.permission.NETWORK_SETTINGS;
+import static android.net.thread.ThreadNetworkController.DEVICE_ROLE_DETACHED;
+import static android.net.thread.ThreadNetworkController.DEVICE_ROLE_LEADER;
+import static android.net.thread.ThreadNetworkController.DEVICE_ROLE_STOPPED;
+import static android.net.thread.ThreadNetworkManager.PERMISSION_THREAD_NETWORK_PRIVILEGED;
+import static android.net.thread.utils.IntegrationTestUtils.CALLBACK_TIMEOUT;
+import static android.net.thread.utils.IntegrationTestUtils.LEAVE_TIMEOUT;
+import static android.net.thread.utils.IntegrationTestUtils.RESTART_JOIN_TIMEOUT;
+import static android.net.thread.utils.IntegrationTestUtils.waitForStateAnyOf;
+
+import static com.android.compatibility.common.util.SystemUtil.runShellCommand;
+import static com.android.testutils.TestPermissionUtil.runAsShell;
+
+import static com.google.common.io.BaseEncoding.base16;
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.util.concurrent.MoreExecutors.directExecutor;
+
+import static org.junit.Assume.assumeNotNull;
+
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.net.thread.ThreadNetworkController.StateCallback;
+import android.net.thread.utils.OtDaemonController;
+import android.os.SystemClock;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.filters.LargeTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.net.Inet6Address;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+
+/** Tests for E2E Android Thread integration with ot-daemon, ConnectivityService, etc.. */
+@LargeTest
+@RunWith(AndroidJUnit4.class)
+public class ThreadIntegrationTest {
+    private final Context mContext = ApplicationProvider.getApplicationContext();
+    private ThreadNetworkController mController;
+    private OtDaemonController mOtCtl;
+
+    // A valid Thread Active Operational Dataset generated from OpenThread CLI "dataset init new".
+    private static final byte[] DEFAULT_DATASET_TLVS =
+            base16().decode(
+                            "0E080000000000010000000300001335060004001FFFE002"
+                                    + "08ACC214689BC40BDF0708FD64DB1225F47E0B0510F26B31"
+                                    + "53760F519A63BAFDDFFC80D2AF030F4F70656E5468726561"
+                                    + "642D643961300102D9A00410A245479C836D551B9CA557F7"
+                                    + "B9D351B40C0402A0FFF8");
+    private static final ActiveOperationalDataset DEFAULT_DATASET =
+            ActiveOperationalDataset.fromThreadTlvs(DEFAULT_DATASET_TLVS);
+
+    @Before
+    public void setUp() throws Exception {
+        final ThreadNetworkManager manager = mContext.getSystemService(ThreadNetworkManager.class);
+        if (manager != null) {
+            mController = manager.getAllThreadNetworkControllers().get(0);
+        }
+
+        // Run the tests on only devices where the Thread feature is available
+        assumeNotNull(mController);
+
+        mOtCtl = new OtDaemonController();
+        leaveAndWait(mController);
+
+        // TODO: b/323301831 - This is a workaround to avoid unnecessary delay to re-form a network
+        mOtCtl.factoryReset();
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        if (mController == null) {
+            return;
+        }
+
+        setTestUpStreamNetworkAndWait(mController, null);
+        leaveAndWait(mController);
+    }
+
+    @Test
+    public void otDaemonRestart_notJoinedAndStopped_deviceRoleIsStopped() throws Exception {
+        leaveAndWait(mController);
+
+        runShellCommand("stop ot-daemon");
+        // TODO(b/323331973): the sleep is needed to workaround the race conditions
+        SystemClock.sleep(200);
+
+        waitForStateAnyOf(mController, List.of(DEVICE_ROLE_STOPPED), CALLBACK_TIMEOUT);
+    }
+
+    @Test
+    public void otDaemonRestart_JoinedNetworkAndStopped_autoRejoined() throws Exception {
+        joinAndWait(mController, DEFAULT_DATASET);
+
+        runShellCommand("stop ot-daemon");
+
+        waitForStateAnyOf(mController, List.of(DEVICE_ROLE_DETACHED), CALLBACK_TIMEOUT);
+        waitForStateAnyOf(mController, List.of(DEVICE_ROLE_LEADER), RESTART_JOIN_TIMEOUT);
+    }
+
+    @Test
+    public void otDaemonFactoryReset_deviceRoleIsStopped() throws Exception {
+        joinAndWait(mController, DEFAULT_DATASET);
+
+        mOtCtl.factoryReset();
+
+        assertThat(getDeviceRole(mController)).isEqualTo(DEVICE_ROLE_STOPPED);
+    }
+
+    @Test
+    public void otDaemonFactoryReset_addressesRemoved() throws Exception {
+        joinAndWait(mController, DEFAULT_DATASET);
+
+        mOtCtl.factoryReset();
+        String ifconfig = runShellCommand("ifconfig thread-wpan");
+
+        assertThat(ifconfig).doesNotContain("inet6 addr");
+    }
+
+    @Test
+    public void tunInterface_joinedNetwork_otAddressesAddedToTunInterface() throws Exception {
+        joinAndWait(mController, DEFAULT_DATASET);
+
+        String ifconfig = runShellCommand("ifconfig thread-wpan");
+        List<Inet6Address> otAddresses = mOtCtl.getAddresses();
+        assertThat(otAddresses).isNotEmpty();
+        for (Inet6Address otAddress : otAddresses) {
+            assertThat(ifconfig).contains(otAddress.getHostAddress());
+        }
+    }
+
+    // TODO (b/323300829): add more tests for integration with linux platform and
+    // ConnectivityService
+
+    private static int getDeviceRole(ThreadNetworkController controller) throws Exception {
+        CompletableFuture<Integer> future = new CompletableFuture<>();
+        StateCallback callback = future::complete;
+        controller.registerStateCallback(directExecutor(), callback);
+        try {
+            return future.get(CALLBACK_TIMEOUT.toMillis(), MILLISECONDS);
+        } finally {
+            controller.unregisterStateCallback(callback);
+        }
+    }
+
+    private static void joinAndWait(
+            ThreadNetworkController controller, ActiveOperationalDataset activeDataset)
+            throws Exception {
+        runAsShell(
+                PERMISSION_THREAD_NETWORK_PRIVILEGED,
+                () -> controller.join(activeDataset, directExecutor(), result -> {}));
+        waitForStateAnyOf(controller, List.of(DEVICE_ROLE_LEADER), RESTART_JOIN_TIMEOUT);
+    }
+
+    private static void leaveAndWait(ThreadNetworkController controller) throws Exception {
+        CompletableFuture<Void> future = new CompletableFuture<>();
+        runAsShell(
+                PERMISSION_THREAD_NETWORK_PRIVILEGED,
+                () -> controller.leave(directExecutor(), future::complete));
+        future.get(LEAVE_TIMEOUT.toMillis(), MILLISECONDS);
+    }
+
+    private static void setTestUpStreamNetworkAndWait(
+            ThreadNetworkController controller, @Nullable String networkInterfaceName)
+            throws Exception {
+        CompletableFuture<Void> future = new CompletableFuture<>();
+        runAsShell(
+                PERMISSION_THREAD_NETWORK_PRIVILEGED,
+                NETWORK_SETTINGS,
+                () -> {
+                    controller.setTestNetworkAsUpstream(
+                            networkInterfaceName, directExecutor(), future::complete);
+                });
+        future.get(CALLBACK_TIMEOUT.toMillis(), MILLISECONDS);
+    }
+}
diff --git a/thread/tests/integration/src/android/net/thread/utils/FullThreadDevice.java b/thread/tests/integration/src/android/net/thread/utils/FullThreadDevice.java
new file mode 100644
index 0000000..6306a65
--- /dev/null
+++ b/thread/tests/integration/src/android/net/thread/utils/FullThreadDevice.java
@@ -0,0 +1,419 @@
+/*
+ * Copyright (C) 2023 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 android.net.thread.utils;
+
+import static android.net.thread.utils.IntegrationTestUtils.SERVICE_DISCOVERY_TIMEOUT;
+import static android.net.thread.utils.IntegrationTestUtils.waitFor;
+
+import static com.google.common.io.BaseEncoding.base16;
+
+import static org.junit.Assert.fail;
+
+import android.net.InetAddresses;
+import android.net.IpPrefix;
+import android.net.thread.ActiveOperationalDataset;
+
+import com.google.errorprone.annotations.FormatMethod;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
+import java.net.Inet6Address;
+import java.nio.charset.StandardCharsets;
+import java.time.Duration;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.TimeoutException;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * A class that launches and controls a simulation Full Thread Device (FTD).
+ *
+ * <p>This class launches an `ot-cli-ftd` process and communicates with it via command line input
+ * and output. See <a
+ * href="https://github.com/openthread/openthread/blob/main/src/cli/README.md">this page</a> for
+ * available commands.
+ */
+public final class FullThreadDevice {
+    private final Process mProcess;
+    private final BufferedReader mReader;
+    private final BufferedWriter mWriter;
+
+    private ActiveOperationalDataset mActiveOperationalDataset;
+
+    /**
+     * Constructs a {@link FullThreadDevice} for the given node ID.
+     *
+     * <p>It launches an `ot-cli-ftd` process using the given node ID. The node ID is an integer in
+     * range [1, OPENTHREAD_SIMULATION_MAX_NETWORK_SIZE]. `OPENTHREAD_SIMULATION_MAX_NETWORK_SIZE`
+     * is defined in `external/openthread/examples/platforms/simulation/platform-config.h`.
+     *
+     * @param nodeId the node ID for the simulation Full Thread Device.
+     * @throws IllegalStateException the node ID is already occupied by another simulation Thread
+     *     device.
+     */
+    public FullThreadDevice(int nodeId) {
+        try {
+            mProcess = Runtime.getRuntime().exec("/system/bin/ot-cli-ftd " + nodeId);
+        } catch (IOException e) {
+            throw new IllegalStateException("Failed to start ot-cli-ftd (id=" + nodeId + ")", e);
+        }
+        mReader = new BufferedReader(new InputStreamReader(mProcess.getInputStream()));
+        mWriter = new BufferedWriter(new OutputStreamWriter(mProcess.getOutputStream()));
+        mActiveOperationalDataset = null;
+    }
+
+    public void destroy() {
+        mProcess.destroy();
+    }
+
+    /**
+     * Returns an OMR (Off-Mesh-Routable) address on this device if any.
+     *
+     * <p>This methods goes through all unicast addresses on the device and returns the first
+     * address which is neither link-local nor mesh-local.
+     */
+    public Inet6Address getOmrAddress() {
+        List<String> addresses = executeCommand("ipaddr");
+        IpPrefix meshLocalPrefix = mActiveOperationalDataset.getMeshLocalPrefix();
+        for (String address : addresses) {
+            if (address.startsWith("fe80:")) {
+                continue;
+            }
+            Inet6Address addr = (Inet6Address) InetAddresses.parseNumericAddress(address);
+            if (!meshLocalPrefix.contains(addr)) {
+                return addr;
+            }
+        }
+        return null;
+    }
+
+    /** Returns the Mesh-local EID address on this device if any. */
+    public Inet6Address getMlEid() {
+        List<String> addresses = executeCommand("ipaddr mleid");
+        return (Inet6Address) InetAddresses.parseNumericAddress(addresses.get(0));
+    }
+
+    /**
+     * Returns the link-local address of the device.
+     *
+     * <p>This methods goes through all unicast addresses on the device and returns the address that
+     * begins with fe80.
+     */
+    public Inet6Address getLinkLocalAddress() {
+        List<String> output = executeCommand("ipaddr linklocal");
+        if (!output.isEmpty() && output.get(0).startsWith("fe80:")) {
+            return (Inet6Address) InetAddresses.parseNumericAddress(output.get(0));
+        }
+        return null;
+    }
+
+    /**
+     * Returns the mesh-local addresses of the device.
+     *
+     * <p>This methods goes through all unicast addresses on the device and returns the address that
+     * begins with mesh-local prefix.
+     */
+    public List<Inet6Address> getMeshLocalAddresses() {
+        List<String> addresses = executeCommand("ipaddr");
+        List<Inet6Address> meshLocalAddresses = new ArrayList<>();
+        IpPrefix meshLocalPrefix = mActiveOperationalDataset.getMeshLocalPrefix();
+        for (String address : addresses) {
+            Inet6Address addr = (Inet6Address) InetAddresses.parseNumericAddress(address);
+            if (meshLocalPrefix.contains(addr)) {
+                meshLocalAddresses.add(addr);
+            }
+        }
+        return meshLocalAddresses;
+    }
+
+    /**
+     * Joins the Thread network using the given {@link ActiveOperationalDataset}.
+     *
+     * @param dataset the Active Operational Dataset
+     */
+    public void joinNetwork(ActiveOperationalDataset dataset) {
+        mActiveOperationalDataset = dataset;
+        executeCommand("dataset set active " + base16().lowerCase().encode(dataset.toThreadTlvs()));
+        executeCommand("ifconfig up");
+        executeCommand("thread start");
+    }
+
+    /** Stops the Thread network radio. */
+    public void stopThreadRadio() {
+        executeCommand("thread stop");
+        executeCommand("ifconfig down");
+    }
+
+    /**
+     * Waits for the Thread device to enter the any state of the given {@link List<String>}.
+     *
+     * @param states the list of states to wait for. Valid states are "disabled", "detached",
+     *     "child", "router" and "leader".
+     * @param timeout the time to wait for the expected state before throwing
+     */
+    public void waitForStateAnyOf(List<String> states, Duration timeout) throws TimeoutException {
+        waitFor(() -> states.contains(getState()), timeout);
+    }
+
+    /**
+     * Gets the state of the Thread device.
+     *
+     * @return a string representing the state.
+     */
+    public String getState() {
+        return executeCommand("state").get(0);
+    }
+
+    /** Closes the UDP socket. */
+    public void udpClose() {
+        executeCommand("udp close");
+    }
+
+    /** Opens the UDP socket. */
+    public void udpOpen() {
+        executeCommand("udp open");
+    }
+
+    /** Opens the UDP socket and binds it to a specific address and port. */
+    public void udpBind(Inet6Address address, int port) {
+        udpClose();
+        udpOpen();
+        executeCommand("udp bind %s %d", address.getHostAddress(), port);
+    }
+
+    /** Returns the message received on the UDP socket. */
+    public String udpReceive() throws IOException {
+        Pattern pattern =
+                Pattern.compile("> (\\d+) bytes from ([\\da-f:]+) (\\d+) ([\\x00-\\x7F]+)");
+        Matcher matcher = pattern.matcher(mReader.readLine());
+        matcher.matches();
+
+        return matcher.group(4);
+    }
+
+    /** Enables the SRP client and run in autostart mode. */
+    public void autoStartSrpClient() {
+        executeCommand("srp client autostart enable");
+    }
+
+    /** Sets the hostname (e.g. "MyHost") for the SRP client. */
+    public void setSrpHostname(String hostname) {
+        executeCommand("srp client host name " + hostname);
+    }
+
+    /** Sets the host addresses for the SRP client. */
+    public void setSrpHostAddresses(List<Inet6Address> addresses) {
+        executeCommand(
+                "srp client host address "
+                        + String.join(
+                                " ",
+                                addresses.stream().map(Inet6Address::getHostAddress).toList()));
+    }
+
+    /** Removes the SRP host */
+    public void removeSrpHost() {
+        executeCommand("srp client host remove 1 1");
+    }
+
+    /**
+     * Adds an SRP service for the SRP client and wait for the registration to complete.
+     *
+     * @param serviceName the service name like "MyService"
+     * @param serviceType the service type like "_test._tcp"
+     * @param subtypes the service subtypes like "_sub1"
+     * @param port the port number in range [1, 65535]
+     * @param txtMap the map of TXT names and values
+     * @throws TimeoutException if the service isn't registered within timeout
+     */
+    public void addSrpService(
+            String serviceName,
+            String serviceType,
+            List<String> subtypes,
+            int port,
+            Map<String, byte[]> txtMap)
+            throws TimeoutException {
+        StringBuilder fullServiceType = new StringBuilder(serviceType);
+        for (String subtype : subtypes) {
+            fullServiceType.append(",").append(subtype);
+        }
+        executeCommand(
+                "srp client service add %s %s %d %d %d %s",
+                serviceName,
+                fullServiceType,
+                port,
+                0 /* priority */,
+                0 /* weight */,
+                txtMapToHexString(txtMap));
+        waitFor(() -> isSrpServiceRegistered(serviceName, serviceType), SERVICE_DISCOVERY_TIMEOUT);
+    }
+
+    /**
+     * Removes an SRP service for the SRP client.
+     *
+     * @param serviceName the service name like "MyService"
+     * @param serviceType the service type like "_test._tcp"
+     * @param notifyServer whether to notify SRP server about the removal
+     */
+    public void removeSrpService(String serviceName, String serviceType, boolean notifyServer) {
+        String verb = notifyServer ? "remove" : "clear";
+        executeCommand("srp client service %s %s %s", verb, serviceName, serviceType);
+    }
+
+    /**
+     * Updates an existing SRP service for the SRP client.
+     *
+     * <p>This is essentially a 'remove' and an 'add' on the SRP client's side.
+     *
+     * @param serviceName the service name like "MyService"
+     * @param serviceType the service type like "_test._tcp"
+     * @param subtypes the service subtypes like "_sub1"
+     * @param port the port number in range [1, 65535]
+     * @param txtMap the map of TXT names and values
+     * @throws TimeoutException if the service isn't updated within timeout
+     */
+    public void updateSrpService(
+            String serviceName,
+            String serviceType,
+            List<String> subtypes,
+            int port,
+            Map<String, byte[]> txtMap)
+            throws TimeoutException {
+        removeSrpService(serviceName, serviceType, false /* notifyServer */);
+        addSrpService(serviceName, serviceType, subtypes, port, txtMap);
+    }
+
+    /** Checks if an SRP service is registered. */
+    public boolean isSrpServiceRegistered(String serviceName, String serviceType) {
+        List<String> lines = executeCommand("srp client service");
+        for (String line : lines) {
+            if (line.contains(serviceName) && line.contains(serviceType)) {
+                return line.contains("Registered");
+            }
+        }
+        return false;
+    }
+
+    /** Checks if an SRP host is registered. */
+    public boolean isSrpHostRegistered() {
+        List<String> lines = executeCommand("srp client host");
+        for (String line : lines) {
+            return line.contains("Registered");
+        }
+        return false;
+    }
+
+    /** Runs the "factoryreset" command on the device. */
+    public void factoryReset() {
+        try {
+            mWriter.write("factoryreset\n");
+            mWriter.flush();
+            // fill the input buffer to avoid truncating next command
+            for (int i = 0; i < 1000; ++i) {
+                mWriter.write("\n");
+            }
+            mWriter.flush();
+        } catch (IOException e) {
+            throw new IllegalStateException("Failed to run factoryreset on ot-cli-ftd", e);
+        }
+    }
+
+    public void subscribeMulticastAddress(Inet6Address address) {
+        executeCommand("ipmaddr add " + address.getHostAddress());
+    }
+
+    public void ping(Inet6Address address, Inet6Address source, int size, int count) {
+        String cmd =
+                "ping"
+                        + ((source == null) ? "" : (" -I " + source.getHostAddress()))
+                        + " "
+                        + address.getHostAddress()
+                        + " "
+                        + size
+                        + " "
+                        + count;
+        executeCommand(cmd);
+    }
+
+    public void ping(Inet6Address address) {
+        ping(address, null, 100 /* size */, 1 /* count */);
+    }
+
+    @FormatMethod
+    private List<String> executeCommand(String commandFormat, Object... args) {
+        return executeCommand(String.format(commandFormat, args));
+    }
+
+    private List<String> executeCommand(String command) {
+        try {
+            mWriter.write(command + "\n");
+            mWriter.flush();
+        } catch (IOException e) {
+            throw new IllegalStateException(
+                    "Failed to write the command " + command + " to ot-cli-ftd", e);
+        }
+        try {
+            return readUntilDone();
+        } catch (IOException e) {
+            throw new IllegalStateException(
+                    "Failed to read the ot-cli-ftd output of command: " + command, e);
+        }
+    }
+
+    private List<String> readUntilDone() throws IOException {
+        ArrayList<String> result = new ArrayList<>();
+        String line;
+        while ((line = mReader.readLine()) != null) {
+            if (line.equals("Done")) {
+                break;
+            }
+            if (line.startsWith("Error")) {
+                fail("ot-cli-ftd reported an error: " + line);
+            }
+            if (!line.startsWith("> ")) {
+                result.add(line);
+            }
+        }
+        return result;
+    }
+
+    private static String txtMapToHexString(Map<String, byte[]> txtMap) {
+        if (txtMap == null) {
+            return "";
+        }
+        StringBuilder sb = new StringBuilder();
+        for (Map.Entry<String, byte[]> entry : txtMap.entrySet()) {
+            int length = entry.getKey().length() + entry.getValue().length + 1;
+            sb.append(String.format("%02x", length));
+            sb.append(toHexString(entry.getKey()));
+            sb.append(toHexString("="));
+            sb.append(toHexString(entry.getValue()));
+        }
+        return sb.toString();
+    }
+
+    private static String toHexString(String s) {
+        return toHexString(s.getBytes(StandardCharsets.UTF_8));
+    }
+
+    private static String toHexString(byte[] bytes) {
+        return base16().encode(bytes);
+    }
+}
diff --git a/thread/tests/integration/src/android/net/thread/InfraNetworkDevice.java b/thread/tests/integration/src/android/net/thread/utils/InfraNetworkDevice.java
similarity index 90%
rename from thread/tests/integration/src/android/net/thread/InfraNetworkDevice.java
rename to thread/tests/integration/src/android/net/thread/utils/InfraNetworkDevice.java
index 43a800d..72a278c 100644
--- a/thread/tests/integration/src/android/net/thread/InfraNetworkDevice.java
+++ b/thread/tests/integration/src/android/net/thread/utils/InfraNetworkDevice.java
@@ -13,11 +13,11 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package android.net.thread;
+package android.net.thread.utils;
 
-import static android.net.thread.IntegrationTestUtils.getRaPios;
-import static android.net.thread.IntegrationTestUtils.readPacketFrom;
-import static android.net.thread.IntegrationTestUtils.waitFor;
+import static android.net.thread.utils.IntegrationTestUtils.getRaPios;
+import static android.net.thread.utils.IntegrationTestUtils.pollForPacket;
+import static android.net.thread.utils.IntegrationTestUtils.waitFor;
 
 import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ND_OPTION_SLLA;
 import static com.android.net.module.util.NetworkStackConstants.IPV6_ADDR_ALL_ROUTERS_MULTICAST;
@@ -34,6 +34,7 @@
 import java.net.Inet6Address;
 import java.net.InetAddress;
 import java.nio.ByteBuffer;
+import java.time.Duration;
 import java.util.List;
 import java.util.Random;
 import java.util.concurrent.TimeoutException;
@@ -100,15 +101,15 @@
      * @param timeoutSeconds the number of seconds to wait for.
      * @throws TimeoutException when the device fails to generate a SLAAC address in given timeout.
      */
-    public void runSlaac(int timeoutSeconds) throws TimeoutException {
-        waitFor(() -> (ipv6Addr = runSlaac()) != null, timeoutSeconds, 5 /* intervalSeconds */);
+    public void runSlaac(Duration timeout) throws TimeoutException {
+        waitFor(() -> (ipv6Addr = runSlaac()) != null, timeout);
     }
 
     private Inet6Address runSlaac() {
         try {
             sendRsPacket();
 
-            final byte[] raPacket = readPacketFrom(packetReader, p -> !getRaPios(p).isEmpty());
+            final byte[] raPacket = pollForPacket(packetReader, p -> !getRaPios(p).isEmpty());
 
             final List<PrefixInformationOption> options = getRaPios(raPacket);
 
diff --git a/thread/tests/integration/src/android/net/thread/utils/IntegrationTestUtils.java b/thread/tests/integration/src/android/net/thread/utils/IntegrationTestUtils.java
new file mode 100644
index 0000000..bb2d973
--- /dev/null
+++ b/thread/tests/integration/src/android/net/thread/utils/IntegrationTestUtils.java
@@ -0,0 +1,427 @@
+/*
+ * Copyright (C) 2023 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 android.net.thread.utils;
+
+import static android.system.OsConstants.IPPROTO_ICMPV6;
+
+import static com.android.compatibility.common.util.SystemUtil.runShellCommandOrThrow;
+import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ND_OPTION_PIO;
+import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ROUTER_ADVERTISEMENT;
+import static com.android.testutils.DeviceInfoUtils.isKernelVersionAtLeast;
+
+import static com.google.common.util.concurrent.MoreExecutors.directExecutor;
+
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+
+import android.net.TestNetworkInterface;
+import android.net.nsd.NsdManager;
+import android.net.nsd.NsdServiceInfo;
+import android.net.thread.ThreadNetworkController;
+import android.os.Handler;
+import android.os.SystemClock;
+import android.os.SystemProperties;
+import android.os.VintfRuntimeInfo;
+
+import androidx.annotation.NonNull;
+
+import com.android.net.module.util.Struct;
+import com.android.net.module.util.structs.Icmpv6Header;
+import com.android.net.module.util.structs.Ipv6Header;
+import com.android.net.module.util.structs.PrefixInformationOption;
+import com.android.net.module.util.structs.RaHeader;
+import com.android.testutils.HandlerUtils;
+import com.android.testutils.TapPacketReader;
+
+import com.google.common.util.concurrent.SettableFuture;
+
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.net.DatagramPacket;
+import java.net.DatagramSocket;
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.SocketAddress;
+import java.nio.ByteBuffer;
+import java.time.Duration;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.function.Predicate;
+import java.util.function.Supplier;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/** Static utility methods relating to Thread integration tests. */
+public final class IntegrationTestUtils {
+    // The timeout of join() after restarting ot-daemon. The device needs to send 6 Link Request
+    // every 5 seconds, followed by 4 Parent Request every second. So this value needs to be 40
+    // seconds to be safe
+    public static final Duration RESTART_JOIN_TIMEOUT = Duration.ofSeconds(40);
+    public static final Duration JOIN_TIMEOUT = Duration.ofSeconds(30);
+    public static final Duration LEAVE_TIMEOUT = Duration.ofSeconds(2);
+    public static final Duration CALLBACK_TIMEOUT = Duration.ofSeconds(1);
+    public static final Duration SERVICE_DISCOVERY_TIMEOUT = Duration.ofSeconds(20);
+
+    private static final String KERNEL_VERSION_MULTICAST_ROUTING_SUPPORTED = "5.15.0";
+    private static final int KERNEL_ANDROID_VERSION_MULTICAST_ROUTING_SUPPORTED = 14;
+
+    private IntegrationTestUtils() {}
+
+    /** Returns whether the device supports simulated Thread radio. */
+    public static boolean isSimulatedThreadRadioSupported() {
+        // The integration test uses SIMULATION Thread radio so that it only supports CuttleFish.
+        return SystemProperties.get("ro.product.model").startsWith("Cuttlefish");
+    }
+
+    public static boolean isMulticastRoutingSupported() {
+        return isKernelVersionAtLeast(KERNEL_VERSION_MULTICAST_ROUTING_SUPPORTED)
+                && isKernelAndroidVersionAtLeast(
+                        KERNEL_ANDROID_VERSION_MULTICAST_ROUTING_SUPPORTED);
+    }
+
+    private static boolean isKernelAndroidVersionAtLeast(int n) {
+        final String osRelease = VintfRuntimeInfo.getOsRelease();
+        final Pattern pattern = Pattern.compile("android(\\d+)");
+        Matcher matcher = pattern.matcher(osRelease);
+
+        if (matcher.find()) {
+            int version = Integer.parseInt(matcher.group(1));
+            return (version >= n);
+        }
+        return false;
+    }
+
+    /**
+     * Waits for the given {@link Supplier} to be true until given timeout.
+     *
+     * @param condition the condition to check
+     * @param timeout the time to wait for the condition before throwing
+     * @throws TimeoutException if the condition is still not met when the timeout expires
+     */
+    public static void waitFor(Supplier<Boolean> condition, Duration timeout)
+            throws TimeoutException {
+        final long intervalMills = 1000;
+        final long timeoutMills = timeout.toMillis();
+
+        for (long i = 0; i < timeoutMills; i += intervalMills) {
+            if (condition.get()) {
+                return;
+            }
+            SystemClock.sleep(intervalMills);
+        }
+        if (condition.get()) {
+            return;
+        }
+        throw new TimeoutException("The condition failed to become true in " + timeout);
+    }
+
+    /**
+     * Creates a {@link TapPacketReader} given the {@link TestNetworkInterface} and {@link Handler}.
+     *
+     * @param testNetworkInterface the TUN interface of the test network
+     * @param handler the handler to process the packets
+     * @return the {@link TapPacketReader}
+     */
+    public static TapPacketReader newPacketReader(
+            TestNetworkInterface testNetworkInterface, Handler handler) {
+        FileDescriptor fd = testNetworkInterface.getFileDescriptor().getFileDescriptor();
+        final TapPacketReader reader =
+                new TapPacketReader(handler, fd, testNetworkInterface.getMtu());
+        handler.post(() -> reader.start());
+        HandlerUtils.waitForIdle(handler, 5000 /* timeout in milliseconds */);
+        return reader;
+    }
+
+    /**
+     * Waits for the Thread module to enter any state of the given {@code deviceRoles}.
+     *
+     * @param controller the {@link ThreadNetworkController}
+     * @param deviceRoles the desired device roles. See also {@link
+     *     ThreadNetworkController.DeviceRole}
+     * @param timeout the time to wait for the expected state before throwing
+     * @return the {@link ThreadNetworkController.DeviceRole} after waiting
+     * @throws TimeoutException if the device hasn't become any of expected roles until the timeout
+     *     expires
+     */
+    public static int waitForStateAnyOf(
+            ThreadNetworkController controller, List<Integer> deviceRoles, Duration timeout)
+            throws TimeoutException {
+        SettableFuture<Integer> future = SettableFuture.create();
+        ThreadNetworkController.StateCallback callback =
+                newRole -> {
+                    if (deviceRoles.contains(newRole)) {
+                        future.set(newRole);
+                    }
+                };
+        controller.registerStateCallback(directExecutor(), callback);
+        try {
+            return future.get(timeout.toMillis(), TimeUnit.MILLISECONDS);
+        } catch (InterruptedException | ExecutionException e) {
+            throw new TimeoutException(
+                    String.format(
+                            "The device didn't become an expected role in %s: %s",
+                            timeout, e.getMessage()));
+        } finally {
+            controller.unregisterStateCallback(callback);
+        }
+    }
+
+    /**
+     * Polls for a packet from a given {@link TapPacketReader} that satisfies the {@code filter}.
+     *
+     * @param packetReader a TUN packet reader
+     * @param filter the filter to be applied on the packet
+     * @return the first IPv6 packet that satisfies the {@code filter}. If it has waited for more
+     *     than 3000ms to read the next packet, the method will return null
+     */
+    public static byte[] pollForPacket(TapPacketReader packetReader, Predicate<byte[]> filter) {
+        byte[] packet;
+        while ((packet = packetReader.poll(3000 /* timeoutMs */, filter)) != null) {
+            return packet;
+        }
+        return null;
+    }
+
+    /** Returns {@code true} if {@code packet} is an ICMPv6 packet of given {@code type}. */
+    public static boolean isExpectedIcmpv6Packet(byte[] packet, int type) {
+        if (packet == null) {
+            return false;
+        }
+        ByteBuffer buf = ByteBuffer.wrap(packet);
+        try {
+            if (Struct.parse(Ipv6Header.class, buf).nextHeader != (byte) IPPROTO_ICMPV6) {
+                return false;
+            }
+            return Struct.parse(Icmpv6Header.class, buf).type == (short) type;
+        } catch (IllegalArgumentException ignored) {
+            // It's fine that the passed in packet is malformed because it's could be sent
+            // by anybody.
+        }
+        return false;
+    }
+
+    public static boolean isFromIpv6Source(byte[] packet, Inet6Address src) {
+        if (packet == null) {
+            return false;
+        }
+        ByteBuffer buf = ByteBuffer.wrap(packet);
+        try {
+            return Struct.parse(Ipv6Header.class, buf).srcIp.equals(src);
+        } catch (IllegalArgumentException ignored) {
+            // It's fine that the passed in packet is malformed because it's could be sent
+            // by anybody.
+        }
+        return false;
+    }
+
+    public static boolean isToIpv6Destination(byte[] packet, Inet6Address dest) {
+        if (packet == null) {
+            return false;
+        }
+        ByteBuffer buf = ByteBuffer.wrap(packet);
+        try {
+            return Struct.parse(Ipv6Header.class, buf).dstIp.equals(dest);
+        } catch (IllegalArgumentException ignored) {
+            // It's fine that the passed in packet is malformed because it's could be sent
+            // by anybody.
+        }
+        return false;
+    }
+
+    /** Returns the Prefix Information Options (PIO) extracted from an ICMPv6 RA message. */
+    public static List<PrefixInformationOption> getRaPios(byte[] raMsg) {
+        final ArrayList<PrefixInformationOption> pioList = new ArrayList<>();
+
+        if (raMsg == null) {
+            return pioList;
+        }
+
+        final ByteBuffer buf = ByteBuffer.wrap(raMsg);
+        final Ipv6Header ipv6Header = Struct.parse(Ipv6Header.class, buf);
+        if (ipv6Header.nextHeader != (byte) IPPROTO_ICMPV6) {
+            return pioList;
+        }
+
+        final Icmpv6Header icmpv6Header = Struct.parse(Icmpv6Header.class, buf);
+        if (icmpv6Header.type != (short) ICMPV6_ROUTER_ADVERTISEMENT) {
+            return pioList;
+        }
+
+        Struct.parse(RaHeader.class, buf);
+        while (buf.position() < raMsg.length) {
+            final int currentPos = buf.position();
+            final int type = Byte.toUnsignedInt(buf.get());
+            final int length = Byte.toUnsignedInt(buf.get());
+            if (type == ICMPV6_ND_OPTION_PIO) {
+                final ByteBuffer pioBuf =
+                        ByteBuffer.wrap(
+                                buf.array(),
+                                currentPos,
+                                Struct.getSize(PrefixInformationOption.class));
+                final PrefixInformationOption pio =
+                        Struct.parse(PrefixInformationOption.class, pioBuf);
+                pioList.add(pio);
+
+                // Move ByteBuffer position to the next option.
+                buf.position(currentPos + Struct.getSize(PrefixInformationOption.class));
+            } else {
+                // The length is in units of 8 octets.
+                buf.position(currentPos + (length * 8));
+            }
+        }
+        return pioList;
+    }
+
+    /**
+     * Sends a UDP message to a destination.
+     *
+     * @param dstAddress the IP address of the destination
+     * @param dstPort the port of the destination
+     * @param message the message in UDP payload
+     * @throws IOException if failed to send the message
+     */
+    public static void sendUdpMessage(InetAddress dstAddress, int dstPort, String message)
+            throws IOException {
+        SocketAddress dstSockAddr = new InetSocketAddress(dstAddress, dstPort);
+
+        try (DatagramSocket socket = new DatagramSocket()) {
+            socket.connect(dstSockAddr);
+
+            byte[] msgBytes = message.getBytes();
+            DatagramPacket packet = new DatagramPacket(msgBytes, msgBytes.length);
+
+            socket.send(packet);
+        }
+    }
+
+    public static boolean isInMulticastGroup(String interfaceName, Inet6Address address) {
+        final String cmd = "ip -6 maddr show dev " + interfaceName;
+        final String output = runShellCommandOrThrow(cmd);
+        final String addressStr = address.getHostAddress();
+        for (final String line : output.split("\\n")) {
+            if (line.contains(addressStr)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /** Return the first discovered service of {@code serviceType}. */
+    public static NsdServiceInfo discoverService(NsdManager nsdManager, String serviceType)
+            throws Exception {
+        CompletableFuture<NsdServiceInfo> serviceInfoFuture = new CompletableFuture<>();
+        NsdManager.DiscoveryListener listener =
+                new DefaultDiscoveryListener() {
+                    @Override
+                    public void onServiceFound(NsdServiceInfo serviceInfo) {
+                        serviceInfoFuture.complete(serviceInfo);
+                    }
+                };
+        nsdManager.discoverServices(serviceType, NsdManager.PROTOCOL_DNS_SD, listener);
+        try {
+            serviceInfoFuture.get(SERVICE_DISCOVERY_TIMEOUT.toMillis(), MILLISECONDS);
+        } finally {
+            nsdManager.stopServiceDiscovery(listener);
+        }
+
+        return serviceInfoFuture.get();
+    }
+
+    /**
+     * Returns the {@link NsdServiceInfo} when a service instance of {@code serviceType} gets lost.
+     */
+    public static NsdManager.DiscoveryListener discoverForServiceLost(
+            NsdManager nsdManager,
+            String serviceType,
+            CompletableFuture<NsdServiceInfo> serviceInfoFuture) {
+        NsdManager.DiscoveryListener listener =
+                new DefaultDiscoveryListener() {
+                    @Override
+                    public void onServiceLost(NsdServiceInfo serviceInfo) {
+                        serviceInfoFuture.complete(serviceInfo);
+                    }
+                };
+        nsdManager.discoverServices(serviceType, NsdManager.PROTOCOL_DNS_SD, listener);
+        return listener;
+    }
+
+    /** Resolves the service. */
+    public static NsdServiceInfo resolveService(NsdManager nsdManager, NsdServiceInfo serviceInfo)
+            throws Exception {
+        return resolveServiceUntil(nsdManager, serviceInfo, s -> true);
+    }
+
+    /** Returns the first resolved service that satisfies the {@code predicate}. */
+    public static NsdServiceInfo resolveServiceUntil(
+            NsdManager nsdManager, NsdServiceInfo serviceInfo, Predicate<NsdServiceInfo> predicate)
+            throws Exception {
+        CompletableFuture<NsdServiceInfo> resolvedServiceInfoFuture = new CompletableFuture<>();
+        NsdManager.ServiceInfoCallback callback =
+                new DefaultServiceInfoCallback() {
+                    @Override
+                    public void onServiceUpdated(@NonNull NsdServiceInfo serviceInfo) {
+                        if (predicate.test(serviceInfo)) {
+                            resolvedServiceInfoFuture.complete(serviceInfo);
+                        }
+                    }
+                };
+        nsdManager.registerServiceInfoCallback(serviceInfo, directExecutor(), callback);
+        try {
+            return resolvedServiceInfoFuture.get(
+                    SERVICE_DISCOVERY_TIMEOUT.toMillis(), MILLISECONDS);
+        } finally {
+            nsdManager.unregisterServiceInfoCallback(callback);
+        }
+    }
+
+    private static class DefaultDiscoveryListener implements NsdManager.DiscoveryListener {
+        @Override
+        public void onStartDiscoveryFailed(String serviceType, int errorCode) {}
+
+        @Override
+        public void onStopDiscoveryFailed(String serviceType, int errorCode) {}
+
+        @Override
+        public void onDiscoveryStarted(String serviceType) {}
+
+        @Override
+        public void onDiscoveryStopped(String serviceType) {}
+
+        @Override
+        public void onServiceFound(NsdServiceInfo serviceInfo) {}
+
+        @Override
+        public void onServiceLost(NsdServiceInfo serviceInfo) {}
+    }
+
+    private static class DefaultServiceInfoCallback implements NsdManager.ServiceInfoCallback {
+        @Override
+        public void onServiceInfoCallbackRegistrationFailed(int errorCode) {}
+
+        @Override
+        public void onServiceUpdated(@NonNull NsdServiceInfo serviceInfo) {}
+
+        @Override
+        public void onServiceLost() {}
+
+        @Override
+        public void onServiceInfoCallbackUnregistered() {}
+    }
+}
diff --git a/thread/tests/integration/src/android/net/thread/utils/OtDaemonController.java b/thread/tests/integration/src/android/net/thread/utils/OtDaemonController.java
new file mode 100644
index 0000000..4a06fe8
--- /dev/null
+++ b/thread/tests/integration/src/android/net/thread/utils/OtDaemonController.java
@@ -0,0 +1,68 @@
+/*
+ * 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.
+ */
+
+package android.net.thread.utils;
+
+import android.net.InetAddresses;
+import android.os.SystemClock;
+
+import com.android.compatibility.common.util.SystemUtil;
+
+import java.net.Inet6Address;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Wrapper of the "/system/bin/ot-ctl" which can be used to send CLI commands to ot-daemon to
+ * control its behavior.
+ *
+ * <p>Note that this class takes root privileged to run.
+ */
+public final class OtDaemonController {
+    private static final String OT_CTL = "/system/bin/ot-ctl";
+
+    /**
+     * Factory resets ot-daemon.
+     *
+     * <p>This will erase all persistent data written into apexdata/com.android.apex/ot-daemon and
+     * restart the ot-daemon service.
+     */
+    public void factoryReset() {
+        executeCommand("factoryreset");
+
+        // TODO(b/323164524): ot-ctl is a separate process so that the tests can't depend on the
+        // time sequence. Here needs to wait for system server to receive the ot-daemon death
+        // signal and take actions.
+        // A proper fix is to replace "ot-ctl" with "cmd thread_network ot-ctl" which is
+        // synchronized with the system server
+        SystemClock.sleep(500);
+    }
+
+    /** Returns the list of IPv6 addresses on ot-daemon. */
+    public List<Inet6Address> getAddresses() {
+        String output = executeCommand("ipaddr");
+        return Arrays.asList(output.split("\n")).stream()
+                .map(String::trim)
+                .filter(str -> !str.equals("Done"))
+                .map(addr -> InetAddresses.parseNumericAddress(addr))
+                .map(inetAddr -> (Inet6Address) inetAddr)
+                .toList();
+    }
+
+    public String executeCommand(String cmd) {
+        return SystemUtil.runShellCommand(OT_CTL + " " + cmd);
+    }
+}
diff --git a/thread/tests/unit/Android.bp b/thread/tests/unit/Android.bp
index 291475e..3365cd0 100644
--- a/thread/tests/unit/Android.bp
+++ b/thread/tests/unit/Android.bp
@@ -15,6 +15,7 @@
 //
 
 package {
+    default_team: "trendy_team_fwk_thread_network",
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
@@ -29,6 +30,7 @@
     ],
     test_suites: [
         "general-tests",
+        "mts-tethering",
     ],
     static_libs: [
         "frameworks-base-testutils",
diff --git a/thread/tests/unit/AndroidTest.xml b/thread/tests/unit/AndroidTest.xml
index 26813c1..d16e423 100644
--- a/thread/tests/unit/AndroidTest.xml
+++ b/thread/tests/unit/AndroidTest.xml
@@ -19,6 +19,18 @@
     <option name="test-tag" value="ThreadNetworkUnitTests" />
     <option name="test-suite-tag" value="apct" />
 
+    <!--
+        Only run tests if the device under test is SDK version 34 (Android 14) or above.
+    -->
+    <object type="module_controller"
+            class="com.android.tradefed.testtype.suite.module.Sdk34ModuleController" />
+
+    <!-- Run tests in MTS only if the Tethering Mainline module is installed. -->
+    <object type="module_controller"
+            class="com.android.tradefed.testtype.suite.module.MainlineTestModuleController">
+        <option name="mainline-module-package-name" value="com.google.android.tethering" />
+    </object>
+
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="test-file-name" value="ThreadNetworkUnitTests.apk" />
         <option name="check-min-sdk" value="true" />
diff --git a/thread/tests/unit/src/android/net/thread/ThreadNetworkExceptionTest.java b/thread/tests/unit/src/android/net/thread/ThreadNetworkExceptionTest.java
new file mode 100644
index 0000000..f62b437
--- /dev/null
+++ b/thread/tests/unit/src/android/net/thread/ThreadNetworkExceptionTest.java
@@ -0,0 +1,37 @@
+/*
+ * 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.
+ */
+
+package android.net.thread;
+
+import static org.junit.Assert.assertThrows;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/** Unit tests for {@link ThreadNetworkException} to cover what is not covered in CTS tests. */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public final class ThreadNetworkExceptionTest {
+    @Test
+    public void constructor_tooLargeErrorCode_throwsIllegalArgumentException() throws Exception {
+        // TODO (b/323791003): move this test case to cts/ThreadNetworkExceptionTest when mainline
+        // CTS is ready.
+        assertThrows(IllegalArgumentException.class, () -> new ThreadNetworkException(13, "13"));
+    }
+}
diff --git a/thread/tests/unit/src/com/android/server/thread/NsdPublisherTest.java b/thread/tests/unit/src/com/android/server/thread/NsdPublisherTest.java
new file mode 100644
index 0000000..54e89b1
--- /dev/null
+++ b/thread/tests/unit/src/com/android/server/thread/NsdPublisherTest.java
@@ -0,0 +1,581 @@
+/*
+ * 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.
+ */
+
+package com.android.server.thread;
+
+import static android.net.nsd.NsdManager.FAILURE_INTERNAL_ERROR;
+import static android.net.nsd.NsdManager.PROTOCOL_DNS_SD;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.net.InetAddresses;
+import android.net.nsd.NsdManager;
+import android.net.nsd.NsdServiceInfo;
+import android.os.Handler;
+import android.os.test.TestLooper;
+
+import com.android.server.thread.openthread.DnsTxtAttribute;
+import com.android.server.thread.openthread.INsdStatusReceiver;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.net.InetAddress;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.Executor;
+
+/** Unit tests for {@link NsdPublisher}. */
+public final class NsdPublisherTest {
+    @Mock private NsdManager mMockNsdManager;
+
+    @Mock private INsdStatusReceiver mRegistrationReceiver;
+    @Mock private INsdStatusReceiver mUnregistrationReceiver;
+
+    private TestLooper mTestLooper;
+    private NsdPublisher mNsdPublisher;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+    }
+
+    @Test
+    public void registerService_nsdManagerSucceeds_serviceRegistrationSucceeds() throws Exception {
+        prepareTest();
+
+        DnsTxtAttribute txt1 = makeTxtAttribute("key1", List.of(0x01, 0x02));
+        DnsTxtAttribute txt2 = makeTxtAttribute("key2", List.of(0x03));
+
+        mNsdPublisher.registerService(
+                null,
+                "MyService",
+                "_test._tcp",
+                List.of("_subtype1", "_subtype2"),
+                12345,
+                List.of(txt1, txt2),
+                mRegistrationReceiver,
+                16 /* listenerId */);
+
+        mTestLooper.dispatchAll();
+
+        ArgumentCaptor<NsdServiceInfo> actualServiceInfoCaptor =
+                ArgumentCaptor.forClass(NsdServiceInfo.class);
+        ArgumentCaptor<NsdManager.RegistrationListener> actualRegistrationListenerCaptor =
+                ArgumentCaptor.forClass(NsdManager.RegistrationListener.class);
+
+        verify(mMockNsdManager, times(1))
+                .registerService(
+                        actualServiceInfoCaptor.capture(),
+                        eq(PROTOCOL_DNS_SD),
+                        any(),
+                        actualRegistrationListenerCaptor.capture());
+
+        NsdServiceInfo actualServiceInfo = actualServiceInfoCaptor.getValue();
+        NsdManager.RegistrationListener actualRegistrationListener =
+                actualRegistrationListenerCaptor.getValue();
+
+        actualRegistrationListener.onServiceRegistered(actualServiceInfo);
+        mTestLooper.dispatchAll();
+
+        assertThat(actualServiceInfo.getServiceName()).isEqualTo("MyService");
+        assertThat(actualServiceInfo.getServiceType()).isEqualTo("_test._tcp");
+        assertThat(actualServiceInfo.getSubtypes()).isEqualTo(Set.of("_subtype1", "_subtype2"));
+        assertThat(actualServiceInfo.getPort()).isEqualTo(12345);
+        assertThat(actualServiceInfo.getAttributes().size()).isEqualTo(2);
+        assertThat(actualServiceInfo.getAttributes().get("key1"))
+                .isEqualTo(new byte[] {(byte) 0x01, (byte) 0x02});
+        assertThat(actualServiceInfo.getAttributes().get("key2"))
+                .isEqualTo(new byte[] {(byte) 0x03});
+
+        verify(mRegistrationReceiver, times(1)).onSuccess();
+    }
+
+    @Test
+    public void registerService_nsdManagerFails_serviceRegistrationFails() throws Exception {
+        prepareTest();
+
+        DnsTxtAttribute txt1 = makeTxtAttribute("key1", List.of(0x01, 0x02));
+        DnsTxtAttribute txt2 = makeTxtAttribute("key2", List.of(0x03));
+
+        mNsdPublisher.registerService(
+                null,
+                "MyService",
+                "_test._tcp",
+                List.of("_subtype1", "_subtype2"),
+                12345,
+                List.of(txt1, txt2),
+                mRegistrationReceiver,
+                16 /* listenerId */);
+
+        mTestLooper.dispatchAll();
+
+        ArgumentCaptor<NsdServiceInfo> actualServiceInfoCaptor =
+                ArgumentCaptor.forClass(NsdServiceInfo.class);
+        ArgumentCaptor<NsdManager.RegistrationListener> actualRegistrationListenerCaptor =
+                ArgumentCaptor.forClass(NsdManager.RegistrationListener.class);
+
+        verify(mMockNsdManager, times(1))
+                .registerService(
+                        actualServiceInfoCaptor.capture(),
+                        eq(PROTOCOL_DNS_SD),
+                        any(Executor.class),
+                        actualRegistrationListenerCaptor.capture());
+
+        NsdServiceInfo actualServiceInfo = actualServiceInfoCaptor.getValue();
+        NsdManager.RegistrationListener actualRegistrationListener =
+                actualRegistrationListenerCaptor.getValue();
+
+        actualRegistrationListener.onRegistrationFailed(actualServiceInfo, FAILURE_INTERNAL_ERROR);
+        mTestLooper.dispatchAll();
+
+        assertThat(actualServiceInfo.getServiceName()).isEqualTo("MyService");
+        assertThat(actualServiceInfo.getServiceType()).isEqualTo("_test._tcp");
+        assertThat(actualServiceInfo.getSubtypes()).isEqualTo(Set.of("_subtype1", "_subtype2"));
+        assertThat(actualServiceInfo.getPort()).isEqualTo(12345);
+        assertThat(actualServiceInfo.getAttributes().size()).isEqualTo(2);
+        assertThat(actualServiceInfo.getAttributes().get("key1"))
+                .isEqualTo(new byte[] {(byte) 0x01, (byte) 0x02});
+        assertThat(actualServiceInfo.getAttributes().get("key2"))
+                .isEqualTo(new byte[] {(byte) 0x03});
+
+        verify(mRegistrationReceiver, times(1)).onError(FAILURE_INTERNAL_ERROR);
+    }
+
+    @Test
+    public void registerService_nsdManagerThrows_serviceRegistrationFails() throws Exception {
+        prepareTest();
+
+        DnsTxtAttribute txt1 = makeTxtAttribute("key1", List.of(0x01, 0x02));
+        DnsTxtAttribute txt2 = makeTxtAttribute("key2", List.of(0x03));
+
+        doThrow(new IllegalArgumentException("NsdManager fails"))
+                .when(mMockNsdManager)
+                .registerService(any(), anyInt(), any(Executor.class), any());
+
+        mNsdPublisher.registerService(
+                null,
+                "MyService",
+                "_test._tcp",
+                List.of("_subtype1", "_subtype2"),
+                12345,
+                List.of(txt1, txt2),
+                mRegistrationReceiver,
+                16 /* listenerId */);
+        mTestLooper.dispatchAll();
+
+        verify(mRegistrationReceiver, times(1)).onError(FAILURE_INTERNAL_ERROR);
+    }
+
+    @Test
+    public void unregisterService_nsdManagerSucceeds_serviceUnregistrationSucceeds()
+            throws Exception {
+        prepareTest();
+
+        DnsTxtAttribute txt1 = makeTxtAttribute("key1", List.of(0x01, 0x02));
+        DnsTxtAttribute txt2 = makeTxtAttribute("key2", List.of(0x03));
+
+        mNsdPublisher.registerService(
+                null,
+                "MyService",
+                "_test._tcp",
+                List.of("_subtype1", "_subtype2"),
+                12345,
+                List.of(txt1, txt2),
+                mRegistrationReceiver,
+                16 /* listenerId */);
+
+        mTestLooper.dispatchAll();
+
+        ArgumentCaptor<NsdServiceInfo> actualServiceInfoCaptor =
+                ArgumentCaptor.forClass(NsdServiceInfo.class);
+        ArgumentCaptor<NsdManager.RegistrationListener> actualRegistrationListenerCaptor =
+                ArgumentCaptor.forClass(NsdManager.RegistrationListener.class);
+
+        verify(mMockNsdManager, times(1))
+                .registerService(
+                        actualServiceInfoCaptor.capture(),
+                        eq(PROTOCOL_DNS_SD),
+                        any(Executor.class),
+                        actualRegistrationListenerCaptor.capture());
+
+        NsdServiceInfo actualServiceInfo = actualServiceInfoCaptor.getValue();
+        NsdManager.RegistrationListener actualRegistrationListener =
+                actualRegistrationListenerCaptor.getValue();
+
+        actualRegistrationListener.onServiceRegistered(actualServiceInfo);
+        mNsdPublisher.unregister(mUnregistrationReceiver, 16 /* listenerId */);
+        mTestLooper.dispatchAll();
+        verify(mMockNsdManager, times(1)).unregisterService(actualRegistrationListener);
+
+        actualRegistrationListener.onServiceUnregistered(actualServiceInfo);
+        mTestLooper.dispatchAll();
+        verify(mUnregistrationReceiver, times(1)).onSuccess();
+    }
+
+    @Test
+    public void unregisterService_nsdManagerFails_serviceUnregistrationFails() throws Exception {
+        prepareTest();
+
+        DnsTxtAttribute txt1 = makeTxtAttribute("key1", List.of(0x01, 0x02));
+        DnsTxtAttribute txt2 = makeTxtAttribute("key2", List.of(0x03));
+
+        mNsdPublisher.registerService(
+                null,
+                "MyService",
+                "_test._tcp",
+                List.of("_subtype1", "_subtype2"),
+                12345,
+                List.of(txt1, txt2),
+                mRegistrationReceiver,
+                16 /* listenerId */);
+
+        mTestLooper.dispatchAll();
+
+        ArgumentCaptor<NsdServiceInfo> actualServiceInfoCaptor =
+                ArgumentCaptor.forClass(NsdServiceInfo.class);
+        ArgumentCaptor<NsdManager.RegistrationListener> actualRegistrationListenerCaptor =
+                ArgumentCaptor.forClass(NsdManager.RegistrationListener.class);
+
+        verify(mMockNsdManager, times(1))
+                .registerService(
+                        actualServiceInfoCaptor.capture(),
+                        eq(PROTOCOL_DNS_SD),
+                        any(Executor.class),
+                        actualRegistrationListenerCaptor.capture());
+
+        NsdServiceInfo actualServiceInfo = actualServiceInfoCaptor.getValue();
+        NsdManager.RegistrationListener actualRegistrationListener =
+                actualRegistrationListenerCaptor.getValue();
+
+        actualRegistrationListener.onServiceRegistered(actualServiceInfo);
+        mNsdPublisher.unregister(mUnregistrationReceiver, 16 /* listenerId */);
+        mTestLooper.dispatchAll();
+        verify(mMockNsdManager, times(1)).unregisterService(actualRegistrationListener);
+
+        actualRegistrationListener.onUnregistrationFailed(
+                actualServiceInfo, FAILURE_INTERNAL_ERROR);
+        mTestLooper.dispatchAll();
+        verify(mUnregistrationReceiver, times(1)).onError(0);
+    }
+
+    @Test
+    public void registerHost_nsdManagerSucceeds_serviceRegistrationSucceeds() throws Exception {
+        prepareTest();
+
+        mNsdPublisher.registerHost(
+                "MyHost",
+                List.of("2001:db8::1", "2001:db8::2", "2001:db8::3"),
+                mRegistrationReceiver,
+                16 /* listenerId */);
+
+        mTestLooper.dispatchAll();
+
+        ArgumentCaptor<NsdServiceInfo> actualServiceInfoCaptor =
+                ArgumentCaptor.forClass(NsdServiceInfo.class);
+        ArgumentCaptor<NsdManager.RegistrationListener> actualRegistrationListenerCaptor =
+                ArgumentCaptor.forClass(NsdManager.RegistrationListener.class);
+
+        verify(mMockNsdManager, times(1))
+                .registerService(
+                        actualServiceInfoCaptor.capture(),
+                        eq(PROTOCOL_DNS_SD),
+                        any(),
+                        actualRegistrationListenerCaptor.capture());
+
+        NsdServiceInfo actualServiceInfo = actualServiceInfoCaptor.getValue();
+        NsdManager.RegistrationListener actualRegistrationListener =
+                actualRegistrationListenerCaptor.getValue();
+
+        actualRegistrationListener.onServiceRegistered(actualServiceInfo);
+        mTestLooper.dispatchAll();
+
+        assertThat(actualServiceInfo.getServiceName()).isNull();
+        assertThat(actualServiceInfo.getServiceType()).isNull();
+        assertThat(actualServiceInfo.getSubtypes()).isEmpty();
+        assertThat(actualServiceInfo.getPort()).isEqualTo(0);
+        assertThat(actualServiceInfo.getAttributes()).isEmpty();
+        assertThat(actualServiceInfo.getHostname()).isEqualTo("MyHost");
+        assertThat(actualServiceInfo.getHostAddresses())
+                .isEqualTo(makeAddresses("2001:db8::1", "2001:db8::2", "2001:db8::3"));
+
+        verify(mRegistrationReceiver, times(1)).onSuccess();
+    }
+
+    @Test
+    public void registerHost_nsdManagerFails_serviceRegistrationFails() throws Exception {
+        prepareTest();
+
+        mNsdPublisher.registerHost(
+                "MyHost",
+                List.of("2001:db8::1", "2001:db8::2", "2001:db8::3"),
+                mRegistrationReceiver,
+                16 /* listenerId */);
+
+        mTestLooper.dispatchAll();
+
+        ArgumentCaptor<NsdServiceInfo> actualServiceInfoCaptor =
+                ArgumentCaptor.forClass(NsdServiceInfo.class);
+        ArgumentCaptor<NsdManager.RegistrationListener> actualRegistrationListenerCaptor =
+                ArgumentCaptor.forClass(NsdManager.RegistrationListener.class);
+
+        verify(mMockNsdManager, times(1))
+                .registerService(
+                        actualServiceInfoCaptor.capture(),
+                        eq(PROTOCOL_DNS_SD),
+                        any(),
+                        actualRegistrationListenerCaptor.capture());
+        mTestLooper.dispatchAll();
+
+        NsdServiceInfo actualServiceInfo = actualServiceInfoCaptor.getValue();
+        NsdManager.RegistrationListener actualRegistrationListener =
+                actualRegistrationListenerCaptor.getValue();
+
+        actualRegistrationListener.onRegistrationFailed(actualServiceInfo, FAILURE_INTERNAL_ERROR);
+        mTestLooper.dispatchAll();
+
+        assertThat(actualServiceInfo.getServiceName()).isNull();
+        assertThat(actualServiceInfo.getServiceType()).isNull();
+        assertThat(actualServiceInfo.getSubtypes()).isEmpty();
+        assertThat(actualServiceInfo.getPort()).isEqualTo(0);
+        assertThat(actualServiceInfo.getAttributes()).isEmpty();
+        assertThat(actualServiceInfo.getHostname()).isEqualTo("MyHost");
+        assertThat(actualServiceInfo.getHostAddresses())
+                .isEqualTo(makeAddresses("2001:db8::1", "2001:db8::2", "2001:db8::3"));
+
+        verify(mRegistrationReceiver, times(1)).onError(FAILURE_INTERNAL_ERROR);
+    }
+
+    @Test
+    public void registerHost_nsdManagerThrows_serviceRegistrationFails() throws Exception {
+        prepareTest();
+
+        doThrow(new IllegalArgumentException("NsdManager fails"))
+                .when(mMockNsdManager)
+                .registerService(any(), anyInt(), any(Executor.class), any());
+
+        mNsdPublisher.registerHost(
+                "MyHost",
+                List.of("2001:db8::1", "2001:db8::2", "2001:db8::3"),
+                mRegistrationReceiver,
+                16 /* listenerId */);
+
+        mTestLooper.dispatchAll();
+
+        verify(mRegistrationReceiver, times(1)).onError(FAILURE_INTERNAL_ERROR);
+    }
+
+    @Test
+    public void unregisterHost_nsdManagerSucceeds_serviceUnregistrationSucceeds() throws Exception {
+        prepareTest();
+
+        mNsdPublisher.registerHost(
+                "MyHost",
+                List.of("2001:db8::1", "2001:db8::2", "2001:db8::3"),
+                mRegistrationReceiver,
+                16 /* listenerId */);
+
+        mTestLooper.dispatchAll();
+
+        ArgumentCaptor<NsdServiceInfo> actualServiceInfoCaptor =
+                ArgumentCaptor.forClass(NsdServiceInfo.class);
+        ArgumentCaptor<NsdManager.RegistrationListener> actualRegistrationListenerCaptor =
+                ArgumentCaptor.forClass(NsdManager.RegistrationListener.class);
+
+        verify(mMockNsdManager, times(1))
+                .registerService(
+                        actualServiceInfoCaptor.capture(),
+                        eq(PROTOCOL_DNS_SD),
+                        any(Executor.class),
+                        actualRegistrationListenerCaptor.capture());
+
+        NsdServiceInfo actualServiceInfo = actualServiceInfoCaptor.getValue();
+        NsdManager.RegistrationListener actualRegistrationListener =
+                actualRegistrationListenerCaptor.getValue();
+
+        actualRegistrationListener.onServiceRegistered(actualServiceInfo);
+        mNsdPublisher.unregister(mUnregistrationReceiver, 16 /* listenerId */);
+        mTestLooper.dispatchAll();
+        verify(mMockNsdManager, times(1)).unregisterService(actualRegistrationListener);
+
+        actualRegistrationListener.onServiceUnregistered(actualServiceInfo);
+        mTestLooper.dispatchAll();
+        verify(mUnregistrationReceiver, times(1)).onSuccess();
+    }
+
+    @Test
+    public void unregisterHost_nsdManagerFails_serviceUnregistrationFails() throws Exception {
+        prepareTest();
+
+        mNsdPublisher.registerHost(
+                "MyHost",
+                List.of("2001:db8::1", "2001:db8::2", "2001:db8::3"),
+                mRegistrationReceiver,
+                16 /* listenerId */);
+
+        mTestLooper.dispatchAll();
+
+        ArgumentCaptor<NsdServiceInfo> actualServiceInfoCaptor =
+                ArgumentCaptor.forClass(NsdServiceInfo.class);
+        ArgumentCaptor<NsdManager.RegistrationListener> actualRegistrationListenerCaptor =
+                ArgumentCaptor.forClass(NsdManager.RegistrationListener.class);
+
+        verify(mMockNsdManager, times(1))
+                .registerService(
+                        actualServiceInfoCaptor.capture(),
+                        eq(PROTOCOL_DNS_SD),
+                        any(Executor.class),
+                        actualRegistrationListenerCaptor.capture());
+
+        NsdServiceInfo actualServiceInfo = actualServiceInfoCaptor.getValue();
+        NsdManager.RegistrationListener actualRegistrationListener =
+                actualRegistrationListenerCaptor.getValue();
+
+        actualRegistrationListener.onServiceRegistered(actualServiceInfo);
+        mNsdPublisher.unregister(mUnregistrationReceiver, 16 /* listenerId */);
+        mTestLooper.dispatchAll();
+        verify(mMockNsdManager, times(1)).unregisterService(actualRegistrationListener);
+
+        actualRegistrationListener.onUnregistrationFailed(
+                actualServiceInfo, FAILURE_INTERNAL_ERROR);
+        mTestLooper.dispatchAll();
+        verify(mUnregistrationReceiver, times(1)).onError(0);
+    }
+
+    @Test
+    public void onOtDaemonDied_unregisterAll() {
+        prepareTest();
+
+        DnsTxtAttribute txt1 = makeTxtAttribute("key1", List.of(0x01, 0x02));
+        DnsTxtAttribute txt2 = makeTxtAttribute("key2", List.of(0x03));
+
+        ArgumentCaptor<NsdServiceInfo> actualServiceInfoCaptor =
+                ArgumentCaptor.forClass(NsdServiceInfo.class);
+        ArgumentCaptor<NsdManager.RegistrationListener> actualRegistrationListenerCaptor =
+                ArgumentCaptor.forClass(NsdManager.RegistrationListener.class);
+
+        mNsdPublisher.registerService(
+                null,
+                "MyService",
+                "_test._tcp",
+                List.of("_subtype1", "_subtype2"),
+                12345,
+                List.of(txt1, txt2),
+                mRegistrationReceiver,
+                16 /* listenerId */);
+        mTestLooper.dispatchAll();
+
+        verify(mMockNsdManager, times(1))
+                .registerService(
+                        actualServiceInfoCaptor.capture(),
+                        eq(PROTOCOL_DNS_SD),
+                        any(Executor.class),
+                        actualRegistrationListenerCaptor.capture());
+        NsdManager.RegistrationListener actualListener1 =
+                actualRegistrationListenerCaptor.getValue();
+        actualListener1.onServiceRegistered(actualServiceInfoCaptor.getValue());
+
+        mNsdPublisher.registerService(
+                null,
+                "MyService2",
+                "_test._udp",
+                Collections.emptyList(),
+                11111,
+                Collections.emptyList(),
+                mRegistrationReceiver,
+                17 /* listenerId */);
+
+        mTestLooper.dispatchAll();
+
+        verify(mMockNsdManager, times(2))
+                .registerService(
+                        actualServiceInfoCaptor.capture(),
+                        eq(PROTOCOL_DNS_SD),
+                        any(Executor.class),
+                        actualRegistrationListenerCaptor.capture());
+        NsdManager.RegistrationListener actualListener2 =
+                actualRegistrationListenerCaptor.getAllValues().get(1);
+        actualListener2.onServiceRegistered(actualServiceInfoCaptor.getValue());
+
+        mNsdPublisher.registerHost(
+                "Myhost",
+                List.of("2001:db8::1", "2001:db8::2", "2001:db8::3"),
+                mRegistrationReceiver,
+                18 /* listenerId */);
+
+        mTestLooper.dispatchAll();
+
+        verify(mMockNsdManager, times(3))
+                .registerService(
+                        actualServiceInfoCaptor.capture(),
+                        eq(PROTOCOL_DNS_SD),
+                        any(Executor.class),
+                        actualRegistrationListenerCaptor.capture());
+        NsdManager.RegistrationListener actualListener3 =
+                actualRegistrationListenerCaptor.getAllValues().get(1);
+        actualListener3.onServiceRegistered(actualServiceInfoCaptor.getValue());
+
+        mNsdPublisher.onOtDaemonDied();
+        mTestLooper.dispatchAll();
+
+        verify(mMockNsdManager, times(1)).unregisterService(actualListener1);
+        verify(mMockNsdManager, times(1)).unregisterService(actualListener2);
+        verify(mMockNsdManager, times(1)).unregisterService(actualListener3);
+    }
+
+    private static DnsTxtAttribute makeTxtAttribute(String name, List<Integer> value) {
+        DnsTxtAttribute txtAttribute = new DnsTxtAttribute();
+
+        txtAttribute.name = name;
+        txtAttribute.value = new byte[value.size()];
+
+        for (int i = 0; i < value.size(); ++i) {
+            txtAttribute.value[i] = value.get(i).byteValue();
+        }
+
+        return txtAttribute;
+    }
+
+    private static List<InetAddress> makeAddresses(String... addressStrings) {
+        List<InetAddress> addresses = new ArrayList<>();
+
+        for (String addressString : addressStrings) {
+            addresses.add(InetAddresses.parseNumericAddress(addressString));
+        }
+        return addresses;
+    }
+
+    // @Before and @Test run in different threads. NsdPublisher requires the jobs are run on the
+    // thread looper, so TestLooper needs to be created inside each test case to install the
+    // correct looper.
+    private void prepareTest() {
+        mTestLooper = new TestLooper();
+        Handler handler = new Handler(mTestLooper.getLooper());
+        mNsdPublisher = new NsdPublisher(mMockNsdManager, handler);
+    }
+}
diff --git a/thread/tests/unit/src/com/android/server/thread/ThreadNetworkControllerServiceTest.java b/thread/tests/unit/src/com/android/server/thread/ThreadNetworkControllerServiceTest.java
index 44a8ab7..60a5f2b 100644
--- a/thread/tests/unit/src/com/android/server/thread/ThreadNetworkControllerServiceTest.java
+++ b/thread/tests/unit/src/com/android/server/thread/ThreadNetworkControllerServiceTest.java
@@ -16,31 +16,49 @@
 
 package com.android.server.thread;
 
+import static android.net.thread.ActiveOperationalDataset.CHANNEL_PAGE_24_GHZ;
+import static android.net.thread.ThreadNetworkController.STATE_DISABLED;
+import static android.net.thread.ThreadNetworkController.STATE_ENABLED;
+import static android.net.thread.ThreadNetworkException.ERROR_FAILED_PRECONDITION;
 import static android.net.thread.ThreadNetworkException.ERROR_INTERNAL_ERROR;
+import static android.net.thread.ThreadNetworkManager.DISALLOW_THREAD_NETWORK;
 import static android.net.thread.ThreadNetworkManager.PERMISSION_THREAD_NETWORK_PRIVILEGED;
 
-import static com.android.testutils.TestPermissionUtil.runAsShell;
+import static com.android.server.thread.openthread.IOtDaemon.ErrorCode.OT_ERROR_INVALID_STATE;
 
 import static com.google.common.io.BaseEncoding.base16;
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.junit.Assert.assertThrows;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.content.BroadcastReceiver;
 import android.content.Context;
+import android.content.Intent;
 import android.net.ConnectivityManager;
 import android.net.NetworkAgent;
 import android.net.NetworkProvider;
 import android.net.thread.ActiveOperationalDataset;
+import android.net.thread.IActiveOperationalDatasetReceiver;
 import android.net.thread.IOperationReceiver;
+import android.net.thread.ThreadNetworkException;
 import android.os.Handler;
+import android.os.IBinder;
 import android.os.ParcelFileDescriptor;
 import android.os.RemoteException;
+import android.os.UserManager;
 import android.os.test.TestLooper;
 
 import androidx.test.core.app.ApplicationProvider;
@@ -52,9 +70,15 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.atomic.AtomicReference;
+
 /** Unit tests for {@link ThreadNetworkControllerService}. */
 @SmallTest
 @RunWith(AndroidJUnit4.class)
@@ -79,45 +103,66 @@
                                     + "B9D351B40C0402A0FFF8");
     private static final ActiveOperationalDataset DEFAULT_ACTIVE_DATASET =
             ActiveOperationalDataset.fromThreadTlvs(DEFAULT_ACTIVE_DATASET_TLVS);
+    private static final String DEFAULT_NETWORK_NAME = "thread-wpan0";
+    private static final int OT_ERROR_NONE = 0;
+    private static final int DEFAULT_SUPPORTED_CHANNEL_MASK = 0x07FFF800; // from channel 11 to 26
+    private static final int DEFAULT_PREFERRED_CHANNEL_MASK = 0x00000800; // channel 11
+    private static final int DEFAULT_SELECTED_CHANNEL = 11;
+    private static final byte[] DEFAULT_SUPPORTED_CHANNEL_MASK_ARRAY = base16().decode("001FFFE0");
 
     @Mock private ConnectivityManager mMockConnectivityManager;
     @Mock private NetworkAgent mMockNetworkAgent;
     @Mock private TunInterfaceController mMockTunIfController;
     @Mock private ParcelFileDescriptor mMockTunFd;
     @Mock private InfraInterfaceController mMockInfraIfController;
+    @Mock private ThreadPersistentSettings mMockPersistentSettings;
+    @Mock private NsdPublisher mMockNsdPublisher;
+    @Mock private UserManager mMockUserManager;
+    @Mock private IBinder mIBinder;
     private Context mContext;
     private TestLooper mTestLooper;
     private FakeOtDaemon mFakeOtDaemon;
     private ThreadNetworkControllerService mService;
+    @Captor private ArgumentCaptor<ActiveOperationalDataset> mActiveDatasetCaptor;
 
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
 
-        mContext = ApplicationProvider.getApplicationContext();
+        mContext = spy(ApplicationProvider.getApplicationContext());
+        doNothing()
+                .when(mContext)
+                .enforceCallingOrSelfPermission(
+                        eq(PERMISSION_THREAD_NETWORK_PRIVILEGED), anyString());
+
         mTestLooper = new TestLooper();
         final Handler handler = new Handler(mTestLooper.getLooper());
         NetworkProvider networkProvider =
                 new NetworkProvider(mContext, mTestLooper.getLooper(), "ThreadNetworkProvider");
 
         mFakeOtDaemon = new FakeOtDaemon(handler);
-
         when(mMockTunIfController.getTunFd()).thenReturn(mMockTunFd);
 
+        when(mMockPersistentSettings.get(any())).thenReturn(true);
+        when(mMockUserManager.hasUserRestriction(eq(DISALLOW_THREAD_NETWORK))).thenReturn(false);
+
         mService =
                 new ThreadNetworkControllerService(
-                        ApplicationProvider.getApplicationContext(),
+                        mContext,
                         handler,
                         networkProvider,
                         () -> mFakeOtDaemon,
                         mMockConnectivityManager,
                         mMockTunIfController,
-                        mMockInfraIfController);
+                        mMockInfraIfController,
+                        mMockPersistentSettings,
+                        mMockNsdPublisher,
+                        mMockUserManager);
         mService.setTestNetworkAgent(mMockNetworkAgent);
     }
 
     @Test
-    public void initialize_tunInterfaceSetToOtDaemon() throws Exception {
+    public void initialize_tunInterfaceAndNsdPublisherSetToOtDaemon() throws Exception {
         when(mMockTunIfController.getTunFd()).thenReturn(mMockTunFd);
 
         mService.initialize();
@@ -125,6 +170,7 @@
 
         verify(mMockTunIfController, times(1)).createTunInterface();
         assertThat(mFakeOtDaemon.getTunFd()).isEqualTo(mMockTunFd);
+        assertThat(mFakeOtDaemon.getNsdPublisher()).isEqualTo(mMockNsdPublisher);
     }
 
     @Test
@@ -133,9 +179,7 @@
         final IOperationReceiver mockReceiver = mock(IOperationReceiver.class);
         mFakeOtDaemon.setJoinException(new RemoteException("ot-daemon join() throws"));
 
-        runAsShell(
-                PERMISSION_THREAD_NETWORK_PRIVILEGED,
-                () -> mService.join(DEFAULT_ACTIVE_DATASET, mockReceiver));
+        mService.join(DEFAULT_ACTIVE_DATASET, mockReceiver);
         mTestLooper.dispatchAll();
 
         verify(mockReceiver, never()).onSuccess();
@@ -147,9 +191,7 @@
         mService.initialize();
         final IOperationReceiver mockReceiver = mock(IOperationReceiver.class);
 
-        runAsShell(
-                PERMISSION_THREAD_NETWORK_PRIVILEGED,
-                () -> mService.join(DEFAULT_ACTIVE_DATASET, mockReceiver));
+        mService.join(DEFAULT_ACTIVE_DATASET, mockReceiver);
         // Here needs to call Testlooper#dispatchAll twices because TestLooper#moveTimeForward
         // operates on only currently enqueued messages but the delayed message is posted from
         // another Handler task.
@@ -160,4 +202,134 @@
         verify(mockReceiver, times(1)).onSuccess();
         verify(mMockNetworkAgent, times(1)).register();
     }
+
+    @Test
+    public void userRestriction_initWithUserRestricted_threadIsDisabled() {
+        when(mMockUserManager.hasUserRestriction(eq(DISALLOW_THREAD_NETWORK))).thenReturn(true);
+
+        mService.initialize();
+        mTestLooper.dispatchAll();
+
+        assertThat(mFakeOtDaemon.getEnabledState()).isEqualTo(STATE_DISABLED);
+    }
+
+    @Test
+    public void userRestriction_initWithUserNotRestricted_threadIsEnabled() {
+        when(mMockUserManager.hasUserRestriction(eq(DISALLOW_THREAD_NETWORK))).thenReturn(false);
+
+        mService.initialize();
+        mTestLooper.dispatchAll();
+
+        assertThat(mFakeOtDaemon.getEnabledState()).isEqualTo(STATE_ENABLED);
+    }
+
+    @Test
+    public void userRestriction_userBecomesRestricted_stateIsDisabledButNotPersisted() {
+        AtomicReference<BroadcastReceiver> receiverRef = new AtomicReference<>();
+        when(mMockUserManager.hasUserRestriction(eq(DISALLOW_THREAD_NETWORK))).thenReturn(false);
+        doAnswer(
+                        invocation -> {
+                            receiverRef.set((BroadcastReceiver) invocation.getArguments()[0]);
+                            return null;
+                        })
+                .when(mContext)
+                .registerReceiver(any(BroadcastReceiver.class), any(), any(), any());
+        mService.initialize();
+        mTestLooper.dispatchAll();
+
+        when(mMockUserManager.hasUserRestriction(eq(DISALLOW_THREAD_NETWORK))).thenReturn(true);
+        receiverRef.get().onReceive(mContext, new Intent());
+        mTestLooper.dispatchAll();
+
+        assertThat(mFakeOtDaemon.getEnabledState()).isEqualTo(STATE_DISABLED);
+        verify(mMockPersistentSettings, never())
+                .put(eq(ThreadPersistentSettings.THREAD_ENABLED.key), eq(false));
+    }
+
+    @Test
+    public void userRestriction_userBecomesNotRestricted_stateIsEnabledButNotPersisted() {
+        AtomicReference<BroadcastReceiver> receiverRef = new AtomicReference<>();
+        when(mMockUserManager.hasUserRestriction(eq(DISALLOW_THREAD_NETWORK))).thenReturn(true);
+        doAnswer(
+                        invocation -> {
+                            receiverRef.set((BroadcastReceiver) invocation.getArguments()[0]);
+                            return null;
+                        })
+                .when(mContext)
+                .registerReceiver(any(BroadcastReceiver.class), any(), any(), any());
+        mService.initialize();
+        mTestLooper.dispatchAll();
+
+        when(mMockUserManager.hasUserRestriction(eq(DISALLOW_THREAD_NETWORK))).thenReturn(false);
+        receiverRef.get().onReceive(mContext, new Intent());
+        mTestLooper.dispatchAll();
+
+        assertThat(mFakeOtDaemon.getEnabledState()).isEqualTo(STATE_ENABLED);
+        verify(mMockPersistentSettings, never())
+                .put(eq(ThreadPersistentSettings.THREAD_ENABLED.key), eq(true));
+    }
+
+    @Test
+    public void userRestriction_setEnabledWhenUserRestricted_failedPreconditionError() {
+        when(mMockUserManager.hasUserRestriction(eq(DISALLOW_THREAD_NETWORK))).thenReturn(true);
+        mService.initialize();
+
+        CompletableFuture<Void> setEnabledFuture = new CompletableFuture<>();
+        mService.setEnabled(true, newOperationReceiver(setEnabledFuture));
+        mTestLooper.dispatchAll();
+
+        var thrown = assertThrows(ExecutionException.class, () -> setEnabledFuture.get());
+        ThreadNetworkException failure = (ThreadNetworkException) thrown.getCause();
+        assertThat(failure.getErrorCode()).isEqualTo(ERROR_FAILED_PRECONDITION);
+    }
+
+    private static IOperationReceiver newOperationReceiver(CompletableFuture<Void> future) {
+        return new IOperationReceiver.Stub() {
+            @Override
+            public void onSuccess() {
+                future.complete(null);
+            }
+
+            @Override
+            public void onError(int errorCode, String errorMessage) {
+                future.completeExceptionally(new ThreadNetworkException(errorCode, errorMessage));
+            }
+        };
+    }
+
+    @Test
+    public void createRandomizedDataset_succeed_activeDatasetCreated() throws Exception {
+        final IActiveOperationalDatasetReceiver mockReceiver =
+                mock(IActiveOperationalDatasetReceiver.class);
+        mFakeOtDaemon.setChannelMasks(
+                DEFAULT_SUPPORTED_CHANNEL_MASK, DEFAULT_PREFERRED_CHANNEL_MASK);
+        mFakeOtDaemon.setChannelMasksReceiverOtError(OT_ERROR_NONE);
+
+        mService.createRandomizedDataset(DEFAULT_NETWORK_NAME, mockReceiver);
+        mTestLooper.dispatchAll();
+
+        verify(mockReceiver, never()).onError(anyInt(), anyString());
+        verify(mockReceiver, times(1)).onSuccess(mActiveDatasetCaptor.capture());
+        ActiveOperationalDataset activeDataset = mActiveDatasetCaptor.getValue();
+        assertThat(activeDataset.getNetworkName()).isEqualTo(DEFAULT_NETWORK_NAME);
+        assertThat(activeDataset.getChannelMask().size()).isEqualTo(1);
+        assertThat(activeDataset.getChannelMask().get(CHANNEL_PAGE_24_GHZ))
+                .isEqualTo(DEFAULT_SUPPORTED_CHANNEL_MASK_ARRAY);
+        assertThat(activeDataset.getChannel()).isEqualTo(DEFAULT_SELECTED_CHANNEL);
+    }
+
+    @Test
+    public void createRandomizedDataset_otDaemonRemoteFailure_returnsPreconditionError()
+            throws Exception {
+        final IActiveOperationalDatasetReceiver mockReceiver =
+                mock(IActiveOperationalDatasetReceiver.class);
+        mFakeOtDaemon.setChannelMasksReceiverOtError(OT_ERROR_INVALID_STATE);
+        when(mockReceiver.asBinder()).thenReturn(mIBinder);
+
+        mService.createRandomizedDataset(DEFAULT_NETWORK_NAME, mockReceiver);
+        mTestLooper.dispatchAll();
+
+        verify(mockReceiver, never()).onSuccess(any(ActiveOperationalDataset.class));
+        verify(mockReceiver, times(1)).onError(eq(ERROR_INTERNAL_ERROR), anyString());
+    }
 }
diff --git a/thread/tests/unit/src/com/android/server/thread/ThreadNetworkCountryCodeTest.java b/thread/tests/unit/src/com/android/server/thread/ThreadNetworkCountryCodeTest.java
index 17cdd01..5ca6511 100644
--- a/thread/tests/unit/src/com/android/server/thread/ThreadNetworkCountryCodeTest.java
+++ b/thread/tests/unit/src/com/android/server/thread/ThreadNetworkCountryCodeTest.java
@@ -22,6 +22,7 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.junit.Assert.assertThrows;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.anyDouble;
@@ -55,6 +56,7 @@
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 
+import androidx.annotation.Nullable;
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
@@ -70,6 +72,9 @@
 import org.mockito.MockitoAnnotations;
 import org.mockito.stubbing.Answer;
 
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.io.StringWriter;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Locale;
@@ -80,6 +85,8 @@
 public class ThreadNetworkCountryCodeTest {
     private static final String TEST_COUNTRY_CODE_US = "US";
     private static final String TEST_COUNTRY_CODE_CN = "CN";
+    private static final String TEST_COUNTRY_CODE_INVALID = "INVALID";
+    private static final String TEST_WIFI_DEFAULT_COUNTRY_CODE = "00";
     private static final int TEST_SIM_SLOT_INDEX_0 = 0;
     private static final int TEST_SIM_SLOT_INDEX_1 = 1;
 
@@ -144,16 +151,20 @@
                 .when(mThreadNetworkControllerService)
                 .setCountryCode(any(), any(IOperationReceiver.class));
 
-        mThreadNetworkCountryCode =
-                new ThreadNetworkCountryCode(
-                        mLocationManager,
-                        mThreadNetworkControllerService,
-                        mGeocoder,
-                        mConnectivityResources,
-                        mWifiManager,
-                        mContext,
-                        mTelephonyManager,
-                        mSubscriptionManager);
+        mThreadNetworkCountryCode = newCountryCodeWithOemSource(null);
+    }
+
+    private ThreadNetworkCountryCode newCountryCodeWithOemSource(@Nullable String oemCountryCode) {
+        return new ThreadNetworkCountryCode(
+                mLocationManager,
+                mThreadNetworkControllerService,
+                mGeocoder,
+                mConnectivityResources,
+                mWifiManager,
+                mContext,
+                mTelephonyManager,
+                mSubscriptionManager,
+                oemCountryCode);
     }
 
     private static Address newAddress(String countryCode) {
@@ -163,6 +174,13 @@
     }
 
     @Test
+    public void threadNetworkCountryCode_invalidOemCountryCode_illegalArgumentExceptionIsThrown() {
+        assertThrows(
+                IllegalArgumentException.class,
+                () -> newCountryCodeWithOemSource(TEST_COUNTRY_CODE_INVALID));
+    }
+
+    @Test
     public void initialize_defaultCountryCodeIsUsed() {
         mThreadNetworkCountryCode.initialize();
 
@@ -170,6 +188,15 @@
     }
 
     @Test
+    public void initialize_oemCountryCodeAvailable_oemCountryCodeIsUsed() {
+        mThreadNetworkCountryCode = newCountryCodeWithOemSource(TEST_COUNTRY_CODE_US);
+
+        mThreadNetworkCountryCode.initialize();
+
+        assertThat(mThreadNetworkCountryCode.getCountryCode()).isEqualTo(TEST_COUNTRY_CODE_US);
+    }
+
+    @Test
     public void initialize_locationUseIsDisabled_locationFunctionIsNotCalled() {
         when(mResources.getBoolean(R.bool.config_thread_location_use_for_country_code_enabled))
                 .thenReturn(false);
@@ -233,6 +260,21 @@
     }
 
     @Test
+    public void wifiCountryCode_wifiDefaultCountryCodeIsActive_wifiCountryCodeIsNotUsed() {
+        mThreadNetworkCountryCode.initialize();
+
+        verify(mWifiManager)
+                .registerActiveCountryCodeChangedCallback(
+                        any(), mWifiCountryCodeReceiverCaptor.capture());
+        mWifiCountryCodeReceiverCaptor
+                .getValue()
+                .onActiveCountryCodeChanged(TEST_WIFI_DEFAULT_COUNTRY_CODE);
+
+        assertThat(mThreadNetworkCountryCode.getCountryCode())
+                .isNotEqualTo(TEST_WIFI_DEFAULT_COUNTRY_CODE);
+    }
+
+    @Test
     public void wifiCountryCode_wifiCountryCodeIsInactive_defaultCountryCodeIsUsed() {
         mThreadNetworkCountryCode.initialize();
         verify(mWifiManager)
@@ -406,4 +448,22 @@
                 .setCountryCode(eq(TEST_COUNTRY_CODE_CN), mOperationReceiverCaptor.capture());
         assertThat(mThreadNetworkCountryCode.getCountryCode()).isEqualTo(DEFAULT_COUNTRY_CODE);
     }
+
+    @Test
+    public void dump_allCountryCodeInfoAreDumped() {
+        StringWriter stringWriter = new StringWriter();
+        PrintWriter printWriter = new PrintWriter(stringWriter);
+
+        mThreadNetworkCountryCode.dump(new FileDescriptor(), printWriter, null);
+        String outputString = stringWriter.toString();
+
+        assertThat(outputString).contains("mOverrideCountryCodeInfo");
+        assertThat(outputString).contains("mTelephonyCountryCodeSlotInfoMap");
+        assertThat(outputString).contains("mTelephonyCountryCodeInfo");
+        assertThat(outputString).contains("mWifiCountryCodeInfo");
+        assertThat(outputString).contains("mTelephonyLastCountryCodeInfo");
+        assertThat(outputString).contains("mLocationCountryCodeInfo");
+        assertThat(outputString).contains("mOemCountryCodeInfo");
+        assertThat(outputString).contains("mCurrentCountryCodeInfo");
+    }
 }
diff --git a/thread/tests/unit/src/android/net/thread/ThreadPersistentSettingsTest.java b/thread/tests/unit/src/com/android/server/thread/ThreadPersistentSettingsTest.java
similarity index 66%
rename from thread/tests/unit/src/android/net/thread/ThreadPersistentSettingsTest.java
rename to thread/tests/unit/src/com/android/server/thread/ThreadPersistentSettingsTest.java
index 11aabb8..9406a2f 100644
--- a/thread/tests/unit/src/android/net/thread/ThreadPersistentSettingsTest.java
+++ b/thread/tests/unit/src/com/android/server/thread/ThreadPersistentSettingsTest.java
@@ -23,18 +23,22 @@
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.anyInt;
 import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.eq;
 import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.validateMockitoUsage;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.content.res.Resources;
 import android.os.PersistableBundle;
-import android.test.suitebuilder.annotation.SmallTest;
 import android.util.AtomicFile;
 
+import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.connectivity.resources.R;
+import com.android.server.connectivity.ConnectivityResources;
+
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
@@ -51,16 +55,22 @@
 @SmallTest
 public class ThreadPersistentSettingsTest {
     @Mock private AtomicFile mAtomicFile;
+    @Mock Resources mResources;
+    @Mock ConnectivityResources mConnectivityResources;
 
-    private ThreadPersistentSettings mThreadPersistentSetting;
+    private ThreadPersistentSettings mThreadPersistentSettings;
 
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
 
+        when(mConnectivityResources.get()).thenReturn(mResources);
+        when(mResources.getBoolean(eq(R.bool.config_thread_default_enabled))).thenReturn(true);
+
         FileOutputStream fos = mock(FileOutputStream.class);
         when(mAtomicFile.startWrite()).thenReturn(fos);
-        mThreadPersistentSetting = new ThreadPersistentSettings(mAtomicFile);
+        mThreadPersistentSettings =
+                new ThreadPersistentSettings(mAtomicFile, mConnectivityResources);
     }
 
     /** Called after each test */
@@ -70,10 +80,42 @@
     }
 
     @Test
-    public void put_ThreadFeatureEnabledTrue_returnsTrue() throws Exception {
-        mThreadPersistentSetting.put(THREAD_ENABLED.key, true);
+    public void initialize_readsFromFile() throws Exception {
+        byte[] data = createXmlForParsing(THREAD_ENABLED.key, false);
+        setupAtomicFileMockForRead(data);
 
-        assertThat(mThreadPersistentSetting.get(THREAD_ENABLED)).isTrue();
+        mThreadPersistentSettings.initialize();
+
+        assertThat(mThreadPersistentSettings.get(THREAD_ENABLED)).isFalse();
+    }
+
+    @Test
+    public void initialize_ThreadDisabledInResources_returnsThreadDisabled() throws Exception {
+        when(mResources.getBoolean(eq(R.bool.config_thread_default_enabled))).thenReturn(false);
+        setupAtomicFileMockForRead(new byte[0]);
+
+        mThreadPersistentSettings.initialize();
+
+        assertThat(mThreadPersistentSettings.get(THREAD_ENABLED)).isFalse();
+    }
+
+    @Test
+    public void initialize_ThreadDisabledInResourcesButEnabledInXml_returnsThreadEnabled()
+            throws Exception {
+        when(mResources.getBoolean(eq(R.bool.config_thread_default_enabled))).thenReturn(false);
+        byte[] data = createXmlForParsing(THREAD_ENABLED.key, true);
+        setupAtomicFileMockForRead(data);
+
+        mThreadPersistentSettings.initialize();
+
+        assertThat(mThreadPersistentSettings.get(THREAD_ENABLED)).isTrue();
+    }
+
+    @Test
+    public void put_ThreadFeatureEnabledTrue_returnsTrue() throws Exception {
+        mThreadPersistentSettings.put(THREAD_ENABLED.key, true);
+
+        assertThat(mThreadPersistentSettings.get(THREAD_ENABLED)).isTrue();
         // Confirm that file writes have been triggered.
         verify(mAtomicFile).startWrite();
         verify(mAtomicFile).finishWrite(any());
@@ -81,26 +123,14 @@
 
     @Test
     public void put_ThreadFeatureEnabledFalse_returnsFalse() throws Exception {
-        mThreadPersistentSetting.put(THREAD_ENABLED.key, false);
+        mThreadPersistentSettings.put(THREAD_ENABLED.key, false);
 
-        assertThat(mThreadPersistentSetting.get(THREAD_ENABLED)).isFalse();
+        assertThat(mThreadPersistentSettings.get(THREAD_ENABLED)).isFalse();
         // Confirm that file writes have been triggered.
         verify(mAtomicFile).startWrite();
         verify(mAtomicFile).finishWrite(any());
     }
 
-    @Test
-    public void initialize_readsFromFile() throws Exception {
-        byte[] data = createXmlForParsing(THREAD_ENABLED.key, false);
-        setupAtomicFileMockForRead(data);
-
-        // Trigger file read.
-        mThreadPersistentSetting.initialize();
-
-        assertThat(mThreadPersistentSetting.get(THREAD_ENABLED)).isFalse();
-        verify(mAtomicFile, never()).startWrite();
-    }
-
     private byte[] createXmlForParsing(String key, Boolean value) throws Exception {
         PersistableBundle bundle = new PersistableBundle();
         ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
diff --git a/thread/flags/Android.bp b/thread/tests/utils/Android.bp
similarity index 60%
copy from thread/flags/Android.bp
copy to thread/tests/utils/Android.bp
index 225022c..24e9bb9 100644
--- a/thread/flags/Android.bp
+++ b/thread/tests/utils/Android.bp
@@ -1,5 +1,5 @@
 //
-// Copyright (C) 2024 The Android Open Source Project
+// Copyright (C) 2023 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.
@@ -15,21 +15,23 @@
 //
 
 package {
+    default_team: "trendy_team_fwk_thread_network",
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
-aconfig_declarations {
-    name: "thread_aconfig_flags",
-    package: "com.android.net.thread.flags",
-    srcs: ["thread_base.aconfig"],
-}
-
-java_aconfig_library {
-    name: "thread_aconfig_flags_lib",
-    aconfig_declarations: "thread_aconfig_flags",
+java_library {
+    name: "ThreadNetworkTestUtils",
     min_sdk_version: "30",
-    apex_available: [
-        "//apex_available:platform",
-        "com.android.tethering",
+    static_libs: [
+        "compatibility-device-util-axt",
+        "net-tests-utils",
+        "net-utils-device-common",
+        "net-utils-device-common-bpf",
+    ],
+    srcs: [
+        "src/**/*.java",
+    ],
+    defaults: [
+        "framework-connectivity-test-defaults",
     ],
 }
diff --git a/thread/tests/utils/src/android/net/thread/utils/TapTestNetworkTracker.java b/thread/tests/utils/src/android/net/thread/utils/TapTestNetworkTracker.java
new file mode 100644
index 0000000..43f177d
--- /dev/null
+++ b/thread/tests/utils/src/android/net/thread/utils/TapTestNetworkTracker.java
@@ -0,0 +1,185 @@
+/*
+ * 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.
+ */
+package android.net.thread.utils;
+
+import static android.Manifest.permission.MANAGE_TEST_NETWORKS;
+import static android.net.InetAddresses.parseNumericAddress;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_TRUSTED;
+import static android.net.NetworkCapabilities.TRANSPORT_TEST;
+import static android.system.OsConstants.AF_INET6;
+import static android.system.OsConstants.IPPROTO_UDP;
+import static android.system.OsConstants.SOCK_DGRAM;
+
+import static com.android.testutils.RecorderCallback.CallbackEntry.LINK_PROPERTIES_CHANGED;
+import static com.android.testutils.TestPermissionUtil.runAsShell;
+
+import android.content.Context;
+import android.net.ConnectivityManager;
+import android.net.LinkAddress;
+import android.net.LinkProperties;
+import android.net.Network;
+import android.net.NetworkAgentConfig;
+import android.net.NetworkCapabilities;
+import android.net.NetworkRequest;
+import android.net.TestNetworkInterface;
+import android.net.TestNetworkManager;
+import android.net.TestNetworkSpecifier;
+import android.os.Looper;
+import android.system.ErrnoException;
+import android.system.Os;
+
+import com.android.compatibility.common.util.PollingCheck;
+import com.android.testutils.TestableNetworkAgent;
+import com.android.testutils.TestableNetworkCallback;
+
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.net.InterfaceAddress;
+import java.net.NetworkInterface;
+import java.net.SocketException;
+import java.time.Duration;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/** A class that can create/destroy a test network based on TAP interface. */
+public final class TapTestNetworkTracker {
+    private static final Duration TIMEOUT = Duration.ofSeconds(2);
+    private final Context mContext;
+    private final Looper mLooper;
+    private TestNetworkInterface mInterface;
+    private TestableNetworkAgent mAgent;
+    private final TestableNetworkCallback mNetworkCallback;
+    private final ConnectivityManager mConnectivityManager;
+
+    /**
+     * Constructs a {@link TapTestNetworkTracker}.
+     *
+     * <p>It creates a TAP interface (e.g. testtap0) and registers a test network using that
+     * interface. It also requests the test network by {@link ConnectivityManager#requestNetwork} so
+     * the test network won't be automatically turned down by {@link
+     * com.android.server.ConnectivityService}.
+     */
+    public TapTestNetworkTracker(Context context, Looper looper) {
+        mContext = context;
+        mLooper = looper;
+        mConnectivityManager = mContext.getSystemService(ConnectivityManager.class);
+        mNetworkCallback = new TestableNetworkCallback();
+        runAsShell(MANAGE_TEST_NETWORKS, this::setUpTestNetwork);
+    }
+
+    /** Tears down the test network. */
+    public void tearDown() {
+        runAsShell(MANAGE_TEST_NETWORKS, this::tearDownTestNetwork);
+    }
+
+    /** Returns the interface name of the test network. */
+    public String getInterfaceName() {
+        return mInterface.getInterfaceName();
+    }
+
+    private void setUpTestNetwork() throws Exception {
+        mInterface = mContext.getSystemService(TestNetworkManager.class).createTapInterface();
+
+        mConnectivityManager.requestNetwork(newNetworkRequest(), mNetworkCallback);
+
+        LinkProperties lp = new LinkProperties();
+        lp.setInterfaceName(getInterfaceName());
+        mAgent =
+                new TestableNetworkAgent(
+                        mContext,
+                        mLooper,
+                        newNetworkCapabilities(),
+                        lp,
+                        new NetworkAgentConfig.Builder().build());
+        final Network network = mAgent.register();
+        mAgent.markConnected();
+
+        PollingCheck.check(
+                "No usable address on interface",
+                TIMEOUT.toMillis(),
+                () -> hasUsableAddress(network, getInterfaceName()));
+
+        lp.setLinkAddresses(makeLinkAddresses());
+        mAgent.sendLinkProperties(lp);
+        mNetworkCallback.eventuallyExpect(
+                LINK_PROPERTIES_CHANGED,
+                TIMEOUT.toMillis(),
+                l -> !l.getLp().getAddresses().isEmpty());
+    }
+
+    private void tearDownTestNetwork() throws IOException {
+        mConnectivityManager.unregisterNetworkCallback(mNetworkCallback);
+        mAgent.unregister();
+        mInterface.getFileDescriptor().close();
+        mAgent.waitForIdle(TIMEOUT.toMillis());
+    }
+
+    private NetworkRequest newNetworkRequest() {
+        return new NetworkRequest.Builder()
+                .removeCapability(NET_CAPABILITY_TRUSTED)
+                .addTransportType(TRANSPORT_TEST)
+                .setNetworkSpecifier(new TestNetworkSpecifier(getInterfaceName()))
+                .build();
+    }
+
+    private NetworkCapabilities newNetworkCapabilities() {
+        return new NetworkCapabilities()
+                .removeCapability(NET_CAPABILITY_TRUSTED)
+                .addTransportType(TRANSPORT_TEST)
+                .setNetworkSpecifier(new TestNetworkSpecifier(getInterfaceName()));
+    }
+
+    private List<LinkAddress> makeLinkAddresses() {
+        List<LinkAddress> linkAddresses = new ArrayList<>();
+        List<InterfaceAddress> interfaceAddresses = Collections.emptyList();
+
+        try {
+            interfaceAddresses =
+                    NetworkInterface.getByName(getInterfaceName()).getInterfaceAddresses();
+        } catch (SocketException ignored) {
+            // Ignore failures when getting the addresses.
+        }
+
+        for (InterfaceAddress address : interfaceAddresses) {
+            linkAddresses.add(
+                    new LinkAddress(address.getAddress(), address.getNetworkPrefixLength()));
+        }
+
+        return linkAddresses;
+    }
+
+    private static boolean hasUsableAddress(Network network, String interfaceName) {
+        try {
+            if (NetworkInterface.getByName(interfaceName).getInterfaceAddresses().isEmpty()) {
+                return false;
+            }
+        } catch (SocketException e) {
+            return false;
+        }
+        // Check if the link-local address can be used. Address flags are not available without
+        // elevated permissions, so check that bindSocket works.
+        try {
+            FileDescriptor sock = Os.socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP);
+            network.bindSocket(sock);
+            Os.connect(sock, parseNumericAddress("ff02::fb%" + interfaceName), 12345);
+            Os.close(sock);
+        } catch (ErrnoException | IOException e) {
+            return false;
+        }
+        return true;
+    }
+}
diff --git a/tools/Android.bp b/tools/Android.bp
index 3ce76f6..9216b5b 100644
--- a/tools/Android.bp
+++ b/tools/Android.bp
@@ -15,6 +15,7 @@
 //
 
 package {
+    default_team: "trendy_team_fwk_core_networking",
     // See: http://go/android-license-faq
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
@@ -41,6 +42,7 @@
     name: "jarjar-rules-generator-testjavalib",
     srcs: ["testdata/java/**/*.java"],
     libs: ["unsupportedappusage"],
+    sdk_version: "core_platform",
     visibility: ["//visibility:private"],
 }
 
@@ -55,6 +57,7 @@
     static_libs: [
         "framework-connectivity.stubs.module_lib",
     ],
+    sdk_version: "module_current",
     // Not strictly necessary but specified as this MUST not have generate
     // a dex jar as that will break the tests.
     compile_dex: false,
@@ -66,6 +69,7 @@
     static_libs: [
         "framework-connectivity-t.stubs.module_lib",
     ],
+    sdk_version: "module_current",
     // Not strictly necessary but specified as this MUST not have generate
     // a dex jar as that will break the tests.
     compile_dex: false,