summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--packages/SystemUI/res/values/strings.xml2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModel.kt100
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt168
3 files changed, 172 insertions, 98 deletions
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 18b70731700a..fd943d0a5414 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1869,6 +1869,8 @@
<!-- Text displayed indicating that the user is connected to a satellite signal. -->
<string name="satellite_connected_carrier_text">Satellite SOS</string>
+ <!-- Text displayed indicating that the user might be able to use satellite SOS. -->
+ <string name="satellite_emergency_only_carrier_text">Emergency calls or SOS</string>
<!-- Accessibility label for managed profile icon (not shown on screen) [CHAR LIMIT=NONE] -->
<string name="accessibility_managed_profile">Work profile</string>
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModel.kt
index 199b5b672140..37f2f195ebf6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModel.kt
@@ -36,14 +36,12 @@ import kotlin.time.Duration.Companion.seconds
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.delay
-import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
-import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.stateIn
/**
@@ -76,37 +74,10 @@ constructor(
@DeviceBasedSatelliteInputLog logBuffer: LogBuffer,
@DeviceBasedSatelliteTableLog tableLog: TableLogBuffer,
) : DeviceBasedSatelliteViewModel {
- private val shouldShowIcon: Flow<Boolean> =
- interactor.areAllConnectionsOutOfService
- .flatMapLatest { allOos ->
- if (!allOos) {
- flowOf(false)
- } else {
- combine(
- interactor.isSatelliteAllowed,
- interactor.isSatelliteProvisioned,
- interactor.isWifiActive,
- airplaneModeRepository.isAirplaneMode
- ) { isSatelliteAllowed, isSatelliteProvisioned, isWifiActive, isAirplaneMode ->
- isSatelliteAllowed &&
- isSatelliteProvisioned &&
- !isWifiActive &&
- !isAirplaneMode
- }
- }
- }
- .distinctUntilChanged()
- .logDiffsForTable(
- tableLog,
- columnPrefix = "vm",
- columnName = COL_VISIBLE_CONDITION,
- initialValue = false,
- )
// This adds a 10 seconds delay before showing the icon
- private val shouldActuallyShowIcon: StateFlow<Boolean> =
- shouldShowIcon
- .distinctUntilChanged()
+ private val shouldShowIconForOosAfterHysteresis: StateFlow<Boolean> =
+ interactor.areAllConnectionsOutOfService
.flatMapLatest { shouldShow ->
if (shouldShow) {
logBuffer.log(
@@ -125,6 +96,45 @@ constructor(
.logDiffsForTable(
tableLog,
columnPrefix = "vm",
+ columnName = COL_VISIBLE_FOR_OOS,
+ initialValue = false,
+ )
+ .stateIn(scope, SharingStarted.WhileSubscribed(), false)
+
+ private val canShowIcon =
+ combine(
+ interactor.isSatelliteAllowed,
+ interactor.isSatelliteProvisioned,
+ ) { allowed, provisioned ->
+ allowed && provisioned
+ }
+
+ private val showIcon =
+ canShowIcon
+ .flatMapLatest { canShow ->
+ if (!canShow) {
+ flowOf(false)
+ } else {
+ combine(
+ shouldShowIconForOosAfterHysteresis,
+ interactor.connectionState,
+ interactor.isWifiActive,
+ airplaneModeRepository.isAirplaneMode,
+ ) { showForOos, connectionState, isWifiActive, isAirplaneMode ->
+ if (isWifiActive || isAirplaneMode) {
+ false
+ } else {
+ showForOos ||
+ connectionState == SatelliteConnectionState.On ||
+ connectionState == SatelliteConnectionState.Connected
+ }
+ }
+ }
+ }
+ .distinctUntilChanged()
+ .logDiffsForTable(
+ tableLog,
+ columnPrefix = "vm",
columnName = COL_VISIBLE,
initialValue = false,
)
@@ -132,7 +142,7 @@ constructor(
override val icon: StateFlow<Icon?> =
combine(
- shouldActuallyShowIcon,
+ showIcon,
interactor.connectionState,
interactor.signalStrength,
) { shouldShow, state, signalStrength ->
@@ -146,7 +156,7 @@ constructor(
override val carrierText: StateFlow<String?> =
combine(
- shouldActuallyShowIcon,
+ showIcon,
interactor.connectionState,
) { shouldShow, connectionState ->
logBuffer.log(
@@ -156,7 +166,7 @@ constructor(
bool1 = shouldShow
str1 = connectionState.name
},
- { "Updating carrier text. shouldActuallyShow=$bool1 connectionState=$str1" }
+ { "Updating carrier text. shouldShow=$bool1 connectionState=$str1" }
)
if (shouldShow) {
when (connectionState) {
@@ -165,28 +175,30 @@ constructor(
context.getString(R.string.satellite_connected_carrier_text)
SatelliteConnectionState.Off,
SatelliteConnectionState.Unknown -> {
- null
+ // If we're showing the satellite icon opportunistically, use the
+ // emergency-only version of the carrier string
+ context.getString(R.string.satellite_emergency_only_carrier_text)
}
}
} else {
null
}
}
- .onEach {
- logBuffer.log(
- TAG,
- LogLevel.INFO,
- { str1 = it },
- { "Resulting carrier text = $str1" }
- )
- }
+ .distinctUntilChanged()
+ .logDiffsForTable(
+ tableLog,
+ columnPrefix = "vm",
+ columnName = COL_CARRIER_TEXT,
+ initialValue = null,
+ )
.stateIn(scope, SharingStarted.WhileSubscribed(), null)
companion object {
private const val TAG = "DeviceBasedSatelliteViewModel"
private val DELAY_DURATION = 10.seconds
- const val COL_VISIBLE_CONDITION = "visCondition"
+ const val COL_VISIBLE_FOR_OOS = "visibleForOos"
const val COL_VISIBLE = "visible"
+ const val COL_CARRIER_TEXT = "carrierText"
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt
index c3cc33ffb81a..bf31f1e6d569 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt
@@ -45,6 +45,7 @@ import org.junit.runner.RunWith
import org.mockito.MockitoAnnotations
import org.mockito.kotlin.mock
+@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
class DeviceBasedSatelliteViewModelTest : SysuiTestCase() {
@@ -88,7 +89,7 @@ class DeviceBasedSatelliteViewModelTest : SysuiTestCase() {
}
@Test
- fun icon_nullWhenShouldNotShow_satelliteNotAllowed() =
+ fun icon_null_satelliteNotAllowed() =
testScope.runTest {
val latest by collectLastValue(underTest.icon)
@@ -108,7 +109,30 @@ class DeviceBasedSatelliteViewModelTest : SysuiTestCase() {
}
@Test
- fun icon_nullWhenShouldNotShow_notAllOos() =
+ fun icon_null_connectedAndNotAllowed() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.icon)
+
+ // GIVEN satellite is not allowed
+ repo.isSatelliteAllowedForCurrentLocation.value = false
+
+ // GIVEN all icons are OOS
+ val i1 = mobileIconsInteractor.getMobileConnectionInteractorForSubId(1)
+ i1.isInService.value = false
+ i1.isEmergencyOnly.value = false
+
+ // GIVEN satellite state is Connected. (this should not ever occur, but still)
+ repo.connectionState.value = SatelliteConnectionState.Connected
+
+ // GIVEN apm is disabled
+ airplaneModeRepository.setIsAirplaneMode(false)
+
+ // THEN icon is null despite the connected state
+ assertThat(latest).isNull()
+ }
+
+ @Test
+ fun icon_null_notAllOos() =
testScope.runTest {
val latest by collectLastValue(underTest.icon)
@@ -127,9 +151,28 @@ class DeviceBasedSatelliteViewModelTest : SysuiTestCase() {
assertThat(latest).isNull()
}
- @OptIn(ExperimentalCoroutinesApi::class)
@Test
- fun icon_nullWhenShouldNotShow_isEmergencyOnly() =
+ fun icon_null_allOosAndNotAllowed() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.icon)
+
+ // GIVEN satellite is allowed
+ repo.isSatelliteAllowedForCurrentLocation.value = false
+
+ // GIVEN all icons are OOS
+ val i1 = mobileIconsInteractor.getMobileConnectionInteractorForSubId(1)
+ i1.isInService.value = false
+ i1.isEmergencyOnly.value = false
+
+ // GIVEN apm is disabled
+ airplaneModeRepository.setIsAirplaneMode(false)
+
+ // THEN icon is null because it is not allowed
+ assertThat(latest).isNull()
+ }
+
+ @Test
+ fun icon_null_isEmergencyOnly() =
testScope.runTest {
val latest by collectLastValue(underTest.icon)
@@ -158,7 +201,7 @@ class DeviceBasedSatelliteViewModelTest : SysuiTestCase() {
}
@Test
- fun icon_nullWhenShouldNotShow_apmIsEnabled() =
+ fun icon_null_apmIsEnabled() =
testScope.runTest {
val latest by collectLastValue(underTest.icon)
@@ -177,9 +220,8 @@ class DeviceBasedSatelliteViewModelTest : SysuiTestCase() {
assertThat(latest).isNull()
}
- @OptIn(ExperimentalCoroutinesApi::class)
@Test
- fun icon_satelliteIsOn() =
+ fun icon_notNull_satelliteAllowedAndAllOos() =
testScope.runTest {
val latest by collectLastValue(underTest.icon)
@@ -201,7 +243,6 @@ class DeviceBasedSatelliteViewModelTest : SysuiTestCase() {
assertThat(latest).isInstanceOf(Icon::class.java)
}
- @OptIn(ExperimentalCoroutinesApi::class)
@Test
fun icon_hysteresisWhenEnablingIcon() =
testScope.runTest {
@@ -234,9 +275,56 @@ class DeviceBasedSatelliteViewModelTest : SysuiTestCase() {
assertThat(latest).isNull()
}
- @OptIn(ExperimentalCoroutinesApi::class)
@Test
- fun icon_deviceIsProvisioned() =
+ fun icon_ignoresHysteresis_whenConnected() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.icon)
+
+ // GIVEN satellite is allowed
+ repo.isSatelliteAllowedForCurrentLocation.value = true
+
+ // GIVEN all icons are OOS
+ val i1 = mobileIconsInteractor.getMobileConnectionInteractorForSubId(1)
+ i1.isInService.value = false
+ i1.isEmergencyOnly.value = false
+
+ // GIVEN apm is disabled
+ airplaneModeRepository.setIsAirplaneMode(false)
+
+ // GIVEN satellite reports that it is Connected
+ repo.connectionState.value = SatelliteConnectionState.Connected
+
+ // THEN icon is non null because we are connected, despite the normal OOS icon waiting
+ // 10 seconds for hysteresis
+ assertThat(latest).isInstanceOf(Icon::class.java)
+ }
+
+ @Test
+ fun icon_ignoresHysteresis_whenOn() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.icon)
+
+ // GIVEN satellite is allowed
+ repo.isSatelliteAllowedForCurrentLocation.value = true
+
+ // GIVEN all icons are OOS
+ val i1 = mobileIconsInteractor.getMobileConnectionInteractorForSubId(1)
+ i1.isInService.value = false
+ i1.isEmergencyOnly.value = false
+
+ // GIVEN apm is disabled
+ airplaneModeRepository.setIsAirplaneMode(false)
+
+ // GIVEN satellite reports that it is Connected
+ repo.connectionState.value = SatelliteConnectionState.On
+
+ // THEN icon is non null because the connection state is On, despite the normal OOS icon
+ // waiting 10 seconds for hysteresis
+ assertThat(latest).isInstanceOf(Icon::class.java)
+ }
+
+ @Test
+ fun icon_satelliteIsProvisioned() =
testScope.runTest {
val latest by collectLastValue(underTest.icon)
@@ -267,7 +355,6 @@ class DeviceBasedSatelliteViewModelTest : SysuiTestCase() {
assertThat(latest).isInstanceOf(Icon::class.java)
}
- @OptIn(ExperimentalCoroutinesApi::class)
@Test
fun icon_wifiIsActive() =
testScope.runTest {
@@ -324,13 +411,13 @@ class DeviceBasedSatelliteViewModelTest : SysuiTestCase() {
}
@Test
- fun carrierText_nullWhenShouldNotShow_notAllOos() =
+ fun carrierText_null_notAllOos() =
testScope.runTest {
val latest by collectLastValue(underTest.carrierText)
- // GIVEN satellite is allowed + connected
+ // GIVEN satellite is allowed + off
repo.isSatelliteAllowedForCurrentLocation.value = true
- repo.connectionState.value = SatelliteConnectionState.Connected
+ repo.connectionState.value = SatelliteConnectionState.Off
// GIVEN all icons are not OOS
val i1 = mobileIconsInteractor.getMobileConnectionInteractorForSubId(1)
@@ -344,9 +431,8 @@ class DeviceBasedSatelliteViewModelTest : SysuiTestCase() {
assertThat(latest).isNull()
}
- @OptIn(ExperimentalCoroutinesApi::class)
@Test
- fun carrierText_nullWhenShouldNotShow_isEmergencyOnly() =
+ fun carrierText_notNull_notAllOos_butConnected() =
testScope.runTest {
val latest by collectLastValue(underTest.carrierText)
@@ -354,25 +440,17 @@ class DeviceBasedSatelliteViewModelTest : SysuiTestCase() {
repo.isSatelliteAllowedForCurrentLocation.value = true
repo.connectionState.value = SatelliteConnectionState.Connected
- // GIVEN all icons are OOS
+ // GIVEN all icons are not OOS
val i1 = mobileIconsInteractor.getMobileConnectionInteractorForSubId(1)
- i1.isInService.value = false
+ i1.isInService.value = true
i1.isEmergencyOnly.value = false
// GIVEN apm is disabled
airplaneModeRepository.setIsAirplaneMode(false)
- // Wait for delay to be completed
- advanceTimeBy(10.seconds)
-
- // THEN carrier text is set because we don't have service
+ // THEN carrier text is not null, because it is connected
+ // This case should never happen, but let's test it anyway
assertThat(latest).isNotNull()
-
- // GIVEN the connection is emergency only
- i1.isEmergencyOnly.value = true
-
- // THEN carrier text is null because we have emergency connection
- assertThat(latest).isNull()
}
@Test
@@ -396,7 +474,6 @@ class DeviceBasedSatelliteViewModelTest : SysuiTestCase() {
assertThat(latest).isNull()
}
- @OptIn(ExperimentalCoroutinesApi::class)
@Test
fun carrierText_satelliteIsOn() =
testScope.runTest {
@@ -421,9 +498,8 @@ class DeviceBasedSatelliteViewModelTest : SysuiTestCase() {
assertThat(latest).isNotNull()
}
- @OptIn(ExperimentalCoroutinesApi::class)
@Test
- fun carrierText_hysteresisWhenEnablingText() =
+ fun carrierText_noHysteresisWhenEnablingText_connected() =
testScope.runTest {
val latest by collectLastValue(underTest.carrierText)
@@ -439,23 +515,10 @@ class DeviceBasedSatelliteViewModelTest : SysuiTestCase() {
// GIVEN apm is disabled
airplaneModeRepository.setIsAirplaneMode(false)
- // THEN carrier text is null because of the hysteresis
- assertThat(latest).isNull()
-
- // Wait for delay to be completed
- advanceTimeBy(10.seconds)
-
- // THEN carrier text is set after the delay
+ // THEN carrier text is not null because we skip hysteresis when connected
assertThat(latest).isNotNull()
-
- // GIVEN apm is enabled
- airplaneModeRepository.setIsAirplaneMode(true)
-
- // THEN carrier text is null immediately
- assertThat(latest).isNull()
}
- @OptIn(ExperimentalCoroutinesApi::class)
@Test
fun carrierText_deviceIsProvisioned() =
testScope.runTest {
@@ -489,7 +552,6 @@ class DeviceBasedSatelliteViewModelTest : SysuiTestCase() {
assertThat(latest).isNotNull()
}
- @OptIn(ExperimentalCoroutinesApi::class)
@Test
fun carrierText_wifiIsActive() =
testScope.runTest {
@@ -526,9 +588,8 @@ class DeviceBasedSatelliteViewModelTest : SysuiTestCase() {
assertThat(latest).isNotNull()
}
- @OptIn(ExperimentalCoroutinesApi::class)
@Test
- fun carrierText_connectionStateUnknown_null() =
+ fun carrierText_connectionStateUnknown_usesEmergencyOnlyText() =
testScope.runTest {
val latest by collectLastValue(underTest.carrierText)
@@ -544,12 +605,12 @@ class DeviceBasedSatelliteViewModelTest : SysuiTestCase() {
// Wait for delay to be completed
advanceTimeBy(10.seconds)
- assertThat(latest).isNull()
+ assertThat(latest)
+ .isEqualTo(context.getString(R.string.satellite_emergency_only_carrier_text))
}
- @OptIn(ExperimentalCoroutinesApi::class)
@Test
- fun carrierText_connectionStateOff_null() =
+ fun carrierText_connectionStateOff_usesEmergencyOnlyText() =
testScope.runTest {
val latest by collectLastValue(underTest.carrierText)
@@ -565,10 +626,10 @@ class DeviceBasedSatelliteViewModelTest : SysuiTestCase() {
// Wait for delay to be completed
advanceTimeBy(10.seconds)
- assertThat(latest).isNull()
+ assertThat(latest)
+ .isEqualTo(context.getString(R.string.satellite_emergency_only_carrier_text))
}
- @OptIn(ExperimentalCoroutinesApi::class)
@Test
fun carrierText_connectionStateOn_notConnectedString() =
testScope.runTest {
@@ -590,7 +651,6 @@ class DeviceBasedSatelliteViewModelTest : SysuiTestCase() {
.isEqualTo(context.getString(R.string.satellite_connected_carrier_text))
}
- @OptIn(ExperimentalCoroutinesApi::class)
@Test
fun carrierText_connectionStateConnected_connectedString() =
testScope.runTest {