Merge changes I7fae1964,Ic7cc9e1d into main

* changes:
  Add carrier-privileged app bypass for start/stopTethering
  Fix CarrierConfigRule#setHoldCarrierPrivilege race
diff --git a/Tethering/src/com/android/networkstack/tethering/TetheringService.java b/Tethering/src/com/android/networkstack/tethering/TetheringService.java
index b92cf69..737041e 100644
--- a/Tethering/src/com/android/networkstack/tethering/TetheringService.java
+++ b/Tethering/src/com/android/networkstack/tethering/TetheringService.java
@@ -146,7 +146,7 @@
             mTethering.setUsbTethering(enable, listener);
         }
 
-        private boolean isRequestAllowedForDeviceOwner(@NonNull TetheringRequest request) {
+        private boolean isRequestAllowedForDOOrCarrierApp(@NonNull TetheringRequest request) {
             return request.getTetheringType() == TETHERING_WIFI
                     && request.getSoftApConfiguration() != null;
         }
@@ -159,10 +159,10 @@
             request.setPackageName(callerPkg);
             boolean onlyAllowPrivileged = request.isExemptFromEntitlementCheck()
                     || request.getInterfaceName() != null;
-            boolean isDeviceOwnerAllowed = mTethering.isTetheringWithSoftApConfigEnabled()
-                    && isRequestAllowedForDeviceOwner(request);
+            boolean isDOOrCarrierAppAllowed = mTethering.isTetheringWithSoftApConfigEnabled()
+                    && isRequestAllowedForDOOrCarrierApp(request);
             if (checkAndNotifyCommonError(callerPkg, callingAttributionTag, onlyAllowPrivileged,
-                    isDeviceOwnerAllowed, listener)) {
+                    isDOOrCarrierAppAllowed, listener)) {
                 return;
             }
             mTethering.startTethering(request, callerPkg, listener);
@@ -191,10 +191,10 @@
             if (listener == null) return;
             request.setUid(getBinderCallingUid());
             request.setPackageName(callerPkg);
-            boolean isDeviceOwnerAllowed = mTethering.isTetheringWithSoftApConfigEnabled()
-                    && isRequestAllowedForDeviceOwner(request);
+            boolean isDOOrCarrierAppAllowed = mTethering.isTetheringWithSoftApConfigEnabled()
+                    && isRequestAllowedForDOOrCarrierApp(request);
             if (checkAndNotifyCommonError(callerPkg, callingAttributionTag,
-                    false /* onlyAllowPrivileged */, isDeviceOwnerAllowed, listener)) {
+                    false /* onlyAllowPrivileged */, isDOOrCarrierAppAllowed, listener)) {
                 return;
             }
             // Note: Whether tethering is actually stopped or not will depend on whether the request
