Loading src/com/android/settings/bluetooth/BluetoothFeatureProvider.java +0 −9 Original line number Diff line number Diff line Loading @@ -20,7 +20,6 @@ import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.content.ComponentName; import android.content.Context; import android.media.AudioManager; import android.media.Spatializer; import android.net.Uri; Loading @@ -28,7 +27,6 @@ import androidx.annotation.NonNull; import androidx.preference.Preference; import com.android.settings.SettingsPreferenceFragment; import com.android.settings.bluetooth.domain.interactor.SpatialAudioInteractor; import com.android.settings.bluetooth.ui.view.DeviceDetailsFragmentFormatter; import com.android.settingslib.bluetooth.CachedBluetoothDevice; import com.android.settingslib.bluetooth.devicesettings.data.repository.DeviceSettingRepository; Loading Loading @@ -98,13 +96,6 @@ public interface BluetoothFeatureProvider { @NonNull BluetoothAdapter bluetoothAdapter, @NonNull CoroutineScope scope); /** Gets spatial audio interactor. */ @NonNull SpatialAudioInteractor getSpatialAudioInteractor( @NonNull Context context, @NonNull AudioManager audioManager, @NonNull CoroutineScope scope); /** Gets device details fragment layout formatter. */ @NonNull DeviceDetailsFragmentFormatter getDeviceDetailsFragmentFormatter( Loading src/com/android/settings/bluetooth/BluetoothFeatureProviderImpl.kt +0 −21 Original line number Diff line number Diff line Loading @@ -22,20 +22,14 @@ import android.content.Context import android.media.AudioManager import android.media.Spatializer import android.net.Uri import android.util.Log import androidx.lifecycle.LifecycleCoroutineScope import androidx.preference.Preference import com.android.settings.SettingsPreferenceFragment import com.android.settings.bluetooth.domain.interactor.SpatialAudioInteractor import com.android.settings.bluetooth.domain.interactor.SpatialAudioInteractorImpl import com.android.settings.bluetooth.ui.view.DeviceDetailsFragmentFormatter import com.android.settings.bluetooth.ui.view.DeviceDetailsFragmentFormatterImpl 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.media.data.repository.SpatializerRepositoryImpl import com.android.settingslib.media.domain.interactor.SpatializerInteractor import com.google.common.collect.ImmutableList import com.google.common.collect.ImmutableSet import kotlinx.coroutines.CoroutineScope Loading Loading @@ -82,21 +76,6 @@ open class BluetoothFeatureProviderImpl : BluetoothFeatureProvider { ): DeviceSettingRepository = DeviceSettingRepositoryImpl(context, bluetoothAdapter, scope, Dispatchers.IO) override fun getSpatialAudioInteractor( context: Context, audioManager: AudioManager, scope: CoroutineScope, ): SpatialAudioInteractor { return SpatialAudioInteractorImpl( context, audioManager, SpatializerInteractor( SpatializerRepositoryImpl( getSpatializer(context), Dispatchers.IO ) ), scope, Dispatchers.IO) } override fun getDeviceDetailsFragmentFormatter( context: Context, fragment: SettingsPreferenceFragment, Loading src/com/android/settings/bluetooth/domain/interactor/SpatialAudioInteractor.ktdeleted 100644 → 0 +0 −180 Original line number Diff line number Diff line /* * 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.domain.interactor import android.content.Context import android.media.AudioManager import android.util.Log import com.android.settings.R import com.android.settingslib.bluetooth.BluetoothUtils import com.android.settingslib.bluetooth.CachedBluetoothDevice import com.android.settingslib.bluetooth.devicesettings.DeviceSettingId import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingIcon 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.media.domain.interactor.SpatializerInteractor import kotlin.coroutines.CoroutineContext import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.callbackFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch /** Provides device setting for spatial audio. */ interface SpatialAudioInteractor { /** Gets device setting for spatial audio */ fun getDeviceSetting(cachedDevice: CachedBluetoothDevice): Flow<DeviceSettingModel?> } class SpatialAudioInteractorImpl( private val context: Context, private val audioManager: AudioManager, private val spatializerInteractor: SpatializerInteractor, private val coroutineScope: CoroutineScope, private val backgroundCoroutineContext: CoroutineContext, ) : SpatialAudioInteractor { private val spatialAudioOffToggle = ToggleModel( context.getString(R.string.spatial_audio_multi_toggle_off), DeviceSettingIcon.ResourceIcon(R.drawable.ic_spatial_audio_off), ) private val spatialAudioOnToggle = ToggleModel( context.getString(R.string.spatial_audio_multi_toggle_on), DeviceSettingIcon.ResourceIcon(R.drawable.ic_spatial_audio), ) private val headTrackingOnToggle = ToggleModel( context.getString(R.string.spatial_audio_multi_toggle_head_tracking_on), DeviceSettingIcon.ResourceIcon(R.drawable.ic_head_tracking), ) private val changes = MutableSharedFlow<Unit>() override fun getDeviceSetting(cachedDevice: CachedBluetoothDevice): Flow<DeviceSettingModel?> = changes .onStart { emit(Unit) } .combine( isDeviceConnected(cachedDevice), ) { _, connected -> if (connected) { getSpatialAudioDeviceSettingModel(cachedDevice) } else { null } } .flowOn(backgroundCoroutineContext) .stateIn(coroutineScope, SharingStarted.WhileSubscribed(), initialValue = null) private fun isDeviceConnected(cachedDevice: CachedBluetoothDevice): Flow<Boolean> = callbackFlow { val listener = CachedBluetoothDevice.Callback { launch { send(cachedDevice.isConnected) } } cachedDevice.registerCallback(context.mainExecutor, listener) awaitClose { cachedDevice.unregisterCallback(listener) } } .onStart { emit(cachedDevice.isConnected) } .flowOn(backgroundCoroutineContext) private suspend fun getSpatialAudioDeviceSettingModel( cachedDevice: CachedBluetoothDevice ): DeviceSettingModel? { // TODO(b/343317785): use audio repository instead of calling AudioManager directly. Log.i(TAG, "CachedDevice: $cachedDevice profiles: ${cachedDevice.profiles}") val attributes = BluetoothUtils.getAudioDeviceAttributesForSpatialAudio( cachedDevice, audioManager.getBluetoothAudioDeviceCategory(cachedDevice.address), ) ?: run { Log.i(TAG, "No audio profiles in cachedDevice: ${cachedDevice.address}.") return null } Log.i(TAG, "Audio device attributes for ${cachedDevice.address}: $attributes.") val spatialAudioAvailable = spatializerInteractor.isSpatialAudioAvailable(attributes) if (!spatialAudioAvailable) { Log.i(TAG, "Spatial audio is not available for ${cachedDevice.address}") return null } val headTrackingAvailable = spatialAudioAvailable && spatializerInteractor.isHeadTrackingAvailable(attributes) val toggles = if (headTrackingAvailable) { listOf(spatialAudioOffToggle, spatialAudioOnToggle, headTrackingOnToggle) } else { listOf(spatialAudioOffToggle, spatialAudioOnToggle) } val spatialAudioEnabled = spatializerInteractor.isSpatialAudioEnabled(attributes) val headTrackingEnabled = spatialAudioEnabled && spatializerInteractor.isHeadTrackingEnabled(attributes) val activeIndex = when { headTrackingEnabled -> INDEX_HEAD_TRACKING_ENABLED spatialAudioEnabled -> INDEX_SPATIAL_AUDIO_ON else -> INDEX_SPATIAL_AUDIO_OFF } Log.i( TAG, "Head tracking available: $headTrackingAvailable, " + "spatial audio enabled: $spatialAudioEnabled, " + "head tracking enabled: $headTrackingEnabled", ) return DeviceSettingModel.MultiTogglePreference( cachedDevice = cachedDevice, id = DeviceSettingId.DEVICE_SETTING_ID_SPATIAL_AUDIO_MULTI_TOGGLE, title = context.getString(R.string.spatial_audio_multi_toggle_title), toggles = toggles, isActive = spatialAudioEnabled, state = DeviceSettingStateModel.MultiTogglePreferenceState(activeIndex), isAllowedChangingState = true, updateState = { newState -> coroutineScope.launch(backgroundCoroutineContext) { Log.i(TAG, "Update spatial audio state: $newState") when (newState.selectedIndex) { INDEX_SPATIAL_AUDIO_OFF -> { spatializerInteractor.setSpatialAudioEnabled(attributes, false) } INDEX_SPATIAL_AUDIO_ON -> { spatializerInteractor.setSpatialAudioEnabled(attributes, true) spatializerInteractor.setHeadTrackingEnabled(attributes, false) } INDEX_HEAD_TRACKING_ENABLED -> { spatializerInteractor.setSpatialAudioEnabled(attributes, true) spatializerInteractor.setHeadTrackingEnabled(attributes, true) } } changes.emit(Unit) } }, ) } companion object { private const val TAG = "SpatialAudioInteractor" private const val INDEX_SPATIAL_AUDIO_OFF = 0 private const val INDEX_SPATIAL_AUDIO_ON = 1 private const val INDEX_HEAD_TRACKING_ENABLED = 2 } } src/com/android/settings/bluetooth/ui/composable/MultiTogglePreference.kt 0 → 100644 +124 −0 Original line number Diff line number Diff line /* * 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.composable import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.defaultMinSize import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.Button import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.android.settings.bluetooth.ui.composable.Icon as DeviceSettingComposeIcon import com.android.settings.bluetooth.ui.model.DeviceSettingPreferenceModel @Composable fun MultiTogglePreference(pref: DeviceSettingPreferenceModel.MultiTogglePreference) { Column(modifier = Modifier.padding(24.dp)) { Row( modifier = Modifier.fillMaxWidth().height(56.dp), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.SpaceEvenly, ) { Box { Row { for ((idx, toggle) in pref.toggles.withIndex()) { val selected = idx == pref.selectedIndex Column( modifier = Modifier.weight(1f) .padding(start = if (idx == 0) 0.dp else 1.dp) .height(56.dp) .background( Color.Transparent, shape = RoundedCornerShape(12.dp), ), verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally, ) { val startCornerRadius = if (idx == 0) 12.dp else 0.dp val endCornerRadius = if (idx == pref.toggles.size - 1) 12.dp else 0.dp Button( onClick = { pref.onSelectedChange(idx) }, modifier = Modifier.fillMaxSize(), enabled = pref.isAllowedChangingState, colors = getButtonColors(selected), shape = RoundedCornerShape( startCornerRadius, endCornerRadius, endCornerRadius, startCornerRadius, ) ) { DeviceSettingComposeIcon( toggle.icon, modifier = Modifier.size(24.dp), ) } } } } } } Spacer(modifier = Modifier.height(12.dp)) Row( modifier = Modifier.fillMaxWidth().defaultMinSize(32.dp), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.SpaceEvenly, ) { for (toggle in pref.toggles) { Text( text = toggle.label, fontSize = 12.sp, textAlign = TextAlign.Center, overflow = TextOverflow.Visible, modifier = Modifier.weight(1f).padding(horizontal = 8.dp), ) } } } } @Composable private fun getButtonColors(isActive: Boolean) = if (isActive) { ButtonDefaults.buttonColors( containerColor = MaterialTheme.colorScheme.primary, contentColor = MaterialTheme.colorScheme.onPrimary, ) } else { ButtonDefaults.buttonColors( containerColor = MaterialTheme.colorScheme.surfaceVariant, contentColor = MaterialTheme.colorScheme.onPrimaryContainer, ) } src/com/android/settings/bluetooth/ui/composable/MultiTogglePreferenceGroup.ktdeleted 100644 → 0 +0 −280 File deleted.Preview size limit exceeded, changes collapsed. Show changes Loading
src/com/android/settings/bluetooth/BluetoothFeatureProvider.java +0 −9 Original line number Diff line number Diff line Loading @@ -20,7 +20,6 @@ import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.content.ComponentName; import android.content.Context; import android.media.AudioManager; import android.media.Spatializer; import android.net.Uri; Loading @@ -28,7 +27,6 @@ import androidx.annotation.NonNull; import androidx.preference.Preference; import com.android.settings.SettingsPreferenceFragment; import com.android.settings.bluetooth.domain.interactor.SpatialAudioInteractor; import com.android.settings.bluetooth.ui.view.DeviceDetailsFragmentFormatter; import com.android.settingslib.bluetooth.CachedBluetoothDevice; import com.android.settingslib.bluetooth.devicesettings.data.repository.DeviceSettingRepository; Loading Loading @@ -98,13 +96,6 @@ public interface BluetoothFeatureProvider { @NonNull BluetoothAdapter bluetoothAdapter, @NonNull CoroutineScope scope); /** Gets spatial audio interactor. */ @NonNull SpatialAudioInteractor getSpatialAudioInteractor( @NonNull Context context, @NonNull AudioManager audioManager, @NonNull CoroutineScope scope); /** Gets device details fragment layout formatter. */ @NonNull DeviceDetailsFragmentFormatter getDeviceDetailsFragmentFormatter( Loading
src/com/android/settings/bluetooth/BluetoothFeatureProviderImpl.kt +0 −21 Original line number Diff line number Diff line Loading @@ -22,20 +22,14 @@ import android.content.Context import android.media.AudioManager import android.media.Spatializer import android.net.Uri import android.util.Log import androidx.lifecycle.LifecycleCoroutineScope import androidx.preference.Preference import com.android.settings.SettingsPreferenceFragment import com.android.settings.bluetooth.domain.interactor.SpatialAudioInteractor import com.android.settings.bluetooth.domain.interactor.SpatialAudioInteractorImpl import com.android.settings.bluetooth.ui.view.DeviceDetailsFragmentFormatter import com.android.settings.bluetooth.ui.view.DeviceDetailsFragmentFormatterImpl 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.media.data.repository.SpatializerRepositoryImpl import com.android.settingslib.media.domain.interactor.SpatializerInteractor import com.google.common.collect.ImmutableList import com.google.common.collect.ImmutableSet import kotlinx.coroutines.CoroutineScope Loading Loading @@ -82,21 +76,6 @@ open class BluetoothFeatureProviderImpl : BluetoothFeatureProvider { ): DeviceSettingRepository = DeviceSettingRepositoryImpl(context, bluetoothAdapter, scope, Dispatchers.IO) override fun getSpatialAudioInteractor( context: Context, audioManager: AudioManager, scope: CoroutineScope, ): SpatialAudioInteractor { return SpatialAudioInteractorImpl( context, audioManager, SpatializerInteractor( SpatializerRepositoryImpl( getSpatializer(context), Dispatchers.IO ) ), scope, Dispatchers.IO) } override fun getDeviceDetailsFragmentFormatter( context: Context, fragment: SettingsPreferenceFragment, Loading
src/com/android/settings/bluetooth/domain/interactor/SpatialAudioInteractor.ktdeleted 100644 → 0 +0 −180 Original line number Diff line number Diff line /* * 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.domain.interactor import android.content.Context import android.media.AudioManager import android.util.Log import com.android.settings.R import com.android.settingslib.bluetooth.BluetoothUtils import com.android.settingslib.bluetooth.CachedBluetoothDevice import com.android.settingslib.bluetooth.devicesettings.DeviceSettingId import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingIcon 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.media.domain.interactor.SpatializerInteractor import kotlin.coroutines.CoroutineContext import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.callbackFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch /** Provides device setting for spatial audio. */ interface SpatialAudioInteractor { /** Gets device setting for spatial audio */ fun getDeviceSetting(cachedDevice: CachedBluetoothDevice): Flow<DeviceSettingModel?> } class SpatialAudioInteractorImpl( private val context: Context, private val audioManager: AudioManager, private val spatializerInteractor: SpatializerInteractor, private val coroutineScope: CoroutineScope, private val backgroundCoroutineContext: CoroutineContext, ) : SpatialAudioInteractor { private val spatialAudioOffToggle = ToggleModel( context.getString(R.string.spatial_audio_multi_toggle_off), DeviceSettingIcon.ResourceIcon(R.drawable.ic_spatial_audio_off), ) private val spatialAudioOnToggle = ToggleModel( context.getString(R.string.spatial_audio_multi_toggle_on), DeviceSettingIcon.ResourceIcon(R.drawable.ic_spatial_audio), ) private val headTrackingOnToggle = ToggleModel( context.getString(R.string.spatial_audio_multi_toggle_head_tracking_on), DeviceSettingIcon.ResourceIcon(R.drawable.ic_head_tracking), ) private val changes = MutableSharedFlow<Unit>() override fun getDeviceSetting(cachedDevice: CachedBluetoothDevice): Flow<DeviceSettingModel?> = changes .onStart { emit(Unit) } .combine( isDeviceConnected(cachedDevice), ) { _, connected -> if (connected) { getSpatialAudioDeviceSettingModel(cachedDevice) } else { null } } .flowOn(backgroundCoroutineContext) .stateIn(coroutineScope, SharingStarted.WhileSubscribed(), initialValue = null) private fun isDeviceConnected(cachedDevice: CachedBluetoothDevice): Flow<Boolean> = callbackFlow { val listener = CachedBluetoothDevice.Callback { launch { send(cachedDevice.isConnected) } } cachedDevice.registerCallback(context.mainExecutor, listener) awaitClose { cachedDevice.unregisterCallback(listener) } } .onStart { emit(cachedDevice.isConnected) } .flowOn(backgroundCoroutineContext) private suspend fun getSpatialAudioDeviceSettingModel( cachedDevice: CachedBluetoothDevice ): DeviceSettingModel? { // TODO(b/343317785): use audio repository instead of calling AudioManager directly. Log.i(TAG, "CachedDevice: $cachedDevice profiles: ${cachedDevice.profiles}") val attributes = BluetoothUtils.getAudioDeviceAttributesForSpatialAudio( cachedDevice, audioManager.getBluetoothAudioDeviceCategory(cachedDevice.address), ) ?: run { Log.i(TAG, "No audio profiles in cachedDevice: ${cachedDevice.address}.") return null } Log.i(TAG, "Audio device attributes for ${cachedDevice.address}: $attributes.") val spatialAudioAvailable = spatializerInteractor.isSpatialAudioAvailable(attributes) if (!spatialAudioAvailable) { Log.i(TAG, "Spatial audio is not available for ${cachedDevice.address}") return null } val headTrackingAvailable = spatialAudioAvailable && spatializerInteractor.isHeadTrackingAvailable(attributes) val toggles = if (headTrackingAvailable) { listOf(spatialAudioOffToggle, spatialAudioOnToggle, headTrackingOnToggle) } else { listOf(spatialAudioOffToggle, spatialAudioOnToggle) } val spatialAudioEnabled = spatializerInteractor.isSpatialAudioEnabled(attributes) val headTrackingEnabled = spatialAudioEnabled && spatializerInteractor.isHeadTrackingEnabled(attributes) val activeIndex = when { headTrackingEnabled -> INDEX_HEAD_TRACKING_ENABLED spatialAudioEnabled -> INDEX_SPATIAL_AUDIO_ON else -> INDEX_SPATIAL_AUDIO_OFF } Log.i( TAG, "Head tracking available: $headTrackingAvailable, " + "spatial audio enabled: $spatialAudioEnabled, " + "head tracking enabled: $headTrackingEnabled", ) return DeviceSettingModel.MultiTogglePreference( cachedDevice = cachedDevice, id = DeviceSettingId.DEVICE_SETTING_ID_SPATIAL_AUDIO_MULTI_TOGGLE, title = context.getString(R.string.spatial_audio_multi_toggle_title), toggles = toggles, isActive = spatialAudioEnabled, state = DeviceSettingStateModel.MultiTogglePreferenceState(activeIndex), isAllowedChangingState = true, updateState = { newState -> coroutineScope.launch(backgroundCoroutineContext) { Log.i(TAG, "Update spatial audio state: $newState") when (newState.selectedIndex) { INDEX_SPATIAL_AUDIO_OFF -> { spatializerInteractor.setSpatialAudioEnabled(attributes, false) } INDEX_SPATIAL_AUDIO_ON -> { spatializerInteractor.setSpatialAudioEnabled(attributes, true) spatializerInteractor.setHeadTrackingEnabled(attributes, false) } INDEX_HEAD_TRACKING_ENABLED -> { spatializerInteractor.setSpatialAudioEnabled(attributes, true) spatializerInteractor.setHeadTrackingEnabled(attributes, true) } } changes.emit(Unit) } }, ) } companion object { private const val TAG = "SpatialAudioInteractor" private const val INDEX_SPATIAL_AUDIO_OFF = 0 private const val INDEX_SPATIAL_AUDIO_ON = 1 private const val INDEX_HEAD_TRACKING_ENABLED = 2 } }
src/com/android/settings/bluetooth/ui/composable/MultiTogglePreference.kt 0 → 100644 +124 −0 Original line number Diff line number Diff line /* * 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.composable import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.defaultMinSize import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.Button import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.android.settings.bluetooth.ui.composable.Icon as DeviceSettingComposeIcon import com.android.settings.bluetooth.ui.model.DeviceSettingPreferenceModel @Composable fun MultiTogglePreference(pref: DeviceSettingPreferenceModel.MultiTogglePreference) { Column(modifier = Modifier.padding(24.dp)) { Row( modifier = Modifier.fillMaxWidth().height(56.dp), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.SpaceEvenly, ) { Box { Row { for ((idx, toggle) in pref.toggles.withIndex()) { val selected = idx == pref.selectedIndex Column( modifier = Modifier.weight(1f) .padding(start = if (idx == 0) 0.dp else 1.dp) .height(56.dp) .background( Color.Transparent, shape = RoundedCornerShape(12.dp), ), verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally, ) { val startCornerRadius = if (idx == 0) 12.dp else 0.dp val endCornerRadius = if (idx == pref.toggles.size - 1) 12.dp else 0.dp Button( onClick = { pref.onSelectedChange(idx) }, modifier = Modifier.fillMaxSize(), enabled = pref.isAllowedChangingState, colors = getButtonColors(selected), shape = RoundedCornerShape( startCornerRadius, endCornerRadius, endCornerRadius, startCornerRadius, ) ) { DeviceSettingComposeIcon( toggle.icon, modifier = Modifier.size(24.dp), ) } } } } } } Spacer(modifier = Modifier.height(12.dp)) Row( modifier = Modifier.fillMaxWidth().defaultMinSize(32.dp), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.SpaceEvenly, ) { for (toggle in pref.toggles) { Text( text = toggle.label, fontSize = 12.sp, textAlign = TextAlign.Center, overflow = TextOverflow.Visible, modifier = Modifier.weight(1f).padding(horizontal = 8.dp), ) } } } } @Composable private fun getButtonColors(isActive: Boolean) = if (isActive) { ButtonDefaults.buttonColors( containerColor = MaterialTheme.colorScheme.primary, contentColor = MaterialTheme.colorScheme.onPrimary, ) } else { ButtonDefaults.buttonColors( containerColor = MaterialTheme.colorScheme.surfaceVariant, contentColor = MaterialTheme.colorScheme.onPrimaryContainer, ) }
src/com/android/settings/bluetooth/ui/composable/MultiTogglePreferenceGroup.ktdeleted 100644 → 0 +0 −280 File deleted.Preview size limit exceeded, changes collapsed. Show changes