Fix empty network scan result

Settings use TelephonyScanManager.NetworkScanCallback to scan, and its
onComplete() could happens before onResults().

We will change Settings to fix.

Fix: 338986191
Test: manual - on NetworkSelectSettings
Test: unit test
Change-Id: If41d957f916a99eacc1becb6b460e58722a4dca7
Merged-In: If41d957f916a99eacc1becb6b460e58722a4dca7
diff --git a/src/com/android/settings/network/telephony/NetworkSelectSettings.java b/src/com/android/settings/network/telephony/NetworkSelectSettings.java
index 33ece6b..5995a13 100644
--- a/src/com/android/settings/network/telephony/NetworkSelectSettings.java
+++ b/src/com/android/settings/network/telephony/NetworkSelectSettings.java
@@ -50,16 +50,13 @@
 import com.android.settings.R;
 import com.android.settings.dashboard.DashboardFragment;
 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 com.google.common.collect.ImmutableList;
+
 import kotlin.Unit;
-import kotlin.jvm.functions.Function1;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -87,7 +84,8 @@
     private View mProgressHeader;
     private Preference mStatusMessagePreference;
     @VisibleForTesting
-    List<CellInfo> mCellInfoList;
+    @NonNull
+    List<CellInfo> mCellInfoList = ImmutableList.of();
     private int mSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
     private TelephonyManager mTelephonyManager;
     private SatelliteManager mSatelliteManager;
@@ -100,7 +98,6 @@
     private AtomicBoolean mShouldFilterOutSatellitePlmn = new AtomicBoolean();
 
     private NetworkScanRepository mNetworkScanRepository;
-    private boolean mUpdateScanResult = false;
 
     @Override
     public void onCreate(Bundle icicle) {
@@ -208,40 +205,14 @@
     }
 
     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);
-                }
-
-                return Unit.INSTANCE;
-            }
-        });
-    }
-
-    @Override
-    public void onStart() {
-        super.onStart();
-
-        updateForbiddenPlmns();
         setProgressBarVisible(true);
-        mUpdateScanResult = true;
+        mNetworkScanRepository.launchNetworkScan(getViewLifecycleOwner(), (networkScanResult) -> {
+            if (isPreferenceScreenEnabled()) {
+                scanResultHandler(networkScanResult);
+            }
+
+            return Unit.INSTANCE;
+        });
     }
 
     /**
@@ -267,8 +238,6 @@
             return false;
         }
 
-        mUpdateScanResult = false;
-
         // Refresh the last selected item in case users reselect network.
         clearPreferenceSummary();
         if (mSelectedPreference != null) {
@@ -373,27 +342,19 @@
         }
     }
 
-    @Keep
     @VisibleForTesting
-    protected void scanResultHandler(List<CellInfo> results) {
-        mCellInfoList = filterOutSatellitePlmn(results);
+    protected void scanResultHandler(NetworkScanRepository.NetworkScanResult results) {
+        mCellInfoList = filterOutSatellitePlmn(results.getCellInfos());
         Log.d(TAG, "CellInfoList: " + CellInfoUtil.cellInfoListToString(mCellInfoList));
-        if (mCellInfoList != null && mCellInfoList.size() != 0) {
-            final NetworkOperatorPreference connectedPref = updateAllPreferenceCategory();
-            if (connectedPref != null) {
-                // update selected preference instance into connected preference
-                if (mSelectedPreference != null) {
-                    mSelectedPreference = connectedPref;
-                }
-            } else if (!isPreferenceScreenEnabled()) {
-                mSelectedPreference.setSummary(R.string.network_connecting);
-            }
-            enablePreferenceScreen(true);
-        } else if (isPreferenceScreenEnabled()) {
+        updateAllPreferenceCategory();
+        NetworkScanRepository.NetworkScanState state = results.getState();
+        if (state == NetworkScanRepository.NetworkScanState.ERROR) {
+            addMessagePreference(R.string.network_query_error);
+        } else if (mCellInfoList.isEmpty()) {
             addMessagePreference(R.string.empty_networks_list);
-            // keep showing progress bar, it will be stopped when error or completed
-            setProgressBarVisible(true);
         }
+        // keep showing progress bar, it will be stopped when error or completed
+        setProgressBarVisible(state == NetworkScanRepository.NetworkScanState.ACTIVE);
     }
 
     @Keep
@@ -410,11 +371,8 @@
 
     /**
      * Update the content of network operators list.
-     *
-     * @return preference which shows connected
      */