@@ -274,9 +274,9 @@
         @Override
         public void isTetheringSupported(String callerPkg, String callingAttributionTag,
                 IIntResultListener listener) {
-            boolean isDeviceOwnerAppAllowed = mTethering.isTetheringWithSoftApConfigEnabled();
+            boolean isDOOrCarrierAppAllowed = mTethering.isTetheringWithSoftApConfigEnabled();
             if (checkAndNotifyCommonError(callerPkg, callingAttributionTag,
-                    false /* onlyAllowPrivileged */, isDeviceOwnerAppAllowed, listener)) {
+                    false /* onlyAllowPrivileged */, isDOOrCarrierAppAllowed, listener)) {
                 return;
             }
             try {
@@ -304,7 +304,7 @@
 
         private boolean checkAndNotifyCommonError(final String callerPkg,
                 final String callingAttributionTag, final boolean onlyAllowPrivileged,
-                final boolean isDeviceOwnerAppAllowed, final IIntResultListener listener) {
+                final boolean isDOOrCarrierAppAllowed, final IIntResultListener listener) {
             try {
                 final int uid = getBinderCallingUid();
                 if (!checkPackageNameMatchesUid(uid, callerPkg)) {
@@ -313,7 +313,7 @@
                     return true;
                 }
                 if (!hasTetherChangePermission(uid, callerPkg, callingAttributionTag,
-                        onlyAllowPrivileged, isDeviceOwnerAppAllowed)) {
+                        onlyAllowPrivileged, isDOOrCarrierAppAllowed)) {
                     listener.onResult(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION);
                     return true;
                 }
@@ -347,14 +347,18 @@
 
         private boolean hasTetherChangePermission(final int uid, final String callerPkg,
                 final String callingAttributionTag, final boolean onlyAllowPrivileged,
-                final boolean isDeviceOwnerAppAllowed) {
+                final boolean isDOOrCarrierAppAllowed) {
             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)) {
+            // Allow DO and carrier-privileged apps to change tethering even if they don't have
+            // TETHER_PRIVILEGED.
+            // TODO: Stop tethering if the app loses DO status or carrier-privileges.
+            if (isDOOrCarrierAppAllowed
+                    && (mService.isDeviceOwner(uid, callerPkg)
+                            || mService.isCarrierPrivileged(callerPkg))) {
                 return true;
             }
 
@@ -436,6 +440,14 @@
     }
 
     /**
+     * Wrapper for {@link TetheringPermissionsUtils#isCarrierPrivileged(String)}, used for mocks.
+     */
+    @VisibleForTesting
+    boolean isCarrierPrivileged(final String callerPkg) {
+        return mTetheringPermissionsUtils.isCarrierPrivileged(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
index 944e861..603fa9c 100644
--- a/Tethering/src/com/android/networkstack/tethering/util/TetheringPermissionsUtils.java
+++ b/Tethering/src/com/android/networkstack/tethering/util/TetheringPermissionsUtils.java
@@ -19,7 +19,9 @@
 import android.app.admin.DevicePolicyManager;
 import android.content.Context;
 import android.content.pm.PackageManager;
+import android.os.Binder;
 import android.os.UserHandle;
+import android.telephony.TelephonyManager;
 import android.util.Log;
 
 import androidx.annotation.NonNull;
@@ -68,4 +70,20 @@
         }
         return devicePolicyManager;
     }
+
+    /**
+     * Checks if the package name has carrier privileges.
+     */
+    public boolean isCarrierPrivileged(@NonNull final String packageName) {
+        TelephonyManager telephonyManager = mContext.getSystemService(TelephonyManager.class);
+        if (telephonyManager == null) return false;
+
+        long ident = Binder.clearCallingIdentity();
+        try {
+            return telephonyManager.checkCarrierPrivilegesForPackageAnyPhone(packageName)
+                    == TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS;
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
 }
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 a8bd221..01d7198 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/MockTetheringService.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/MockTetheringService.java
@@ -37,6 +37,7 @@
     private final ArrayMap<String, Integer> mMockedPermissions = new ArrayMap<>();
     private final ArrayMap<String, Integer> mMockedPackageUids = new ArrayMap<>();
     private final Set<String> mMockedDeviceOwnerPackages = new ArraySet<>();
+    private final Set<String> mMockedCarrierPrivilegedPackages = new ArraySet<>();
     private int mMockCallingUid;
 
     @Override
@@ -83,6 +84,11 @@
         return mMockedDeviceOwnerPackages.contains(callerPkg);
     }
 
+    @Override
+    boolean isCarrierPrivileged(final String callerPkg) {
+        return mMockedCarrierPrivilegedPackages.contains(callerPkg);
+    }
+
     public Tethering getTethering() {
         return mTethering;
     }
@@ -141,5 +147,19 @@
         public void removeDeviceOwnerPackage(final String packageName) {
             mMockedDeviceOwnerPackages.remove(packageName);
         }
+
+        /**
+         * Add a mocked carrier privileges package
+         */
+        public void addCarrierPrivilegedPackage(final String packageName) {
+            mMockedCarrierPrivilegedPackages.add(packageName);
+        }
+
+        /**
+         * Remove a mocked carrier privileges package
+         */
+        public void removeCarrierPrivilegedPackage(final String packageName) {
+            mMockedCarrierPrivilegedPackages.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 87163ef..b58fa14 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringServiceTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringServiceTest.java
@@ -90,6 +90,15 @@
     private static final int TEST_CALLER_UID = 1234;
     private static final String TEST_ATTRIBUTION_TAG = null;
     private static final String TEST_WRONG_PACKAGE = "wrong.package";
+    private static final int NO_RESULT = -1;
+    private static final TetheringRequest USB_REQUEST =
+            new TetheringRequest.Builder(TETHERING_USB).build();
+    private static final TetheringRequest WIFI_REQUEST_NO_CONFIG =
+            new TetheringRequest.Builder(TETHERING_WIFI).build();
+    private static final TetheringRequest WIFI_REQUEST_WITH_CONFIG =
+            new TetheringRequest.Builder(TETHERING_WIFI)
+                    .setSoftApConfiguration(new SoftApConfiguration.Builder().build())
+                    .build();
     @Mock private ITetheringEventCallback mITetheringEventCallback;
     @Rule public ServiceTestRule mServiceTestRule;
     private Tethering mTethering;
@@ -100,7 +109,7 @@
     @Mock private AppOpsManager mAppOps;
 
     private class TestTetheringResult extends IIntResultListener.Stub {
-        private int mResult = -1; // Default value that does not match any result code.
+        private int mResult = NO_RESULT;
         @Override
         public void onResult(final int resultCode) {
             mResult = resultCode;
@@ -115,7 +124,7 @@
         MyResultReceiver(Handler handler) {
             super(handler);
         }
-        private int mResult = -1; // Default value that does not match any result code.
+        private int mResult = NO_RESULT;
         @Override
         protected void onReceiveResult(int resultCode, Bundle resultData) {
             mResult = resultCode;
@@ -206,6 +215,21 @@
         mMockConnector.removeDeviceOwnerPackage(TEST_CALLER_PKG);
     }
 
+    private void runAsCarrierPrivileged(final TestTetheringCall test) throws Exception {
+        mMockConnector.addCarrierPrivilegedPackage(TEST_CALLER_PKG);
+        runTetheringCall(test, true /* isTetheringAllowed */,
+                true /* isTetheringWithSoftApConfigEnabled */, new String[0]);
+        mMockConnector.removeCarrierPrivilegedPackage(TEST_CALLER_PKG);
+    }
+
+    private void runAsCarrierPrivilegedWhenCarrierPrivilegeBypassNotEnabled(
+            final TestTetheringCall test) throws Exception {
+        mMockConnector.addCarrierPrivilegedPackage(TEST_CALLER_PKG);
+        runTetheringCall(test, true /* isTetheringAllowed */,
+                false /* isTetheringWithSoftApConfigEnabled */, new String[0]);
+        mMockConnector.removeCarrierPrivilegedPackage(TEST_CALLER_PKG);
+    }
+
     private void runTetheringCall(final TestTetheringCall test, boolean isTetheringAllowed,
             boolean isTetheringWithSoftApConfigEnabled, String... permissions) throws Exception {
         // Allow the test to run even if ACCESS_NETWORK_STATE was granted at the APK level
@@ -381,125 +405,114 @@
         });
     }
 
-    private void runStartTethering(final TestTetheringResult result,
-            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(
-                eq(new TetheringRequest(request)), eq(TEST_CALLER_PKG), eq(result));
+    private void verifyStartTetheringRequestSucceeds(final TetheringRequest request,
+            final TestTetheringResult result) throws Exception {
+        mTetheringConnector.startTethering(request.getParcel(), TEST_CALLER_PKG,
+                TEST_ATTRIBUTION_TAG, result);
+        verify(mTethering).startTethering(eq(request), eq(TEST_CALLER_PKG), eq(result));
+        reset(mTethering);
+        result.assertResult(NO_RESULT);
+    }
+
+    private void verifyStartTetheringRequestFails(final TetheringRequest request,
+            final TestTetheringResult result, final int resultCode) throws Exception {
+        mTetheringConnector.startTethering(request.getParcel(), TEST_CALLER_PKG,
+                TEST_ATTRIBUTION_TAG, result);
+        verify(mTethering, never()).startTethering(any(), any(), any());
+        reset(mTethering);
+        result.assertResult(resultCode);
+    }
+
+    private void verifyStartTetheringRequestWithWrongPackageFails(final TetheringRequest request,
+            final TestTetheringResult result) throws Exception {
+        mTetheringConnector.startTethering(request.getParcel(), TEST_WRONG_PACKAGE,
+                TEST_ATTRIBUTION_TAG, result);
+        verify(mTethering, never()).startTethering(any(), any(), any());
+        reset(mTethering);
+        result.assertResult(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION);
     }
 
     @Test
     public void testStartTethering() throws Exception {
-        final TetheringRequestParcel request = new TetheringRequestParcel();
-        request.tetheringType = TETHERING_WIFI;
-
         runAsNoPermission((result) -> {
-            mTetheringConnector.startTethering(request, TEST_CALLER_PKG, TEST_ATTRIBUTION_TAG,
-                    result);
-            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();
+            verifyStartTetheringRequestFails(WIFI_REQUEST_NO_CONFIG, result,
+                    TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION);
         });
 
         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);
-            verifyNoMoreInteractionsForTethering();
+            verifyStartTetheringRequestSucceeds(WIFI_REQUEST_NO_CONFIG, result);
         });
 
         runAsTetherPrivileged((result) -> {
-            runStartTethering(result, request);
-            verifyNoMoreInteractionsForTethering();
+            verifyStartTetheringRequestWithWrongPackageFails(WIFI_REQUEST_NO_CONFIG, result);
         });
 
         runAsWriteSettings((result) -> {
-            mTetheringConnector.startTethering(request, TEST_CALLER_PKG, TEST_ATTRIBUTION_TAG,
-                    result);
-            verify(mTethering, atLeastOnce()).isTetheringWithSoftApConfigEnabled();
-            result.assertResult(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION);
-            verifyNoMoreInteractionsForTethering();
+            verifyStartTetheringRequestFails(WIFI_REQUEST_NO_CONFIG, result,
+                    TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION);
         });
 
         runAsWriteSettingsWhenWriteSettingsAllowed((result) -> {
-            runStartTethering(result, request);
-            verify(mTethering, atLeastOnce()).isTetheringWithSoftApConfigEnabled();
-            verify(mTethering).isTetherProvisioningRequired();
-            verifyNoMoreInteractionsForTethering();
+            verifyStartTetheringRequestSucceeds(WIFI_REQUEST_NO_CONFIG, result);
         });
 
         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);
