Merge "[Audiosharing] Clean up audio sharing aconfigs in Settings" into main
diff --git a/res/layout/private_space_education_screen.xml b/res/layout/private_space_education_screen.xml
index 0f57e1e..377c923 100644
--- a/res/layout/private_space_education_screen.xml
+++ b/res/layout/private_space_education_screen.xml
@@ -43,28 +43,28 @@
             android:src="@drawable/private_space_illustration"/>
         <TextView
             style="@style/PrivateSpaceSetupSubHeaderStyle"
-            android:text="@string/private_space_how_title"/>
+            android:text="@string/private_space_setup_sub_header"/>
         <RelativeLayout
             style="@style/PrivateSpaceSetupBulletPointLayoutStyle">
             <ImageView
                 android:id="@+id/lockIcon"
                 style="@style/PrivateSpaceBulletPointIconStyle"
-                android:src="@drawable/ic_lock_closed" />
+                android:src="@drawable/counter_1_24dp" />
             <TextView
                 style="@style/PrivateSpaceBulletPointTextFontStyle"
                 android:layout_toRightOf="@+id/lockIcon"
-                android:text="@string/private_space_protected_lock_text"/>
+                android:text="@string/private_space_separate_account_text"/>
         </RelativeLayout>
         <RelativeLayout
             style="@style/PrivateSpaceSetupBulletPointLayoutStyle">
             <ImageView
                 android:id="@+id/bellIcon"
                 style="@style/PrivateSpaceBulletPointIconStyle"
-                android:src="@drawable/ic_notifications" />
+                android:src="@drawable/counter_2_24dp" />
             <TextView
                 style="@style/PrivateSpaceBulletPointTextFontStyle"
                 android:layout_toRightOf="@+id/bellIcon"
-                android:text="@string/private_space_hidden_notifications_text"/>
+                android:text="@string/private_space_protected_lock_text"/>
         </RelativeLayout>
         <RelativeLayout
             style="@style/PrivateSpaceSetupBulletPointLayoutStyle"
@@ -73,11 +73,11 @@
             <ImageView
                 android:id="@+id/appsIcon"
                 style="@style/PrivateSpaceBulletPointIconStyle"
-                android:src="@drawable/ic_apps" />
+                android:src="@drawable/counter_3_24dp" />
             <TextView
                 style="@style/PrivateSpaceBulletPointTextFontStyle"
                 android:layout_toRightOf="@+id/appsIcon"
-                android:text="@string/private_space_access_bottom_text"/>
+                android:text="@string/private_space_install_apps_text"/>
         </RelativeLayout>
         <Space
             android:layout_width="wrap_content"
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 7ffa63c..107423f 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -1298,25 +1298,25 @@
     <!-- Label for private space setup button to create private space [CHAR LIMIT=30] -->
     <string name="private_space_setup_button_label">Set up</string>
     <!-- Title for Private Space setup education screen. [CHAR LIMIT=50] -->
-    <string name="private_space_setup_title">Set up a private space</string>
+    <string name="private_space_setup_title">Private space</string>
     <!-- Summary for the private space setup education screen. [CHAR LIMIT=NONE] -->
-    <string name="private_space_hide_apps_summary">Keep private apps in a separate space that you can hide or lock</string>
+    <string name="private_space_hide_apps_summary">Hide or lock private apps in a separate space. Use a dedicated Google Account for extra security.</string>
     <!-- Text shown in private space setup screen which explains how the private space works [CHAR LIMIT=50] -->
-    <string name="private_space_how_title">How it works</string>
+    <string name="private_space_setup_sub_header">Set up your private space</string>
     <!-- Text shown in private space setup screen which explains private space can be accessed from bottom of all apps list. [CHAR LIMIT=NONE] -->
-    <string name="private_space_access_bottom_text">You can access your private space from the bottom of your apps list</string>
-    <!-- Text shown in private space setup screen which explains private space apps are protected by a lock. [CHAR LIMIT=60] -->
-    <string name="private_space_protected_lock_text">Apps in your private space are protected by a lock</string>
+    <string name="private_space_separate_account_text"><b>Choose a Google Account for your space</b>\nUsing a dedicated account helps to stop synced files, photos, and emails appearing outside your space</string>
+    <!-- Text shown in private space setup screen which explains private space apps are protected by a lock. [CHAR LIMIT=NONE] -->
+    <string name="private_space_protected_lock_text"><b>Set a lock</b>\nLock your space to stop other people opening it</string>
     <!-- Text shown in private space setup screen which explains notifications from private space apps will not be shown when private space is locked. [CHAR LIMIT=NONE] -->
