Get NetworkRegistrationInfo on background thread
To fix ANR.
Fix: 322943652
Test: manual - on Network Selection
Test: unit test
Change-Id: I9cd7137542de007e5be2830b2ba1cbfaff8b2c05
diff --git a/src/com/android/settings/network/telephony/NetworkSelectRepository.kt b/src/com/android/settings/network/telephony/NetworkSelectRepository.kt
new file mode 100644
index 0000000..1f5fbc2
--- /dev/null
+++ b/src/com/android/settings/network/telephony/NetworkSelectRepository.kt
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.network.telephony
+
+import android.content.Context
+import android.telephony.AccessNetworkConstants
+import android.telephony.NetworkRegistrationInfo
+import android.telephony.TelephonyManager
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.lifecycleScope
+import androidx.lifecycle.repeatOnLifecycle
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+
+class NetworkSelectRepository(context: Context, subId: Int) {
+ private val telephonyManager =
+ context.getSystemService(TelephonyManager::class.java)!!.createForSubscriptionId(subId)
+
+ data class NetworkRegistrationAndForbiddenInfo(
+ val networkList: List<NetworkRegistrationInfo>,
+ val forbiddenPlmns: List<String>,
+ )
+
+ /** TODO: Move this to UI layer, when UI layer migrated to Kotlin. */
+ fun launchUpdateNetworkRegistrationInfo(
+ lifecycleOwner: LifecycleOwner,
+ action: (NetworkRegistrationAndForbiddenInfo) -> Unit,
+ ) {
+ lifecycleOwner.lifecycleScope.launch {
+ lifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
+ withContext(Dispatchers.Default) {
+ getNetworkRegistrationInfo()
+ }?.let(action)
+ }
+ }
+ }
+
+ fun getNetworkRegistrationInfo(): NetworkRegistrationAndForbiddenInfo? {
+ if (telephonyManager.dataState != TelephonyManager.DATA_CONNECTED) return null
+ // Try to get the network registration states
+ val serviceState = telephonyManager.serviceState ?: return null
+ val networkList = serviceState.getNetworkRegistrationInfoListForTransportType(
+ AccessNetworkConstants.TRANSPORT_TYPE_WWAN
+ )
+ if (networkList.isEmpty()) return null
+ // Due to the aggregation of cell between carriers, it's possible to get CellIdentity
+ // containing forbidden PLMN.
+ // Getting current network from ServiceState is no longer a good idea.
+ // Add an additional rule to avoid from showing forbidden PLMN to the user.
+ return NetworkRegistrationAndForbiddenInfo(networkList, getForbiddenPlmns())
+ }
+
+ /**
+ * Update forbidden PLMNs from the USIM App
+ */
+ private fun getForbiddenPlmns(): List<String> {
+ return telephonyManager.forbiddenPlmns?.toList() ?: emptyList()
+ }
+}
diff --git a/src/com/android/settings/network/telephony/NetworkSelectSettings.java b/src/com/android/settings/network/telephony/NetworkSelectSettings.java
index eb89d9e..19bc390 100644
--- a/src/com/android/settings/network/telephony/NetworkSelectSettings.java
+++ b/src/com/android/settings/network/telephony/NetworkSelectSettings.java
@@ -24,12 +24,10 @@
import android.os.Message;
import android.os.PersistableBundle;
import android.provider.Settings;
-import android.telephony.AccessNetworkConstants;
import android.telephony.CarrierConfigManager;
import android.telephony.CellIdentity;
import android.telephony.CellInfo;
import android.telephony.NetworkRegistrationInfo;
-import android.telephony.ServiceState;
import android.telephony.SignalStrength;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
@@ -52,13 +50,11 @@
import com.android.settings.network.telephony.scan.NetworkScanRepository.NetworkScanCellInfos;
import com.android.settings.network.telephony.scan.NetworkScanRepository.NetworkScanComplete;
import com.android.settings.network.telephony.scan.NetworkScanRepository.NetworkScanError;
-import com.android.settings.network.telephony.scan.NetworkScanRepository.NetworkScanResult;
import com.android.settings.overlay.FeatureFactory;
import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
import com.android.settingslib.utils.ThreadUtils;
import kotlin.Unit;
-import kotlin.jvm.functions.Function1;
import java.util.ArrayList;
import java.util.Arrays;
@@ -101,6 +97,8 @@
private NetworkScanRepository mNetworkScanRepository;
private boolean mUpdateScanResult = false;
+ private NetworkSelectRepository mNetworkSelectRepository;
+
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
@@ -138,6 +136,7 @@
mCarrierConfigManager.registerCarrierConfigChangeListener(mNetworkScanExecutor,
mCarrierConfigChangeListener);
mNetworkScanRepository = new NetworkScanRepository(context, mSubId);
+ mNetworkSelectRepository = new NetworkSelectRepository(context, mSubId);
}
@Keep
@@ -202,35 +201,37 @@
mProgressHeader = setPinnedHeaderView(
com.android.settingslib.widget.progressbar.R.layout.progress_header
).findViewById(com.android.settingslib.widget.progressbar.R.id.progress_bar_animation);
- forceUpdateConnectedPreferenceCategory();
+ mNetworkSelectRepository.launchUpdateNetworkRegistrationInfo(
+ getViewLifecycleOwner(),
+ (info) -> {
+ forceUpdateConnectedPreferenceCategory(info);
+ return Unit.INSTANCE;
+ });
launchNetworkScan();
}
private void launchNetworkScan() {
- mNetworkScanRepository.launchNetworkScan(getViewLifecycleOwner(), new Function1<>() {
- @Override
- public Unit invoke(@NonNull NetworkScanResult networkScanResult) {
- if (!mUpdateScanResult) {
- // Not update UI if not in scan mode.
- return Unit.INSTANCE;
- }
- if (networkScanResult instanceof NetworkScanCellInfos networkScanCellInfos) {
- scanResultHandler(networkScanCellInfos.getCellInfos());
- return Unit.INSTANCE;
- }
- if (!isPreferenceScreenEnabled()) {
- clearPreferenceSummary();
- enablePreferenceScreen(true);
- } else if (networkScanResult instanceof NetworkScanComplete
- && mCellInfoList == null) {
- // In case the scan timeout before getting any results
- addMessagePreference(R.string.empty_networks_list);
- } else if (networkScanResult instanceof NetworkScanError) {
- addMessagePreference(R.string.network_query_error);
- }
-
+ mNetworkScanRepository.launchNetworkScan(getViewLifecycleOwner(), (networkScanResult) -> {
+ if (!mUpdateScanResult) {
+ // Not update UI if not in scan mode.
return Unit.INSTANCE;
}
+ if (networkScanResult instanceof NetworkScanCellInfos networkScanCellInfos) {
+ scanResultHandler(networkScanCellInfos.getCellInfos());
+ return Unit.INSTANCE;
+ }
+ if (!isPreferenceScreenEnabled()) {
+ clearPreferenceSummary();
+ enablePreferenceScreen(true);
+ } else if (networkScanResult instanceof NetworkScanComplete
+ && mCellInfoList == null) {
+ // In case the scan timeout before getting any results
+ addMessagePreference(R.string.empty_networks_list);
+ } else if (networkScanResult instanceof NetworkScanError) {
+ addMessagePreference(R.string.network_query_error);
+ }
+
+ return Unit.INSTANCE;
});
}
@@ -238,7 +239,6 @@
public void onStart() {
super.onStart();
- updateForbiddenPlmns();
setProgressBarVisible(true);
mUpdateScanResult = true;
}
@@ -477,45 +477,26 @@
* - If the device has no data, we will remove the connected network operators list from the
* screen.
*/
- private void forceUpdateConnectedPreferenceCategory() {
- if (mTelephonyManager.getDataState() == mTelephonyManager.DATA_CONNECTED) {
- // Try to get the network registration states
- final ServiceState ss = mTelephonyManager.getServiceState();
- if (ss == null) {
- return;
+ private void forceUpdateConnectedPreferenceCategory(
+ NetworkSelectRepository.NetworkRegistrationAndForbiddenInfo info) {
+ for (NetworkRegistrationInfo regInfo : info.getNetworkList()) {
+ final CellIdentity cellIdentity = regInfo.getCellIdentity();
+ if (cellIdentity == null) {
+ continue;
}
- final List<NetworkRegistrationInfo> networkList =
- ss.getNetworkRegistrationInfoListForTransportType(
- AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
- if (networkList == null || networkList.size() == 0) {
- return;
+ final NetworkOperatorPreference pref = new NetworkOperatorPreference(
+ getPrefContext(), info.getForbiddenPlmns(), mShow4GForLTE);
+ pref.updateCell(null, cellIdentity);
+ if (pref.isForbiddenNetwork()) {
+ continue;
}
- // Due to the aggregation of cell between carriers, it's possible to get CellIdentity
- // containing forbidden PLMN.
- // Getting current network from ServiceState is no longer a good idea.
- // Add an additional rule to avoid from showing forbidden PLMN to the user.
- if (mForbiddenPlmns == null) {
- updateForbiddenPlmns();
- }
- for (NetworkRegistrationInfo regInfo : networkList) {
- final CellIdentity cellIdentity = regInfo.getCellIdentity();
- if (cellIdentity == null) {
- continue;
- }
- final NetworkOperatorPreference pref = new NetworkOperatorPreference(
- getPrefContext(), mForbiddenPlmns, mShow4GForLTE);
- pref.updateCell(null, cellIdentity);
- if (pref.isForbiddenNetwork()) {
- continue;
- }
- pref.setSummary(R.string.network_connected);
- // Update the signal strength icon, since the default signalStrength value
- // would be zero
- // (it would be quite confusing why the connected network has no signal)
- pref.setIcon(SignalStrength.NUM_SIGNAL_STRENGTH_BINS - 1);
- mPreferenceCategory.addPreference(pref);
- break;
- }
+ pref.setSummary(R.string.network_connected);
+ // Update the signal strength icon, since the default signalStrength value
+ // would be zero
+ // (it would be quite confusing why the connected network has no signal)
+ pref.setIcon(SignalStrength.NUM_SIGNAL_STRENGTH_BINS - 1);
+ mPreferenceCategory.addPreference(pref);
+ break;
}
}
diff --git a/tests/spa_unit/src/com/android/settings/network/telephony/NetworkSelectRepositoryTest.kt b/tests/spa_unit/src/com/android/settings/network/telephony/NetworkSelectRepositoryTest.kt
new file mode 100644
index 0000000..4137de4
--- /dev/null
+++ b/tests/spa_unit/src/com/android/settings/network/telephony/NetworkSelectRepositoryTest.kt
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.network.telephony
+
+import android.content.Context
+import android.telephony.AccessNetworkConstants
+import android.telephony.NetworkRegistrationInfo
+import android.telephony.ServiceState
+import android.telephony.TelephonyManager
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settings.network.telephony.scan.NetworkScanRepositoryTest
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.spy
+import org.mockito.kotlin.stub
+
+@RunWith(AndroidJUnit4::class)
+class NetworkSelectRepositoryTest {
+
+ private val mockServiceState = mock<ServiceState> {
+ on {
+ getNetworkRegistrationInfoListForTransportType(
+ AccessNetworkConstants.TRANSPORT_TYPE_WWAN
+ )
+ } doReturn NetworkRegistrationInfos
+ }
+
+ private val mockTelephonyManager = mock<TelephonyManager> {
+ on { createForSubscriptionId(SUB_ID) } doReturn mock
+ on { dataState } doReturn TelephonyManager.DATA_CONNECTED
+ on { serviceState } doReturn mockServiceState
+ }
+
+ private val context: Context = spy(ApplicationProvider.getApplicationContext()) {
+ on { getSystemService(TelephonyManager::class.java) } doReturn mockTelephonyManager
+ }
+
+ private val repository = NetworkSelectRepository(context, SUB_ID)
+
+ @Test
+ fun getNetworkRegistrationInfo_notConnected_returnNull() {
+ mockTelephonyManager.stub {
+ on { dataState } doReturn TelephonyManager.DATA_DISCONNECTED
+ }
+
+ val info = repository.getNetworkRegistrationInfo()
+
+ assertThat(info).isNull()
+ }
+
+ @Test
+ fun getNetworkRegistrationInfo_nullServiceState_returnNull() {
+ mockTelephonyManager.stub {
+ on { serviceState } doReturn null
+ }
+
+ val info = repository.getNetworkRegistrationInfo()
+
+ assertThat(info).isNull()
+ }
+
+ @Test
+ fun getNetworkRegistrationInfo_emptyNetworkList_returnNull() {
+ mockServiceState.stub {
+ on {
+ getNetworkRegistrationInfoListForTransportType(
+ AccessNetworkConstants.TRANSPORT_TYPE_WWAN
+ )
+ } doReturn emptyList()
+ }
+
+ val info = repository.getNetworkRegistrationInfo()
+
+ assertThat(info).isNull()
+ }
+
+ @Test
+ fun getNetworkRegistrationInfo_hasNetworkList_returnInfo() {
+ mockServiceState.stub {
+ on {
+ getNetworkRegistrationInfoListForTransportType(
+ AccessNetworkConstants.TRANSPORT_TYPE_WWAN
+ )
+ } doReturn NetworkRegistrationInfos
+ }
+ mockTelephonyManager.stub {
+ on { forbiddenPlmns } doReturn arrayOf(FORBIDDEN_PLMN)
+ }
+
+ val info = repository.getNetworkRegistrationInfo()
+
+ assertThat(info).isEqualTo(
+ NetworkSelectRepository.NetworkRegistrationAndForbiddenInfo(
+ networkList = NetworkRegistrationInfos,
+ forbiddenPlmns = listOf(FORBIDDEN_PLMN),
+ )
+ )
+ }
+
+ private companion object {
+ const val SUB_ID = 1
+ val NetworkRegistrationInfos = listOf(NetworkRegistrationInfo.Builder().build())
+ const val FORBIDDEN_PLMN = "Forbidden PLMN"
+ }
+}