Merge "[Thread] add service unit test skeleton" into main
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsRecordRepository.java b/service-t/src/com/android/server/connectivity/mdns/MdnsRecordRepository.java
index f9951a9..6b6632c 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsRecordRepository.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsRecordRepository.java
@@ -321,7 +321,6 @@
      *
      * @param serviceId An existing service ID.
      * @param subtypes New subtypes
-     * @return
      */
     public void updateService(int serviceId, @NonNull Set<String> subtypes) {
         final ServiceRegistration existingRegistration = mServices.get(serviceId);
diff --git a/thread/service/Android.bp b/thread/service/Android.bp
index b5fee95..69295cc 100644
--- a/thread/service/Android.bp
+++ b/thread/service/Android.bp
@@ -36,6 +36,7 @@
         "framework-connectivity-pre-jarjar",
         "framework-connectivity-t-pre-jarjar",
         "framework-location.stubs.module_lib",
+        "framework-wifi",
         "service-connectivity-pre-jarjar",
         "ServiceConnectivityResources",
     ],
diff --git a/thread/service/java/com/android/server/thread/ThreadNetworkCountryCode.java b/thread/service/java/com/android/server/thread/ThreadNetworkCountryCode.java
index df2c56e..b7b6233 100644
--- a/thread/service/java/com/android/server/thread/ThreadNetworkCountryCode.java
+++ b/thread/service/java/com/android/server/thread/ThreadNetworkCountryCode.java
@@ -19,12 +19,22 @@
 import android.annotation.Nullable;
 import android.annotation.StringDef;
 import android.annotation.TargetApi;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
 import android.location.Address;
 import android.location.Geocoder;
 import android.location.Location;
 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.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
+import android.util.ArrayMap;
 import android.util.Log;
 
 import com.android.connectivity.resources.R;
@@ -38,11 +48,13 @@
 import java.time.Instant;
 import java.util.List;
 import java.util.Locale;
+import java.util.Map;
 import java.util.Objects;
 
 /**
  * Provide functions for making changes to Thread Network country code. This Country Code is from
- * location. This class sends Country Code to Thread Network native layer.
+ * location, WiFi or telephony configuration. This class sends Country Code to Thread Network native
+ * layer.
  *
  * <p>This class is thread-safe.
  */
@@ -66,23 +78,39 @@
                 COUNTRY_CODE_SOURCE_DEFAULT,
                 COUNTRY_CODE_SOURCE_LOCATION,
                 COUNTRY_CODE_SOURCE_OVERRIDE,
+                COUNTRY_CODE_SOURCE_TELEPHONY,
+                COUNTRY_CODE_SOURCE_TELEPHONY_LAST,
+                COUNTRY_CODE_SOURCE_WIFI,
             })
     private @interface CountryCodeSource {}
 
     private static final String COUNTRY_CODE_SOURCE_DEFAULT = "Default";
     private static final String COUNTRY_CODE_SOURCE_LOCATION = "Location";
     private static final String COUNTRY_CODE_SOURCE_OVERRIDE = "Override";
+    private static final String COUNTRY_CODE_SOURCE_TELEPHONY = "Telephony";
+    private static final String COUNTRY_CODE_SOURCE_TELEPHONY_LAST = "TelephonyLast";
+    private static final String COUNTRY_CODE_SOURCE_WIFI = "Wifi";
+
     private static final CountryCodeInfo DEFAULT_COUNTRY_CODE_INFO =
             new CountryCodeInfo(DEFAULT_COUNTRY_CODE, COUNTRY_CODE_SOURCE_DEFAULT);
 
     private final ConnectivityResources mResources;
+    private final Context mContext;
     private final LocationManager mLocationManager;
     @Nullable private final Geocoder mGeocoder;
     private final ThreadNetworkControllerService mThreadNetworkControllerService;