-            verifyNoMoreInteractionsForTethering();
+            verifyStartTetheringRequestFails(WIFI_REQUEST_NO_CONFIG, result,
+                    TETHER_ERROR_UNSUPPORTED);
+        });
+
+        // Not wifi -> fail
+        runAsDeviceOwner((result) -> {
+            verifyStartTetheringRequestFails(USB_REQUEST, result,
+                    TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION);
+        });
+
+        // No SoftApConfiguration -> fail
+        runAsDeviceOwner((result) -> {
+            verifyStartTetheringRequestFails(WIFI_REQUEST_NO_CONFIG, result,
+                    TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION);
+        });
+
+        // With SoftApConfiguration -> success
+        runAsDeviceOwner((result) -> {
+            verifyStartTetheringRequestSucceeds(WIFI_REQUEST_WITH_CONFIG, result);
+        });
+
+        runAsDeviceOwnerWhenDeviceOwnerBypassNotEnabled((result) -> {
+            verifyStartTetheringRequestFails(WIFI_REQUEST_WITH_CONFIG, result,
+                    TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION);
+        });
+
+        // Not wifi -> fail
+        runAsCarrierPrivileged((result) -> {
+            verifyStartTetheringRequestFails(USB_REQUEST, result,
+                    TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION);
+        });
+
+        // No SoftApConfiguration -> fail
+        runAsCarrierPrivileged((result) -> {
+            verifyStartTetheringRequestFails(WIFI_REQUEST_NO_CONFIG, result,
+                    TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION);
+        });
+
+        // With SoftApConfiguration -> success
+        runAsCarrierPrivileged((result) -> {
+            verifyStartTetheringRequestSucceeds(WIFI_REQUEST_WITH_CONFIG, result);
+        });
+
+        runAsCarrierPrivilegedWhenCarrierPrivilegeBypassNotEnabled((result) -> {
+            verifyStartTetheringRequestFails(WIFI_REQUEST_WITH_CONFIG, result,
+                    TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION);
         });
     }
 
     @Test
     public void testStartTetheringWithInterfaceSucceeds() throws Exception {
-        final TetheringRequestParcel request = new TetheringRequestParcel();
-        request.tetheringType = TETHERING_VIRTUAL;
-        request.interfaceName = "avf_tap_fixed";
-
+        final TetheringRequest request = new TetheringRequest.Builder(TETHERING_VIRTUAL)
+                .setInterfaceName("avf_tap_fixed")
+                .build();
         runAsNetworkSettings((result) -> {
-            runStartTethering(result, request);
+            verifyStartTetheringRequestSucceeds(request, result);
             verifyNoMoreInteractionsForTethering();
         });
     }
