Merge changes Id80bfca1,I8254c3f4 into main

* changes:
  clatd: one less shutdown log line
  clatd: rework sigterm & error handling
diff --git a/Tethering/apex/canned_fs_config b/Tethering/apex/canned_fs_config
index 1f5fcfa..edc5515 100644
--- a/Tethering/apex/canned_fs_config
+++ b/Tethering/apex/canned_fs_config
@@ -1,3 +1,3 @@
-/bin/for-system 0 1000 0750
+/bin/for-system 1029 1000 0750
 /bin/for-system/clatd 1029 1029 06755
 /bin/netbpfload 0 0 0750
diff --git a/Tethering/common/TetheringLib/src/android/net/TetheringManager.java b/Tethering/common/TetheringLib/src/android/net/TetheringManager.java
index 2f9c3bc..a8a471d 100644
--- a/Tethering/common/TetheringLib/src/android/net/TetheringManager.java
+++ b/Tethering/common/TetheringLib/src/android/net/TetheringManager.java
@@ -340,6 +340,11 @@
      * @hide
      */
     public static final int TETHER_ERROR_BLUETOOTH_SERVICE_PENDING = 19;
+    /**
+     * Never used outside Tethering.java.
+     * @hide
+     */
+    public static final int TETHER_ERROR_SOFT_AP_CALLBACK_PENDING = 20;
 
     /** @hide */
     @Retention(RetentionPolicy.SOURCE)
diff --git a/Tethering/src/com/android/networkstack/tethering/RequestTracker.java b/Tethering/src/com/android/networkstack/tethering/RequestTracker.java
index 9c61716..c91ff58 100644
--- a/Tethering/src/com/android/networkstack/tethering/RequestTracker.java
+++ b/Tethering/src/com/android/networkstack/tethering/RequestTracker.java
@@ -240,6 +240,11 @@
         mServingRequests.entrySet().removeIf(e -> e.getValue().getTetheringType() == type);
     }
 
+    @VisibleForTesting
+    List<TetheringRequest> getServingTetheringRequests() {
+        return new ArrayList<>(mServingRequests.values());
+    }
+
     /**
      * Returns an existing (pending or serving) request that fuzzy matches the given request.
      * Optionally specify matchUid to only return requests with the same uid.
diff --git a/Tethering/src/com/android/networkstack/tethering/Tethering.java b/Tethering/src/com/android/networkstack/tethering/Tethering.java
index c7ae353..0cf008b 100644
--- a/Tethering/src/com/android/networkstack/tethering/Tethering.java
+++ b/Tethering/src/com/android/networkstack/tethering/Tethering.java
@@ -47,6 +47,7 @@
 import static android.net.TetheringManager.TETHER_ERROR_INTERNAL_ERROR;
 import static android.net.TetheringManager.TETHER_ERROR_NO_ERROR;
 import static android.net.TetheringManager.TETHER_ERROR_SERVICE_UNAVAIL;
+import static android.net.TetheringManager.TETHER_ERROR_SOFT_AP_CALLBACK_PENDING;
 import static android.net.TetheringManager.TETHER_ERROR_UNAVAIL_IFACE;
 import static android.net.TetheringManager.TETHER_ERROR_UNKNOWN_IFACE;
 import static android.net.TetheringManager.TETHER_ERROR_UNKNOWN_REQUEST;
@@ -114,6 +115,7 @@
 import android.net.TetheringManager.TetheringRequest;
 import android.net.Uri;
 import android.net.ip.IpServer;
+import android.net.wifi.SoftApState;
 import android.net.wifi.WifiClient;
 import android.net.wifi.WifiManager;
 import android.net.wifi.p2p.WifiP2pGroup;
@@ -775,7 +777,7 @@
         final int result;
         switch (type) {
             case TETHERING_WIFI:
-                result = setWifiTethering(enable);
+                result = setWifiTethering(enable, request, listener);
                 break;
             case TETHERING_USB:
                 result = setUsbTethering(enable);
@@ -800,6 +802,9 @@
         // The result of Bluetooth tethering will be sent after the pan service connects.
         if (result == TETHER_ERROR_BLUETOOTH_SERVICE_PENDING) return;
 
+        // The result of Wifi tethering will be sent after the SoftApCallback result.
+        if (result == TETHER_ERROR_SOFT_AP_CALLBACK_PENDING) return;
+
         sendTetherResultAndRemoveOnError(request, listener, result);
     }
 
@@ -824,7 +829,8 @@
         }
     }
 
-    private int setWifiTethering(final boolean enable) {
+    private int setWifiTethering(final boolean enable, TetheringRequest request,
+            IIntResultListener listener) {
         final long ident = Binder.clearCallingIdentity();
         try {
             final WifiManager mgr = getWifiManager();
@@ -832,8 +838,34 @@
                 mLog.e("setWifiTethering: failed to get WifiManager!");
                 return TETHER_ERROR_SERVICE_UNAVAIL;
             }
-            if ((enable && mgr.startTetheredHotspot(null /* use existing softap config */))
-                    || (!enable && mgr.stopSoftAp())) {
+            final boolean success;
+            if (enable) {
+                if (isTetheringWithSoftApConfigEnabled()) {
+                    // Notes:
+                    // - A call to startTetheredHotspot can only succeed if the SoftAp is idle. If
+                    //   the SoftAp is running or is being disabled, the call will fail.
+                    // - If a call to startTetheredHotspot fails, the callback is immediately called
+                    //   with WIFI_AP_STATE_FAILED and a null interface.
+                    // - If a call to startTetheredHotspot succeeds, the passed-in callback is the
+                    //   only callback that will receive future WIFI_AP_STATE_ENABLED and
+                    //   WIFI_AP_STATE_DISABLED events in the future, until another call to
+                    //   startTetheredHotspot succeeds, at which point the old callback will stop
+                    //   receiving any events.
+                    // - Wifi may decide to restart the hotspot at any time (such as for a CC
+                    //   change), and if it does so, it will send WIFI_AP_STATE_DISABLED and then
+                    //   either WIFI_AP_STATE_ENABLED or (if restarting fails) WIFI_AP_STATE_FAILED.
+                    mgr.startTetheredHotspot(request, mExecutor,
+                            new StartTetheringSoftApCallback(listener));
+                    // Result isn't used since we get the real result via
+                    // StartTetheringSoftApCallback.
+                    return TETHER_ERROR_SOFT_AP_CALLBACK_PENDING;
+                }
+                success = mgr.startTetheredHotspot(null);
+            } else {
+                success = mgr.stopSoftAp();
+            }
+
+            if (success) {
                 return TETHER_ERROR_NO_ERROR;
             }
         } finally {
@@ -1458,6 +1490,9 @@
             final String ifname = intent.getStringExtra(EXTRA_WIFI_AP_INTERFACE_NAME);
             final int ipmode = intent.getIntExtra(EXTRA_WIFI_AP_MODE, IFACE_IP_MODE_UNSPECIFIED);
 
+            // In B+, Tethered AP is handled by StartTetheringSoftApCallback.
+            if (isTetheringWithSoftApConfigEnabled() && ipmode == IFACE_IP_MODE_TETHERED) return;
+
             switch (curState) {
                 case WifiManager.WIFI_AP_STATE_ENABLING:
                     // We can see this state on the way to both enabled and failure states.
@@ -1538,12 +1573,64 @@
         }
     }
 
+    class StartTetheringSoftApCallback implements SoftApCallback {
+
+        @Nullable
+        IIntResultListener mPendingListener;
+
+        StartTetheringSoftApCallback(IIntResultListener pendingListener) {
+            mPendingListener = pendingListener;
+        }
+
+        @Override
+        public void onStateChanged(SoftApState softApState) {
+            final int state = softApState.getState();
+            final String iface = softApState.getIface();
+            final TetheringRequest request = softApState.getTetheringRequest();
+            switch (softApState.getState()) {
+                case WifiManager.WIFI_AP_STATE_ENABLED:
+                    enableIpServing(request, iface);
+                    // If stopTethering has already been called, IP serving will still be started,
+                    // but as soon as the wifi code processes the stop, WIFI_AP_STATE_DISABLED will
+                    // be sent and tethering will be stopped again.
+                    sendTetherResultAndRemoveOnError(request, mPendingListener,
+                            TETHER_ERROR_NO_ERROR);
+                    mPendingListener = null;
+                    break;
+                case WifiManager.WIFI_AP_STATE_FAILED:
+                    // TODO: if a call to startTethering happens just after a call to stopTethering,
+                    // the start will fail because hotspot is still being disabled. This likely
+                    // cannot be fixed in tethering code but must be fixed in WiFi.
+                    sendTetherResultAndRemoveOnError(request, mPendingListener,
+                            TETHER_ERROR_INTERNAL_ERROR);
+                    mPendingListener = null;
+                    break;
+                case WifiManager.WIFI_AP_STATE_DISABLED:
+                    // TODO(b/403164072): SoftAP may restart due to CC change, in which we'll get
+                    // DISABLED -> ENABLED (or FAILED). Before the transition back to ENABLED is
+                    // complete, it is possible that a new Wifi request is accepted since there's no
+                    // active request to fuzzy-match it, which will unexpectedly cause Wifi to
+                    // overwrite this SoftApCallback. This should be fixed in Wifi to disallow any
+                    // new calls to startTetheredHotspot while SoftAP is restarting.
+                    disableWifiIpServing(iface, state);
+                    break;
+                default:
+                    break;
+            }
+        }
+    }
+
     @VisibleForTesting
     List<TetheringRequest> getPendingTetheringRequests() {
         return mRequestTracker.getPendingTetheringRequests();
     }
 
     @VisibleForTesting
