Merge "Get NetworkRegistrationInfo on background thread" into main

This commit is contained in:
Chaohui Wang
2024-03-14 05:41:09 +00:00
committed by Android (Google) Code Review
3 changed files with 244 additions and 65 deletions

View File

@@ -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()
}
}

View File

@@ -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<NetworkRegistrationInfo> 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;
}
}

View File

@@ -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"
}
}