Fix Can't Able to Click Sims

The root cause is SubscriptionManager.OnSubscriptionsChangedListener
.onSubscriptionsChanged() not invoked in some cases.

Even the SubscriptionManager.addOnSubscriptionsChangedListener's doc
says the onSubscriptionsChanged() method will also be invoked once
initially when calling it, there still case that the
onSubscriptionsChanged() method is not invoked initially.
For example, when the onSubscriptionsChanged event never happens before,
on a device never ever has any subscriptions.

Adding a .onStart { emit(Unit) } to fix.

Also make the subscriptionsChangedFlow() a shared flow to mitigate the
extra emit cost.

Bug: 369276595
Flag: EXEMPT bug fix
Test: manual - factory reset & no any sim
Test: atest SubscriptionRepositoryTest
(cherry picked from https://googleplex-android-review.googlesource.com/q/commit:9969334647b4b9439f549c0c0b2543fb3dec8813)
Merged-In: Ic32a5666f14373926b5dfedb5dedadb4369acfc7
Change-Id: Ic32a5666f14373926b5dfedb5dedadb4369acfc7
diff --git a/src/com/android/settings/network/telephony/SubscriptionRepository.kt b/src/com/android/settings/network/telephony/SubscriptionRepository.kt
index 6b5b4cb..43bba07 100644
--- a/src/com/android/settings/network/telephony/SubscriptionRepository.kt
+++ b/src/com/android/settings/network/telephony/SubscriptionRepository.kt
@@ -23,11 +23,13 @@
 import androidx.lifecycle.LifecycleOwner
 import com.android.settings.network.SubscriptionUtil
 import com.android.settingslib.spa.framework.util.collectLatestWithLifecycle
+import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.asExecutor
 import kotlinx.coroutines.channels.awaitClose
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.callbackFlow
 import kotlinx.coroutines.flow.conflate
 import kotlinx.coroutines.flow.distinctUntilChanged
@@ -36,6 +38,8 @@
 import kotlinx.coroutines.flow.flowOn
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.flow.shareIn
 
 private const val TAG = "SubscriptionRepository"
 
@@ -132,20 +136,7 @@
     fun canDisablePhysicalSubscription() = subscriptionManager.canDisablePhysicalSubscription()
 
     /** Flow for subscriptions changes. */
-    fun subscriptionsChangedFlow() = callbackFlow {
-        val listener = object : SubscriptionManager.OnSubscriptionsChangedListener() {
-            override fun onSubscriptionsChanged() {
-                trySend(Unit)
-            }
-        }
-
-        subscriptionManager.addOnSubscriptionsChangedListener(
-            Dispatchers.Default.asExecutor(),
-            listener,
-        )
-
-        awaitClose { subscriptionManager.removeOnSubscriptionsChangedListener(listener) }
-    }.conflate().onEach { Log.d(TAG, "subscriptions changed") }.flowOn(Dispatchers.Default)
+    fun subscriptionsChangedFlow() = getSharedSubscriptionsChangedFlow(context)
 
     /** Flow of active subscription ids. */
     fun activeSubscriptionIdListFlow(): Flow<List<Int>> =
@@ -172,6 +163,57 @@
                 flowOf(null)
             }
         }