+    List<TetheringRequest> getServingTetheringRequests() {
+        return mRequestTracker.getServingTetheringRequests();
+    }
+
+    @VisibleForTesting
     boolean isTetheringActive() {
         return getTetheredIfaces().length > 0;
     }
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
index 1083ef9..dc3cbd2 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
@@ -69,6 +69,7 @@
 import static android.net.wifi.WifiManager.IFACE_IP_MODE_TETHERED;
 import static android.net.wifi.WifiManager.WIFI_AP_STATE_DISABLED;
 import static android.net.wifi.WifiManager.WIFI_AP_STATE_ENABLED;
+import static android.net.wifi.WifiManager.WIFI_AP_STATE_FAILED;
 import static android.system.OsConstants.RT_SCOPE_UNIVERSE;
 import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
 
@@ -169,6 +170,7 @@
 import android.net.ip.IpServer;
 import android.net.ip.RouterAdvertisementDaemon;
 import android.net.wifi.SoftApConfiguration;
+import android.net.wifi.SoftApState;
 import android.net.wifi.WifiClient;
 import android.net.wifi.WifiManager;
 import android.net.wifi.WifiManager.SoftApCallback;
@@ -241,6 +243,7 @@
 import java.util.List;
 import java.util.Set;
 import java.util.Vector;
+import java.util.concurrent.Executor;
 
 @RunWith(AndroidJUnit4.class)
 @SmallTest
@@ -842,6 +845,38 @@
         mLooper.dispatchAll();
     }
 
