Merge "Add loading screen for Device details fragment to avoid ANR" into main
diff --git a/src/com/android/settings/bluetooth/BluetoothDetailsProfilesController.java b/src/com/android/settings/bluetooth/BluetoothDetailsProfilesController.java
index 924ba2c..f782c6b 100644
--- a/src/com/android/settings/bluetooth/BluetoothDetailsProfilesController.java
+++ b/src/com/android/settings/bluetooth/BluetoothDetailsProfilesController.java
@@ -27,7 +27,6 @@
 import android.text.TextUtils;
 import android.util.Log;
 
-import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
 import androidx.preference.Preference;
 import androidx.preference.PreferenceCategory;
@@ -109,28 +108,34 @@
             PreferenceFragmentCompat fragment,
             LocalBluetoothManager manager,
             CachedBluetoothDevice device,
-            Lifecycle lifecycle,
-            @Nullable List<String> invisibleProfiles,
-            boolean hasExtraSpace) {
+            Lifecycle lifecycle) {
         super(context, fragment, device, lifecycle);
         mManager = manager;
         mProfileManager = mManager.getProfileManager();
         mCachedDevice = device;
         mCachedDeviceGroup = Utils.findAllCachedBluetoothDevicesByGroupId(mManager, mCachedDevice);
+    }
+
+    /** Sets the profiles to be hidden. */
+    public void setInvisibleProfiles(List<String> invisibleProfiles) {
         if (invisibleProfiles != null) {
             mInvisibleProfiles = Set.copyOf(invisibleProfiles);
         }
-        mHasExtraSpace = hasExtraSpace;
     }
 