+    private final WifiManager mWifiManager;
+    private final TelephonyManager mTelephonyManager;
+    private final SubscriptionManager mSubscriptionManager;
+    private final Map<Integer, TelephonyCountryCodeSlotInfo> mTelephonyCountryCodeSlotInfoMap =
+            new ArrayMap();
 
     @Nullable private CountryCodeInfo mCurrentCountryCodeInfo;
     @Nullable private CountryCodeInfo mLocationCountryCodeInfo;
     @Nullable private CountryCodeInfo mOverrideCountryCodeInfo;
+    @Nullable private CountryCodeInfo mWifiCountryCodeInfo;
+    @Nullable private CountryCodeInfo mTelephonyCountryCodeInfo;
+    @Nullable private CountryCodeInfo mTelephonyLastCountryCodeInfo;
 
     /** Container class to store Thread country code information. */
     private static final class CountryCodeInfo {
@@ -125,6 +153,27 @@
         }
     }
 
+    /** Container class to store country code per SIM slot. */
+    private static final class TelephonyCountryCodeSlotInfo {
+        public int slotIndex;
+        public String countryCode;
+        public String lastKnownCountryCode;
+        public Instant timestamp;
+
+        @Override
+        public String toString() {
+            return "TelephonyCountryCodeSlotInfo{ slotIndex: "
+                    + slotIndex
+                    + ", countryCode: "
+                    + countryCode
+                    + ", lastKnownCountryCode: "
+                    + lastKnownCountryCode
+                    + ", timestamp: "
+                    + timestamp
+                    + "}";
+        }
+    }
+
     private boolean isLocationUseForCountryCodeEnabled() {
         return mResources
                 .get()
@@ -135,16 +184,40 @@
             LocationManager locationManager,
             ThreadNetworkControllerService threadNetworkControllerService,
             @Nullable Geocoder geocoder,
-            ConnectivityResources resources) {
+            ConnectivityResources resources,
+            WifiManager wifiManager,
+            Context context,
+            TelephonyManager telephonyManager,
+            SubscriptionManager subscriptionManager) {
         mLocationManager = locationManager;
         mThreadNetworkControllerService = threadNetworkControllerService;
         mGeocoder = geocoder;
         mResources = resources;
+        mWifiManager = wifiManager;
+        mContext = context;
+        mTelephonyManager = telephonyManager;
+        mSubscriptionManager = subscriptionManager;
+    }
+
+    public static ThreadNetworkCountryCode newInstance(
+            Context context, ThreadNetworkControllerService controllerService) {
+        return new ThreadNetworkCountryCode(
+                context.getSystemService(LocationManager.class),
+                controllerService,
+                Geocoder.isPresent() ? new Geocoder(context) : null,
+                new ConnectivityResources(context),
+                context.getSystemService(WifiManager.class),
+                context,
+                context.getSystemService(TelephonyManager.class),
+                context.getSystemService(SubscriptionManager.class));
     }
 
     /** Sets up this country code module to listen to location country code changes. */
     public synchronized void initialize() {
         registerGeocoderCountryCodeCallback();
+        registerWifiCountryCodeCallback();
+        registerTelephonyCountryCodeCallback();
+        updateTelephonyCountryCodeFromSimCard();
         updateCountryCode(false /* forceUpdate */);
     }
 
@@ -193,13 +266,157 @@
                 this::geocodeListener);
     }
 