-    @Nullable
-    private NetworkOperatorPreference updateAllPreferenceCategory() {
+    private void updateAllPreferenceCategory() {
         int numberOfPreferences = mPreferenceCategory.getPreferenceCount();
 
         // remove unused preferences
@@ -425,7 +383,6 @@
         }
 
         // update the content of preference
-        NetworkOperatorPreference connectedPref = null;
         for (int index = 0; index < mCellInfoList.size(); index++) {
             final CellInfo cellInfo = mCellInfoList.get(index);
 
@@ -450,23 +407,10 @@
 
             if (mCellInfoList.get(index).isRegistered()) {
                 pref.setSummary(R.string.network_connected);
-                connectedPref = pref;
             } else {
                 pref.setSummary(null);
             }
         }
-
-        // update selected preference instance by index
-        for (int index = 0; index < mCellInfoList.size(); index++) {
-            final CellInfo cellInfo = mCellInfoList.get(index);
-
-            if ((mSelectedPreference != null) && mSelectedPreference.isSameCell(cellInfo)) {
-                mSelectedPreference = (NetworkOperatorPreference)
-                        (mPreferenceCategory.getPreference(index));
-            }
-        }
-
-        return connectedPref;
     }
 
     /**
@@ -535,13 +479,6 @@
         }
     }
 
-    private boolean isProgressBarVisible() {
-        if (mProgressHeader == null) {
-            return false;
-        }
-        return (mProgressHeader.getVisibility() == View.VISIBLE);
-    }
-
     protected void setProgressBarVisible(boolean visible) {
         if (mProgressHeader != null) {
             mProgressHeader.setVisibility(visible ? View.VISIBLE : View.GONE);
@@ -549,7 +486,6 @@
     }
 
     private void addMessagePreference(int messageId) {
-        setProgressBarVisible(false);
         mStatusMessagePreference.setTitle(messageId);
         mPreferenceCategory.removeAll();
         mPreferenceCategory.addPreference(mStatusMessagePreference);
diff --git a/src/com/android/settings/network/telephony/scan/NetworkScanRepository.kt b/src/com/android/settings/network/telephony/scan/NetworkScanRepository.kt
index 2067b8c..0344002 100644
--- a/src/com/android/settings/network/telephony/scan/NetworkScanRepository.kt
+++ b/src/com/android/settings/network/telephony/scan/NetworkScanRepository.kt
@@ -27,7 +27,6 @@
 import android.util.Log
 import androidx.annotation.VisibleForTesting
 import androidx.lifecycle.LifecycleOwner
-import com.android.settings.network.telephony.CellInfoUtil
 import com.android.settings.network.telephony.CellInfoUtil.getNetworkTitle
 import com.android.settingslib.spa.framework.util.collectLatestWithLifecycle
 import kotlinx.coroutines.Dispatchers
@@ -35,14 +34,19 @@
 import kotlinx.coroutines.channels.awaitClose
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.callbackFlow
+import kotlinx.coroutines.flow.conflate
 import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.onEach
 
 class NetworkScanRepository(context: Context, subId: Int) {
-    sealed interface NetworkScanResult
+    enum class NetworkScanState {
+        ACTIVE, COMPLETE, ERROR
+    }
 
-    data class NetworkScanCellInfos(val cellInfos: List<CellInfo>) : NetworkScanResult
-    data object NetworkScanComplete : NetworkScanResult
-    data class NetworkScanError(val error: Int) : NetworkScanResult
+    data class NetworkScanResult(
+        val state: NetworkScanState,
+        val cellInfos: List<CellInfo>,
+    )
 
     private val telephonyManager =
         context.getSystemService(TelephonyManager::class.java)!!.createForSubscriptionId(subId)
@@ -65,23 +69,29 @@
     }
 
     fun networkScanFlow(): Flow<NetworkScanResult> = callbackFlow {
+        var state = NetworkScanState.ACTIVE
+        var cellInfos: List<CellInfo> = emptyList()
+
         val callback = object : TelephonyScanManager.NetworkScanCallback() {
             override fun onResults(results: List<CellInfo>) {
-                val cellInfos = results.distinctBy { CellInfoScanKey(it) }
-                trySend(NetworkScanCellInfos(cellInfos))
-                Log.d(TAG, "CellInfoList: ${CellInfoUtil.cellInfoListToString(cellInfos)}")
+                cellInfos = results.distinctBy { CellInfoScanKey(it) }
+                sendResult()
             }
 
             override fun onComplete() {
-                trySend(NetworkScanComplete)
-                close()
-                Log.d(TAG, "onComplete")
+                state = NetworkScanState.COMPLETE
+                sendResult()
+                // Don't call close() here since onComplete() could happens before onResults()
             }
 
             override fun onError(error: Int) {
-                trySend(NetworkScanError(error))
+                state = NetworkScanState.ERROR
+                sendResult()
                 close()
-                Log.d(TAG, "onError: $error")
+            }
+
+            private fun sendResult() {
+                trySend(NetworkScanResult(state, cellInfos))
             }
         }
 
@@ -92,7 +102,7 @@
         )
 
         awaitClose { networkScan.stopScan() }
-    }.flowOn(Dispatchers.Default)
+    }.conflate().onEach { Log.d(TAG, "networkScanFlow: $it") }.flowOn(Dispatchers.Default)
 
     /** Create network scan for allowed network types. */
     private fun createNetworkScan(): NetworkScanRequest {
diff --git a/tests/spa_unit/src/com/android/settings/network/telephony/scan/NetworkScanRepositoryTest.kt b/tests/spa_unit/src/com/android/settings/network/telephony/scan/NetworkScanRepositoryTest.kt
index 070c779..c0b918f 100644
--- a/tests/spa_unit/src/com/android/settings/network/telephony/scan/NetworkScanRepositoryTest.kt
+++ b/tests/spa_unit/src/com/android/settings/network/telephony/scan/NetworkScanRepositoryTest.kt
@@ -32,9 +32,6 @@
 import android.telephony.TelephonyScanManager
 import androidx.test.core.app.ApplicationProvider
 import androidx.test.ext.junit.runners.AndroidJUnit4
-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.settingslib.spa.testutils.firstWithTimeoutOrNull
 import com.android.settingslib.spa.testutils.toListWithTimeout
 import com.google.common.truth.Truth.assertThat
@@ -88,7 +85,12 @@
 
         callback?.onResults(cellInfos)
 
-        assertThat(listDeferred.await()).containsExactly(NetworkScanCellInfos(cellInfos))
+        assertThat(listDeferred.await()).containsExactly(
+            NetworkScanRepository.NetworkScanResult(
+                state = NetworkScanRepository.NetworkScanState.ACTIVE,
+                cellInfos = cellInfos,
+            )
+        )
     }
 
     @Test
