Merge cherrypicks of ['googleplex-android-review.googlesource.com/29590947'] into 24Q4-release.

Change-Id: Iae17a95aabc1bbeea4eedd04f7ffa70c0d31620e
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