-    <string name="private_space_hidden_notifications_text">Notifications from apps in your private space are hidden when it\u2019s locked</string>
+    <string name="private_space_install_apps_text"><b>Install apps</b>\nYour private space has its own Play Store so you can install apps easily.</string>
     <!-- This is info text to help explain in private space setup screen that the permissions granted to private space apps will not be shown in settings when private space is locked. [CHAR LIMIT=NONE] -->
     <string name="private_space_apps_permission_text">Apps in your private space won\'t appear in permission manager, privacy dashboard, and other settings when your private space is locked.\n\nYour private space can\'t be moved to a new device. You\'ll need to set up another private space if you want to use it on another device.\n\nAnyone that connects your device to a computer or installs harmful apps on your device may be able to access your private space.</string>
     <!-- Text shown at the bottom in private space auto advancing  screens. [CHAR LIMIT=60] -->
     <string name="private_space_setting_up_text">Setting up private space\u2026</string>
     <!-- Title for private space setup in auto advancing screen informing private space notifications are hidden when locked. [CHAR LIMIT=NONE] -->
     <string name="private_space_notifications_hidden_title">Notifications from private space apps are hidden when it\u2019s locked</string>
-    <!-- Title for private space setup in auto advancing screen informing photos/files from private space can be shared when unlocked. [CHAR LIMIT=NONE] -->
-    <string name="private_space_share_photos_title">Unlock private space to share photos or files</string>
+    <!-- Title for private space setup in auto advancing screen informing to explore private space settings for hide and auto lock. [CHAR LIMIT=NONE] -->
+    <string name="private_space_explore_settings_title">Explore private space settings to hide private space and set up automatic locking</string>
     <!-- Title for private space setup in auto advancing screen informing some system apps are already installed in private space. [CHAR LIMIT=NONE] -->
     <string name="private_space_apps_installed_title">Some apps are already installed in your private space</string>
     <!-- Title for private space creation error screen. [CHAR LIMIT=60] -->
@@ -2821,6 +2821,10 @@
     <string name="dark_ui_bedtime_footer_summary">Dark theme is currently following your Bedtime mode schedule</string>
     <!-- Dark UI screen footer action text shown when the when Dark theme turns on/off automatically according to a user bedtime schedule. [CHAR LIMIT=NONE] -->
     <string name="dark_ui_bedtime_footer_action">Bedtime mode settings</string>
+    <!-- Even Dimmer setting title. Allows device to reduce brightness even further than standard range. [CHAR LIMIT=NONE] -->
+    <string name="even_dimmer_display_title">Even Dimmer</string>
+    <!-- Even Dimmer setting summary. [CHAR LIMIT=NONE] -->
+    <string name="even_dimmer_display_summary">Allow device to go dimmer than usual</string>
 
 
     <!-- Sound & display settings screen, setting option name to change screen timeout -->
diff --git a/res/values/styles.xml b/res/values/styles.xml
index fbc6d7f..0a28b01 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -966,6 +966,7 @@
         <item name="android:paddingTop">20dp</item>
         <item name="android:paddingBottom">8dp</item>
         <item name="android:textSize">14sp</item>
+        <item name="android:fontFamily">google-sans-medium</item>
     </style>
 
     <style name="PrivateSpaceSetupBulletPointLayoutStyle">
diff --git a/res/xml/display_settings.xml b/res/xml/display_settings.xml
index 2df360d..5b4bee8 100644
--- a/res/xml/display_settings.xml
+++ b/res/xml/display_settings.xml
@@ -36,6 +36,11 @@
             android:title="@string/auto_brightness_title"
             android:fragment="com.android.settings.display.AutoBrightnessSettings"
             settings:controller="com.android.settings.display.AutoBrightnessPreferenceController"/>
+        <SwitchPreferenceCompat
+            android:key="even_dimmer_activated"
+            android:title="@string/even_dimmer_display_title"
+            android:summary="@string/even_dimmer_display_summary"
+            settings:controller="com.android.settings.display.EvenDimmerPreferenceController"/>
     </PreferenceCategory>
 
     <PreferenceCategory