@@ -100,7 +102,12 @@
 
         callback?.onComplete()
 
-        assertThat(listDeferred.await()).containsExactly(NetworkScanComplete)
+        assertThat(listDeferred.await()).containsExactly(
+            NetworkScanRepository.NetworkScanResult(
+                state = NetworkScanRepository.NetworkScanState.COMPLETE,
+                cellInfos = emptyList(),
+            )
+        )
     }
 
     @Test
@@ -112,7 +119,12 @@
 
         callback?.onError(1)
 
-        assertThat(listDeferred.await()).containsExactly(NetworkScanError(1))
+        assertThat(listDeferred.await()).containsExactly(
+            NetworkScanRepository.NetworkScanResult(
+                state = NetworkScanRepository.NetworkScanState.ERROR,
+                cellInfos = emptyList(),
+            )
+        )
     }
 
     @Test
@@ -133,12 +145,13 @@
         callback?.onResults(cellInfos)
 
         assertThat(listDeferred.await()).containsExactly(
-            NetworkScanCellInfos(
-                listOf(
+            NetworkScanRepository.NetworkScanResult(
+                state = NetworkScanRepository.NetworkScanState.ACTIVE,
+                cellInfos = listOf(
                     createCellInfoLte("123", false),
                     createCellInfoLte("124", true),
                     createCellInfoGsm("123", false),
-                )
+                ),
             )
         )
     }
@@ -162,8 +175,9 @@
         callback?.onResults(cellInfos)
 
         assertThat(listDeferred.await()).containsExactly(
-            NetworkScanCellInfos(
-                listOf(
+            NetworkScanRepository.NetworkScanResult(
+                state = NetworkScanRepository.NetworkScanState.ACTIVE,
+                cellInfos = listOf(
                     createCellInfoLte("123", false),
                     createCellInfoLte("123", true),
                     createCellInfoLte("124", false),
diff --git a/tests/unit/src/com/android/settings/network/telephony/NetworkSelectSettingsTest.java b/tests/unit/src/com/android/settings/network/telephony/NetworkSelectSettingsTest.java
index a4657ce..d71af84 100644
--- a/tests/unit/src/com/android/settings/network/telephony/NetworkSelectSettingsTest.java
+++ b/tests/unit/src/com/android/settings/network/telephony/NetworkSelectSettingsTest.java
@@ -44,8 +44,12 @@
 import androidx.test.annotation.UiThreadTest;
 import androidx.test.core.app.ApplicationProvider;
 
+import com.android.settings.network.telephony.scan.NetworkScanRepository;
+import com.android.settings.network.telephony.scan.NetworkScanRepository.NetworkScanResult;
 import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
 
+import com.google.common.collect.ImmutableList;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.mockito.Mock;
@@ -163,8 +167,7 @@
         }
 
         @Override
-        protected NetworkOperatorPreference
-                createNetworkOperatorPreference(CellInfo cellInfo) {
+        protected NetworkOperatorPreference createNetworkOperatorPreference(CellInfo cellInfo) {
             NetworkOperatorPreference pref = super.createNetworkOperatorPreference(cellInfo);
             if (cellInfo == mTestEnv.mCellInfo1) {
                 pref.updateCell(cellInfo, mTestEnv.mCellId1);
@@ -183,9 +186,14 @@
     @Test
     @UiThreadTest
     public void updateAllPreferenceCategory_correctOrderingPreference() {
+        NetworkScanResult result = new NetworkScanResult(
+                NetworkScanRepository.NetworkScanState.COMPLETE,
+                ImmutableList.of(mCellInfo1, mCellInfo2));
         mNetworkSelectSettings.onCreateInitialization();
         mNetworkSelectSettings.enablePreferenceScreen(true);
-        mNetworkSelectSettings.scanResultHandler(Arrays.asList(mCellInfo1, mCellInfo2));
+
+        mNetworkSelectSettings.scanResultHandler(result);
+
         assertThat(mPreferenceCategory.getPreferenceCount()).isEqualTo(2);
         final NetworkOperatorPreference preference =
                 (NetworkOperatorPreference) mPreferenceCategory.getPreference(1);