Merge "Add device details more settings page" into main

This commit is contained in:
Haijie Hong
2024-08-20 09:43:30 +00:00
committed by Android (Google) Code Review
12 changed files with 428 additions and 118 deletions

View File

@@ -1864,6 +1864,10 @@
<string name="device_details_title">Device details</string> <string name="device_details_title">Device details</string>
<!-- Title for keyboard settings preferences. [CHAR LIMIT=50] --> <!-- Title for keyboard settings preferences. [CHAR LIMIT=50] -->
<string name="bluetooth_device_keyboard_settings_preference_title">Keyboard settings</string> <string name="bluetooth_device_keyboard_settings_preference_title">Keyboard settings</string>
<!-- Title for more settings preferences. [CHAR LIMIT=50] -->
<string name="bluetooth_device_more_settings_preference_title">More settings</string>
<!-- Title for more settings summary. [CHAR LIMIT=50] -->
<string name="bluetooth_device_more_settings_preference_summary">Firmware updates, about, and more</string>
<!-- Title of the item to show device MAC address --> <!-- Title of the item to show device MAC address -->
<string name="bluetooth_device_mac_address">Device\'s Bluetooth address: <xliff:g id="address">%1$s</xliff:g></string> <string name="bluetooth_device_mac_address">Device\'s Bluetooth address: <xliff:g id="address">%1$s</xliff:g></string>
<!-- Title of the items to show multuple devices MAC address [CHAR LIMIT=NONE]--> <!-- Title of the items to show multuple devices MAC address [CHAR LIMIT=NONE]-->
@@ -1884,6 +1888,9 @@
<!-- Bluetooth device details companion apps. In the confirmation dialog for removing an associated app, this is the label on the button that will complete the disassociate action. [CHAR LIMIT=80] --> <!-- Bluetooth device details companion apps. In the confirmation dialog for removing an associated app, this is the label on the button that will complete the disassociate action. [CHAR LIMIT=80] -->
<string name = "bluetooth_companion_app_remove_association_confirm_button">Disconnect app</string> <string name = "bluetooth_companion_app_remove_association_confirm_button">Disconnect app</string>
<!-- Title of device details screen [CHAR LIMIT=28]-->
<string name="device_details_more_settings">More settings</string>
<!-- Bluetooth developer settings: Maximum number of connected audio devices --> <!-- Bluetooth developer settings: Maximum number of connected audio devices -->
<string name="bluetooth_max_connected_audio_devices_string">Maximum connected Bluetooth audio devices</string> <string name="bluetooth_max_connected_audio_devices_string">Maximum connected Bluetooth audio devices</string>
<!-- Bluetooth developer settings: Maximum number of connected audio devices --> <!-- Bluetooth developer settings: Maximum number of connected audio devices -->

View File

@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2024 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android"
android:key="bluetooth_device_more_settings_screen"
android:title="@string/device_details_more_settings">
<PreferenceCategory
android:key="bluetooth_profiles"/>
</PreferenceScreen>

View File