+    private void sendStartTetheringSoftApCallback(int state, TetheringRequest request,
+            String ifname) {
+        ArgumentCaptor<SoftApCallback> callbackCaptor =
+                ArgumentCaptor.forClass(SoftApCallback.class);
+        verify(mWifiManager, atLeastOnce()).startTetheredHotspot(any(TetheringRequest.class),
+                any(Executor.class), callbackCaptor.capture());
+        SoftApState softApState = mock(SoftApState.class);
+        when(softApState.getState()).thenReturn(state);
+        when(softApState.getTetheringRequest()).thenReturn(request);
+        when(softApState.getIface()).thenReturn(ifname);
+        callbackCaptor.getValue().onStateChanged(softApState);
+        mLooper.dispatchAll();
+    }
+
+    private void verifyWifiTetheringRequested() {
+        if (mTetheringDependencies.isTetheringWithSoftApConfigEnabled()) {
+            verify(mWifiManager).startTetheredHotspot(any(), any(), any());
+        } else {
+            verify(mWifiManager).startTetheredHotspot(null);
+        }
+        verify(mWifiManager, never()).stopSoftAp();
+        verifyNoMoreInteractions(mWifiManager);
+    }
+
+    private void sendSoftApEvent(int state, TetheringRequest request, String ifname) {
+        if (mTetheringDependencies.isTetheringWithSoftApConfigEnabled()) {
+            sendStartTetheringSoftApCallback(state, request, ifname);
+        } else {
+            sendWifiApStateChanged(state, ifname, IFACE_IP_MODE_TETHERED);
+        }
+    }
+
     private static final String[] P2P_RECEIVER_PERMISSIONS_FOR_BROADCAST = {
             android.Manifest.permission.ACCESS_FINE_LOCATION,
             android.Manifest.permission.ACCESS_WIFI_STATE
@@ -1964,11 +1999,9 @@
         initTetheringOnTestThread();
 
         // Emulate pressing the WiFi tethering button.
-        mTethering.startTethering(createTetheringRequest(TETHERING_WIFI), TEST_CALLER_PKG,
-                null);
+        mTethering.startTethering(createTetheringRequest(TETHERING_WIFI), TEST_CALLER_PKG, null);
         mLooper.dispatchAll();
-        verify(mWifiManager, times(1)).startTetheredHotspot(null);
-        verifyNoMoreInteractions(mWifiManager);
+        verifyWifiTetheringRequested();
         verifyNoMoreInteractions(mNetd);
 
         // Emulate externally-visible WifiManager effects, causing the
@@ -1997,14 +2030,15 @@
     // TODO: Test with and without interfaceStatusChanged().
     @Test
     public void workingWifiTetheringEnrichedApBroadcast() throws Exception {
+        // B+ uses SoftApCallback instead of WIFI_AP_STATE_CHANGED for tethered hotspot.
+        mTetheringWithSoftApConfigEnabled = false;
         initTetheringOnTestThread();
 
         // Emulate pressing the WiFi tethering button.
         mTethering.startTethering(createTetheringRequest(TETHERING_WIFI), TEST_CALLER_PKG,
                 null);
         mLooper.dispatchAll();
-        verify(mWifiManager, times(1)).startTetheredHotspot(null);
-        verifyNoMoreInteractions(mWifiManager);
+        verifyWifiTetheringRequested();
         verifyNoMoreInteractions(mNetd);
 
         // Emulate externally-visible WifiManager effects, causing the
@@ -2042,6 +2076,122 @@
         verifyStopHotpot();
     }
 
+    @Test
+    public void startWifiTetheringWithSoftApConfigurationSuccess() throws Exception {
+        assumeTrue(mTetheringDependencies.isTetheringWithSoftApConfigEnabled());
+        initTetheringOnTestThread();
+
+        // Emulate pressing the WiFi tethering button.
+        TetheringRequest request = new TetheringRequest.Builder(TETHERING_WIFI)
+                .setSoftApConfiguration(new SoftApConfiguration.Builder()
+                        .setWifiSsid(WifiSsid.fromBytes("SSID".getBytes(StandardCharsets.UTF_8)))
+                        .build())
+                .build();
+        IIntResultListener startResultListener = mock(IIntResultListener.class);
+        mTethering.startTethering(request, TEST_CALLER_PKG, startResultListener);
+        mLooper.dispatchAll();
+        verifyNoMoreInteractions(mNetd);
+        verify(startResultListener, never()).onResult(anyInt());
+        // Emulate Wifi iface enabled
+        sendStartTetheringSoftApCallback(WIFI_AP_STATE_ENABLED, request, TEST_WLAN_IFNAME);
+
+        verifyStartHotspot();
+        verifyTetheringBroadcast(TEST_WLAN_IFNAME, EXTRA_ACTIVE_TETHER);
+        verify(startResultListener).onResult(TETHER_ERROR_NO_ERROR);
+    }
+
+    @Test
+    public void startWifiTetheringWithSoftApConfigurationFailure() throws Exception {
+        assumeTrue(mTetheringDependencies.isTetheringWithSoftApConfigEnabled());
+        initTetheringOnTestThread();
+
+        // Emulate pressing the WiFi tethering button.
+        TetheringRequest request = new TetheringRequest.Builder(TETHERING_WIFI)
+                .setSoftApConfiguration(new SoftApConfiguration.Builder()
+                        .setWifiSsid(WifiSsid.fromBytes("SSID".getBytes(StandardCharsets.UTF_8)))
+                        .build())
+                .build();
+        IIntResultListener startResultListener = mock(IIntResultListener.class);
+        mTethering.startTethering(request, TEST_CALLER_PKG, startResultListener);
+        mLooper.dispatchAll();
+        verify(startResultListener, never()).onResult(anyInt());
+        // Emulate Wifi iface failure
+        sendStartTetheringSoftApCallback(WIFI_AP_STATE_FAILED, request, TEST_WLAN_IFNAME);
+
+        verify(startResultListener).onResult(TETHER_ERROR_INTERNAL_ERROR);
+    }
+
+    @Test
+    public void startWifiTetheringWithSoftApConfigurationRestartAfterStarting() throws Exception {
+        assumeTrue(mTetheringDependencies.isTetheringWithSoftApConfigEnabled());
+        initTetheringOnTestThread();
+        TestTetheringEventCallback callback = new TestTetheringEventCallback();
+        SoftApConfiguration softApConfig = new SoftApConfiguration.Builder()
+                .setWifiSsid(WifiSsid.fromBytes("SSID".getBytes(StandardCharsets.UTF_8)))
+                .build();
+        final TetheringInterface wifiIface = new TetheringInterface(
+                TETHERING_WIFI, TEST_WLAN_IFNAME);
+        final TetheringInterface wifiIfaceWithConfig = new TetheringInterface(
+                TETHERING_WIFI, TEST_WLAN_IFNAME, softApConfig);
+
+        // 1. Register one callback before running any tethering.
+        mTethering.registerTetheringEventCallback(callback);
+        mLooper.dispatchAll();
+        assertTetherStatesNotNullButEmpty(callback.pollTetherStatesChanged());
+        // Emulate pressing the WiFi tethering button.
+        TetheringRequest request = new TetheringRequest.Builder(TETHERING_WIFI)
+                .setSoftApConfiguration(softApConfig)
+                .build();
+        IIntResultListener startResultListener = mock(IIntResultListener.class);
+        mTethering.startTethering(request, TEST_CALLER_PKG, startResultListener);
+        mLooper.dispatchAll();
+
+        // Wifi success
+        sendStartTetheringSoftApCallback(WIFI_AP_STATE_ENABLED, request, TEST_WLAN_IFNAME);
+        verifyStartHotspot();
+        TetherStatesParcel tetherState = callback.pollTetherStatesChanged();
+        assertArrayEquals(tetherState.availableList, new TetheringInterface[] {wifiIface});
+        tetherState = callback.pollTetherStatesChanged();
+        assertArrayEquals(tetherState.tetheredList, new TetheringInterface[] {wifiIfaceWithConfig});
+        verify(startResultListener).onResult(TETHER_ERROR_NO_ERROR);
+
+        // Restart Wifi
+        sendStartTetheringSoftApCallback(WIFI_AP_STATE_DISABLED, request, TEST_WLAN_IFNAME);
+        sendStartTetheringSoftApCallback(WIFI_AP_STATE_ENABLED, request, TEST_WLAN_IFNAME);
+
+        // Verify we go from TETHERED -> AVAILABLE -> TETHERED with the same config.
+        tetherState = callback.pollTetherStatesChanged();
+        assertArrayEquals(tetherState.availableList, new TetheringInterface[] {wifiIface});
+        tetherState = callback.pollTetherStatesChanged();
+        assertArrayEquals(tetherState.tetheredList, new TetheringInterface[] {wifiIfaceWithConfig});
+    }
+
+    @Test
+    public void startWifiApBroadcastDoesNotStartIpServing() throws Exception {
+        assumeTrue(mTetheringDependencies.isTetheringWithSoftApConfigEnabled());
+        initTetheringOnTestThread();
+
+        // Call startTethering for wifi
+        TetheringRequest request = new TetheringRequest.Builder(TETHERING_WIFI)
+                .setSoftApConfiguration(new SoftApConfiguration.Builder()
+                        .setWifiSsid(WifiSsid.fromBytes("SSID".getBytes(StandardCharsets.UTF_8)))
+                        .build())
+                .build();
+        IIntResultListener startResultListener = mock(IIntResultListener.class);
+        mTethering.startTethering(request, TEST_CALLER_PKG, startResultListener);
+        mLooper.dispatchAll();
+
+        // WIFI_AP_STATE_CHANGED broadcast should be ignored since we should only be using
+        // SoftApCallback for tethered AP.
+        sendWifiApStateChanged(WIFI_AP_STATE_ENABLED, TEST_WLAN_IFNAME, IFACE_IP_MODE_TETHERED);
+        sendWifiApStateChanged(WIFI_AP_STATE_DISABLED, TEST_WLAN_IFNAME, IFACE_IP_MODE_TETHERED);
+        verify(mNetd, never()).tetherStartWithConfiguration(any());
+        verify(mNotificationUpdater, never()).onDownstreamChanged(DOWNSTREAM_NONE);
+        verify(mWifiManager, never()).updateInterfaceIpState(
+                TEST_WLAN_IFNAME, WifiManager.IFACE_IP_MODE_UNSPECIFIED);
+        assertTrue(mTethering.getServingTetheringRequests().isEmpty());
+    }
+
     // TODO: Test with and without interfaceStatusChanged().
     @Test
     public void failureEnablingIpForwarding() throws Exception {
@@ -2049,11 +2199,10 @@
         doThrow(new RemoteException()).when(mNetd).ipfwdEnableForwarding(TETHERING_NAME);
 
         // Emulate pressing the WiFi tethering button.
-        mTethering.startTethering(createTetheringRequest(TETHERING_WIFI), TEST_CALLER_PKG,
-                null);
+        TetheringRequest request = createTetheringRequest(TETHERING_WIFI);
+        mTethering.startTethering(request, TEST_CALLER_PKG, null);
         mLooper.dispatchAll();
-        verify(mWifiManager, times(1)).startTetheredHotspot(null);
-        verifyNoMoreInteractions(mWifiManager);
+        verifyWifiTetheringRequested();
         verifyNoMoreInteractions(mNetd);
         verify(mTetheringMetrics).createBuilder(eq(TETHERING_WIFI), anyString());
 
@@ -2061,7 +2210,7 @@
         // per-interface state machine to start up, and telling us that
         // tethering mode is to be started.
         mTethering.interfaceStatusChanged(TEST_WLAN_IFNAME, true);
-        sendWifiApStateChanged(WIFI_AP_STATE_ENABLED, TEST_WLAN_IFNAME, IFACE_IP_MODE_TETHERED);
+        sendSoftApEvent(WIFI_AP_STATE_ENABLED, request, TEST_WLAN_IFNAME);
 
         // We verify get/set called three times here: twice for setup and once during
         // teardown because all events happen over the course of the single
@@ -2390,7 +2539,8 @@
         UpstreamNetworkState upstreamState = buildMobileDualStackUpstreamState();
         initTetheringUpstream(upstreamState);
 
-        mTethering.startTethering(createTetheringRequest(TETHERING_WIFI), TEST_CALLER_PKG,
+        TetheringRequest request = createTetheringRequest(TETHERING_WIFI);
+        mTethering.startTethering(request, TEST_CALLER_PKG,
                 null);
         mTethering.interfaceStatusChanged(TEST_WLAN_IFNAME, true);
         mLooper.dispatchAll();
@@ -2398,7 +2548,7 @@
             // Starting in B, ignore the interfaceStatusChanged
             callback.assertNoStateChangeCallback();
         }
-        sendWifiApStateChanged(WIFI_AP_STATE_ENABLED, TEST_WLAN_IFNAME, IFACE_IP_MODE_TETHERED);
+        sendSoftApEvent(WIFI_AP_STATE_ENABLED, request, TEST_WLAN_IFNAME);
         mLooper.dispatchAll();
         tetherState = callback.pollTetherStatesChanged();
         assertArrayEquals(tetherState.availableList, new TetheringInterface[] {wifiIface});
@@ -2426,8 +2576,7 @@
         if (isAtLeastT()) {
             // After T, tethering doesn't support WIFI_AP_STATE_DISABLED with null interface name.
             callback2.assertNoStateChangeCallback();
-            sendWifiApStateChanged(WIFI_AP_STATE_DISABLED, TEST_WLAN_IFNAME,
-                    IFACE_IP_MODE_TETHERED);
+            sendSoftApEvent(WIFI_AP_STATE_DISABLED, request, TEST_WLAN_IFNAME);
         }
         tetherState = callback2.pollTetherStatesChanged();
         assertArrayEquals(tetherState.availableList, new TetheringInterface[] {wifiIface});
@@ -2500,7 +2649,8 @@
         mLooper.dispatchAll();
         // Netd "up" event should not trigger a state change callback in B+.
         callback.assertNoStateChangeCallback();
-        sendWifiApStateChanged(WIFI_AP_STATE_ENABLED, TEST_WLAN_IFNAME, IFACE_IP_MODE_TETHERED);
+        sendStartTetheringSoftApCallback(WIFI_AP_STATE_ENABLED, tetheringRequest,
+                TEST_WLAN_IFNAME);
         // Verify we see  Available -> Tethered states
         assertArrayEquals(new TetheringInterface[] {wifiIfaceWithoutConfig},
                 callback.pollTetherStatesChanged().availableList);
@@ -2520,8 +2670,8 @@
         // Disable wifi tethering
         mTethering.stopTethering(TETHERING_WIFI);
         mLooper.dispatchAll();
-        sendWifiApStateChanged(WIFI_AP_STATE_DISABLED, TEST_WLAN_IFNAME,
-                    IFACE_IP_MODE_TETHERED);
+        sendStartTetheringSoftApCallback(WIFI_AP_STATE_DISABLED, tetheringRequest,
+                TEST_WLAN_IFNAME);
         assertArrayEquals(new TetheringInterface[] {wifiIfaceWithoutConfig},
                 callback.pollTetherStatesChanged().availableList);
         assertArrayEquals(new TetheringInterface[] {wifiIfaceWithoutConfig},
@@ -2566,7 +2716,7 @@
         ResultListener successListener = new ResultListener(TETHER_ERROR_NO_ERROR);
         mTethering.startTethering(tetheringRequest, TEST_CALLER_PKG, successListener);
         mLooper.dispatchAll();
-        successListener.assertHasResult();
+        successListener.assertDoesNotHaveResult();
 
         // Try starting wifi tethering with various fuzzy-matching requests and verify we get
         // TETHER_ERROR_DUPLICATE_REQUEST.
@@ -2581,8 +2731,7 @@
         ResultListener differentIpAddrListener = new ResultListener(TETHER_ERROR_DUPLICATE_REQUEST);
         mTethering.startTethering(differentIpAddr, TEST_CALLER_PKG, differentIpAddrListener);
         mLooper.dispatchAll();
-        verify(mWifiManager, times(1)).startTetheredHotspot(any());
-        verify(mWifiManager, never()).stopSoftAp();
+        verifyWifiTetheringRequested();
         differentIpAddrListener.assertHasResult();
 
         // Different UID
@@ -2593,13 +2742,13 @@
         mTethering.startTethering(differentUid, TEST_CALLER_PKG, differentUidListener);
         mLooper.dispatchAll();
         differentUidListener.assertHasResult();
-        verify(mWifiManager, times(1)).startTetheredHotspot(any());
-        verify(mWifiManager, never()).stopSoftAp();
+        verifyWifiTetheringRequested();
 
-        // Mock the link layer event to start IP serving and verify we still get
-        // TETHER_ERROR_DUPLICATE_REQUEST even though the request is no longer pending and is
-        // already serving.
-        sendWifiApStateChanged(WIFI_AP_STATE_ENABLED, TEST_WLAN_IFNAME, IFACE_IP_MODE_TETHERED);
+        // Mock the link layer event to start IP serving and verify
+        // 1) The original request's result listener is called.
+        // 2) We still get TETHER_ERROR_DUPLICATE_REQUEST for new requests.
+        sendStartTetheringSoftApCallback(WIFI_AP_STATE_ENABLED, tetheringRequest, TEST_WLAN_IFNAME);
+        successListener.assertHasResult();
         assertArrayEquals(new TetheringInterface[] {wifiIfaceWithoutConfig},
                 callback.pollTetherStatesChanged().availableList);
         assertArrayEquals(new TetheringInterface[] {wifiIfaceWithConfig},
@@ -2613,7 +2762,7 @@
         mLooper.dispatchAll();
         differentIpAddrListener.assertHasResult();
         differentUidListener.assertHasResult();
-        verify(mWifiManager, times(1)).startTetheredHotspot(any());
+        verify(mWifiManager, times(1)).startTetheredHotspot(any(), any(), any());
         verify(mWifiManager, never()).stopSoftAp();
     }
 
@@ -2630,8 +2779,8 @@
         ResultListener successListener = new ResultListener(TETHER_ERROR_NO_ERROR);
         mTethering.startTethering(tetheringRequest, TEST_CALLER_PKG, successListener);
         mLooper.dispatchAll();
+        sendStartTetheringSoftApCallback(WIFI_AP_STATE_ENABLED, tetheringRequest, TEST_WLAN_IFNAME);
         successListener.assertHasResult();
-        sendWifiApStateChanged(WIFI_AP_STATE_ENABLED, TEST_WLAN_IFNAME, IFACE_IP_MODE_TETHERED);
 
         // Starting wifi again will cause TETHER_ERROR_DUPLICATE_REQUEST
         ResultListener failureListener = new ResultListener(TETHER_ERROR_DUPLICATE_REQUEST);
@@ -2662,9 +2811,8 @@
         ResultListener successListener = new ResultListener(TETHER_ERROR_NO_ERROR);
         mTethering.startTethering(tetheringRequest, TEST_CALLER_PKG, successListener);
         mLooper.dispatchAll();
+        sendStartTetheringSoftApCallback(WIFI_AP_STATE_ENABLED, tetheringRequest, TEST_WLAN_IFNAME);
         successListener.assertHasResult();
-        sendWifiApStateChanged(WIFI_AP_STATE_ENABLED, TEST_WLAN_IFNAME, IFACE_IP_MODE_TETHERED);
-
         // Starting wifi again will cause TETHER_ERROR_DUPLICATE_REQUEST
         ResultListener failureListener = new ResultListener(TETHER_ERROR_DUPLICATE_REQUEST);
         mTethering.startTethering(tetheringRequest, TEST_CALLER_PKG, failureListener);
@@ -2672,12 +2820,15 @@
         failureListener.assertHasResult();
 
         // Trigger wifi ap state change to tell IpServer it's unwanted.
-        sendWifiApStateChanged(WIFI_AP_STATE_DISABLED, TEST_WLAN_IFNAME, IFACE_IP_MODE_TETHERED);
+        sendStartTetheringSoftApCallback(WIFI_AP_STATE_DISABLED, tetheringRequest,
+                TEST_WLAN_IFNAME);
 
         // We should be able to request the same Wifi again
         ResultListener successListener2 = new ResultListener(TETHER_ERROR_NO_ERROR);
         mTethering.startTethering(tetheringRequest, TEST_CALLER_PKG, successListener2);
         mLooper.dispatchAll();
+        sendStartTetheringSoftApCallback(WIFI_AP_STATE_ENABLED, tetheringRequest,
+                TEST_WLAN_IFNAME);
         successListener2.assertHasResult();
     }
 
@@ -2697,14 +2848,15 @@
         ResultListener successListener = new ResultListener(TETHER_ERROR_NO_ERROR);
         mTethering.startTethering(tetheringRequest, TEST_CALLER_PKG, successListener);
         mLooper.dispatchAll();
+        sendStartTetheringSoftApCallback(WIFI_AP_STATE_ENABLED, tetheringRequest, TEST_WLAN_IFNAME);
         successListener.assertHasResult();
-        sendWifiApStateChanged(WIFI_AP_STATE_ENABLED, TEST_WLAN_IFNAME, IFACE_IP_MODE_TETHERED);
 
         // We should be able to request the same Wifi again since the DHCP server transitioned the
         // IpServer back to InitialState.
         ResultListener successListener2 = new ResultListener(TETHER_ERROR_NO_ERROR);
         mTethering.startTethering(tetheringRequest, TEST_CALLER_PKG, successListener2);
         mLooper.dispatchAll();
+        sendStartTetheringSoftApCallback(WIFI_AP_STATE_ENABLED, tetheringRequest, TEST_WLAN_IFNAME);
         successListener2.assertHasResult();
     }
 
@@ -2721,7 +2873,10 @@
         ResultListener successListener = new ResultListener(TETHER_ERROR_NO_ERROR);
         mTethering.startTethering(tetheringRequest, TEST_CALLER_PKG, successListener);
         mLooper.dispatchAll();
-        successListener.assertHasResult();
+        ArgumentCaptor<SoftApCallback> callbackCaptor =
+                ArgumentCaptor.forClass(SoftApCallback.class);
+        verify(mWifiManager, atLeastOnce()).startTetheredHotspot(any(TetheringRequest.class),
+                any(Executor.class), callbackCaptor.capture());
 
         // Starting wifi again will cause TETHER_ERROR_DUPLICATE_REQUEST
         ResultListener failureListener = new ResultListener(TETHER_ERROR_DUPLICATE_REQUEST);
@@ -2736,6 +2891,20 @@
         ResultListener successListener2 = new ResultListener(TETHER_ERROR_NO_ERROR);
         mTethering.startTethering(tetheringRequest, TEST_CALLER_PKG, successListener2);
         mLooper.dispatchAll();
+        sendStartTetheringSoftApCallback(WIFI_AP_STATE_ENABLED, tetheringRequest, TEST_WLAN_IFNAME);
+        successListener2.assertHasResult();
+
+        // Mock the first request going up and then down from the stop request.
+        SoftApState softApState = mock(SoftApState.class);
+        when(softApState.getState()).thenReturn(WIFI_AP_STATE_ENABLED);
+        when(softApState.getTetheringRequest()).thenReturn(tetheringRequest);
+        when(softApState.getIface()).thenReturn(TEST_WLAN_IFNAME);
+        callbackCaptor.getValue().onStateChanged(softApState);
+        mLooper.dispatchAll();
+        successListener.assertHasResult();
+
+        // Mock the second request going up
+        sendStartTetheringSoftApCallback(WIFI_AP_STATE_ENABLED, tetheringRequest, TEST_WLAN_IFNAME);
         successListener2.assertHasResult();
     }
 
