Merge "Revert "Skip multi-device tests instead of abort if device is non-B+ devices"" into main
diff --git a/Tethering/src/com/android/networkstack/tethering/TetheringConfiguration.java b/Tethering/src/com/android/networkstack/tethering/TetheringConfiguration.java
index 3c91a1b..5f0e5d0 100644
--- a/Tethering/src/com/android/networkstack/tethering/TetheringConfiguration.java
+++ b/Tethering/src/com/android/networkstack/tethering/TetheringConfiguration.java
@@ -24,6 +24,7 @@
 import static android.provider.DeviceConfig.NAMESPACE_CONNECTIVITY;
 
 import static com.android.networkstack.apishim.ConstantsShim.KEY_CARRIER_SUPPORTS_TETHERING_BOOL;
+import static com.android.net.module.util.SdkUtil.isAtLeast25Q2;
 
 import android.content.ContentResolver;
 import android.content.Context;
@@ -395,7 +396,7 @@
      * use the async state machine.
      */
     public void readEnableSyncSM(final Context ctx) {
-        USE_SYNC_SM = mDeps.isFeatureNotChickenedOut(ctx, TETHER_ENABLE_SYNC_SM);
+        USE_SYNC_SM = isAtLeast25Q2() || mDeps.isFeatureNotChickenedOut(ctx, TETHER_ENABLE_SYNC_SM);
     }
 
     /** Does the dumping.*/
diff --git a/Tethering/src/com/android/networkstack/tethering/TetheringService.java b/Tethering/src/com/android/networkstack/tethering/TetheringService.java
index f501a50..b92cf69 100644
--- a/Tethering/src/com/android/networkstack/tethering/TetheringService.java
+++ b/Tethering/src/com/android/networkstack/tethering/TetheringService.java
@@ -22,6 +22,7 @@
 import static android.Manifest.permission.TETHER_PRIVILEGED;
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 import static android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK;
+import static android.net.TetheringManager.TETHERING_WIFI;
 import static android.net.TetheringManager.TETHER_ERROR_NO_ACCESS_TETHERING_PERMISSION;
 import static android.net.TetheringManager.TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION;
 import static android.net.TetheringManager.TETHER_ERROR_NO_ERROR;
@@ -58,6 +59,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.networkstack.apishim.SettingsShimImpl;
 import com.android.networkstack.apishim.common.SettingsShim;
+import com.android.networkstack.tethering.util.TetheringPermissionsUtils;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -72,6 +74,7 @@
 
     private TetheringConnector mConnector;
     private SettingsShim mSettingsShim;
+    private TetheringPermissionsUtils mTetheringPermissionsUtils;
 
     @Override
     public void onCreate() {
@@ -81,6 +84,7 @@
         mConnector = new TetheringConnector(makeTethering(deps), TetheringService.this);
 
         mSettingsShim = SettingsShimImpl.newInstance();
+        mTetheringPermissionsUtils = new TetheringPermissionsUtils(deps.getContext());
     }
 
     /**
@@ -109,7 +113,11 @@
         @Override
         public void tether(String iface, String callerPkg, String callingAttributionTag,
                 IIntResultListener listener) {
-            if (checkAndNotifyCommonError(callerPkg, callingAttributionTag, listener)) return;
+            if (checkAndNotifyCommonError(callerPkg, callingAttributionTag,
+                    false /* onlyAllowPrivileged */, false /* isDeviceOwnerAppAllowed */,
+                    listener)) {
+                return;
+            }
 
             mTethering.legacyTether(iface, listener);
         }
@@ -117,7 +125,11 @@
         @Override
         public void untether(String iface, String callerPkg, String callingAttributionTag,
                 IIntResultListener listener) {
-            if (checkAndNotifyCommonError(callerPkg, callingAttributionTag, listener)) return;
+            if (checkAndNotifyCommonError(callerPkg, callingAttributionTag,
+                    false /* onlyAllowPrivileged */, false /* isDeviceOwnerAppAllowed */,
+                    listener)) {
+                return;
+            }
 
             mTethering.legacyUntether(iface, listener);
         }
@@ -125,32 +137,45 @@
         @Override
         public void setUsbTethering(boolean enable, String callerPkg, String callingAttributionTag,
                 IIntResultListener listener) {
-            if (checkAndNotifyCommonError(callerPkg, callingAttributionTag, listener)) return;
+            if (checkAndNotifyCommonError(callerPkg, callingAttributionTag,
+                    false /* onlyAllowPrivileged */, false /* isDeviceOwnerAppAllowed */,
+                    listener)) {
+                return;
+            }
 
             mTethering.setUsbTethering(enable, listener);
         }
 
+        private boolean isRequestAllowedForDeviceOwner(@NonNull TetheringRequest request) {
+            return request.getTetheringType() == TETHERING_WIFI
+                    && request.getSoftApConfiguration() != null;
+        }
+
         @Override
-        public void startTethering(TetheringRequestParcel request, String callerPkg,
+        public void startTethering(TetheringRequestParcel requestParcel, String callerPkg,
                 String callingAttributionTag, IIntResultListener listener) {
-            boolean onlyAllowPrivileged = request.exemptFromEntitlementCheck
-                    || request.interfaceName != null;
-            if (checkAndNotifyCommonError(callerPkg,
-                    callingAttributionTag,
-                    onlyAllowPrivileged,
-                    listener)) {
+            TetheringRequest request = new TetheringRequest(requestParcel);
+            request.setUid(getBinderCallingUid());
+            request.setPackageName(callerPkg);
+            boolean onlyAllowPrivileged = request.isExemptFromEntitlementCheck()
+                    || request.getInterfaceName() != null;
+            boolean isDeviceOwnerAllowed = mTethering.isTetheringWithSoftApConfigEnabled()
+                    && isRequestAllowedForDeviceOwner(request);
+            if (checkAndNotifyCommonError(callerPkg, callingAttributionTag, onlyAllowPrivileged,
+                    isDeviceOwnerAllowed, listener)) {
                 return;
             }
-            TetheringRequest external = new TetheringRequest(request);
-            external.setUid(getBinderCallingUid());
-            external.setPackageName(callerPkg);
-            mTethering.startTethering(external, callerPkg, listener);
+            mTethering.startTethering(request, callerPkg, listener);
         }
 
         @Override
         public void stopTethering(int type, String callerPkg, String callingAttributionTag,
                 IIntResultListener listener) {
-            if (checkAndNotifyCommonError(callerPkg, callingAttributionTag, listener)) return;
+            if (checkAndNotifyCommonError(callerPkg, callingAttributionTag,
+                    false /* onlyAllowPrivileged */, false /* isDeviceOwnerAppAllowed */,
+                    listener)) {
+                return;
+            }
 
             try {
                 mTethering.stopTethering(type);
@@ -164,9 +189,16 @@
                 IIntResultListener listener) {
             if (request == null) return;
             if (listener == null) return;
-            if (checkAndNotifyCommonError(callerPkg, callingAttributionTag, listener)) return;
             request.setUid(getBinderCallingUid());
             request.setPackageName(callerPkg);
+            boolean isDeviceOwnerAllowed = mTethering.isTetheringWithSoftApConfigEnabled()
+                    && isRequestAllowedForDeviceOwner(request);
+            if (checkAndNotifyCommonError(callerPkg, callingAttributionTag,
+                    false /* onlyAllowPrivileged */, isDeviceOwnerAllowed, listener)) {
+                return;
+            }
+            // Note: Whether tethering is actually stopped or not will depend on whether the request
+            // matches an active one with the same UID (see RequestTracker#findFuzzyMatchedRequest).
             mTethering.stopTetheringRequest(request, listener);
         }
 
@@ -187,7 +219,11 @@
                             + " internal-only listener");
                 }
             };
-            if (checkAndNotifyCommonError(callerPkg, callingAttributionTag, listener)) return;
+            if (checkAndNotifyCommonError(callerPkg, callingAttributionTag,
+                    false /* onlyAllowPrivileged */, false /* isDeviceOwnerAppAllowed */,
+                    listener)) {
+                return;
+            }
 
             mTethering.requestLatestTetheringEntitlementResult(type, receiver, showEntitlementUi);
         }