@@ -48,6 +48,7 @@ import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting; import androidx.annotation.VisibleForTesting;
import com.android.settings.R; 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.bluetooth.ui.view.DeviceDetailsFragmentFormatter;
import com.android.settings.connecteddevice.stylus.StylusDevicesController; import com.android.settings.connecteddevice.stylus.StylusDevicesController;
import com.android.settings.core.SettingsUIDeviceConfig; import com.android.settings.core.SettingsUIDeviceConfig;
@@ -343,7 +344,7 @@ public class BluetoothDeviceDetailsFragment extends RestrictedDashboardFragment
public void onCreatePreferences(@NonNull Bundle savedInstanceState, @NonNull String rootKey) { public void onCreatePreferences(@NonNull Bundle savedInstanceState, @NonNull String rootKey) {
super.onCreatePreferences(savedInstanceState, rootKey); super.onCreatePreferences(savedInstanceState, rootKey);
if (Flags.enableBluetoothDeviceDetailsPolish()) { if (Flags.enableBluetoothDeviceDetailsPolish()) {
mFormatter.updateLayout(); mFormatter.updateLayout(FragmentTypeModel.DeviceDetailsMainFragment.INSTANCE);
} }
} }
@@ -400,7 +401,9 @@ public class BluetoothDeviceDetailsFragment extends RestrictedDashboardFragment
@Override @Override
protected void addPreferenceController(AbstractPreferenceController controller) { protected void addPreferenceController(AbstractPreferenceController controller) {
if (Flags.enableBluetoothDeviceDetailsPolish()) { if (Flags.enableBluetoothDeviceDetailsPolish()) {
List<String> keys = mFormatter.getVisiblePreferenceKeysForMainPage(); List<String> keys =
mFormatter.getVisiblePreferenceKeys(
FragmentTypeModel.DeviceDetailsMainFragment.INSTANCE);
Lifecycle lifecycle = getSettingsLifecycle(); Lifecycle lifecycle = getSettingsLifecycle();
if (keys == null || keys.contains(controller.getPreferenceKey())) { if (keys == null || keys.contains(controller.getPreferenceKey())) {
super.addPreferenceController(controller); super.addPreferenceController(controller);

View File

@@ -66,15 +66,14 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import androidx.compose.ui.window.DialogProperties import androidx.compose.ui.window.DialogProperties
import com.android.settings.R import com.android.settings.R
import com.android.settings.bluetooth.ui.model.DeviceSettingPreferenceModel
import com.android.settings.bluetooth.ui.composable.Icon as DeviceSettingComposeIcon import com.android.settings.bluetooth.ui.composable.Icon as DeviceSettingComposeIcon
import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingModel
import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingStateModel
import com.android.settingslib.spa.framework.theme.SettingsDimension import com.android.settingslib.spa.framework.theme.SettingsDimension
import com.android.settingslib.spa.widget.dialog.getDialogWidth import com.android.settingslib.spa.widget.dialog.getDialogWidth
@Composable @Composable
fun MultiTogglePreferenceGroup( fun MultiTogglePreferenceGroup(
preferenceModels: List<DeviceSettingModel.MultiTogglePreference>, preferenceModels: List<DeviceSettingPreferenceModel.MultiTogglePreference>,
) { ) {
var settingIdForPopUp by remember { mutableStateOf<Int?>(null) } var settingIdForPopUp by remember { mutableStateOf<Int?>(null) }
@@ -115,7 +114,7 @@ fun MultiTogglePreferenceGroup(
colors = getButtonColors(preferenceModel.isActive), colors = getButtonColors(preferenceModel.isActive),
contentPadding = PaddingValues(0.dp)) { contentPadding = PaddingValues(0.dp)) {
DeviceSettingComposeIcon( DeviceSettingComposeIcon(
preferenceModel.toggles[preferenceModel.state.selectedIndex] preferenceModel.toggles[preferenceModel.selectedIndex]
.icon, .icon,
modifier = Modifier.size(24.dp)) modifier = Modifier.size(24.dp))
} }
@@ -144,7 +143,7 @@ private fun getButtonColors(isActive: Boolean) =
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
private fun dialog( private fun dialog(
multiTogglePreference: DeviceSettingModel.MultiTogglePreference, multiTogglePreference: DeviceSettingPreferenceModel.MultiTogglePreference,
onDismiss: () -> Unit onDismiss: () -> Unit
) { ) {
BasicAlertDialog( BasicAlertDialog(
@@ -179,7 +178,7 @@ private fun dialog(
} }
@Composable @Composable
private fun dialogContent(multiTogglePreference: DeviceSettingModel.MultiTogglePreference) { private fun dialogContent(multiTogglePreference: DeviceSettingPreferenceModel.MultiTogglePreference) {
Column { Column {
Row( Row(
modifier = Modifier.fillMaxWidth().height(24.dp), modifier = Modifier.fillMaxWidth().height(24.dp),
@@ -219,7 +218,7 @@ private fun dialogContent(multiTogglePreference: DeviceSettingModel.MultiToggleP
} }
Row { Row {
for ((idx, toggle) in multiTogglePreference.toggles.withIndex()) { for ((idx, toggle) in multiTogglePreference.toggles.withIndex()) {
val selected = idx == multiTogglePreference.state.selectedIndex val selected = idx == multiTogglePreference.selectedIndex
Column( Column(
modifier = modifier =
Modifier.weight(1f) Modifier.weight(1f)
@@ -237,8 +236,7 @@ private fun dialogContent(multiTogglePreference: DeviceSettingModel.MultiToggleP
) { ) {
Button( Button(
onClick = { onClick = {
multiTogglePreference.updateState( multiTogglePreference.onSelectedChange(idx)
DeviceSettingStateModel.MultiTogglePreferenceState(idx))
}, },
modifier = Modifier.fillMaxSize(), modifier = Modifier.fillMaxSize(),
colors = colors =

View File

@@ -0,0 +1,69 @@
/*
* Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.bluetooth.ui.model
import com.android.settingslib.bluetooth.devicesettings.DeviceSettingId
import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingIcon
import com.android.settingslib.bluetooth.devicesettings.shared.model.ToggleModel
/** Models a device setting preference. */
sealed interface DeviceSettingPreferenceModel {
@DeviceSettingId
val id: Int
/** Models a plain preference. */
data class PlainPreference(
@DeviceSettingId override val id: Int,
val title: String,
val summary: String? = null,
val icon: DeviceSettingIcon? = null,
val onClick: (() -> Unit)? = null,
) : DeviceSettingPreferenceModel
/** Models a switch preference. */
data class SwitchPreference(
@DeviceSettingId override val id: Int,
val title: String,
val summary: String? = null,
val icon: DeviceSettingIcon? = null,
val checked: Boolean,
val onCheckedChange: ((Boolean) -> Unit),
val onPrimaryClick: (() -> Unit)? = null,
) : DeviceSettingPreferenceModel
/** Models a multi-toggle preference. */
data class MultiTogglePreference(
@DeviceSettingId override val id: Int,
val title: String,
val toggles: List<ToggleModel>,
val isActive: Boolean,
val selectedIndex: Int,
val isAllowedChangingState: Boolean,
val onSelectedChange: (Int) -> Unit,
) : DeviceSettingPreferenceModel
/** Models a footer preference. */
data class FooterPreference(
@DeviceSettingId override val id: Int,
val footerText: String,
) : DeviceSettingPreferenceModel
/** Models a preference which could navigate to more settings fragment. */
data class MoreSettingsPreference(
@DeviceSettingId override val id: Int,
) : DeviceSettingPreferenceModel
}

View File

@@ -0,0 +1,25 @@
/*
* Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.bluetooth.ui.model
/** Models a device details fragment type. */
sealed interface FragmentTypeModel {
/** Device details main page. */
data object DeviceDetailsMainFragment : FragmentTypeModel
/** Device details more settings page. */
data object DeviceDetailsMoreSettingsFragment : FragmentTypeModel
}

View File

@@ -19,47 +19,52 @@ package com.android.settings.bluetooth.ui.view
import android.bluetooth.BluetoothAdapter import android.bluetooth.BluetoothAdapter
import android.content.Context import android.content.Context
import android.media.AudioManager import android.media.AudioManager
import android.util.Log import android.os.Bundle
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.preference.Preference import androidx.preference.Preference
import com.android.settings.R
import com.android.settings.SettingsPreferenceFragment import com.android.settings.SettingsPreferenceFragment
import com.android.settings.bluetooth.ui.composable.Icon import com.android.settings.bluetooth.ui.composable.Icon
import com.android.settings.bluetooth.ui.composable.MultiTogglePreferenceGroup import com.android.settings.bluetooth.ui.composable.MultiTogglePreferenceGroup
import com.android.settings.bluetooth.ui.layout.DeviceSettingLayout import com.android.settings.bluetooth.ui.layout.DeviceSettingLayout
import com.android.settings.bluetooth.ui.model.DeviceSettingPreferenceModel
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.bluetooth.ui.viewmodel.BluetoothDeviceDetailsViewModel
import com.android.settings.core.SubSettingLauncher
import com.android.settings.overlay.FeatureFactory.Companion.featureFactory import com.android.settings.overlay.FeatureFactory.Companion.featureFactory
import com.android.settings.spa.preference.ComposePreference import com.android.settings.spa.preference.ComposePreference
import com.android.settingslib.bluetooth.CachedBluetoothDevice import com.android.settingslib.bluetooth.CachedBluetoothDevice
import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingConfigItemModel import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingConfigItemModel
import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingModel import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingIcon
import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingStateModel
import com.android.settingslib.spa.framework.theme.SettingsDimension 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 import com.android.settingslib.spa.widget.preference.PreferenceModel
import com.android.settingslib.spa.widget.preference.SwitchPreference import com.android.settingslib.spa.widget.preference.SwitchPreference
import com.android.settingslib.spa.widget.preference.SwitchPreferenceModel import com.android.settingslib.spa.widget.preference.SwitchPreferenceModel
import com.android.settingslib.spa.widget.preference.TwoTargetSwitchPreference import com.android.settingslib.spa.widget.preference.TwoTargetSwitchPreference
import com.android.settingslib.spa.widget.ui.Footer
import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import com.android.settingslib.spa.widget.preference.Preference as SpaPreference
/** Handles device details fragment layout according to config. */ /** Handles device details fragment layout according to config. */
interface DeviceDetailsFragmentFormatter { interface DeviceDetailsFragmentFormatter {
/** Gets keys of visible preferences in built-in preference in xml. */ /** Gets keys of visible preferences in built-in preference in xml. */
fun getVisiblePreferenceKeysForMainPage(): List<String>? fun getVisiblePreferenceKeys(fragmentType: FragmentTypeModel): List<String>?
/** Updates device details fragment layout. */ /** Updates device details fragment layout. */
fun updateLayout() fun updateLayout(fragmentType: FragmentTypeModel)
} }
@OptIn(ExperimentalCoroutinesApi::class) @OptIn(ExperimentalCoroutinesApi::class)
@@ -79,23 +84,25 @@ class DeviceDetailsFragmentFormatterImpl(
ViewModelProvider( ViewModelProvider(
fragment, fragment,
BluetoothDeviceDetailsViewModel.Factory( BluetoothDeviceDetailsViewModel.Factory(
fragment.requireActivity().application,
repository, repository,
spatialAudioInteractor, spatialAudioInteractor,
cachedDevice, cachedDevice,
)) ))
.get(BluetoothDeviceDetailsViewModel::class.java) .get(BluetoothDeviceDetailsViewModel::class.java)
override fun getVisiblePreferenceKeysForMainPage(): List<String>? = runBlocking { override fun getVisiblePreferenceKeys(fragmentType: FragmentTypeModel): List<String>? =
viewModel runBlocking {
.getItems() viewModel
?.filterIsInstance<DeviceSettingConfigItemModel.BuiltinItem>() .getItems(fragmentType)
?.mapNotNull { it.preferenceKey } ?.filterIsInstance<DeviceSettingConfigItemModel.BuiltinItem>()
} ?.mapNotNull { it.preferenceKey }
}
/** Updates bluetooth device details fragment layout. */ /** Updates bluetooth device details fragment layout. */
override fun updateLayout() = runBlocking { override fun updateLayout(fragmentType: FragmentTypeModel) = runBlocking {
val items = viewModel.getItems() ?: return@runBlocking val items = viewModel.getItems(fragmentType) ?: return@runBlocking
val layout = viewModel.getLayout() ?: return@runBlocking val layout = viewModel.getLayout(fragmentType) ?: return@runBlocking
val prefKeyToSettingId = val prefKeyToSettingId =
items items
.filterIsInstance<DeviceSettingConfigItemModel.BuiltinItem>() .filterIsInstance<DeviceSettingConfigItemModel.BuiltinItem>()
@@ -124,6 +131,8 @@ class DeviceDetailsFragmentFormatterImpl(
fragment.preferenceScreen.addPreference(pref) fragment.preferenceScreen.addPreference(pref)
} }
} }
// TODO(b/343317785): figure out how to remove the foot preference.
fragment.preferenceScreen.addPreference(Preference(context).apply { order = 10000 })
} }
@Composable @Composable
@@ -132,7 +141,7 @@ class DeviceDetailsFragmentFormatterImpl(
remember(row) { remember(row) {
layout.rows[row].settingIds.flatMapLatest { settingIds -> layout.rows[row].settingIds.flatMapLatest { settingIds ->
if (settingIds.isEmpty()) { if (settingIds.isEmpty()) {
flowOf(emptyList<DeviceSettingModel>()) flowOf(emptyList<DeviceSettingPreferenceModel>())
} else { } else {
combine( combine(
settingIds.map { settingId -> settingIds.map { settingId ->
@@ -150,72 +159,104 @@ class DeviceDetailsFragmentFormatterImpl(
0 -> {} 0 -> {}
1 -> { 1 -> {
when (val setting = settings[0]) { when (val setting = settings[0]) {
is DeviceSettingModel.ActionSwitchPreference -> { is DeviceSettingPreferenceModel.PlainPreference -> {
buildActionSwitchPreference(setting) buildPlainPreference(setting)
} }
is DeviceSettingModel.MultiTogglePreference -> { is DeviceSettingPreferenceModel.SwitchPreference -> {
buildSwitchPreference(setting)
}
is DeviceSettingPreferenceModel.MultiTogglePreference -> {
buildMultiTogglePreference(listOf(setting)) buildMultiTogglePreference(listOf(setting))
} }
null -> {} is DeviceSettingPreferenceModel.FooterPreference -> {
else -> { buildFooterPreference(setting)
Log.w(TAG, "Unknown preference type ${setting.id}, skip.")
} }
is DeviceSettingPreferenceModel.MoreSettingsPreference -> {
buildMoreSettingsPreference()
}
null -> {}
} }
} }
else -> { else -> {
if (!settings.all { it is DeviceSettingModel.MultiTogglePreference }) { if (!settings.all { it is DeviceSettingPreferenceModel.MultiTogglePreference }) {
return return
} }
buildMultiTogglePreference( buildMultiTogglePreference(
settings.filterIsInstance<DeviceSettingModel.MultiTogglePreference>()) settings.filterIsInstance<DeviceSettingPreferenceModel.MultiTogglePreference>())
} }
} }
} }
@Composable @Composable
private fun buildMultiTogglePreference(prefs: List<DeviceSettingModel.MultiTogglePreference>) { private fun buildMultiTogglePreference(
prefs: List<DeviceSettingPreferenceModel.MultiTogglePreference>
) {
MultiTogglePreferenceGroup(prefs) MultiTogglePreferenceGroup(prefs)
} }
@Composable @Composable
private fun buildActionSwitchPreference(model: DeviceSettingModel.ActionSwitchPreference) { private fun buildSwitchPreference(model: DeviceSettingPreferenceModel.SwitchPreference) {
if (model.switchState != null) { val switchPrefModel =
val switchPrefModel = object : SwitchPreferenceModel {
object : SwitchPreferenceModel { override val title = model.title
override val title = model.title override val summary = { model.summary ?: "" }
override val summary = { model.summary ?: "" } override val checked = { model.checked }
override val checked = { model.switchState?.checked } override val onCheckedChange = { newChecked: Boolean ->
override val onCheckedChange = { newChecked: Boolean -> model.onCheckedChange(newChecked)
model.updateState?.invoke(
DeviceSettingStateModel.ActionSwitchPreferenceState(newChecked))
Unit
}
override val icon = @Composable { deviceSettingIcon(model) }
} }
if (model.intent != null) { override val icon = @Composable { deviceSettingIcon(model.icon) }
TwoTargetSwitchPreference(switchPrefModel) { context.startActivity(model.intent) }
} else {
SwitchPreference(switchPrefModel)
} }
if (model.onPrimaryClick != null) {
TwoTargetSwitchPreference(
switchPrefModel, primaryOnClick = model.onPrimaryClick::invoke)
} else { } else {
SpaPreference( SwitchPreference(switchPrefModel)
object : PreferenceModel {
override val title = model.title
override val summary = { model.summary ?: "" }
override val onClick = {
model.intent?.let { context.startActivity(it) }
Unit
}
override val icon = @Composable { deviceSettingIcon(model) }
})
} }
} }
@Composable @Composable
private fun deviceSettingIcon(model: DeviceSettingModel.ActionSwitchPreference) { private fun buildPlainPreference(model: DeviceSettingPreferenceModel.PlainPreference) {
model.icon?.let { icon -> SpaPreference(
Icon(icon, modifier = Modifier.size(SettingsDimension.itemIconSize)) object : PreferenceModel {
} override val title = model.title
override val summary = { model.summary ?: "" }
override val onClick = {
model.onClick?.invoke()
Unit
}
override val icon = @Composable { deviceSettingIcon(model.icon) }
})
}
@Composable
fun buildMoreSettingsPreference() {
SpaPreference(
object : PreferenceModel {
override val title =
stringResource(R.string.bluetooth_device_more_settings_preference_title)
override val summary = {
context.getString(R.string.bluetooth_device_more_settings_preference_summary)
}
override val onClick = {
SubSettingLauncher(context)
.setDestination(DeviceDetailsMoreSettingsFragment::class.java.name)
.setSourceMetricsCategory(fragment.getMetricsCategory())
.setArguments(
Bundle().apply { putString(KEY_DEVICE_ADDRESS, cachedDevice.address) })
.launch()
}
override val icon = @Composable { deviceSettingIcon(null) }
})
}
@Composable
fun buildFooterPreference(model: DeviceSettingPreferenceModel.FooterPreference) {
Footer(footerText = model.footerText)
}
@Composable
private fun deviceSettingIcon(icon: DeviceSettingIcon?) {
icon?.let { Icon(it, modifier = Modifier.size(SettingsDimension.itemIconSize)) }
} }
private fun getPreferenceKey(settingId: Int) = "DEVICE_SETTING_${settingId}" private fun getPreferenceKey(settingId: Int) = "DEVICE_SETTING_${settingId}"

View File

@@ -0,0 +1,92 @@
/*
* Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.bluetooth.ui.view
import android.bluetooth.BluetoothDevice
import android.bluetooth.BluetoothManager
import android.content.Context
import android.os.Bundle
import com.android.settings.R
import com.android.settings.bluetooth.BluetoothDetailsProfilesController
import com.android.settings.bluetooth.Utils
import com.android.settings.bluetooth.ui.model.FragmentTypeModel
import com.android.settings.dashboard.DashboardFragment
import com.android.settings.overlay.FeatureFactory.Companion.featureFactory
import com.android.settingslib.bluetooth.CachedBluetoothDevice
import com.android.settingslib.bluetooth.LocalBluetoothManager
import com.android.settingslib.core.AbstractPreferenceController
import com.android.settingslib.core.lifecycle.LifecycleObserver
class DeviceDetailsMoreSettingsFragment : DashboardFragment() {
private lateinit var formatter: DeviceDetailsFragmentFormatter
private lateinit var localBluetoothManager: LocalBluetoothManager
private lateinit var cachedDevice: CachedBluetoothDevice
// TODO(b/343317785): add metrics category
override fun getMetricsCategory(): Int = 0
override fun getPreferenceScreenResId(): Int {
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))
}
}
private fun getCachedDevice(): CachedBluetoothDevice? {
val bluetoothAddress = arguments?.getString(KEY_DEVICE_ADDRESS) ?: return null
localBluetoothManager = Utils.getLocalBtManager(context) ?: return null
val remoteDevice: BluetoothDevice =
localBluetoothManager.bluetoothAdapter.getRemoteDevice(bluetoothAddress) ?: return null
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()
}
formatter =
featureFactory.bluetoothFeatureProvider.getDeviceDetailsFragmentFormatter(
requireContext(), this, bluetoothManager.adapter, cachedDevice)
return listOf(
BluetoothDetailsProfilesController(
context, this, localBluetoothManager, cachedDevice, settingsLifecycle))
}
override fun getLogTag(): String = TAG
companion object {
const val TAG: String = "DeviceMoreSettingsFrg"
const val KEY_DEVICE_ADDRESS: String = "device_address"
}
}

View File

@@ -16,17 +16,22 @@
package com.android.settings.bluetooth.ui.viewmodel package com.android.settings.bluetooth.ui.viewmodel
import android.app.Application
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.android.settings.bluetooth.domain.interactor.SpatialAudioInteractor import com.android.settings.bluetooth.domain.interactor.SpatialAudioInteractor
import com.android.settings.bluetooth.ui.layout.DeviceSettingLayout import com.android.settings.bluetooth.ui.layout.DeviceSettingLayout
import com.android.settings.bluetooth.ui.layout.DeviceSettingLayoutRow import com.android.settings.bluetooth.ui.layout.DeviceSettingLayoutRow
import com.android.settings.bluetooth.ui.model.DeviceSettingPreferenceModel
import com.android.settings.bluetooth.ui.model.FragmentTypeModel
import com.android.settingslib.bluetooth.CachedBluetoothDevice import com.android.settingslib.bluetooth.CachedBluetoothDevice
import com.android.settingslib.bluetooth.devicesettings.DeviceSettingId import com.android.settingslib.bluetooth.devicesettings.DeviceSettingId
import com.android.settingslib.bluetooth.devicesettings.data.repository.DeviceSettingRepository import com.android.settingslib.bluetooth.devicesettings.data.repository.DeviceSettingRepository
import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingConfigItemModel import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingConfigItemModel
import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingModel import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingModel
import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingStateModel
import kotlinx.coroutines.CoroutineStart import kotlinx.coroutines.CoroutineStart
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async import kotlinx.coroutines.async
@@ -38,30 +43,81 @@ import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.stateIn
class BluetoothDeviceDetailsViewModel( class BluetoothDeviceDetailsViewModel(
private val application: Application,
private val deviceSettingRepository: DeviceSettingRepository, private val deviceSettingRepository: DeviceSettingRepository,
private val spatialAudioInteractor: SpatialAudioInteractor, private val spatialAudioInteractor: SpatialAudioInteractor,
private val cachedDevice: CachedBluetoothDevice, private val cachedDevice: CachedBluetoothDevice,
) : ViewModel() { ) : AndroidViewModel(application){
private val items = private val items =
viewModelScope.async(Dispatchers.IO, start = CoroutineStart.LAZY) { viewModelScope.async(Dispatchers.IO, start = CoroutineStart.LAZY) {
deviceSettingRepository.getDeviceSettingsConfig(cachedDevice) deviceSettingRepository.getDeviceSettingsConfig(cachedDevice)
} }
suspend fun getItems(): List<DeviceSettingConfigItemModel>? = items.await()?.mainItems suspend fun getItems(fragment: FragmentTypeModel): List<DeviceSettingConfigItemModel>? =
when (fragment) {
is FragmentTypeModel.DeviceDetailsMainFragment -> items.await()?.mainItems
is FragmentTypeModel.DeviceDetailsMoreSettingsFragment ->
items.await()?.moreSettingsItems
}
fun getDeviceSetting( fun getDeviceSetting(
cachedDevice: CachedBluetoothDevice, cachedDevice: CachedBluetoothDevice,
@DeviceSettingId settingId: Int @DeviceSettingId settingId: Int
): Flow<DeviceSettingModel?> { ): Flow<DeviceSettingPreferenceModel?> {
if (settingId == DeviceSettingId.DEVICE_SETTING_ID_MORE_SETTINGS) {
return flowOf(DeviceSettingPreferenceModel.MoreSettingsPreference(settingId))
}
return when (settingId) { return when (settingId) {
DeviceSettingId.DEVICE_SETTING_ID_SPATIAL_AUDIO_MULTI_TOGGLE -> DeviceSettingId.DEVICE_SETTING_ID_SPATIAL_AUDIO_MULTI_TOGGLE ->
spatialAudioInteractor.getDeviceSetting(cachedDevice) spatialAudioInteractor.getDeviceSetting(cachedDevice)
else -> deviceSettingRepository.getDeviceSetting(cachedDevice, settingId) else -> deviceSettingRepository.getDeviceSetting(cachedDevice, settingId)
}.map { it?.toPreferenceModel() }
}
private fun DeviceSettingModel.toPreferenceModel(): DeviceSettingPreferenceModel? {
return when (this) {
is DeviceSettingModel.ActionSwitchPreference -> {
if (switchState != null) {
DeviceSettingPreferenceModel.SwitchPreference(
id = id,
title = title,
summary = summary,
icon = icon,
checked = switchState?.checked ?: false,
onCheckedChange = { newState ->
updateState?.invoke(
DeviceSettingStateModel.ActionSwitchPreferenceState(newState))
},
onPrimaryClick = { intent?.let { application.startActivity(it) } })
} else {
DeviceSettingPreferenceModel.PlainPreference(
id = id,
title = title,
summary = summary,
icon = icon,
onClick = { intent?.let { application.startActivity(it) } })
}
}
is DeviceSettingModel.FooterPreference ->
DeviceSettingPreferenceModel.FooterPreference(id = id, footerText = footerText)
is DeviceSettingModel.MultiTogglePreference ->
DeviceSettingPreferenceModel.MultiTogglePreference(
id = id,
title = title,
toggles = toggles,
isActive = isActive,
selectedIndex = state.selectedIndex,
isAllowedChangingState = isAllowedChangingState,
onSelectedChange = { newState ->
updateState(DeviceSettingStateModel.MultiTogglePreferenceState(newState))
})
is DeviceSettingModel.Unknown -> null
} }
} }
suspend fun getLayout(): DeviceSettingLayout? { suspend fun getLayout(fragment: FragmentTypeModel): DeviceSettingLayout? {
val configItems = getItems() ?: return null val configItems = getItems(fragment) ?: return null
val idToDeviceSetting = val idToDeviceSetting =
configItems configItems
.filterIsInstance<DeviceSettingConfigItemModel.AppProvidedItem>() .filterIsInstance<DeviceSettingConfigItemModel.AppProvidedItem>()
@@ -80,7 +136,7 @@ class BluetoothDeviceDetailsViewModel(
if (!isXmlPreference && setting == null) { if (!isXmlPreference && setting == null) {
continue continue
} }
if (setting !is DeviceSettingModel.MultiTogglePreference) { if (setting !is DeviceSettingPreferenceModel.MultiTogglePreference) {
multiToggleSettingIds = null multiToggleSettingIds = null
positionMapping[i] = listOf(configItem.settingId) positionMapping[i] = listOf(configItem.settingId)
continue continue
@@ -103,6 +159,7 @@ class BluetoothDeviceDetailsViewModel(
} }
class Factory( class Factory(
private val application: Application,
private val deviceSettingRepository: DeviceSettingRepository, private val deviceSettingRepository: DeviceSettingRepository,
private val spatialAudioInteractor: SpatialAudioInteractor, private val spatialAudioInteractor: SpatialAudioInteractor,
private val cachedDevice: CachedBluetoothDevice, private val cachedDevice: CachedBluetoothDevice,
@@ -110,7 +167,7 @@ class BluetoothDeviceDetailsViewModel(
override fun <T : ViewModel> create(modelClass: Class<T>): T { override fun <T : ViewModel> create(modelClass: Class<T>): T {
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
return BluetoothDeviceDetailsViewModel( return BluetoothDeviceDetailsViewModel(
deviceSettingRepository, spatialAudioInteractor, cachedDevice) application, deviceSettingRepository, spatialAudioInteractor, cachedDevice)
as T as T
} }
} }

View File

@@ -50,6 +50,7 @@ import androidx.fragment.app.FragmentTransaction;
import androidx.preference.PreferenceScreen; import androidx.preference.PreferenceScreen;
import com.android.settings.R; 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.bluetooth.ui.view.DeviceDetailsFragmentFormatter;
import com.android.settings.testutils.FakeFeatureFactory; import com.android.settings.testutils.FakeFeatureFactory;
import com.android.settingslib.bluetooth.CachedBluetoothDevice; import com.android.settingslib.bluetooth.CachedBluetoothDevice;
@@ -117,7 +118,9 @@ public class BluetoothDeviceDetailsFragmentTest {
FakeFeatureFactory fakeFeatureFactory = FakeFeatureFactory.setupForTest(); FakeFeatureFactory fakeFeatureFactory = FakeFeatureFactory.setupForTest();
when(fakeFeatureFactory.mBluetoothFeatureProvider.getDeviceDetailsFragmentFormatter(any(), when(fakeFeatureFactory.mBluetoothFeatureProvider.getDeviceDetailsFragmentFormatter(any(),
any(), any(), eq(mCachedDevice))).thenReturn(mFormatter); any(), any(), eq(mCachedDevice))).thenReturn(mFormatter);
when(mFormatter.getVisiblePreferenceKeysForMainPage()).thenReturn(null); when(mFormatter.getVisiblePreferenceKeys(
FragmentTypeModel.DeviceDetailsMainFragment.INSTANCE))
.thenReturn(null);
mFragment = setupFragment(); mFragment = setupFragment();
mFragment.onAttach(mContext); mFragment.onAttach(mContext);

View File

@@ -26,6 +26,7 @@ import androidx.preference.PreferenceManager
import androidx.preference.PreferenceScreen import androidx.preference.PreferenceScreen
import androidx.test.core.app.ApplicationProvider import androidx.test.core.app.ApplicationProvider
import com.android.settings.bluetooth.domain.interactor.SpatialAudioInteractor import com.android.settings.bluetooth.domain.interactor.SpatialAudioInteractor
import com.android.settings.bluetooth.ui.model.FragmentTypeModel
import com.android.settings.dashboard.DashboardFragment import com.android.settings.dashboard.DashboardFragment
import com.android.settings.testutils.FakeFeatureFactory import com.android.settings.testutils.FakeFeatureFactory
import com.android.settingslib.bluetooth.CachedBluetoothDevice import com.android.settingslib.bluetooth.CachedBluetoothDevice
@@ -45,7 +46,6 @@ import org.junit.Before
import org.junit.Rule import org.junit.Rule
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.any
import org.mockito.ArgumentMatchers.eq import org.mockito.ArgumentMatchers.eq
import org.mockito.Mock import org.mockito.Mock
import org.mockito.Mockito.any import org.mockito.Mockito.any
@@ -111,10 +111,9 @@ class DeviceDetailsFragmentFormatterTest {
DeviceSettingConfigItemModel.BuiltinItem( DeviceSettingConfigItemModel.BuiltinItem(
DeviceSettingId.DEVICE_SETTING_ID_ACTION_BUTTONS, "action_buttons"), DeviceSettingId.DEVICE_SETTING_ID_ACTION_BUTTONS, "action_buttons"),
), ),
listOf(), listOf()))
"footer"))
val keys = underTest.getVisiblePreferenceKeysForMainPage() val keys = underTest.getVisiblePreferenceKeys(FragmentTypeModel.DeviceDetailsMainFragment)
assertThat(keys).containsExactly("bluetooth_device_header", "action_buttons") assertThat(keys).containsExactly("bluetooth_device_header", "action_buttons")
} }
@@ -125,7 +124,7 @@ class DeviceDetailsFragmentFormatterTest {
testScope.runTest { testScope.runTest {
`when`(repository.getDeviceSettingsConfig(cachedDevice)).thenReturn(null) `when`(repository.getDeviceSettingsConfig(cachedDevice)).thenReturn(null)
val keys = underTest.getVisiblePreferenceKeysForMainPage() val keys = underTest.getVisiblePreferenceKeys(FragmentTypeModel.DeviceDetailsMainFragment)
assertThat(keys).isNull() assertThat(keys).isNull()
} }
@@ -136,9 +135,9 @@ class DeviceDetailsFragmentFormatterTest {
testScope.runTest { testScope.runTest {
`when`(repository.getDeviceSettingsConfig(cachedDevice)).thenReturn(null) `when`(repository.getDeviceSettingsConfig(cachedDevice)).thenReturn(null)
underTest.updateLayout() underTest.updateLayout(FragmentTypeModel.DeviceDetailsMainFragment)
assertThat(getDisplayedPreferences().map { it.key }) assertThat(getDisplayedPreferences().mapNotNull { it.key })
.containsExactly("bluetooth_device_header", "action_buttons", "keyboard_settings") .containsExactly("bluetooth_device_header", "action_buttons", "keyboard_settings")
} }
} }
@@ -157,12 +156,11 @@ class DeviceDetailsFragmentFormatterTest {
DeviceSettingId.DEVICE_SETTING_ID_KEYBOARD_SETTINGS, DeviceSettingId.DEVICE_SETTING_ID_KEYBOARD_SETTINGS,
"keyboard_settings"), "keyboard_settings"),
), ),
listOf(), listOf()))
"footer"))
underTest.updateLayout() underTest.updateLayout(FragmentTypeModel.DeviceDetailsMainFragment)
assertThat(getDisplayedPreferences().map { it.key }) assertThat(getDisplayedPreferences().mapNotNull { it.key })
.containsExactly("bluetooth_device_header", "keyboard_settings") .containsExactly("bluetooth_device_header", "keyboard_settings")
} }
} }
@@ -183,8 +181,7 @@ class DeviceDetailsFragmentFormatterTest {
DeviceSettingId.DEVICE_SETTING_ID_KEYBOARD_SETTINGS, DeviceSettingId.DEVICE_SETTING_ID_KEYBOARD_SETTINGS,
"keyboard_settings"), "keyboard_settings"),
), ),
listOf(), listOf()))
"footer"))
`when`(repository.getDeviceSetting(cachedDevice, DeviceSettingId.DEVICE_SETTING_ID_ANC)) `when`(repository.getDeviceSetting(cachedDevice, DeviceSettingId.DEVICE_SETTING_ID_ANC))
.thenReturn( .thenReturn(
flowOf( flowOf(
@@ -209,9 +206,9 @@ class DeviceDetailsFragmentFormatterTest {
isAllowedChangingState = true, isAllowedChangingState = true,
updateState = {}))) updateState = {})))
underTest.updateLayout() underTest.updateLayout(FragmentTypeModel.DeviceDetailsMainFragment)
assertThat(getDisplayedPreferences().map { it.key }) assertThat(getDisplayedPreferences().mapNotNull { it.key })
.containsExactly( .containsExactly(
"bluetooth_device_header", "bluetooth_device_header",
"DEVICE_SETTING_${DeviceSettingId.DEVICE_SETTING_ID_ANC}", "DEVICE_SETTING_${DeviceSettingId.DEVICE_SETTING_ID_ANC}",

View File

@@ -16,12 +16,14 @@
package com.android.settings.bluetooth.ui.viewmodel package com.android.settings.bluetooth.ui.viewmodel
import android.app.Application
import android.bluetooth.BluetoothAdapter import android.bluetooth.BluetoothAdapter
import android.content.Context
import android.graphics.Bitmap import android.graphics.Bitmap
import androidx.test.core.app.ApplicationProvider import androidx.test.core.app.ApplicationProvider
import com.android.settings.bluetooth.domain.interactor.SpatialAudioInteractor import com.android.settings.bluetooth.domain.interactor.SpatialAudioInteractor
import com.android.settings.bluetooth.ui.layout.DeviceSettingLayout import com.android.settings.bluetooth.ui.layout.DeviceSettingLayout
import com.android.settings.bluetooth.ui.model.DeviceSettingPreferenceModel
import com.android.settings.bluetooth.ui.model.FragmentTypeModel
import com.android.settings.testutils.FakeFeatureFactory import com.android.settings.testutils.FakeFeatureFactory
import com.android.settingslib.bluetooth.CachedBluetoothDevice import com.android.settingslib.bluetooth.CachedBluetoothDevice
import com.android.settingslib.bluetooth.devicesettings.DeviceSettingId import com.android.settingslib.bluetooth.devicesettings.DeviceSettingId
@@ -44,8 +46,6 @@ import org.junit.Before
import org.junit.Rule import org.junit.Rule
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.any
import org.mockito.ArgumentMatchers.eq
import org.mockito.Mock import org.mockito.Mock
import org.mockito.Mockito.times import org.mockito.Mockito.times
import org.mockito.Mockito.verify import org.mockito.Mockito.verify
@@ -73,26 +73,23 @@ class BluetoothDeviceDetailsViewModelTest {
@Before @Before
fun setUp() { fun setUp() {
val context = ApplicationProvider.getApplicationContext<Context>() val application = ApplicationProvider.getApplicationContext<Application>()
featureFactory = FakeFeatureFactory.setupForTest() featureFactory = FakeFeatureFactory.setupForTest()
`when`(
featureFactory.bluetoothFeatureProvider.getDeviceSettingRepository(
eq(context), eq(bluetoothAdapter), any()))
.thenReturn(repository)
underTest = underTest =
BluetoothDeviceDetailsViewModel(repository, spatialAudioInteractor, cachedDevice) BluetoothDeviceDetailsViewModel(
application, repository, spatialAudioInteractor, cachedDevice)
} }
@Test @Test
fun getItems_returnConfigMainItems() { fun getItems_returnConfigMainMainItems() {
testScope.runTest { testScope.runTest {
`when`(repository.getDeviceSettingsConfig(cachedDevice)) `when`(repository.getDeviceSettingsConfig(cachedDevice))
.thenReturn( .thenReturn(
DeviceSettingConfigModel( DeviceSettingConfigModel(
listOf(BUILTIN_SETTING_ITEM_1, BUILDIN_SETTING_ITEM_2), listOf(), "footer")) listOf(BUILTIN_SETTING_ITEM_1, BUILDIN_SETTING_ITEM_2), listOf()))
val keys = underTest.getItems() val keys = underTest.getItems(FragmentTypeModel.DeviceDetailsMainFragment)
assertThat(keys).containsExactly(BUILTIN_SETTING_ITEM_1, BUILDIN_SETTING_ITEM_2) assertThat(keys).containsExactly(BUILTIN_SETTING_ITEM_1, BUILDIN_SETTING_ITEM_2)
} }
@@ -110,19 +107,18 @@ class BluetoothDeviceDetailsViewModelTest {
BUILTIN_SETTING_ITEM_1, BUILTIN_SETTING_ITEM_1,
buildRemoteSettingItem(remoteSettingId1), buildRemoteSettingItem(remoteSettingId1),
), ),
listOf(), listOf()))
"footer"))
`when`(repository.getDeviceSetting(cachedDevice, remoteSettingId1)) `when`(repository.getDeviceSetting(cachedDevice, remoteSettingId1))
.thenReturn(flowOf(pref)) .thenReturn(flowOf(pref))
var deviceSetting: DeviceSettingModel? = null var deviceSettingPreference: DeviceSettingPreferenceModel? = null
underTest underTest
.getDeviceSetting(cachedDevice, remoteSettingId1) .getDeviceSetting(cachedDevice, remoteSettingId1)
.onEach { deviceSetting = it } .onEach { deviceSettingPreference = it }
.launchIn(testScope.backgroundScope) .launchIn(testScope.backgroundScope)
runCurrent() runCurrent()
assertThat(deviceSetting).isSameInstanceAs(pref) assertThat(deviceSettingPreference?.id).isEqualTo(pref.id)
verify(repository, times(1)).getDeviceSetting(cachedDevice, remoteSettingId1) verify(repository, times(1)).getDeviceSetting(cachedDevice, remoteSettingId1)
} }
} }
@@ -141,19 +137,18 @@ class BluetoothDeviceDetailsViewModelTest {
buildRemoteSettingItem( buildRemoteSettingItem(
DeviceSettingId.DEVICE_SETTING_ID_SPATIAL_AUDIO_MULTI_TOGGLE), DeviceSettingId.DEVICE_SETTING_ID_SPATIAL_AUDIO_MULTI_TOGGLE),
), ),
listOf(), listOf()))
"footer"))
`when`(spatialAudioInteractor.getDeviceSetting(cachedDevice)).thenReturn(flowOf(pref)) `when`(spatialAudioInteractor.getDeviceSetting(cachedDevice)).thenReturn(flowOf(pref))
var deviceSetting: DeviceSettingModel? = null var deviceSettingPreference: DeviceSettingPreferenceModel? = null
underTest underTest
.getDeviceSetting( .getDeviceSetting(
cachedDevice, DeviceSettingId.DEVICE_SETTING_ID_SPATIAL_AUDIO_MULTI_TOGGLE) cachedDevice, DeviceSettingId.DEVICE_SETTING_ID_SPATIAL_AUDIO_MULTI_TOGGLE)
.onEach { deviceSetting = it } .onEach { deviceSettingPreference = it }
.launchIn(testScope.backgroundScope) .launchIn(testScope.backgroundScope)
runCurrent() runCurrent()
assertThat(deviceSetting).isSameInstanceAs(pref) assertThat(deviceSettingPreference?.id).isEqualTo(pref.id)
verify(spatialAudioInteractor, times(1)).getDeviceSetting(cachedDevice) verify(spatialAudioInteractor, times(1)).getDeviceSetting(cachedDevice)
} }
} }
@@ -164,9 +159,9 @@ class BluetoothDeviceDetailsViewModelTest {
`when`(repository.getDeviceSettingsConfig(cachedDevice)) `when`(repository.getDeviceSettingsConfig(cachedDevice))
.thenReturn( .thenReturn(
DeviceSettingConfigModel( DeviceSettingConfigModel(
listOf(BUILTIN_SETTING_ITEM_1, BUILDIN_SETTING_ITEM_2), listOf(), "footer")) listOf(BUILTIN_SETTING_ITEM_1, BUILDIN_SETTING_ITEM_2), listOf()))
val layout = underTest.getLayout()!! val layout = underTest.getLayout(FragmentTypeModel.DeviceDetailsMainFragment)!!
assertThat(getLatestLayout(layout)) assertThat(getLatestLayout(layout))
.isEqualTo( .isEqualTo(
@@ -191,8 +186,7 @@ class BluetoothDeviceDetailsViewModelTest {
buildRemoteSettingItem(remoteSettingId2), buildRemoteSettingItem(remoteSettingId2),
buildRemoteSettingItem(remoteSettingId3), buildRemoteSettingItem(remoteSettingId3),
), ),
listOf(), listOf()))
"footer"))
`when`(repository.getDeviceSetting(cachedDevice, remoteSettingId1)) `when`(repository.getDeviceSetting(cachedDevice, remoteSettingId1))
.thenReturn(flowOf(buildMultiTogglePreference(remoteSettingId1))) .thenReturn(flowOf(buildMultiTogglePreference(remoteSettingId1)))
`when`(repository.getDeviceSetting(cachedDevice, remoteSettingId2)) `when`(repository.getDeviceSetting(cachedDevice, remoteSettingId2))
@@ -200,7 +194,7 @@ class BluetoothDeviceDetailsViewModelTest {
`when`(repository.getDeviceSetting(cachedDevice, remoteSettingId3)) `when`(repository.getDeviceSetting(cachedDevice, remoteSettingId3))
.thenReturn(flowOf(buildActionSwitchPreference(remoteSettingId3))) .thenReturn(flowOf(buildActionSwitchPreference(remoteSettingId3)))
val layout = underTest.getLayout()!! val layout = underTest.getLayout(FragmentTypeModel.DeviceDetailsMainFragment)!!
assertThat(getLatestLayout(layout)) assertThat(getLatestLayout(layout))
.isEqualTo( .isEqualTo(