Merge "Create CallStateRepository.isInCallFlow" into 24D1-dev am: ac55241fc5
Original change: https://googleplex-android-review.googlesource.com/c/platform/packages/apps/Settings/+/27139248
Change-Id: Ie5cde4ca5e04167a63fd41adfb5ccd1376a0d870
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
diff --git a/src/com/android/settings/network/telephony/CallStateFlow.kt b/src/com/android/settings/network/telephony/CallStateFlow.kt
deleted file mode 100644
index f5164e0..0000000
--- a/src/com/android/settings/network/telephony/CallStateFlow.kt
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright (C) 2023 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.TelephonyCallback
-import kotlinx.coroutines.flow.Flow
-
-/**
- * Flow for call state.
- */
-fun Context.callStateFlow(subId: Int): Flow<Int> = telephonyCallbackFlow(subId) {
- object : TelephonyCallback(), TelephonyCallback.CallStateListener {
- override fun onCallStateChanged(state: Int) {
- trySend(state)
- }
- }
-}
diff --git a/src/com/android/settings/network/telephony/CallStateRepository.kt b/src/com/android/settings/network/telephony/CallStateRepository.kt
new file mode 100644
index 0000000..1c93af3
--- /dev/null
+++ b/src/com/android/settings/network/telephony/CallStateRepository.kt
@@ -0,0 +1,64 @@
+/*
+ * 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.TelephonyCallback
+import android.telephony.TelephonyManager
+import android.util.Log
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.conflate
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.onEach
+
+@OptIn(ExperimentalCoroutinesApi::class)
+class CallStateRepository(private val context: Context) {
+ private val subscriptionManager = context.requireSubscriptionManager()
+
+ /** Flow for call state of given [subId]. */
+ fun callStateFlow(subId: Int): Flow<Int> = context.telephonyCallbackFlow(subId) {
+ object : TelephonyCallback(), TelephonyCallback.CallStateListener {
+ override fun onCallStateChanged(state: Int) {
+ trySend(state)
+ }
+ }
+ }
+
+ /**
+ * Flow for in call state.
+ *
+ * @return true if any active subscription's call state is not idle.
+ */
+ fun isInCallFlow(): Flow<Boolean> = context.subscriptionsChangedFlow()
+ .flatMapLatest {
+ val subIds = subscriptionManager.activeSubscriptionIdList
+ combine(subIds.map(::callStateFlow)) { states ->
+ states.any { it != TelephonyManager.CALL_STATE_IDLE }
+ }
+ }
+ .conflate()
+ .flowOn(Dispatchers.Default)
+ .onEach { Log.d(TAG, "isInCallFlow: $it") }
+
+ private companion object {
+ private const val TAG = "CallStateRepository"
+ }
+}
diff --git a/src/com/android/settings/network/telephony/DeleteSimProfilePreferenceController.kt b/src/com/android/settings/network/telephony/DeleteSimProfilePreferenceController.kt
index 312d446..07b4e60 100644
--- a/src/com/android/settings/network/telephony/DeleteSimProfilePreferenceController.kt
+++ b/src/com/android/settings/network/telephony/DeleteSimProfilePreferenceController.kt
@@ -23,10 +23,8 @@
import androidx.lifecycle.LifecycleOwner
import androidx.preference.Preference
import androidx.preference.PreferenceScreen
-import com.android.settings.R
import com.android.settings.core.BasePreferenceController
import com.android.settings.network.SubscriptionUtil
-import com.android.settings.network.telephony.MobileNetworkUtils
import com.android.settingslib.spa.framework.util.collectLatestWithLifecycle
/** This controls a preference allowing the user to delete the profile for an eSIM. */
@@ -57,9 +55,10 @@
}
override fun onViewCreated(viewLifecycleOwner: LifecycleOwner) {
- mContext.callStateFlow(subscriptionId).collectLatestWithLifecycle(viewLifecycleOwner) {
- preference.isEnabled = (it == TelephonyManager.CALL_STATE_IDLE)
- }
+ CallStateRepository(mContext).callStateFlow(subscriptionId)
+ .collectLatestWithLifecycle(viewLifecycleOwner) {
+ preference.isEnabled = (it == TelephonyManager.CALL_STATE_IDLE)
+ }
}
override fun handlePreferenceTreeClick(preference: Preference): Boolean {
diff --git a/src/com/android/settings/network/telephony/MobileNetworkSwitchController.kt b/src/com/android/settings/network/telephony/MobileNetworkSwitchController.kt
index 447909e..6c5127f 100644
--- a/src/com/android/settings/network/telephony/MobileNetworkSwitchController.kt
+++ b/src/com/android/settings/network/telephony/MobileNetworkSwitchController.kt
@@ -18,7 +18,6 @@
import android.content.Context
import android.telephony.SubscriptionManager
-import android.telephony.TelephonyManager
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
@@ -26,19 +25,17 @@
import androidx.compose.ui.res.stringResource
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.settings.R
-import com.android.settings.network.SatelliteRepository
import com.android.settings.network.SubscriptionUtil
import com.android.settings.spa.preference.ComposePreferenceController
import com.android.settingslib.spa.widget.preference.MainSwitchPreference
import com.android.settingslib.spa.widget.preference.SwitchPreferenceModel
-import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.map
class MobileNetworkSwitchController @JvmOverloads constructor(
context: Context,
preferenceKey: String,
private val subscriptionRepository: SubscriptionRepository = SubscriptionRepository(context),
- private val satelliteRepository: SatelliteRepository = SatelliteRepository(context)
+ private val subscriptionActivationRepository: SubscriptionActivationRepository =
+ SubscriptionActivationRepository(context),
) : ComposePreferenceController(context, preferenceKey) {
private var subId = SubscriptionManager.INVALID_SUBSCRIPTION_ID
@@ -57,12 +54,7 @@
subscriptionRepository.isSubscriptionEnabledFlow(subId)
}.collectAsStateWithLifecycle(initialValue = null)
val changeable by remember {
- combine(
- context.callStateFlow(subId).map { it == TelephonyManager.CALL_STATE_IDLE },
- satelliteRepository.getIsSessionStartedFlow()
- ) { isCallStateIdle, isSatelliteModemEnabled ->
- isCallStateIdle && !isSatelliteModemEnabled
- }
+ subscriptionActivationRepository.isActivationChangeableFlow()
}.collectAsStateWithLifecycle(initialValue = true)
MainSwitchPreference(model = object : SwitchPreferenceModel {
override val title = stringResource(R.string.mobile_network_use_sim_on)
diff --git a/src/com/android/settings/network/telephony/SubscriptionActivationRepository.kt b/src/com/android/settings/network/telephony/SubscriptionActivationRepository.kt
new file mode 100644
index 0000000..416dda1
--- /dev/null
+++ b/src/com/android/settings/network/telephony/SubscriptionActivationRepository.kt
@@ -0,0 +1,35 @@
+/*
+ * 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 com.android.settings.network.SatelliteRepository
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+
+class SubscriptionActivationRepository(
+ private val context: Context,
+ private val callStateRepository: CallStateRepository = CallStateRepository(context),
+ private val satelliteRepository: SatelliteRepository = SatelliteRepository(context),
+) {
+ fun isActivationChangeableFlow(): Flow<Boolean> = combine(
+ callStateRepository.isInCallFlow(),
+ satelliteRepository.getIsSessionStartedFlow()
+ ) { isInCall, isSatelliteModemEnabled ->
+ !isInCall && !isSatelliteModemEnabled
+ }
+}
diff --git a/src/com/android/settings/network/telephony/WifiCallingPreferenceController.kt b/src/com/android/settings/network/telephony/WifiCallingPreferenceController.kt
index f184092..3bb2679 100644
--- a/src/com/android/settings/network/telephony/WifiCallingPreferenceController.kt
+++ b/src/com/android/settings/network/telephony/WifiCallingPreferenceController.kt
@@ -30,7 +30,6 @@
import com.android.settings.network.telephony.wificalling.WifiCallingRepository
import com.android.settingslib.spa.framework.util.collectLatestWithLifecycle
import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.withContext
/**
@@ -41,7 +40,7 @@
open class WifiCallingPreferenceController @JvmOverloads constructor(
context: Context,
key: String,
- private val callStateFlowFactory: (subId: Int) -> Flow<Int> = context::callStateFlow,
+ private val callStateRepository: CallStateRepository = CallStateRepository(context),
private val wifiCallingRepositoryFactory: (subId: Int) -> WifiCallingRepository = { subId ->
WifiCallingRepository(context, subId)
},
@@ -91,7 +90,7 @@
if (isReady) update()
}
- callStateFlowFactory(mSubId).collectLatestWithLifecycle(viewLifecycleOwner) {
+ callStateRepository.callStateFlow(mSubId).collectLatestWithLifecycle(viewLifecycleOwner) {
preference.isEnabled = (it == TelephonyManager.CALL_STATE_IDLE)
}
}
diff --git a/tests/spa_unit/src/com/android/settings/network/telephony/CallStateFlowTest.kt b/tests/spa_unit/src/com/android/settings/network/telephony/CallStateRepositoryTest.kt
similarity index 66%
rename from tests/spa_unit/src/com/android/settings/network/telephony/CallStateFlowTest.kt
rename to tests/spa_unit/src/com/android/settings/network/telephony/CallStateRepositoryTest.kt
index d353d44..8213ecf 100644
--- a/tests/spa_unit/src/com/android/settings/network/telephony/CallStateFlowTest.kt
+++ b/tests/spa_unit/src/com/android/settings/network/telephony/CallStateRepositoryTest.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * 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.
@@ -17,6 +17,7 @@
package com.android.settings.network.telephony
import android.content.Context
+import android.telephony.SubscriptionManager
import android.telephony.TelephonyCallback
import android.telephony.TelephonyManager
import androidx.test.core.app.ApplicationProvider
@@ -36,7 +37,7 @@
import org.mockito.kotlin.spy
@RunWith(AndroidJUnit4::class)
-class CallStateFlowTest {
+class CallStateRepositoryTest {
private var callStateListener: TelephonyCallback.CallStateListener? = null
private val mockTelephonyManager = mock<TelephonyManager> {
@@ -47,13 +48,24 @@
}
}
+ private val mockSubscriptionManager = mock<SubscriptionManager> {
+ on { activeSubscriptionIdList } doReturn intArrayOf(SUB_ID)
+ on { addOnSubscriptionsChangedListener(any(), any()) } doAnswer {
+ val listener = it.arguments[1] as SubscriptionManager.OnSubscriptionsChangedListener
+ listener.onSubscriptionsChanged()
+ }
+ }
+
private val context: Context = spy(ApplicationProvider.getApplicationContext()) {
on { getSystemService(TelephonyManager::class.java) } doReturn mockTelephonyManager
+ on { subscriptionManager } doReturn mockSubscriptionManager
}
+ private val repository = CallStateRepository(context)
+
@Test
fun callStateFlow_initial_sendInitialState() = runBlocking {
- val flow = context.callStateFlow(SUB_ID)
+ val flow = repository.callStateFlow(SUB_ID)
val state = flow.firstWithTimeoutOrNull()
@@ -63,7 +75,7 @@
@Test
fun callStateFlow_changed_sendChangedState() = runBlocking {
val listDeferred = async {
- context.callStateFlow(SUB_ID).toListWithTimeout()
+ repository.callStateFlow(SUB_ID).toListWithTimeout()
}
delay(100)
@@ -74,6 +86,27 @@
.inOrder()
}
+ @Test
+ fun isInCallFlow_initial() = runBlocking {
+ val isInCall = repository.isInCallFlow().firstWithTimeoutOrNull()
+
+ assertThat(isInCall).isFalse()
+ }
+
+ @Test
+ fun isInCallFlow_changed_sendChangedState() = runBlocking {
+ val listDeferred = async {
+ repository.isInCallFlow().toListWithTimeout()
+ }
+ delay(100)
+
+ callStateListener?.onCallStateChanged(TelephonyManager.CALL_STATE_RINGING)
+
+ assertThat(listDeferred.await())
+ .containsExactly(false, true)
+ .inOrder()
+ }
+
private companion object {
const val SUB_ID = 1
}
diff --git a/tests/spa_unit/src/com/android/settings/network/telephony/SubscriptionActivationRepositoryTest.kt b/tests/spa_unit/src/com/android/settings/network/telephony/SubscriptionActivationRepositoryTest.kt
new file mode 100644
index 0000000..dd9c505
--- /dev/null
+++ b/tests/spa_unit/src/com/android/settings/network/telephony/SubscriptionActivationRepositoryTest.kt
@@ -0,0 +1,84 @@
+/*
+ * 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 androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settings.network.SatelliteRepository
+import com.android.settingslib.spa.testutils.firstWithTimeoutOrNull
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.runBlocking
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.stub
+
+@RunWith(AndroidJUnit4::class)
+class SubscriptionActivationRepositoryTest {
+
+ private val context: Context = ApplicationProvider.getApplicationContext()
+ private val mockCallStateRepository = mock<CallStateRepository>()
+ private val mockSatelliteRepository = mock<SatelliteRepository>()
+
+ private val repository =
+ SubscriptionActivationRepository(context, mockCallStateRepository, mockSatelliteRepository)
+
+ @Test
+ fun isActivationChangeableFlow_changeable() = runBlocking {
+ mockCallStateRepository.stub {
+ on { isInCallFlow() } doReturn flowOf(false)
+ }
+ mockSatelliteRepository.stub {
+ on { getIsSessionStartedFlow() } doReturn flowOf(false)
+ }
+
+ val changeable = repository.isActivationChangeableFlow().firstWithTimeoutOrNull()
+
+ assertThat(changeable).isTrue()
+ }
+
+ @Test
+ fun isActivationChangeableFlow_inCall_notChangeable() = runBlocking {
+ mockCallStateRepository.stub {
+ on { isInCallFlow() } doReturn flowOf(true)
+ }
+ mockSatelliteRepository.stub {
+ on { getIsSessionStartedFlow() } doReturn flowOf(false)
+ }
+
+ val changeable = repository.isActivationChangeableFlow().firstWithTimeoutOrNull()
+
+ assertThat(changeable).isFalse()
+ }
+
+ @Test
+ fun isActivationChangeableFlow_satelliteSessionStarted_notChangeable() = runBlocking {
+ mockCallStateRepository.stub {
+ on { isInCallFlow() } doReturn flowOf(false)
+ }
+ mockSatelliteRepository.stub {
+ on { getIsSessionStartedFlow() } doReturn flowOf(true)
+ }
+
+ val changeable = repository.isActivationChangeableFlow().firstWithTimeoutOrNull()
+
+ assertThat(changeable).isFalse()
+ }
+}
diff --git a/tests/spa_unit/src/com/android/settings/network/telephony/WifiCallingPreferenceControllerTest.kt b/tests/spa_unit/src/com/android/settings/network/telephony/WifiCallingPreferenceControllerTest.kt
index 92776df..005ad67 100644
--- a/tests/spa_unit/src/com/android/settings/network/telephony/WifiCallingPreferenceControllerTest.kt
+++ b/tests/spa_unit/src/com/android/settings/network/telephony/WifiCallingPreferenceControllerTest.kt
@@ -58,7 +58,9 @@
}
private val preferenceScreen = PreferenceManager(context).createPreferenceScreen(context)
- private var callState = TelephonyManager.CALL_STATE_IDLE
+ private val mockCallStateRepository = mock<CallStateRepository> {
+ on { callStateFlow(SUB_ID) } doReturn flowOf(TelephonyManager.CALL_STATE_IDLE)
+ }
private val mockWifiCallingRepository = mock<WifiCallingRepository> {
on { getWiFiCallingMode() } doReturn ImsMmTelManager.WIFI_MODE_UNKNOWN
@@ -71,7 +73,7 @@
private val controller = WifiCallingPreferenceController(
context = context,
key = TEST_KEY,
- callStateFlowFactory = { flowOf(callState) },
+ callStateRepository = mockCallStateRepository,
wifiCallingRepositoryFactory = { mockWifiCallingRepository },
).init(subId = SUB_ID, callingPreferenceCategoryController)
@@ -112,7 +114,9 @@
@Test
fun isEnabled_callIdle_enabled() = runBlocking {
- callState = TelephonyManager.CALL_STATE_IDLE
+ mockCallStateRepository.stub {
+ on { callStateFlow(SUB_ID) } doReturn flowOf(TelephonyManager.CALL_STATE_IDLE)
+ }
controller.onViewCreated(TestLifecycleOwner())
delay(100)
@@ -122,7 +126,9 @@
@Test
fun isEnabled_notCallIdle_disabled() = runBlocking {
- callState = TelephonyManager.CALL_STATE_RINGING
+ mockCallStateRepository.stub {
+ on { callStateFlow(SUB_ID) } doReturn flowOf(TelephonyManager.CALL_STATE_RINGING)
+ }
controller.onViewCreated(TestLifecycleOwner())
delay(100)