Merge "Skip enabling BTF on kernel older than 4.18" into main
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/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/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/src/com/android/server/BpfNetMaps.java b/service/src/com/android/server/BpfNetMaps.java
index 923920f..9bd407d 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) {
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/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/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/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/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");