@@ -2752,8 +2921,8 @@
         ResultListener successListener = new ResultListener(TETHER_ERROR_NO_ERROR);
         mTethering.startTethering(tetheringRequest, TEST_CALLER_PKG, successListener);
         mLooper.dispatchAll();
+        sendStartTetheringSoftApCallback(WIFI_AP_STATE_ENABLED, tetheringRequest, TEST_WLAN_IFNAME);
         successListener.assertHasResult();
-        sendWifiApStateChanged(WIFI_AP_STATE_ENABLED, TEST_WLAN_IFNAME, IFACE_IP_MODE_TETHERED);
 
         // Starting wifi again will cause TETHER_ERROR_DUPLICATE_REQUEST
         ResultListener failureListener = new ResultListener(TETHER_ERROR_DUPLICATE_REQUEST);
@@ -2768,6 +2937,7 @@
         ResultListener successListener2 = new ResultListener(TETHER_ERROR_NO_ERROR);
         mTethering.startTethering(tetheringRequest, TEST_CALLER_PKG, successListener2);
         mLooper.dispatchAll();
+        sendStartTetheringSoftApCallback(WIFI_AP_STATE_ENABLED, tetheringRequest, TEST_WLAN_IFNAME);
         successListener2.assertHasResult();
     }
 
@@ -3216,7 +3386,7 @@
                         serverLinkAddr, clientLinkAddr, false, CONNECTIVITY_SCOPE_GLOBAL, null),
                 TEST_CALLER_PKG, null);
         mLooper.dispatchAll();
-        verify(mWifiManager, times(1)).startTetheredHotspot(any());
+        verifyWifiTetheringRequested();
         mTethering.interfaceStatusChanged(TEST_WLAN_IFNAME, true);
 
         // Call legacyTether on the interface before the link layer event comes back.
@@ -3588,8 +3758,11 @@
         reset(mDhcpServer);
 
         // Run wifi tethering.
+        TetheringRequest request = createTetheringRequest(TETHERING_WIFI);
+        mTethering.startTethering(request, TEST_CALLER_PKG, null);
+        mLooper.dispatchAll();
         mTethering.interfaceStatusChanged(TEST_WLAN_IFNAME, true);
-        sendWifiApStateChanged(WIFI_AP_STATE_ENABLED, TEST_WLAN_IFNAME, IFACE_IP_MODE_TETHERED);
+        sendSoftApEvent(WIFI_AP_STATE_ENABLED, request, TEST_WLAN_IFNAME);
         verify(mDhcpServer, timeout(DHCPSERVER_START_TIMEOUT_MS)).startWithCallbacks(
                 any(), dhcpEventCbsCaptor.capture());
         eventCallbacks = dhcpEventCbsCaptor.getValue();
@@ -3652,8 +3825,12 @@
         });
         callback.expectTetheredClientChanged(Collections.emptyList());
 
+        TetheringRequest request = createTetheringRequest(TETHERING_WIFI);
+        mTethering.startTethering(request, TEST_CALLER_PKG, null);
+        mLooper.dispatchAll();
+        verifyWifiTetheringRequested();
         mTethering.interfaceStatusChanged(TEST_WLAN_IFNAME, true);
