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