Fix Wi-Fi calling option does not appear

After turning on non-DDS sim.

This is because IMS feature takes time to provisioning, and IMS takes
time to become ready.

Before this fix, we do not observe the status.
After this fix, observe the readiness to fix.

Fix: 325414275
Test: manual - turn DSDS on - off
Test: unit tests
Change-Id: If10abdb331da1ff8310e10681d055d7fbe04eaee
diff --git a/src/com/android/settings/network/ims/WifiCallingQueryImsState.java b/src/com/android/settings/network/ims/WifiCallingQueryImsState.java
index efa93e5..00d162b 100644
--- a/src/com/android/settings/network/ims/WifiCallingQueryImsState.java
+++ b/src/com/android/settings/network/ims/WifiCallingQueryImsState.java
@@ -27,6 +27,8 @@
 
 import androidx.annotation.VisibleForTesting;
 
+import com.android.settings.network.telephony.wificalling.WifiCallingRepository;
+
 /**
  * Controller class for querying Wifi calling status
  */
@@ -92,7 +94,9 @@
      * Check whether Wifi Calling can be perform or not on this subscription
      *
      * @return true when Wifi Calling can be performed, otherwise false
+     * @deprecated Use {@link WifiCallingRepository#wifiCallingReadyFlow()} instead.
      */
+    @Deprecated
     public boolean isReadyToWifiCalling() {
         if (!SubscriptionManager.isValidSubscriptionId(mSubId)) {
             return false;
diff --git a/src/com/android/settings/network/telephony/MobileNetworkUtils.java b/src/com/android/settings/network/telephony/MobileNetworkUtils.java
index 47515d8..8a63505 100644
--- a/src/com/android/settings/network/telephony/MobileNetworkUtils.java
+++ b/src/com/android/settings/network/telephony/MobileNetworkUtils.java
@@ -80,6 +80,7 @@
 import com.android.settings.network.SubscriptionUtil;
 import com.android.settings.network.ims.WifiCallingQueryImsState;
 import com.android.settings.network.telephony.TelephonyConstants.TelephonyManagerConstants;
+import com.android.settings.network.telephony.wificalling.WifiCallingRepository;
 import com.android.settingslib.core.instrumentation.Instrumentable;
 import com.android.settingslib.development.DevelopmentSettingsEnabler;
 import com.android.settingslib.graph.SignalDrawable;
@@ -928,7 +929,10 @@
 
     /**
      * Copied from WifiCallingPreferenceController#isWifiCallingEnabled()
+     *
+     * @deprecated Use {@link WifiCallingRepository#wifiCallingReadyFlow()} instead.
      */
+    @Deprecated
     public static boolean isWifiCallingEnabled(Context context, int subId,
             @Nullable WifiCallingQueryImsState queryImsState) {
         if (queryImsState == null) {
diff --git a/src/com/android/settings/network/telephony/SubscriptionRepository.kt b/src/com/android/settings/network/telephony/SubscriptionRepository.kt
index 7a14d6b..ee4ac1e 100644
--- a/src/com/android/settings/network/telephony/SubscriptionRepository.kt
+++ b/src/com/android/settings/network/telephony/SubscriptionRepository.kt
@@ -18,12 +18,16 @@
 
 import android.content.Context
 import android.telephony.SubscriptionManager
+import android.util.Log
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.asExecutor
 import kotlinx.coroutines.channels.awaitClose
 import kotlinx.coroutines.flow.callbackFlow
 import kotlinx.coroutines.flow.conflate
 import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.onEach
+
+private const val TAG = "SubscriptionRepository"
 
 fun Context.subscriptionsChangedFlow() = callbackFlow {
     val subscriptionManager = getSystemService(SubscriptionManager::class.java)!!
@@ -40,4 +44,4 @@
     )
 
     awaitClose { subscriptionManager.removeOnSubscriptionsChangedListener(listener) }
-}.conflate().flowOn(Dispatchers.Default)
+}.conflate().onEach { Log.d(TAG, "subscriptions changed") }.flowOn(Dispatchers.Default)
diff --git a/src/com/android/settings/network/telephony/WifiCallingPreferenceController.kt b/src/com/android/settings/network/telephony/WifiCallingPreferenceController.kt
index 698341c..b0ea6a6 100644
--- a/src/com/android/settings/network/telephony/WifiCallingPreferenceController.kt
+++ b/src/com/android/settings/network/telephony/WifiCallingPreferenceController.kt
@@ -45,7 +45,7 @@
     context: Context,
     key: String,
     private val callStateFlowFactory: (subId: Int) -> Flow<Int> = context::callStateFlow,
-    private val wifiCallingRepository: (subId: Int) -> WifiCallingRepository = { subId ->
+    private val wifiCallingRepositoryFactory: (subId: Int) -> WifiCallingRepository = { subId ->
         WifiCallingRepository(context, subId)
     },
 ) : TelephonyBasePreferenceController(context, key) {
@@ -80,15 +80,11 @@
     }
 
     override fun onViewCreated(viewLifecycleOwner: LifecycleOwner) {
-        viewLifecycleOwner.lifecycleScope.launch {
-            viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
-                val isVisible = withContext(Dispatchers.Default) {
-                    MobileNetworkUtils.isWifiCallingEnabled(mContext, mSubId, null)
-                }
-                preference.isVisible = isVisible
-                callingPreferenceCategoryController.updateChildVisible(preferenceKey, isVisible)
+        wifiCallingRepositoryFactory(mSubId).wifiCallingReadyFlow()
+            .collectLatestWithLifecycle(viewLifecycleOwner) {
+                preference.isVisible = it
+                callingPreferenceCategoryController.updateChildVisible(preferenceKey, it)
             }
-        }
 
         viewLifecycleOwner.lifecycleScope.launch {
             viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
@@ -122,7 +118,7 @@
     }
 
     private fun getSummaryForWfcMode(): String {
-        val resId = when (wifiCallingRepository(mSubId).getWiFiCallingMode()) {
+        val resId = when (wifiCallingRepositoryFactory(mSubId).getWiFiCallingMode()) {
             ImsMmTelManager.WIFI_MODE_WIFI_ONLY ->
                 com.android.internal.R.string.wfc_mode_wifi_only_summary
 
diff --git a/src/com/android/settings/network/telephony/ims/ImsFeatureProvisionedFlow.kt b/src/com/android/settings/network/telephony/ims/ImsFeatureProvisionedFlow.kt
new file mode 100644
index 0000000..6769498
--- /dev/null
+++ b/src/com/android/settings/network/telephony/ims/ImsFeatureProvisionedFlow.kt
@@ -0,0 +1,83 @@
+/*
+ * 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.ims
+
+import android.telephony.ims.ProvisioningManager
+import android.telephony.ims.ProvisioningManager.FeatureProvisioningCallback
+import android.telephony.ims.feature.MmTelFeature.MmTelCapabilities.MmTelCapability
+import android.telephony.ims.stub.ImsRegistrationImplBase.ImsRegistrationTech
+import android.util.Log
+import androidx.annotation.VisibleForTesting
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.asExecutor
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.callbackFlow
+import kotlinx.coroutines.flow.catch
+import kotlinx.coroutines.flow.conflate
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.onEach
+
+private const val TAG = "ImsFeatureProvisioned"
+
+fun imsFeatureProvisionedFlow(
+    subId: Int,
+    @MmTelCapability capability: Int,
+    @ImsRegistrationTech tech: Int,
+): Flow<Boolean> = imsFeatureProvisionedFlow(
+    subId = subId,
+    capability = capability,
+    tech = tech,
+    provisioningManager = ProvisioningManager.createForSubscriptionId(subId),
+)
+
+@VisibleForTesting
+fun imsFeatureProvisionedFlow(
+    subId: Int,
+    @MmTelCapability capability: Int,
+    @ImsRegistrationTech tech: Int,
+    provisioningManager : ProvisioningManager,
+): Flow<Boolean> = callbackFlow {
+    val callback = object : FeatureProvisioningCallback() {
+        override fun onFeatureProvisioningChanged(
+            receivedCapability: Int,
+            receivedTech: Int,
+            isProvisioned: Boolean,
+        ) {
+            if (capability == receivedCapability && tech == receivedTech) trySend(isProvisioned)
+        }
+
+        override fun onRcsFeatureProvisioningChanged(
+            capability: Int,
+            tech: Int,
+            isProvisioned: Boolean,
+        ) {
+        }
+    }
+
+    provisioningManager.registerFeatureProvisioningChangedCallback(
+        Dispatchers.Default.asExecutor(),
+        callback,
+    )
+    trySend(provisioningManager.getProvisioningStatusForCapability(capability, tech))
+
+    awaitClose { provisioningManager.unregisterFeatureProvisioningChangedCallback(callback) }
+}.catch { e ->
+    Log.w(TAG, "[$subId] error while imsFeatureProvisionedFlow", e)
+}.conflate().onEach {
+    Log.d(TAG, "[$subId] changed: capability=$capability tech=$tech isProvisioned=$it")
+}.flowOn(Dispatchers.Default)
diff --git a/src/com/android/settings/network/telephony/ims/ImsMmTelRepository.kt b/src/com/android/settings/network/telephony/ims/ImsMmTelRepository.kt
index 1d288d4..822c20a 100644
--- a/src/com/android/settings/network/telephony/ims/ImsMmTelRepository.kt
+++ b/src/com/android/settings/network/telephony/ims/ImsMmTelRepository.kt
@@ -17,14 +17,33 @@
 package com.android.settings.network.telephony.ims
 
 import android.content.Context
+import android.telephony.AccessNetworkConstants
 import android.telephony.ims.ImsManager
 import android.telephony.ims.ImsMmTelManager
 import android.telephony.ims.ImsMmTelManager.WiFiCallingMode
+import android.telephony.ims.ImsStateCallback
+import android.telephony.ims.feature.MmTelFeature
 import android.util.Log
+import kotlin.coroutines.resume
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.asExecutor
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.callbackFlow
+import kotlinx.coroutines.flow.catch
+import kotlinx.coroutines.flow.conflate
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.suspendCancellableCoroutine
+import kotlinx.coroutines.withContext
 
 interface ImsMmTelRepository {
     @WiFiCallingMode
     fun getWiFiCallingMode(useRoamingMode: Boolean): Int
+    fun imsReadyFlow(): Flow<Boolean>
+    suspend fun isSupported(
+        @MmTelFeature.MmTelCapabilities.MmTelCapability capability: Int,
+        @AccessNetworkConstants.TransportType transportType: Int,
+    ): Boolean
 }
 
 class ImsMmTelRepositoryImpl(
@@ -45,6 +64,50 @@
         ImsMmTelManager.WIFI_MODE_UNKNOWN
     }
 
+    override fun imsReadyFlow(): Flow<Boolean> = callbackFlow {
+        val callback = object : ImsStateCallback() {
+            override fun onAvailable() {
+                Log.d(TAG, "[$subId] IMS onAvailable")
+                trySend(true)
+            }
+
+            override fun onError() {
+                Log.d(TAG, "[$subId] IMS onError")
+                trySend(false)
+            }
+
+            override fun onUnavailable(reason: Int) {
+                Log.d(TAG, "[$subId] IMS onUnavailable")
+                trySend(false)
+            }
+        }
+
+        imsMmTelManager.registerImsStateCallback(Dispatchers.Default.asExecutor(), callback)
+
+        awaitClose { imsMmTelManager.unregisterImsStateCallback(callback) }
+    }.catch { e ->
+        Log.w(TAG, "[$subId] error while imsReadyFlow", e)
+    }.conflate().flowOn(Dispatchers.Default)
+
+    override suspend fun isSupported(
+        @MmTelFeature.MmTelCapabilities.MmTelCapability capability: Int,
+        @AccessNetworkConstants.TransportType transportType: Int,
+    ): Boolean = withContext(Dispatchers.Default) {
+        suspendCancellableCoroutine { continuation ->
+            try {
+                imsMmTelManager.isSupported(
+                    capability,
+                    transportType,
+                    Dispatchers.Default.asExecutor(),
+                    continuation::resume,
+                )
+            } catch (e: Exception) {
+                continuation.resume(false)
+                Log.w(TAG, "[$subId] isSupported failed", e)
+            }
+        }.also { Log.d(TAG, "[$subId] isSupported = $it") }
+    }
+
     private companion object {
         private const val TAG = "ImsMmTelRepository"
     }
diff --git a/src/com/android/settings/network/telephony/wificalling/WifiCallingRepository.kt b/src/com/android/settings/network/telephony/wificalling/WifiCallingRepository.kt
index 3d841d5..ac95404 100644
--- a/src/com/android/settings/network/telephony/wificalling/WifiCallingRepository.kt
+++ b/src/com/android/settings/network/telephony/wificalling/WifiCallingRepository.kt
@@ -17,12 +17,24 @@
 package com.android.settings.network.telephony.wificalling
 
 import android.content.Context
+import android.telephony.AccessNetworkConstants
 import android.telephony.CarrierConfigManager
 import android.telephony.CarrierConfigManager.KEY_USE_WFC_HOME_NETWORK_MODE_IN_ROAMING_NETWORK_BOOL
+import android.telephony.SubscriptionManager
 import android.telephony.TelephonyManager
 import android.telephony.ims.ImsMmTelManager.WiFiCallingMode
+import android.telephony.ims.feature.MmTelFeature
+import android.telephony.ims.stub.ImsRegistrationImplBase
 import com.android.settings.network.telephony.ims.ImsMmTelRepository
 import com.android.settings.network.telephony.ims.ImsMmTelRepositoryImpl
+import com.android.settings.network.telephony.ims.imsFeatureProvisionedFlow
+import com.android.settings.network.telephony.subscriptionsChangedFlow
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
 
 class WifiCallingRepository(
     private val context: Context,
@@ -44,4 +56,30 @@
         carrierConfigManager
             .getConfigForSubId(subId, KEY_USE_WFC_HOME_NETWORK_MODE_IN_ROAMING_NETWORK_BOOL)
             .getBoolean(KEY_USE_WFC_HOME_NETWORK_MODE_IN_ROAMING_NETWORK_BOOL)
+
+    @OptIn(ExperimentalCoroutinesApi::class)
+    fun wifiCallingReadyFlow(): Flow<Boolean> {
+        if (!SubscriptionManager.isValidSubscriptionId(subId)) return flowOf(false)
+        return context.subscriptionsChangedFlow().flatMapLatest {
+            combine(
+                imsFeatureProvisionedFlow(
+                    subId = subId,
+                    capability = MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VOICE,
+                    tech = ImsRegistrationImplBase.REGISTRATION_TECH_IWLAN,
+                ),
+                isWifiCallingSupportedFlow(),
+            ) { imsFeatureProvisioned, isWifiCallingSupported ->
+                imsFeatureProvisioned && isWifiCallingSupported
+            }
+        }
+    }
+
+    private fun isWifiCallingSupportedFlow(): Flow<Boolean> {
+        return imsMmTelRepository.imsReadyFlow().map { imsReady ->
+            imsReady && imsMmTelRepository.isSupported(
+                capability = MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VOICE,
+                transportType = AccessNetworkConstants.TRANSPORT_TYPE_WLAN,
+            )
+        }
+    }
 }
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 f947f81..92776df 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
@@ -62,6 +62,7 @@
 
     private val mockWifiCallingRepository = mock<WifiCallingRepository> {
         on { getWiFiCallingMode() } doReturn ImsMmTelManager.WIFI_MODE_UNKNOWN
+        on { wifiCallingReadyFlow() } doReturn flowOf(true)
     }
 
     private val callingPreferenceCategoryController =
@@ -71,7 +72,7 @@
         context = context,
         key = TEST_KEY,
         callStateFlowFactory = { flowOf(callState) },
-        wifiCallingRepository = { mockWifiCallingRepository },
+        wifiCallingRepositoryFactory = { mockWifiCallingRepository },
     ).init(subId = SUB_ID, callingPreferenceCategoryController)
 
     @Before
diff --git a/tests/spa_unit/src/com/android/settings/network/telephony/ims/ImsFeatureProvisionedFlowTest.kt b/tests/spa_unit/src/com/android/settings/network/telephony/ims/ImsFeatureProvisionedFlowTest.kt
new file mode 100644
index 0000000..75f933a
--- /dev/null
+++ b/tests/spa_unit/src/com/android/settings/network/telephony/ims/ImsFeatureProvisionedFlowTest.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.ims
+
+import android.telephony.ims.ProvisioningManager
+import android.telephony.ims.ProvisioningManager.FeatureProvisioningCallback
+import android.telephony.ims.feature.MmTelFeature
+import android.telephony.ims.stub.ImsRegistrationImplBase
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spa.testutils.toListWithTimeout
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.async
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.runBlocking
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.any
+import org.mockito.kotlin.doAnswer
+import org.mockito.kotlin.mock
+
+@RunWith(AndroidJUnit4::class)
+class ImsFeatureProvisionedFlowTest {
+
+    private var callback: FeatureProvisioningCallback? = null
+
+    private val mockProvisioningManager = mock<ProvisioningManager> {
+        on { registerFeatureProvisioningChangedCallback(any(), any()) } doAnswer {
+            callback = it.arguments[1] as FeatureProvisioningCallback
+            callback?.onFeatureProvisioningChanged(CAPABILITY, TECH, true)
+        }
+    }
+
+    @Test
+    fun imsFeatureProvisionedFlow_sendInitialValue() = runBlocking {
+        val flow = imsFeatureProvisionedFlow(SUB_ID, CAPABILITY, TECH, mockProvisioningManager)
+
+        val state = flow.first()
+
+        assertThat(state).isTrue()
+    }
+
+    @Test
+    fun imsFeatureProvisionedFlow_changed(): Unit = runBlocking {
+        val listDeferred = async {
+            imsFeatureProvisionedFlow(SUB_ID, CAPABILITY, TECH, mockProvisioningManager)
+                .toListWithTimeout()
+        }
+        delay(100)
+
+        callback?.onFeatureProvisioningChanged(CAPABILITY, TECH, false)
+
+        assertThat(listDeferred.await().last()).isFalse()
+    }
+
+    private companion object {
+        const val SUB_ID = 1
+        const val CAPABILITY = MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VOICE
+        const val TECH = ImsRegistrationImplBase.REGISTRATION_TECH_IWLAN
+    }
+}
diff --git a/tests/spa_unit/src/com/android/settings/network/telephony/ims/ImsMmTelRepositoryTest.kt b/tests/spa_unit/src/com/android/settings/network/telephony/ims/ImsMmTelRepositoryTest.kt
index 106a82f..24b081a 100644
--- a/tests/spa_unit/src/com/android/settings/network/telephony/ims/ImsMmTelRepositoryTest.kt
+++ b/tests/spa_unit/src/com/android/settings/network/telephony/ims/ImsMmTelRepositoryTest.kt
@@ -17,14 +17,26 @@
 package com.android.settings.network.telephony.ims
 
 import android.content.Context
+import android.telephony.AccessNetworkConstants
 import android.telephony.ims.ImsMmTelManager
+import android.telephony.ims.ImsStateCallback
+import android.telephony.ims.feature.MmTelFeature
 import androidx.test.core.app.ApplicationProvider
 import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spa.testutils.toListWithTimeout
 import com.google.common.truth.Truth.assertThat
+import java.util.function.Consumer
+import kotlinx.coroutines.async
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.runBlocking
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.kotlin.any
+import org.mockito.kotlin.doAnswer
 import org.mockito.kotlin.doReturn
 import org.mockito.kotlin.doThrow
+import org.mockito.kotlin.eq
 import org.mockito.kotlin.mock
 import org.mockito.kotlin.stub
 
@@ -32,10 +44,21 @@
 class ImsMmTelRepositoryTest {
     private val context: Context = ApplicationProvider.getApplicationContext()
 
+    private var stateCallback: ImsStateCallback? = null
+
     private val mockImsMmTelManager = mock<ImsMmTelManager> {
         on { isVoWiFiSettingEnabled } doReturn true
         on { getVoWiFiRoamingModeSetting() } doReturn ImsMmTelManager.WIFI_MODE_WIFI_PREFERRED
         on { getVoWiFiModeSetting() } doReturn ImsMmTelManager.WIFI_MODE_CELLULAR_PREFERRED
+        on { registerImsStateCallback(any(), any()) } doAnswer {
+            stateCallback = it.arguments[1] as ImsStateCallback
+            stateCallback?.onAvailable()
+        }
+        on { isSupported(eq(CAPABILITY), eq(TRANSPORT), any(), any()) } doAnswer {
+            @Suppress("UNCHECKED_CAST")
+            val consumer = it.arguments[3] as Consumer<Boolean>
+            consumer.accept(true)
+        }
     }
 
     private val repository = ImsMmTelRepositoryImpl(context, SUB_ID, mockImsMmTelManager)
@@ -76,7 +99,37 @@
         assertThat(wiFiCallingMode).isEqualTo(ImsMmTelManager.WIFI_MODE_UNKNOWN)
     }
 
+    @Test
+    fun imsReadyFlow_sendInitialValue() = runBlocking {
+        val flow = repository.imsReadyFlow()
+
+        val state = flow.first()
+
+        assertThat(state).isTrue()
+    }
+
+    @Test
+    fun imsReadyFlow_changed(): Unit = runBlocking {
+        val listDeferred = async {
+            repository.imsReadyFlow().toListWithTimeout()
+        }
+        delay(100)
+
+        stateCallback?.onUnavailable(ImsStateCallback.REASON_IMS_SERVICE_NOT_READY)
+
+        assertThat(listDeferred.await().last()).isFalse()
+    }
+
+    @Test
+    fun isSupported() = runBlocking {
+        val isSupported = repository.isSupported(CAPABILITY, TRANSPORT)
+
+        assertThat(isSupported).isTrue()
+    }
+
     private companion object {
         const val SUB_ID = 1
+        const val CAPABILITY = MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VOICE
+        const val TRANSPORT = AccessNetworkConstants.TRANSPORT_TYPE_WLAN
     }
 }