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
This commit is contained in:
@@ -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<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;
|
||||
}
|
||||
|
||||
/** 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());
|
||||
if (mHasExtraSpace) {
|
||||
mProfilesContainer.setLayoutResource(R.layout.preference_bluetooth_profile_category);
|
||||
} else {
|
||||
mProfilesContainer.setLayoutResource(R.layout.preference_category_bluetooth_no_padding);
|
||||
}
|
||||
// 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();
|
||||
|
@@ -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 -> {
|
||||
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<AbstractPreferenceController> 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<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 @@ 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,
|
||||
|
@@ -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<AbstractPreferenceController> controllers);
|
||||
}
|
||||
|
@@ -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<AbstractPreferenceController>,
|
||||
): DeviceDetailsFragmentFormatter {
|
||||
return DeviceDetailsFragmentFormatterImpl(
|
||||
context,
|
||||
fragment,
|
||||
controllers,
|
||||
bluetoothAdapter,
|
||||
cachedDevice,
|
||||
Dispatchers.IO
|
||||
|
@@ -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<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 @@ 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,
|
||||
@@ -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(
|
||||
@@ -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) = 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(
|
||||
@@ -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 {
|
||||
|
@@ -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<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 @@ 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<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 @@ class DeviceDetailsMoreSettingsFragment : DashboardFragment() {
|
||||
localBluetoothManager,
|
||||
cachedDevice,
|
||||
settingsLifecycle,
|
||||
formatter.getInvisibleBluetoothProfiles(
|
||||
FragmentTypeModel.DeviceDetailsMoreSettingsFragment
|
||||
),
|
||||
false,
|
||||
),
|
||||
BluetoothDetailsAudioDeviceTypeController(
|
||||
context,
|
||||
|
@@ -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<String> invisibleProfiles) {
|
||||
mController = new BluetoothDetailsProfilesController(mContext, mFragment, mLocalManager,
|
||||
mCachedDevice, mLifecycle, invisibleProfiles, true);
|
||||
mProfiles.setKey(mController.getPreferenceKey());
|
||||
mController.mProfilesContainer = mProfiles;
|
||||
mScreen.removeAll();
|
||||
mScreen.addPreference(mProfiles);
|
||||
}
|
||||
}
|
||||
|
@@ -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);
|
||||
|
@@ -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,
|
||||
|
Reference in New Issue
Block a user