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/src/com/android/settings/network/SimOnboardingService.kt b/src/com/android/settings/network/SimOnboardingService.kt index e4f17e25802..0882b817360 100644 --- a/src/com/android/settings/network/SimOnboardingService.kt +++ b/src/com/android/settings/network/SimOnboardingService.kt @@ -74,8 +74,11 @@ class SimOnboardingService { } var isEsimProfileEnabled: Boolean = false get() { - activeSubInfoList.stream().anyMatch { it.isEmbedded } - return false + return activeSubInfoList.stream().anyMatch { it.isEmbedded } + } + var isRemovableSimProfileEnabled: Boolean = false + get() { + return activeSubInfoList.stream().anyMatch { !it.isEmbedded } } var doesTargetSimActive = false get() { @@ -288,8 +291,8 @@ class SimOnboardingService { Log.d(TAG, "Hardware does not support DSDS.") return false } - val isActiveSim = activeSubInfoList.isNotEmpty() - if (isMultipleEnabledProfilesSupported && isActiveSim) { + val anyActiveSim = activeSubInfoList.isNotEmpty() + if (isMultipleEnabledProfilesSupported && anyActiveSim) { Log.d(TAG, "Device supports MEP and eSIM operation and eSIM profile is enabled." + " DSDS condition satisfied." @@ -297,15 +300,13 @@ class SimOnboardingService { return true } - if (doesTargetSimHaveEsimOperation) { - if (UiccSlotRepository(telephonyManager).anyRemovablePhysicalSimEnabled()) { - Log.d( - TAG, - "eSIM operation and removable PSIM is enabled. DSDS condition satisfied." - ) - return true - } - } else if (isEsimProfileEnabled) { + if (doesTargetSimHaveEsimOperation && isRemovableSimProfileEnabled) { + Log.d(TAG, + "eSIM operation and removable PSIM is enabled. DSDS condition satisfied." + ) + return true + } + if (!doesTargetSimHaveEsimOperation && isEsimProfileEnabled) { Log.d(TAG, "Removable SIM operation and eSIM profile is enabled. DSDS condition" + " satisfied." diff --git a/src/com/android/settings/network/telephony/ToggleSubscriptionDialogActivity.java b/src/com/android/settings/network/telephony/ToggleSubscriptionDialogActivity.java index f9751f1bc3c..f4d2ce2b03c 100644 --- a/src/com/android/settings/network/telephony/ToggleSubscriptionDialogActivity.java +++ b/src/com/android/settings/network/telephony/ToggleSubscriptionDialogActivity.java @@ -557,15 +557,17 @@ public class ToggleSubscriptionDialogActivity extends SubscriptionActionDialogAc Log.d(TAG, "Hardware does not support DSDS."); return false; } - boolean isActiveSim = SubscriptionUtil.getActiveSubscriptions( + boolean anyActiveSim = SubscriptionUtil.getActiveSubscriptions( mSubscriptionManager).size() > 0; - if (isMultipleEnabledProfilesSupported() && isActiveSim) { + if (isMultipleEnabledProfilesSupported() && anyActiveSim) { Log.d(TAG, "Device supports MEP and eSIM operation and eSIM profile is enabled." + " DSDS condition satisfied."); return true; } - boolean isRemovableSimEnabled = isRemovableSimEnabled(); + boolean isRemovableSimEnabled = + SubscriptionUtil.getActiveSubscriptions(mSubscriptionManager).stream() + .anyMatch(subInfo-> !subInfo.isEmbedded()); if (mIsEsimOperation && isRemovableSimEnabled) { Log.d(TAG, "eSIM operation and removable SIM is enabled. DSDS condition satisfied."); return true; @@ -583,7 +585,7 @@ public class ToggleSubscriptionDialogActivity extends SubscriptionActionDialogAc } private boolean isRemovableSimEnabled() { - return new UiccSlotRepository(mTelMgr).anyRemovablePhysicalSimEnabled(); + return new UiccSlotRepository(mTelMgr).anyRemovablePhysicalSimSlotActiveAndInserted(); } private boolean isMultipleEnabledProfilesSupported() { diff --git a/src/com/android/settings/network/telephony/UiccSlotRepository.kt b/src/com/android/settings/network/telephony/UiccSlotRepository.kt index 3a83805d7a5..8a20329c925 100644 --- a/src/com/android/settings/network/telephony/UiccSlotRepository.kt +++ b/src/com/android/settings/network/telephony/UiccSlotRepository.kt @@ -22,17 +22,17 @@ import android.util.Log class UiccSlotRepository(private val telephonyManager: TelephonyManager?) { - /** Returns whether any removable physical sim is enabled. */ - fun anyRemovablePhysicalSimEnabled(): Boolean { + /** Returns whether any removable physical sim slot is active and the sim is inserted. */ + fun anyRemovablePhysicalSimSlotActiveAndInserted(): Boolean { val result = telephonyManager?.uiccSlotsInfo?.any { uiccSlotInfo: UiccSlotInfo? -> - uiccSlotInfo.isRemovablePhysicalSimEnabled() + uiccSlotInfo.isRemovablePhysicalSimSlotActiveAndInserted() } ?: false Log.i(TAG, "anyRemovablePhysicalSimEnabled: $result") return result } - private fun UiccSlotInfo?.isRemovablePhysicalSimEnabled(): Boolean { + private fun UiccSlotInfo?.isRemovablePhysicalSimSlotActiveAndInserted(): Boolean { return this != null && isRemovable && !isEuicc && diff --git a/src/com/android/settings/notification/modes/ZenModeTriggerUpdatePreferenceController.java b/src/com/android/settings/notification/modes/ZenModeTriggerUpdatePreferenceController.java index 014a1905b3b..13d5c6e9904 100644 --- a/src/com/android/settings/notification/modes/ZenModeTriggerUpdatePreferenceController.java +++ b/src/com/android/settings/notification/modes/ZenModeTriggerUpdatePreferenceController.java @@ -40,6 +40,7 @@ import androidx.annotation.VisibleForTesting; import androidx.preference.Preference; import com.android.settings.R; +import com.android.settings.Utils; import com.android.settingslib.PrimarySwitchPreference; import com.android.settingslib.notification.modes.ZenMode; import com.android.settingslib.notification.modes.ZenModesBackend; @@ -108,7 +109,9 @@ class ZenModeTriggerUpdatePreferenceController extends AbstractZenModePreference tryParseScheduleConditionId(mode.getRule().getConditionId()); if (schedule != null) { preference.setTitle(SystemZenRules.getTimeSummary(mContext, schedule)); - preference.setSummary(SystemZenRules.getShortDaysSummary(mContext, schedule)); + preference.setSummary(Utils.createAccessibleSequence( + SystemZenRules.getDaysOfWeekShort(mContext, schedule), + SystemZenRules.getDaysOfWeekFull(mContext, schedule))); } else { // Fallback, but shouldn't happen. Log.wtf(TAG, "SCHEDULE_TIME mode without schedule: " + mode); diff --git a/src/com/android/settings/wifi/tether/WifiTetherSwitchBarController.java b/src/com/android/settings/wifi/tether/WifiTetherSwitchBarController.java index 9febba3b7d0..5ea9b3c8756 100644 --- a/src/com/android/settings/wifi/tether/WifiTetherSwitchBarController.java +++ b/src/com/android/settings/wifi/tether/WifiTetherSwitchBarController.java @@ -57,6 +57,9 @@ public class WifiTetherSwitchBarController implements private final ConnectivityManager mConnectivityManager; private final WifiManager mWifiManager; + @VisibleForTesting + boolean mIsSwitchBusy; + @VisibleForTesting DataSaverBackend mDataSaverBackend; @VisibleForTesting @@ -102,8 +105,8 @@ public class WifiTetherSwitchBarController implements @Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { - // Filter out unnecessary callbacks when switch is disabled. - if (!buttonView.isEnabled()) return; + // Filter out inappropriate callbacks when switch is busy. + if (mIsSwitchBusy) return; if (isChecked) { startTether(); @@ -115,14 +118,14 @@ public class WifiTetherSwitchBarController implements void stopTether() { if (!isWifiApActivated()) return; - mSwitchBar.setEnabled(false); + mIsSwitchBusy = true; mConnectivityManager.stopTethering(TETHERING_WIFI); } void startTether() { if (isWifiApActivated()) return; - mSwitchBar.setEnabled(false); + mIsSwitchBusy = true; mConnectivityManager.startTethering(TETHERING_WIFI, true /* showProvisioningUi */, mOnStartTetheringCallback, new Handler(Looper.getMainLooper())); } @@ -159,6 +162,7 @@ public class WifiTetherSwitchBarController implements private void updateWifiSwitch() { mSwitchBar.setEnabled(!mDataSaverBackend.isDataSaverEnabled()); + mIsSwitchBusy = false; } @Override 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) } diff --git a/tests/robotests/src/com/android/settings/notification/modes/ZenModeTriggerUpdatePreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/modes/ZenModeTriggerUpdatePreferenceControllerTest.java index b7af71b50fd..d916dcfb068 100644 --- a/tests/robotests/src/com/android/settings/notification/modes/ZenModeTriggerUpdatePreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/notification/modes/ZenModeTriggerUpdatePreferenceControllerTest.java @@ -44,6 +44,8 @@ import android.platform.test.annotations.EnableFlags; import android.platform.test.flag.junit.SetFlagsRule; import android.service.notification.SystemZenRules; import android.service.notification.ZenModeConfig; +import android.text.Spanned; +import android.text.style.TtsSpan; import android.widget.TextView; import androidx.preference.PreferenceManager; @@ -293,7 +295,14 @@ public class ZenModeTriggerUpdatePreferenceControllerTest { assertThat(mPreference.isVisible()).isTrue(); assertThat(mPreference.getTitle()).isEqualTo("1:00 AM - 3:00 PM"); - assertThat(mPreference.getSummary()).isEqualTo("Mon - Tue, Thu"); + Spanned summary = (Spanned) mPreference.getSummary(); + assertThat(summary.toString()).isEqualTo("Mon - Tue, Thu"); + TtsSpan[] ttsSpans = summary.getSpans(0, summary.length(), TtsSpan.class); + assertThat(ttsSpans).hasLength(1); + assertThat(ttsSpans[0].getType()).isEqualTo(TtsSpan.TYPE_TEXT); + assertThat(ttsSpans[0].getArgs().getString(TtsSpan.ARG_TEXT)).isEqualTo( + "Monday to Tuesday, Thursday"); + // Destination as written into the intent by SubSettingLauncher assertThat( mPreference.getIntent().getStringExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT)) diff --git a/tests/robotests/src/com/android/settings/wifi/tether/WifiTetherSwitchBarControllerTest.java b/tests/robotests/src/com/android/settings/wifi/tether/WifiTetherSwitchBarControllerTest.java index 0982f26d76c..32e3a61c860 100644 --- a/tests/robotests/src/com/android/settings/wifi/tether/WifiTetherSwitchBarControllerTest.java +++ b/tests/robotests/src/com/android/settings/wifi/tether/WifiTetherSwitchBarControllerTest.java @@ -147,8 +147,8 @@ public class WifiTetherSwitchBarControllerTest { } @Test - public void onSwitchChanged_switchNotEnabled_doNothingForTethering() { - when(mSwitch.isEnabled()).thenReturn(false); + public void onSwitchChanged_switchIsBusy_doNothingForTethering() { + mController.mIsSwitchBusy = true; mController.onCheckedChanged(mSwitch, true); diff --git a/tests/spa_unit/src/com/android/settings/network/SimOnboardingServiceTest.kt b/tests/spa_unit/src/com/android/settings/network/SimOnboardingServiceTest.kt index 6f9029e6609..676ac48dfa9 100644 --- a/tests/spa_unit/src/com/android/settings/network/SimOnboardingServiceTest.kt +++ b/tests/spa_unit/src/com/android/settings/network/SimOnboardingServiceTest.kt @@ -16,21 +16,55 @@ package com.android.settings.network +import android.content.Context import android.telephony.SubscriptionInfo +import android.telephony.SubscriptionManager +import android.telephony.TelephonyManager +import android.telephony.UiccCardInfo +import android.telephony.UiccPortInfo +import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 import com.google.common.truth.Truth.assertThat -import org.junit.Rule +import kotlinx.coroutines.delay +import kotlinx.coroutines.runBlocking import org.junit.Test import org.junit.runner.RunWith +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.mock +import org.mockito.kotlin.spy +import org.mockito.kotlin.stub + @RunWith(AndroidJUnit4::class) class SimOnboardingServiceTest { + val simOnboardingService = SimOnboardingService() + + private val mockTelephonyManager = mock { + on { activeModemCount } doReturn 2 + on { isMultiSimSupported } doReturn TelephonyManager.MULTISIM_ALLOWED + on { uiccCardsInfo } doReturn mepUiccCardInfoList + } + + private val mockSubscriptionManager = mock { + on { activeSubscriptionInfoList } doReturn listOf( + SUB_INFO_1, + SUB_INFO_2 + ) + on { availableSubscriptionInfoList } doReturn listOf( + SUB_INFO_1, + SUB_INFO_2, + SUB_INFO_3, + ) + } + + private val context: Context = spy(ApplicationProvider.getApplicationContext()) { + on { getSystemService(SubscriptionManager::class.java) } doReturn mockSubscriptionManager + on { getSystemService(TelephonyManager::class.java) } doReturn mockTelephonyManager + } @Test fun addItemForRenaming_addItemWithNewName_findItem() { - val simOnboardingService = SimOnboardingService() val newName = "NewName" - simOnboardingService.addItemForRenaming(SUB_INFO_1, newName) assertThat(simOnboardingService.renameMutableMap) @@ -39,8 +73,6 @@ class SimOnboardingServiceTest { @Test fun addItemForRenaming_sameNameAndItemNotInList_removeItem() { - val simOnboardingService = SimOnboardingService() - simOnboardingService.addItemForRenaming(SUB_INFO_1, DISPLAY_NAME_1) assertThat(simOnboardingService.renameMutableMap) @@ -49,7 +81,6 @@ class SimOnboardingServiceTest { @Test fun addItemForRenaming_sameNameAndItemInList_removeItem() { - val simOnboardingService = SimOnboardingService() simOnboardingService.renameMutableMap[SUB_INFO_1.subscriptionId] = "NewName" simOnboardingService.addItemForRenaming(SUB_INFO_1, DISPLAY_NAME_1) @@ -58,13 +89,205 @@ class SimOnboardingServiceTest { .doesNotContainKey(SUB_INFO_1.subscriptionId) } + @Test + fun isDsdsConditionSatisfied_isMultiSimEnabled_returnFalse(){ + simOnboardingService.initData(SUB_ID_3, context, {}) + + assertThat(simOnboardingService.isDsdsConditionSatisfied()).isFalse() + } + + @Test + fun isDsdsConditionSatisfied_isNotMultiSimSupported_returnFalse() { + mockTelephonyManager.stub { + on { activeModemCount } doReturn 1 + on { + isMultiSimSupported + } doReturn TelephonyManager.MULTISIM_NOT_SUPPORTED_BY_HARDWARE + } + simOnboardingService.initData(SUB_ID_3, context, {}) + + assertThat(simOnboardingService.isDsdsConditionSatisfied()).isFalse() + } + + @Test + fun isDsdsConditionSatisfied_mepAndOneActiveSim_returnTrue() = runBlocking { + mockTelephonyManager.stub { + on { activeModemCount } doReturn 1 + } + simOnboardingService.initData(SUB_ID_3, context, {}) + delay(100) + + assertThat(simOnboardingService.isDsdsConditionSatisfied()).isTrue() + } + + @Test + fun isDsdsConditionSatisfied_mepAndNoActiveSim_returnFalse() = runBlocking { + mockTelephonyManager.stub { + on { activeModemCount } doReturn 1 + } + mockSubscriptionManager.stub { + on { activeSubscriptionInfoList } doReturn listOf() + } + simOnboardingService.initData(SUB_ID_3, context, {}) + delay(100) + + assertThat(simOnboardingService.isDsdsConditionSatisfied()).isFalse() + } + + @Test + fun isDsdsConditionSatisfied_insertEsimAndOneActivePsimNoMep_returnTrue() = runBlocking { + mockTelephonyManager.stub { + on { getActiveModemCount() } doReturn 1 + on { uiccCardsInfo } doReturn noMepUiccCardInfoList + } + simOnboardingService.initData(SUB_ID_3, context, {}) + delay(100) + + assertThat(simOnboardingService.isDsdsConditionSatisfied()).isTrue() + } + + @Test + fun isDsdsConditionSatisfied_insertEsimAndNoPsimNoMep_returnFalse() = runBlocking { + mockTelephonyManager.stub { + on { getActiveModemCount() } doReturn 1 + on { uiccCardsInfo } doReturn noMepUiccCardInfoList + } + mockSubscriptionManager.stub { + on { activeSubscriptionInfoList } doReturn listOf() + } + simOnboardingService.initData(SUB_ID_3, context, {}) + delay(100) + + assertThat(simOnboardingService.isDsdsConditionSatisfied()).isFalse() + } + + @Test + fun isDsdsConditionSatisfied_insertPsimAndOneActiveEsimNoMep_returnTrue() = runBlocking { + mockTelephonyManager.stub { + on { getActiveModemCount() } doReturn 1 + on { uiccCardsInfo } doReturn noMepUiccCardInfoList + } + mockSubscriptionManager.stub { + on { activeSubscriptionInfoList } doReturn listOf( + SUB_INFO_2 + ) + } + simOnboardingService.initData(SUB_ID_1, context, {}) + delay(100) + + assertThat(simOnboardingService.isDsdsConditionSatisfied()).isTrue() + } + + @Test + fun isDsdsConditionSatisfied_insertPsimAndNoEsimNoMep_returnFalse() = runBlocking { + mockTelephonyManager.stub { + on { getActiveModemCount() } doReturn 1 + on { uiccCardsInfo } doReturn noMepUiccCardInfoList + } + mockSubscriptionManager.stub { + on { activeSubscriptionInfoList } doReturn listOf() + } + simOnboardingService.initData(SUB_ID_1, context, {}) + delay(100) + + assertThat(simOnboardingService.isDsdsConditionSatisfied()).isFalse() + } + private companion object { const val SUB_ID_1 = 1 + const val SUB_ID_2 = 2 + const val SUB_ID_3 = 3 + const val SUB_ID_4 = 4 const val DISPLAY_NAME_1 = "Sub 1" val SUB_INFO_1: SubscriptionInfo = SubscriptionInfo.Builder().apply { setId(SUB_ID_1) setDisplayName(DISPLAY_NAME_1) }.build() + + val SUB_INFO_2: SubscriptionInfo = SubscriptionInfo.Builder().apply { + setId(SUB_ID_2) + setEmbedded(true) + }.build() + + val SUB_INFO_3: SubscriptionInfo = SubscriptionInfo.Builder().apply { + setId(SUB_ID_3) + setEmbedded(true) + }.build() + + val SUB_INFO_4: SubscriptionInfo = SubscriptionInfo.Builder().apply { + setId(SUB_ID_4) + }.build() + + private const val REMOVABLE_CARD_ID_1: Int = 25 + private const val REMOVABLE_CARD_ID_2: Int = 26 + private const val EUICC_CARD_ID_3: Int = 27 + private const val EUICC_CARD_ID_4: Int = 28 + + val noMepUiccCardInfoList: List = listOf( + createUiccCardInfo( + isEuicc = true, + cardId = EUICC_CARD_ID_3, + physicalSlotIndex = 0, + isRemovable = false, + isMultipleEnabledProfileSupported = false, + logicalSlotIndex = -1, + portIndex = -1 + ), + createUiccCardInfo( + isEuicc = false, + cardId = REMOVABLE_CARD_ID_1, + physicalSlotIndex = 1, + isRemovable = true, + isMultipleEnabledProfileSupported = false, + logicalSlotIndex = -1, + portIndex = -1 + ) + ) + val mepUiccCardInfoList: List = listOf( + createUiccCardInfo( + isEuicc = true, + cardId = EUICC_CARD_ID_3, + physicalSlotIndex = 0, + isRemovable = false, + logicalSlotIndex = -1, + portIndex = -1 + ), + createUiccCardInfo( + isEuicc = false, + cardId = REMOVABLE_CARD_ID_1, + physicalSlotIndex = 1, + isRemovable = true, + logicalSlotIndex = -1, + portIndex = -1 + ) + ) + + private fun createUiccCardInfo( + isEuicc: Boolean, + cardId: Int, + physicalSlotIndex: Int, + isRemovable: Boolean, + logicalSlotIndex: Int, + portIndex: Int, + isMultipleEnabledProfileSupported:Boolean = true, + ): UiccCardInfo { + return UiccCardInfo( + isEuicc, /* isEuicc */ + cardId, /* cardId */ + null, /* eid */ + physicalSlotIndex, /* physicalSlotIndex */ + isRemovable, /* isRemovable */ + isMultipleEnabledProfileSupported, /* isMultipleEnabledProfileSupported */ + listOf( + UiccPortInfo( + "123451234567890", /* iccId */ + portIndex, /* portIdx */ + logicalSlotIndex, /* logicalSlotIdx */ + true /* isActive */ + ) + ) + ) + } } } \ No newline at end of file diff --git a/tests/spa_unit/src/com/android/settings/network/telephony/UiccSlotRepositoryTest.kt b/tests/spa_unit/src/com/android/settings/network/telephony/UiccSlotRepositoryTest.kt index 96aa1514b15..911e1d1a7c6 100644 --- a/tests/spa_unit/src/com/android/settings/network/telephony/UiccSlotRepositoryTest.kt +++ b/tests/spa_unit/src/com/android/settings/network/telephony/UiccSlotRepositoryTest.kt @@ -44,7 +44,7 @@ class UiccSlotRepositoryTest { ) } - val result = repository.anyRemovablePhysicalSimEnabled() + val result = repository.anyRemovablePhysicalSimSlotActiveAndInserted() assertThat(result).isFalse() } @@ -61,7 +61,7 @@ class UiccSlotRepositoryTest { ) } - val result = repository.anyRemovablePhysicalSimEnabled() + val result = repository.anyRemovablePhysicalSimSlotActiveAndInserted() assertThat(result).isFalse() } @@ -78,7 +78,7 @@ class UiccSlotRepositoryTest { ) } - val result = repository.anyRemovablePhysicalSimEnabled() + val result = repository.anyRemovablePhysicalSimSlotActiveAndInserted() assertThat(result).isTrue() } @@ -95,7 +95,7 @@ class UiccSlotRepositoryTest { ) } - val result = repository.anyRemovablePhysicalSimEnabled() + val result = repository.anyRemovablePhysicalSimSlotActiveAndInserted() assertThat(result).isTrue() } @@ -116,7 +116,7 @@ class UiccSlotRepositoryTest { ) } - val result = repository.anyRemovablePhysicalSimEnabled() + val result = repository.anyRemovablePhysicalSimSlotActiveAndInserted() assertThat(result).isFalse() } @@ -137,13 +137,13 @@ class UiccSlotRepositoryTest { ) } - val result = repository.anyRemovablePhysicalSimEnabled() + val result = repository.anyRemovablePhysicalSimSlotActiveAndInserted() assertThat(result).isTrue() } @Test - fun anyRemovablePhysicalSimEnabled_activePsim_returnsTrue() { + fun anyRemovablePhysicalSimSlotActiveAndInserted_activePsim_returnsTrue() { mockTelephonyManager.stub { on { uiccSlotsInfo } doReturn arrayOf( @@ -152,13 +152,13 @@ class UiccSlotRepositoryTest { ) } - val result = repository.anyRemovablePhysicalSimEnabled() + val result = repository.anyRemovablePhysicalSimSlotActiveAndInserted() assertThat(result).isTrue() } @Test - fun anyRemovablePhysicalSimEnabled_inactivePsim_returnsFalse() { + fun anyRemovablePhysicalSimSlotActiveAndInserted_inactivePsim_returnsFalse() { mockTelephonyManager.stub { on { uiccSlotsInfo } doReturn arrayOf( @@ -167,13 +167,13 @@ class UiccSlotRepositoryTest { ) } - val result = repository.anyRemovablePhysicalSimEnabled() + val result = repository.anyRemovablePhysicalSimSlotActiveAndInserted() assertThat(result).isFalse() } @Test - fun anyRemovablePhysicalSimEnabled_activeEsimAndActivePsim_returnsTrue() { + fun anyRemovablePhysicalSimSlotActiveAndInserted_activeEsimAndActivePsim_returnsTrue() { mockTelephonyManager.stub { on { uiccSlotsInfo } doReturn arrayOf( @@ -184,13 +184,13 @@ class UiccSlotRepositoryTest { ) } - val result = repository.anyRemovablePhysicalSimEnabled() + val result = repository.anyRemovablePhysicalSimSlotActiveAndInserted() assertThat(result).isTrue() } @Test - fun anyRemovablePhysicalSimEnabled_activeEsimAndInactivePsim_returnsFalse() { + fun anyRemovablePhysicalSimSlotActiveAndInserted_activeEsimAndInactivePsim_returnsFalse() { mockTelephonyManager.stub { on { uiccSlotsInfo } doReturn arrayOf( @@ -201,16 +201,16 @@ class UiccSlotRepositoryTest { ) } - val result = repository.anyRemovablePhysicalSimEnabled() + val result = repository.anyRemovablePhysicalSimSlotActiveAndInserted() assertThat(result).isFalse() } @Test - fun anyRemovablePhysicalSimEnabled_uiccSlotInfoIsNull_returnsFalse() { + fun anyRemovablePhysicalSimSlotActiveAndInserted_uiccSlotInfoIsNull_returnsFalse() { mockTelephonyManager.stub { on { uiccSlotsInfo } doReturn arrayOf(null) } - val result = repository.anyRemovablePhysicalSimEnabled() + val result = repository.anyRemovablePhysicalSimSlotActiveAndInserted() assertThat(result).isFalse() }