diff options
14 files changed, 123 insertions, 5 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt index 3a11635f75c3..c1af6df12bd1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt @@ -118,6 +118,11 @@ interface MobileConnectionRepository { /** The service provider name for this network connection, or the default name */ val networkName: StateFlow<NetworkNameModel> + /** + * True if this type of connection is allowed while airplane mode is on, and false otherwise. + */ + val isAllowedDuringAirplaneMode: StateFlow<Boolean> + companion object { /** The default number of levels to use for [numberOfLevels]. */ const val DEFAULT_NUM_LEVELS = 4 diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionRepository.kt index 6b86432b8171..17d20c297861 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionRepository.kt @@ -186,6 +186,8 @@ class DemoMobileConnectionRepository( override val networkName = MutableStateFlow(NetworkNameModel.IntentDerived("demo network")) + override val isAllowedDuringAirplaneMode = MutableStateFlow(false) + /** * Process a new demo mobile event. Note that [resolvedNetworkType] must be passed in separately * from the event, due to the requirement to reverse the mobile mappings lookup in the top-level @@ -217,6 +219,8 @@ class DemoMobileConnectionRepository( (event.activity ?: TelephonyManager.DATA_ACTIVITY_NONE).toMobileDataActivityModel() _carrierNetworkChangeActive.value = event.carrierNetworkChange _resolvedNetworkType.value = resolvedNetworkType + + isAllowedDuringAirplaneMode.value = false } fun processCarrierMergedEvent(event: FakeWifiEventModel.CarrierMerged) { @@ -240,6 +244,7 @@ class DemoMobileConnectionRepository( _isInService.value = true _isGsm.value = false _carrierNetworkChangeActive.value = false + isAllowedDuringAirplaneMode.value = true } companion object { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepository.kt index a609917351d9..65f486683837 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepository.kt @@ -165,6 +165,13 @@ class CarrierMergedConnectionRepository( override val isGsm = MutableStateFlow(false).asStateFlow() override val carrierNetworkChangeActive = MutableStateFlow(false).asStateFlow() + /** + * Carrier merged connections happen over wifi but are displayed as a mobile triangle. Because + * they occur over wifi, it's possible to have a valid carrier merged connection even during + * airplane mode. See b/291993542. + */ + override val isAllowedDuringAirplaneMode = MutableStateFlow(true).asStateFlow() + override val dataEnabled: StateFlow<Boolean> = wifiRepository.isWifiEnabled companion object { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepository.kt index 8869dfe02697..8ba7d2197c14 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepository.kt @@ -287,6 +287,15 @@ class FullMobileConnectionRepository( ) .stateIn(scope, SharingStarted.WhileSubscribed(), activeRepo.value.networkName.value) + override val isAllowedDuringAirplaneMode = + activeRepo + .flatMapLatest { it.isAllowedDuringAirplaneMode } + .stateIn( + scope, + SharingStarted.WhileSubscribed(), + activeRepo.value.isAllowedDuringAirplaneMode.value, + ) + class Factory @Inject constructor( diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt index b475183d98c6..aadc975a10de 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt @@ -59,8 +59,10 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.asExecutor import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.callbackFlow import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.map @@ -331,6 +333,9 @@ class MobileConnectionRepositoryImpl( .stateIn(scope, SharingStarted.WhileSubscribed(), initial) } + /** Typical mobile connections aren't available during airplane mode. */ + override val isAllowedDuringAirplaneMode = MutableStateFlow(false).asStateFlow() + class Factory @Inject constructor( diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt index d42e30c9b1b9..1a138272d67c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt @@ -111,6 +111,9 @@ interface MobileIconInteractor { /** See [MobileIconsInteractor.isForceHidden]. */ val isForceHidden: Flow<Boolean> + /** See [MobileConnectionRepository.isAllowedDuringAirplaneMode]. */ + val isAllowedDuringAirplaneMode: StateFlow<Boolean> + /** True when in carrier network change mode */ val carrierNetworkChangeActive: StateFlow<Boolean> } @@ -267,4 +270,6 @@ class MobileIconInteractorImpl( .stateIn(scope, SharingStarted.WhileSubscribed(), false) override val isInService = connectionRepository.isInService + + override val isAllowedDuringAirplaneMode = connectionRepository.isAllowedDuringAirplaneMode } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt index 35f4f9aa4622..fe2481595ff4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt @@ -102,9 +102,16 @@ constructor( } else { combine( airplaneModeInteractor.isAirplaneMode, + iconInteractor.isAllowedDuringAirplaneMode, iconInteractor.isForceHidden, - ) { isAirplaneMode, isForceHidden -> - !isAirplaneMode && !isForceHidden + ) { isAirplaneMode, isAllowedDuringAirplaneMode, isForceHidden -> + if (isForceHidden) { + false + } else if (isAirplaneMode) { + isAllowedDuringAirplaneMode + } else { + true + } } } .distinctUntilChanged() diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt index 6306a36d9730..50ee6a31d389 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt @@ -55,6 +55,8 @@ class FakeMobileConnectionRepository( override val networkName = MutableStateFlow<NetworkNameModel>(NetworkNameModel.Default("default")) + override val isAllowedDuringAirplaneMode = MutableStateFlow(false) + fun setDataEnabled(enabled: Boolean) { _dataEnabled.value = enabled } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepositoryTest.kt index 441186acb6b7..a251c2850d41 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepositoryTest.kt @@ -20,6 +20,7 @@ import android.telephony.TelephonyManager import android.testing.AndroidTestingRunner import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectLastValue import com.android.systemui.log.table.TableLogBuffer import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel @@ -319,6 +320,14 @@ class CarrierMergedConnectionRepositoryTest : SysuiTestCase() { job.cancel() } + @Test + fun isAllowedDuringAirplaneMode_alwaysTrue() = + testScope.runTest { + val latest by collectLastValue(underTest.isAllowedDuringAirplaneMode) + + assertThat(latest).isTrue() + } + private companion object { const val SUB_ID = 123 const val NET_ID = 456 diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt index b701fbd66937..3dd2eaff7bce 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt @@ -22,6 +22,7 @@ import android.telephony.TelephonyCallback import android.telephony.TelephonyManager import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectLastValue import com.android.systemui.log.table.TableLogBuffer import com.android.systemui.log.table.TableLogBufferFactory import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel @@ -84,7 +85,11 @@ class FullMobileConnectionRepositoryTest : SysuiTestCase() { @Before fun setUp() { mobileRepo = FakeMobileConnectionRepository(SUB_ID, tableLogBuffer) - carrierMergedRepo = FakeMobileConnectionRepository(SUB_ID, tableLogBuffer) + carrierMergedRepo = + FakeMobileConnectionRepository(SUB_ID, tableLogBuffer).apply { + // Mimicks the real carrier merged repository + this.isAllowedDuringAirplaneMode.value = true + } whenever( mobileFactory.build( @@ -300,6 +305,24 @@ class FullMobileConnectionRepositoryTest : SysuiTestCase() { } @Test + fun isAllowedDuringAirplaneMode_updatesWhenCarrierMergedUpdates() = + testScope.runTest { + initializeRepo(startingIsCarrierMerged = false) + + val latest by collectLastValue(underTest.isAllowedDuringAirplaneMode) + + assertThat(latest).isFalse() + + underTest.setIsCarrierMerged(true) + + assertThat(latest).isTrue() + + underTest.setIsCarrierMerged(false) + + assertThat(latest).isFalse() + } + + @Test fun factory_reusesLogBuffersForSameConnection() = testScope.runTest { val realLoggerFactory = diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt index cf832b4ab059..1ff737bfc137 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt @@ -53,6 +53,7 @@ import android.telephony.TelephonyManager.NETWORK_TYPE_UNKNOWN import androidx.test.filters.SmallTest import com.android.settingslib.mobile.MobileMappings import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectLastValue import com.android.systemui.log.table.TableLogBuffer import com.android.systemui.statusbar.pipeline.mobile.data.MobileInputLogger import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState @@ -812,6 +813,14 @@ class MobileConnectionRepositoryTest : SysuiTestCase() { job.cancel() } + @Test + fun isAllowedDuringAirplaneMode_alwaysFalse() = + testScope.runTest { + val latest by collectLastValue(underTest.isAllowedDuringAirplaneMode) + + assertThat(latest).isFalse() + } + private inline fun <reified T> getTelephonyCallbackForType(): T { return MobileTelephonyHelpers.getTelephonyCallbackForType(telephonyManager) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt index c4e419366759..8d1da69d6877 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt @@ -77,6 +77,8 @@ class FakeMobileIconInteractor( override val isForceHidden = MutableStateFlow(false) + override val isAllowedDuringAirplaneMode = MutableStateFlow(false) + fun setIsEmergencyOnly(emergency: Boolean) { _isEmergencyOnly.value = emergency } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt index c2768654809b..58d3804b7155 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt @@ -23,6 +23,7 @@ import com.android.settingslib.mobile.MobileIconCarrierIdOverrides import com.android.settingslib.mobile.MobileIconCarrierIdOverridesImpl import com.android.settingslib.mobile.TelephonyIcons import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectLastValue import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType.CarrierMergedNetworkType @@ -473,6 +474,18 @@ class MobileIconInteractorTest : SysuiTestCase() { job.cancel() } + @Test + fun isAllowedDuringAirplaneMode_matchesRepo() = + testScope.runTest { + val latest by collectLastValue(underTest.isAllowedDuringAirplaneMode) + + connectionRepository.isAllowedDuringAirplaneMode.value = true + assertThat(latest).isTrue() + + connectionRepository.isAllowedDuringAirplaneMode.value = false + assertThat(latest).isFalse() + } + private fun createInteractor( overrides: MobileIconCarrierIdOverrides = MobileIconCarrierIdOverridesImpl() ) = diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt index b5ab29d6217e..72feec78a979 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt @@ -116,12 +116,13 @@ class MobileIconViewModelTest : SysuiTestCase() { } @Test - fun isVisible_airplane_false() = + fun isVisible_airplaneAndNotAllowed_false() = testScope.runTest { var latest: Boolean? = null val job = underTest.isVisible.onEach { latest = it }.launchIn(this) airplaneModeRepository.setIsAirplaneMode(true) + interactor.isAllowedDuringAirplaneMode.value = false interactor.isForceHidden.value = false assertThat(latest).isFalse() @@ -129,6 +130,22 @@ class MobileIconViewModelTest : SysuiTestCase() { job.cancel() } + /** Regression test for b/291993542. */ + @Test + fun isVisible_airplaneButAllowed_true() = + testScope.runTest { + var latest: Boolean? = null + val job = underTest.isVisible.onEach { latest = it }.launchIn(this) + + airplaneModeRepository.setIsAirplaneMode(true) + interactor.isAllowedDuringAirplaneMode.value = true + interactor.isForceHidden.value = false + + assertThat(latest).isTrue() + + job.cancel() + } + @Test fun isVisible_forceHidden_false() = testScope.runTest { @@ -157,7 +174,7 @@ class MobileIconViewModelTest : SysuiTestCase() { airplaneModeRepository.setIsAirplaneMode(true) assertThat(latest).isFalse() - airplaneModeRepository.setIsAirplaneMode(false) + interactor.isAllowedDuringAirplaneMode.value = true assertThat(latest).isTrue() interactor.isForceHidden.value = true |