-        sendWifiApStateChanged(WIFI_AP_STATE_ENABLED, TEST_WLAN_IFNAME, IFACE_IP_MODE_TETHERED);
+        sendSoftApEvent(WIFI_AP_STATE_ENABLED, request, TEST_WLAN_IFNAME);
         final ArgumentCaptor<IDhcpEventCallbacks> dhcpEventCbsCaptor =
                  ArgumentCaptor.forClass(IDhcpEventCallbacks.class);
         verify(mDhcpServer, timeout(DHCPSERVER_START_TIMEOUT_MS)).startWithCallbacks(
@@ -4184,8 +4361,11 @@
     @Test
     public void testIpv4AddressForSapAndLohsConcurrency() throws Exception {
         initTetheringOnTestThread();
+        TetheringRequest request = createTetheringRequest(TETHERING_WIFI);
+        mTethering.startTethering(request, TEST_CALLER_PKG, null);
+        mLooper.dispatchAll();
         mTethering.interfaceStatusChanged(TEST_WLAN_IFNAME, true);
-        sendWifiApStateChanged(WIFI_AP_STATE_ENABLED, TEST_WLAN_IFNAME, IFACE_IP_MODE_TETHERED);
+        sendSoftApEvent(WIFI_AP_STATE_ENABLED, request, TEST_WLAN_IFNAME);
 
         ArgumentCaptor<InterfaceConfigurationParcel> ifaceConfigCaptor =
                 ArgumentCaptor.forClass(InterfaceConfigurationParcel.class);
@@ -4234,14 +4414,13 @@
         assertEquals(TETHER_ERROR_NO_ERROR, mTethering.getLastErrorForTest(TEST_P2P_IFNAME));
 
         // Emulate pressing the WiFi tethering button.
-        mTethering.startTethering(createTetheringRequest(TETHERING_WIFI), TEST_CALLER_PKG,
-                null);
+        TetheringRequest request = createTetheringRequest(TETHERING_WIFI);
+        mTethering.startTethering(request, TEST_CALLER_PKG, null);
         mLooper.dispatchAll();
-        verify(mWifiManager).startTetheredHotspot(null);
-        verifyNoMoreInteractions(mWifiManager);
+        verifyWifiTetheringRequested();
 
         mTethering.interfaceStatusChanged(TEST_WLAN_IFNAME, true);
-        sendWifiApStateChanged(WIFI_AP_STATE_ENABLED, TEST_WLAN_IFNAME, IFACE_IP_MODE_TETHERED);
+        sendSoftApEvent(WIFI_AP_STATE_ENABLED, request, TEST_WLAN_IFNAME);
 
         verifyTetheringBroadcast(TEST_WLAN_IFNAME, EXTRA_AVAILABLE_TETHER);
         verify(mWifiManager).updateInterfaceIpState(
diff --git a/bpf/progs/netd.c b/bpf/progs/netd.c
index 08635b3..b146e45 100644
--- a/bpf/progs/netd.c
+++ b/bpf/progs/netd.c
@@ -597,7 +597,7 @@
 //    V |     |      |  x   |  x  |  x   |  x   |  x  |  x  |      | (netbpfload)
 //    U |     |  x   |  x   |  x  |  x   |  x   |  x  |     |      |
 //    T |  x  |  x   |  x   |  x  |  x   |  x   |     |     |      | (magic netbpfload)
-//    S |  x  |  x   |  x   |  x  |  x   |      |     |     |      | (platform loads offload)
+//    S |  x  |  x   |  x   |  x  |  x   |      |     |     |      | (dns netbpfload for offload)
 //    R |  x  |  x   |  x   |  x  |      |      |     |     |      | (no mainline ebpf)
 //
 // Not relevant for eBPF, but R can also run on 4.4
diff --git a/service/jni/com_android_server_connectivity_ClatCoordinator.cpp b/service/jni/com_android_server_connectivity_ClatCoordinator.cpp
index c0082bb..622fba8 100644
--- a/service/jni/com_android_server_connectivity_ClatCoordinator.cpp
+++ b/service/jni/com_android_server_connectivity_ClatCoordinator.cpp
@@ -104,7 +104,7 @@
     // First verify the clatd directory and binary,
     // since this is built into the apex file system image,
     // failures here are 99% likely to be build problems.
-    V(kClatdDir, S_IFDIR|0750, ROOT, SYSTEM, "system_file", DIR);
+    V(kClatdDir, S_IFDIR|0750, CLAT, SYSTEM, "system_file", DIR);
     V(kClatdBin, S_IFREG|S_ISUID|S_ISGID|0755, CLAT, CLAT, "clatd_exec", BIN);
 
     // Move on to verifying that the bpf programs and maps are as expected.
diff --git a/staticlibs/tests/unit/host/python/apf_utils_test.py b/staticlibs/tests/unit/host/python/apf_utils_test.py
index 348df3b..8059e22 100644
--- a/staticlibs/tests/unit/host/python/apf_utils_test.py
+++ b/staticlibs/tests/unit/host/python/apf_utils_test.py
@@ -26,7 +26,7 @@
     get_apf_counter,
     get_apf_counters_from_dumpsys,
     get_ipv4_addresses,
-    get_ipv6_addresses,
+    get_non_tentative_ipv6_addresses,
     get_hardware_address,
     is_send_raw_packet_downstream_supported,
     is_packet_capture_supported,
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/CarrierConfigRule.kt b/staticlibs/testutils/devicetests/com/android/testutils/CarrierConfigRule.kt
index ae0de79..c9d2527 100644
--- a/staticlibs/testutils/devicetests/com/android/testutils/CarrierConfigRule.kt
+++ b/staticlibs/testutils/devicetests/com/android/testutils/CarrierConfigRule.kt
@@ -18,20 +18,28 @@
 
 import android.Manifest.permission.MODIFY_PHONE_STATE
 import android.Manifest.permission.READ_PHONE_STATE
+import android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE
 import android.content.BroadcastReceiver
 import android.content.Context
 import android.content.Intent
 import android.content.IntentFilter
+import android.content.pm.PackageManager
 import android.os.ConditionVariable
+import android.os.ParcelFileDescriptor
 import android.os.PersistableBundle
+import android.os.Process
 import android.telephony.CarrierConfigManager
 import android.telephony.CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED
+import android.telephony.SubscriptionManager
 import android.telephony.SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX
+import android.telephony.TelephonyManager
+import android.telephony.TelephonyManager.CarrierPrivilegesCallback
 import android.util.Log
 import androidx.test.platform.app.InstrumentationRegistry
-import com.android.modules.utils.build.SdkLevel.isAtLeastU
+import com.android.modules.utils.build.SdkLevel
 import com.android.testutils.runAsShell
 import com.android.testutils.tryTest
+import java.security.MessageDigest
 import kotlin.test.assertNotNull
 import kotlin.test.assertTrue
 import org.junit.rules.TestRule
@@ -46,12 +54,20 @@
  * configuration automatically on teardown.
  */
 class CarrierConfigRule : TestRule {
+    private val HEX_CHARS: CharArray = charArrayOf(
+        '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'
+    )
+
     private val context by lazy { InstrumentationRegistry.getInstrumentation().context }
+    private val uiAutomation by lazy { InstrumentationRegistry.getInstrumentation().uiAutomation }
     private val ccm by lazy { context.getSystemService(CarrierConfigManager::class.java) }
 
     // Map of (subId) -> (original values of overridden settings)
     private val originalConfigs = mutableMapOf<Int, PersistableBundle>()
 
+    // Map of (subId) -> (original values of carrier service package)
+    private val originalCarrierServicePackages = mutableMapOf<Int, String?>()
+
     override fun apply(base: Statement, description: Description): Statement {
         return CarrierConfigStatement(base, description)
     }
@@ -118,6 +134,177 @@
         }
     }
 
+    private fun runShellCommand(cmd: String) {
+        val fd: ParcelFileDescriptor = uiAutomation.executeShellCommand(cmd)
+        fd.close() // Don't care about the output.
+    }
+
+    /**
+     * Converts a byte array into a String of hexadecimal characters.
+     *
+     * @param bytes an array of bytes
+     * @return hex string representation of bytes array
+     */
+    private fun bytesToHexString(bytes: ByteArray?): String? {
+        if (bytes == null) return null
+
+        val ret = StringBuilder(2 * bytes.size)
+
+        for (i in bytes.indices) {
+            var b: Int
+            b = 0x0f and (bytes[i].toInt() shr 4)
+            ret.append(HEX_CHARS[b])
+            b = 0x0f and bytes[i].toInt()
+            ret.append(HEX_CHARS[b])
+        }
+
+        return ret.toString()
+    }
+
+    private fun setHoldCarrierPrivilege(hold: Boolean, subId: Int) {
+        if (!SdkLevel.isAtLeastT()) {
+            throw UnsupportedOperationException(
+                "Acquiring carrier privilege requires at least T SDK"
+            )
+        }
+
+        fun getCertHash(): String {
+            val pkgInfo = context.packageManager.getPackageInfo(
+                context.opPackageName,
+                PackageManager.GET_SIGNATURES
+            )
+            val digest = MessageDigest.getInstance("SHA-256")
+            val certHash = digest.digest(pkgInfo.signatures!![0]!!.toByteArray())
+            return bytesToHexString(certHash)!!
+        }
+
+        val tm = context.getSystemService(TelephonyManager::class.java)!!
+
+        val cv = ConditionVariable()
+        val cpb = PrivilegeWaiterCallback(cv)
+        // The lambda below is capturing |cpb|, whose type inherits from a class that appeared in
+        // T. This means the lambda will compile as a private method of this class taking a
+        // PrivilegeWaiterCallback argument. As JUnit uses reflection to enumerate all methods
+        // including private methods, this would fail with a link error when running on S-.
+        // To solve this, make the lambda serializable, which causes the compiler to emit a
+        // synthetic class instead of a synthetic method.
+        tryTest @JvmSerializableLambda {
+            val slotIndex = SubscriptionManager.getSlotIndex(subId)!!
+            runAsShell(READ_PRIVILEGED_PHONE_STATE) @JvmSerializableLambda {
+                tm.registerCarrierPrivilegesCallback(slotIndex, { it.run() }, cpb)
+            }
+            // Wait for the callback to be registered
+            assertTrue(cv.block(CARRIER_CONFIG_CHANGE_TIMEOUT_MS),
+                "Can't register CarrierPrivilegesCallback")
+            if (cpb.hasPrivilege == hold) {
+                if (hold) {
+                    Log.w(TAG, "Package ${context.opPackageName} already is privileged")
+                } else {
+                    Log.w(TAG, "Package ${context.opPackageName} already isn't privileged")
+                }
+                return@tryTest
+            }
+            if (hold) {
+                addConfigOverrides(subId, PersistableBundle().also {
+                    it.putStringArray(CarrierConfigManager.KEY_CARRIER_CERTIFICATE_STRING_ARRAY,
+                        arrayOf(getCertHash()))
+                })
+            } else {
+                cleanUpNow()
+            }
+        } cleanup @JvmSerializableLambda {
+            runAsShell(READ_PRIVILEGED_PHONE_STATE) @JvmSerializableLambda {
+                tm.unregisterCarrierPrivilegesCallback(cpb)
+            }
+        }
+    }
+
+    /**
+     * Acquires carrier privilege on the given subscription ID.
+     */
+    fun acquireCarrierPrivilege(subId: Int) = setHoldCarrierPrivilege(true, subId)
+
+    /**
+     * Drops carrier privilege from the given subscription ID.
+     */
+    fun dropCarrierPrivilege(subId: Int) = setHoldCarrierPrivilege(false, subId)
+
+    /**
+     * Sets the carrier service package override for the given subscription ID. A null argument will
+     * clear any previously-set override.
+     */
+    fun setCarrierServicePackageOverride(subId: Int, pkg: String?) {
+        if (!SdkLevel.isAtLeastU()) {
+            throw UnsupportedOperationException(
+                "Setting carrier service package override requires at least U SDK"
+            )
+        }
+
+        val tm = context.getSystemService(TelephonyManager::class.java)!!
+
+        val cv = ConditionVariable()
+        val cpb = CarrierServiceChangedWaiterCallback(cv)
+        // The lambda below is capturing |cpb|, whose type inherits from a class that appeared in
+        // T. This means the lambda will compile as a private method of this class taking a
+        // PrivilegeWaiterCallback argument. As JUnit uses reflection to enumerate all methods
+        // including private methods, this would fail with a link error when running on S-.
+        // To solve this, make the lambda serializable, which causes the compiler to emit a
+        // synthetic class instead of a synthetic method.
+        tryTest @JvmSerializableLambda {
+            val slotIndex = SubscriptionManager.getSlotIndex(subId)!!
+            runAsShell(READ_PRIVILEGED_PHONE_STATE) @JvmSerializableLambda {
+                tm.registerCarrierPrivilegesCallback(slotIndex, { it.run() }, cpb)
+            }
+            // Wait for the callback to be registered
+            assertTrue(cv.block(CARRIER_CONFIG_CHANGE_TIMEOUT_MS),
+                "Can't register CarrierPrivilegesCallback")
+            if (cpb.pkgName == pkg) {
+                Log.w(TAG, "Carrier service package was already $pkg")
+                return@tryTest
+            }
+            if (!originalCarrierServicePackages.contains(subId)) {
+                originalCarrierServicePackages.put(subId, cpb.pkgName)
+            }
+            cv.close()
+            runAsShell(MODIFY_PHONE_STATE) {
+                if (null == pkg) {
+                    // There is a bug in clear-carrier-service-package-override where not adding
+                    // the -s argument will use the wrong slot index : b/299604822
+                    runShellCommand("cmd phone clear-carrier-service-package-override" +
+                            " -s $subId")
+                } else {
+                    runShellCommand("cmd phone set-carrier-service-package-override $pkg" +
+                            " -s $subId")
+                }
+            }
+            assertTrue(cv.block(CARRIER_CONFIG_CHANGE_TIMEOUT_MS),
+                "Can't modify carrier service package")
+        } cleanup @JvmSerializableLambda {
+            runAsShell(READ_PRIVILEGED_PHONE_STATE) @JvmSerializableLambda {
+                tm.unregisterCarrierPrivilegesCallback(cpb)
+            }
+        }
+    }
+
+    private class PrivilegeWaiterCallback(private val cv: ConditionVariable) :
+        CarrierPrivilegesCallback {
+        var hasPrivilege = false
+        override fun onCarrierPrivilegesChanged(p: MutableSet<String>, uids: MutableSet<Int>) {
+            hasPrivilege = uids.contains(Process.myUid())
+            cv.open()
+        }
+    }
+
+    private class CarrierServiceChangedWaiterCallback(private val cv: ConditionVariable) :
+        CarrierPrivilegesCallback {
+        var pkgName: String? = null
+        override fun onCarrierPrivilegesChanged(p: MutableSet<String>, u: MutableSet<Int>) {}
+        override fun onCarrierServiceChanged(pkgName: String?, uid: Int) {
+            this.pkgName = pkgName
+            cv.open()
+        }
+    }
+
     /**
      * Cleanup overrides that were added by the test case.
      *
@@ -138,6 +325,10 @@
             }
             originalConfigs.clear()
         }
+        originalCarrierServicePackages.forEach { (subId, pkg) ->
+            setCarrierServicePackageOverride(subId, pkg)
+        }
+        originalCarrierServicePackages.clear()
     }
 }
 
@@ -145,7 +336,7 @@
     subId: Int,
     keys: Set<String>
 ): PersistableBundle {
-    return if (isAtLeastU()) {
+    return if (SdkLevel.isAtLeastU()) {
         // This method is U+
         getConfigForSubId(subId, *keys.toTypedArray())
     } else {
diff --git a/staticlibs/testutils/host/python/apf_test_base.py b/staticlibs/testutils/host/python/apf_test_base.py
index 2552aa3..33b3838 100644
--- a/staticlibs/testutils/host/python/apf_test_base.py
+++ b/staticlibs/testutils/host/python/apf_test_base.py
@@ -60,10 +60,10 @@
     self.client_ipv4_addresses = apf_utils.get_ipv4_addresses(
         self.clientDevice, self.client_iface_name
     )
-    self.server_ipv6_addresses = apf_utils.get_ipv6_addresses(
+    self.server_ipv6_addresses = apf_utils.get_non_tentative_ipv6_addresses(
         self.serverDevice, self.server_iface_name
     )
-    self.client_ipv6_addresses = apf_utils.get_ipv6_addresses(
+    self.client_ipv6_addresses = apf_utils.get_non_tentative_ipv6_addresses(
         self.clientDevice, self.client_iface_name
     )
 
diff --git a/staticlibs/testutils/host/python/apf_utils.py b/staticlibs/testutils/host/python/apf_utils.py
index c2ad18e..1648d36 100644
--- a/staticlibs/testutils/host/python/apf_utils.py
+++ b/staticlibs/testutils/host/python/apf_utils.py
@@ -116,12 +116,12 @@
   else:
     return []
 
-def get_ipv6_addresses(
+def get_non_tentative_ipv6_addresses(
     ad: android_device.AndroidDevice, iface_name: str
 ) -> list[str]:
-  """Retrieves the IPv6 addresses of a given interface on an Android device.
+  """Retrieves the non-tentative IPv6 addresses of a given interface on an Android device.
 
-  This function executes an ADB shell command (`ip -6 address show`) to get the
+  This function executes an ADB shell command (`ip -6 address show -tentative`) to get the
   network interface information and extracts the IPv6 address from the output.
   If devices have no IPv6 address, raise PatternNotFoundException.
 
@@ -139,7 +139,7 @@
   #         valid_lft forever preferred_lft forever
   #     inet6 fe80::1233:aadb:3d32:1234/64 scope link
   #         valid_lft forever preferred_lft forever
-  output = adb_utils.adb_shell(ad, f"ip -6 address show {iface_name}")
+  output = adb_utils.adb_shell(ad, f"ip -6 address show -tentative {iface_name}")
   pattern = r"inet6\s+([0-9a-fA-F:]+)\/\d+"
   matches = re.findall(pattern, output)
 
diff --git a/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt b/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt
index 5e035a2..6dc23ff 100644
--- a/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt
+++ b/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt
@@ -15,7 +15,6 @@
  */
 package android.net.cts
 
-import android.Manifest.permission.MODIFY_PHONE_STATE
 import android.Manifest.permission.NETWORK_SETTINGS
 import android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE
 import android.app.Instrumentation
@@ -80,12 +79,10 @@
 import android.net.cts.NetworkAgentTest.TestableQosCallback.CallbackEntry.OnQosSessionLost
 import android.net.wifi.WifiInfo
 import android.os.Build
-import android.os.ConditionVariable
 import android.os.Handler
 import android.os.HandlerThread
 import android.os.Looper
 import android.os.Message
-import android.os.PersistableBundle
 import android.os.Process
 import android.os.SystemClock
 import android.platform.test.annotations.AppModeFull
@@ -94,19 +91,15 @@
 import android.system.OsConstants.IPPROTO_TCP
 import android.system.OsConstants.IPPROTO_UDP
 import android.system.OsConstants.SOCK_DGRAM
-import android.telephony.CarrierConfigManager
 import android.telephony.SubscriptionManager
 import android.telephony.TelephonyManager
-import android.telephony.TelephonyManager.CarrierPrivilegesCallback
 import android.telephony.data.EpsBearerQosSessionAttributes
 import android.util.ArraySet
 import android.util.DebugUtils.valueToString
-import android.util.Log
 import androidx.test.InstrumentationRegistry
 import com.android.compatibility.common.util.SystemUtil.runShellCommand
 import com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity
 import com.android.compatibility.common.util.ThrowingSupplier
-import com.android.compatibility.common.util.UiccUtil
 import com.android.modules.utils.build.SdkLevel
 import com.android.net.module.util.ArrayTrackRecord
 import com.android.net.module.util.NetworkStackConstants.ETHER_MTU
@@ -151,7 +144,6 @@
 import java.net.InetSocketAddress
 import java.net.Socket
 import java.nio.ByteBuffer
-import java.security.MessageDigest
 import java.time.Duration
 import java.util.Arrays
 import java.util.Random
@@ -708,102 +700,6 @@
         doTestAllowedUids(transports, uid, expectUidsPresent, specifier, transportInfo)
     }
 
-    private fun setHoldCarrierPrivilege(hold: Boolean, subId: Int) {
-        fun getCertHash(): String {
-            val pkgInfo = realContext.packageManager.getPackageInfo(
-                realContext.opPackageName,
-                PackageManager.GET_SIGNATURES
-            )
-            val digest = MessageDigest.getInstance("SHA-256")
-            val certHash = digest.digest(pkgInfo.signatures!![0]!!.toByteArray())
-            return UiccUtil.bytesToHexString(certHash)!!
-        }
-
-        val tm = realContext.getSystemService(TelephonyManager::class.java)!!
-
-        val cv = ConditionVariable()
-        val cpb = PrivilegeWaiterCallback(cv)
-        // The lambda below is capturing |cpb|, whose type inherits from a class that appeared in
-        // T. This means the lambda will compile as a private method of this class taking a
-        // PrivilegeWaiterCallback argument. As JUnit uses reflection to enumerate all methods
-        // including private methods, this would fail with a link error when running on S-.
-        // To solve this, make the lambda serializable, which causes the compiler to emit a
-        // synthetic class instead of a synthetic method.
-        tryTest @JvmSerializableLambda {
-            val slotIndex = SubscriptionManager.getSlotIndex(subId)!!
-            runAsShell(READ_PRIVILEGED_PHONE_STATE) @JvmSerializableLambda {
-                tm.registerCarrierPrivilegesCallback(slotIndex, { it.run() }, cpb)
-            }
-            // Wait for the callback to be registered
-            assertTrue(cv.block(DEFAULT_TIMEOUT_MS), "Can't register CarrierPrivilegesCallback")
-            if (cpb.hasPrivilege == hold) {
-                if (hold) {
-                    Log.w(TAG, "Package ${realContext.opPackageName} already is privileged")
-                } else {
-                    Log.w(TAG, "Package ${realContext.opPackageName} already isn't privileged")
-                }
-                return@tryTest
-            }
-            if (hold) {
-                carrierConfigRule.addConfigOverrides(subId, PersistableBundle().also {
-                    it.putStringArray(CarrierConfigManager.KEY_CARRIER_CERTIFICATE_STRING_ARRAY,
-                        arrayOf(getCertHash()))
-                })
-            } else {
-                carrierConfigRule.cleanUpNow()
-            }
-        } cleanup @JvmSerializableLambda {
-            runAsShell(READ_PRIVILEGED_PHONE_STATE) @JvmSerializableLambda {
-                tm.unregisterCarrierPrivilegesCallback(cpb)
-            }
-        }
-    }
-
-    private fun acquireCarrierPrivilege(subId: Int) = setHoldCarrierPrivilege(true, subId)
-    private fun dropCarrierPrivilege(subId: Int) = setHoldCarrierPrivilege(false, subId)
-
-    private fun setCarrierServicePackageOverride(subId: Int, pkg: String?) {
-        val tm = realContext.getSystemService(TelephonyManager::class.java)!!
-
-        val cv = ConditionVariable()
-        val cpb = CarrierServiceChangedWaiterCallback(cv)
-        // The lambda below is capturing |cpb|, whose type inherits from a class that appeared in
-        // T. This means the lambda will compile as a private method of this class taking a
-        // PrivilegeWaiterCallback argument. As JUnit uses reflection to enumerate all methods
-        // including private methods, this would fail with a link error when running on S-.
-        // To solve this, make the lambda serializable, which causes the compiler to emit a
-        // synthetic class instead of a synthetic method.
-        tryTest @JvmSerializableLambda {
-            val slotIndex = SubscriptionManager.getSlotIndex(subId)!!
-            runAsShell(READ_PRIVILEGED_PHONE_STATE) @JvmSerializableLambda {
-                tm.registerCarrierPrivilegesCallback(slotIndex, { it.run() }, cpb)
-            }
-            // Wait for the callback to be registered
-            assertTrue(cv.block(DEFAULT_TIMEOUT_MS), "Can't register CarrierPrivilegesCallback")
-            if (cpb.pkgName == pkg) {
-                Log.w(TAG, "Carrier service package was already $pkg")
-                return@tryTest
-            }
-            cv.close()
-            runAsShell(MODIFY_PHONE_STATE) {
-                if (null == pkg) {
-                    // There is a bug is clear-carrier-service-package-override where not adding
-                    // the -s argument will use the wrong slot index : b/299604822
-                    runShellCommand("cmd phone clear-carrier-service-package-override" +
-                            " -s $subId")
-                } else {
-                    // -s could set the subId, but this test works with the default subId.
-                    runShellCommand("cmd phone set-carrier-service-package-override $pkg")
-                }
-            }
-            assertTrue(cv.block(DEFAULT_TIMEOUT_MS), "Can't modify carrier service package")
-        } cleanup @JvmSerializableLambda {
-            runAsShell(READ_PRIVILEGED_PHONE_STATE) @JvmSerializableLambda {
-                tm.unregisterCarrierPrivilegesCallback(cpb)
-            }
-        }
-    }
-
     private fun String.execute() = runShellCommand(this).trim()
 
     @Test
@@ -856,8 +752,8 @@
             if (!SdkLevel.isAtLeastU()) return@tryTest
             // Acquiring carrier privilege is necessary to override the carrier service package.
             val defaultSlotIndex = SubscriptionManager.getSlotIndex(defaultSubId)
-            acquireCarrierPrivilege(defaultSubId)
-            setCarrierServicePackageOverride(defaultSubId, servicePackage)
+            carrierConfigRule.acquireCarrierPrivilege(defaultSubId)
+            carrierConfigRule.setCarrierServicePackageOverride(defaultSubId, servicePackage)
             val actualServicePackage: String? = runAsShell(READ_PRIVILEGED_PHONE_STATE) {
                 tm.getCarrierServicePackageNameForLogicalSlot(defaultSlotIndex)
             }
@@ -896,10 +792,6 @@
                     expectUidsPresent = false)
             doTestAllowedUidsWithSubId(defaultSubId, intArrayOf(TRANSPORT_CELLULAR, TRANSPORT_WIFI),
                     uid, expectUidsPresent = false)