@@ -223,7 +259,11 @@
         @Override
         public void stopAllTethering(String callerPkg, String callingAttributionTag,
                 IIntResultListener listener) {
-            if (checkAndNotifyCommonError(callerPkg, callingAttributionTag, listener)) return;
+            if (checkAndNotifyCommonError(callerPkg, callingAttributionTag,
+                    false /* onlyAllowPrivileged */, false /* isDeviceOwnerAppAllowed */,
+                    listener)) {
+                return;
+            }
 
             try {
                 mTethering.stopAllTethering();
@@ -234,8 +274,11 @@
         @Override
         public void isTetheringSupported(String callerPkg, String callingAttributionTag,
                 IIntResultListener listener) {
-            if (checkAndNotifyCommonError(callerPkg, callingAttributionTag, listener)) return;
-
+            boolean isDeviceOwnerAppAllowed = mTethering.isTetheringWithSoftApConfigEnabled();
+            if (checkAndNotifyCommonError(callerPkg, callingAttributionTag,
+                    false /* onlyAllowPrivileged */, isDeviceOwnerAppAllowed, listener)) {
+                return;
+            }
             try {
                 listener.onResult(TETHER_ERROR_NO_ERROR);
             } catch (RemoteException e) { }
@@ -260,23 +303,17 @@
         }
 
         private boolean checkAndNotifyCommonError(final String callerPkg,
-                final String callingAttributionTag, final IIntResultListener listener) {
-            return checkAndNotifyCommonError(callerPkg, callingAttributionTag,
-                    false /* onlyAllowPrivileged */, listener);
-        }
-
-        private boolean checkAndNotifyCommonError(final String callerPkg,
                 final String callingAttributionTag, final boolean onlyAllowPrivileged,
-                final IIntResultListener listener) {
+                final boolean isDeviceOwnerAppAllowed, final IIntResultListener listener) {
             try {
-                if (!checkPackageNameMatchesUid(getBinderCallingUid(), callerPkg)) {
-                    Log.e(TAG, "Package name " + callerPkg + " does not match UID "
-                            + getBinderCallingUid());
+                final int uid = getBinderCallingUid();
+                if (!checkPackageNameMatchesUid(uid, callerPkg)) {
+                    Log.e(TAG, "Package name " + callerPkg + " does not match UID " + uid);
                     listener.onResult(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION);
                     return true;
                 }
-                if (!hasTetherChangePermission(callerPkg, callingAttributionTag,
-                        onlyAllowPrivileged)) {
+                if (!hasTetherChangePermission(uid, callerPkg, callingAttributionTag,
+                        onlyAllowPrivileged, isDeviceOwnerAppAllowed)) {
                     listener.onResult(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION);
                     return true;
                 }
@@ -308,21 +345,25 @@
             return mService.checkCallingOrSelfPermission(permission) == PERMISSION_GRANTED;
         }
 
-        private boolean hasTetherChangePermission(final String callerPkg,
-                final String callingAttributionTag, final boolean onlyAllowPrivileged) {
+        private boolean hasTetherChangePermission(final int uid, final String callerPkg,
+                final String callingAttributionTag, final boolean onlyAllowPrivileged,
+                final boolean isDeviceOwnerAppAllowed) {
             if (onlyAllowPrivileged && !hasNetworkStackPermission()
                     && !hasNetworkSettingsPermission()) return false;
 
             if (hasTetherPrivilegedPermission()) return true;
 
+            // Allow DO apps to change tethering even if they don't have TETHER_PRIVILEGED.
+            if (isDeviceOwnerAppAllowed && mService.isDeviceOwner(uid, callerPkg)) {
+                return true;
+            }
+
             // After TetheringManager moves to public API, prevent third-party apps from being able
             // to change tethering with only WRITE_SETTINGS permission.
             if (mTethering.isTetheringWithSoftApConfigEnabled()) return false;
 
             if (mTethering.isTetherProvisioningRequired()) return false;
 
-            int uid = getBinderCallingUid();
-
             // If callerPkg's uid is not same as getBinderCallingUid(),
             // checkAndNoteWriteSettingsOperation will return false and the operation will be
             // denied.
@@ -387,6 +428,14 @@
     }
 
     /**
+     * Wrapper for {@link TetheringPermissionsUtils#isDeviceOwner(int, String)}, used for mocks.
+     */
+    @VisibleForTesting
+    boolean isDeviceOwner(final int uid, final String callerPkg) {
+        return mTetheringPermissionsUtils.isDeviceOwner(uid, callerPkg);
+    }
+
+    /**
      * An injection method for testing.
      */
     @VisibleForTesting
diff --git a/Tethering/src/com/android/networkstack/tethering/util/TetheringPermissionsUtils.java b/Tethering/src/com/android/networkstack/tethering/util/TetheringPermissionsUtils.java
new file mode 100644
index 0000000..944e861
--- /dev/null
+++ b/Tethering/src/com/android/networkstack/tethering/util/TetheringPermissionsUtils.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2025 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.networkstack.tethering.util;
+
+import android.app.admin.DevicePolicyManager;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.os.UserHandle;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+
+/**
+ * Utils class for checking permissions related to Tethering APIs.
+ */
+public class TetheringPermissionsUtils {
+    private static final String TAG = "TetherPermUtils";
+
+    @NonNull private final Context mContext;
+
+    public TetheringPermissionsUtils(@NonNull Context context) {
+        mContext = context;
+    }
+
+    /**
+     * Checks if the package name is a Device Owner.
+     */
+    public boolean isDeviceOwner(final int uid, @NonNull final String packageName) {
+        Context userContext;
+        try {
+            // There is no safe way to invoke this method since tethering package might not be
+            // installed for a certain user on the OEM devices, refer to b/382628161.
+            userContext = mContext.createContextAsUser(UserHandle.getUserHandleForUid(uid),
+                    0 /* flags */);
+        } catch (IllegalStateException e) {
+            // TODO: Add a terrible error metric for this case.
+            Log.e(TAG, "createContextAsUser failed, skipping Device Owner check", e);
+            return false;
+        }
+        DevicePolicyManager devicePolicyManager =
+                retrieveDevicePolicyManagerFromContext(userContext);
+        if (devicePolicyManager == null) return false;
+        return devicePolicyManager.isDeviceOwnerApp(packageName);
+    }
+
+    private DevicePolicyManager retrieveDevicePolicyManagerFromContext(
+            @NonNull final Context context) {
+        DevicePolicyManager devicePolicyManager =
+                context.getSystemService(DevicePolicyManager.class);
+        if (devicePolicyManager == null
+                && context.getPackageManager().hasSystemFeature(
+                PackageManager.FEATURE_DEVICE_ADMIN)) {
+            Log.w(TAG, "Error retrieving DPM service");
+        }
+        return devicePolicyManager;
+    }
+}
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/MockTetheringService.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/MockTetheringService.java
index 7fcc5f1..a8bd221 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/MockTetheringService.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/MockTetheringService.java
@@ -25,14 +25,18 @@
 import android.os.Binder;
 import android.os.IBinder;
 import android.util.ArrayMap;
+import android.util.ArraySet;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
+import java.util.Set;
+
 public class MockTetheringService extends TetheringService {
     private final Tethering mTethering = mock(Tethering.class);
     private final ArrayMap<String, Integer> mMockedPermissions = new ArrayMap<>();
     private final ArrayMap<String, Integer> mMockedPackageUids = new ArrayMap<>();
+    private final Set<String> mMockedDeviceOwnerPackages = new ArraySet<>();
     private int mMockCallingUid;
 
     @Override
@@ -74,6 +78,11 @@
         return mMockCallingUid;
     }
 
+    @Override
+    boolean isDeviceOwner(final int uid, final String callerPkg) {
+        return mMockedDeviceOwnerPackages.contains(callerPkg);
+    }
+
     public Tethering getTethering() {
         return mTethering;
     }
@@ -118,5 +127,19 @@
         public void setCallingUid(int uid) {
             mMockCallingUid = uid;
         }
+
+        /**
+         * Add a mocked carrier privileges package
+         */
+        public void addDeviceOwnerPackage(final String packageName) {
+            mMockedDeviceOwnerPackages.add(packageName);
+        }
+
+        /**
+         * Remove a mocked carrier privileges package
+         */
+        public void removeDeviceOwnerPackage(final String packageName) {
+            mMockedDeviceOwnerPackages.remove(packageName);
+        }
     }
 }
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 0159573..c4c7c9c 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringConfigurationTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringConfigurationTest.java
@@ -63,6 +63,7 @@
 import com.android.internal.util.test.FakeSettingsProvider;
 import com.android.modules.utils.build.SdkLevel;
 import com.android.net.module.util.DeviceConfigUtils;
+import com.android.net.module.util.SdkUtil;
 import com.android.net.module.util.SharedLog;
 import com.android.testutils.DevSdkIgnoreRule;
 import com.android.testutils.DevSdkIgnoreRule.IgnoreAfter;
@@ -779,7 +780,10 @@
         setTetherEnableSyncSMFlagEnabled(true);
         assertEnableSyncSM(true);
 
-        setTetherEnableSyncSMFlagEnabled(false);
-        assertEnableSyncSM(false);
+        // Feature is enabled by default after 25Q2 release.
+        if (!SdkUtil.isAtLeast25Q2()) {
+            setTetherEnableSyncSMFlagEnabled(false);
+            assertEnableSyncSM(false);
+        }
     }
 }
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringServiceTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringServiceTest.java
index b550ada..87163ef 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringServiceTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringServiceTest.java
@@ -21,6 +21,7 @@
 import static android.Manifest.permission.TETHER_PRIVILEGED;
 import static android.Manifest.permission.WRITE_SETTINGS;
 import static android.content.pm.PackageManager.PERMISSION_DENIED;
+import static android.net.TetheringManager.TETHERING_USB;
 import static android.net.TetheringManager.TETHERING_VIRTUAL;
 import static android.net.TetheringManager.TETHERING_WIFI;
 import static android.net.TetheringManager.TETHER_ERROR_NO_ACCESS_TETHERING_PERMISSION;
@@ -36,6 +37,7 @@
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.never;
@@ -53,6 +55,7 @@
 import android.net.TetheringManager;
 import android.net.TetheringManager.TetheringRequest;
 import android.net.TetheringRequestParcel;
+import android.net.wifi.SoftApConfiguration;
 import android.os.Bundle;
 import android.os.ConditionVariable;
 import android.os.Handler;
@@ -153,43 +156,58 @@
     }
 
     private void runAsNoPermission(final TestTetheringCall test) throws Exception {
-        runTetheringCall(test, true /* isTetheringAllowed */, false /* isWriteSettingsAllowed */,
-                new String[0]);
+        runTetheringCall(test, true /* isTetheringAllowed */,
+                true /* isTetheringWithSoftApConfigEnabled */, new String[0]);
     }
 
     private void runAsTetherPrivileged(final TestTetheringCall test) throws Exception {
-        runTetheringCall(test, true /* isTetheringAllowed */, false /* isWriteSettingsAllowed */,
-                TETHER_PRIVILEGED);
+        runTetheringCall(test, true /* isTetheringAllowed */,
+                true /* isTetheringWithSoftApConfigEnabled */, TETHER_PRIVILEGED);
     }
 
     private void runAsAccessNetworkState(final TestTetheringCall test) throws Exception {
-        runTetheringCall(test, true /* isTetheringAllowed */, false /* isWriteSettingsAllowed */,
-                ACCESS_NETWORK_STATE);
+        runTetheringCall(test, true /* isTetheringAllowed */,
+                true /* isTetheringWithSoftApConfigEnabled */, ACCESS_NETWORK_STATE);
     }
 
     private void runAsWriteSettings(final TestTetheringCall test) throws Exception {
-        runTetheringCall(test, true /* isTetheringAllowed */, false /* isWriteSettingsAllowed */,
-                WRITE_SETTINGS);
+        runTetheringCall(test, true /* isTetheringAllowed */,
+                true /* isTetheringWithSoftApConfigEnabled */, WRITE_SETTINGS);
     }
 
     private void runAsWriteSettingsWhenWriteSettingsAllowed(
             final TestTetheringCall test) throws Exception {
-        runTetheringCall(test, true /* isTetheringAllowed */, true /* isWriteSettingsAllowed */,
-                WRITE_SETTINGS);
+        runTetheringCall(test, true /* isTetheringAllowed */,
+                false /* isTetheringWithSoftApConfigEnabled */, WRITE_SETTINGS);
     }
 
     private void runAsTetheringDisallowed(final TestTetheringCall test) throws Exception {
-        runTetheringCall(test, false /* isTetheringAllowed */, false /* isWriteSettingsAllowed */,
-                TETHER_PRIVILEGED);
+        runTetheringCall(test, false /* isTetheringAllowed */,
+                true /* isTetheringWithSoftApConfigEnabled */, TETHER_PRIVILEGED);
     }
 
     private void runAsNetworkSettings(final TestTetheringCall test) throws Exception {
-        runTetheringCall(test, true /* isTetheringAllowed */, false /* isWriteSettingsAllowed */,
-                NETWORK_SETTINGS, TETHER_PRIVILEGED);
+        runTetheringCall(test, true /* isTetheringAllowed */,
+                true /* isTetheringWithSoftApConfigEnabled */, NETWORK_SETTINGS, TETHER_PRIVILEGED);
+    }
+
+    private void runAsDeviceOwner(final TestTetheringCall test) throws Exception {
+        mMockConnector.addDeviceOwnerPackage(TEST_CALLER_PKG);
+        runTetheringCall(test, true /* isTetheringAllowed */,
+                true /* isTetheringWithSoftApConfigEnabled */, new String[0]);
+        mMockConnector.removeDeviceOwnerPackage(TEST_CALLER_PKG);
+    }
+
+    private void runAsDeviceOwnerWhenDeviceOwnerBypassNotEnabled(final TestTetheringCall test)
+            throws Exception {
+        mMockConnector.addDeviceOwnerPackage(TEST_CALLER_PKG);
+        runTetheringCall(test, true /* isTetheringAllowed */,
+                false /* isTetheringWithSoftApConfigEnabled */, new String[0]);
+        mMockConnector.removeDeviceOwnerPackage(TEST_CALLER_PKG);
     }
 
     private void runTetheringCall(final TestTetheringCall test, boolean isTetheringAllowed,
-            boolean isWriteSettingsAllowed, String... permissions) throws Exception {
+            boolean isTetheringWithSoftApConfigEnabled, String... permissions) throws Exception {
         // Allow the test to run even if ACCESS_NETWORK_STATE was granted at the APK level
         if (!CollectionUtils.contains(permissions, ACCESS_NETWORK_STATE)) {
             mMockConnector.setPermission(ACCESS_NETWORK_STATE, PERMISSION_DENIED);
@@ -200,7 +218,7 @@
             when(mTethering.isTetheringSupported()).thenReturn(true);
             when(mTethering.isTetheringAllowed()).thenReturn(isTetheringAllowed);
             when(mTethering.isTetheringWithSoftApConfigEnabled())
-                    .thenReturn(!isWriteSettingsAllowed);
+                    .thenReturn(isTetheringWithSoftApConfigEnabled);
             test.runTetheringCall(new TestTetheringResult());
         } finally {
             mUiAutomation.dropShellPermissionIdentity();
@@ -226,7 +244,7 @@
         runAsNoPermission((result) -> {
             mTetheringConnector.tether(TEST_IFACE_NAME, TEST_CALLER_PKG, TEST_ATTRIBUTION_TAG,
                     result);
-            verify(mTethering).isTetheringWithSoftApConfigEnabled();
+            verify(mTethering, atLeastOnce()).isTetheringWithSoftApConfigEnabled();
             result.assertResult(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION);
             verifyNoMoreInteractionsForTethering();
         });
@@ -239,14 +257,14 @@
         runAsWriteSettings((result) -> {
             mTetheringConnector.tether(TEST_IFACE_NAME, TEST_CALLER_PKG, TEST_ATTRIBUTION_TAG,
                     result);
-            verify(mTethering).isTetheringWithSoftApConfigEnabled();
+            verify(mTethering, atLeastOnce()).isTetheringWithSoftApConfigEnabled();
             result.assertResult(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION);
             verifyNoMoreInteractionsForTethering();
         });
 
         runAsWriteSettingsWhenWriteSettingsAllowed((result) -> {
             runTether(result);
-            verify(mTethering).isTetheringWithSoftApConfigEnabled();
+            verify(mTethering, atLeastOnce()).isTetheringWithSoftApConfigEnabled();
             verify(mTethering).isTetherProvisioningRequired();
             verifyNoMoreInteractionsForTethering();
         });
@@ -274,7 +292,7 @@
         runAsNoPermission((result) -> {
             mTetheringConnector.untether(TEST_IFACE_NAME, TEST_CALLER_PKG, TEST_ATTRIBUTION_TAG,
                     result);
-            verify(mTethering).isTetheringWithSoftApConfigEnabled();
+            verify(mTethering, atLeastOnce()).isTetheringWithSoftApConfigEnabled();
             result.assertResult(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION);
             verifyNoMoreInteractionsForTethering();
         });
@@ -287,14 +305,14 @@
         runAsWriteSettings((result) -> {
             mTetheringConnector.untether(TEST_IFACE_NAME, TEST_CALLER_PKG, TEST_ATTRIBUTION_TAG,
                     result);
-            verify(mTethering).isTetheringWithSoftApConfigEnabled();
+            verify(mTethering, atLeastOnce()).isTetheringWithSoftApConfigEnabled();
             result.assertResult(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION);
             verifyNoMoreInteractionsForTethering();
         });
 
         runAsWriteSettingsWhenWriteSettingsAllowed((result) -> {
             runUnTether(result);
-            verify(mTethering).isTetheringWithSoftApConfigEnabled();
+            verify(mTethering, atLeastOnce()).isTetheringWithSoftApConfigEnabled();
             verify(mTethering).isTetherProvisioningRequired();
             verifyNoMoreInteractionsForTethering();
         });
@@ -328,7 +346,7 @@
         runAsNoPermission((result) -> {
             mTetheringConnector.setUsbTethering(true /* enable */, TEST_CALLER_PKG,
                     TEST_ATTRIBUTION_TAG, result);
-            verify(mTethering).isTetheringWithSoftApConfigEnabled();
+            verify(mTethering, atLeastOnce()).isTetheringWithSoftApConfigEnabled();
             result.assertResult(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION);
             verifyNoMoreInteractionsForTethering();
         });
@@ -341,14 +359,14 @@
         runAsWriteSettings((result) -> {
             mTetheringConnector.setUsbTethering(true /* enable */, TEST_CALLER_PKG,
                     TEST_ATTRIBUTION_TAG, result);
-            verify(mTethering).isTetheringWithSoftApConfigEnabled();
+            verify(mTethering, atLeastOnce()).isTetheringWithSoftApConfigEnabled();
             result.assertResult(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION);
             verifyNoMoreInteractionsForTethering();
         });
 
         runAsWriteSettingsWhenWriteSettingsAllowed((result) -> {
             runSetUsbTethering(result);
-            verify(mTethering).isTetheringWithSoftApConfigEnabled();
+            verify(mTethering, atLeastOnce()).isTetheringWithSoftApConfigEnabled();
             verify(mTethering).isTetherProvisioningRequired();
             verifyNoMoreInteractionsForTethering();
         });
@@ -367,6 +385,7 @@
             final TetheringRequestParcel request) throws Exception {
         mTetheringConnector.startTethering(request, TEST_CALLER_PKG, TEST_ATTRIBUTION_TAG,
                 result);
+        verify(mTethering, atLeastOnce()).isTetheringWithSoftApConfigEnabled();
         verify(mTethering).isTetheringSupported();
         verify(mTethering).isTetheringAllowed();
         verify(mTethering).startTethering(
@@ -381,14 +400,61 @@
         runAsNoPermission((result) -> {
             mTetheringConnector.startTethering(request, TEST_CALLER_PKG, TEST_ATTRIBUTION_TAG,
                     result);
-            verify(mTethering).isTetheringWithSoftApConfigEnabled();
+            verify(mTethering, atLeastOnce()).isTetheringWithSoftApConfigEnabled();
             result.assertResult(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION);
             verifyNoMoreInteractionsForTethering();
         });
 
+        // Not a Wifi request - Fail
+        runAsDeviceOwner((result) -> {
+            final TetheringRequestParcel notWifi = new TetheringRequestParcel();
+            notWifi.tetheringType = TETHERING_USB;
+            mTetheringConnector.startTethering(notWifi, TEST_CALLER_PKG, TEST_ATTRIBUTION_TAG,
+                    result);
+            verify(mTethering, atLeastOnce()).isTetheringWithSoftApConfigEnabled();
+            result.assertResult(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION);
+            verifyNoMoreInteractionsForTethering();
+        });
+
+        // Request has no SoftApConfiguration - Fail
+        runAsDeviceOwner((result) -> {
+            final TetheringRequestParcel noConfig = new TetheringRequestParcel();
+            noConfig.tetheringType = TETHERING_WIFI;
+            mTetheringConnector.startTethering(noConfig, TEST_CALLER_PKG, TEST_ATTRIBUTION_TAG,
+                    result);
+            verify(mTethering, atLeastOnce()).isTetheringWithSoftApConfigEnabled();
+            result.assertResult(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION);
+            verifyNoMoreInteractionsForTethering();
+        });
+
+        // Wifi request with SoftApConfiguration - Succeed
+        runAsDeviceOwner((result) -> {
+            final TetheringRequestParcel withConfig = new TetheringRequestParcel();
+            withConfig.tetheringType = TETHERING_WIFI;
+            withConfig.softApConfig = new SoftApConfiguration.Builder().build();
+            mTetheringConnector.startTethering(withConfig, TEST_CALLER_PKG, TEST_ATTRIBUTION_TAG,
+                    result);
+            verify(mTethering, atLeastOnce()).isTetheringWithSoftApConfigEnabled();
+            verify(mTethering).isTetheringSupported();
+            verify(mTethering).isTetheringAllowed();
+            verify(mTethering).startTethering(any(), any(), any());
+            result.assertResult(-1); // No result
+            verifyNoMoreInteractionsForTethering();
+        });
+
+        runAsDeviceOwnerWhenDeviceOwnerBypassNotEnabled((result) -> {
+            mTetheringConnector.startTethering(request, TEST_CALLER_PKG, TEST_ATTRIBUTION_TAG,
+                    result);
+            verify(mTethering, atLeastOnce()).isTetheringWithSoftApConfigEnabled();
+            result.assertResult(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION);
+            verify(mTethering).isTetherProvisioningRequired();
+            verifyNoMoreInteractionsForTethering();
+        });
+
         runAsTetherPrivileged((result) -> {
             mTetheringConnector.startTethering(request, TEST_WRONG_PACKAGE,
                     TEST_ATTRIBUTION_TAG, result);
+            verify(mTethering, atLeastOnce()).isTetheringWithSoftApConfigEnabled();
             verify(mTethering, never()).startTethering(
                     eq(new TetheringRequest(request)), eq(TEST_WRONG_PACKAGE), eq(result));
             result.assertResult(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION);
@@ -403,14 +469,14 @@
         runAsWriteSettings((result) -> {
             mTetheringConnector.startTethering(request, TEST_CALLER_PKG, TEST_ATTRIBUTION_TAG,
                     result);
-            verify(mTethering).isTetheringWithSoftApConfigEnabled();
+            verify(mTethering, atLeastOnce()).isTetheringWithSoftApConfigEnabled();
             result.assertResult(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION);
             verifyNoMoreInteractionsForTethering();
         });
 
         runAsWriteSettingsWhenWriteSettingsAllowed((result) -> {
             runStartTethering(result, request);
-            verify(mTethering).isTetheringWithSoftApConfigEnabled();
+            verify(mTethering, atLeastOnce()).isTetheringWithSoftApConfigEnabled();
             verify(mTethering).isTetherProvisioningRequired();
             verifyNoMoreInteractionsForTethering();
         });
@@ -418,6 +484,7 @@
         runAsTetheringDisallowed((result) -> {
             mTetheringConnector.startTethering(request, TEST_CALLER_PKG, TEST_ATTRIBUTION_TAG,
                     result);
+            verify(mTethering, atLeastOnce()).isTetheringWithSoftApConfigEnabled();
             verify(mTethering).isTetheringSupported();
             verify(mTethering).isTetheringAllowed();
             result.assertResult(TETHER_ERROR_UNSUPPORTED);
@@ -447,6 +514,7 @@
             mTetheringConnector.startTethering(request, TEST_CALLER_PKG, TEST_ATTRIBUTION_TAG,
                     result);
             result.assertResult(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION);
+            verify(mTethering, atLeastOnce()).isTetheringWithSoftApConfigEnabled();
             verifyNoMoreInteractionsForTethering();
         });
     }
@@ -459,6 +527,7 @@
         mTetheringConnector.startTethering(request, TEST_CALLER_PKG, TEST_ATTRIBUTION_TAG,
                 result);
         result.assertResult(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION);
+        verify(mTethering, atLeastOnce()).isTetheringWithSoftApConfigEnabled();
         verifyNoMoreInteractionsForTethering();
     }
 
@@ -495,7 +564,7 @@
         runAsNoPermission((result) -> {
             mTetheringConnector.stopTethering(TETHERING_WIFI, TEST_CALLER_PKG,
                     TEST_ATTRIBUTION_TAG, result);
-            verify(mTethering).isTetheringWithSoftApConfigEnabled();
+            verify(mTethering, atLeastOnce()).isTetheringWithSoftApConfigEnabled();
             result.assertResult(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION);
             verifyNoMoreInteractionsForTethering();
         });
@@ -508,14 +577,14 @@
         runAsWriteSettings((result) -> {
             mTetheringConnector.stopTethering(TETHERING_WIFI, TEST_CALLER_PKG,
                     TEST_ATTRIBUTION_TAG, result);
-            verify(mTethering).isTetheringWithSoftApConfigEnabled();
+            verify(mTethering, atLeastOnce()).isTetheringWithSoftApConfigEnabled();
             result.assertResult(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION);
             verifyNoMoreInteractionsForTethering();
         });
 
         runAsWriteSettingsWhenWriteSettingsAllowed((result) -> {
             runStopTethering(result);
-            verify(mTethering).isTetheringWithSoftApConfigEnabled();
+            verify(mTethering, atLeastOnce()).isTetheringWithSoftApConfigEnabled();
             verify(mTethering).isTetherProvisioningRequired();
             verifyNoMoreInteractionsForTethering();
         });
@@ -530,6 +599,87 @@
         });
     }
 
+    private void verifyHasPermissionForStopTetheringRequest(TetheringRequest request,
+            final TestTetheringResult result) throws Exception {
+        mTetheringConnector.stopTetheringRequest(request, TEST_CALLER_PKG,
+                TEST_ATTRIBUTION_TAG, result);
+        verify(mTethering).stopTetheringRequest(any(), any());
+        verify(mTethering).isTetheringSupported();
+        verify(mTethering).isTetheringAllowed();
+        reset(mTethering);
+    }
+
+    private void verifyDoesNotHavePermissionForStopTetheringRequest(TetheringRequest request,
+            final TestTetheringResult result) throws Exception {
+        mTetheringConnector.stopTetheringRequest(request, TEST_CALLER_PKG,
+                TEST_ATTRIBUTION_TAG, result);
+        verify(mTethering, never()).stopTetheringRequest(any(), any());
+        result.assertResult(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION);
+        reset(mTethering);
+    }
+
+    private void verifyStopTetheringRequestWithTetheringDisallowed(TetheringRequest request,
+            final TestTetheringResult result) throws Exception {
+        mTetheringConnector.stopTetheringRequest(request, TEST_CALLER_PKG,
+                TEST_ATTRIBUTION_TAG, result);
+        verify(mTethering, never()).stopTetheringRequest(any(), any());
+        result.assertResult(TETHER_ERROR_UNSUPPORTED);
+        reset(mTethering);
+    }
+
+    @Test
+    public void testStopTetheringRequest() throws Exception {
+        TetheringRequest request = new TetheringRequest.Builder(TETHERING_WIFI).build();
+
+        runAsNoPermission((result) -> {
+            verifyDoesNotHavePermissionForStopTetheringRequest(request, result);
+        });
+
+        runAsTetherPrivileged((result) -> {
+            verifyHasPermissionForStopTetheringRequest(request, result);
+        });
+
+        runAsWriteSettings((result) -> {
+            verifyDoesNotHavePermissionForStopTetheringRequest(request, result);
+        });
+
+        runAsWriteSettingsWhenWriteSettingsAllowed((result) -> {
+            verifyHasPermissionForStopTetheringRequest(request, result);
+        });
+
+        runAsTetheringDisallowed((result) -> {
+            verifyStopTetheringRequestWithTetheringDisallowed(request, result);
+        });
+
+        runAsNetworkSettings((result) -> {
+            verifyHasPermissionForStopTetheringRequest(request, result);
+        });
+
+        // Not wifi -> fail
+        runAsDeviceOwner((result) -> {
+            TetheringRequest notWifi = new TetheringRequest.Builder(TETHERING_USB).build();
+            verifyDoesNotHavePermissionForStopTetheringRequest(notWifi, result);
+        });
+
+        // No config -> fail
+        runAsDeviceOwner((result) -> {
+            TetheringRequest noConfig = new TetheringRequest.Builder(TETHERING_WIFI).build();
+            verifyDoesNotHavePermissionForStopTetheringRequest(noConfig, result);
+        });
+
+        // With config -> success
+        TetheringRequest withConfig = new TetheringRequest.Builder(TETHERING_WIFI)
+                .setSoftApConfiguration(new SoftApConfiguration.Builder().build())
+                .build();
+        runAsDeviceOwner((result) -> {
+            verifyHasPermissionForStopTetheringRequest(withConfig, result);
+        });
+
+        runAsDeviceOwnerWhenDeviceOwnerBypassNotEnabled((result) -> {
+            verifyDoesNotHavePermissionForStopTetheringRequest(withConfig, result);
+        });
+    }
+
     private void runRequestLatestTetheringEntitlementResult() throws Exception {
         final MyResultReceiver result = new MyResultReceiver(null);
         mTetheringConnector.requestLatestTetheringEntitlementResult(TETHERING_WIFI, result,
@@ -547,7 +697,7 @@
         runAsNoPermission((none) -> {
             mTetheringConnector.requestLatestTetheringEntitlementResult(TETHERING_WIFI, result,
                     true /* showEntitlementUi */, TEST_CALLER_PKG, TEST_ATTRIBUTION_TAG);
-            verify(mTethering).isTetheringWithSoftApConfigEnabled();
+            verify(mTethering, atLeastOnce()).isTetheringWithSoftApConfigEnabled();
             result.assertResult(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION);
             verifyNoMoreInteractionsForTethering();
         });
@@ -567,14 +717,14 @@
         runAsWriteSettings((none) -> {
             mTetheringConnector.requestLatestTetheringEntitlementResult(TETHERING_WIFI, result,
                     true /* showEntitlementUi */, TEST_CALLER_PKG, TEST_ATTRIBUTION_TAG);
-            verify(mTethering).isTetheringWithSoftApConfigEnabled();
+            verify(mTethering, atLeastOnce()).isTetheringWithSoftApConfigEnabled();
             result.assertResult(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION);
             verifyNoMoreInteractionsForTethering();
         });
 
         runAsWriteSettingsWhenWriteSettingsAllowed((none) -> {
             runRequestLatestTetheringEntitlementResult();
-            verify(mTethering).isTetheringWithSoftApConfigEnabled();
+            verify(mTethering, atLeastOnce()).isTetheringWithSoftApConfigEnabled();
             verify(mTethering).isTetherProvisioningRequired();
             verifyNoMoreInteractionsForTethering();
         });
@@ -668,7 +818,7 @@
     public void testStopAllTethering() throws Exception {
         runAsNoPermission((result) -> {
             mTetheringConnector.stopAllTethering(TEST_CALLER_PKG, TEST_ATTRIBUTION_TAG, result);
-            verify(mTethering).isTetheringWithSoftApConfigEnabled();
+            verify(mTethering, atLeastOnce()).isTetheringWithSoftApConfigEnabled();
             result.assertResult(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION);
             verifyNoMoreInteractionsForTethering();
         });
@@ -680,14 +830,14 @@
 
         runAsWriteSettings((result) -> {
             mTetheringConnector.stopAllTethering(TEST_CALLER_PKG, TEST_ATTRIBUTION_TAG, result);
-            verify(mTethering).isTetheringWithSoftApConfigEnabled();
+            verify(mTethering, atLeastOnce()).isTetheringWithSoftApConfigEnabled();
             result.assertResult(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION);
             verifyNoMoreInteractionsForTethering();
         });
 
         runAsWriteSettingsWhenWriteSettingsAllowed((result) -> {
             runStopAllTethering(result);
-            verify(mTethering).isTetheringWithSoftApConfigEnabled();
+            verify(mTethering, atLeastOnce()).isTetheringWithSoftApConfigEnabled();
             verify(mTethering).isTetherProvisioningRequired();
             verifyNoMoreInteractionsForTethering();
         });
@@ -703,6 +853,7 @@
 
     private void runIsTetheringSupported(final TestTetheringResult result) throws Exception {
         mTetheringConnector.isTetheringSupported(TEST_CALLER_PKG, TEST_ATTRIBUTION_TAG, result);
+        verify(mTethering, atLeastOnce()).isTetheringWithSoftApConfigEnabled();
         verify(mTethering).isTetheringSupported();
         verify(mTethering).isTetheringAllowed();
         result.assertResult(TETHER_ERROR_NO_ERROR);
@@ -713,7 +864,7 @@
         runAsNoPermission((result) -> {
             mTetheringConnector.isTetheringSupported(TEST_CALLER_PKG, TEST_ATTRIBUTION_TAG,
                     result);
-            verify(mTethering).isTetheringWithSoftApConfigEnabled();
+            verify(mTethering, atLeastOnce()).isTetheringWithSoftApConfigEnabled();
             result.assertResult(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION);
             verifyNoMoreInteractionsForTethering();
         });
@@ -726,14 +877,13 @@
         runAsWriteSettings((result) -> {
             mTetheringConnector.isTetheringSupported(TEST_CALLER_PKG, TEST_ATTRIBUTION_TAG,
                     result);
-            verify(mTethering).isTetheringWithSoftApConfigEnabled();
+            verify(mTethering, atLeastOnce()).isTetheringWithSoftApConfigEnabled();
             result.assertResult(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION);
             verifyNoMoreInteractionsForTethering();
         });
 
         runAsWriteSettingsWhenWriteSettingsAllowed((result) -> {
             runIsTetheringSupported(result);
-            verify(mTethering).isTetheringWithSoftApConfigEnabled();
             verify(mTethering).isTetherProvisioningRequired();
             verifyNoMoreInteractionsForTethering();
         });
@@ -741,6 +891,7 @@
         runAsTetheringDisallowed((result) -> {
             mTetheringConnector.isTetheringSupported(TEST_CALLER_PKG, TEST_ATTRIBUTION_TAG,
                     result);
+            verify(mTethering, atLeastOnce()).isTetheringWithSoftApConfigEnabled();
             verify(mTethering).isTetheringSupported();
             verify(mTethering).isTetheringAllowed();
             result.assertResult(TETHER_ERROR_UNSUPPORTED);
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/util/TetheringPermissionsUtilsTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/util/TetheringPermissionsUtilsTest.java
new file mode 100644
index 0000000..57c3eca
--- /dev/null
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/util/TetheringPermissionsUtilsTest.java
@@ -0,0 +1,70 @@
+/*
+ * 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.networkstack.tethering.util;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.when;
+
+import android.app.admin.DevicePolicyManager;
+import android.content.Context;
+import android.os.UserHandle;
+import android.telephony.TelephonyManager;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class TetheringPermissionsUtilsTest {
+    private static final int TEST_UID = 12345;
+    private static final String TEST_PACKAGE = "test.package";
+
+    @Mock Context mContext;
+    @Mock Context mUserContext;
+    @Mock DevicePolicyManager mDevicePolicyManager;
+    @Mock TelephonyManager mTelephonyManager;
+
+    TetheringPermissionsUtils mTetheringPermissionsUtils;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+
+        when(mContext.createContextAsUser(UserHandle.getUserHandleForUid(TEST_UID), 0))
+                .thenReturn(mUserContext);
+        when(mUserContext.getSystemService(DevicePolicyManager.class))
+                .thenReturn(mDevicePolicyManager);
+        when(mContext.getSystemService(TelephonyManager.class)).thenReturn(mTelephonyManager);
+        mTetheringPermissionsUtils = new TetheringPermissionsUtils(mContext);
+    }
+
+    @Test
+    public void testIsDeviceOwner() {
+        when(mDevicePolicyManager.isDeviceOwnerApp(TEST_PACKAGE)).thenReturn(false);
+        assertThat(mTetheringPermissionsUtils.isDeviceOwner(TEST_UID, TEST_PACKAGE)).isFalse();
+
+        when(mDevicePolicyManager.isDeviceOwnerApp(TEST_PACKAGE)).thenReturn(true);
+        assertThat(mTetheringPermissionsUtils.isDeviceOwner(TEST_UID, TEST_PACKAGE)).isTrue();
+    }
+}
diff --git a/bpf/loader/NetBpfLoad.cpp b/bpf/loader/NetBpfLoad.cpp
index 28cb09e..d70a2c8 100644
--- a/bpf/loader/NetBpfLoad.cpp
+++ b/bpf/loader/NetBpfLoad.cpp
@@ -856,20 +856,24 @@
     ret = getSectionSymNames(elfFile, "maps", mapNames);
     if (ret) return ret;
 
-    ret = readSectionByName(".BTF", elfFile, btfData);
-    if (ret) {
-        ALOGE("Failed to read .BTF section, ret:%d", ret);
-        return ret;
-    }
-    struct btf *btf = btf__new(btfData.data(), btfData.size());
-    if (btf == NULL) {
-        ALOGE("btf__new failed, errno: %d", errno);
-        return -errno;
-    }
-    auto scopeGuard = base::make_scope_guard([btf] { btf__free(btf); });
+    struct btf *btf = NULL;
+    auto scopeGuard = base::make_scope_guard([btf] { if (btf) btf__free(btf); });
+    if (isAtLeastKernelVersion(4, 18, 0)) {
+        // On Linux Kernels older than 4.18 BPF_BTF_LOAD command doesn't exist.
+        ret = readSectionByName(".BTF", elfFile, btfData);
+        if (ret) {
+            ALOGE("Failed to read .BTF section, ret:%d", ret);
+            return ret;
+        }
+        struct btf *btf = btf__new(btfData.data(), btfData.size());
+        if (btf == NULL) {
+            ALOGE("btf__new failed, errno: %d", errno);
+            return -errno;
+        }
 
-    ret = loadBtf(elfFile, btf);
-    if (ret) return ret;
+        ret = loadBtf(elfFile, btf);
+        if (ret) return ret;
+    }
 
     unsigned kvers = kernelVersion();
 
@@ -997,7 +1001,8 @@
             if (isAtLeastKernelVersion(4, 15, 0))
                 strlcpy(req.map_name, mapNames[i].c_str(), sizeof(req.map_name));
 
-            if (isBtfSupported(type)) {
+            bool haveBtf = btf && isBtfSupported(type);
+            if (haveBtf) {
                 uint32_t kTid, vTid;
                 ret = getKeyValueTids(btf, mapNames[i].c_str(), md[i].key_size,
                                       md[i].value_size, &kTid, &vTid);
@@ -1005,17 +1010,16 @@
                 req.btf_fd = btf__fd(btf);
                 req.btf_key_type_id = kTid;
                 req.btf_value_type_id = vTid;
-                ALOGI("Create map with BTF, map: %s", mapNames[i].c_str());
-            } else {
-                ALOGI("Create map without BTF, map: %s", mapNames[i].c_str());
             }
 
             fd.reset(bpf(BPF_MAP_CREATE, req));
             saved_errno = errno;
             if (fd.ok()) {
-                ALOGD("bpf_create_map[%s] -> %d", mapNames[i].c_str(), fd.get());
+                ALOGD("bpf_create_map[%s] btf:%d -> %d",
+                      mapNames[i].c_str(), haveBtf, fd.get());
             } else {
-                ALOGE("bpf_create_map[%s] -> %d errno:%d", mapNames[i].c_str(), fd.get(), saved_errno);
+                ALOGE("bpf_create_map[%s] btf:%d -> %d errno:%d",
+                      mapNames[i].c_str(), haveBtf, fd.get(), saved_errno);
             }
         }
 
diff --git a/clatd/clatd.h b/clatd/clatd.h
index daa5ebc..11e9687 100644
--- a/clatd/clatd.h
+++ b/clatd/clatd.h
@@ -48,7 +48,7 @@
 // plus some extra just-in-case headroom, because it doesn't hurt.
 #define MAXDUMPLEN (64 + MAXMTU)
 
-#define CLATD_VERSION "1.7"
+#define CLATD_VERSION "1.8"
 
 #define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
 
diff --git a/clatd/main.c b/clatd/main.c
index bc29041..9e5710e 100644
--- a/clatd/main.c
+++ b/clatd/main.c
@@ -18,12 +18,17 @@
 
 #include <arpa/inet.h>
 #include <errno.h>
+#include <linux/audit.h>
+#include <linux/filter.h>
+#include <linux/seccomp.h>
+#include <linux/unistd.h>
 #include <netinet/in.h>
 #include <stdbool.h>
 #include <stdint.h>
 #include <stdlib.h>
 #include <string.h>
 #include <sys/personality.h>
+#include <sys/prctl.h>
 #include <sys/utsname.h>
 #include <unistd.h>
 
@@ -53,6 +58,131 @@
   printf("-w [write socket descriptor number]\n");
 }
 
+// Load the architecture identifier (AUDIT_ARCH_* constant)
+#define BPF_SECCOMP_LOAD_AUDIT_ARCH \
+	BPF_STMT(BPF_LD | BPF_W | BPF_ABS, offsetof(struct seccomp_data, arch))
+
+// Load the system call number
+#define BPF_SECCOMP_LOAD_SYSCALL_NR \
+	BPF_STMT(BPF_LD | BPF_W | BPF_ABS, offsetof(struct seccomp_data, nr))
+
+#if __BYTE_ORDER == __LITTLE_ENDIAN
+// Load the system call argument n, where n is [0..5]
+#define BPF_SECCOMP_LOAD_SYSCALL_ARG_LO32(n) \
+	BPF_STMT(BPF_LD | BPF_W | BPF_ABS, offsetof(struct seccomp_data, args[n]))
+#define BPF_SECCOMP_LOAD_SYSCALL_ARG_HI32(n) \
+	BPF_STMT(BPF_LD | BPF_W | BPF_ABS, offsetof(struct seccomp_data, args[n]) + 4)
+#else
+#error "Not a little endian architecture?"
+#endif
+
+// Allow the system call
+#define BPF_SECCOMP_ALLOW BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW)
+
+// Allow (but 'audit' log) the system call
+#define BPF_SECCOMP_LOG BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_LOG)
+
+// Reject the system call (kill thread)
+#define BPF_SECCOMP_KILL BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_KILL)
+
+// Note arguments to BPF_JUMP(opcode, operand, true_offset, false_offset)
+
+// If not equal, jump over count instructions
+#define BPF_JUMP_IF_NOT_EQUAL(v, count) \
+	BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, (v), 0, (count))
+
+// If equal, jump over count instructions
+#define BPF_JUMP_IF_EQUAL(v, count) \
+	BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, (v), (count), 0)
+
+// *TWO* instructions: compare and if not equal jump over the allow statement
+#define BPF2_SECCOMP_ALLOW_IF_EQUAL(v) \
+	BPF_JUMP_IF_NOT_EQUAL((v), 1), \
+	BPF_SECCOMP_ALLOW
+
+// *TWO* instructions: compare and if not equal jump over the log statement
+#define BPF2_SECCOMP_LOG_IF_EQUAL(v) \
+	BPF_JUMP_IF_NOT_EQUAL((v), 1), \
+	BPF_SECCOMP_LOG
+
+// *TWO* instructions: compare and if equal jump over the kill statement
+#define BPF2_SECCOMP_KILL_IF_NOT_EQUAL(v) \
+	BPF_JUMP_IF_EQUAL((v), 1), \
+	BPF_SECCOMP_KILL
+
+// Android only supports the following 5 little endian architectures
+#if defined(__aarch64__) && defined(__LP64__)
+  #define MY_AUDIT_ARCH AUDIT_ARCH_AARCH64
+#elif defined(__arm__) && defined(__ILP32__)
+  #define MY_AUDIT_ARCH AUDIT_ARCH_ARM
+#elif defined(__i386__) && defined(__ILP32__)
+  #define MY_AUDIT_ARCH AUDIT_ARCH_I386
+#elif defined(__x86_64__) && defined(__LP64__)
+  #define MY_AUDIT_ARCH AUDIT_ARCH_X86_64
+#elif defined(__riscv) && defined(__LP64__)
+  #define MY_AUDIT_ARCH AUDIT_ARCH_RISCV64
+#else
+  #error "Unknown AUDIT_ARCH_* architecture."
+#endif
+
+void enable_seccomp(void) {
+  static const struct sock_filter filter[] = {
+    BPF_SECCOMP_LOAD_AUDIT_ARCH,
+    BPF2_SECCOMP_KILL_IF_NOT_EQUAL(MY_AUDIT_ARCH),
+
+    BPF_SECCOMP_LOAD_SYSCALL_NR,                     // aarch64
+
+    // main event loop:
+    //   ppoll ( read sendmsg | recvmsg writev )
+    BPF2_SECCOMP_ALLOW_IF_EQUAL(__NR_ppoll),         // 73
+    BPF2_SECCOMP_ALLOW_IF_EQUAL(__NR_read),          // 63
+    BPF2_SECCOMP_ALLOW_IF_EQUAL(__NR_sendmsg),       // 211
+    BPF2_SECCOMP_ALLOW_IF_EQUAL(__NR_recvmsg),       // 212
+    BPF2_SECCOMP_ALLOW_IF_EQUAL(__NR_writev),        // 66
+
+    // logging: getuid writev
+    BPF2_SECCOMP_ALLOW_IF_EQUAL(__NR_getuid),        // 174
+
+    // inbound signal (SIGTERM) processing
+    BPF2_SECCOMP_ALLOW_IF_EQUAL(__NR_rt_sigreturn),  // 139
+
+    // sleep(n)
+    BPF2_SECCOMP_ALLOW_IF_EQUAL(__NR_nanosleep),     // 101
+
+    // _exit(0)
+    BPF2_SECCOMP_ALLOW_IF_EQUAL(__NR_exit_group),    // 94
+
+#if defined(__aarch64__)
+    // Pixels are aarch64 - if we break clatd functionality on them,
+    // we *will* notice on GoogleGuest WiFi network (which is ipv6 only)
+    BPF_SECCOMP_KILL,
+#else
+    // All other architectures: generate audit lines visible in dmesg and logcat
+    BPF_SECCOMP_LOG,
+#endif
+  };
+  static const struct sock_fprog prog = {
+    .len = (unsigned short)ARRAY_SIZE(filter),
+    .filter = (struct sock_filter *)filter,
+  };
+
+  // https://man7.org/linux/man-pages/man2/PR_SET_NO_NEW_PRIVS.2const.html
+  // required to allow non-privileged seccomp filter installation
+  int rv = prctl(PR_SET_NO_NEW_PRIVS, 1L, 0L, 0L, 0L);
+  if (rv) {
+    logmsg(ANDROID_LOG_FATAL, "prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) = %d [%d]", rv, errno);
+    exit(1);
+  }
+
+  // https://man7.org/linux/man-pages/man2/PR_SET_SECCOMP.2const.html
+  // but see also https://man7.org/linux/man-pages/man2/seccomp.2.html
+  rv = prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &prog, 0L, 0L);
+  if (rv) {
+    logmsg(ANDROID_LOG_FATAL, "prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &prog, 0, 0) = %d [%d]", rv, errno);
+    exit(1);
+  }
+}
+
 /* function: main
  * allocate and setup the tun device, then run the event loop
  */
@@ -241,6 +371,8 @@
   // TODO: actually perform true DAD
   send_dad(tunnel.write_fd6, &Global_Clatd_Config.ipv6_local_subnet);
 
+  enable_seccomp();  // WARNING: from this point forward very limited system calls available.
+
   event_loop(&tunnel);
 
   if (sigterm) {
@@ -255,5 +387,7 @@
     logmsg(ANDROID_LOG_INFO, "Clatd on %s %s SIGTERM", uplink_interface,
            sigterm ? "received" : "timed out waiting for");
   }
-  return 0;
+
+  // Using _exit() here avoids 4 mprotect() syscalls triggered via 'exit(0)' or 'return 0'
+  _exit(0);
 }
diff --git a/common/src/com/android/net/module/util/bpf/LocalNetAccessKey.java b/common/src/com/android/net/module/util/bpf/LocalNetAccessKey.java
index 95265b9..48e8b06 100644
--- a/common/src/com/android/net/module/util/bpf/LocalNetAccessKey.java
+++ b/common/src/com/android/net/module/util/bpf/LocalNetAccessKey.java
@@ -61,12 +61,46 @@
 
     @Override
     public String toString() {
-        return "LocalNetAccessKey{"
-                + "lpmBitlen=" + lpmBitlen
-                + ", ifIndex=" + ifIndex
-                + ", remoteAddress=" + remoteAddress
-                + ", protocol=" + protocol
-                + ", remotePort=" + remotePort
-                + "}";
+        String s = "LocalNetAccessKey{lpmBitlen=" + lpmBitlen;
+
+        long bits = lpmBitlen;
+
+        // u32 ifIndex
+        if (bits <= 0 && ifIndex != 0) s += " ??";
+        if (bits > 0 || ifIndex != 0) s += " ifIndex=" + ifIndex;
+        if (bits > 0 && bits < 32) s += "/" + bits + "[LE]";
+        bits -= 32;
+
+        // u128 remoteAddress
+        if (bits <= 0 && !remoteAddress.isAnyLocalAddress()) s += " ??";
+        if (bits > 0 || !remoteAddress.isAnyLocalAddress()) {
+            s += " remoteAddress=";
+            String ip = remoteAddress.toString();
+            if (ip.startsWith("/::ffff:")) { // technically wrong IPv4-mapped IPv6 address detection
+              s += ip.substring(8);
+              if (bits >= 96 && bits < 128) s += "/" + (bits - 96);
+            } else if (ip.startsWith("/")) {
+              s += ip.substring(1);
+              if (bits >= 0 && bits < 128) s += "/" + bits;
+            } else { // WTF, includes a hostname or what?
+              s += ip;
+            }
+        }
+        bits -= 128;
+
+        // u16 protocol
+        if (bits <= 0 && protocol != 0) s += " ??";
+        if (bits > 0 || protocol != 0) s += " protocol=" + protocol;
+        if (bits > 0 && bits < 16) s += "/" + bits + "[LE16]";
+        bits -= 16;
+
+        // be16 remotePort
+        if (bits <= 0 && remotePort != 0) s += " ??";
+        if (bits > 0 || remotePort != 0) s += " remotePort=" + remotePort;
+        if (bits > 0 && bits < 16) s += "/" + bits + "[BE16]";
+        bits -= 16;
+
+        s += "}";
+        return s;
     }
 }
diff --git a/service/ServiceConnectivityResources/res/values/config_thread.xml b/service/ServiceConnectivityResources/res/values/config_thread.xml
index 128a98f..a458c7f 100644
--- a/service/ServiceConnectivityResources/res/values/config_thread.xml
+++ b/service/ServiceConnectivityResources/res/values/config_thread.xml
@@ -30,6 +30,15 @@
     -->
     <bool name="config_thread_border_router_default_enabled">false</bool>
 
+    <!-- Whether to enable or disable setting Thread country code from the telephony, wifi, location,
+     etc. The country code could be used by the Thread co-processor for setting the fixed output
+	 power of Thread radio. If the device needs to dynamically change the max output power according
+	 to the user scenario to meet the requirement of Specific Absorption Rate (SAR), it should call
+	 the API `setChannelMaxPowers()` to change the max output power, and this configuration could be
+	 set to false to disable the Thread service from setting the Thread country code.
+    -->
+    <bool name="config_thread_country_code_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.
diff --git a/service/ServiceConnectivityResources/res/values/overlayable.xml b/service/ServiceConnectivityResources/res/values/overlayable.xml
index 5c0ba78..f6dbf6c 100644
--- a/service/ServiceConnectivityResources/res/values/overlayable.xml
+++ b/service/ServiceConnectivityResources/res/values/overlayable.xml
@@ -48,6 +48,7 @@
 
             <!-- Configuration values for ThreadNetworkService -->
             <item type="bool" name="config_thread_default_enabled" />
+            <item type="bool" name="config_thread_border_router_default_enabled" />
             <item type="bool" name="config_thread_location_use_for_country_code_enabled" />
             <item type="string" name="config_thread_vendor_name" />
             <item type="string" name="config_thread_vendor_oui" />
diff --git a/service/src/com/android/server/BpfNetMaps.java b/service/src/com/android/server/BpfNetMaps.java
index 25c0617..523ffee 100644
--- a/service/src/com/android/server/BpfNetMaps.java
+++ b/service/src/com/android/server/BpfNetMaps.java
@@ -915,10 +915,13 @@
             final InetAddress address, final int protocol, final int remotePort,
             final boolean isAllowed) {
         throwIfPre25Q2("addLocalNetAccess is not available on pre-B devices");
-        final int ifIndex;
         if (iface == null) {
-            ifIndex = 0;
-        } else {
+            Log.e(TAG, "Null iface, skip addLocalNetAccess for " + address);
+            return;
+        }
+        int ifIndex = mInterfaceTracker.getInterfaceIndex(iface);
+        if (ifIndex == 0) {
+            mInterfaceTracker.addInterface(iface);
             ifIndex = mInterfaceTracker.getInterfaceIndex(iface);
         }
         if (ifIndex == 0) {
@@ -1324,12 +1327,12 @@
                             + value.iif1 + "(" + mDeps.getIfName(value.iif1) + "), "
                             + value.iif2 + "(" + mDeps.getIfName(value.iif2) + ")");
             if (sLocalNetBlockedUidMap != null) {
-                BpfDump.dumpMap(sLocalNetAccessMap, pw, "sLocalNetAccessMap",
-                        (key, value) -> "[" + key + "]: " + value);
+                BpfDump.dumpMap(sLocalNetAccessMap, pw, "sLocalNetAccessMap (default is true meaning global)",
+                        (key, value) -> "" + key + ": " + value.val);
             }
             if (sLocalNetBlockedUidMap != null) {
                 BpfDump.dumpMap(sLocalNetBlockedUidMap, pw, "sLocalNetBlockedUidMap",
-                        (key, value) -> "[" + key + "]: " + value);
+                        (key, value) -> "" + key + ": " + value.val);
             }
             dumpDataSaverConfig(pw);
             pw.decreaseIndent();
diff --git a/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java
index dc4a35b..bfb51da 100644
--- a/service/src/com/android/server/ConnectivityService.java
+++ b/service/src/com/android/server/ConnectivityService.java
@@ -10005,10 +10005,12 @@
             // Adds dns allow rule to LocalNetAccessMap for both TCP and UDP protocol at port 53,
             // if it is a local dns (ie. it falls in the local prefix range).
             if (prefix.contains(dnsServer)) {
-                mBpfNetMaps.addLocalNetAccess(getIpv4MappedAddressBitLen(), iface, dnsServer,
+                mBpfNetMaps.addLocalNetAccess(32 + 128 + 16 + 16, iface, dnsServer,
                         IPPROTO_UDP, 53, true);
-                mBpfNetMaps.addLocalNetAccess(getIpv4MappedAddressBitLen(), iface, dnsServer,
+                mBpfNetMaps.addLocalNetAccess(32 + 128 + 16 + 16, iface, dnsServer,
                         IPPROTO_TCP, 53, true);
+                mBpfNetMaps.addLocalNetAccess(32 + 128 + 16 + 16, iface, dnsServer,
+                        IPPROTO_TCP, 853, true);  // DNS over TLS
             }
         }
     }
@@ -10027,25 +10029,17 @@
             // Removes dns allow rule from LocalNetAccessMap for both TCP and UDP protocol
             // at port 53, if it is a local dns (ie. it falls in the prefix range).
             if (prefix.contains(dnsServer)) {
-                mBpfNetMaps.removeLocalNetAccess(getIpv4MappedAddressBitLen(), iface, dnsServer,
+                mBpfNetMaps.removeLocalNetAccess(32 + 128 + 16 + 16, iface, dnsServer,
                         IPPROTO_UDP, 53);
-                mBpfNetMaps.removeLocalNetAccess(getIpv4MappedAddressBitLen(), iface, dnsServer,
+                mBpfNetMaps.removeLocalNetAccess(32 + 128 + 16 + 16, iface, dnsServer,
                         IPPROTO_TCP, 53);
+                mBpfNetMaps.removeLocalNetAccess(32 + 128 + 16 + 16, iface, dnsServer,
+                        IPPROTO_TCP, 853);  // DNS over TLS
             }
         }
     }
 
     /**
-     * Returns total bit length of an Ipv4 mapped address.
-     */
-    private int getIpv4MappedAddressBitLen() {
-        final int ifaceLen = 32; // bit length of interface
-        final int inetAddressLen = 32 + 96; // length of ipv4 mapped addresses
-        final int portProtocolLen = 32;  //16 for port + 16 for protocol;
-        return ifaceLen + inetAddressLen + portProtocolLen;
-    }
-
-    /**
      * Have netd update routes from oldLp to newLp.
      * @return true if routes changed between oldLp and newLp
      */
diff --git a/service/src/com/android/server/connectivity/DscpPolicyTracker.java b/service/src/com/android/server/connectivity/DscpPolicyTracker.java
index 9c2b9e8..857d705 100644
--- a/service/src/com/android/server/connectivity/DscpPolicyTracker.java
+++ b/service/src/com/android/server/connectivity/DscpPolicyTracker.java
@@ -233,6 +233,11 @@
      */
     public void addDscpPolicy(NetworkAgentInfo nai, DscpPolicy policy) {
         String iface = nai.linkProperties.getInterfaceName();
+        if (null == iface) {
+            Log.e(TAG, "DSCP policies are not supported on null interfaces.");
+            sendStatus(nai, policy.getPolicyId(), DSCP_POLICY_STATUS_REQUEST_DECLINED);
+            return;
+        }
         if (!isEthernet(iface)) {
             Log.e(TAG, "DSCP policies are not supported on raw IP interfaces.");
             sendStatus(nai, policy.getPolicyId(), DSCP_POLICY_STATUS_REQUEST_DECLINED);
diff --git a/staticlibs/device/com/android/net/module/util/TcUtils.java b/staticlibs/device/com/android/net/module/util/TcUtils.java
index a6b222f..eb119c8 100644
--- a/staticlibs/device/com/android/net/module/util/TcUtils.java
+++ b/staticlibs/device/com/android/net/module/util/TcUtils.java
@@ -16,6 +16,8 @@
 
 package com.android.net.module.util;
 
+import androidx.annotation.NonNull;
+
 import java.io.IOException;
 
 /**
@@ -33,7 +35,7 @@
      * @return true if the interface uses an ethernet L2 header.
      * @throws IOException
      */
-    public static native boolean isEthernet(String iface) throws IOException;
+    public static native boolean isEthernet(@NonNull String iface) throws IOException;
 
     /**
      * Attach a tc bpf filter.
diff --git a/staticlibs/framework/com/android/net/module/util/SdkUtil.java b/staticlibs/framework/com/android/net/module/util/SdkUtil.java
index 5006ba9..63558dd 100644
--- a/staticlibs/framework/com/android/net/module/util/SdkUtil.java
+++ b/staticlibs/framework/com/android/net/module/util/SdkUtil.java
@@ -16,7 +16,10 @@
 
 package com.android.net.module.util;
 
+import static android.os.Build.VERSION.SDK_INT;
+
 import android.annotation.Nullable;
+import android.os.Build;
 
 /**
  * Utilities to deal with multiple SDKs in a single mainline module.
@@ -46,4 +49,9 @@
             this.value = value;
         }
     }
+
+    /** Checks if the device is running on a release version of Android Baklava or newer */
+    public static boolean isAtLeast25Q2() {
+        return SDK_INT >= 36  || (SDK_INT == 35 && "Baklava".equals(Build.VERSION.CODENAME));
+    }
 }
diff --git a/staticlibs/native/bpfmapjni/com_android_net_module_util_TcUtils.cpp b/staticlibs/native/bpfmapjni/com_android_net_module_util_TcUtils.cpp
index 2a587b6..22b084c 100644
--- a/staticlibs/native/bpfmapjni/com_android_net_module_util_TcUtils.cpp
+++ b/staticlibs/native/bpfmapjni/com_android_net_module_util_TcUtils.cpp
@@ -32,6 +32,10 @@
 static jboolean com_android_net_module_util_TcUtils_isEthernet(JNIEnv *env,
                                                                jclass clazz,
                                                                jstring iface) {
+  if (nullptr == iface) {
+    jniThrowNullPointerException(env, "iface is null");
+    return false;
+  }
   ScopedUtfChars interface(env, iface);
   bool result = false;
   int error = isEthernet(interface.c_str(), result);
diff --git a/staticlibs/testutils/host/python/apf_test_base.py b/staticlibs/testutils/host/python/apf_test_base.py
index 33b3838..6a62e21 100644
--- a/staticlibs/testutils/host/python/apf_test_base.py
+++ b/staticlibs/testutils/host/python/apf_test_base.py
@@ -70,6 +70,9 @@
     # Enable doze mode to activate APF.
     adb_utils.set_doze_mode(self.clientDevice, True)
 
+    # wait for APF to become active.
+    time.sleep(3)
+
   def teardown_class(self):
     adb_utils.set_doze_mode(self.clientDevice, False)
     tether_utils.cleanup_tethering_for_upstream_type(
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/TetheringTest.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/TetheringTest.java
index a1cf968..65844a3 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/TetheringTest.java
+++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/TetheringTest.java
@@ -23,6 +23,7 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
+import static org.junit.Assume.assumeTrue;
 
 import android.content.Context;
 import android.net.TetheringInterface;
@@ -31,6 +32,8 @@
 import android.net.wifi.SoftApConfiguration;
 import android.net.wifi.WifiSsid;
 
+import com.android.modules.utils.build.SdkLevel;
+
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
@@ -64,6 +67,8 @@
      */
     @Test
     public void testSoftApConfigurationRedactedForOtherUids() throws Exception {
+        assumeTrue(SdkLevel.isAtLeastB());
+
         mTetheringEventCallback.assumeWifiTetheringSupported(
                 getInstrumentation().getTargetContext());
         SoftApConfiguration softApConfig = new SoftApConfiguration.Builder()
diff --git a/tests/cts/multidevices/apfv6_test.py b/tests/cts/multidevices/apfv6_test.py
index fb45f4a..ee2368d 100644
--- a/tests/cts/multidevices/apfv6_test.py
+++ b/tests/cts/multidevices/apfv6_test.py
@@ -14,7 +14,20 @@
 
 from mobly import asserts
 from scapy.layers.inet import IP, ICMP, IPOption_Router_Alert
-from scapy.layers.inet6 import IPv6, ICMPv6EchoRequest, ICMPv6EchoReply
+from scapy.layers.inet6 import (
+    IPv6,
+    IPv6ExtHdrHopByHop,
+    ICMPv6EchoRequest,
+    ICMPv6EchoReply,
+    ICMPv6MLQuery2,
+    ICMPv6MLReport2,
+    ICMPv6MLDMultAddrRec,
+    ICMPv6NDOptSrcLLAddr,
+    ICMPv6NDOptDstLLAddr,
+    ICMPv6ND_NS,
+    ICMPv6ND_NA,
+    RouterAlert
+)
 from scapy.layers.l2 import Ether
 from scapy.contrib.igmpv3 import IGMPv3, IGMPv3mq, IGMPv3mr, IGMPv3gr
 from net_tests_utils.host.python import apf_test_base, apf_utils, adb_utils, assert_utils, packet_utils
@@ -87,6 +100,24 @@
             arp_request, "DROPPED_ARP_REQUEST_REPLIED", arp_reply
         )
 
+    def test_non_dad_ipv6_neighbor_solicitation_offload(self):
+        eth = Ether(src=self.server_mac_address, dst=self.client_mac_address)
+        ip = IPv6(src=self.server_ipv6_addresses[0], dst=self.client_ipv6_addresses[0])
+        icmpv6 = ICMPv6ND_NS(tgt=self.client_ipv6_addresses[0])
+        opt = ICMPv6NDOptSrcLLAddr(lladdr=self.server_mac_address)
+        neighbor_solicitation = bytes(eth/ip/icmpv6/opt).hex()
+
+        eth = Ether(src=self.client_mac_address, dst=self.server_mac_address)
+        ip = IPv6(src=self.client_ipv6_addresses[0], dst=self.server_ipv6_addresses[0])
+        icmpv6 = ICMPv6ND_NA(tgt=self.client_ipv6_addresses[0], R=1, S=1, O=1)
+        opt = ICMPv6NDOptDstLLAddr(lladdr=self.client_mac_address)
+        expected_neighbor_advertisement = bytes(eth/ip/icmpv6/opt).hex()
+        self.send_packet_and_expect_reply_received(
+            neighbor_solicitation,
+            "DROPPED_IPV6_NS_REPLIED_NON_DAD",
+            expected_neighbor_advertisement
+        )
+
     @apf_utils.at_least_B()
     def test_ipv4_icmp_echo_request_offload(self):
         eth = Ether(src=self.server_mac_address, dst=self.client_mac_address)
@@ -103,6 +134,7 @@
         )
 
     @apf_utils.at_least_B()
+    @apf_utils.apf_ram_at_least(3000)
     def test_ipv6_icmp_echo_request_offload(self):
         eth = Ether(src=self.server_mac_address, dst=self.client_mac_address)
         ip = IPv6(src=self.server_ipv6_addresses[0], dst=self.client_ipv6_addresses[0])
@@ -161,3 +193,28 @@
                 self.clientDevice,
                 f'ip addr del {addr}/32 dev {self.client_iface_name}'
             )
+
+    @apf_utils.at_least_B()
+    @apf_utils.apf_ram_at_least(3000)
+    def test_mldv2_general_query_offload(self):
+        ether = Ether(src=self.server_mac_address, dst='33:33:00:00:00:01')
+        ip = IPv6(src=self.server_ipv6_addresses[0], dst='ff02::1', hlim=1)
+        hopOpts = IPv6ExtHdrHopByHop(options=[RouterAlert(otype=5)])
+        mld = ICMPv6MLQuery2()
+        mldv2_general_query = bytes(ether/ip/hopOpts/mld).hex()
+
+        ether = Ether(src=self.client_mac_address, dst='33:33:00:00:00:16')
+        ip = IPv6(src=self.client_ipv6_addresses[0], dst='ff02::16', hlim=1)
+
+        mcast_addrs = apf_utils.get_exclude_all_host_ipv6_multicast_addresses(
+            self.clientDevice, self.client_iface_name
+        )
+
+        mld_records = []
+        for addr in mcast_addrs:
+            mld_records.append(ICMPv6MLDMultAddrRec(dst=addr, rtype=2))
+        mld = ICMPv6MLReport2(records=mld_records)
+        expected_mldv2_report = bytes(ether/ip/hopOpts/mld).hex()
+        self.send_packet_and_expect_reply_received(
+            mldv2_general_query, "DROPPED_IPV6_MLD_V2_GENERAL_QUERY_REPLIED", expected_mldv2_report
+        )
diff --git a/tests/cts/net/src/android/net/cts/DscpPolicyTest.kt b/tests/cts/net/src/android/net/cts/DscpPolicyTest.kt
index ceccf0b..df4dab5 100644
--- a/tests/cts/net/src/android/net/cts/DscpPolicyTest.kt
+++ b/tests/cts/net/src/android/net/cts/DscpPolicyTest.kt
@@ -757,6 +757,25 @@
         assertEquals(IPPROTO_UDP, policy2.protocol)
         assertParcelingIsLossless(policy2)
     }
+
+    @Test
+    fun testSendDscpPolicyWithoutInterfaceName() {
+        val nc = NetworkCapabilities().apply {
+            addTransportType(TRANSPORT_TEST)
+        }
+        val agent = TestableNetworkAgent(
+                realContext,
+                handlerThread.looper,
+                nc,
+                LinkProperties() /* note: no interface name */,
+                NetworkAgentConfig.Builder().build()
+        )
+        agentsToCleanUp.add(agent)
+        runAsShell(MANAGE_TEST_NETWORKS) { agent.register() }
+        // Without the fix, this will crash the system with SIGSEGV.
+        agent.sendAddDscpPolicy(DscpPolicy.Builder(1, 1).build())
+        agent.expectCallback<OnDscpPolicyStatusUpdated>()
+    }
 }
 
 private fun ByteBuffer.readAsArray(): ByteArray {
diff --git a/tests/unit/java/com/android/server/connectivityservice/CSKeepConnectedTest.kt b/tests/unit/java/com/android/server/connectivityservice/CSKeepConnectedTest.kt
index 94c68c0..4aeae19 100644
--- a/tests/unit/java/com/android/server/connectivityservice/CSKeepConnectedTest.kt
+++ b/tests/unit/java/com/android/server/connectivityservice/CSKeepConnectedTest.kt
@@ -45,20 +45,29 @@
                 .addTransportType(TRANSPORT_WIFI)
                 .addCapability(NET_CAPABILITY_LOCAL_NETWORK)
                 .build()
-        val keepConnectedAgent = Agent(nc = nc, score = FromS(NetworkScore.Builder()
-                .setKeepConnectedReason(KEEP_CONNECTED_LOCAL_NETWORK)
-                .build()),
-                lnc = FromS(LocalNetworkConfig.Builder().build()))
-        val dontKeepConnectedAgent = Agent(nc = nc,
-                lnc = FromS(LocalNetworkConfig.Builder().build()))
+        val keepConnectedAgent = Agent(
+            nc = nc,
+            score = FromS(
+                    NetworkScore.Builder()
+                            .setKeepConnectedReason(KEEP_CONNECTED_LOCAL_NETWORK)
+                            .build()
+            ),
+            lnc = FromS(LocalNetworkConfig.Builder().build())
+        )
+        val dontKeepConnectedAgent = Agent(
+            nc = nc,
+            lnc = FromS(LocalNetworkConfig.Builder().build())
+        )
         doTestKeepConnected(keepConnectedAgent, dontKeepConnectedAgent)
     }
 
     @Test
     fun testKeepConnectedForTest() {
-        val keepAgent = Agent(score = FromS(NetworkScore.Builder()
-                .setKeepConnectedReason(KEEP_CONNECTED_FOR_TEST)
-                .build()))
+        val keepAgent = Agent(score = FromS(
+                NetworkScore.Builder()
+                        .setKeepConnectedReason(KEEP_CONNECTED_FOR_TEST)
+                        .build()
+        ))
         val dontKeepAgent = Agent()
         doTestKeepConnected(keepAgent, dontKeepAgent)
     }
diff --git a/tests/unit/java/com/android/server/connectivityservice/CSLocalAgentCreationTests.kt b/tests/unit/java/com/android/server/connectivityservice/CSLocalAgentCreationTests.kt
index 16a30aa..6805d9a 100644
--- a/tests/unit/java/com/android/server/connectivityservice/CSLocalAgentCreationTests.kt
+++ b/tests/unit/java/com/android/server/connectivityservice/CSLocalAgentCreationTests.kt
@@ -61,7 +61,8 @@
     data class TestParams(
             val sdkLevel: Int,
             val isTv: Boolean = false,
-            val addLocalNetCapToRequest: Boolean = true)
+            val addLocalNetCapToRequest: Boolean = true
+    )
 
     companion object {
         @JvmStatic
@@ -81,8 +82,14 @@
     }
 
     private fun makeNativeNetworkConfigLocal(netId: Int, permission: Int) =
-            NativeNetworkConfig(netId, NativeNetworkType.PHYSICAL_LOCAL, permission,
-                    false /* secure */, VpnManager.TYPE_VPN_NONE, false /* excludeLocalRoutes */)
+            NativeNetworkConfig(
+                    netId,
+                    NativeNetworkType.PHYSICAL_LOCAL,
+                    permission,
+                    false /* secure */,
+                    VpnManager.TYPE_VPN_NONE,
+                    false /* excludeLocalRoutes */
+            )
 
     @Test
     fun testLocalAgents() {
@@ -99,8 +106,8 @@
             addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
             addCapability(NET_CAPABILITY_LOCAL_NETWORK)
         }.build()
-        val localAgent = if (params.sdkLevel >= VERSION_V
-                || params.sdkLevel == VERSION_U && params.isTv) {
+        val localAgent = if (params.sdkLevel >= VERSION_V ||
+                params.sdkLevel == VERSION_U && params.isTv) {
             Agent(nc = ncTemplate, score = keepConnectedScore(), lnc = defaultLnc())
         } else {
             assertFailsWith<IllegalArgumentException> { Agent(nc = ncTemplate, lnc = defaultLnc()) }
@@ -109,7 +116,8 @@
         }
         localAgent.connect()
         netdInOrder.verify(netd).networkCreate(
-                makeNativeNetworkConfigLocal(localAgent.network.netId, INetd.PERMISSION_NONE))
+                makeNativeNetworkConfigLocal(localAgent.network.netId, INetd.PERMISSION_NONE)
+        )
         if (params.addLocalNetCapToRequest) {
             assertEquals(localAgent.network, allNetworksCb.expect<Available>().network)
         } else {
@@ -123,10 +131,12 @@
     @Test
     fun testBadAgents() {
         assertFailsWith<IllegalArgumentException> {
-            Agent(nc = NetworkCapabilities.Builder()
-                    .addCapability(NET_CAPABILITY_LOCAL_NETWORK)
-                    .build(),
-                    lnc = null)
+            Agent(
+                    nc = NetworkCapabilities.Builder()
+                            .addCapability(NET_CAPABILITY_LOCAL_NETWORK)
+                            .build(),
+                    lnc = null
+            )
         }
         assertFailsWith<IllegalArgumentException> {
             Agent(nc = NetworkCapabilities.Builder().build(), lnc = defaultLnc())
diff --git a/tests/unit/java/com/android/server/connectivityservice/CSLocalAgentTests.kt b/tests/unit/java/com/android/server/connectivityservice/CSLocalAgentTests.kt
index 3583f84..d835155 100644
--- a/tests/unit/java/com/android/server/connectivityservice/CSLocalAgentTests.kt
+++ b/tests/unit/java/com/android/server/connectivityservice/CSLocalAgentTests.kt
@@ -111,14 +111,18 @@
         deps.setBuildSdk(VERSION_V)
 
         assertFailsWith<IllegalArgumentException> {
-            Agent(nc = NetworkCapabilities.Builder()
-                    .addCapability(NET_CAPABILITY_LOCAL_NETWORK)
-                    .build(),
-                    lnc = null)
+            Agent(
+                    nc = NetworkCapabilities.Builder()
+                            .addCapability(NET_CAPABILITY_LOCAL_NETWORK)
+                            .build(),
+                    lnc = null
+            )
         }
         assertFailsWith<IllegalArgumentException> {
-            Agent(nc = NetworkCapabilities.Builder().build(),
-                    lnc = FromS(LocalNetworkConfig.Builder().build()))
+            Agent(
+                    nc = NetworkCapabilities.Builder().build(),
+                    lnc = FromS(LocalNetworkConfig.Builder().build())
+            )
         }
     }
 
@@ -127,27 +131,31 @@
         deps.setBuildSdk(VERSION_V)
 
         val cb = TestableNetworkCallback()
-        cm.requestNetwork(NetworkRequest.Builder()
-                .clearCapabilities()
-                .build(),
-                cb)
-        val agent = Agent(nc = NetworkCapabilities.Builder()
-                .addCapability(NET_CAPABILITY_LOCAL_NETWORK)
-                .build(),
-                lnc = FromS(LocalNetworkConfig.Builder().build()))
+        cm.requestNetwork(
+                NetworkRequest.Builder()
+                        .clearCapabilities()
+                        .build(),
+                cb
+        )
+        val agent = Agent(
+                nc = NetworkCapabilities.Builder()
+                        .addCapability(NET_CAPABILITY_LOCAL_NETWORK)
+                        .build(),
+                lnc = FromS(LocalNetworkConfig.Builder().build())
+        )
         agent.connect()
         cb.expectAvailableCallbacks(agent.network, validated = false)
         agent.sendNetworkCapabilities(NetworkCapabilities.Builder().build())
         cb.expect<Lost>(agent.network)
 
-        val agent2 = Agent(nc = NetworkCapabilities.Builder()
-                .build(),
-                lnc = null)
+        val agent2 = Agent(nc = NetworkCapabilities.Builder().build(), lnc = null)
         agent2.connect()
         cb.expectAvailableCallbacks(agent2.network, validated = false)
-        agent2.sendNetworkCapabilities(NetworkCapabilities.Builder()
-                .addCapability(NET_CAPABILITY_LOCAL_NETWORK)
-                .build())
+        agent2.sendNetworkCapabilities(
+                NetworkCapabilities.Builder()
+                        .addCapability(NET_CAPABILITY_LOCAL_NETWORK)
+                        .build()
+        )
         cb.expect<Lost>(agent2.network)
     }
 
@@ -156,10 +164,12 @@
         deps.setBuildSdk(VERSION_V)
 
         val cb = TestableNetworkCallback()
-        cm.requestNetwork(NetworkRequest.Builder()
-                .addCapability(NET_CAPABILITY_LOCAL_NETWORK)
-                .build(),
-                cb)
+        cm.requestNetwork(
+                NetworkRequest.Builder()
+                        .addCapability(NET_CAPABILITY_LOCAL_NETWORK)
+                        .build(),
+                cb
+        )
 
         // Set up a local agent that should forward its traffic to the best DUN upstream.
         val localAgent = Agent(
@@ -171,15 +181,20 @@
 
         cb.expectAvailableCallbacks(localAgent.network, validated = false)
 
-        val wifiAgent = Agent(score = keepScore(), lp = lp("wifi0"),
-                nc = nc(TRANSPORT_WIFI, NET_CAPABILITY_INTERNET))
+        val wifiAgent = Agent(
+                score = keepScore(),
+                lp = lp("wifi0"),
+                nc = nc(TRANSPORT_WIFI, NET_CAPABILITY_INTERNET)
+        )
         wifiAgent.connect()
 
         val newLnc = LocalNetworkConfig.Builder()
-                .setUpstreamSelector(NetworkRequest.Builder()
-                        .addTransportType(TRANSPORT_WIFI)
-                        .addForbiddenCapability(NET_CAPABILITY_LOCAL_NETWORK)
-                        .build())
+                .setUpstreamSelector(
+                        NetworkRequest.Builder()
+                                .addTransportType(TRANSPORT_WIFI)
+                                .addForbiddenCapability(NET_CAPABILITY_LOCAL_NETWORK)
+                                .build()
+                )
                 .build()
         localAgent.sendLocalNetworkConfig(newLnc)
 
@@ -207,21 +222,29 @@
                 nc = nc(TRANSPORT_THREAD, NET_CAPABILITY_LOCAL_NETWORK),
                 lp = lp(name),
                 lnc = localNetworkConfig,
-                score = FromS(NetworkScore.Builder()
+                score = FromS(
+                    NetworkScore.Builder()
                         .setKeepConnectedReason(KEEP_CONNECTED_LOCAL_NETWORK)
-                        .build())
+                        .build()
+                )
         )
         return localAgent
     }
 
     private fun createWifiAgent(name: String): CSAgentWrapper {
-        return Agent(score = keepScore(), lp = lp(name),
-                nc = nc(TRANSPORT_WIFI, NET_CAPABILITY_INTERNET))
+        return Agent(
+                score = keepScore(),
+                lp = lp(name),
+                nc = nc(TRANSPORT_WIFI, NET_CAPABILITY_INTERNET)
+        )
     }
 
     private fun createCellAgent(name: String): CSAgentWrapper {
-        return Agent(score = keepScore(), lp = lp(name),
-                nc = nc(TRANSPORT_CELLULAR, NET_CAPABILITY_INTERNET))
+        return Agent(
+                score = keepScore(),
+                lp = lp(name),
+                nc = nc(TRANSPORT_CELLULAR, NET_CAPABILITY_INTERNET)
+        )
     }
 
     private fun sendLocalNetworkConfig(
@@ -245,11 +268,12 @@
         cm.registerNetworkCallback(NetworkRequest.Builder().clearCapabilities().build(), cb)
         val inOrder = inOrder(multicastRoutingCoordinatorService)
 
-        val lnc = FromS(LocalNetworkConfig.Builder()
-                .setUpstreamSelector(upstreamSelectorWifi)
-                .setUpstreamMulticastRoutingConfig(multicastRoutingConfigMinScope)
-                .setDownstreamMulticastRoutingConfig(multicastRoutingConfigSelected)
-                .build()
+        val lnc = FromS(
+                LocalNetworkConfig.Builder()
+                        .setUpstreamSelector(upstreamSelectorWifi)
+                        .setUpstreamMulticastRoutingConfig(multicastRoutingConfigMinScope)
+                        .setDownstreamMulticastRoutingConfig(multicastRoutingConfigSelected)
+                        .build()
         )
         val localAgent = createLocalAgent("local0", lnc)
         localAgent.connect()
@@ -264,9 +288,15 @@
         }
 
         inOrder.verify(multicastRoutingCoordinatorService).applyMulticastRoutingConfig(
-                "local0", "wifi0", multicastRoutingConfigMinScope)
+                "local0",
+                "wifi0",
+                multicastRoutingConfigMinScope
+        )
         inOrder.verify(multicastRoutingCoordinatorService).applyMulticastRoutingConfig(
-                "wifi0", "local0", multicastRoutingConfigSelected)
+                "wifi0",
+                "local0",
+                multicastRoutingConfigSelected
+        )
 
         wifiAgent.disconnect()
 
@@ -282,11 +312,12 @@
     fun testMulticastRoutingConfig_2LocalNetworks() {
         deps.setBuildSdk(VERSION_V)
         val inOrder = inOrder(multicastRoutingCoordinatorService)
-        val lnc = FromS(LocalNetworkConfig.Builder()
-                .setUpstreamSelector(upstreamSelectorWifi)
-                .setUpstreamMulticastRoutingConfig(multicastRoutingConfigMinScope)
-                .setDownstreamMulticastRoutingConfig(multicastRoutingConfigSelected)
-                .build()
+        val lnc = FromS(
+                LocalNetworkConfig.Builder()
+                        .setUpstreamSelector(upstreamSelectorWifi)
+                        .setUpstreamMulticastRoutingConfig(multicastRoutingConfigMinScope)
+                        .setDownstreamMulticastRoutingConfig(multicastRoutingConfigSelected)
+                        .build()
         )
         val localAgent0 = createLocalAgent("local0", lnc)
         localAgent0.connect()
@@ -296,18 +327,30 @@
         waitForIdle()
 
         inOrder.verify(multicastRoutingCoordinatorService).applyMulticastRoutingConfig(
-                "local0", "wifi0", multicastRoutingConfigMinScope)
+                "local0",
+                "wifi0",
+                multicastRoutingConfigMinScope
+        )
         inOrder.verify(multicastRoutingCoordinatorService).applyMulticastRoutingConfig(
-                "wifi0", "local0", multicastRoutingConfigSelected)
+                "wifi0",
+                "local0",
+                multicastRoutingConfigSelected
+        )
 
         val localAgent1 = createLocalAgent("local1", lnc)
         localAgent1.connect()
         waitForIdle()
 
         inOrder.verify(multicastRoutingCoordinatorService).applyMulticastRoutingConfig(
-                "local1", "wifi0", multicastRoutingConfigMinScope)
+                "local1",
+                "wifi0",
+                multicastRoutingConfigMinScope
+        )
         inOrder.verify(multicastRoutingCoordinatorService).applyMulticastRoutingConfig(
-                "wifi0", "local1", multicastRoutingConfigSelected)
+                "wifi0",
+                "local1",
+                multicastRoutingConfigSelected
+        )
 
         localAgent0.disconnect()
         localAgent1.disconnect()