@@ -599,84 +612,110 @@
         });
     }
 
-    private void verifyHasPermissionForStopTetheringRequest(TetheringRequest request,
+    private void verifyStopTetheringRequestSucceeds(final 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);
+        result.assertResult(NO_RESULT);
     }
 
-    private void verifyDoesNotHavePermissionForStopTetheringRequest(TetheringRequest request,
-            final TestTetheringResult result) throws Exception {
+    private void verifyStopTetheringRequestFails(final TetheringRequest request,
+            final TestTetheringResult result, int resultCode) throws Exception {
         mTetheringConnector.stopTetheringRequest(request, TEST_CALLER_PKG,
                 TEST_ATTRIBUTION_TAG, result);
         verify(mTethering, never()).stopTetheringRequest(any(), any());
+        reset(mTethering);
+        result.assertResult(resultCode);
+    }
+
+    private void verifyStopTetheringRequestWithWrongPackageFails(final TetheringRequest request,
+            final TestTetheringResult result) throws Exception {
+        mTetheringConnector.stopTetheringRequest(request, TEST_WRONG_PACKAGE,
+                TEST_ATTRIBUTION_TAG, result);
+        verify(mTethering, never()).stopTetheringRequest(any(), any());
+        reset(mTethering);
         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);
+            verifyStopTetheringRequestFails(WIFI_REQUEST_NO_CONFIG, result,
+                    TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION);
         });
 
         runAsTetherPrivileged((result) -> {
-            verifyHasPermissionForStopTetheringRequest(request, result);
+            verifyStopTetheringRequestSucceeds(WIFI_REQUEST_NO_CONFIG, result);
+        });
+
+        runAsTetherPrivileged((result) -> {
+            verifyStopTetheringRequestWithWrongPackageFails(WIFI_REQUEST_NO_CONFIG, result);
         });
 
         runAsWriteSettings((result) -> {
-            verifyDoesNotHavePermissionForStopTetheringRequest(request, result);
+            verifyStopTetheringRequestFails(WIFI_REQUEST_NO_CONFIG, result,
+                    TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION);
         });
 
         runAsWriteSettingsWhenWriteSettingsAllowed((result) -> {
-            verifyHasPermissionForStopTetheringRequest(request, result);
+            // Note: This can't happen in practice since WRITE_SETTINGS is only allowed on V- and
+            // stopTetheringRequest is only allowed on B+, but we test here for completeness.
+            verifyStopTetheringRequestSucceeds(WIFI_REQUEST_NO_CONFIG, result);
         });
 
         runAsTetheringDisallowed((result) -> {
-            verifyStopTetheringRequestWithTetheringDisallowed(request, result);
+            verifyStopTetheringRequestFails(WIFI_REQUEST_NO_CONFIG, result,
+                    TETHER_ERROR_UNSUPPORTED);
         });
 
         runAsNetworkSettings((result) -> {
-            verifyHasPermissionForStopTetheringRequest(request, result);
+            verifyStopTetheringRequestSucceeds(WIFI_REQUEST_NO_CONFIG, result);
         });
 
         // Not wifi -> fail
         runAsDeviceOwner((result) -> {
-            TetheringRequest notWifi = new TetheringRequest.Builder(TETHERING_USB).build();
-            verifyDoesNotHavePermissionForStopTetheringRequest(notWifi, result);
+            verifyStopTetheringRequestFails(USB_REQUEST, result,
+                    TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION);
         });
 
