From 67ac0faf3da788b6a0178e6a5c29c360bbe94cfb Mon Sep 17 00:00:00 2001 From: Haijie Hong Date: Thu, 28 Nov 2024 23:25:00 +0800 Subject: [PATCH] Add loading screen for Device details fragment to avoid ANR BUG: 343317785 Test: local tested Flag: com.android.settings.flags.enable_bluetooth_device_details_polish Change-Id: Iad57fc2fe4cb0a3f90e8d01310b9c7ad20d02233 --- .../BluetoothDetailsProfilesController.java | 23 +-- .../BluetoothDeviceDetailsFragment.java | 60 +++----- .../bluetooth/BluetoothFeatureProvider.java | 8 +- .../bluetooth/BluetoothFeatureProviderImpl.kt | 9 +- .../ui/view/DeviceDetailsFragmentFormatter.kt | 140 +++++++++++------- .../view/DeviceDetailsMoreSettingsFragment.kt | 54 +++---- ...luetoothDetailsProfilesControllerTest.java | 20 +-- .../BluetoothDeviceDetailsFragmentTest.java | 6 +- .../DeviceDetailsFragmentFormatterTest.kt | 63 +++----- 9 files changed, 186 insertions(+), 197 deletions(-) diff --git a/src/com/android/settings/bluetooth/BluetoothDetailsProfilesController.java b/src/com/android/settings/bluetooth/BluetoothDetailsProfilesController.java index 924ba2c7fa7..f782c6b2a6a 100644 --- a/src/com/android/settings/bluetooth/BluetoothDetailsProfilesController.java +++ b/src/com/android/settings/bluetooth/BluetoothDetailsProfilesController.java @@ -27,7 +27,6 @@ import android.sysprop.BluetoothProperties; 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 @@ public class BluetoothDetailsProfilesController extends BluetoothDetailsControll PreferenceFragmentCompat fragment, LocalBluetoothManager manager, CachedBluetoothDevice device, - Lifecycle lifecycle, - @Nullable List 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 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 633b44f62e9..403a82429cc 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.bluetooth.LocalBluetoothManager; 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 @@ public class BluetoothDeviceDetailsFragment extends RestrictedDashboardFragment 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 @@ public class BluetoothDeviceDetailsFragment extends RestrictedDashboardFragment } @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 controllers = getPreferenceControllers().stream() + .flatMap(List::stream) + .toList(); + mFormatter = + FeatureFactory.getFeatureFactory() + .getBluetoothFeatureProvider() + .getDeviceDetailsFragmentFormatter( + requireContext(), + this, + mBluetoothAdapter, + mCachedDevice, + controllers); + } mFormatter.updateLayout(FragmentTypeModel.DeviceDetailsMainFragment.INSTANCE); } } @@ -409,38 +425,8 @@ public class BluetoothDeviceDetailsFragment extends RestrictedDashboardFragment return super.onOptionsItemSelected(menuItem); } - @Override - protected void addPreferenceController(AbstractPreferenceController controller) { - if (Flags.enableBluetoothDeviceDetailsPolish()) { - List 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 createPreferenceControllers(Context context) { - List 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 controllers = new ArrayList<>(); if (mCachedDevice != null) { @@ -459,7 +445,7 @@ public class BluetoothDeviceDetailsFragment extends RestrictedDashboardFragment 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 d87e6096e92..dae7bb09bd4 100644 --- a/src/com/android/settings/bluetooth/BluetoothFeatureProvider.java +++ b/src/com/android/settings/bluetooth/BluetoothFeatureProvider.java @@ -26,10 +26,11 @@ import android.net.Uri; 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 @@ public interface BluetoothFeatureProvider { @NonNull DeviceDetailsFragmentFormatter getDeviceDetailsFragmentFormatter( @NonNull Context context, - @NonNull SettingsPreferenceFragment fragment, + @NonNull DashboardFragment fragment, @NonNull BluetoothAdapter bluetoothAdapter, - @NonNull CachedBluetoothDevice cachedDevice); + @NonNull CachedBluetoothDevice cachedDevice, + @NonNull List controllers); } diff --git a/src/com/android/settings/bluetooth/BluetoothFeatureProviderImpl.kt b/src/com/android/settings/bluetooth/BluetoothFeatureProviderImpl.kt index 082c6932c91..4807899ba49 100644 --- a/src/com/android/settings/bluetooth/BluetoothFeatureProviderImpl.kt +++ b/src/com/android/settings/bluetooth/BluetoothFeatureProviderImpl.kt @@ -23,13 +23,14 @@ import android.media.AudioManager 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 @@ open class BluetoothFeatureProviderImpl : BluetoothFeatureProvider { override fun getDeviceDetailsFragmentFormatter( context: Context, - fragment: SettingsPreferenceFragment, + fragment: DashboardFragment, bluetoothAdapter: BluetoothAdapter, - cachedDevice: CachedBluetoothDevice + cachedDevice: CachedBluetoothDevice, + controllers: List, ): 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 caa41ef86ef..b093d9c0a64 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.compose.collectAsStateWithLifecycle 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.model.FragmentTypeModel 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.flowOf 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? - - /** Updates device details fragment layout. */ - fun getInvisibleBluetoothProfiles(fragmentType: FragmentTypeModel): List? - /** Updates device details fragment layout. */ fun updateLayout(fragmentType: FragmentTypeModel) @@ -104,7 +105,8 @@ interface DeviceDetailsFragmentFormatter { @OptIn(ExperimentalCoroutinesApi::class) class DeviceDetailsFragmentFormatterImpl( private val context: Context, - private val fragment: SettingsPreferenceFragment, + private val fragment: DashboardFragment, + controllers: List, private val bluetoothAdapter: BluetoothAdapter, private val cachedDevice: CachedBluetoothDevice, private val backgroundCoroutineContext: CoroutineContext, @@ -112,40 +114,32 @@ class DeviceDetailsFragmentFormatterImpl( private val metricsFeatureProvider = FeatureFactory.featureFactory.metricsFeatureProvider private val prefVisibility = mutableMapOf>() private val prefVisibilityJobs = mutableListOf() + private var isLoading = false + private var prefKeyToController: Map = + 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? = - runBlocking { - viewModel - .getItems(fragmentType) - ?.filterIsInstance() - ?.map { it.preferenceKey } - } - - override fun getInvisibleBluetoothProfiles(fragmentType: FragmentTypeModel): List? = - runBlocking { - viewModel - .getItems(fragmentType) - ?.filterIsInstance() - ?.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 @@ class DeviceDetailsFragmentFormatterImpl( 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 @@ class DeviceDetailsFragmentFormatterImpl( 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 @@ class DeviceDetailsFragmentFormatterImpl( @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 @@ class DeviceDetailsFragmentFormatterImpl( } } + 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 47fda7455bc..cc0fe7bc33a 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.graphics.PorterDuff 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.Utils 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 @@ class DeviceDetailsMoreSettingsFragment : DashboardFragment() { return R.xml.bluetooth_device_more_settings_fragment } - override fun addPreferenceController(controller: AbstractPreferenceController) { - val keys: List? = - 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 -> 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 @@ class DeviceDetailsMoreSettingsFragment : DashboardFragment() { 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 { - 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 @@ class DeviceDetailsMoreSettingsFragment : DashboardFragment() { 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 e21bf9a605e..2d007e1b134 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 @@ public class BluetoothDetailsProfilesControllerTest extends BluetoothDetailsCont .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 @@ public class BluetoothDetailsProfilesControllerTest extends BluetoothDetailsCont @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 @@ public class BluetoothDetailsProfilesControllerTest extends BluetoothDetailsCont @Test public void prefKeyNotInBlockingList_showToggle() { - initController(List.of()); setupDevice(makeDefaultDeviceConfig()); addA2dpProfileToDevice(true, true, true); @@ -653,13 +658,4 @@ public class BluetoothDetailsProfilesControllerTest extends BluetoothDetailsCont assertThat(switches.getFirst().getTitle()).isEqualTo( mContext.getString(mLeAudioProfile.getNameResource(mDevice))); } - - private void initController(List 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 7e9017167d9..0e052ab154f 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.fragment.app.FragmentTransaction; 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 @@ public class BluetoothDeviceDetailsFragmentTest { 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 28eaeaaa194..d0bd27d7a6e 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.DeviceSetti 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 @@ class DeviceDetailsFragmentFormatterTest { @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,55 +102,22 @@ class DeviceDetailsFragmentFormatterTest { 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 { @@ -187,7 +158,7 @@ class DeviceDetailsFragmentFormatterTest { 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 @@ class DeviceDetailsFragmentFormatterTest { 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 @@ class DeviceDetailsFragmentFormatterTest { 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 @@ class DeviceDetailsFragmentFormatterTest { SettingsEnums.PAGE_UNKNOWN, SettingsEnums.ACTION_BLUETOOTH_DEVICE_DETAILS_ITEM_SHOWN, 0, - "keyboard_settings", 1) + "bluetooth_profiles", 1) } } @@ -242,9 +213,9 @@ class DeviceDetailsFragmentFormatterTest { 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 @@ class DeviceDetailsFragmentFormatterTest { .containsExactly( "bluetooth_device_header", "DEVICE_SETTING_${DeviceSettingId.DEVICE_SETTING_ID_ANC}", - "keyboard_settings") + "bluetooth_profiles") verify(featureFactory.metricsFeatureProvider) .action( SettingsEnums.PAGE_UNKNOWN,