@@ -318,15 +361,19 @@
     fun testMulticastRoutingConfig_UpstreamNetworkCellToWifi() {
         deps.setBuildSdk(VERSION_V)
         val cb = TestableNetworkCallback()
-        cm.registerNetworkCallback(NetworkRequest.Builder().clearCapabilities()
+        cm.registerNetworkCallback(
+                NetworkRequest.Builder().clearCapabilities()
                         .addCapability(NET_CAPABILITY_LOCAL_NETWORK)
-                        .build(), cb)
+                        .build(),
+            cb
+        )
         val inOrder = inOrder(multicastRoutingCoordinatorService)
-        val lnc = FromS(LocalNetworkConfig.Builder()
-                .setUpstreamSelector(upstreamSelectorAny)
-                .setUpstreamMulticastRoutingConfig(multicastRoutingConfigMinScope)
-                .setDownstreamMulticastRoutingConfig(multicastRoutingConfigSelected)
-                .build()
+        val lnc = FromS(
+                LocalNetworkConfig.Builder()
+                        .setUpstreamSelector(upstreamSelectorAny)
+                        .setUpstreamMulticastRoutingConfig(multicastRoutingConfigMinScope)
+                        .setDownstreamMulticastRoutingConfig(multicastRoutingConfigSelected)
+                        .build()
         )
         val localAgent = createLocalAgent("local0", lnc)
         val wifiAgent = createWifiAgent("wifi0")
@@ -341,9 +388,15 @@
         }
 
         inOrder.verify(multicastRoutingCoordinatorService).applyMulticastRoutingConfig(
-                "local0", "cell0", multicastRoutingConfigMinScope)
+                "local0",
+                "cell0",
+                multicastRoutingConfigMinScope
+        )
         inOrder.verify(multicastRoutingCoordinatorService).applyMulticastRoutingConfig(
-                "cell0", "local0", multicastRoutingConfigSelected)
+                "cell0",
+                "local0",
+                multicastRoutingConfigSelected
+        )
 
         wifiAgent.connect()
 