-        // No config -> fail
+        // No SoftApConfiguration -> fail
         runAsDeviceOwner((result) -> {
-            TetheringRequest noConfig = new TetheringRequest.Builder(TETHERING_WIFI).build();
-            verifyDoesNotHavePermissionForStopTetheringRequest(noConfig, result);
+            verifyStopTetheringRequestFails(WIFI_REQUEST_NO_CONFIG, result,
+                    TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION);
         });
 
-        // With config -> success
-        TetheringRequest withConfig = new TetheringRequest.Builder(TETHERING_WIFI)
-                .setSoftApConfiguration(new SoftApConfiguration.Builder().build())
-                .build();
+        // With SoftApConfiguration -> success
         runAsDeviceOwner((result) -> {
-            verifyHasPermissionForStopTetheringRequest(withConfig, result);
+            verifyStopTetheringRequestSucceeds(WIFI_REQUEST_WITH_CONFIG, result);
         });
 
         runAsDeviceOwnerWhenDeviceOwnerBypassNotEnabled((result) -> {
-            verifyDoesNotHavePermissionForStopTetheringRequest(withConfig, result);
+            verifyStopTetheringRequestFails(WIFI_REQUEST_WITH_CONFIG, result,
+                    TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION);
+        });
+
+        // Not wifi -> fail
+        runAsCarrierPrivileged((result) -> {
+            verifyStopTetheringRequestFails(USB_REQUEST, result,
+                    TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION);
+        });
+
+        // No SoftApConfiguration -> fail
+        runAsCarrierPrivileged((result) -> {
+            verifyStopTetheringRequestFails(WIFI_REQUEST_NO_CONFIG, result,
+                    TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION);
+        });
+
+        // With SoftApConfiguration -> success
+        runAsCarrierPrivileged((result) -> {
+            verifyStopTetheringRequestSucceeds(WIFI_REQUEST_WITH_CONFIG, result);
+        });
+
+        runAsCarrierPrivilegedWhenCarrierPrivilegeBypassNotEnabled((result) -> {
+            verifyStopTetheringRequestFails(WIFI_REQUEST_WITH_CONFIG, result,
+                    TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION);
         });
     }
 
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
index 57c3eca..2b70e39 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/util/TetheringPermissionsUtilsTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/util/TetheringPermissionsUtilsTest.java
@@ -67,4 +67,15 @@
         when(mDevicePolicyManager.isDeviceOwnerApp(TEST_PACKAGE)).thenReturn(true);
         assertThat(mTetheringPermissionsUtils.isDeviceOwner(TEST_UID, TEST_PACKAGE)).isTrue();
     }