-        } cleanupStep {
-            if (SdkLevel.isAtLeastU()) setCarrierServicePackageOverride(defaultSubId, null)
-        } cleanup {
-            if (SdkLevel.isAtLeastU()) dropCarrierPrivilege(defaultSubId)
         }
     }
 
@@ -2001,25 +1893,3 @@
         doTestNativeNetworkCreation(expectCreatedImmediately = true, intArrayOf(TRANSPORT_VPN))
     }
 }
-
-// Subclasses of CarrierPrivilegesCallback can't be inline, or they'll be compiled as
-// inner classes of the test class and will fail resolution on R as the test harness
-// uses reflection to list all methods and classes
-class PrivilegeWaiterCallback(private val cv: ConditionVariable) :
-        CarrierPrivilegesCallback {
-    var hasPrivilege = false
-    override fun onCarrierPrivilegesChanged(p: MutableSet<String>, uids: MutableSet<Int>) {
-        hasPrivilege = uids.contains(Process.myUid())
-        cv.open()
-    }
-}
-
-class CarrierServiceChangedWaiterCallback(private val cv: ConditionVariable) :
-        CarrierPrivilegesCallback {
-    var pkgName: String? = null
-    override fun onCarrierPrivilegesChanged(p: MutableSet<String>, u: MutableSet<Int>) {}
-    override fun onCarrierServiceChanged(pkgName: String?, uid: Int) {
-        this.pkgName = pkgName
-        cv.open()
-    }
-}
diff --git a/thread/service/java/com/android/server/thread/ThreadNetworkCountryCode.java b/thread/service/java/com/android/server/thread/ThreadNetworkCountryCode.java
index ff0e2c1..a96d06e 100644
--- a/thread/service/java/com/android/server/thread/ThreadNetworkCountryCode.java
+++ b/thread/service/java/com/android/server/thread/ThreadNetworkCountryCode.java
@@ -16,6 +16,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;
@@ -28,11 +29,13 @@
 import android.location.Address;
 import android.location.Geocoder;
 import android.location.Location;