@@ -357,9 +410,15 @@
         inOrder.verify(multicastRoutingCoordinatorService)
                 .applyMulticastRoutingConfig("cell0", "local0", CONFIG_FORWARD_NONE)
         inOrder.verify(multicastRoutingCoordinatorService).applyMulticastRoutingConfig(
-                "local0", "wifi0", multicastRoutingConfigMinScope)
+                "local0",
+                "wifi0",
+                multicastRoutingConfigMinScope
+        )
         inOrder.verify(multicastRoutingCoordinatorService).applyMulticastRoutingConfig(
-                "wifi0", "local0", multicastRoutingConfigSelected)
+                "wifi0",
+                "local0",
+                multicastRoutingConfigSelected
+        )
 
         localAgent.disconnect()
         cellAgent.disconnect()
@@ -370,15 +429,19 @@
     fun testMulticastRoutingConfig_UpstreamSelectorCellToWifi() {
         deps.setBuildSdk(VERSION_V)
         val cb = TestableNetworkCallback()
-        cm.registerNetworkCallback(NetworkRequest.Builder().clearCapabilities()
+        cm.registerNetworkCallback(
+                NetworkRequest.Builder().clearCapabilities()
                         .addCapability(NET_CAPABILITY_LOCAL_NETWORK)
-                        .build(), cb)
+                        .build(),
+                cb
+        )
         val inOrder = inOrder(multicastRoutingCoordinatorService)
-        val lnc = FromS(LocalNetworkConfig.Builder()
-                .setUpstreamSelector(upstreamSelectorCell)
-                .setUpstreamMulticastRoutingConfig(multicastRoutingConfigMinScope)
-                .setDownstreamMulticastRoutingConfig(multicastRoutingConfigSelected)
-                .build()
+        val lnc = FromS(
+                LocalNetworkConfig.Builder()
+                        .setUpstreamSelector(upstreamSelectorCell)
+                        .setUpstreamMulticastRoutingConfig(multicastRoutingConfigMinScope)
+                        .setDownstreamMulticastRoutingConfig(multicastRoutingConfigSelected)
+                        .build()
         )
         val localAgent = createLocalAgent("local0", lnc)
         val wifiAgent = createWifiAgent("wifi0")
@@ -393,12 +456,22 @@
         }
 
         inOrder.verify(multicastRoutingCoordinatorService).applyMulticastRoutingConfig(
-                "local0", "cell0", multicastRoutingConfigMinScope)
+                "local0",
+                "cell0",
+                multicastRoutingConfigMinScope
+        )
         inOrder.verify(multicastRoutingCoordinatorService).applyMulticastRoutingConfig(
-                "cell0", "local0", multicastRoutingConfigSelected)
+                "cell0",
+                "local0",
+                multicastRoutingConfigSelected
+        )
 
