Loading src/com/android/settings/bluetooth/BluetoothDetailsProfilesController.java +14 −9 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -109,28 +108,34 @@ public class BluetoothDetailsProfilesController extends BluetoothDetailsControll 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(); Loading src/com/android/settings/bluetooth/BluetoothDeviceDetailsFragment.java +23 −37 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -289,9 +288,12 @@ public class BluetoothDeviceDetailsFragment extends RestrictedDashboardFragment getController( SlicePreferenceController.class, controller -> { 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 Loading Loading @@ -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<AbstractPreferenceController> controllers = getPreferenceControllers().stream() .flatMap(List::stream) .toList(); mFormatter = FeatureFactory.getFeatureFactory() .getBluetoothFeatureProvider() .getDeviceDetailsFragmentFormatter( requireContext(), this, mBluetoothAdapter, mCachedDevice, controllers); } mFormatter.updateLayout(FragmentTypeModel.DeviceDetailsMainFragment.INSTANCE); } } Loading Loading @@ -409,38 +425,8 @@ public class BluetoothDeviceDetailsFragment extends RestrictedDashboardFragment return super.onOptionsItemSelected(menuItem); } @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) { Loading @@ -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, Loading src/com/android/settings/bluetooth/BluetoothFeatureProvider.java +5 −3 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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<AbstractPreferenceController> controllers); } src/com/android/settings/bluetooth/BluetoothFeatureProviderImpl.kt +6 −3 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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<AbstractPreferenceController>, ): DeviceDetailsFragmentFormatter { return DeviceDetailsFragmentFormatterImpl( context, fragment, controllers, bluetoothAdapter, cachedDevice, Dispatchers.IO Loading src/com/android/settings/bluetooth/ui/view/DeviceDetailsFragmentFormatter.kt +90 −50 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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 Loading @@ -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<String>? /** Updates device details fragment layout. */ fun getInvisibleBluetoothProfiles(fragmentType: FragmentTypeModel): List<String>? /** Updates device details fragment layout. */ fun updateLayout(fragmentType: FragmentTypeModel) Loading @@ -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<AbstractPreferenceController>, private val bluetoothAdapter: BluetoothAdapter, private val cachedDevice: CachedBluetoothDevice, private val backgroundCoroutineContext: CoroutineContext, Loading @@ -112,6 +114,9 @@ class DeviceDetailsFragmentFormatterImpl( 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( Loading @@ -125,27 +130,16 @@ class DeviceDetailsFragmentFormatterImpl( ) .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) { fragment.setLoading(true, false) isLoading = true fragment.lifecycleScope.launch { updateLayoutInternal(fragmentType) } } /** 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 private suspend fun updateLayoutInternal(fragmentType: FragmentTypeModel) { val items = viewModel.getItems(fragmentType) ?: return val layout = viewModel.getLayout(fragmentType) ?: return val prefKeyToSettingId = items Loading @@ -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( Loading @@ -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( Loading Loading @@ -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 { Loading Loading
src/com/android/settings/bluetooth/BluetoothDetailsProfilesController.java +14 −9 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -109,28 +108,34 @@ public class BluetoothDetailsProfilesController extends BluetoothDetailsControll 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(); Loading
src/com/android/settings/bluetooth/BluetoothDeviceDetailsFragment.java +23 −37 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -289,9 +288,12 @@ public class BluetoothDeviceDetailsFragment extends RestrictedDashboardFragment getController( SlicePreferenceController.class, controller -> { 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 Loading Loading @@ -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<AbstractPreferenceController> controllers = getPreferenceControllers().stream() .flatMap(List::stream) .toList(); mFormatter = FeatureFactory.getFeatureFactory() .getBluetoothFeatureProvider() .getDeviceDetailsFragmentFormatter( requireContext(), this, mBluetoothAdapter, mCachedDevice, controllers); } mFormatter.updateLayout(FragmentTypeModel.DeviceDetailsMainFragment.INSTANCE); } } Loading Loading @@ -409,38 +425,8 @@ public class BluetoothDeviceDetailsFragment extends RestrictedDashboardFragment return super.onOptionsItemSelected(menuItem); } @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) { Loading @@ -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, Loading
src/com/android/settings/bluetooth/BluetoothFeatureProvider.java +5 −3 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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<AbstractPreferenceController> controllers); }
src/com/android/settings/bluetooth/BluetoothFeatureProviderImpl.kt +6 −3 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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<AbstractPreferenceController>, ): DeviceDetailsFragmentFormatter { return DeviceDetailsFragmentFormatterImpl( context, fragment, controllers, bluetoothAdapter, cachedDevice, Dispatchers.IO Loading
src/com/android/settings/bluetooth/ui/view/DeviceDetailsFragmentFormatter.kt +90 −50 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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 Loading @@ -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<String>? /** Updates device details fragment layout. */ fun getInvisibleBluetoothProfiles(fragmentType: FragmentTypeModel): List<String>? /** Updates device details fragment layout. */ fun updateLayout(fragmentType: FragmentTypeModel) Loading @@ -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<AbstractPreferenceController>, private val bluetoothAdapter: BluetoothAdapter, private val cachedDevice: CachedBluetoothDevice, private val backgroundCoroutineContext: CoroutineContext, Loading @@ -112,6 +114,9 @@ class DeviceDetailsFragmentFormatterImpl( 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( Loading @@ -125,27 +130,16 @@ class DeviceDetailsFragmentFormatterImpl( ) .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) { fragment.setLoading(true, false) isLoading = true fragment.lifecycleScope.launch { updateLayoutInternal(fragmentType) } } /** 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 private suspend fun updateLayoutInternal(fragmentType: FragmentTypeModel) { val items = viewModel.getItems(fragmentType) ?: return val layout = viewModel.getLayout(fragmentType) ?: return val prefKeyToSettingId = items Loading @@ -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( Loading @@ -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( Loading Loading @@ -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 { Loading