-    @Override
-    protected void init(PreferenceScreen screen) {
-        mProfilesContainer = (PreferenceCategory)screen.findPreference(getPreferenceKey());
-        if (mHasExtraSpace) {
+    /** Sets whether it should show an extra padding on top of the preference. */
+    public void setHasExtraSpace(boolean hasExtraSpace) {
+        if (hasExtraSpace) {
             mProfilesContainer.setLayoutResource(R.layout.preference_bluetooth_profile_category);
         } else {
             mProfilesContainer.setLayoutResource(R.layout.preference_category_bluetooth_no_padding);
         }
+    }
+
+    @Override
+    protected void init(PreferenceScreen screen) {
+        mProfilesContainer = (PreferenceCategory) screen.findPreference(getPreferenceKey());
+        mProfilesContainer.setLayoutResource(R.layout.preference_bluetooth_profile_category);
         // Call refresh here even though it will get called later in onResume, to avoid the
         // list of switches appearing to "pop" into the page.
         refresh();
diff --git a/src/com/android/settings/bluetooth/BluetoothDeviceDetailsFragment.java b/src/com/android/settings/bluetooth/BluetoothDeviceDetailsFragment.java
index 633b44f..403a824 100644
--- a/src/com/android/settings/bluetooth/BluetoothDeviceDetailsFragment.java
+++ b/src/com/android/settings/bluetooth/BluetoothDeviceDetailsFragment.java
@@ -61,7 +61,6 @@
 import com.android.settingslib.core.AbstractPreferenceController;
 import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
 import com.android.settingslib.core.lifecycle.Lifecycle;
-import com.android.settingslib.core.lifecycle.LifecycleObserver;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -289,9 +288,12 @@
         getController(
                 SlicePreferenceController.class,
                 controller -> {
-                    controller.setSliceUri(finalControlUri);
-                    controller.onStart();
-                    controller.displayPreference(getPreferenceScreen());
+                    if (getPreferenceScreen().findPreference(controller.getPreferenceKey())
+                            != null) {
+                        controller.setSliceUri(finalControlUri);
+                        controller.onStart();
+                        controller.displayPreference(getPreferenceScreen());
+                    }
                 });
 
         // Temporarily fix the issue that the page will be automatically scrolled to a wrong
@@ -352,9 +354,23 @@
     }
 
     @Override
-    public void onCreatePreferences(@NonNull Bundle savedInstanceState, @NonNull String rootKey) {
-        super.onCreatePreferences(savedInstanceState, rootKey);
+    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
+        super.onViewCreated(view, savedInstanceState);
         if (Flags.enableBluetoothDeviceDetailsPolish()) {
+            if (mFormatter == null) {
+                List<AbstractPreferenceController> controllers = getPreferenceControllers().stream()
+                        .flatMap(List::stream)
+                        .toList();
+                mFormatter =
+                        FeatureFactory.getFeatureFactory()
+                                .getBluetoothFeatureProvider()
+                                .getDeviceDetailsFragmentFormatter(
+                                        requireContext(),
+                                        this,
+                                        mBluetoothAdapter,
+                                        mCachedDevice,
+                                        controllers);
+            }
             mFormatter.updateLayout(FragmentTypeModel.DeviceDetailsMainFragment.INSTANCE);
         }
     }
@@ -410,37 +426,7 @@
     }
 
     @Override
-    protected void addPreferenceController(AbstractPreferenceController controller) {
-        if (Flags.enableBluetoothDeviceDetailsPolish()) {
-            List<String> keys =
-                    mFormatter.getVisiblePreferenceKeys(
-                            FragmentTypeModel.DeviceDetailsMainFragment.INSTANCE);
-            Lifecycle lifecycle = getSettingsLifecycle();
-            if (keys == null || keys.contains(controller.getPreferenceKey())) {
-                super.addPreferenceController(controller);
-            } else if (controller instanceof LifecycleObserver) {
-                lifecycle.removeObserver((LifecycleObserver) controller);
-            }
-        } else {
-            super.addPreferenceController(controller);
-        }
-    }
-
-    @Override
     protected List<AbstractPreferenceController> createPreferenceControllers(Context context) {
-        List<String> invisibleProfiles = List.of();
-        if (Flags.enableBluetoothDeviceDetailsPolish()) {
-            if (mFormatter == null) {
-                mFormatter =
-                        FeatureFactory.getFeatureFactory()
-                                .getBluetoothFeatureProvider()
-                                .getDeviceDetailsFragmentFormatter(
-                                        requireContext(), this, mBluetoothAdapter, mCachedDevice);
-            }
-            invisibleProfiles =
-                    mFormatter.getInvisibleBluetoothProfiles(
-                            FragmentTypeModel.DeviceDetailsMainFragment.INSTANCE);
-        }
         ArrayList<AbstractPreferenceController> controllers = new ArrayList<>();
 
         if (mCachedDevice != null) {
@@ -459,7 +445,7 @@
             controllers.add(new BluetoothDetailsSpatialAudioController(context, this, mCachedDevice,
                     lifecycle));
             controllers.add(new BluetoothDetailsProfilesController(context, this, mManager,
-                    mCachedDevice, lifecycle, invisibleProfiles, invisibleProfiles == null));
+                    mCachedDevice, lifecycle));
             controllers.add(new BluetoothDetailsMacAddressController(context, this, mCachedDevice,
                     lifecycle));
             controllers.add(new StylusDevicesController(context, mInputDevice, mCachedDevice,
diff --git a/src/com/android/settings/bluetooth/BluetoothFeatureProvider.java b/src/com/android/settings/bluetooth/BluetoothFeatureProvider.java
index d87e609..dae7bb0 100644
--- a/src/com/android/settings/bluetooth/BluetoothFeatureProvider.java
+++ b/src/com/android/settings/bluetooth/BluetoothFeatureProvider.java
@@ -26,10 +26,11 @@
 import androidx.annotation.NonNull;
 import androidx.preference.Preference;
 
-import com.android.settings.SettingsPreferenceFragment;
 import com.android.settings.bluetooth.ui.view.DeviceDetailsFragmentFormatter;
+import com.android.settings.dashboard.DashboardFragment;
 import com.android.settingslib.bluetooth.CachedBluetoothDevice;
 import com.android.settingslib.bluetooth.devicesettings.data.repository.DeviceSettingRepository;
+import com.android.settingslib.core.AbstractPreferenceController;
 
 import kotlinx.coroutines.CoroutineScope;
 
@@ -100,7 +101,8 @@
     @NonNull
     DeviceDetailsFragmentFormatter getDeviceDetailsFragmentFormatter(
             @NonNull Context context,
-            @NonNull SettingsPreferenceFragment fragment,
+            @NonNull DashboardFragment fragment,
             @NonNull BluetoothAdapter bluetoothAdapter,
-            @NonNull CachedBluetoothDevice cachedDevice);
+            @NonNull CachedBluetoothDevice cachedDevice,
+            @NonNull List<AbstractPreferenceController> controllers);
 }
diff --git a/src/com/android/settings/bluetooth/BluetoothFeatureProviderImpl.kt b/src/com/android/settings/bluetooth/BluetoothFeatureProviderImpl.kt
index 082c693..4807899 100644
--- a/src/com/android/settings/bluetooth/BluetoothFeatureProviderImpl.kt
+++ b/src/com/android/settings/bluetooth/BluetoothFeatureProviderImpl.kt
@@ -23,13 +23,14 @@
 import android.media.Spatializer
 import android.net.Uri
 import androidx.preference.Preference
-import com.android.settings.SettingsPreferenceFragment
 import com.android.settings.bluetooth.ui.view.DeviceDetailsFragmentFormatter
 import com.android.settings.bluetooth.ui.view.DeviceDetailsFragmentFormatterImpl
+import com.android.settings.dashboard.DashboardFragment
 import com.android.settingslib.bluetooth.BluetoothUtils
 import com.android.settingslib.bluetooth.CachedBluetoothDevice
 import com.android.settingslib.bluetooth.devicesettings.data.repository.DeviceSettingRepository
 import com.android.settingslib.bluetooth.devicesettings.data.repository.DeviceSettingRepositoryImpl
+import com.android.settingslib.core.AbstractPreferenceController
 import com.google.common.collect.ImmutableList
 import com.google.common.collect.ImmutableSet
 import kotlinx.coroutines.CoroutineScope
@@ -78,13 +79,15 @@
 
     override fun getDeviceDetailsFragmentFormatter(
         context: Context,
-        fragment: SettingsPreferenceFragment,
+        fragment: DashboardFragment,
         bluetoothAdapter: BluetoothAdapter,
-        cachedDevice: CachedBluetoothDevice
+        cachedDevice: CachedBluetoothDevice,
+        controllers: List<AbstractPreferenceController>,
     ): DeviceDetailsFragmentFormatter {
         return DeviceDetailsFragmentFormatterImpl(
             context,
             fragment,
+            controllers,
             bluetoothAdapter,
             cachedDevice,
             Dispatchers.IO
diff --git a/src/com/android/settings/bluetooth/ui/view/DeviceDetailsFragmentFormatter.kt b/src/com/android/settings/bluetooth/ui/view/DeviceDetailsFragmentFormatter.kt
index caa41ef..b093d9c 100644
--- a/src/com/android/settings/bluetooth/ui/view/DeviceDetailsFragmentFormatter.kt
+++ b/src/com/android/settings/bluetooth/ui/view/DeviceDetailsFragmentFormatter.kt
@@ -45,7 +45,8 @@
 import androidx.lifecycle.lifecycleScope
 import androidx.preference.Preference
 import com.android.settings.R
-import com.android.settings.SettingsPreferenceFragment
+import com.android.settings.bluetooth.BlockingPrefWithSliceController
+import com.android.settings.bluetooth.BluetoothDetailsProfilesController
 import com.android.settings.bluetooth.ui.composable.Icon
 import com.android.settings.bluetooth.ui.composable.MultiTogglePreference
 import com.android.settings.bluetooth.ui.layout.DeviceSettingLayout
@@ -54,12 +55,18 @@
 import com.android.settings.bluetooth.ui.view.DeviceDetailsMoreSettingsFragment.Companion.KEY_DEVICE_ADDRESS
 import com.android.settings.bluetooth.ui.viewmodel.BluetoothDeviceDetailsViewModel
 import com.android.settings.core.SubSettingLauncher
+import com.android.settings.dashboard.DashboardFragment
 import com.android.settings.overlay.FeatureFactory
 import com.android.settings.spa.preference.ComposePreference
 import com.android.settingslib.bluetooth.CachedBluetoothDevice
+import com.android.settingslib.bluetooth.devicesettings.DeviceSettingId
 import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingActionModel
 import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingConfigItemModel
 import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingIcon
+import com.android.settingslib.core.AbstractPreferenceController
+import com.android.settingslib.core.lifecycle.LifecycleObserver
+import com.android.settingslib.core.lifecycle.events.OnPause
+import com.android.settingslib.core.lifecycle.events.OnStop
 import com.android.settingslib.spa.framework.theme.SettingsDimension
 import com.android.settingslib.spa.widget.preference.Preference as SpaPreference
 import com.android.settingslib.spa.widget.preference.PreferenceModel
@@ -81,16 +88,10 @@
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.onEach
-import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.launch
 
 /** Handles device details fragment layout according to config. */
 interface DeviceDetailsFragmentFormatter {
-    /** Gets keys of visible preferences in built-in preference in xml. */
-    fun getVisiblePreferenceKeys(fragmentType: FragmentTypeModel): List<String>?
-
-    /** Updates device details fragment layout. */
-    fun getInvisibleBluetoothProfiles(fragmentType: FragmentTypeModel): List<String>?
-
     /** Updates device details fragment layout. */
     fun updateLayout(fragmentType: FragmentTypeModel)
 
@@ -104,7 +105,8 @@
 @OptIn(ExperimentalCoroutinesApi::class)
 class DeviceDetailsFragmentFormatterImpl(
     private val context: Context,
-    private val fragment: SettingsPreferenceFragment,
+    private val fragment: DashboardFragment,
+    controllers: List<AbstractPreferenceController>,
     private val bluetoothAdapter: BluetoothAdapter,
     private val cachedDevice: CachedBluetoothDevice,
     private val backgroundCoroutineContext: CoroutineContext,
@@ -112,40 +114,32 @@
     private val metricsFeatureProvider = FeatureFactory.featureFactory.metricsFeatureProvider
     private val prefVisibility = mutableMapOf<String, MutableStateFlow<Boolean>>()
     private val prefVisibilityJobs = mutableListOf<Job>()
+    private var isLoading = false
+    private var prefKeyToController: Map<String, AbstractPreferenceController> =
+        controllers.associateBy { it.preferenceKey }
 
     private val viewModel: BluetoothDeviceDetailsViewModel =
         ViewModelProvider(
-                fragment,
-                BluetoothDeviceDetailsViewModel.Factory(
-                    fragment.requireActivity().application,
-                    bluetoothAdapter,
-                    cachedDevice,
-                    backgroundCoroutineContext,
-                ),
-            )
+            fragment,
+            BluetoothDeviceDetailsViewModel.Factory(
+                fragment.requireActivity().application,
+                bluetoothAdapter,
+                cachedDevice,
+                backgroundCoroutineContext,
+            ),
+        )
             .get(BluetoothDeviceDetailsViewModel::class.java)
 
-    override fun getVisiblePreferenceKeys(fragmentType: FragmentTypeModel): List<String>? =
-        runBlocking {
-            viewModel
-                .getItems(fragmentType)
-                ?.filterIsInstance<DeviceSettingConfigItemModel.BuiltinItem>()
-                ?.map { it.preferenceKey }
-        }
-
-    override fun getInvisibleBluetoothProfiles(fragmentType: FragmentTypeModel): List<String>? =
-        runBlocking {
-            viewModel
-                .getItems(fragmentType)
-                ?.filterIsInstance<DeviceSettingConfigItemModel.BuiltinItem.BluetoothProfilesItem>()
-                ?.firstOrNull()
-                ?.invisibleProfiles
-        }
-
     /** Updates bluetooth device details fragment layout. */
-    override fun updateLayout(fragmentType: FragmentTypeModel) = runBlocking {
-        val items = viewModel.getItems(fragmentType) ?: return@runBlocking
-        val layout = viewModel.getLayout(fragmentType) ?: return@runBlocking
+    override fun updateLayout(fragmentType: FragmentTypeModel) {
+        fragment.setLoading(true, false)
+        isLoading = true
+        fragment.lifecycleScope.launch { updateLayoutInternal(fragmentType) }
+    }
+
+    private suspend fun updateLayoutInternal(fragmentType: FragmentTypeModel) {
+        val items = viewModel.getItems(fragmentType) ?: return
+        val layout = viewModel.getLayout(fragmentType) ?: return
 
         val prefKeyToSettingId =
             items
@@ -156,21 +150,21 @@
         for (i in 0 until fragment.preferenceScreen.preferenceCount) {
             val pref = fragment.preferenceScreen.getPreference(i)
             prefKeyToSettingId[pref.key]?.let { id -> settingIdToXmlPreferences[id] = pref }
+            if (pref.key !in prefKeyToSettingId) {
+                getController(pref.key)?.let { disableController(it) }
+            }
         }
         fragment.preferenceScreen.removeAll()
         for (job in prefVisibilityJobs) {
             job.cancel()
         }
         prefVisibilityJobs.clear()
-
         for (row in items.indices) {
-            val settingId = items[row].settingId
+            val settingItem = items[row]
+            val settingId = settingItem.settingId
             if (settingIdToXmlPreferences.containsKey(settingId)) {
-                fragment.preferenceScreen.addPreference(
-                    settingIdToXmlPreferences[settingId]!!
-                        .apply { order = row }
-                        .also { logItemShown(it.key, it.isVisible) }
-                )
+                val pref = settingIdToXmlPreferences[settingId]!!.apply { order = row }
+                fragment.preferenceScreen.addPreference(pref)
             } else {
                 val prefKey = getPreferenceKey(settingId)
                 prefVisibilityJobs.add(
@@ -195,6 +189,29 @@
             isSelectable = false
             setContent { Spacer(modifier = Modifier.height(1.dp)) }
         })
+
+        for (row in items.indices) {
+            val settingItem = items[row]
+            val settingId = settingItem.settingId
+            if (settingIdToXmlPreferences.containsKey(settingId)) {
+                val pref = fragment.preferenceScreen.getPreference(row)
+                if (settingId == DeviceSettingId.DEVICE_SETTING_ID_BLUETOOTH_PROFILES) {
+                    (getController(pref.key) as? BluetoothDetailsProfilesController)?.run {
+                        if (settingItem is DeviceSettingConfigItemModel.BuiltinItem.BluetoothProfilesItem) {
+                            setInvisibleProfiles(settingItem.invisibleProfiles)
+                            setHasExtraSpace(false)
+                        }
+                    }
+                }
+                getController(pref.key)?.displayPreference(fragment.preferenceScreen)
+                logItemShown(pref.key, pref.isVisible)
+            }
+        }
+
+        if (isLoading) {
+            fragment.setLoading(false, false)
+            isLoading = false
+        }
     }
 
     override fun getMenuItem(
@@ -232,14 +249,14 @@
     @Composable
     private fun buildPreference(layout: DeviceSettingLayout, row: Int, prefKey: String) {
         val contents by
-            remember(row) { getDevicesSettingForRow(layout, row) }
-                .collectAsStateWithLifecycle(initialValue = listOf())
+        remember(row) { getDevicesSettingForRow(layout, row) }
+            .collectAsStateWithLifecycle(initialValue = listOf())
 
         val highlighted by
-            remember(row) {
-                    layout.rows[row].columns.map { columns -> columns.any { it.highlighted } }
-                }
-                .collectAsStateWithLifecycle(initialValue = false)
+        remember(row) {
+            layout.rows[row].columns.map { columns -> columns.any { it.highlighted } }
+        }
+            .collectAsStateWithLifecycle(initialValue = false)
 
         val settings = contents
         AnimatedVisibility(visible = settings.isNotEmpty(), enter = fadeIn(), exit = fadeOut()) {
@@ -454,6 +471,29 @@
         }
     }
 
+    private fun getController(key: String): AbstractPreferenceController? {
+        return prefKeyToController[key]
+    }
+
+    private fun disableController(controller: AbstractPreferenceController) {
+        if (controller is LifecycleObserver) {
+            fragment.settingsLifecycle.removeObserver(controller as LifecycleObserver)
+        }
+
+        if (controller is BlockingPrefWithSliceController) {
+            // Make UiBlockListener finished, otherwise UI will flicker.
+            controller.onChanged(null)
+        }
+
+        if (controller is OnPause) {
+            (controller as OnPause).onPause()
+        }
+
+        if (controller is OnStop) {
+            (controller as OnStop).onStop()
+        }
+    }
+
     private fun getPreferenceKey(settingId: Int) = "DEVICE_SETTING_${settingId}"
 
     private companion object {
diff --git a/src/com/android/settings/bluetooth/ui/view/DeviceDetailsMoreSettingsFragment.kt b/src/com/android/settings/bluetooth/ui/view/DeviceDetailsMoreSettingsFragment.kt
index 47fda74..cc0fe7b 100644
--- a/src/com/android/settings/bluetooth/ui/view/DeviceDetailsMoreSettingsFragment.kt
+++ b/src/com/android/settings/bluetooth/ui/view/DeviceDetailsMoreSettingsFragment.kt
@@ -25,6 +25,7 @@
 import android.os.Bundle
 import android.view.Menu
 import android.view.MenuItem
+import android.view.View
 import androidx.lifecycle.lifecycleScope
 import com.android.settings.R
 import com.android.settings.bluetooth.BluetoothDetailsAudioDeviceTypeController
@@ -33,12 +34,12 @@
 import com.android.settings.bluetooth.ui.model.DeviceSettingPreferenceModel
 import com.android.settings.bluetooth.ui.model.FragmentTypeModel
 import com.android.settings.dashboard.DashboardFragment
+import com.android.settings.flags.Flags
 import com.android.settings.overlay.FeatureFactory.Companion.featureFactory
 import com.android.settingslib.bluetooth.CachedBluetoothDevice
 import com.android.settingslib.bluetooth.LocalBluetoothManager
 import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingIcon
 import com.android.settingslib.core.AbstractPreferenceController
-import com.android.settingslib.core.lifecycle.LifecycleObserver
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.filterNotNull
@@ -88,17 +89,29 @@
         return R.xml.bluetooth_device_more_settings_fragment
     }
 
-    override fun addPreferenceController(controller: AbstractPreferenceController) {
-        val keys: List<String>? =
-            formatter.getVisiblePreferenceKeys(FragmentTypeModel.DeviceDetailsMoreSettingsFragment)
-        val lifecycle = settingsLifecycle
-        if (keys == null || keys.contains(controller.preferenceKey)) {
-            super.addPreferenceController(controller)
-        } else if (controller is LifecycleObserver) {
-            lifecycle.removeObserver((controller as LifecycleObserver))
+    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+        super.onViewCreated(view, savedInstanceState)
+        if (!this::formatter.isInitialized) {
+            val controllers = preferenceControllers.stream()
+                .flatMap { obj: List<AbstractPreferenceController?> -> obj.stream() }
+                .toList()
+            val bluetoothManager = requireContext().getSystemService(BluetoothManager::class.java)
+            formatter =
+                featureFactory
+                    .bluetoothFeatureProvider
+                    .getDeviceDetailsFragmentFormatter(
+                        requireContext(), this, bluetoothManager.adapter, cachedDevice, controllers
+                    )
         }
+        formatter.updateLayout(FragmentTypeModel.DeviceDetailsMoreSettingsFragment)
+        helpItem =
+            formatter
+                .getMenuItem(FragmentTypeModel.DeviceDetailsMoreSettingsFragment)
+                .stateIn(lifecycleScope, SharingStarted.WhileSubscribed(), initialValue = null)
     }
 
+
+
     private fun getCachedDevice(): CachedBluetoothDevice? {
         val bluetoothAddress = arguments?.getString(KEY_DEVICE_ADDRESS) ?: return null
         localBluetoothManager = Utils.getLocalBtManager(context) ?: return null
@@ -107,32 +120,13 @@
         return Utils.getLocalBtManager(context).cachedDeviceManager.findDevice(remoteDevice)
     }
 
-    override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
-        super.onCreatePreferences(savedInstanceState, rootKey)
-        formatter.updateLayout(FragmentTypeModel.DeviceDetailsMoreSettingsFragment)
-    }
-
     override fun createPreferenceControllers(context: Context): List<AbstractPreferenceController> {
-        val bluetoothManager = context.getSystemService(BluetoothManager::class.java)
         cachedDevice =
             getCachedDevice()
                 ?: run {
                     finish()
                     return emptyList()
                 }
-        if (!this::formatter.isInitialized) {
-            formatter =
-                featureFactory.bluetoothFeatureProvider.getDeviceDetailsFragmentFormatter(
-                    requireContext(),
-                    this,
-                    bluetoothManager.adapter,
-                    cachedDevice,
-                )
-        }
-        helpItem =
-            formatter
-                .getMenuItem(FragmentTypeModel.DeviceDetailsMoreSettingsFragment)
-                .stateIn(lifecycleScope, SharingStarted.WhileSubscribed(), initialValue = null)
         return listOf(
             BluetoothDetailsProfilesController(
                 context,
@@ -140,10 +134,6 @@
                 localBluetoothManager,
                 cachedDevice,
                 settingsLifecycle,
-                formatter.getInvisibleBluetoothProfiles(
-                    FragmentTypeModel.DeviceDetailsMoreSettingsFragment
-                ),
-                false,
             ),
             BluetoothDetailsAudioDeviceTypeController(
                 context,
diff --git a/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsProfilesControllerTest.java b/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsProfilesControllerTest.java
index e21bf9a..2d007e1 100644
--- a/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsProfilesControllerTest.java
+++ b/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsProfilesControllerTest.java
@@ -120,7 +120,12 @@
                 .thenAnswer(invocation -> ImmutableList.of(mConnectableProfiles));
 
         setupDevice(mDeviceConfig);
-        initController(List.of());
+        mController = new BluetoothDetailsProfilesController(mContext, mFragment, mLocalManager,
+                mCachedDevice, mLifecycle);
+        mProfiles.setKey(mController.getPreferenceKey());
+        mController.mProfilesContainer = mProfiles;
+        mScreen.removeAll();
+        mScreen.addPreference(mProfiles);
         BluetoothProperties.le_audio_allow_list(Lists.newArrayList(LE_DEVICE_MODEL));
     }
 
@@ -550,7 +555,8 @@
 
     @Test
     public void prefKeyInBlockingList_hideToggle() {
-        initController(List.of("A2DP"));
+        mController.setInvisibleProfiles(List.of("A2DP"));
+        mController.setHasExtraSpace(true);
         setupDevice(makeDefaultDeviceConfig());
 
         addA2dpProfileToDevice(true, true, true);
@@ -565,7 +571,6 @@
 
     @Test
     public void prefKeyNotInBlockingList_showToggle() {
-        initController(List.of());
         setupDevice(makeDefaultDeviceConfig());
 
         addA2dpProfileToDevice(true, true, true);
@@ -653,13 +658,4 @@
         assertThat(switches.getFirst().getTitle()).isEqualTo(
                 mContext.getString(mLeAudioProfile.getNameResource(mDevice)));
     }
-
-    private void initController(List<String> invisibleProfiles) {
-        mController = new BluetoothDetailsProfilesController(mContext, mFragment, mLocalManager,
-                mCachedDevice, mLifecycle, invisibleProfiles, true);
-        mProfiles.setKey(mController.getPreferenceKey());
-        mController.mProfilesContainer = mProfiles;
-        mScreen.removeAll();
-        mScreen.addPreference(mProfiles);
-    }
 }
diff --git a/tests/robotests/src/com/android/settings/bluetooth/BluetoothDeviceDetailsFragmentTest.java b/tests/robotests/src/com/android/settings/bluetooth/BluetoothDeviceDetailsFragmentTest.java
index 7e90171..0e052ab 100644
--- a/tests/robotests/src/com/android/settings/bluetooth/BluetoothDeviceDetailsFragmentTest.java
+++ b/tests/robotests/src/com/android/settings/bluetooth/BluetoothDeviceDetailsFragmentTest.java
@@ -52,7 +52,6 @@
 import androidx.preference.PreferenceScreen;
 
 import com.android.settings.R;
-import com.android.settings.bluetooth.ui.model.FragmentTypeModel;
 import com.android.settings.bluetooth.ui.view.DeviceDetailsFragmentFormatter;
 import com.android.settings.flags.Flags;
 import com.android.settings.testutils.FakeFeatureFactory;
@@ -122,10 +121,7 @@
         removeInputDeviceWithMatchingBluetoothAddress();
         FakeFeatureFactory fakeFeatureFactory = FakeFeatureFactory.setupForTest();
         when(fakeFeatureFactory.mBluetoothFeatureProvider.getDeviceDetailsFragmentFormatter(any(),
-                any(), any(), eq(mCachedDevice))).thenReturn(mFormatter);
-        when(mFormatter.getVisiblePreferenceKeys(
-                        FragmentTypeModel.DeviceDetailsMainFragment.INSTANCE))
-                .thenReturn(null);
+                any(), any(), eq(mCachedDevice), any())).thenReturn(mFormatter);
 
         mFragment = setupFragment();
         mFragment.onAttach(mContext);
diff --git a/tests/robotests/src/com/android/settings/bluetooth/ui/view/DeviceDetailsFragmentFormatterTest.kt b/tests/robotests/src/com/android/settings/bluetooth/ui/view/DeviceDetailsFragmentFormatterTest.kt
index 28eaeaa..d0bd27d 100644
--- a/tests/robotests/src/com/android/settings/bluetooth/ui/view/DeviceDetailsFragmentFormatterTest.kt
+++ b/tests/robotests/src/com/android/settings/bluetooth/ui/view/DeviceDetailsFragmentFormatterTest.kt
@@ -39,6 +39,7 @@
 import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingModel
 import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingStateModel
 import com.android.settingslib.bluetooth.devicesettings.shared.model.ToggleModel
+import com.android.settingslib.core.AbstractPreferenceController
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.delay
@@ -73,6 +74,9 @@
     @Mock private lateinit var cachedDevice: CachedBluetoothDevice
     @Mock private lateinit var bluetoothAdapter: BluetoothAdapter
     @Mock private lateinit var repository: DeviceSettingRepository
+    @Mock private lateinit var profileController: AbstractPreferenceController
+    @Mock private lateinit var headerController: AbstractPreferenceController
+    @Mock private lateinit var buttonController: AbstractPreferenceController
 
     private lateinit var context: Context
     private lateinit var fragment: TestFragment
@@ -98,56 +102,23 @@
         fragment.preferenceScreen.run {
             addPreference(Preference(context).apply { key = "bluetooth_device_header" })
             addPreference(Preference(context).apply { key = "action_buttons" })
-            addPreference(Preference(context).apply { key = "keyboard_settings" })
+            addPreference(Preference(context).apply { key = "bluetooth_profiles" })
         }
+        `when`(profileController.preferenceKey).thenReturn("bluetooth_profiles")
+        `when`(headerController.preferenceKey).thenReturn("bluetooth_device_header")
+        `when`(buttonController.preferenceKey).thenReturn("action_buttons")
 
         underTest =
             DeviceDetailsFragmentFormatterImpl(
                 context,
                 fragment,
+                listOf(profileController, headerController, buttonController),
                 bluetoothAdapter,
                 cachedDevice,
                 testScope.testScheduler)
     }
 
     @Test
-    fun getVisiblePreferenceKeysForMainPage_hasConfig_returnList() {
-        testScope.runTest {
-            `when`(repository.getDeviceSettingsConfig(cachedDevice))
-                .thenReturn(
-                    DeviceSettingConfigModel(
-                        listOf(
-                            DeviceSettingConfigItemModel.BuiltinItem.CommonBuiltinItem(
-                                DeviceSettingId.DEVICE_SETTING_ID_HEADER,
-                                highlighted = false,
-                                preferenceKey = "bluetooth_device_header"
-                            ),
-                            DeviceSettingConfigItemModel.BuiltinItem.CommonBuiltinItem(
-                                DeviceSettingId.DEVICE_SETTING_ID_ACTION_BUTTONS, highlighted = false, preferenceKey = "action_buttons"),
-                        ),
-                        listOf(),
-                        null))
-
-            val keys =
-                underTest.getVisiblePreferenceKeys(FragmentTypeModel.DeviceDetailsMainFragment)
-
-            assertThat(keys).containsExactly("bluetooth_device_header", "action_buttons")
-        }
-    }
-
-    @Test
-    fun getVisiblePreferenceKeysForMainPage_noConfig_returnNull() {
-        testScope.runTest {
-            `when`(repository.getDeviceSettingsConfig(cachedDevice)).thenReturn(null)
-
-            val keys =
-                underTest.getVisiblePreferenceKeys(FragmentTypeModel.DeviceDetailsMainFragment)
-
-            assertThat(keys).isNull()
-        }
-    }
-
-    @Test
     fun getMenuItem_returnItem() {
         testScope.runTest {
             `when`(repository.getDeviceSettingsConfig(cachedDevice))
@@ -187,7 +158,7 @@
             underTest.updateLayout(FragmentTypeModel.DeviceDetailsMainFragment)
 
             assertThat(getDisplayedPreferences().mapNotNull { it.key })
-                .containsExactly("bluetooth_device_header", "action_buttons", "keyboard_settings")
+                .containsExactly("bluetooth_device_header", "action_buttons", "bluetooth_profiles")
         }
     }
 
@@ -202,8 +173,8 @@
                                 DeviceSettingId.DEVICE_SETTING_ID_HEADER,
                                 highlighted = false, preferenceKey = "bluetooth_device_header"),
                             DeviceSettingConfigItemModel.BuiltinItem.CommonBuiltinItem(
-                                DeviceSettingId.DEVICE_SETTING_ID_KEYBOARD_SETTINGS,
-                                highlighted = false, preferenceKey = "keyboard_settings"),
+                                DeviceSettingId.DEVICE_SETTING_ID_BLUETOOTH_PROFILES,
+                                highlighted = false, preferenceKey = "bluetooth_profiles"),
                         ),
                         listOf(),
                         null))
@@ -212,7 +183,7 @@
             runCurrent()
 
             assertThat(getDisplayedPreferences().mapNotNull { it.key })
-                .containsExactly("bluetooth_device_header", "keyboard_settings")
+                .containsExactly("bluetooth_device_header", "bluetooth_profiles")
             verify(featureFactory.metricsFeatureProvider)
                 .action(
                     SettingsEnums.PAGE_UNKNOWN,
@@ -224,7 +195,7 @@
                     SettingsEnums.PAGE_UNKNOWN,
                     SettingsEnums.ACTION_BLUETOOTH_DEVICE_DETAILS_ITEM_SHOWN,
                     0,
-                    "keyboard_settings", 1)
+                    "bluetooth_profiles", 1)
         }
     }
 
@@ -242,9 +213,9 @@
                             DeviceSettingConfigItemModel.AppProvidedItem(
                                 DeviceSettingId.DEVICE_SETTING_ID_ANC, highlighted = false),
                             DeviceSettingConfigItemModel.BuiltinItem.CommonBuiltinItem(
-                                DeviceSettingId.DEVICE_SETTING_ID_KEYBOARD_SETTINGS,
+                                DeviceSettingId.DEVICE_SETTING_ID_BLUETOOTH_PROFILES,
                                 highlighted = false,
-                                preferenceKey = "keyboard_settings"),
+                                preferenceKey = "bluetooth_profiles"),
                         ),
                         listOf(),
                         null))
@@ -273,7 +244,7 @@
                 .containsExactly(
                     "bluetooth_device_header",
                     "DEVICE_SETTING_${DeviceSettingId.DEVICE_SETTING_ID_ANC}",
-                    "keyboard_settings")
+                    "bluetooth_profiles")
             verify(featureFactory.metricsFeatureProvider)
                 .action(
                     SettingsEnums.PAGE_UNKNOWN,