-        sendLocalNetworkConfig(localAgent, upstreamSelectorWifi, multicastRoutingConfigMinScope,
-                multicastRoutingConfigSelected)
+        sendLocalNetworkConfig(
+                localAgent,
+                upstreamSelectorWifi,
+                multicastRoutingConfigMinScope,
+                multicastRoutingConfigSelected
+        )
         cb.expect<LocalInfoChanged>(localAgent.network) {
             it.info.upstreamNetwork == wifiAgent.network
         }
@@ -409,9 +482,15 @@
         inOrder.verify(multicastRoutingCoordinatorService)
                 .applyMulticastRoutingConfig("cell0", "local0", CONFIG_FORWARD_NONE)
         inOrder.verify(multicastRoutingCoordinatorService).applyMulticastRoutingConfig(
-                "local0", "wifi0", multicastRoutingConfigMinScope)
+                "local0",
+                "wifi0",
+                multicastRoutingConfigMinScope
+        )
         inOrder.verify(multicastRoutingCoordinatorService).applyMulticastRoutingConfig(
-                "wifi0", "local0", multicastRoutingConfigSelected)
+                "wifi0",
+                "local0",
+                multicastRoutingConfigSelected
+        )
 
         localAgent.disconnect()
         cellAgent.disconnect()
@@ -422,15 +501,19 @@
     fun testMulticastRoutingConfig_UpstreamSelectorWifiToNull() {
         deps.setBuildSdk(VERSION_V)
         val cb = TestableNetworkCallback()
-        cm.registerNetworkCallback(NetworkRequest.Builder().clearCapabilities()
+        cm.registerNetworkCallback(
+                NetworkRequest.Builder().clearCapabilities()
                         .addCapability(NET_CAPABILITY_LOCAL_NETWORK)
-                        .build(), cb)
+                        .build(),
+                cb
+        )
         val inOrder = inOrder(multicastRoutingCoordinatorService)
-        val lnc = FromS(LocalNetworkConfig.Builder()
-                .setUpstreamSelector(upstreamSelectorWifi)
-                .setUpstreamMulticastRoutingConfig(multicastRoutingConfigMinScope)
-                .setDownstreamMulticastRoutingConfig(multicastRoutingConfigSelected)
-                .build()
+        val lnc = FromS(
+                LocalNetworkConfig.Builder()
+                        .setUpstreamSelector(upstreamSelectorWifi)
+                        .setUpstreamMulticastRoutingConfig(multicastRoutingConfigMinScope)
+                        .setDownstreamMulticastRoutingConfig(multicastRoutingConfigSelected)
+                        .build()
         )
         val localAgent = createLocalAgent("local0", lnc)
         localAgent.connect()
@@ -442,12 +525,22 @@
         }
 
         inOrder.verify(multicastRoutingCoordinatorService).applyMulticastRoutingConfig(
-                "local0", "wifi0", multicastRoutingConfigMinScope)
+                "local0",
+                "wifi0",
+                multicastRoutingConfigMinScope
+        )
         inOrder.verify(multicastRoutingCoordinatorService).applyMulticastRoutingConfig(
-                "wifi0", "local0", multicastRoutingConfigSelected)
+                "wifi0",
+                "local0",
+                multicastRoutingConfigSelected
+        )
 