+
+    companion object {
+        private lateinit var SharedSubscriptionsChangedFlow: Flow<Unit>
+
+        private fun getSharedSubscriptionsChangedFlow(context: Context): Flow<Unit> {
+            if (!this::SharedSubscriptionsChangedFlow.isInitialized) {
+                SharedSubscriptionsChangedFlow =
+                    context.applicationContext
+                        .requireSubscriptionManager()
+                        .subscriptionsChangedFlow()
+                        .shareIn(
+                            scope = CoroutineScope(Dispatchers.Default),
+                            started = SharingStarted.WhileSubscribed(),
+                            replay = 1,
+                        )
+            }
+            return SharedSubscriptionsChangedFlow
+        }
+
+        /**
+         * Flow for subscriptions changes.
+         *
+         * Note: Even the SubscriptionManager.addOnSubscriptionsChangedListener's doc says the
+         * SubscriptionManager.OnSubscriptionsChangedListener.onSubscriptionsChanged() method will
+         * also be invoked once initially when calling it, there still case that the
+         * onSubscriptionsChanged() method is not invoked initially. For example, when the
+         * onSubscriptionsChanged event never happens before, on a device never ever has any
+         * subscriptions.
+         */
+        private fun SubscriptionManager.subscriptionsChangedFlow() =
+            callbackFlow {
+                    val listener =
+                        object : SubscriptionManager.OnSubscriptionsChangedListener() {
+                            override fun onSubscriptionsChanged() {
+                                trySend(Unit)
+                            }
+
+                            override fun onAddListenerFailed() {
+                                close()
+                            }
+                        }
+
+                    addOnSubscriptionsChangedListener(Dispatchers.Default.asExecutor(), listener)
+
+                    awaitClose { removeOnSubscriptionsChangedListener(listener) }
+                }
+                .onStart { emit(Unit) } // Ensure this flow is never empty
+                .conflate()
+                .onEach { Log.d(TAG, "subscriptions changed") }
+                .flowOn(Dispatchers.Default)
+    }
 }
 
 val Context.subscriptionManager: SubscriptionManager?
diff --git a/tests/spa_unit/src/com/android/settings/network/telephony/MobileNetworkSwitchControllerTest.kt b/tests/spa_unit/src/com/android/settings/network/telephony/MobileNetworkSwitchControllerTest.kt
index ca37082..f47c635 100644
--- a/tests/spa_unit/src/com/android/settings/network/telephony/MobileNetworkSwitchControllerTest.kt
+++ b/tests/spa_unit/src/com/android/settings/network/telephony/MobileNetworkSwitchControllerTest.kt
@@ -62,10 +62,15 @@
         on { isSubscriptionEnabledFlow(SUB_ID) } doReturn flowOf(false)
     }
 
+    private val mockSubscriptionActivationRepository = mock<SubscriptionActivationRepository> {
+        on { isActivationChangeableFlow() } doReturn flowOf(true)
+    }
+
     private val controller = MobileNetworkSwitchController(
         context = context,
         preferenceKey = TEST_KEY,
         subscriptionRepository = mockSubscriptionRepository,
+        subscriptionActivationRepository = mockSubscriptionActivationRepository,
     ).apply { init(SUB_ID) }
 
     @Test
diff --git a/tests/spa_unit/src/com/android/settings/network/telephony/SubscriptionRepositoryTest.kt b/tests/spa_unit/src/com/android/settings/network/telephony/SubscriptionRepositoryTest.kt
index 5052f57..ba5142e 100644
--- a/tests/spa_unit/src/com/android/settings/network/telephony/SubscriptionRepositoryTest.kt
+++ b/tests/spa_unit/src/com/android/settings/network/telephony/SubscriptionRepositoryTest.kt
@@ -91,7 +91,23 @@
 
         subInfoListener?.onSubscriptionsChanged()
 
-        assertThat(listDeferred.await()).hasSize(2)
+        assertThat(listDeferred.await().size).isAtLeast(2)
+    }
+
+    @Test
+    fun subscriptionsChangedFlow_managerNotCallOnSubscriptionsChangedInitially() = runBlocking {
+        mockSubscriptionManager.stub {
+            on { addOnSubscriptionsChangedListener(any(), any()) } doAnswer
+                {
+                    subInfoListener =
+                        it.arguments[1] as SubscriptionManager.OnSubscriptionsChangedListener
+                    // not call onSubscriptionsChanged here
+                }
+        }
+
+        val initialValue = repository.subscriptionsChangedFlow().firstWithTimeoutOrNull()
+
+        assertThat(initialValue).isSameInstanceAs(Unit)
     }
 
     @Test