From 4e56c7a273d8d95ba0b91c340b3c277ecc18dd08 Mon Sep 17 00:00:00 2001 From: Chaohui Wang Date: Wed, 13 Mar 2024 17:55:58 +0800 Subject: [PATCH] Get NetworkRegistrationInfo on background thread To fix ANR. Fix: 322943652 Test: manual - on Network Selection Test: unit test Change-Id: I9cd7137542de007e5be2830b2ba1cbfaff8b2c05 --- .../telephony/NetworkSelectRepository.kt | 75 +++++++++++ .../telephony/NetworkSelectSettings.java | 111 +++++++--------- .../telephony/NetworkSelectRepositoryTest.kt | 123 ++++++++++++++++++ 3 files changed, 244 insertions(+), 65 deletions(-) create mode 100644 src/com/android/settings/network/telephony/NetworkSelectRepository.kt create mode 100644 tests/spa_unit/src/com/android/settings/network/telephony/NetworkSelectRepositoryTest.kt 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 00000000000..1f5fbc202f5 --- /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, + val forbiddenPlmns: List, + ) + + /** 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 { + 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 eb89d9ef95c..19bc3905a32 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.Handler; 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; 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 @@ public class NetworkSelectSettings extends DashboardFragment { private NetworkScanRepository mNetworkScanRepository; private boolean mUpdateScanResult = false; + private NetworkSelectRepository mNetworkSelectRepository; + @Override public void onCreate(Bundle icicle) { super.onCreate(icicle); @@ -138,6 +136,7 @@ public class NetworkSelectSettings extends DashboardFragment { mCarrierConfigManager.registerCarrierConfigChangeListener(mNetworkScanExecutor, mCarrierConfigChangeListener); mNetworkScanRepository = new NetworkScanRepository(context, mSubId); + mNetworkSelectRepository = new NetworkSelectRepository(context, mSubId); } @Keep @@ -202,35 +201,37 @@ public class NetworkSelectSettings extends DashboardFragment { 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 class NetworkSelectSettings extends DashboardFragment { public void onStart() { super.onStart(); - updateForbiddenPlmns(); setProgressBarVisible(true); mUpdateScanResult = true; } @@ -477,45 +477,26 @@ public class NetworkSelectSettings extends DashboardFragment { * - 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 networkList = - ss.getNetworkRegistrationInfoListForTransportType( - AccessNetworkConstants.TRANSPORT_TYPE_WWAN); - if (networkList == null || networkList.size() == 0) { - return; - } - // 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; + final NetworkOperatorPreference pref = new NetworkOperatorPreference( + getPrefContext(), info.getForbiddenPlmns(), 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; } } 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 00000000000..4137de494bc --- /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 { + on { + getNetworkRegistrationInfoListForTransportType( + AccessNetworkConstants.TRANSPORT_TYPE_WWAN + ) + } doReturn NetworkRegistrationInfos + } + + private val mockTelephonyManager = mock { + 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" + } +}