+import android.location.LocationListener;
 import android.location.LocationManager;
 import android.net.thread.IOperationReceiver;
 import android.net.wifi.WifiManager;
 import android.net.wifi.WifiManager.ActiveCountryCodeChangedCallback;
 import android.os.Build;
+import android.os.Bundle;
 import android.sysprop.ThreadNetworkProperties;
 import android.telephony.SubscriptionInfo;
 import android.telephony.SubscriptionManager;
@@ -115,6 +118,13 @@
             new ArrayMap();
     private final ThreadPersistentSettings mPersistentSettings;
 
+    @Nullable private LocationListener mLocationListener;
+    @Nullable private WifiCountryCodeCallback mWifiCountryCodeCallback;
+    @Nullable private BroadcastReceiver mTelephonyBroadcastReceiver;
+
+    /** Indicates whether the Thread co-processor supports setting the country code. */
+    private boolean mIsCpSettingCountryCodeSupported = true;
+
     @Nullable private CountryCodeInfo mCurrentCountryCodeInfo;
     @Nullable private CountryCodeInfo mLocationCountryCodeInfo;
     @Nullable private CountryCodeInfo mOverrideCountryCodeInfo;
@@ -267,13 +277,40 @@
         updateCountryCode(false /* forceUpdate */);
     }
 
