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:
@@ -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);
|
||||
|
@@ -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
|
||||
|
@@ -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,
|
||||
|
@@ -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
|
||||
|
@@ -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(
|
||||
|
@@ -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)
|
||||
|
@@ -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,
|
||||
)
|
||||
|
@@ -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)
|
||||
}
|
||||
|
Reference in New Issue
Block a user