diff --git a/src/com/android/settings/bluetooth/BluetoothDeviceDetailsFragment.java b/src/com/android/settings/bluetooth/BluetoothDeviceDetailsFragment.java index 0e51d178fe8..2860ce83fb8 100644 --- a/src/com/android/settings/bluetooth/BluetoothDeviceDetailsFragment.java +++ b/src/com/android/settings/bluetooth/BluetoothDeviceDetailsFragment.java @@ -421,11 +421,13 @@ public class BluetoothDeviceDetailsFragment extends RestrictedDashboardFragment protected List createPreferenceControllers(Context context) { List invisibleProfiles = List.of(); if (Flags.enableBluetoothDeviceDetailsPolish()) { - mFormatter = - FeatureFactory.getFeatureFactory() - .getBluetoothFeatureProvider() - .getDeviceDetailsFragmentFormatter( - requireContext(), this, mBluetoothAdapter, mCachedDevice); + if (mFormatter == null) { + mFormatter = + FeatureFactory.getFeatureFactory() + .getBluetoothFeatureProvider() + .getDeviceDetailsFragmentFormatter( + requireContext(), this, mBluetoothAdapter, mCachedDevice); + } invisibleProfiles = mFormatter.getInvisibleBluetoothProfiles( FragmentTypeModel.DeviceDetailsMainFragment.INSTANCE); diff --git a/src/com/android/settings/bluetooth/BluetoothFeatureProvider.java b/src/com/android/settings/bluetooth/BluetoothFeatureProvider.java index be0f6f36b6c..1bad5e56fa4 100644 --- a/src/com/android/settings/bluetooth/BluetoothFeatureProvider.java +++ b/src/com/android/settings/bluetooth/BluetoothFeatureProvider.java @@ -25,7 +25,6 @@ import android.media.Spatializer; import android.net.Uri; import androidx.annotation.NonNull; -import androidx.lifecycle.LifecycleCoroutineScope; import androidx.preference.Preference; import com.android.settings.SettingsPreferenceFragment; @@ -34,12 +33,12 @@ import com.android.settings.bluetooth.ui.view.DeviceDetailsFragmentFormatter; import com.android.settingslib.bluetooth.CachedBluetoothDevice; import com.android.settingslib.bluetooth.devicesettings.data.repository.DeviceSettingRepository; +import kotlinx.coroutines.CoroutineScope; + import java.util.List; import java.util.Set; -/** - * Provider for bluetooth related features. - */ +/** Provider for bluetooth related features. */ public interface BluetoothFeatureProvider { /** @@ -86,26 +85,25 @@ public interface BluetoothFeatureProvider { /** * Gets the bluetooth profile preference keys which should be hidden in the device details page. * - * @param context Context + * @param context Context * @param bluetoothDevice the bluetooth device * @return the profiles which should be hidden */ - Set getInvisibleProfilePreferenceKeys( - Context context, BluetoothDevice bluetoothDevice); + Set getInvisibleProfilePreferenceKeys(Context context, BluetoothDevice bluetoothDevice); /** Gets DeviceSettingRepository. */ @NonNull DeviceSettingRepository getDeviceSettingRepository( @NonNull Context context, @NonNull BluetoothAdapter bluetoothAdapter, - @NonNull LifecycleCoroutineScope scope); + @NonNull CoroutineScope scope); /** Gets spatial audio interactor. */ @NonNull SpatialAudioInteractor getSpatialAudioInteractor( @NonNull Context context, @NonNull AudioManager audioManager, - @NonNull LifecycleCoroutineScope scope); + @NonNull CoroutineScope scope); /** Gets device details fragment layout formatter. */ @NonNull diff --git a/src/com/android/settings/bluetooth/BluetoothFeatureProviderImpl.kt b/src/com/android/settings/bluetooth/BluetoothFeatureProviderImpl.kt index 25c586e06bc..6f967a2da6d 100644 --- a/src/com/android/settings/bluetooth/BluetoothFeatureProviderImpl.kt +++ b/src/com/android/settings/bluetooth/BluetoothFeatureProviderImpl.kt @@ -22,6 +22,7 @@ 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 @@ -37,6 +38,7 @@ 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 import kotlinx.coroutines.Dispatchers /** Impl of [BluetoothFeatureProvider] */ @@ -76,14 +78,14 @@ open class BluetoothFeatureProviderImpl : BluetoothFeatureProvider { override fun getDeviceSettingRepository( context: Context, bluetoothAdapter: BluetoothAdapter, - scope: LifecycleCoroutineScope + scope: CoroutineScope ): DeviceSettingRepository = DeviceSettingRepositoryImpl(context, bluetoothAdapter, scope, Dispatchers.IO) override fun getSpatialAudioInteractor( context: Context, audioManager: AudioManager, - scope: LifecycleCoroutineScope + scope: CoroutineScope, ): SpatialAudioInteractor { return SpatialAudioInteractorImpl( context, audioManager, diff --git a/src/com/android/settings/bluetooth/domain/interactor/SpatialAudioInteractor.kt b/src/com/android/settings/bluetooth/domain/interactor/SpatialAudioInteractor.kt index 6b72b53aa3f..4b91716a4b4 100644 --- a/src/com/android/settings/bluetooth/domain/interactor/SpatialAudioInteractor.kt +++ b/src/com/android/settings/bluetooth/domain/interactor/SpatialAudioInteractor.kt @@ -147,7 +147,7 @@ class SpatialAudioInteractorImpl( } companion object { - private const val TAG = "SpatialAudioInteractorImpl" + 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 diff --git a/src/com/android/settings/bluetooth/ui/view/DeviceDetailsFragmentFormatter.kt b/src/com/android/settings/bluetooth/ui/view/DeviceDetailsFragmentFormatter.kt index ad4176fc1a2..13c3b501025 100644 --- a/src/com/android/settings/bluetooth/ui/view/DeviceDetailsFragmentFormatter.kt +++ b/src/com/android/settings/bluetooth/ui/view/DeviceDetailsFragmentFormatter.kt @@ -19,11 +19,10 @@ package com.android.settings.bluetooth.ui.view import android.bluetooth.BluetoothAdapter import android.content.Context import android.content.Intent -import android.media.AudioManager import android.os.Bundle import androidx.compose.animation.AnimatedVisibility -import androidx.compose.animation.expandVertically -import androidx.compose.animation.shrinkVertically +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.padding @@ -33,14 +32,12 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.remember -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.compose.collectAsStateWithLifecycle -import androidx.lifecycle.lifecycleScope import androidx.preference.Preference import com.android.settings.R import com.android.settings.SettingsPreferenceFragment @@ -52,7 +49,6 @@ 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.overlay.FeatureFactory.Companion.featureFactory import com.android.settings.spa.preference.ComposePreference import com.android.settingslib.bluetooth.CachedBluetoothDevice import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingActionModel @@ -97,29 +93,17 @@ interface DeviceDetailsFragmentFormatter { class DeviceDetailsFragmentFormatterImpl( private val context: Context, private val fragment: SettingsPreferenceFragment, - bluetoothAdapter: BluetoothAdapter, + private val bluetoothAdapter: BluetoothAdapter, private val cachedDevice: CachedBluetoothDevice, private val backgroundCoroutineContext: CoroutineContext, ) : DeviceDetailsFragmentFormatter { - private val repository = - featureFactory.bluetoothFeatureProvider.getDeviceSettingRepository( - fragment.requireActivity().application, - bluetoothAdapter, - fragment.lifecycleScope, - ) - private val spatialAudioInteractor = - featureFactory.bluetoothFeatureProvider.getSpatialAudioInteractor( - fragment.requireActivity().application, - context.getSystemService(AudioManager::class.java), - fragment.lifecycleScope, - ) + private val viewModel: BluetoothDeviceDetailsViewModel = ViewModelProvider( fragment, BluetoothDeviceDetailsViewModel.Factory( fragment.requireActivity().application, - repository, - spatialAudioInteractor, + bluetoothAdapter, cachedDevice, backgroundCoroutineContext, ), @@ -224,8 +208,8 @@ class DeviceDetailsFragmentFormatterImpl( val settings = contents AnimatedVisibility( visible = settings.isNotEmpty(), - enter = expandVertically(expandFrom = Alignment.Top), - exit = shrinkVertically(shrinkTowards = Alignment.Top), + enter = fadeIn(), + exit = fadeOut(), ) { Box { Box( diff --git a/src/com/android/settings/bluetooth/ui/view/DeviceDetailsMoreSettingsFragment.kt b/src/com/android/settings/bluetooth/ui/view/DeviceDetailsMoreSettingsFragment.kt index 7cb1c0d0fba..66fba70e7c0 100644 --- a/src/com/android/settings/bluetooth/ui/view/DeviceDetailsMoreSettingsFragment.kt +++ b/src/com/android/settings/bluetooth/ui/view/DeviceDetailsMoreSettingsFragment.kt @@ -120,13 +120,15 @@ class DeviceDetailsMoreSettingsFragment : DashboardFragment() { finish() return emptyList() } - formatter = - featureFactory.bluetoothFeatureProvider.getDeviceDetailsFragmentFormatter( - requireContext(), - this, - bluetoothManager.adapter, - cachedDevice, - ) + if (!this::formatter.isInitialized) { + formatter = + featureFactory.bluetoothFeatureProvider.getDeviceDetailsFragmentFormatter( + requireContext(), + this, + bluetoothManager.adapter, + cachedDevice, + ) + } helpItem = formatter .getMenuItem(FragmentTypeModel.DeviceDetailsMoreSettingsFragment) diff --git a/src/com/android/settings/bluetooth/ui/viewmodel/BluetoothDeviceDetailsViewModel.kt b/src/com/android/settings/bluetooth/ui/viewmodel/BluetoothDeviceDetailsViewModel.kt index 3b7a5829bcc..1ea2da3d2a3 100644 --- a/src/com/android/settings/bluetooth/ui/viewmodel/BluetoothDeviceDetailsViewModel.kt +++ b/src/com/android/settings/bluetooth/ui/viewmodel/BluetoothDeviceDetailsViewModel.kt @@ -17,20 +17,22 @@ package com.android.settings.bluetooth.ui.viewmodel import android.app.Application +import android.bluetooth.BluetoothAdapter +import android.media.AudioManager +import android.util.Log import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.viewModelScope import com.android.settings.R -import com.android.settings.bluetooth.domain.interactor.SpatialAudioInteractor import com.android.settings.bluetooth.ui.layout.DeviceSettingLayout import com.android.settings.bluetooth.ui.layout.DeviceSettingLayoutColumn 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.settings.overlay.FeatureFactory.Companion.featureFactory import com.android.settingslib.bluetooth.CachedBluetoothDevice import com.android.settingslib.bluetooth.devicesettings.DeviceSettingId -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.DeviceSettingIcon import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingModel @@ -47,12 +49,24 @@ import kotlinx.coroutines.flow.stateIn class BluetoothDeviceDetailsViewModel( private val application: Application, - private val deviceSettingRepository: DeviceSettingRepository, - private val spatialAudioInteractor: SpatialAudioInteractor, + private val bluetoothAdapter: BluetoothAdapter, private val cachedDevice: CachedBluetoothDevice, backgroundCoroutineContext: CoroutineContext, ) : AndroidViewModel(application) { + private val deviceSettingRepository = + featureFactory.bluetoothFeatureProvider.getDeviceSettingRepository( + application, + bluetoothAdapter, + viewModelScope, + ) + private val spatialAudioInteractor = + featureFactory.bluetoothFeatureProvider.getSpatialAudioInteractor( + application, + application.getSystemService(AudioManager::class.java), + viewModelScope, + ) + private val items = viewModelScope.async(backgroundCoroutineContext, start = CoroutineStart.LAZY) { deviceSettingRepository.getDeviceSettingsConfig(cachedDevice) @@ -202,8 +216,7 @@ class BluetoothDeviceDetailsViewModel( class Factory( private val application: Application, - private val deviceSettingRepository: DeviceSettingRepository, - private val spatialAudioInteractor: SpatialAudioInteractor, + private val bluetoothAdapter: BluetoothAdapter, private val cachedDevice: CachedBluetoothDevice, private val backgroundCoroutineContext: CoroutineContext, ) : ViewModelProvider.Factory { @@ -211,8 +224,7 @@ class BluetoothDeviceDetailsViewModel( @Suppress("UNCHECKED_CAST") return BluetoothDeviceDetailsViewModel( application, - deviceSettingRepository, - spatialAudioInteractor, + bluetoothAdapter, cachedDevice, backgroundCoroutineContext, ) diff --git a/tests/robotests/src/com/android/settings/bluetooth/ui/viewmodel/BluetoothDeviceDetailsViewModelTest.kt b/tests/robotests/src/com/android/settings/bluetooth/ui/viewmodel/BluetoothDeviceDetailsViewModelTest.kt index c3f938c3c46..6813d943499 100644 --- a/tests/robotests/src/com/android/settings/bluetooth/ui/viewmodel/BluetoothDeviceDetailsViewModelTest.kt +++ b/tests/robotests/src/com/android/settings/bluetooth/ui/viewmodel/BluetoothDeviceDetailsViewModelTest.kt @@ -19,6 +19,7 @@ package com.android.settings.bluetooth.ui.viewmodel import android.app.Application import android.bluetooth.BluetoothAdapter import android.graphics.Bitmap +import android.media.AudioManager import androidx.test.core.app.ApplicationProvider import com.android.settings.bluetooth.domain.interactor.SpatialAudioInteractor import com.android.settings.bluetooth.ui.layout.DeviceSettingLayout @@ -46,7 +47,9 @@ import org.junit.Before import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers.eq import org.mockito.Mock +import org.mockito.Mockito.any import org.mockito.Mockito.times import org.mockito.Mockito.verify import org.mockito.Mockito.`when` @@ -76,11 +79,21 @@ class BluetoothDeviceDetailsViewModelTest { val application = ApplicationProvider.getApplicationContext() featureFactory = FakeFeatureFactory.setupForTest() + `when`( + featureFactory.bluetoothFeatureProvider.getDeviceSettingRepository( + eq(application), eq(bluetoothAdapter), any() + )) + .thenReturn(repository) + `when`( + featureFactory.bluetoothFeatureProvider.getSpatialAudioInteractor( + eq(application), any(AudioManager::class.java), any() + )) + .thenReturn(spatialAudioInteractor) + underTest = BluetoothDeviceDetailsViewModel( application, - repository, - spatialAudioInteractor, + bluetoothAdapter, cachedDevice, testScope.testScheduler) }