+    private synchronized void unregisterAllCountryCodeCallbacks() {
+        unregisterGeocoderCountryCodeCallback();
+        unregisterWifiCountryCodeCallback();
+        unregisterTelephonyCountryCodeCallback();
+    }
+
     private synchronized void registerGeocoderCountryCodeCallback() {
         if ((mGeocoder != null) && isLocationUseForCountryCodeEnabled()) {
+            mLocationListener =
+                    new LocationListener() {
+                        public void onLocationChanged(Location location) {
+                            setCountryCodeFromGeocodingLocation(location);
+                        }
+
+                        // not used yet
+                        public void onProviderDisabled(String provider) {}
+
+                        public void onProviderEnabled(String provider) {}
+
+                        public void onStatusChanged(String provider, int status, Bundle extras) {}
+                    };
+
             mLocationManager.requestLocationUpdates(
                     LocationManager.PASSIVE_PROVIDER,
                     TIME_BETWEEN_LOCATION_UPDATES_MS,
                     DISTANCE_BETWEEN_LOCALTION_UPDATES_METERS,
-                    location -> setCountryCodeFromGeocodingLocation(location));
+                    mLocationListener);
+        }
+    }
+
+    private synchronized void unregisterGeocoderCountryCodeCallback() {
+        if (mLocationListener != null) {
+            mLocationManager.removeUpdates(mLocationListener);
+            mLocationListener = null;
         }
     }
 
@@ -313,8 +350,16 @@
 
     private synchronized void registerWifiCountryCodeCallback() {
         if (mWifiManager != null) {
+            mWifiCountryCodeCallback = new WifiCountryCodeCallback();
             mWifiManager.registerActiveCountryCodeChangedCallback(
-                    r -> r.run(), new WifiCountryCodeCallback());
+                    r -> r.run(), mWifiCountryCodeCallback);
+        }
+    }
+
+    private synchronized void unregisterWifiCountryCodeCallback() {
+        if ((mWifiManager != null) && (mWifiCountryCodeCallback != null)) {
+            mWifiManager.unregisterActiveCountryCodeChangedCallback(mWifiCountryCodeCallback);
+            mWifiCountryCodeCallback = null;
         }
     }
 
@@ -353,7 +398,7 @@
             return;
         }
 
-        BroadcastReceiver broadcastReceiver =
+        mTelephonyBroadcastReceiver =
                 new BroadcastReceiver() {
                     @Override
                     public void onReceive(Context context, Intent intent) {
@@ -377,11 +422,18 @@
                 };
 
         mContext.registerReceiver(
-                broadcastReceiver,
+                mTelephonyBroadcastReceiver,
                 new IntentFilter(TelephonyManager.ACTION_NETWORK_COUNTRY_CHANGED),
                 Context.RECEIVER_EXPORTED);
     }
 
+    private synchronized void unregisterTelephonyCountryCodeCallback() {
+        if (mTelephonyBroadcastReceiver != null) {
+            mContext.unregisterReceiver(mTelephonyBroadcastReceiver);
+            mTelephonyBroadcastReceiver = null;
+        }
+    }
+
     private synchronized void updateTelephonyCountryCodeFromSimCard() {
         List<SubscriptionInfo> subscriptionInfoList =
                 mSubscriptionManager.getActiveSubscriptionInfoList();
@@ -520,6 +572,11 @@
 
             @Override
             public void onError(int otError, String message) {
+                if (otError == ERROR_UNSUPPORTED_FEATURE) {
+                    mIsCpSettingCountryCodeSupported = false;
+                    unregisterAllCountryCodeCallbacks();
+                }
+
                 LOG.e(
                         "Error "
                                 + otError
@@ -546,6 +603,11 @@
             return;
         }
 
+        if (!mIsCpSettingCountryCodeSupported) {
+            LOG.i("Thread co-processor doesn't support setting the country code");
+            return;
+        }
+
         LOG.i("Set country code: " + countryCodeInfo);
         mThreadNetworkControllerService.setCountryCode(
                 countryCodeInfo.getCountryCode().toUpperCase(Locale.ROOT),
@@ -592,6 +654,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("mIsCpSettingCountryCodeSupported: " + mIsCpSettingCountryCodeSupported);
         pw.println("mOverrideCountryCodeInfo        : " + mOverrideCountryCodeInfo);
         pw.println("mTelephonyCountryCodeSlotInfoMap: " + mTelephonyCountryCodeSlotInfoMap);
         pw.println("mTelephonyCountryCodeInfo       : " + mTelephonyCountryCodeInfo);
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 139f4c8..6eb9b50 100644
--- a/thread/tests/unit/src/com/android/server/thread/ThreadNetworkCountryCodeTest.java
+++ b/thread/tests/unit/src/com/android/server/thread/ThreadNetworkCountryCodeTest.java
@@ -17,6 +17,7 @@
 package com.android.server.thread;
 
 import static android.net.thread.ThreadNetworkException.ERROR_INTERNAL_ERROR;
+import static android.net.thread.ThreadNetworkException.ERROR_UNSUPPORTED_FEATURE;
 
 import static com.android.server.thread.ThreadNetworkCountryCode.DEFAULT_COUNTRY_CODE;
 import static com.android.server.thread.ThreadPersistentSettings.KEY_COUNTRY_CODE;
@@ -109,6 +110,7 @@
 
     private ThreadNetworkCountryCode mThreadNetworkCountryCode;
     private boolean mErrorSetCountryCode;
+    private boolean mErrorUnsupportedFeatureSetCountryCode;
 
     @Captor private ArgumentCaptor<LocationListener> mLocationListenerCaptor;
     @Captor private ArgumentCaptor<Geocoder.GeocodeListener> mGeocodeListenerCaptor;
@@ -143,6 +145,10 @@
 
                     if (mErrorSetCountryCode) {
                         cb.onError(ERROR_INTERNAL_ERROR, new String("Invalid country code"));
+                    } else if (mErrorUnsupportedFeatureSetCountryCode) {
+                        cb.onError(
+                                ERROR_UNSUPPORTED_FEATURE,
+                                new String("Setting country code is not supported"));
                     } else {
                         cb.onSuccess();
                     }
@@ -453,6 +459,39 @@
     }
 
     @Test
+    public void setCountryCodeNotSupported_returnUnsupportedFeatureError_countryCodeNotSetAgain() {
+        mThreadNetworkCountryCode.initialize();
+        assertThat(mThreadNetworkCountryCode.getCountryCode()).isEqualTo(DEFAULT_COUNTRY_CODE);
+
+        mErrorUnsupportedFeatureSetCountryCode = true;
+        mThreadNetworkCountryCode.setOverrideCountryCode(TEST_COUNTRY_CODE_CN);
+        verify(mThreadNetworkControllerService)
+                .setCountryCode(eq(TEST_COUNTRY_CODE_CN), mOperationReceiverCaptor.capture());
+
+        mThreadNetworkCountryCode.setOverrideCountryCode(TEST_COUNTRY_CODE_US);
+        verifyNoMoreInteractions(mThreadNetworkControllerService);
+
+        assertThat(mThreadNetworkCountryCode.getCountryCode()).isEqualTo(DEFAULT_COUNTRY_CODE);
+    }
+
+    @Test
+    public void setCountryCodeNotSupported_returnUnsupportedFeatureError_unregisterAllCallbacks() {
+        mThreadNetworkCountryCode.initialize();
+        assertThat(mThreadNetworkCountryCode.getCountryCode()).isEqualTo(DEFAULT_COUNTRY_CODE);
+
+        mErrorUnsupportedFeatureSetCountryCode = true;
+        mThreadNetworkCountryCode.setOverrideCountryCode(TEST_COUNTRY_CODE_CN);
+        verify(mThreadNetworkControllerService)
+                .setCountryCode(eq(TEST_COUNTRY_CODE_CN), mOperationReceiverCaptor.capture());
+
+        verify(mLocationManager).removeUpdates(mLocationListenerCaptor.capture());
+        verify(mWifiManager)
+                .unregisterActiveCountryCodeChangedCallback(
+                        mWifiCountryCodeReceiverCaptor.capture());
+        verify(mContext).unregisterReceiver(mTelephonyCountryCodeReceiverCaptor.capture());
+    }
+
+    @Test
     public void settingsCountryCode_settingsCountryCodeIsActive_settingsCountryCodeIsUsed() {
         when(mPersistentSettings.get(KEY_COUNTRY_CODE)).thenReturn(TEST_COUNTRY_CODE_CN);
         mThreadNetworkCountryCode.initialize();
@@ -468,6 +507,7 @@
         mThreadNetworkCountryCode.dump(new FileDescriptor(), printWriter, null);
         String outputString = stringWriter.toString();
 
+        assertThat(outputString).contains("mIsCpSettingCountryCodeSupported");
         assertThat(outputString).contains("mOverrideCountryCodeInfo");
         assertThat(outputString).contains("mTelephonyCountryCodeSlotInfoMap");
         assertThat(outputString).contains("mTelephonyCountryCodeInfo");