-        sendLocalNetworkConfig(localAgent, null, multicastRoutingConfigMinScope,
-                multicastRoutingConfigSelected)
+        sendLocalNetworkConfig(
+                localAgent,
+                null,
+                multicastRoutingConfigMinScope,
+                multicastRoutingConfigSelected
+        )
         cb.expect<LocalInfoChanged>(localAgent.network) {
             it.info.upstreamNetwork == null
         }
@@ -458,9 +551,15 @@
         inOrder.verify(multicastRoutingCoordinatorService)
                 .applyMulticastRoutingConfig("wifi0", "local0", CONFIG_FORWARD_NONE)
         inOrder.verify(multicastRoutingCoordinatorService, never()).applyMulticastRoutingConfig(
-                eq("local0"), any(), eq(multicastRoutingConfigMinScope))
+                eq("local0"),
+                any(),
+                eq(multicastRoutingConfigMinScope)
+        )
         inOrder.verify(multicastRoutingCoordinatorService, never()).applyMulticastRoutingConfig(
-                any(), eq("local0"), eq(multicastRoutingConfigSelected))
+                any(),
+                eq("local0"),
+                eq(multicastRoutingConfigSelected)
+        )
 
         localAgent.disconnect()
         wifiAgent.disconnect()
@@ -482,23 +581,30 @@
         cm.registerNetworkCallback(NetworkRequest.Builder().clearCapabilities().build(), cb)
 
         // Set up a local agent that should forward its traffic to the best wifi upstream.