diff --git a/src/com/android/settings/display/EvenDimmerPreferenceController.java b/src/com/android/settings/display/EvenDimmerPreferenceController.java
new file mode 100644
index 0000000..b86c845
--- /dev/null
+++ b/src/com/android/settings/display/EvenDimmerPreferenceController.java
@@ -0,0 +1,76 @@
+/*
+ * 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.display;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.provider.Settings;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+
+import com.android.server.display.feature.flags.Flags;
+import com.android.settings.R;
+import com.android.settings.core.TogglePreferenceController;
+
+/**
+ * Controller for the settings toggle which allows screen brightness to go even dimmer than usual.
+ *
+ */
+public class EvenDimmerPreferenceController extends TogglePreferenceController {
+
+    private static final String TAG = "EvenDimmerPreferenceController";
+
+    private final Resources mResources;
+
+    public EvenDimmerPreferenceController(@NonNull Context context, @NonNull String key) {
+        super(context, key);
+        mResources = context.getResources();
+    }
+
+    @Override
+    public int getAvailabilityStatus() {
+        // enable based on flag and config.xml
+        final boolean enabledInConfig = mResources.getBoolean(
+                com.android.internal.R.bool.config_evenDimmerEnabled);
+        return (Flags.evenDimmer() && enabledInConfig) ? AVAILABLE : UNSUPPORTED_ON_DEVICE;
+    }
+
+    @Override
+    public boolean isChecked() {
+        return getEvenDimmerActivated();
+    }
+
+    @Override
+    public boolean setChecked(boolean isChecked) {
+        final float enabled = getAvailabilityStatus() == AVAILABLE && isChecked ? 1 : 0;
+        Log.i(TAG, "setChecked to : " + enabled);
+
+        return Settings.Secure.putFloat(
+                mContext.getContentResolver(), Settings.Secure.EVEN_DIMMER_ACTIVATED, enabled);
+    }
+
+    @Override
+    public int getSliceHighlightMenuRes() {
+        return R.string.menu_key_display;
+    }
+
+    private boolean getEvenDimmerActivated() {
+        return Settings.Secure.getFloat(mContext.getContentResolver(),
+                Settings.Secure.EVEN_DIMMER_ACTIVATED, 0) == 1;
+    }
+}
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 e7b8318..b0ea6a6 100644
--- a/src/com/android/settings/network/telephony/WifiCallingPreferenceController.kt
+++ b/src/com/android/settings/network/telephony/WifiCallingPreferenceController.kt
@@ -29,8 +29,7 @@
 import androidx.preference.Preference
 import androidx.preference.PreferenceScreen
 import com.android.settings.R
-import com.android.settings.network.telephony.ims.ImsMmTelRepository
-import com.android.settings.network.telephony.ims.ImsMmTelRepositoryImpl
+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
@@ -46,8 +45,8 @@
     context: Context,
     key: String,
     private val callStateFlowFactory: (subId: Int) -> Flow<Int> = context::callStateFlow,
