diff --git a/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java b/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java index 155296d932..706c446591 100644 --- a/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java +++ b/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java @@ -182,6 +182,7 @@ final class ThreadNetworkControllerService extends IThreadNetworkController.Stub private final NsdPublisher mNsdPublisher; private final OtDaemonCallbackProxy mOtDaemonCallbackProxy = new OtDaemonCallbackProxy(); private final ConnectivityResources mResources; + private final Supplier mCountryCodeSupplier; // This should not be directly used for calling IOtDaemon APIs because ot-daemon may die and // {@code mOtDaemon} will be set to {@code null}. Instead, use {@code getOtDaemon()} @@ -215,7 +216,8 @@ final class ThreadNetworkControllerService extends IThreadNetworkController.Stub ThreadPersistentSettings persistentSettings, NsdPublisher nsdPublisher, UserManager userManager, - ConnectivityResources resources) { + ConnectivityResources resources, + Supplier countryCodeSupplier) { mContext = context; mHandler = handler; mNetworkProvider = networkProvider; @@ -230,10 +232,13 @@ final class ThreadNetworkControllerService extends IThreadNetworkController.Stub mNsdPublisher = nsdPublisher; mUserManager = userManager; mResources = resources; + mCountryCodeSupplier = countryCodeSupplier; } public static ThreadNetworkControllerService newInstance( - Context context, ThreadPersistentSettings persistentSettings) { + Context context, + ThreadPersistentSettings persistentSettings, + Supplier countryCodeSupplier) { HandlerThread handlerThread = new HandlerThread("ThreadHandlerThread"); handlerThread.start(); Handler handler = new Handler(handlerThread.getLooper()); @@ -251,7 +256,8 @@ final class ThreadNetworkControllerService extends IThreadNetworkController.Stub persistentSettings, NsdPublisher.newInstance(context, handler), context.getSystemService(UserManager.class), - new ConnectivityResources(context)); + new ConnectivityResources(context), + countryCodeSupplier); } private static Inet6Address bytesToInet6Address(byte[] addressBytes) { @@ -347,7 +353,8 @@ final class ThreadNetworkControllerService extends IThreadNetworkController.Stub isEnabled(), mNsdPublisher, getMeshcopTxtAttributes(mResources.get()), - mOtDaemonCallbackProxy); + mOtDaemonCallbackProxy, + mCountryCodeSupplier.get()); otDaemon.asBinder().linkToDeath(() -> mHandler.post(this::onOtDaemonDied), 0); mOtDaemon = otDaemon; return mOtDaemon; diff --git a/thread/service/java/com/android/server/thread/ThreadNetworkCountryCode.java b/thread/service/java/com/android/server/thread/ThreadNetworkCountryCode.java index ffa7b4484b..a194114af5 100644 --- a/thread/service/java/com/android/server/thread/ThreadNetworkCountryCode.java +++ b/thread/service/java/com/android/server/thread/ThreadNetworkCountryCode.java @@ -16,6 +16,8 @@ package com.android.server.thread; +import static com.android.server.thread.ThreadPersistentSettings.THREAD_COUNTRY_CODE; + import android.annotation.Nullable; import android.annotation.StringDef; import android.annotation.TargetApi; @@ -83,6 +85,7 @@ public class ThreadNetworkCountryCode { COUNTRY_CODE_SOURCE_TELEPHONY, COUNTRY_CODE_SOURCE_TELEPHONY_LAST, COUNTRY_CODE_SOURCE_WIFI, + COUNTRY_CODE_SOURCE_SETTINGS, }) private @interface CountryCodeSource {} @@ -93,6 +96,7 @@ public class ThreadNetworkCountryCode { 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 String COUNTRY_CODE_SOURCE_SETTINGS = "Settings"; private static final CountryCodeInfo DEFAULT_COUNTRY_CODE_INFO = new CountryCodeInfo(DEFAULT_COUNTRY_CODE, COUNTRY_CODE_SOURCE_DEFAULT); @@ -107,6 +111,7 @@ public class ThreadNetworkCountryCode { private final SubscriptionManager mSubscriptionManager; private final Map mTelephonyCountryCodeSlotInfoMap = new ArrayMap(); + private final ThreadPersistentSettings mPersistentSettings; @Nullable private CountryCodeInfo mCurrentCountryCodeInfo; @Nullable private CountryCodeInfo mLocationCountryCodeInfo; @@ -215,7 +220,8 @@ public class ThreadNetworkCountryCode { Context context, TelephonyManager telephonyManager, SubscriptionManager subscriptionManager, - @Nullable String oemCountryCode) { + @Nullable String oemCountryCode, + ThreadPersistentSettings persistentSettings) { mLocationManager = locationManager; mThreadNetworkControllerService = threadNetworkControllerService; mGeocoder = geocoder; @@ -224,14 +230,19 @@ public class ThreadNetworkCountryCode { mContext = context; mTelephonyManager = telephonyManager; mSubscriptionManager = subscriptionManager; + mPersistentSettings = persistentSettings; if (oemCountryCode != null) { mOemCountryCodeInfo = new CountryCodeInfo(oemCountryCode, COUNTRY_CODE_SOURCE_OEM); } + + mCurrentCountryCodeInfo = pickCountryCode(); } public static ThreadNetworkCountryCode newInstance( - Context context, ThreadNetworkControllerService controllerService) { + Context context, + ThreadNetworkControllerService controllerService, + ThreadPersistentSettings persistentSettings) { return new ThreadNetworkCountryCode( context.getSystemService(LocationManager.class), controllerService, @@ -241,7 +252,8 @@ public class ThreadNetworkCountryCode { context, context.getSystemService(TelephonyManager.class), context.getSystemService(SubscriptionManager.class), - ThreadNetworkProperties.country_code().orElse(null)); + ThreadNetworkProperties.country_code().orElse(null), + persistentSettings); } /** Sets up this country code module to listen to location country code changes. */ @@ -485,6 +497,11 @@ public class ThreadNetworkCountryCode { return mLocationCountryCodeInfo; } + String settingsCountryCode = mPersistentSettings.get(THREAD_COUNTRY_CODE); + if (settingsCountryCode != null) { + return new CountryCodeInfo(settingsCountryCode, COUNTRY_CODE_SOURCE_SETTINGS); + } + if (mOemCountryCodeInfo != null) { return mOemCountryCodeInfo; } @@ -498,6 +515,8 @@ public class ThreadNetworkCountryCode { public void onSuccess() { synchronized ("ThreadNetworkCountryCode.this") { mCurrentCountryCodeInfo = countryCodeInfo; + mPersistentSettings.put( + THREAD_COUNTRY_CODE.key, countryCodeInfo.getCountryCode()); } } @@ -536,10 +555,9 @@ public class ThreadNetworkCountryCode { newOperationReceiver(countryCodeInfo)); } - /** Returns the current country code or {@code null} if no country code is set. */ - @Nullable + /** Returns the current country code. */ public synchronized String getCountryCode() { - return (mCurrentCountryCodeInfo != null) ? mCurrentCountryCodeInfo.getCountryCode() : null; + return mCurrentCountryCodeInfo.getCountryCode(); } /** diff --git a/thread/service/java/com/android/server/thread/ThreadNetworkService.java b/thread/service/java/com/android/server/thread/ThreadNetworkService.java index 37c1cf18c5..30c67ca1f2 100644 --- a/thread/service/java/com/android/server/thread/ThreadNetworkService.java +++ b/thread/service/java/com/android/server/thread/ThreadNetworkService.java @@ -60,13 +60,16 @@ public class ThreadNetworkService extends IThreadNetworkManager.Stub { if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) { mPersistentSettings.initialize(); mControllerService = - ThreadNetworkControllerService.newInstance(mContext, mPersistentSettings); + ThreadNetworkControllerService.newInstance( + mContext, mPersistentSettings, () -> mCountryCode.getCountryCode()); + mCountryCode = + ThreadNetworkCountryCode.newInstance( + mContext, mControllerService, mPersistentSettings); mControllerService.initialize(); } else if (phase == SystemService.PHASE_BOOT_COMPLETED) { // Country code initialization is delayed to the BOOT_COMPLETED phase because it will // call into Wi-Fi and Telephony service whose country code module is ready after // PHASE_ACTIVITY_MANAGER_READY and PHASE_THIRD_PARTY_APPS_CAN_START - mCountryCode = ThreadNetworkCountryCode.newInstance(mContext, mControllerService); mCountryCode.initialize(); mShellCommand = new ThreadNetworkShellCommand(requireNonNull(mControllerService), mCountryCode); diff --git a/thread/service/java/com/android/server/thread/ThreadPersistentSettings.java b/thread/service/java/com/android/server/thread/ThreadPersistentSettings.java index 5cb53fe75b..3df2b6b273 100644 --- a/thread/service/java/com/android/server/thread/ThreadPersistentSettings.java +++ b/thread/service/java/com/android/server/thread/ThreadPersistentSettings.java @@ -63,6 +63,9 @@ public class ThreadPersistentSettings { /** Stores the Thread feature toggle state, true for enabled and false for disabled. */ public static final Key THREAD_ENABLED = new Key<>("Thread_enabled", true); + /** Stores the Thread country code, null if no country code is stored. */ + public static final Key THREAD_COUNTRY_CODE = new Key<>("thread_country_code", null); + /******** Thread persistent setting keys ***************/ @GuardedBy("mLock") @@ -123,7 +126,9 @@ public class ThreadPersistentSettings { private T getObject(String key, T defaultValue) { Object value; synchronized (mLock) { - if (defaultValue instanceof Boolean) { + if (defaultValue == null) { + value = mSettings.getString(key, null); + } else if (defaultValue instanceof Boolean) { value = mSettings.getBoolean(key, (Boolean) defaultValue); } else if (defaultValue instanceof Integer) { value = mSettings.getInt(key, (Integer) defaultValue); diff --git a/thread/tests/integration/src/android/net/thread/ThreadIntegrationTest.java b/thread/tests/integration/src/android/net/thread/ThreadIntegrationTest.java index bfded1d5dd..c70f3af9ed 100644 --- a/thread/tests/integration/src/android/net/thread/ThreadIntegrationTest.java +++ b/thread/tests/integration/src/android/net/thread/ThreadIntegrationTest.java @@ -23,6 +23,7 @@ import static android.net.thread.utils.IntegrationTestUtils.CALLBACK_TIMEOUT; import static android.net.thread.utils.IntegrationTestUtils.RESTART_JOIN_TIMEOUT; import static com.android.compatibility.common.util.SystemUtil.runShellCommand; +import static com.android.compatibility.common.util.SystemUtil.runShellCommandOrThrow; import static com.google.common.io.BaseEncoding.base16; import static com.google.common.truth.Truth.assertThat; @@ -140,6 +141,22 @@ public class ThreadIntegrationTest { } } + @Test + public void otDaemonRestart_latestCountryCodeIsSetToOtDaemon() throws Exception { + runThreadCommand("force-country-code enabled CN"); + + runShellCommand("stop ot-daemon"); + // TODO(b/323331973): the sleep is needed to workaround the race conditions + SystemClock.sleep(200); + mController.waitForRole(DEVICE_ROLE_STOPPED, CALLBACK_TIMEOUT); + + assertThat(mOtCtl.getCountryCode()).isEqualTo("CN"); + } + + private static String runThreadCommand(String cmd) { + return runShellCommandOrThrow("cmd thread_network " + cmd); + } + // TODO (b/323300829): add more tests for integration with linux platform and // ConnectivityService } diff --git a/thread/tests/integration/src/android/net/thread/utils/OtDaemonController.java b/thread/tests/integration/src/android/net/thread/utils/OtDaemonController.java index ade06695db..f39a06415b 100644 --- a/thread/tests/integration/src/android/net/thread/utils/OtDaemonController.java +++ b/thread/tests/integration/src/android/net/thread/utils/OtDaemonController.java @@ -74,6 +74,12 @@ public final class OtDaemonController { return (Inet6Address) InetAddresses.parseNumericAddress(addressStr); } + /** Returns the country code on ot-daemon. */ + public String getCountryCode() { + String countryCodeStr = executeCommand("region").split("\n")[0].trim(); + return countryCodeStr; + } + public String executeCommand(String cmd) { return SystemUtil.runShellCommand(OT_CTL + " " + cmd); } diff --git a/thread/tests/unit/src/com/android/server/thread/ThreadNetworkControllerServiceTest.java b/thread/tests/unit/src/com/android/server/thread/ThreadNetworkControllerServiceTest.java index 0c7d0864c7..85b68734da 100644 --- a/thread/tests/unit/src/com/android/server/thread/ThreadNetworkControllerServiceTest.java +++ b/thread/tests/unit/src/com/android/server/thread/ThreadNetworkControllerServiceTest.java @@ -25,6 +25,7 @@ import static android.net.thread.ThreadNetworkException.ERROR_THREAD_DISABLED; import static android.net.thread.ThreadNetworkManager.DISALLOW_THREAD_NETWORK; import static android.net.thread.ThreadNetworkManager.PERMISSION_THREAD_NETWORK_PRIVILEGED; +import static com.android.server.thread.ThreadNetworkCountryCode.DEFAULT_COUNTRY_CODE; import static com.android.server.thread.openthread.IOtDaemon.ErrorCode.OT_ERROR_INVALID_STATE; import static com.google.common.io.BaseEncoding.base16; @@ -182,7 +183,8 @@ public final class ThreadNetworkControllerServiceTest { mMockPersistentSettings, mMockNsdPublisher, mMockUserManager, - mConnectivityResources); + mConnectivityResources, + () -> DEFAULT_COUNTRY_CODE); mService.setTestNetworkAgent(mMockNetworkAgent); } 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 5ca6511bc8..ca9741df86 100644 --- a/thread/tests/unit/src/com/android/server/thread/ThreadNetworkCountryCodeTest.java +++ b/thread/tests/unit/src/com/android/server/thread/ThreadNetworkCountryCodeTest.java @@ -19,6 +19,7 @@ package com.android.server.thread; import static android.net.thread.ThreadNetworkException.ERROR_INTERNAL_ERROR; import static com.android.server.thread.ThreadNetworkCountryCode.DEFAULT_COUNTRY_CODE; +import static com.android.server.thread.ThreadPersistentSettings.THREAD_COUNTRY_CODE; import static com.google.common.truth.Truth.assertThat; @@ -104,6 +105,7 @@ public class ThreadNetworkCountryCodeTest { @Mock List mSubscriptionInfoList; @Mock SubscriptionInfo mSubscriptionInfo0; @Mock SubscriptionInfo mSubscriptionInfo1; + @Mock ThreadPersistentSettings mPersistentSettings; private ThreadNetworkCountryCode mThreadNetworkCountryCode; private boolean mErrorSetCountryCode; @@ -164,7 +166,8 @@ public class ThreadNetworkCountryCodeTest { mContext, mTelephonyManager, mSubscriptionManager, - oemCountryCode); + oemCountryCode, + mPersistentSettings); } private static Address newAddress(String countryCode) { @@ -449,6 +452,14 @@ public class ThreadNetworkCountryCodeTest { assertThat(mThreadNetworkCountryCode.getCountryCode()).isEqualTo(DEFAULT_COUNTRY_CODE); } + @Test + public void settingsCountryCode_settingsCountryCodeIsActive_settingsCountryCodeIsUsed() { + when(mPersistentSettings.get(THREAD_COUNTRY_CODE)).thenReturn(TEST_COUNTRY_CODE_CN); + mThreadNetworkCountryCode.initialize(); + + assertThat(mThreadNetworkCountryCode.getCountryCode()).isEqualTo(TEST_COUNTRY_CODE_CN); + } + @Test public void dump_allCountryCodeInfoAreDumped() { StringWriter stringWriter = new StringWriter(); diff --git a/thread/tests/unit/src/com/android/server/thread/ThreadPersistentSettingsTest.java b/thread/tests/unit/src/com/android/server/thread/ThreadPersistentSettingsTest.java index 9406a2fb5d..7d2fe91d69 100644 --- a/thread/tests/unit/src/com/android/server/thread/ThreadPersistentSettingsTest.java +++ b/thread/tests/unit/src/com/android/server/thread/ThreadPersistentSettingsTest.java @@ -16,6 +16,7 @@ package com.android.server.thread; +import static com.android.server.thread.ThreadPersistentSettings.THREAD_COUNTRY_CODE; import static com.android.server.thread.ThreadPersistentSettings.THREAD_ENABLED; import static com.google.common.truth.Truth.assertThat; @@ -54,6 +55,8 @@ import java.io.FileOutputStream; @RunWith(AndroidJUnit4.class) @SmallTest public class ThreadPersistentSettingsTest { + private static final String TEST_COUNTRY_CODE = "CN"; + @Mock private AtomicFile mAtomicFile; @Mock Resources mResources; @Mock ConnectivityResources mConnectivityResources; @@ -131,6 +134,28 @@ public class ThreadPersistentSettingsTest { verify(mAtomicFile).finishWrite(any()); } + @Test + public void put_ThreadCountryCodeString_returnsString() throws Exception { + mThreadPersistentSettings.put(THREAD_COUNTRY_CODE.key, TEST_COUNTRY_CODE); + + assertThat(mThreadPersistentSettings.get(THREAD_COUNTRY_CODE)).isEqualTo(TEST_COUNTRY_CODE); + + // Confirm that file writes have been triggered. + verify(mAtomicFile).startWrite(); + verify(mAtomicFile).finishWrite(any()); + } + + @Test + public void put_ThreadCountryCodeNull_returnsNull() throws Exception { + mThreadPersistentSettings.put(THREAD_COUNTRY_CODE.key, null); + + assertThat(mThreadPersistentSettings.get(THREAD_COUNTRY_CODE)).isNull(); + + // Confirm that file writes have been triggered. + verify(mAtomicFile).startWrite(); + verify(mAtomicFile).finishWrite(any()); + } + private byte[] createXmlForParsing(String key, Boolean value) throws Exception { PersistableBundle bundle = new PersistableBundle(); ByteArrayOutputStream outputStream = new ByteArrayOutputStream();