-        val localAgent = Agent(nc = nc(TRANSPORT_WIFI, NET_CAPABILITY_LOCAL_NETWORK),
+        val localAgent = Agent(
+                nc = nc(TRANSPORT_WIFI, NET_CAPABILITY_LOCAL_NETWORK),
                 lp = lp("local0"),
-                lnc = FromS(LocalNetworkConfig.Builder()
-                        .setUpstreamSelector(upstreamSelectorWifi)
-                        .setUpstreamMulticastRoutingConfig(multicastRoutingConfigMinScope)
-                        .setDownstreamMulticastRoutingConfig(multicastRoutingConfigSelected)
-                        .build()),
-                score = FromS(NetworkScore.Builder()
-                        .setKeepConnectedReason(KEEP_CONNECTED_LOCAL_NETWORK)
-                        .build())
+                lnc = FromS(
+                        LocalNetworkConfig.Builder()
+                                .setUpstreamSelector(upstreamSelectorWifi)
+                                .setUpstreamMulticastRoutingConfig(multicastRoutingConfigMinScope)
+                                .setDownstreamMulticastRoutingConfig(multicastRoutingConfigSelected)
+                                .build()
+                ),
+                score = FromS(
+                        NetworkScore.Builder()
+                                .setKeepConnectedReason(KEEP_CONNECTED_LOCAL_NETWORK)
+                                .build()
+                )
         )
         localAgent.connect()
 
         cb.expectAvailableCallbacks(localAgent.network, validated = false)
 
-        val wifiAgent = Agent(lp = lp("wifi0"),
-                nc = nc(TRANSPORT_WIFI, NET_CAPABILITY_INTERNET))
+        val wifiAgent = Agent(
+                lp = lp("wifi0"),
+                nc = nc(TRANSPORT_WIFI, NET_CAPABILITY_INTERNET)
+        )
         wifiAgent.connect()
 
         cb.expectAvailableCallbacks(wifiAgent.network, validated = false)
@@ -519,8 +625,10 @@
         inOrder.verify(netd).networkDestroy(wifiAgent.network.netId)
 
         val wifiIface2 = if (sameIfaceName) "wifi0" else "wifi1"
-        val wifiAgent2 = Agent(lp = lp(wifiIface2),
-                nc = nc(TRANSPORT_WIFI, NET_CAPABILITY_INTERNET))
+        val wifiAgent2 = Agent(
+                lp = lp(wifiIface2),
+                nc = nc(TRANSPORT_WIFI, NET_CAPABILITY_INTERNET)
+        )
         wifiAgent2.connect()
 
         cb.expectAvailableCallbacks(wifiAgent2.network, validated = false)
@@ -529,9 +637,15 @@
 
         inOrder.verify(netd).ipfwdAddInterfaceForward("local0", wifiIface2)
         inOrder.verify(multicastRoutingCoordinatorService).applyMulticastRoutingConfig(
-                "local0", wifiIface2, multicastRoutingConfigMinScope)
+                "local0",
+                wifiIface2,
+                multicastRoutingConfigMinScope
+        )
         inOrder.verify(multicastRoutingCoordinatorService).applyMulticastRoutingConfig(
-                wifiIface2, "local0", multicastRoutingConfigSelected)
+                wifiIface2,
+                "local0",
+                multicastRoutingConfigSelected
+        )
 
         inOrder.verify(netd, never()).ipfwdRemoveInterfaceForward(any(), any())
         inOrder.verify(multicastRoutingCoordinatorService, never())
@@ -547,24 +661,35 @@
         cm.registerNetworkCallback(NetworkRequest.Builder().clearCapabilities().build(), cb)
 
         // Set up a local agent that should forward its traffic to the best wifi upstream.
-        val localAgent = Agent(nc = nc(TRANSPORT_WIFI, NET_CAPABILITY_LOCAL_NETWORK),
+        val localAgent = Agent(
+                nc = nc(TRANSPORT_WIFI, NET_CAPABILITY_LOCAL_NETWORK),
                 lp = lp("local0"),
-                lnc = FromS(LocalNetworkConfig.Builder()
-                        .setUpstreamSelector(NetworkRequest.Builder()
-                                .addTransportType(TRANSPORT_WIFI)
-                                .addForbiddenCapability(NET_CAPABILITY_LOCAL_NETWORK)
-                                .build())
-                        .build()),
-                score = FromS(NetworkScore.Builder()
-                        .setKeepConnectedReason(KEEP_CONNECTED_LOCAL_NETWORK)
-                        .build())
+                lnc = FromS(
+                        LocalNetworkConfig.Builder()
+                                .setUpstreamSelector(
+                                        NetworkRequest.Builder()
+                                                .addTransportType(TRANSPORT_WIFI)
+                                                .addForbiddenCapability(
+                                                        NET_CAPABILITY_LOCAL_NETWORK
+                                                )
+                                                .build()
+                                )
+                                .build()
+                ),
+                score = FromS(
+                        NetworkScore.Builder()
+                                .setKeepConnectedReason(KEEP_CONNECTED_LOCAL_NETWORK)
+                                .build()
+                )
         )
         localAgent.connect()
 
         cb.expectAvailableCallbacks(localAgent.network, validated = false)
 
-        val wifiAgent = Agent(lp = lp("wifi0"),
-                nc = nc(TRANSPORT_WIFI, NET_CAPABILITY_INTERNET))
+        val wifiAgent = Agent(
+                lp = lp("wifi0"),
+                nc = nc(TRANSPORT_WIFI, NET_CAPABILITY_INTERNET)
+        )
         wifiAgent.connect()
 
         cb.expectAvailableCallbacks(wifiAgent.network, validated = false)
@@ -587,21 +712,27 @@
         deps.setBuildSdk(VERSION_V)
 
         val localCb = TestableNetworkCallback()
-        cm.requestNetwork(NetworkRequest.Builder().clearCapabilities()
-                .addCapability(NET_CAPABILITY_LOCAL_NETWORK)
-                .build(),
-                localCb)
+        cm.requestNetwork(
+                NetworkRequest.Builder().clearCapabilities()
+                        .addCapability(NET_CAPABILITY_LOCAL_NETWORK)
+                        .build(),
+                localCb
+        )
 
         val cb = TestableNetworkCallback()
         cm.registerNetworkCallback(NetworkRequest.Builder().clearCapabilities().build(), cb)
 
         val localNc = nc(TRANSPORT_WIFI, NET_CAPABILITY_LOCAL_NETWORK)
-        val lnc = FromS(LocalNetworkConfig.Builder()
-                .setUpstreamSelector(NetworkRequest.Builder()
-                        .addTransportType(TRANSPORT_WIFI)
-                        .addForbiddenCapability(NET_CAPABILITY_LOCAL_NETWORK)
-                        .build())
-                .build())
+        val lnc = FromS(
+                LocalNetworkConfig.Builder()
+                        .setUpstreamSelector(
+                                NetworkRequest.Builder()
+                                        .addTransportType(TRANSPORT_WIFI)
+                                        .addForbiddenCapability(NET_CAPABILITY_LOCAL_NETWORK)
+                                        .build()
+                        )
+                        .build()
+        )
         val localScore = FromS(NetworkScore.Builder().build())
 
         // Set up a local agent that should forward its traffic to the best wifi upstream.
@@ -628,10 +759,16 @@
         val localAgent2 = Agent(nc = localNc, lp = lp("local0"), lnc = lnc, score = localScore)
         localAgent2.connect()
 
-        localCb.expectAvailableCallbacks(localAgent2.network,
-                validated = false, upstream = wifiAgent.network)
-        cb.expectAvailableCallbacks(localAgent2.network,
-                validated = false, upstream = wifiAgent.network)
+        localCb.expectAvailableCallbacks(
+                localAgent2.network,
+                validated = false,
+                upstream = wifiAgent.network
+        )
+        cb.expectAvailableCallbacks(
+                localAgent2.network,
+                validated = false,
+                upstream = wifiAgent.network
+        )
         cb.expect<Lost> { it.network == localAgent.network }
     }
 
@@ -648,24 +785,36 @@
         // Unregister wifi pending replacement, then set up a local agent that would have
         // this network as its upstream.
         wifiAgent.unregisterAfterReplacement(LONG_TIMEOUT_MS)
-        val localAgent = Agent(nc = nc(TRANSPORT_WIFI, NET_CAPABILITY_LOCAL_NETWORK),
+        val localAgent = Agent(
+                nc = nc(TRANSPORT_WIFI, NET_CAPABILITY_LOCAL_NETWORK),
                 lp = lp("local0"),
-                lnc = FromS(LocalNetworkConfig.Builder()
-                        .setUpstreamSelector(NetworkRequest.Builder()
-                                .addForbiddenCapability(NET_CAPABILITY_LOCAL_NETWORK)
-                                .addTransportType(TRANSPORT_WIFI)
-                                .build())
-                        .build()),
-                score = FromS(NetworkScore.Builder()
-                        .setKeepConnectedReason(KEEP_CONNECTED_LOCAL_NETWORK)
-                        .build())
+                lnc = FromS(
+                        LocalNetworkConfig.Builder()
+                                .setUpstreamSelector(
+                                        NetworkRequest.Builder()
+                                                .addForbiddenCapability(
+                                                        NET_CAPABILITY_LOCAL_NETWORK
+                                                )
+                                                .addTransportType(TRANSPORT_WIFI)
+                                                .build()
+                                )
+                                .build()
+                ),
+                score = FromS(
+                        NetworkScore.Builder()
+                                .setKeepConnectedReason(KEEP_CONNECTED_LOCAL_NETWORK)
+                                .build()
+                )
         )
 
         // Connect the local agent. The zombie wifi is its upstream, but the stack doesn't
         // tell netd to add the forward since the wifi0 interface has gone.
         localAgent.connect()
-        cb.expectAvailableCallbacks(localAgent.network,
-                validated = false, upstream = wifiAgent.network)
+        cb.expectAvailableCallbacks(
+                localAgent.network,
+                validated = false,
+                upstream = wifiAgent.network
+        )
 
         verify(netd, never()).ipfwdAddInterfaceForward("local0", "wifi0")
 
@@ -679,33 +828,51 @@
     fun testForwardingRules() {
         deps.setBuildSdk(VERSION_V)
         // Set up a local agent that should forward its traffic to the best DUN upstream.
-        val lnc = FromS(LocalNetworkConfig.Builder()
-                .setUpstreamSelector(NetworkRequest.Builder()
-                        .addCapability(NET_CAPABILITY_DUN)
-                        .addForbiddenCapability(NET_CAPABILITY_LOCAL_NETWORK)
-                        .build())
-                .build())
-        val localAgent = Agent(nc = nc(TRANSPORT_WIFI, NET_CAPABILITY_LOCAL_NETWORK),
+        val lnc = FromS(
+                LocalNetworkConfig.Builder()
+                        .setUpstreamSelector(
+                                NetworkRequest.Builder()
+                                        .addCapability(NET_CAPABILITY_DUN)
+                                        .addForbiddenCapability(NET_CAPABILITY_LOCAL_NETWORK)
+                                        .build()
+                        )
+                        .build()
+        )
+        val localAgent = Agent(
+                nc = nc(TRANSPORT_WIFI, NET_CAPABILITY_LOCAL_NETWORK),
                 lp = lp("local0"),
                 lnc = lnc,
-                score = FromS(NetworkScore.Builder()
-                        .setKeepConnectedReason(KEEP_CONNECTED_LOCAL_NETWORK)
-                        .build())
+                score = FromS(
+                        NetworkScore.Builder()
+                                .setKeepConnectedReason(KEEP_CONNECTED_LOCAL_NETWORK)
+                                .build()
+                )
         )
         localAgent.connect()
 
-        val wifiAgent = Agent(score = keepScore(), lp = lp("wifi0"),
-                nc = nc(TRANSPORT_WIFI, NET_CAPABILITY_INTERNET))
-        val cellAgentDun = Agent(score = keepScore(), lp = lp("cell0"),
-                nc = nc(TRANSPORT_CELLULAR, NET_CAPABILITY_INTERNET, NET_CAPABILITY_DUN))
-        val wifiAgentDun = Agent(score = keepScore(), lp = lp("wifi1"),
-                nc = nc(TRANSPORT_WIFI, NET_CAPABILITY_INTERNET, NET_CAPABILITY_DUN))
+        val wifiAgent = Agent(
+                score = keepScore(),
+                lp = lp("wifi0"),
+                nc = nc(TRANSPORT_WIFI, NET_CAPABILITY_INTERNET)
+        )
+        val cellAgentDun = Agent(
+                score = keepScore(),
+                lp = lp("cell0"),
+                nc = nc(TRANSPORT_CELLULAR, NET_CAPABILITY_INTERNET, NET_CAPABILITY_DUN)
+        )
+        val wifiAgentDun = Agent(
+                score = keepScore(),
+                lp = lp("wifi1"),
+                nc = nc(TRANSPORT_WIFI, NET_CAPABILITY_INTERNET, NET_CAPABILITY_DUN)
+        )
 
         val cb = TestableNetworkCallback()
-        cm.registerNetworkCallback(NetworkRequest.Builder()
-                .addCapability(NET_CAPABILITY_LOCAL_NETWORK)
-                .build(),
-                cb)
+        cm.registerNetworkCallback(
+                NetworkRequest.Builder()
+                        .addCapability(NET_CAPABILITY_LOCAL_NETWORK)
+                        .build(),
+                cb
+        )
         cb.expectAvailableCallbacks(localAgent.network, validated = false)
 
         val inOrder = inOrder(netd)
@@ -750,8 +917,11 @@
         inOrder.verify(netd).ipfwdDisableForwarding(any())
         cb.expect<LocalInfoChanged>(localAgent.network) { it.info.upstreamNetwork == null }
 
-        val wifiAgentDun2 = Agent(score = keepScore(), lp = lp("wifi2"),
-                nc = nc(TRANSPORT_WIFI, NET_CAPABILITY_INTERNET, NET_CAPABILITY_DUN))
+        val wifiAgentDun2 = Agent(
+                score = keepScore(),
+                lp = lp("wifi2"),
+                nc = nc(TRANSPORT_WIFI, NET_CAPABILITY_INTERNET, NET_CAPABILITY_DUN)
+        )
         wifiAgentDun2.connect()
         inOrder.verify(netd).ipfwdEnableForwarding(any())
         inOrder.verify(netd).ipfwdAddInterfaceForward("local0", "wifi2")
@@ -764,8 +934,11 @@
         inOrder.verify(netd).ipfwdDisableForwarding(any())
         cb.expect<LocalInfoChanged>(localAgent.network) { it.info.upstreamNetwork == null }
 
-        val wifiAgentDun3 = Agent(score = keepScore(), lp = lp("wifi3"),
-                nc = nc(TRANSPORT_WIFI, NET_CAPABILITY_INTERNET, NET_CAPABILITY_DUN))
+        val wifiAgentDun3 = Agent(
+                score = keepScore(),
+                lp = lp("wifi3"),
+                nc = nc(TRANSPORT_WIFI, NET_CAPABILITY_INTERNET, NET_CAPABILITY_DUN)
+        )
         wifiAgentDun3.connect()
         inOrder.verify(netd).ipfwdEnableForwarding(any())
         inOrder.verify(netd).ipfwdAddInterfaceForward("local0", "wifi3")
@@ -800,8 +973,11 @@
         cm.registerNetworkCallback(nr, listenCb)
 
         val upstream = if (haveUpstream) {
-            Agent(score = keepScore(), lp = lp("wifi0"),
-                    nc = nc(TRANSPORT_WIFI)).also { it.connect() }
+            Agent(
+                    score = keepScore(),
+                    lp = lp("wifi0"),
+                    nc = nc(TRANSPORT_WIFI)
+            ).also { it.connect() }
         } else {
             null
         }
@@ -809,23 +985,32 @@
         // Set up a local agent.
         val lnc = FromS(LocalNetworkConfig.Builder().apply {
             if (haveUpstream) {
-                setUpstreamSelector(NetworkRequest.Builder()
-                        .addForbiddenCapability(NET_CAPABILITY_LOCAL_NETWORK)
-                        .addTransportType(TRANSPORT_WIFI)
-                        .build())
+                setUpstreamSelector(
+                        NetworkRequest.Builder()
+                                .addForbiddenCapability(NET_CAPABILITY_LOCAL_NETWORK)
+                                .addTransportType(TRANSPORT_WIFI)
+                                .build()
+                )
             }
         }.build())
-        val localAgent = Agent(nc = nc(TRANSPORT_THREAD, NET_CAPABILITY_LOCAL_NETWORK),
+        val localAgent = Agent(
+                nc = nc(TRANSPORT_THREAD, NET_CAPABILITY_LOCAL_NETWORK),
                 lp = lp("local0"),
                 lnc = lnc,
                 score = FromS(NetworkScore.Builder().build())
         )
         localAgent.connect()
 
-        requestCb.expectAvailableCallbacks(localAgent.network,
-                validated = false, upstream = upstream?.network)
-        listenCb.expectAvailableCallbacks(localAgent.network,
-                validated = false, upstream = upstream?.network)
+        requestCb.expectAvailableCallbacks(
+                localAgent.network,
+                validated = false,
+                upstream = upstream?.network
+        )
+        listenCb.expectAvailableCallbacks(
+                localAgent.network,
+                validated = false,
+                upstream = upstream?.network
+        )
 
         cm.unregisterNetworkCallback(requestCb)
 
@@ -866,12 +1051,14 @@
         doTestLocalNetworkRequest(
                 request,
                 enableMatchLocalNetwork = false,
-                expectCallback = false)
+                expectCallback = false
+        )
         // If ENABLE_MATCH_LOCAL_NETWORK is true, request is satisfied by local network
         doTestLocalNetworkRequest(
                 request,
                 enableMatchLocalNetwork = true,
-                expectCallback = true)
+                expectCallback = true
+        )
     }
 
     @Test
@@ -880,10 +1067,12 @@
         doTestLocalNetworkRequest(
                 request,
                 enableMatchLocalNetwork = false,
-                expectCallback = true)
+                expectCallback = true
+        )
         doTestLocalNetworkRequest(
                 request,
                 enableMatchLocalNetwork = true,
-                expectCallback = true)
+                expectCallback = true
+        )
     }
 }
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 9be7d11..6e07ac6 100644
--- a/tests/unit/java/com/android/server/connectivityservice/base/CSAgentWrapper.kt
+++ b/tests/unit/java/com/android/server/connectivityservice/base/CSAgentWrapper.kt
@@ -104,7 +104,8 @@
         doNothing().`when`(networkStack).makeNetworkMonitor(
                 nmNetworkCaptor.capture(),
                 any() /* name */,
-                nmCbCaptor.capture())
+                nmCbCaptor.capture()
+        )
 
         // Create the actual agent. NetworkAgent is abstract, so make an anonymous subclass.
         if (deps.isAtLeastS()) {
@@ -157,8 +158,10 @@
                 // Instead of forcefully adding NOT_SUSPENDED to all agents like older tools did,
                 // it's better to let the developer manage it as they see fit but help them
                 // debug if they forget.
-                fail("Could not connect the agent. Did you forget to add " +
-                        "NET_CAPABILITY_NOT_SUSPENDED ?")
+                fail(
+                    "Could not connect the agent. Did you forget to add " +
+                        "NET_CAPABILITY_NOT_SUSPENDED ?"
+                )
             }
             fail("Could not connect the agent. Instrumentation failure ?")
         }