-    private val imsMmTelRepositoryFactory: (subId: Int) -> ImsMmTelRepository = { subId ->
-        ImsMmTelRepositoryImpl(context, subId)
+    private val wifiCallingRepositoryFactory: (subId: Int) -> WifiCallingRepository = { subId ->
+        WifiCallingRepository(context, subId)
     },
 ) : TelephonyBasePreferenceController(context, key) {
 
@@ -81,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) {
@@ -123,7 +118,7 @@
     }
 
     private fun getSummaryForWfcMode(): String {
-        val resId = when (imsMmTelRepositoryFactory(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 3408eb7..822c20a 100644
--- a/src/com/android/settings/network/telephony/ims/ImsMmTelRepository.kt
+++ b/src/com/android/settings/network/telephony/ims/ImsMmTelRepository.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,17 +17,33 @@
 package com.android.settings.network.telephony.ims
 
 import android.content.Context
-import android.telephony.CarrierConfigManager
-import android.telephony.CarrierConfigManager.KEY_USE_WFC_HOME_NETWORK_MODE_IN_ROAMING_NETWORK_BOOL
-import android.telephony.TelephonyManager
+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(): Int
+    fun getWiFiCallingMode(useRoamingMode: Boolean): Int
+    fun imsReadyFlow(): Flow<Boolean>
+    suspend fun isSupported(
+        @MmTelFeature.MmTelCapabilities.MmTelCapability capability: Int,
+        @AccessNetworkConstants.TransportType transportType: Int,
+    ): Boolean
 }
 
 class ImsMmTelRepositoryImpl(
@@ -36,30 +52,61 @@
     private val imsMmTelManager: ImsMmTelManager = ImsManager(context).getImsMmTelManager(subId),
 ) : ImsMmTelRepository {
 
-    private val telephonyManager = context.getSystemService(TelephonyManager::class.java)!!
-        .createForSubscriptionId(subId)
-
-    private val carrierConfigManager = context.getSystemService(CarrierConfigManager::class.java)!!
-
     @WiFiCallingMode
-    override fun getWiFiCallingMode(): Int = try {
+    override fun getWiFiCallingMode(useRoamingMode: Boolean): Int = try {
         when {
             !imsMmTelManager.isVoWiFiSettingEnabled -> ImsMmTelManager.WIFI_MODE_UNKNOWN
-
-            telephonyManager.isNetworkRoaming && !useWfcHomeModeForRoaming() ->
-                imsMmTelManager.getVoWiFiRoamingModeSetting()
-
+            useRoamingMode -> imsMmTelManager.getVoWiFiRoamingModeSetting()
             else -> imsMmTelManager.getVoWiFiModeSetting()
         }
     } catch (e: IllegalArgumentException) {
-        Log.w(TAG, "getWiFiCallingMode failed subId=$subId", e)
+        Log.w(TAG, "[$subId] getWiFiCallingMode failed useRoamingMode=$useRoamingMode", e)
         ImsMmTelManager.WIFI_MODE_UNKNOWN
     }
 
-    private fun useWfcHomeModeForRoaming(): Boolean =
-        carrierConfigManager
-            .getConfigForSubId(subId, KEY_USE_WFC_HOME_NETWORK_MODE_IN_ROAMING_NETWORK_BOOL)
-            .getBoolean(KEY_USE_WFC_HOME_NETWORK_MODE_IN_ROAMING_NETWORK_BOOL)
+    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
new file mode 100644
index 0000000..ac95404
--- /dev/null
+++ b/src/com/android/settings/network/telephony/wificalling/WifiCallingRepository.kt
@@ -0,0 +1,85 @@
+/*
+ * 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.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,
+    private val subId: Int,
+    private val imsMmTelRepository : ImsMmTelRepository = ImsMmTelRepositoryImpl(context, subId)
+) {
+    private val telephonyManager = context.getSystemService(TelephonyManager::class.java)!!
+        .createForSubscriptionId(subId)
+
+    private val carrierConfigManager = context.getSystemService(CarrierConfigManager::class.java)!!
+
+    @WiFiCallingMode
+    fun getWiFiCallingMode(): Int {
+        val useRoamingMode = telephonyManager.isNetworkRoaming && !useWfcHomeModeForRoaming()
+        return imsMmTelRepository.getWiFiCallingMode(useRoamingMode)
+    }
+
+    private fun useWfcHomeModeForRoaming(): Boolean =
+        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/src/com/android/settings/privatespace/AutoAdvanceSetupFragment.java b/src/com/android/settings/privatespace/AutoAdvanceSetupFragment.java
index 13c0681..74e8431 100644
--- a/src/com/android/settings/privatespace/AutoAdvanceSetupFragment.java
+++ b/src/com/android/settings/privatespace/AutoAdvanceSetupFragment.java
@@ -16,6 +16,8 @@
 
 package com.android.settings.privatespace;
 
+import static android.text.Layout.BREAK_STRATEGY_SIMPLE;
+
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.AnimatorSet;
@@ -60,10 +62,10 @@
             ImmutableList.of(
                     new Pair(R.string.private_space_notifications_hidden_title,
                             R.drawable.private_space_setup_notification_illustration),
-                    new Pair(R.string.private_space_share_photos_title,
-                            R.drawable.private_space_setup_sharing_illustration),
                     new Pair(R.string.private_space_apps_installed_title,
-                            R.drawable.private_space_setup_preinstalled_illustration));
+                            R.drawable.private_space_setup_preinstalled_illustration),
+                    new Pair(R.string.private_space_explore_settings_title,
+                            R.drawable.private_space_setup_sharing_illustration));
 
     private Runnable mUpdateScreenResources =
             new Runnable() {
@@ -118,6 +120,7 @@
                 (GlifLayout)
                         inflater.inflate(R.layout.private_space_advancing_screen, container, false);
         mRootView.getHeaderTextView().setMaxLines(HEADER_TEXT_MAX_LINES);
+        mRootView.getHeaderTextView().setBreakStrategy(BREAK_STRATEGY_SIMPLE);
         updateHeaderAndImage();
         mHandler = new Handler(Looper.getMainLooper());
         mHandler.postDelayed(mUpdateScreenResources, DELAY_BETWEEN_SCREENS);
diff --git a/tests/robotests/src/com/android/settings/display/EvenDimmerPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/display/EvenDimmerPreferenceControllerTest.java
new file mode 100644
index 0000000..a3cf151
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/display/EvenDimmerPreferenceControllerTest.java
@@ -0,0 +1,114 @@
+/*
+ * 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.display;
+
+
+import static com.android.settings.core.BasePreferenceController.AVAILABLE;
+import static com.android.settings.core.BasePreferenceController.UNSUPPORTED_ON_DEVICE;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.platform.test.annotations.RequiresFlagsDisabled;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
+import android.provider.Settings;
+
+import com.android.server.display.feature.flags.Flags;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+
+@RunWith(RobolectricTestRunner.class)
+public class EvenDimmerPreferenceControllerTest {
+
+    private EvenDimmerPreferenceController mController;
+    @Mock
+    private Context mContext;
+    @Mock
+    private Resources mResources;
+
+    @Rule
+    public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        when(mContext.getResources()).thenReturn(mResources);
+        mController = new EvenDimmerPreferenceController(mContext, "key");
+    }
+
+    @RequiresFlagsDisabled(Flags.FLAG_EVEN_DIMMER)
+    @Test
+    public void testGetAvailabilityStatus_flagOffconfigTrue() {
+        when(mContext.getResources()).thenReturn(mResources);
+        when(mResources.getBoolean(
+                com.android.internal.R.bool.config_evenDimmerEnabled)).thenReturn(true);
+        // setup
+        mController = new EvenDimmerPreferenceController(mContext, "key");
+
+        assertThat(mController.getAvailabilityStatus()).isEqualTo(UNSUPPORTED_ON_DEVICE);
+    }
+
+    @RequiresFlagsDisabled(Flags.FLAG_EVEN_DIMMER)
+    @Test
+    public void testGetCheckedStatus_setTrue() throws Settings.SettingNotFoundException {
+        // setup
+        mController = new EvenDimmerPreferenceController(mContext, "key");
+        mController.setChecked(true);
+
+        assertThat(Settings.Secure.getFloat(mContext.getContentResolver(),
+                Settings.Secure.EVEN_DIMMER_ACTIVATED)).isEqualTo(0.0f); // false
+    }
+
+    @RequiresFlagsEnabled(Flags.FLAG_EVEN_DIMMER)
+    @Test
+    public void testGetAvailabilityStatus_flagOnConfigTrue() {
+        when(mContext.getResources()).thenReturn(mResources);
+        when(mResources.getBoolean(
+                com.android.internal.R.bool.config_evenDimmerEnabled)).thenReturn(true);
+        // setup
+        mController = new EvenDimmerPreferenceController(mContext, "key");
+
+        assertThat(mController.getAvailabilityStatus()).isEqualTo(AVAILABLE);
+    }
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_EVEN_DIMMER)
+    public void testSetChecked_enable() throws Settings.SettingNotFoundException {
+        mController.setChecked(true);
+        assertThat(Settings.Secure.getFloat(mContext.getContentResolver(),
+                Settings.Secure.EVEN_DIMMER_ACTIVATED)).isEqualTo(1.0f); // true
+    }
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_EVEN_DIMMER)
+    public void testSetChecked_disable() throws Settings.SettingNotFoundException {
+        mController.setChecked(false);
+        assertThat(Settings.Secure.getFloat(mContext.getContentResolver(),
+                Settings.Secure.EVEN_DIMMER_ACTIVATED)).isEqualTo(0.0f); // false
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/network/telephony/AutoDataSwitchPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/network/telephony/AutoDataSwitchPreferenceControllerTest.java
index 758d6b0..29592cf 100644
--- a/tests/robotests/src/com/android/settings/network/telephony/AutoDataSwitchPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/network/telephony/AutoDataSwitchPreferenceControllerTest.java
@@ -30,12 +30,19 @@
 import static org.mockito.Mockito.when;
 
 import android.content.Context;
+import android.platform.test.annotations.RequiresFlagsDisabled;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
 import android.telephony.TelephonyManager;
 
 import androidx.preference.PreferenceScreen;
 import androidx.preference.SwitchPreference;
 
+import com.android.settings.flags.Flags;
+
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
@@ -52,6 +59,9 @@
     private static final int SUB_ID_1 = 111;
     private static final int SUB_ID_2 = 222;
 
+    @Rule
+    public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
     @Mock
     private TelephonyManager mTelephonyManager;
     @Mock
@@ -79,6 +89,7 @@
     }
 
     @Test
+    @RequiresFlagsDisabled(Flags.FLAG_IS_DUAL_SIM_ONBOARDING_ENABLED)
     public void getAvailabilityStatus_noInit_notAvailable() {
         ShadowSubscriptionManager.setDefaultDataSubscriptionId(SUB_ID_1);
         AutoDataSwitchPreferenceController controller =
@@ -90,6 +101,7 @@
     }
 
     @Test
+    @RequiresFlagsDisabled(Flags.FLAG_IS_DUAL_SIM_ONBOARDING_ENABLED)
     public void displayPreference_defaultForData_notAvailable() {
         ShadowSubscriptionManager.setDefaultDataSubscriptionId(SUB_ID_1);
 
@@ -100,6 +112,7 @@
     }
 
     @Test
+    @RequiresFlagsDisabled(Flags.FLAG_IS_DUAL_SIM_ONBOARDING_ENABLED)
     public void  displayPreference_notDefaultForData_available() {
         ShadowSubscriptionManager.setDefaultDataSubscriptionId(SUB_ID_2);
 
@@ -110,6 +123,7 @@
     }
 
     @Test
+    @RequiresFlagsDisabled(Flags.FLAG_IS_DUAL_SIM_ONBOARDING_ENABLED)
     public void onSubscriptionsChanged_becomesDefaultForData_notAvailable() {
         ShadowSubscriptionManager.setDefaultDataSubscriptionId(SUB_ID_2);
 
@@ -122,6 +136,7 @@
     }
 
     @Test
+    @RequiresFlagsDisabled(Flags.FLAG_IS_DUAL_SIM_ONBOARDING_ENABLED)
     public void onSubscriptionsChanged_noLongerDefaultForData_available() {
         ShadowSubscriptionManager.setDefaultDataSubscriptionId(SUB_ID_1);
 
@@ -134,6 +149,7 @@
     }
 
     @Test
+    @RequiresFlagsDisabled(Flags.FLAG_IS_DUAL_SIM_ONBOARDING_ENABLED)
     public void getAvailabilityStatus_mobileDataChangWithDefaultDataSubId_returnUnavailable() {
         ShadowSubscriptionManager.setDefaultDataSubscriptionId(SUB_ID_1);
 
@@ -144,6 +160,7 @@
     }
 
     @Test
+    @RequiresFlagsDisabled(Flags.FLAG_IS_DUAL_SIM_ONBOARDING_ENABLED)
     public void getAvailabilityStatus_mobileDataChangWithoutDefaultDataSubId_returnAvailable() {
         ShadowSubscriptionManager.setDefaultDataSubscriptionId(SUB_ID_1);
 
@@ -152,4 +169,16 @@
 
         assertThat(mController.getAvailabilityStatus(SUB_ID_2)).isEqualTo(AVAILABLE);
     }
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_IS_DUAL_SIM_ONBOARDING_ENABLED)
+    public void getAvailabilityStatus_flagIsDualSimOnboardingEnabledOn_returnUnavailable() {
+        ShadowSubscriptionManager.setDefaultDataSubscriptionId(SUB_ID_1);
+
+        mController.displayPreference(mPreferenceScreen);
+        mController.refreshPreference();
+
+        assertThat(mController.getAvailabilityStatus(SUB_ID_1))
+                .isEqualTo(CONDITIONALLY_UNAVAILABLE);
+    }
 }
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 fc53049..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
@@ -29,7 +29,7 @@
 import androidx.preference.PreferenceManager
 import androidx.test.core.app.ApplicationProvider
 import androidx.test.ext.junit.runners.AndroidJUnit4
-import com.android.settings.network.telephony.ims.ImsMmTelRepository
+import com.android.settings.network.telephony.wificalling.WifiCallingRepository
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.delay
 import kotlinx.coroutines.flow.flowOf
@@ -60,9 +60,9 @@
 
     private var callState = TelephonyManager.CALL_STATE_IDLE
 
-    private object FakeImsMmTelRepository : ImsMmTelRepository {
-        var wiFiMode = ImsMmTelManager.WIFI_MODE_UNKNOWN
-        override fun getWiFiCallingMode() = wiFiMode
+    private val mockWifiCallingRepository = mock<WifiCallingRepository> {
+        on { getWiFiCallingMode() } doReturn ImsMmTelManager.WIFI_MODE_UNKNOWN
+        on { wifiCallingReadyFlow() } doReturn flowOf(true)
     }
 
     private val callingPreferenceCategoryController =
@@ -72,7 +72,7 @@
         context = context,
         key = TEST_KEY,
         callStateFlowFactory = { flowOf(callState) },
-        imsMmTelRepositoryFactory = { FakeImsMmTelRepository },
+        wifiCallingRepositoryFactory = { mockWifiCallingRepository },
     ).init(subId = SUB_ID, callingPreferenceCategoryController)
 
     @Before
@@ -86,7 +86,9 @@
         mockTelecomManager.stub {
             on { getSimCallManagerForSubscription(SUB_ID) } doReturn null
         }
-        FakeImsMmTelRepository.wiFiMode = ImsMmTelManager.WIFI_MODE_WIFI_ONLY
+        mockWifiCallingRepository.stub {
+            on { getWiFiCallingMode() } doReturn ImsMmTelManager.WIFI_MODE_WIFI_ONLY
+        }
 
         controller.onViewCreated(TestLifecycleOwner())
         delay(100)
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 d5142fa..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,39 +17,48 @@
 package com.android.settings.network.telephony.ims
 
 import android.content.Context
-import android.telephony.CarrierConfigManager
-import android.telephony.CarrierConfigManager.KEY_USE_WFC_HOME_NETWORK_MODE_IN_ROAMING_NETWORK_BOOL
-import android.telephony.TelephonyManager
+import android.telephony.AccessNetworkConstants
 import android.telephony.ims.ImsMmTelManager
-import androidx.core.os.persistableBundleOf
+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.spy
 import org.mockito.kotlin.stub
 
 @RunWith(AndroidJUnit4::class)
 class ImsMmTelRepositoryTest {
-    private val mockTelephonyManager = mock<TelephonyManager> {
-        on { createForSubscriptionId(SUB_ID) } doReturn mock
-    }
+    private val context: Context = ApplicationProvider.getApplicationContext()
 
-    private val mockCarrierConfigManager = mock<CarrierConfigManager>()
-
-    private val context: Context = spy(ApplicationProvider.getApplicationContext()) {
-        on { getSystemService(TelephonyManager::class.java) } doReturn mockTelephonyManager
-        on { getSystemService(CarrierConfigManager::class.java) } doReturn mockCarrierConfigManager
-    }
+    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)
@@ -60,42 +69,21 @@
             on { isVoWiFiSettingEnabled } doReturn false
         }
 
-        val wiFiCallingMode = repository.getWiFiCallingMode()
+        val wiFiCallingMode = repository.getWiFiCallingMode(false)
 
         assertThat(wiFiCallingMode).isEqualTo(ImsMmTelManager.WIFI_MODE_UNKNOWN)
     }
 
     @Test
-    fun getWiFiCallingMode_roamingAndNotUseWfcHomeModeForRoaming_returnRoamingSetting() {
-        mockTelephonyManager.stub {
-            on { isNetworkRoaming } doReturn true
-        }
-        mockUseWfcHomeModeForRoaming(false)
-
-        val wiFiCallingMode = repository.getWiFiCallingMode()
+    fun getWiFiCallingMode_useRoamingMode_returnRoamingSetting() {
+        val wiFiCallingMode = repository.getWiFiCallingMode(true)
 
         assertThat(wiFiCallingMode).isEqualTo(mockImsMmTelManager.getVoWiFiRoamingModeSetting())
     }
 
     @Test
-    fun getWiFiCallingMode_roamingAndUseWfcHomeModeForRoaming_returnHomeSetting() {
-        mockTelephonyManager.stub {
-            on { isNetworkRoaming } doReturn true
-        }
-        mockUseWfcHomeModeForRoaming(true)
-
-        val wiFiCallingMode = repository.getWiFiCallingMode()
-
-        assertThat(wiFiCallingMode).isEqualTo(mockImsMmTelManager.getVoWiFiModeSetting())
-    }
-
-    @Test
-    fun getWiFiCallingMode_notRoaming_returnHomeSetting() {
-        mockTelephonyManager.stub {
-            on { isNetworkRoaming } doReturn false
-        }
-
-        val wiFiCallingMode = repository.getWiFiCallingMode()
+    fun getWiFiCallingMode_notSseRoamingMode_returnHomeSetting() {
+        val wiFiCallingMode = repository.getWiFiCallingMode(false)
 
         assertThat(wiFiCallingMode).isEqualTo(mockImsMmTelManager.getVoWiFiModeSetting())
     }
@@ -106,22 +94,42 @@
             on { isVoWiFiSettingEnabled } doThrow IllegalArgumentException()
         }
 
-        val wiFiCallingMode = repository.getWiFiCallingMode()
+        val wiFiCallingMode = repository.getWiFiCallingMode(false)
 
         assertThat(wiFiCallingMode).isEqualTo(ImsMmTelManager.WIFI_MODE_UNKNOWN)
     }
 
-    private fun mockUseWfcHomeModeForRoaming(config: Boolean) {
-        mockCarrierConfigManager.stub {
-            on {
-                getConfigForSubId(SUB_ID, KEY_USE_WFC_HOME_NETWORK_MODE_IN_ROAMING_NETWORK_BOOL)
-            } doReturn persistableBundleOf(
-                KEY_USE_WFC_HOME_NETWORK_MODE_IN_ROAMING_NETWORK_BOOL to config,
-            )
+    @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
     }
 }
diff --git a/tests/spa_unit/src/com/android/settings/network/telephony/wificalling/WifiCallingRepositoryTest.kt b/tests/spa_unit/src/com/android/settings/network/telephony/wificalling/WifiCallingRepositoryTest.kt
new file mode 100644
index 0000000..1f3acc2
--- /dev/null
+++ b/tests/spa_unit/src/com/android/settings/network/telephony/wificalling/WifiCallingRepositoryTest.kt
@@ -0,0 +1,114 @@
+/*
+ * 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.wificalling
+
+import android.content.Context
+import android.telephony.CarrierConfigManager
+import android.telephony.CarrierConfigManager.KEY_USE_WFC_HOME_NETWORK_MODE_IN_ROAMING_NETWORK_BOOL
+import android.telephony.TelephonyManager
+import android.telephony.ims.ImsMmTelManager
+import androidx.core.os.persistableBundleOf
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settings.network.telephony.ims.ImsMmTelRepository
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.any
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.spy
+import org.mockito.kotlin.stub
+
+@RunWith(AndroidJUnit4::class)
+class WifiCallingRepositoryTest {
+
+    private val mockTelephonyManager = mock<TelephonyManager> {
+        on { createForSubscriptionId(SUB_ID) } doReturn mock
+    }
+
+    private val mockCarrierConfigManager = mock<CarrierConfigManager>()
+
+    private val context: Context = spy(ApplicationProvider.getApplicationContext()) {
+        on { getSystemService(TelephonyManager::class.java) } doReturn mockTelephonyManager
+        on { getSystemService(CarrierConfigManager::class.java) } doReturn mockCarrierConfigManager
+    }
+
+    private val mockImsMmTelRepository = mock<ImsMmTelRepository> {
+        on { getWiFiCallingMode(any()) } doReturn ImsMmTelManager.WIFI_MODE_UNKNOWN
+    }
+
+    private val repository = WifiCallingRepository(context, SUB_ID, mockImsMmTelRepository)
+
+    @Test
+    fun getWiFiCallingMode_roamingAndNotUseWfcHomeModeForRoaming_returnRoamingSetting() {
+        mockTelephonyManager.stub {
+            on { isNetworkRoaming } doReturn true
+        }
+        mockUseWfcHomeModeForRoaming(false)
+        mockImsMmTelRepository.stub {
+            on { getWiFiCallingMode(true) } doReturn ImsMmTelManager.WIFI_MODE_WIFI_PREFERRED
+        }
+
+        val wiFiCallingMode = repository.getWiFiCallingMode()
+
+        assertThat(wiFiCallingMode).isEqualTo(ImsMmTelManager.WIFI_MODE_WIFI_PREFERRED)
+    }
+
+    @Test
+    fun getWiFiCallingMode_roamingAndUseWfcHomeModeForRoaming_returnHomeSetting() {
+        mockTelephonyManager.stub {
+            on { isNetworkRoaming } doReturn true
+        }
+        mockUseWfcHomeModeForRoaming(true)
+        mockImsMmTelRepository.stub {
+            on { getWiFiCallingMode(false) } doReturn ImsMmTelManager.WIFI_MODE_WIFI_PREFERRED
+        }
+
+        val wiFiCallingMode = repository.getWiFiCallingMode()
+
+        assertThat(wiFiCallingMode).isEqualTo(ImsMmTelManager.WIFI_MODE_WIFI_PREFERRED)
+    }
+
+    @Test
+    fun getWiFiCallingMode_notRoaming_returnHomeSetting() {
+        mockTelephonyManager.stub {
+            on { isNetworkRoaming } doReturn false
+        }
+        mockImsMmTelRepository.stub {
+            on { getWiFiCallingMode(false) } doReturn ImsMmTelManager.WIFI_MODE_WIFI_PREFERRED
+        }
+
+        val wiFiCallingMode = repository.getWiFiCallingMode()
+
+        assertThat(wiFiCallingMode).isEqualTo(ImsMmTelManager.WIFI_MODE_WIFI_PREFERRED)
+    }
+
+    private fun mockUseWfcHomeModeForRoaming(config: Boolean) {
+        mockCarrierConfigManager.stub {
+            on {
+                getConfigForSubId(SUB_ID, KEY_USE_WFC_HOME_NETWORK_MODE_IN_ROAMING_NETWORK_BOOL)
+            } doReturn persistableBundleOf(
+                KEY_USE_WFC_HOME_NETWORK_MODE_IN_ROAMING_NETWORK_BOOL to config,
+            )
+        }
+    }
+
+    private companion object {
+        const val SUB_ID = 1
+    }
+}