Fix coroutine scope expired and UI animation issue

BUG: 375365790
BUG: 375146578
BUG: 375304695
BUG: 375544752
Test: atest BluetoothDeviceDetailsViewModelTest
Flag: com.android.settings.flags.enable_bluetooth_device_details_polish
Change-Id: Ib3bc6699f256288b6c4995b78cc25a16f1af0792
This commit is contained in:
Haijie Hong
2024-10-27 23:30:04 +08:00
parent ab9f9780ab
commit 7764a3e5af
8 changed files with 70 additions and 57 deletions

View File

@@ -421,11 +421,13 @@ public class BluetoothDeviceDetailsFragment extends RestrictedDashboardFragment
protected List<AbstractPreferenceController> createPreferenceControllers(Context context) {
List<String> invisibleProfiles = List.of();
if (Flags.enableBluetoothDeviceDetailsPolish()) {
if (mFormatter == null) {
mFormatter =
FeatureFactory.getFeatureFactory()
.getBluetoothFeatureProvider()
.getDeviceDetailsFragmentFormatter(
requireContext(), this, mBluetoothAdapter, mCachedDevice);
}
invisibleProfiles =
mFormatter.getInvisibleBluetoothProfiles(
FragmentTypeModel.DeviceDetailsMainFragment.INSTANCE);

View File

@@ -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 {
/**
@@ -90,22 +89,21 @@ public interface BluetoothFeatureProvider {
* @param bluetoothDevice the bluetooth device
* @return the profiles which should be hidden
*/
Set<String> getInvisibleProfilePreferenceKeys(
Context context, BluetoothDevice bluetoothDevice);
Set<String> 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

View File

@@ -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,

View File

@@ -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

View File

@@ -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(

View File

@@ -120,6 +120,7 @@ class DeviceDetailsMoreSettingsFragment : DashboardFragment() {
finish()
return emptyList()
}
if (!this::formatter.isInitialized) {
formatter =
featureFactory.bluetoothFeatureProvider.getDeviceDetailsFragmentFormatter(
requireContext(),
@@ -127,6 +128,7 @@ class DeviceDetailsMoreSettingsFragment : DashboardFragment() {
bluetoothManager.adapter,
cachedDevice,
)
}
helpItem =
formatter
.getMenuItem(FragmentTypeModel.DeviceDetailsMoreSettingsFragment)

View File

@@ -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,
)

View File

@@ -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<Application>()
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)
}