diff --git a/tests/unit/java/com/android/server/connectivityservice/base/CSTestHelpers.kt b/tests/unit/java/com/android/server/connectivityservice/base/CSTestHelpers.kt
index a53d430..5e18843 100644
--- a/tests/unit/java/com/android/server/connectivityservice/base/CSTestHelpers.kt
+++ b/tests/unit/java/com/android/server/connectivityservice/base/CSTestHelpers.kt
@@ -114,8 +114,10 @@
     )
     doReturn(true).`when`(pm).hasSystemFeature(argThat { supported.contains(it) })
     val myPackageName = realContext.packageName
-    val myPackageInfo = realContext.packageManager.getPackageInfo(myPackageName,
-            PackageManager.GET_PERMISSIONS)
+    val myPackageInfo = realContext.packageManager.getPackageInfo(
+        myPackageName,
+            PackageManager.GET_PERMISSIONS
+    )
     // Very high version code so that the checks for the module version will always
     // say that it is recent enough. This is the most sensible default, but if some
     // test needs to test with different version codes they can re-mock this with a
@@ -123,7 +125,10 @@
     myPackageInfo.longVersionCode = 9999999L
     doReturn(arrayOf(myPackageName)).`when`(pm).getPackagesForUid(Binder.getCallingUid())
     doReturn(myPackageInfo).`when`(pm).getPackageInfoAsUser(
-            eq(myPackageName), anyInt(), eq(UserHandle.getCallingUserId()))
+            eq(myPackageName),
+            anyInt(),
+            eq(UserHandle.getCallingUserId())
+    )
     doReturn(listOf(myPackageInfo)).`when`(pm)
             .getInstalledPackagesAsUser(eq(PackageManager.GET_PERMISSIONS), anyInt())
 }
@@ -144,12 +149,19 @@
         handler as Handler
         val delayMs = ((date as Long) - SystemClock.elapsedRealtime()).coerceAtLeast(0)
         if (delayMs > UNREASONABLY_LONG_ALARM_WAIT_MS) {
-            fail("Attempting to send msg more than $UNREASONABLY_LONG_ALARM_WAIT_MS" +
-                    "ms into the future : $delayMs")
+            fail(
+                "Attempting to send msg more than $UNREASONABLY_LONG_ALARM_WAIT_MS" +
+                    "ms into the future : $delayMs"
+            )
         }
         alrmHdlr.postDelayed({ handler.post(wakeupMsg::onAlarm) }, wakeupMsg, delayMs)
-    }.`when`(am).setExact(eq(AlarmManager.ELAPSED_REALTIME_WAKEUP), anyLong(), anyString(),
-            any<WakeupMessage>(), any())
+    }.`when`(am).setExact(
+            eq(AlarmManager.ELAPSED_REALTIME_WAKEUP),
+            anyLong(),
+            anyString(),
+            any<WakeupMessage>(),
+            any()
+    )
     doAnswer {
         alrmHdlr.removeCallbacksAndMessages(it.getArgument<WakeupMessage>(0))
     }.`when`(am).cancel(any<WakeupMessage>())
@@ -193,14 +205,20 @@
 
 private val TEST_LINGER_DELAY_MS = 400
 private val TEST_NASCENT_DELAY_MS = 300
-internal fun makeConnectivityService(context: Context, netd: INetd, deps: Dependencies,
-                                     mPermDeps: PermissionMonitor.Dependencies) =
+internal fun makeConnectivityService(
+        context: Context,
+        netd: INetd,
+        deps: Dependencies,
+        mPermDeps: PermissionMonitor.Dependencies
+) =
         ConnectivityService(
                 context,
                 mock<IDnsResolver>(),
                 mock<IpConnectivityLog>(),
                 netd,
-                deps, mPermDeps).also {
+                deps,
+                mPermDeps
+        ).also {
             it.mLingerDelayMs = TEST_LINGER_DELAY_MS
             it.mNascentDelayMs = TEST_NASCENT_DELAY_MS
         }
diff --git a/thread/docs/build-an-android-border-router.md b/thread/docs/build-an-android-border-router.md
index f90a23b..2687e26 100644
--- a/thread/docs/build-an-android-border-router.md
+++ b/thread/docs/build-an-android-border-router.md
@@ -380,10 +380,12 @@
 [config_thread.xml](https://cs.android.com/android/platform/superproject/main/+/main:packages/modules/Connectivity/service/ServiceConnectivityResources/res/values/config_thread.xml)
 for the full list.
 
-Typically, you must change the `config_thread_vendor_name`,
-`config_thread_vendor_oui` and `config_thread_model_name` to your vendor or
-product values. Those values will be included in the `_meshcop._udp` mDNS
-service which is always advertised by a Thread Border Router.
+Typically, you must set `config_thread_border_router_default_enabled` to `true`
+to enable your device as a Thread Border Router, and change the
+`config_thread_vendor_name`, `config_thread_vendor_oui` and
+`config_thread_model_name` to your vendor or product values. Those values will
+be included in the `_meshcop._udp` mDNS service which is always advertised by a
+Thread Border Router.
 
 To add the overlay, you need to create a new `ConnectivityOverlayOrange`
 runtime_resource_overlay target for your Orange device. Create a new
@@ -436,6 +438,7 @@
   ```
 - `config_thread.xml`:
   ```
+  <bool name="config_thread_border_router_default_enabled">true</bool>
   <string translatable="false" name="config_thread_vendor_name">Banana Inc.</string>
   <string translatable="false" name="config_thread_vendor_oui">AC:DE:48</string>
   <string translatable="false" name="config_thread_model_name">Orange</string>
diff --git a/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java b/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java
index 7063357..d859fb2 100644
--- a/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java
+++ b/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java
@@ -628,12 +628,15 @@
         boolean srpServerWaitEnabled = mResources.get().getBoolean(srpServerConfig);
         int autoJoinConfig = R.bool.config_thread_border_router_auto_join_enabled;
         boolean autoJoinEnabled = mResources.get().getBoolean(autoJoinConfig);
+        boolean countryCodeEnabled =
+                mResources.get().getBoolean(R.bool.config_thread_country_code_enabled);
         return new OtDaemonConfiguration.Builder()
                 .setBorderRouterEnabled(threadConfig.isBorderRouterEnabled())
                 .setNat64Enabled(threadConfig.isNat64Enabled())
                 .setDhcpv6PdEnabled(threadConfig.isDhcpv6PdEnabled())
                 .setSrpServerWaitForBorderRoutingEnabled(srpServerWaitEnabled)
                 .setBorderRouterAutoJoinEnabled(autoJoinEnabled)
+                .setCountryCodeEnabled(countryCodeEnabled)
                 .build();
     }
 
diff --git a/thread/service/java/com/android/server/thread/ThreadNetworkCountryCode.java b/thread/service/java/com/android/server/thread/ThreadNetworkCountryCode.java
index a96d06e..16196fa 100644
--- a/thread/service/java/com/android/server/thread/ThreadNetworkCountryCode.java
+++ b/thread/service/java/com/android/server/thread/ThreadNetworkCountryCode.java
@@ -17,6 +17,7 @@
 package com.android.server.thread;
 
 import static android.net.thread.ThreadNetworkException.ERROR_UNSUPPORTED_FEATURE;
+
 import static com.android.server.thread.ThreadPersistentSettings.KEY_COUNTRY_CODE;
 
 import android.annotation.Nullable;
@@ -223,6 +224,10 @@
                 .getBoolean(R.bool.config_thread_location_use_for_country_code_enabled);
     }
 
+    private boolean isCountryCodeEnabled() {
+        return mResources.get().getBoolean(R.bool.config_thread_country_code_enabled);
+    }
+
     public ThreadNetworkCountryCode(
             LocationManager locationManager,
             ThreadNetworkControllerService threadNetworkControllerService,
@@ -270,6 +275,11 @@
 
     /** Sets up this country code module to listen to location country code changes. */
     public synchronized void initialize() {
+        if (!isCountryCodeEnabled()) {
+            LOG.i("Thread country code is disabled");
+            return;
+        }
+
         registerGeocoderCountryCodeCallback();
         registerWifiCountryCodeCallback();
         registerTelephonyCountryCodeCallback();
@@ -654,6 +664,7 @@
     /** 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("isCountryCodeEnabled            : " + isCountryCodeEnabled());
         pw.println("mIsCpSettingCountryCodeSupported: " + mIsCpSettingCountryCodeSupported);
         pw.println("mOverrideCountryCodeInfo        : " + mOverrideCountryCodeInfo);
         pw.println("mTelephonyCountryCodeSlotInfoMap: " + mTelephonyCountryCodeSlotInfoMap);
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 95ebda5..63d6130 100644
--- a/thread/tests/unit/src/com/android/server/thread/ThreadNetworkControllerServiceTest.java
+++ b/thread/tests/unit/src/com/android/server/thread/ThreadNetworkControllerServiceTest.java
@@ -261,6 +261,7 @@
                 .thenReturn(TEST_MODEL_NAME);
         when(mResources.getStringArray(eq(R.array.config_thread_mdns_vendor_specific_txts)))
                 .thenReturn(new String[] {});
+        when(mResources.getBoolean(eq(R.bool.config_thread_country_code_enabled))).thenReturn(true);
 
         final AtomicFile storageFile = new AtomicFile(tempFolder.newFile("thread_settings.xml"));
         mPersistentSettings = new ThreadPersistentSettings(storageFile, mConnectivityResources);
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 6eb9b50..1a6b3cc 100644
--- a/thread/tests/unit/src/com/android/server/thread/ThreadNetworkCountryCodeTest.java
+++ b/thread/tests/unit/src/com/android/server/thread/ThreadNetworkCountryCodeTest.java
@@ -206,6 +206,21 @@
     }
 
     @Test
+    public void initialize_countryCodeDisabled_defaultCountryCodeIsUsed() {
+        when(mResources.getBoolean(R.bool.config_thread_country_code_enabled)).thenReturn(false);
+
+        mThreadNetworkCountryCode.initialize();
+
+        verifyNoMoreInteractions(mWifiManager);
+        verifyNoMoreInteractions(mTelephonyManager);
+        verifyNoMoreInteractions(mSubscriptionManager);
+        verifyNoMoreInteractions(mGeocoder);
+        verifyNoMoreInteractions(mLocationManager);
+
+        assertThat(mThreadNetworkCountryCode.getCountryCode()).isEqualTo(DEFAULT_COUNTRY_CODE);
+    }
+
+    @Test
     public void initialize_locationUseIsDisabled_locationFunctionIsNotCalled() {
         when(mResources.getBoolean(R.bool.config_thread_location_use_for_country_code_enabled))
                 .thenReturn(false);
@@ -507,6 +522,7 @@
         mThreadNetworkCountryCode.dump(new FileDescriptor(), printWriter, null);
         String outputString = stringWriter.toString();
 
+        assertThat(outputString).contains("isCountryCodeEnabled");
         assertThat(outputString).contains("mIsCpSettingCountryCodeSupported");
         assertThat(outputString).contains("mOverrideCountryCodeInfo");
         assertThat(outputString).contains("mTelephonyCountryCodeSlotInfoMap");