+
+    @Test
+    public void testHasCarrierPrivilege() {
+        when(mTelephonyManager.checkCarrierPrivilegesForPackageAnyPhone(TEST_PACKAGE))
+                .thenReturn(TelephonyManager.CARRIER_PRIVILEGE_STATUS_NO_ACCESS);
+        assertThat(mTetheringPermissionsUtils.isCarrierPrivileged(TEST_PACKAGE)).isFalse();
+
+        when(mTelephonyManager.checkCarrierPrivilegesForPackageAnyPhone(TEST_PACKAGE))
+                .thenReturn(TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS);
+        assertThat(mTetheringPermissionsUtils.isCarrierPrivileged(TEST_PACKAGE)).isTrue();
+    }
 }
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/CarrierConfigRule.kt b/staticlibs/testutils/devicetests/com/android/testutils/CarrierConfigRule.kt
index c9d2527..076398e 100644
--- a/staticlibs/testutils/devicetests/com/android/testutils/CarrierConfigRule.kt
+++ b/staticlibs/testutils/devicetests/com/android/testutils/CarrierConfigRule.kt
@@ -40,6 +40,7 @@
 import com.android.testutils.runAsShell
 import com.android.testutils.tryTest
 import java.security.MessageDigest
+import kotlin.test.assertEquals
 import kotlin.test.assertNotNull
 import kotlin.test.assertTrue
 import org.junit.rules.TestRule
@@ -204,6 +205,7 @@
                 }
                 return@tryTest
             }