+    private synchronized void registerWifiCountryCodeCallback() {
+        if (mWifiManager != null) {
+            mWifiManager.registerActiveCountryCodeChangedCallback(
+                    r -> r.run(), new WifiCountryCodeCallback());
+        }
+    }
+
+    private class WifiCountryCodeCallback implements ActiveCountryCodeChangedCallback {
+        @Override
+        public void onActiveCountryCodeChanged(String countryCode) {
+            Log.d(TAG, "Wifi country code is changed to " + countryCode);
+            synchronized ("ThreadNetworkCountryCode.this") {
+                mWifiCountryCodeInfo = new CountryCodeInfo(countryCode, COUNTRY_CODE_SOURCE_WIFI);
+                updateCountryCode(false /* forceUpdate */);
+            }
+        }
+
+        @Override
+        public void onCountryCodeInactive() {
+            Log.d(TAG, "Wifi country code is inactived");
+            synchronized ("ThreadNetworkCountryCode.this") {
+                mWifiCountryCodeInfo = null;
+                updateCountryCode(false /* forceUpdate */);
+            }
+        }
+    }
+
+    private synchronized void registerTelephonyCountryCodeCallback() {
+        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
+            Log.wtf(
+                    TAG,
+                    "Unexpected call to register the telephony country code changed callback, "
+                            + "Thread code never runs under T or lower.");
+            return;
+        }
+
+        BroadcastReceiver broadcastReceiver =
+                new BroadcastReceiver() {
+                    @Override
+                    public void onReceive(Context context, Intent intent) {
+                        int slotIndex =
+                                intent.getIntExtra(
+                                        SubscriptionManager.EXTRA_SLOT_INDEX,
+                                        SubscriptionManager.INVALID_SIM_SLOT_INDEX);
+                        String lastKnownCountryCode = null;
+                        String countryCode =
+                                intent.getStringExtra(TelephonyManager.EXTRA_NETWORK_COUNTRY);
+
+                        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
+                            lastKnownCountryCode =
+                                    intent.getStringExtra(
+                                            TelephonyManager.EXTRA_LAST_KNOWN_NETWORK_COUNTRY);
+                        }
+
+                        setTelephonyCountryCodeAndLastKnownCountryCode(
+                                slotIndex, countryCode, lastKnownCountryCode);
+                    }
+                };
+
+        mContext.registerReceiver(
+                broadcastReceiver,
+                new IntentFilter(TelephonyManager.ACTION_NETWORK_COUNTRY_CHANGED),
+                Context.RECEIVER_EXPORTED);
+    }
+
+    private synchronized void updateTelephonyCountryCodeFromSimCard() {
+        List<SubscriptionInfo> subscriptionInfoList =
+                mSubscriptionManager.getActiveSubscriptionInfoList();
+
+        if (subscriptionInfoList == null) {
+            Log.d(TAG, "No SIM card is found");
+            return;
+        }
+
+        for (SubscriptionInfo subscriptionInfo : subscriptionInfoList) {
+            String countryCode;
+            int slotIndex;
+
+            slotIndex = subscriptionInfo.getSimSlotIndex();
+            try {
+                countryCode = mTelephonyManager.getNetworkCountryIso(slotIndex);
+            } catch (IllegalArgumentException e) {
+                Log.e(TAG, "Failed to get country code for slot index:" + slotIndex, e);
+                continue;
+            }
+
+            Log.d(TAG, "Telephony slot " + slotIndex + " country code is " + countryCode);
+            setTelephonyCountryCodeAndLastKnownCountryCode(
+                    slotIndex, countryCode, null /* lastKnownCountryCode */);
+        }
+    }
+
+    private synchronized void setTelephonyCountryCodeAndLastKnownCountryCode(
+            int slotIndex, String countryCode, String lastKnownCountryCode) {
+        Log.d(
+                TAG,
+                "Set telephony country code to: "
+                        + countryCode
+                        + ", last country code to: "
+                        + lastKnownCountryCode
+                        + " for slotIndex: "
+                        + slotIndex);
+
+        TelephonyCountryCodeSlotInfo telephonyCountryCodeInfoSlot =
+                mTelephonyCountryCodeSlotInfoMap.computeIfAbsent(
+                        slotIndex, k -> new TelephonyCountryCodeSlotInfo());
+        telephonyCountryCodeInfoSlot.slotIndex = slotIndex;
+        telephonyCountryCodeInfoSlot.timestamp = Instant.now();
+        telephonyCountryCodeInfoSlot.countryCode = countryCode;
+        telephonyCountryCodeInfoSlot.lastKnownCountryCode = lastKnownCountryCode;
+
+        mTelephonyCountryCodeInfo = null;
+        mTelephonyLastCountryCodeInfo = null;
+
+        for (TelephonyCountryCodeSlotInfo slotInfo : mTelephonyCountryCodeSlotInfoMap.values()) {
+            if ((mTelephonyCountryCodeInfo == null) && isValidCountryCode(slotInfo.countryCode)) {
+                mTelephonyCountryCodeInfo =
+                        new CountryCodeInfo(
+                                slotInfo.countryCode,
+                                COUNTRY_CODE_SOURCE_TELEPHONY,
+                                slotInfo.timestamp);
+            }
+
+            if ((mTelephonyLastCountryCodeInfo == null)
+                    && isValidCountryCode(slotInfo.lastKnownCountryCode)) {
+                mTelephonyLastCountryCodeInfo =
+                        new CountryCodeInfo(
+                                slotInfo.lastKnownCountryCode,
+                                COUNTRY_CODE_SOURCE_TELEPHONY_LAST,
+                                slotInfo.timestamp);
+            }
+        }
+
+        updateCountryCode(false /* forceUpdate */);
+    }
+
     /**
      * Priority order of country code sources (we stop at the first known country code source):
      *
      * <ul>
      *   <li>1. Override country code - Country code forced via shell command (local/automated
      *       testing)
-     *   <li>2. Location Country code - Country code retrieved from LocationManager passive location
+     *   <li>2. Telephony country code - Current country code retrieved via cellular. If there are
+     *       multiple SIM's, the country code chosen is non-deterministic if they return different
+     *       codes. The first valid country code with the lowest slot number will be used.
+     *   <li>3. Wifi country code - Current country code retrieved via wifi (via 80211.ad).
+     *   <li>4. Last known telephony country code - Last known country code retrieved via cellular.
+     *       If there are multiple SIM's, the country code chosen is non-deterministic if they
+     *       return different codes. The first valid last known country code with the lowest slot
+     *       number will be used.
+     *   <li>5. Location country code - Country code retrieved from LocationManager passive location
      *       provider.
      * </ul>
      *
@@ -210,6 +427,18 @@
             return mOverrideCountryCodeInfo;
         }
 
+        if (mTelephonyCountryCodeInfo != null) {
+            return mTelephonyCountryCodeInfo;
+        }
+
+        if (mWifiCountryCodeInfo != null) {
+            return mWifiCountryCodeInfo;
+        }
+
+        if (mTelephonyLastCountryCodeInfo != null) {
+            return mTelephonyLastCountryCodeInfo;
+        }
+
         if (mLocationCountryCodeInfo != null) {
             return mLocationCountryCodeInfo;
         }
@@ -303,6 +532,10 @@
     public synchronized void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         pw.println("---- Dump of ThreadNetworkCountryCode begin ----");
         pw.println("mOverrideCountryCodeInfo: " + mOverrideCountryCodeInfo);
+        pw.println("mTelephonyCountryCodeSlotInfoMap: " + mTelephonyCountryCodeSlotInfoMap);
+        pw.println("mTelephonyCountryCodeInfo: " + mTelephonyCountryCodeInfo);
+        pw.println("mWifiCountryCodeInfo: " + mWifiCountryCodeInfo);
+        pw.println("mTelephonyLastCountryCodeInfo: " + mTelephonyLastCountryCodeInfo);
         pw.println("mLocationCountryCodeInfo: " + mLocationCountryCodeInfo);
         pw.println("mCurrentCountryCodeInfo: " + mCurrentCountryCodeInfo);
         pw.println("---- Dump of ThreadNetworkCountryCode end ------");
diff --git a/thread/service/java/com/android/server/thread/ThreadNetworkService.java b/thread/service/java/com/android/server/thread/ThreadNetworkService.java
index 95c7256..a3cf278 100644
--- a/thread/service/java/com/android/server/thread/ThreadNetworkService.java
+++ b/thread/service/java/com/android/server/thread/ThreadNetworkService.java
@@ -21,15 +21,12 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
-import android.location.Geocoder;
-import android.location.LocationManager;
 import android.net.thread.IThreadNetworkController;
 import android.net.thread.IThreadNetworkManager;
 import android.os.Binder;
 import android.os.ParcelFileDescriptor;
 
 import com.android.server.SystemService;
-import com.android.server.connectivity.ConnectivityResources;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -59,12 +56,7 @@
         if (phase == SystemService.PHASE_BOOT_COMPLETED) {
             mControllerService = ThreadNetworkControllerService.newInstance(mContext);
             mControllerService.initialize();
-            mCountryCode =
-                    new ThreadNetworkCountryCode(
-                            mContext.getSystemService(LocationManager.class),
-                            mControllerService,
-                            Geocoder.isPresent() ? new Geocoder(mContext) : null,
-                            new ConnectivityResources(mContext));
+            mCountryCode = ThreadNetworkCountryCode.newInstance(mContext, mControllerService);
             mCountryCode.initialize();
 
             mShellCommand = new ThreadNetworkShellCommand(mCountryCode);
diff --git a/thread/tests/unit/Android.bp b/thread/tests/unit/Android.bp
index 71642a1..d0942e2 100644
--- a/thread/tests/unit/Android.bp
+++ b/thread/tests/unit/Android.bp
@@ -49,6 +49,7 @@
         "android.test.base",
         "android.test.runner",
         "ServiceConnectivityResources",
+        "framework-wifi",
     ],
     jni_libs: [
         "libservice-thread-jni",
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 a0eff6c..17cdd01 100644
--- a/thread/tests/unit/src/com/android/server/thread/ThreadNetworkCountryCodeTest.java
+++ b/thread/tests/unit/src/com/android/server/thread/ThreadNetworkCountryCodeTest.java
@@ -32,11 +32,15 @@
 import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.when;
 
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.content.res.Resources;
 import android.location.Address;
@@ -45,6 +49,11 @@
 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.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
@@ -61,6 +70,7 @@
 import org.mockito.MockitoAnnotations;
 import org.mockito.stubbing.Answer;
 
+import java.util.Iterator;
 import java.util.List;
 import java.util.Locale;
 
@@ -70,7 +80,10 @@
 public class ThreadNetworkCountryCodeTest {
     private static final String TEST_COUNTRY_CODE_US = "US";
     private static final String TEST_COUNTRY_CODE_CN = "CN";
+    private static final int TEST_SIM_SLOT_INDEX_0 = 0;
+    private static final int TEST_SIM_SLOT_INDEX_1 = 1;
 
+    @Mock Context mContext;
     @Mock LocationManager mLocationManager;
     @Mock Geocoder mGeocoder;
     @Mock ThreadNetworkControllerService mThreadNetworkControllerService;
@@ -78,6 +91,12 @@
     @Mock Location mLocation;
     @Mock Resources mResources;
     @Mock ConnectivityResources mConnectivityResources;
+    @Mock WifiManager mWifiManager;
+    @Mock SubscriptionManager mSubscriptionManager;
+    @Mock TelephonyManager mTelephonyManager;
+    @Mock List<SubscriptionInfo> mSubscriptionInfoList;
+    @Mock SubscriptionInfo mSubscriptionInfo0;
+    @Mock SubscriptionInfo mSubscriptionInfo1;
 
     private ThreadNetworkCountryCode mThreadNetworkCountryCode;
     private boolean mErrorSetCountryCode;
@@ -85,6 +104,8 @@
     @Captor private ArgumentCaptor<LocationListener> mLocationListenerCaptor;
     @Captor private ArgumentCaptor<Geocoder.GeocodeListener> mGeocodeListenerCaptor;
     @Captor private ArgumentCaptor<IOperationReceiver> mOperationReceiverCaptor;
+    @Captor private ArgumentCaptor<ActiveCountryCodeChangedCallback> mWifiCountryCodeReceiverCaptor;
+    @Captor private ArgumentCaptor<BroadcastReceiver> mTelephonyCountryCodeReceiverCaptor;
 
     @Before
     public void setUp() throws Exception {
@@ -93,6 +114,16 @@
         when(mConnectivityResources.get()).thenReturn(mResources);
         when(mResources.getBoolean(anyInt())).thenReturn(true);
 
+        when(mSubscriptionManager.getActiveSubscriptionInfoList())
+                .thenReturn(mSubscriptionInfoList);
+        Iterator<SubscriptionInfo> iteratorMock = mock(Iterator.class);
+        when(mSubscriptionInfoList.size()).thenReturn(2);
+        when(mSubscriptionInfoList.iterator()).thenReturn(iteratorMock);
+        when(iteratorMock.hasNext()).thenReturn(true).thenReturn(true).thenReturn(false);
+        when(iteratorMock.next()).thenReturn(mSubscriptionInfo0).thenReturn(mSubscriptionInfo1);
+        when(mSubscriptionInfo0.getSimSlotIndex()).thenReturn(TEST_SIM_SLOT_INDEX_0);
+        when(mSubscriptionInfo1.getSimSlotIndex()).thenReturn(TEST_SIM_SLOT_INDEX_1);
+
         when(mLocation.getLatitude()).thenReturn(0.0);
         when(mLocation.getLongitude()).thenReturn(0.0);
 
@@ -118,7 +149,11 @@
                         mLocationManager,
                         mThreadNetworkControllerService,
                         mGeocoder,
-                        mConnectivityResources);
+                        mConnectivityResources,
+                        mWifiManager,
+                        mContext,
+                        mTelephonyManager,
+                        mSubscriptionManager);
     }
 
     private static Address newAddress(String countryCode) {
@@ -162,6 +197,165 @@
     }
 
     @Test
+    public void wifiCountryCode_bothWifiAndLocationAreAvailable_wifiCountryCodeIsUsed() {
+        mThreadNetworkCountryCode.initialize();
+        verify(mLocationManager)
+                .requestLocationUpdates(
+                        anyString(), anyLong(), anyFloat(), mLocationListenerCaptor.capture());
+        mLocationListenerCaptor.getValue().onLocationChanged(mLocation);
+        verify(mGeocoder)
+                .getFromLocation(
+                        anyDouble(), anyDouble(), anyInt(), mGeocodeListenerCaptor.capture());
+
+        Address mockAddress = mock(Address.class);
+        when(mockAddress.getCountryCode()).thenReturn(TEST_COUNTRY_CODE_US);
+        List<Address> addresses = List.of(mockAddress);
+        mGeocodeListenerCaptor.getValue().onGeocode(addresses);
+
+        verify(mWifiManager)
+                .registerActiveCountryCodeChangedCallback(
+                        any(), mWifiCountryCodeReceiverCaptor.capture());
+        mWifiCountryCodeReceiverCaptor.getValue().onActiveCountryCodeChanged(TEST_COUNTRY_CODE_CN);
+
+        assertThat(mThreadNetworkCountryCode.getCountryCode()).isEqualTo(TEST_COUNTRY_CODE_CN);
+    }
+
+    @Test
+    public void wifiCountryCode_wifiCountryCodeIsActive_wifiCountryCodeIsUsed() {
+        mThreadNetworkCountryCode.initialize();
+
+        verify(mWifiManager)
+                .registerActiveCountryCodeChangedCallback(
+                        any(), mWifiCountryCodeReceiverCaptor.capture());
+        mWifiCountryCodeReceiverCaptor.getValue().onActiveCountryCodeChanged(TEST_COUNTRY_CODE_US);
+
+        assertThat(mThreadNetworkCountryCode.getCountryCode()).isEqualTo(TEST_COUNTRY_CODE_US);
+    }
+
+    @Test
+    public void wifiCountryCode_wifiCountryCodeIsInactive_defaultCountryCodeIsUsed() {
+        mThreadNetworkCountryCode.initialize();
+        verify(mWifiManager)
+                .registerActiveCountryCodeChangedCallback(
+                        any(), mWifiCountryCodeReceiverCaptor.capture());
+        mWifiCountryCodeReceiverCaptor.getValue().onActiveCountryCodeChanged(TEST_COUNTRY_CODE_US);
+
+        mWifiCountryCodeReceiverCaptor.getValue().onCountryCodeInactive();
+
+        assertThat(mThreadNetworkCountryCode.getCountryCode())
+                .isEqualTo(ThreadNetworkCountryCode.DEFAULT_COUNTRY_CODE);
+    }
+
+    @Test
+    public void telephonyCountryCode_bothTelephonyAndLocationAvailable_telephonyCodeIsUsed() {
+        mThreadNetworkCountryCode.initialize();
+        verify(mLocationManager)
+                .requestLocationUpdates(
+                        anyString(), anyLong(), anyFloat(), mLocationListenerCaptor.capture());
+        mLocationListenerCaptor.getValue().onLocationChanged(mLocation);
+        verify(mGeocoder)
+                .getFromLocation(
+                        anyDouble(), anyDouble(), anyInt(), mGeocodeListenerCaptor.capture());
+        mGeocodeListenerCaptor.getValue().onGeocode(List.of(newAddress(TEST_COUNTRY_CODE_US)));
+
+        verify(mContext)
+                .registerReceiver(
+                        mTelephonyCountryCodeReceiverCaptor.capture(),
+                        any(),
+                        eq(Context.RECEIVER_EXPORTED));
+        Intent intent =
+                new Intent(TelephonyManager.ACTION_NETWORK_COUNTRY_CHANGED)
+                        .putExtra(TelephonyManager.EXTRA_NETWORK_COUNTRY, TEST_COUNTRY_CODE_CN)
+                        .putExtra(SubscriptionManager.EXTRA_SLOT_INDEX, TEST_SIM_SLOT_INDEX_0);
+        mTelephonyCountryCodeReceiverCaptor.getValue().onReceive(mContext, intent);
+
+        assertThat(mThreadNetworkCountryCode.getCountryCode()).isEqualTo(TEST_COUNTRY_CODE_CN);
+    }
+
+    @Test
+    public void telephonyCountryCode_locationIsAvailable_lastKnownTelephonyCodeIsUsed() {
+        mThreadNetworkCountryCode.initialize();
+        verify(mLocationManager)
+                .requestLocationUpdates(
+                        anyString(), anyLong(), anyFloat(), mLocationListenerCaptor.capture());
+        mLocationListenerCaptor.getValue().onLocationChanged(mLocation);
+        verify(mGeocoder)
+                .getFromLocation(
+                        anyDouble(), anyDouble(), anyInt(), mGeocodeListenerCaptor.capture());
+        mGeocodeListenerCaptor.getValue().onGeocode(List.of(newAddress(TEST_COUNTRY_CODE_US)));
+
+        verify(mContext)
+                .registerReceiver(
+                        mTelephonyCountryCodeReceiverCaptor.capture(),
+                        any(),
+                        eq(Context.RECEIVER_EXPORTED));
+        Intent intent =
+                new Intent(TelephonyManager.ACTION_NETWORK_COUNTRY_CHANGED)
+                        .putExtra(TelephonyManager.EXTRA_NETWORK_COUNTRY, "")
+                        .putExtra(
+                                TelephonyManager.EXTRA_LAST_KNOWN_NETWORK_COUNTRY,
+                                TEST_COUNTRY_CODE_US)
+                        .putExtra(SubscriptionManager.EXTRA_SLOT_INDEX, TEST_SIM_SLOT_INDEX_0);
+        mTelephonyCountryCodeReceiverCaptor.getValue().onReceive(mContext, intent);
+
+        assertThat(mThreadNetworkCountryCode.getCountryCode()).isEqualTo(TEST_COUNTRY_CODE_US);
+    }
+
+    @Test
+    public void telephonyCountryCode_lastKnownCountryCodeAvailable_telephonyCodeIsUsed() {
+        mThreadNetworkCountryCode.initialize();
+        verify(mContext)
+                .registerReceiver(
+                        mTelephonyCountryCodeReceiverCaptor.capture(),
+                        any(),
+                        eq(Context.RECEIVER_EXPORTED));
+        Intent intent0 =
+                new Intent(TelephonyManager.ACTION_NETWORK_COUNTRY_CHANGED)
+                        .putExtra(TelephonyManager.EXTRA_NETWORK_COUNTRY, "")
+                        .putExtra(
+                                TelephonyManager.EXTRA_LAST_KNOWN_NETWORK_COUNTRY,
+                                TEST_COUNTRY_CODE_US)
+                        .putExtra(SubscriptionManager.EXTRA_SLOT_INDEX, TEST_SIM_SLOT_INDEX_0);
+        mTelephonyCountryCodeReceiverCaptor.getValue().onReceive(mContext, intent0);
+
+        verify(mContext)
+                .registerReceiver(
+                        mTelephonyCountryCodeReceiverCaptor.capture(),
+                        any(),
+                        eq(Context.RECEIVER_EXPORTED));
+        Intent intent1 =
+                new Intent(TelephonyManager.ACTION_NETWORK_COUNTRY_CHANGED)
+                        .putExtra(TelephonyManager.EXTRA_NETWORK_COUNTRY, TEST_COUNTRY_CODE_CN)
+                        .putExtra(SubscriptionManager.EXTRA_SLOT_INDEX, TEST_SIM_SLOT_INDEX_1);
+        mTelephonyCountryCodeReceiverCaptor.getValue().onReceive(mContext, intent1);
+
+        assertThat(mThreadNetworkCountryCode.getCountryCode()).isEqualTo(TEST_COUNTRY_CODE_CN);
+    }
+
+    @Test
+    public void telephonyCountryCode_multipleSims_firstSimIsUsed() {
+        mThreadNetworkCountryCode.initialize();
+        verify(mContext)
+                .registerReceiver(
+                        mTelephonyCountryCodeReceiverCaptor.capture(),
+                        any(),
+                        eq(Context.RECEIVER_EXPORTED));
+        Intent intent1 =
+                new Intent(TelephonyManager.ACTION_NETWORK_COUNTRY_CHANGED)
+                        .putExtra(TelephonyManager.EXTRA_NETWORK_COUNTRY, TEST_COUNTRY_CODE_CN)
+                        .putExtra(SubscriptionManager.EXTRA_SLOT_INDEX, TEST_SIM_SLOT_INDEX_1);
+        mTelephonyCountryCodeReceiverCaptor.getValue().onReceive(mContext, intent1);
+
+        Intent intent0 =
+                new Intent(TelephonyManager.ACTION_NETWORK_COUNTRY_CHANGED)
+                        .putExtra(TelephonyManager.EXTRA_NETWORK_COUNTRY, TEST_COUNTRY_CODE_CN)
+                        .putExtra(SubscriptionManager.EXTRA_SLOT_INDEX, TEST_SIM_SLOT_INDEX_0);
+        mTelephonyCountryCodeReceiverCaptor.getValue().onReceive(mContext, intent0);
+
+        assertThat(mThreadNetworkCountryCode.getCountryCode()).isEqualTo(TEST_COUNTRY_CODE_CN);
+    }
+
+    @Test
     public void updateCountryCode_noForceUpdateDefaultCountryCode_noCountryCodeIsUpdated() {
         mThreadNetworkCountryCode.initialize();
         clearInvocations(mThreadNetworkControllerService);