+            cv.close()
             if (hold) {
                 addConfigOverrides(subId, PersistableBundle().also {
                     it.putStringArray(CarrierConfigManager.KEY_CARRIER_CERTIFICATE_STRING_ARRAY,
@@ -212,6 +214,9 @@
             } else {
                 cleanUpNow()
             }
+            assertTrue(cv.block(CARRIER_CONFIG_CHANGE_TIMEOUT_MS),
+                "Timed out waiting for CarrierPrivilegesCallback")
+            assertEquals(cpb.hasPrivilege, hold, "Couldn't set carrier privilege")
         } cleanup @JvmSerializableLambda {
             runAsShell(READ_PRIVILEGED_PHONE_STATE) @JvmSerializableLambda {
                 tm.unregisterCarrierPrivilegesCallback(cpb)
diff --git a/tests/cts/tethering/src/android/tethering/cts/TetheringManagerTest.java b/tests/cts/tethering/src/android/tethering/cts/TetheringManagerTest.java
index abe628b..92d58e6 100644
--- a/tests/cts/tethering/src/android/tethering/cts/TetheringManagerTest.java
+++ b/tests/cts/tethering/src/android/tethering/cts/TetheringManagerTest.java
@@ -36,6 +36,7 @@
 import static android.net.TetheringManager.TETHER_ERROR_ENTITLEMENT_UNKNOWN;
 import static android.net.TetheringManager.TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION;
 import static android.net.TetheringManager.TETHER_ERROR_NO_ERROR;
+import static android.net.TetheringManager.TETHER_ERROR_UNKNOWN_REQUEST;
 import static android.net.cts.util.CtsTetheringUtils.isAnyIfaceMatch;
 import static android.os.Process.INVALID_UID;
 
@@ -714,4 +715,185 @@
         assertThrows(UnsupportedOperationException.class, () -> mTM.tether("iface"));
         assertThrows(UnsupportedOperationException.class, () -> mTM.untether("iface"));
     }
+
+    @Test
+    public void testCarrierPrivilegedIsTetheringSupported() throws Exception {
+        assumeTrue(SdkLevel.isAtLeastB());
+        assumeTrue(mPm.hasSystemFeature(FEATURE_TELEPHONY));
+        int defaultSubId = SubscriptionManager.getDefaultSubscriptionId();
+        mCarrierConfigRule.acquireCarrierPrivilege(defaultSubId);
+        final TestTetheringEventCallback tetherEventCallback =
+                mCtsTetheringUtils.registerTetheringEventCallback();
+        try {
+            tetherEventCallback.assumeWifiTetheringSupported(mContext);
+            tetherEventCallback.expectNoTetheringActive();
+
+            assertTrue(mTM.isTetheringSupported());
+        } finally {
+            mCtsTetheringUtils.unregisterTetheringEventCallback(tetherEventCallback);
+        }
+    }
+
+    @Test
+    public void testCarrierPrivilegedStartTetheringNonWifiFails() throws Exception {
+        assumeTrue(SdkLevel.isAtLeastB());
+        assumeTrue(mPm.hasSystemFeature(FEATURE_TELEPHONY));
+        int defaultSubId = SubscriptionManager.getDefaultSubscriptionId();
+        mCarrierConfigRule.acquireCarrierPrivilege(defaultSubId);
+        final TestTetheringEventCallback tetherEventCallback =
+                mCtsTetheringUtils.registerTetheringEventCallback();
+        try {
+            tetherEventCallback.assumeWifiTetheringSupported(mContext);
+            tetherEventCallback.expectNoTetheringActive();
+            StartTetheringCallback callback = new StartTetheringCallback();
+            TetheringRequest request = new TetheringRequest.Builder(TETHERING_USB).build();
+
+            mTM.startTethering(request, Runnable::run, callback);
+
+            callback.expectTetheringFailed(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION);
+        } finally {
+            mCtsTetheringUtils.unregisterTetheringEventCallback(tetherEventCallback);
+        }
+    }
+
+    @Test
+    public void testCarrierPrivilegedStartTetheringWifiWithoutConfigFails() throws Exception {
+        assumeTrue(SdkLevel.isAtLeastB());
+        assumeTrue(mPm.hasSystemFeature(FEATURE_TELEPHONY));
+        int defaultSubId = SubscriptionManager.getDefaultSubscriptionId();
+        mCarrierConfigRule.acquireCarrierPrivilege(defaultSubId);
+        final TestTetheringEventCallback tetherEventCallback =
+                mCtsTetheringUtils.registerTetheringEventCallback();
+        try {
+            tetherEventCallback.assumeWifiTetheringSupported(mContext);
+            tetherEventCallback.expectNoTetheringActive();
+            StartTetheringCallback callback = new StartTetheringCallback();
+            TetheringRequest request = new TetheringRequest.Builder(TETHERING_WIFI).build();
+
+            mTM.startTethering(request, Runnable::run, callback);
+
+            callback.expectTetheringFailed(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION);
+        } finally {
+            mCtsTetheringUtils.unregisterTetheringEventCallback(tetherEventCallback);
+        }
+    }
+
+    @Test
+    public void testCarrierPrivilegedStartTetheringWifiWithConfigSucceeds() throws Exception {
+        assumeTrue(SdkLevel.isAtLeastB());
+        assumeTrue(mPm.hasSystemFeature(FEATURE_TELEPHONY));
+        int defaultSubId = SubscriptionManager.getDefaultSubscriptionId();
+        mCarrierConfigRule.acquireCarrierPrivilege(defaultSubId);
+        final TestTetheringEventCallback tetherEventCallback =
+                mCtsTetheringUtils.registerTetheringEventCallback();
+        try {
+            tetherEventCallback.assumeWifiTetheringSupported(mContext);
+            tetherEventCallback.expectNoTetheringActive();
+            SoftApConfiguration softApConfig = createSoftApConfiguration("Carrier-privileged");
+
+            mCtsTetheringUtils.startWifiTetheringNoPermissions(tetherEventCallback, softApConfig);
+        } finally {
+            mCtsTetheringUtils.unregisterTetheringEventCallback(tetherEventCallback);
+        }
+    }
+
+    @Test
+    public void testCarrierPrivilegedStopTetheringNonWifiFails() throws Exception {
+        assumeTrue(SdkLevel.isAtLeastB());
+        assumeTrue(mPm.hasSystemFeature(FEATURE_TELEPHONY));
+        int defaultSubId = SubscriptionManager.getDefaultSubscriptionId();
+        mCarrierConfigRule.acquireCarrierPrivilege(defaultSubId);
+        final TestTetheringEventCallback tetherEventCallback =
+                mCtsTetheringUtils.registerTetheringEventCallback();
+        try {
+            tetherEventCallback.assumeWifiTetheringSupported(mContext);
+            tetherEventCallback.expectNoTetheringActive();
+            TetheringRequest request = new TetheringRequest.Builder(TETHERING_USB).build();
+            CtsTetheringUtils.StopTetheringCallback
+                    callback = new CtsTetheringUtils.StopTetheringCallback();
+
+            mTM.stopTethering(request, Runnable::run, callback);
+
+            callback.expectStopTetheringFailed(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION);
+        } finally {
+            mCtsTetheringUtils.unregisterTetheringEventCallback(tetherEventCallback);
+        }
+    }
+
+    @Test
+    public void testCarrierPrivilegedStopTetheringWifiWithoutConfigFails() throws Exception {
+        assumeTrue(SdkLevel.isAtLeastB());
+        assumeTrue(mPm.hasSystemFeature(FEATURE_TELEPHONY));
+        int defaultSubId = SubscriptionManager.getDefaultSubscriptionId();
+        mCarrierConfigRule.acquireCarrierPrivilege(defaultSubId);
+        final TestTetheringEventCallback tetherEventCallback =
+                mCtsTetheringUtils.registerTetheringEventCallback();
+        try {
+            tetherEventCallback.assumeWifiTetheringSupported(mContext);
+            tetherEventCallback.expectNoTetheringActive();
+            TetheringRequest request = new TetheringRequest.Builder(TETHERING_WIFI).build();
+            CtsTetheringUtils.StopTetheringCallback
+                    callback = new CtsTetheringUtils.StopTetheringCallback();
+
+            mTM.stopTethering(request, Runnable::run, callback);
+
+            callback.expectStopTetheringFailed(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION);
+        } finally {
+            mCtsTetheringUtils.unregisterTetheringEventCallback(tetherEventCallback);
+        }
+    }
+
+    @Test
+    public void testCarrierPrivilegedStopTetheringWifiWithConfigButNoActiveRequestFails()
+            throws Exception {
+        assumeTrue(SdkLevel.isAtLeastB());
+        assumeTrue(mPm.hasSystemFeature(FEATURE_TELEPHONY));
+        int defaultSubId = SubscriptionManager.getDefaultSubscriptionId();
+        mCarrierConfigRule.acquireCarrierPrivilege(defaultSubId);
+        final TestTetheringEventCallback tetherEventCallback =
+                mCtsTetheringUtils.registerTetheringEventCallback();
+        try {
+            tetherEventCallback.assumeWifiTetheringSupported(mContext);
+            tetherEventCallback.expectNoTetheringActive();
+            SoftApConfiguration softApConfig = createSoftApConfiguration("Carrier-privileged");
+            TetheringRequest request = new TetheringRequest.Builder(TETHERING_WIFI)
+                    .setSoftApConfiguration(softApConfig)
+                    .build();
+            CtsTetheringUtils.StopTetheringCallback
+                    callback = new CtsTetheringUtils.StopTetheringCallback();
+
+            mTM.stopTethering(request, Runnable::run, callback);
+
+            callback.expectStopTetheringFailed(TETHER_ERROR_UNKNOWN_REQUEST);
+        } finally {
+            mCtsTetheringUtils.unregisterTetheringEventCallback(tetherEventCallback);
+        }
+    }
+
+    @Test
+    public void testCarrierPrivilegedStopTetheringWifiWithConfigSucceeds() throws Exception {
+        assumeTrue(SdkLevel.isAtLeastB());
+        assumeTrue(mPm.hasSystemFeature(FEATURE_TELEPHONY));
+        int defaultSubId = SubscriptionManager.getDefaultSubscriptionId();
+        mCarrierConfigRule.acquireCarrierPrivilege(defaultSubId);
+        final TestTetheringEventCallback tetherEventCallback =
+                mCtsTetheringUtils.registerTetheringEventCallback();
+        try {
+            tetherEventCallback.assumeWifiTetheringSupported(mContext);
+            tetherEventCallback.expectNoTetheringActive();
+            SoftApConfiguration softApConfig = createSoftApConfiguration("Carrier-privileged");
+            mCtsTetheringUtils.startWifiTetheringNoPermissions(tetherEventCallback, softApConfig);
+            TetheringRequest request = new TetheringRequest.Builder(TETHERING_WIFI)
+                    .setSoftApConfiguration(softApConfig)
+                    .build();
+            CtsTetheringUtils.StopTetheringCallback
+                    callback = new CtsTetheringUtils.StopTetheringCallback();
+
+            mTM.stopTethering(request, Runnable::run, callback);
+
+            callback.verifyStopTetheringSucceeded();
+        } finally {
+            mCtsTetheringUtils.unregisterTetheringEventCallback(tetherEventCallback);
+        }
+    }
 }