Merge "[sat] use new ntn signal strength TelephonyCallback" into main
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt
index a8bcfbc..39a1c10 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.statusbar.pipeline.mobile.domain.interactor
 
+import android.platform.test.annotations.DisableFlags
 import android.platform.test.annotations.EnableFlags
 import android.telephony.CellSignalStrength
 import android.telephony.TelephonyManager.NETWORK_TYPE_UNKNOWN
@@ -735,9 +736,10 @@
         }
 
     @EnableFlags(com.android.internal.telephony.flags.Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG)
+    @DisableFlags(com.android.internal.telephony.flags.Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
     @Test
     // See b/346904529 for more context
-    fun satBasedIcon_doesNotInflateSignalStrength() =
+    fun satBasedIcon_doesNotInflateSignalStrength_flagOff() =
         testScope.runTest {
             val latest by collectLastValue(underTest.signalLevelIcon)
 
@@ -756,7 +758,75 @@
             assertThat(latest!!.level).isEqualTo(4)
         }
 
+    @EnableFlags(
+        com.android.internal.telephony.flags.Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG,
+        com.android.internal.telephony.flags.Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN,
+    )
+    @Test
+    // See b/346904529 for more context
+    fun satBasedIcon_doesNotInflateSignalStrength_flagOn() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.signalLevelIcon)
+
+            // GIVEN a satellite connection
+            connectionRepository.isNonTerrestrial.value = true
+            // GIVEN this carrier has set INFLATE_SIGNAL_STRENGTH
+            connectionRepository.inflateSignalStrength.value = true
+
+            connectionRepository.satelliteLevel.value = 4
+            assertThat(latest!!.level).isEqualTo(4)
+
+            connectionRepository.inflateSignalStrength.value = true
+            connectionRepository.primaryLevel.value = 4
+
+            // Icon level is unaffected
+            assertThat(latest!!.level).isEqualTo(4)
+        }
+
     @EnableFlags(com.android.internal.telephony.flags.Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG)
+    @DisableFlags(com.android.internal.telephony.flags.Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
+    @Test
+    fun satBasedIcon_usesPrimaryLevel_flagOff() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.signalLevelIcon)
+
+            // GIVEN a satellite connection
+            connectionRepository.isNonTerrestrial.value = true
+
+            // GIVEN primary level is set
+            connectionRepository.primaryLevel.value = 4
+            connectionRepository.satelliteLevel.value = 0
+
+            // THEN icon uses the primary level because the flag is off
+            assertThat(latest!!.level).isEqualTo(4)
+        }
+
+    @EnableFlags(
+        com.android.internal.telephony.flags.Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG,
+        com.android.internal.telephony.flags.Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN,
+    )
+    @Test
+    fun satBasedIcon_usesSatelliteLevel_flagOn() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.signalLevelIcon)
+
+            // GIVEN a satellite connection
+            connectionRepository.isNonTerrestrial.value = true
+
+            // GIVEN satellite level is set
+            connectionRepository.satelliteLevel.value = 4
+            connectionRepository.primaryLevel.value = 0
+
+            // THEN icon uses the satellite level because the flag is on
+            assertThat(latest!!.level).isEqualTo(4)
+        }
+
+    /**
+     * Context (b/377518113), this test will not be needed after FLAG_CARRIER_ROAMING_NB_IOT_NTN is
+     * rolled out. The new API should report 0 automatically if not in service.
+     */
+    @EnableFlags(com.android.internal.telephony.flags.Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG)
+    @DisableFlags(com.android.internal.telephony.flags.Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
     @Test
     fun satBasedIcon_reportsLevelZeroWhenOutOfService() =
         testScope.runTest {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt
index 4c7cdfa..038722c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt
@@ -151,7 +151,7 @@
                 iconsInteractor.isForceHidden,
                 repository,
                 context,
-                MobileIconCarrierIdOverridesFake()
+                MobileIconCarrierIdOverridesFake(),
             )
         createAndSetViewModel()
     }
@@ -359,7 +359,7 @@
             val expected =
                 Icon.Resource(
                     THREE_G.dataType,
-                    ContentDescription.Resource(THREE_G.dataContentDescription)
+                    ContentDescription.Resource(THREE_G.dataContentDescription),
                 )
             connectionsRepository.mobileIsDefault.value = true
             repository.setNetworkTypeKey(connectionsRepository.GSM_KEY)
@@ -406,7 +406,7 @@
             val expected =
                 Icon.Resource(
                     THREE_G.dataType,
-                    ContentDescription.Resource(THREE_G.dataContentDescription)
+                    ContentDescription.Resource(THREE_G.dataContentDescription),
                 )
             repository.setNetworkTypeKey(connectionsRepository.GSM_KEY)
             repository.setDataEnabled(true)
@@ -426,7 +426,7 @@
             val initial =
                 Icon.Resource(
                     THREE_G.dataType,
-                    ContentDescription.Resource(THREE_G.dataContentDescription)
+                    ContentDescription.Resource(THREE_G.dataContentDescription),
                 )
 
             repository.setNetworkTypeKey(connectionsRepository.GSM_KEY)
@@ -448,7 +448,7 @@
             val expected =
                 Icon.Resource(
                     THREE_G.dataType,
-                    ContentDescription.Resource(THREE_G.dataContentDescription)
+                    ContentDescription.Resource(THREE_G.dataContentDescription),
                 )
             repository.dataEnabled.value = true
             var latest: Icon? = null
@@ -477,7 +477,7 @@
             val expected =
                 Icon.Resource(
                     THREE_G.dataType,
-                    ContentDescription.Resource(THREE_G.dataContentDescription)
+                    ContentDescription.Resource(THREE_G.dataContentDescription),
                 )
             assertThat(latest).isEqualTo(expected)
 
@@ -499,7 +499,7 @@
             val expected =
                 Icon.Resource(
                     THREE_G.dataType,
-                    ContentDescription.Resource(THREE_G.dataContentDescription)
+                    ContentDescription.Resource(THREE_G.dataContentDescription),
                 )
             assertThat(latest).isEqualTo(expected)
 
@@ -520,7 +520,7 @@
             val expected =
                 Icon.Resource(
                     THREE_G.dataType,
-                    ContentDescription.Resource(THREE_G.dataContentDescription)
+                    ContentDescription.Resource(THREE_G.dataContentDescription),
                 )
             assertThat(latest).isEqualTo(expected)
 
@@ -542,7 +542,7 @@
             val expected =
                 Icon.Resource(
                     connectionsRepository.defaultMobileIconGroup.value.dataType,
-                    ContentDescription.Resource(G.dataContentDescription)
+                    ContentDescription.Resource(G.dataContentDescription),
                 )
 
             assertThat(latest).isEqualTo(expected)
@@ -564,7 +564,7 @@
             val expected =
                 Icon.Resource(
                     THREE_G.dataType,
-                    ContentDescription.Resource(THREE_G.dataContentDescription)
+                    ContentDescription.Resource(THREE_G.dataContentDescription),
                 )
             assertThat(latest).isEqualTo(expected)
 
@@ -621,10 +621,7 @@
                 underTest.activityInVisible.onEach { containerVisible = it }.launchIn(this)
 
             repository.dataActivityDirection.value =
-                DataActivityModel(
-                    hasActivityIn = true,
-                    hasActivityOut = true,
-                )
+                DataActivityModel(hasActivityIn = true, hasActivityOut = true)
 
             assertThat(inVisible).isFalse()
             assertThat(outVisible).isFalse()
@@ -654,10 +651,7 @@
                 underTest.activityContainerVisible.onEach { containerVisible = it }.launchIn(this)
 
             repository.dataActivityDirection.value =
-                DataActivityModel(
-                    hasActivityIn = true,
-                    hasActivityOut = false,
-                )
+                DataActivityModel(hasActivityIn = true, hasActivityOut = false)
 
             yield()
 
@@ -666,20 +660,14 @@
             assertThat(containerVisible).isTrue()
 
             repository.dataActivityDirection.value =
-                DataActivityModel(
-                    hasActivityIn = false,
-                    hasActivityOut = true,
-                )
+                DataActivityModel(hasActivityIn = false, hasActivityOut = true)
 
             assertThat(inVisible).isFalse()
             assertThat(outVisible).isTrue()
             assertThat(containerVisible).isTrue()
 
             repository.dataActivityDirection.value =
-                DataActivityModel(
-                    hasActivityIn = false,
-                    hasActivityOut = false,
-                )
+                DataActivityModel(hasActivityIn = false, hasActivityOut = false)
 
             assertThat(inVisible).isFalse()
             assertThat(outVisible).isFalse()
@@ -709,10 +697,7 @@
                 underTest.activityContainerVisible.onEach { containerVisible = it }.launchIn(this)
 
             repository.dataActivityDirection.value =
-                DataActivityModel(
-                    hasActivityIn = true,
-                    hasActivityOut = false,
-                )
+                DataActivityModel(hasActivityIn = true, hasActivityOut = false)
 
             yield()
 
@@ -721,20 +706,14 @@
             assertThat(containerVisible).isTrue()
 
             repository.dataActivityDirection.value =
-                DataActivityModel(
-                    hasActivityIn = false,
-                    hasActivityOut = true,
-                )
+                DataActivityModel(hasActivityIn = false, hasActivityOut = true)
 
             assertThat(inVisible).isFalse()
             assertThat(outVisible).isTrue()
             assertThat(containerVisible).isTrue()
 
             repository.dataActivityDirection.value =
-                DataActivityModel(
-                    hasActivityIn = false,
-                    hasActivityOut = false,
-                )
+                DataActivityModel(hasActivityIn = false, hasActivityOut = false)
 
             assertThat(inVisible).isFalse()
             assertThat(outVisible).isFalse()
@@ -824,10 +803,7 @@
             // sets the background on cellular
             repository.hasPrioritizedNetworkCapabilities.value = true
             repository.dataActivityDirection.value =
-                DataActivityModel(
-                    hasActivityIn = true,
-                    hasActivityOut = true,
-                )
+                DataActivityModel(hasActivityIn = true, hasActivityOut = true)
 
             assertThat(roaming).isFalse()
             assertThat(networkTypeIcon).isNull()
@@ -838,11 +814,13 @@
         }
 
     @EnableFlags(com.android.internal.telephony.flags.Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG)
+    @DisableFlags(com.android.internal.telephony.flags.Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
     @Test
-    fun nonTerrestrial_usesSatelliteIcon() =
+    fun nonTerrestrial_usesSatelliteIcon_flagOff() =
         testScope.runTest {
             repository.isNonTerrestrial.value = true
             repository.setAllLevels(0)
+            repository.satelliteLevel.value = 0
 
             val latest by
                 collectLastValue(underTest.icon.filterIsInstance(SignalIconModel.Satellite::class))
@@ -853,28 +831,66 @@
 
             // 1-2 -> 1 bar
             repository.setAllLevels(1)
+            repository.satelliteLevel.value = 1
             assertThat(latest!!.icon.res).isEqualTo(R.drawable.ic_satellite_connected_1)
 
             repository.setAllLevels(2)
+            repository.satelliteLevel.value = 2
             assertThat(latest!!.icon.res).isEqualTo(R.drawable.ic_satellite_connected_1)
 
             // 3-4 -> 2 bars
             repository.setAllLevels(3)
+            repository.satelliteLevel.value = 3
             assertThat(latest!!.icon.res).isEqualTo(R.drawable.ic_satellite_connected_2)
 
             repository.setAllLevels(4)
+            repository.satelliteLevel.value = 4
+            assertThat(latest!!.icon.res).isEqualTo(R.drawable.ic_satellite_connected_2)
+        }
+
+    @EnableFlags(
+        com.android.internal.telephony.flags.Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG,
+        com.android.internal.telephony.flags.Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN,
+    )
+    @Test
+    fun nonTerrestrial_usesSatelliteIcon_flagOn() =
+        testScope.runTest {
+            repository.isNonTerrestrial.value = true
+            repository.satelliteLevel.value = 0
+
+            val latest by
+                collectLastValue(underTest.icon.filterIsInstance(SignalIconModel.Satellite::class))
+
+            // Level 0 -> no connection
+            assertThat(latest).isNotNull()
+            assertThat(latest!!.icon.res).isEqualTo(R.drawable.ic_satellite_connected_0)
+
+            // 1-2 -> 1 bar
+            repository.satelliteLevel.value = 1
+            assertThat(latest!!.icon.res).isEqualTo(R.drawable.ic_satellite_connected_1)
+
+            repository.satelliteLevel.value = 2
+            assertThat(latest!!.icon.res).isEqualTo(R.drawable.ic_satellite_connected_1)
+
+            // 3-4 -> 2 bars
+            repository.satelliteLevel.value = 3
+            assertThat(latest!!.icon.res).isEqualTo(R.drawable.ic_satellite_connected_2)
+
+            repository.satelliteLevel.value = 4
             assertThat(latest!!.icon.res).isEqualTo(R.drawable.ic_satellite_connected_2)
         }
 
     @EnableFlags(com.android.internal.telephony.flags.Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG)
+    @DisableFlags(com.android.internal.telephony.flags.Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
     @Test
-    fun satelliteIcon_ignoresInflateSignalStrength() =
+    fun satelliteIcon_ignoresInflateSignalStrength_flagOff() =
         testScope.runTest {
             // Note that this is the exact same test as above, but with inflateSignalStrength set to
             // true we note that the level is unaffected by inflation
             repository.inflateSignalStrength.value = true
             repository.isNonTerrestrial.value = true
             repository.setAllLevels(0)
+            repository.satelliteLevel.value = 0
 
             val latest by
                 collectLastValue(underTest.icon.filterIsInstance(SignalIconModel.Satellite::class))
@@ -885,16 +901,55 @@
 
             // 1-2 -> 1 bar
             repository.setAllLevels(1)
+            repository.satelliteLevel.value = 1
             assertThat(latest!!.icon.res).isEqualTo(R.drawable.ic_satellite_connected_1)
 
             repository.setAllLevels(2)
+            repository.satelliteLevel.value = 2
             assertThat(latest!!.icon.res).isEqualTo(R.drawable.ic_satellite_connected_1)
 
             // 3-4 -> 2 bars
             repository.setAllLevels(3)
+            repository.satelliteLevel.value = 3
             assertThat(latest!!.icon.res).isEqualTo(R.drawable.ic_satellite_connected_2)
 
             repository.setAllLevels(4)
+            repository.satelliteLevel.value = 4
+            assertThat(latest!!.icon.res).isEqualTo(R.drawable.ic_satellite_connected_2)
+        }
+
+    @EnableFlags(
+        com.android.internal.telephony.flags.Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG,
+        com.android.internal.telephony.flags.Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN,
+    )
+    @Test
+    fun satelliteIcon_ignoresInflateSignalStrength_flagOn() =
+        testScope.runTest {
+            // Note that this is the exact same test as above, but with inflateSignalStrength set to
+            // true we note that the level is unaffected by inflation
+            repository.inflateSignalStrength.value = true
+            repository.isNonTerrestrial.value = true
+            repository.satelliteLevel.value = 0
+
+            val latest by
+                collectLastValue(underTest.icon.filterIsInstance(SignalIconModel.Satellite::class))
+
+            // Level 0 -> no connection
+            assertThat(latest).isNotNull()
+            assertThat(latest!!.icon.res).isEqualTo(R.drawable.ic_satellite_connected_0)
+
+            // 1-2 -> 1 bar
+            repository.satelliteLevel.value = 1
+            assertThat(latest!!.icon.res).isEqualTo(R.drawable.ic_satellite_connected_1)
+
+            repository.satelliteLevel.value = 2
+            assertThat(latest!!.icon.res).isEqualTo(R.drawable.ic_satellite_connected_1)
+
+            // 3-4 -> 2 bars
+            repository.satelliteLevel.value = 3
+            assertThat(latest!!.icon.res).isEqualTo(R.drawable.ic_satellite_connected_2)
+
+            repository.satelliteLevel.value = 4
             assertThat(latest!!.icon.res).isEqualTo(R.drawable.ic_satellite_connected_2)
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLogger.kt
index 9cbfc44..94e9d26 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLogger.kt
@@ -21,6 +21,7 @@
 import android.telephony.SignalStrength
 import android.telephony.TelephonyDisplayInfo
 import android.telephony.TelephonyManager
+import android.telephony.satellite.NtnSignalStrength
 import com.android.settingslib.SignalIcon
 import com.android.settingslib.mobile.MobileMappings
 import com.android.systemui.dagger.SysUISingleton
@@ -31,11 +32,7 @@
 
 /** Logs for inputs into the mobile pipeline. */
 @SysUISingleton
-class MobileInputLogger
-@Inject
-constructor(
-    @MobileInputLog private val buffer: LogBuffer,
-) {
+class MobileInputLogger @Inject constructor(@MobileInputLog private val buffer: LogBuffer) {
     fun logOnServiceStateChanged(serviceState: ServiceState, subId: Int) {
         buffer.log(
             TAG,
@@ -49,7 +46,7 @@
             {
                 "onServiceStateChanged: subId=$int1 emergencyOnly=$bool1 roaming=$bool2" +
                     " operator=$str1"
-            }
+            },
         )
     }
 
@@ -61,7 +58,7 @@
                 int1 = subId
                 bool1 = serviceState.isEmergencyOnly
             },
-            { "ACTION_SERVICE_STATE for subId=$int1. ServiceState.isEmergencyOnly=$bool1" }
+            { "ACTION_SERVICE_STATE for subId=$int1. ServiceState.isEmergencyOnly=$bool1" },
         )
     }
 
@@ -70,7 +67,7 @@
             TAG,
             LogLevel.INFO,
             { int1 = subId },
-            { "ACTION_SERVICE_STATE for subId=$int1. Intent is missing extras. Ignoring" }
+            { "ACTION_SERVICE_STATE for subId=$int1. Intent is missing extras. Ignoring" },
         )
     }
 
@@ -82,7 +79,16 @@
                 int1 = subId
                 str1 = signalStrength.toString()
             },
-            { "onSignalStrengthsChanged: subId=$int1 strengths=$str1" }
+            { "onSignalStrengthsChanged: subId=$int1 strengths=$str1" },
+        )
+    }
+
+    fun logNtnSignalStrengthChanged(signalStrength: NtnSignalStrength) {
+        buffer.log(
+            TAG,
+            LogLevel.INFO,
+            { int1 = signalStrength.level },
+            { "onCarrierRoamingNtnSignalStrengthChanged: level=$int1" },
         )
     }
 
@@ -128,7 +134,7 @@
             TAG,
             LogLevel.INFO,
             { bool1 = active },
-            { "onCarrierRoamingNtnModeChanged: $bool1" }
+            { "onCarrierRoamingNtnModeChanged: $bool1" },
         )
     }
 
@@ -146,12 +152,7 @@
     }
 
     fun logCarrierConfigChanged(subId: Int) {
-        buffer.log(
-            TAG,
-            LogLevel.INFO,
-            { int1 = subId },
-            { "onCarrierConfigChanged: subId=$int1" },
-        )
+        buffer.log(TAG, LogLevel.INFO, { int1 = subId }, { "onCarrierConfigChanged: subId=$int1" })
     }
 
     fun logOnDataEnabledChanged(enabled: Boolean, subId: Int) {
@@ -175,7 +176,7 @@
             TAG,
             LogLevel.INFO,
             { str1 = config.toString() },
-            { "defaultDataSubRatConfig: $str1" }
+            { "defaultDataSubRatConfig: $str1" },
         )
     }
 
@@ -184,7 +185,7 @@
             TAG,
             LogLevel.INFO,
             { str1 = mapping.toString() },
-            { "defaultMobileIconMapping: $str1" }
+            { "defaultMobileIconMapping: $str1" },
         )
     }
 
@@ -216,7 +217,7 @@
             {
                 "Intent: ACTION_SERVICE_PROVIDERS_UPDATED." +
                     " showSpn=$bool1 spn=$str1 dataSpn=$str2 showPlmn=$bool2 plmn=$str3"
-            }
+            },
         )
     }
 
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 205205e..07843f1 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
@@ -107,6 +107,12 @@
     // @IntRange(from = 0, to = 4)
     val primaryLevel: StateFlow<Int>
 
+    /**
+     * This level can be used to reflect the signal strength when in carrier roaming NTN mode
+     * (carrier-based satellite)
+     */
+    val satelliteLevel: StateFlow<Int>
+
     /** The current data connection state. See [DataConnectionState] */
     val dataConnectionState: StateFlow<DataConnectionState>
 
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 3261b71..be3977e 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
@@ -37,12 +37,14 @@
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.FullMobileConnectionRepository.Companion.COL_OPERATOR
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.FullMobileConnectionRepository.Companion.COL_PRIMARY_LEVEL
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.FullMobileConnectionRepository.Companion.COL_ROAMING
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.FullMobileConnectionRepository.Companion.COL_SATELLITE_LEVEL
 import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
 import com.android.systemui.statusbar.pipeline.shared.data.model.toMobileDataActivityModel
 import com.android.systemui.statusbar.pipeline.wifi.data.repository.demo.model.FakeWifiEventModel
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.stateIn
 
@@ -75,7 +77,7 @@
                 tableLogBuffer,
                 columnPrefix = "",
                 columnName = "inflate",
-                _inflateSignalStrength.value
+                _inflateSignalStrength.value,
             )
             .stateIn(scope, SharingStarted.WhileSubscribed(), _inflateSignalStrength.value)
 
@@ -89,7 +91,7 @@
                 tableLogBuffer,
                 columnPrefix = "",
                 columnName = COL_EMERGENCY,
-                _isEmergencyOnly.value
+                _isEmergencyOnly.value,
             )
             .stateIn(scope, SharingStarted.WhileSubscribed(), _isEmergencyOnly.value)
 
@@ -100,7 +102,7 @@
                 tableLogBuffer,
                 columnPrefix = "",
                 columnName = COL_ROAMING,
-                _isRoaming.value
+                _isRoaming.value,
             )
             .stateIn(scope, SharingStarted.WhileSubscribed(), _isRoaming.value)
 
@@ -111,7 +113,7 @@
                 tableLogBuffer,
                 columnPrefix = "",
                 columnName = COL_OPERATOR,
-                _operatorAlphaShort.value
+                _operatorAlphaShort.value,
             )
             .stateIn(scope, SharingStarted.WhileSubscribed(), _operatorAlphaShort.value)
 
@@ -122,7 +124,7 @@
                 tableLogBuffer,
                 columnPrefix = "",
                 columnName = COL_IS_IN_SERVICE,
-                _isInService.value
+                _isInService.value,
             )
             .stateIn(scope, SharingStarted.WhileSubscribed(), _isInService.value)
 
@@ -133,7 +135,7 @@
                 tableLogBuffer,
                 columnPrefix = "",
                 columnName = COL_IS_NTN,
-                _isNonTerrestrial.value
+                _isNonTerrestrial.value,
             )
             .stateIn(scope, SharingStarted.WhileSubscribed(), _isNonTerrestrial.value)
 
@@ -144,7 +146,7 @@
                 tableLogBuffer,
                 columnPrefix = "",
                 columnName = COL_IS_GSM,
-                _isGsm.value
+                _isGsm.value,
             )
             .stateIn(scope, SharingStarted.WhileSubscribed(), _isGsm.value)
 
@@ -155,7 +157,7 @@
                 tableLogBuffer,
                 columnPrefix = "",
                 columnName = COL_CDMA_LEVEL,
-                _cdmaLevel.value
+                _cdmaLevel.value,
             )
             .stateIn(scope, SharingStarted.WhileSubscribed(), _cdmaLevel.value)
 
@@ -166,10 +168,21 @@
                 tableLogBuffer,
                 columnPrefix = "",
                 columnName = COL_PRIMARY_LEVEL,
-                _primaryLevel.value
+                _primaryLevel.value,
             )
             .stateIn(scope, SharingStarted.WhileSubscribed(), _primaryLevel.value)
 
+    private val _satelliteLevel = MutableStateFlow(0)
+    override val satelliteLevel: StateFlow<Int> =
+        _satelliteLevel
+            .logDiffsForTable(
+                tableLogBuffer,
+                columnPrefix = "",
+                columnName = COL_SATELLITE_LEVEL,
+                _satelliteLevel.value,
+            )
+            .stateIn(scope, SharingStarted.WhileSubscribed(), _satelliteLevel.value)
+
     private val _dataConnectionState = MutableStateFlow(DataConnectionState.Disconnected)
     override val dataConnectionState =
         _dataConnectionState
@@ -177,12 +190,7 @@
             .stateIn(scope, SharingStarted.WhileSubscribed(), _dataConnectionState.value)
 
     private val _dataActivityDirection =
-        MutableStateFlow(
-            DataActivityModel(
-                hasActivityIn = false,
-                hasActivityOut = false,
-            )
-        )
+        MutableStateFlow(DataActivityModel(hasActivityIn = false, hasActivityOut = false))
     override val dataActivityDirection =
         _dataActivityDirection
             .logDiffsForTable(tableLogBuffer, columnPrefix = "", _dataActivityDirection.value)
@@ -195,7 +203,7 @@
                 tableLogBuffer,
                 columnPrefix = "",
                 columnName = COL_CARRIER_NETWORK_CHANGE,
-                _carrierNetworkChangeActive.value
+                _carrierNetworkChangeActive.value,
             )
             .stateIn(scope, SharingStarted.WhileSubscribed(), _carrierNetworkChangeActive.value)
 
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 2e47678..75f613d 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
@@ -90,7 +90,7 @@
                         TAG,
                         "Connection repo subId=$subId " +
                             "does not equal wifi repo subId=${network.subscriptionId}; " +
-                            "not showing carrier merged"
+                            "not showing carrier merged",
                     )
                     null
                 }
@@ -149,7 +149,7 @@
             .stateIn(
                 scope,
                 SharingStarted.WhileSubscribed(),
-                ResolvedNetworkType.UnknownNetworkType
+                ResolvedNetworkType.UnknownNetworkType,
             )
 
     override val dataConnectionState =
@@ -173,6 +173,7 @@
     override val isNonTerrestrial = MutableStateFlow(false).asStateFlow()
     override val isGsm = MutableStateFlow(false).asStateFlow()
     override val carrierNetworkChangeActive = MutableStateFlow(false).asStateFlow()
+    override val satelliteLevel = MutableStateFlow(0)
 
     /**
      * Carrier merged connections happen over wifi but are displayed as a mobile triangle. Because
@@ -207,10 +208,7 @@
         @Application private val scope: CoroutineScope,
         private val wifiRepository: WifiRepository,
     ) {
-        fun build(
-            subId: Int,
-            mobileLogger: TableLogBuffer,
-        ): MobileConnectionRepository {
+        fun build(subId: Int, mobileLogger: TableLogBuffer): MobileConnectionRepository {
             return CarrierMergedConnectionRepository(
                 subId,
                 mobileLogger,
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 a5e47a6..fae9be0 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
@@ -132,12 +132,12 @@
                 tableLogBuffer,
                 columnPrefix = "",
                 columnName = COL_EMERGENCY,
-                activeRepo.value.isEmergencyOnly.value
+                activeRepo.value.isEmergencyOnly.value,
             )
             .stateIn(
                 scope,
                 SharingStarted.WhileSubscribed(),
-                activeRepo.value.isEmergencyOnly.value
+                activeRepo.value.isEmergencyOnly.value,
             )
 
     override val isRoaming =
@@ -147,7 +147,7 @@
                 tableLogBuffer,
                 columnPrefix = "",
                 columnName = COL_ROAMING,
-                activeRepo.value.isRoaming.value
+                activeRepo.value.isRoaming.value,
             )
             .stateIn(scope, SharingStarted.WhileSubscribed(), activeRepo.value.isRoaming.value)
 
@@ -158,12 +158,12 @@
                 tableLogBuffer,
                 columnPrefix = "",
                 columnName = COL_OPERATOR,
-                activeRepo.value.operatorAlphaShort.value
+                activeRepo.value.operatorAlphaShort.value,
             )
             .stateIn(
                 scope,
                 SharingStarted.WhileSubscribed(),
-                activeRepo.value.operatorAlphaShort.value
+                activeRepo.value.operatorAlphaShort.value,
             )
 
     override val isInService =
@@ -173,7 +173,7 @@
                 tableLogBuffer,
                 columnPrefix = "",
                 columnName = COL_IS_IN_SERVICE,
-                activeRepo.value.isInService.value
+                activeRepo.value.isInService.value,
             )
             .stateIn(scope, SharingStarted.WhileSubscribed(), activeRepo.value.isInService.value)
 
@@ -184,12 +184,12 @@
                 tableLogBuffer,
                 columnPrefix = "",
                 columnName = COL_IS_NTN,
-                activeRepo.value.isNonTerrestrial.value
+                activeRepo.value.isNonTerrestrial.value,
             )
             .stateIn(
                 scope,
                 SharingStarted.WhileSubscribed(),
-                activeRepo.value.isNonTerrestrial.value
+                activeRepo.value.isNonTerrestrial.value,
             )
 
     override val isGsm =
@@ -199,7 +199,7 @@
                 tableLogBuffer,
                 columnPrefix = "",
                 columnName = COL_IS_GSM,
-                activeRepo.value.isGsm.value
+                activeRepo.value.isGsm.value,
             )
             .stateIn(scope, SharingStarted.WhileSubscribed(), activeRepo.value.isGsm.value)
 
@@ -210,7 +210,7 @@
                 tableLogBuffer,
                 columnPrefix = "",
                 columnName = COL_CDMA_LEVEL,
-                activeRepo.value.cdmaLevel.value
+                activeRepo.value.cdmaLevel.value,
             )
             .stateIn(scope, SharingStarted.WhileSubscribed(), activeRepo.value.cdmaLevel.value)
 
@@ -221,22 +221,33 @@
                 tableLogBuffer,
                 columnPrefix = "",
                 columnName = COL_PRIMARY_LEVEL,
-                activeRepo.value.primaryLevel.value
+                activeRepo.value.primaryLevel.value,
             )
             .stateIn(scope, SharingStarted.WhileSubscribed(), activeRepo.value.primaryLevel.value)
 
+    override val satelliteLevel: StateFlow<Int> =
+        activeRepo
+            .flatMapLatest { it.satelliteLevel }
+            .logDiffsForTable(
+                tableLogBuffer,
+                columnPrefix = "",
+                columnName = COL_SATELLITE_LEVEL,
+                activeRepo.value.satelliteLevel.value,
+            )
+            .stateIn(scope, SharingStarted.WhileSubscribed(), activeRepo.value.satelliteLevel.value)
+
     override val dataConnectionState =
         activeRepo
             .flatMapLatest { it.dataConnectionState }
             .logDiffsForTable(
                 tableLogBuffer,
                 columnPrefix = "",
-                activeRepo.value.dataConnectionState.value
+                activeRepo.value.dataConnectionState.value,
             )
             .stateIn(
                 scope,
                 SharingStarted.WhileSubscribed(),
-                activeRepo.value.dataConnectionState.value
+                activeRepo.value.dataConnectionState.value,
             )
 
     override val dataActivityDirection =
@@ -245,12 +256,12 @@
             .logDiffsForTable(
                 tableLogBuffer,
                 columnPrefix = "",
-                activeRepo.value.dataActivityDirection.value
+                activeRepo.value.dataActivityDirection.value,
             )
             .stateIn(
                 scope,
                 SharingStarted.WhileSubscribed(),
-                activeRepo.value.dataActivityDirection.value
+                activeRepo.value.dataActivityDirection.value,
             )
 
     override val carrierNetworkChangeActive =
@@ -260,12 +271,12 @@
                 tableLogBuffer,
                 columnPrefix = "",
                 columnName = COL_CARRIER_NETWORK_CHANGE,
-                activeRepo.value.carrierNetworkChangeActive.value
+                activeRepo.value.carrierNetworkChangeActive.value,
             )
             .stateIn(
                 scope,
                 SharingStarted.WhileSubscribed(),
-                activeRepo.value.carrierNetworkChangeActive.value
+                activeRepo.value.carrierNetworkChangeActive.value,
             )
 
     override val resolvedNetworkType =
@@ -274,12 +285,12 @@
             .logDiffsForTable(
                 tableLogBuffer,
                 columnPrefix = "",
-                activeRepo.value.resolvedNetworkType.value
+                activeRepo.value.resolvedNetworkType.value,
             )
             .stateIn(
                 scope,
                 SharingStarted.WhileSubscribed(),
-                activeRepo.value.resolvedNetworkType.value
+                activeRepo.value.resolvedNetworkType.value,
             )
 
     override val dataEnabled =
@@ -305,7 +316,7 @@
             .stateIn(
                 scope,
                 SharingStarted.WhileSubscribed(),
-                activeRepo.value.inflateSignalStrength.value
+                activeRepo.value.inflateSignalStrength.value,
             )
 
     override val allowNetworkSliceIndicator =
@@ -320,7 +331,7 @@
             .stateIn(
                 scope,
                 SharingStarted.WhileSubscribed(),
-                activeRepo.value.allowNetworkSliceIndicator.value
+                activeRepo.value.allowNetworkSliceIndicator.value,
             )
 
     override val numberOfLevels =
@@ -439,6 +450,7 @@
         const val COL_IS_IN_SERVICE = "isInService"
         const val COL_OPERATOR = "operatorName"
         const val COL_PRIMARY_LEVEL = "primaryLevel"
+        const val COL_SATELLITE_LEVEL = "satelliteLevel"
         const val COL_ROAMING = "roaming"
     }
 }
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 62bd8ad..8a1e7f9 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
@@ -41,6 +41,7 @@
 import android.telephony.TelephonyManager.EXTRA_SUBSCRIPTION_ID
 import android.telephony.TelephonyManager.NETWORK_TYPE_UNKNOWN
 import android.telephony.TelephonyManager.UNKNOWN_CARRIER_ID
+import android.telephony.satellite.NtnSignalStrength
 import com.android.settingslib.Utils
 import com.android.systemui.broadcast.BroadcastDispatcher
 import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
@@ -173,7 +174,7 @@
 
                         override fun onDataConnectionStateChanged(
                             dataState: Int,
-                            networkType: Int
+                            networkType: Int,
                         ) {
                             logger.logOnDataConnectionStateChanged(dataState, networkType, subId)
                             trySend(CallbackEvent.OnDataConnectionStateChanged(dataState))
@@ -195,6 +196,17 @@
                             logger.logOnSignalStrengthsChanged(signalStrength, subId)
                             trySend(CallbackEvent.OnSignalStrengthChanged(signalStrength))
                         }
+
+                        override fun onCarrierRoamingNtnSignalStrengthChanged(
+                            signalStrength: NtnSignalStrength
+                        ) {
+                            logger.logNtnSignalStrengthChanged(signalStrength)
+                            trySend(
+                                CallbackEvent.OnCarrierRoamingNtnSignalStrengthChanged(
+                                    signalStrength
+                                )
+                            )
+                        }
                     }
                 telephonyManager.registerTelephonyCallback(bgDispatcher.asExecutor(), callback)
                 awaitClose { telephonyManager.unregisterTelephonyCallback(callback) }
@@ -267,6 +279,12 @@
             .map { it.signalStrength.level }
             .stateIn(scope, SharingStarted.WhileSubscribed(), SIGNAL_STRENGTH_NONE_OR_UNKNOWN)
 
+    override val satelliteLevel: StateFlow<Int> =
+        callbackEvents
+            .mapNotNull { it.onCarrierRoamingNtnSignalStrengthChanged }
+            .map { it.signalStrength.level }
+            .stateIn(scope, SharingStarted.WhileSubscribed(), 0)
+
     override val dataConnectionState =
         callbackEvents
             .mapNotNull { it.onDataConnectionStateChanged }
@@ -280,7 +298,7 @@
             .stateIn(
                 scope,
                 SharingStarted.WhileSubscribed(),
-                DataActivityModel(hasActivityIn = false, hasActivityOut = false)
+                DataActivityModel(hasActivityIn = false, hasActivityOut = false),
             )
 
     override val carrierNetworkChangeActive =
@@ -385,7 +403,7 @@
                             if (
                                 intent.getIntExtra(
                                     EXTRA_SUBSCRIPTION_INDEX,
-                                    INVALID_SUBSCRIPTION_ID
+                                    INVALID_SUBSCRIPTION_ID,
                                 ) == subId
                             ) {
                                 logger.logServiceProvidersUpdatedBroadcast(intent)
@@ -399,7 +417,7 @@
 
                 context.registerReceiver(
                     receiver,
-                    IntentFilter(TelephonyManager.ACTION_SERVICE_PROVIDERS_UPDATED)
+                    IntentFilter(TelephonyManager.ACTION_SERVICE_PROVIDERS_UPDATED),
                 )
 
                 awaitClose { context.unregisterReceiver(receiver) }
@@ -524,6 +542,9 @@
     data class OnServiceStateChanged(val serviceState: ServiceState) : CallbackEvent
 
     data class OnSignalStrengthChanged(val signalStrength: SignalStrength) : CallbackEvent
+
+    data class OnCarrierRoamingNtnSignalStrengthChanged(val signalStrength: NtnSignalStrength) :
+        CallbackEvent
 }
 
 /**
@@ -539,6 +560,9 @@
     val onDisplayInfoChanged: CallbackEvent.OnDisplayInfoChanged? = null,
     val onServiceStateChanged: CallbackEvent.OnServiceStateChanged? = null,
     val onSignalStrengthChanged: CallbackEvent.OnSignalStrengthChanged? = null,
+    val onCarrierRoamingNtnSignalStrengthChanged:
+        CallbackEvent.OnCarrierRoamingNtnSignalStrengthChanged? =
+        null,
 ) {
     fun applyEvent(event: CallbackEvent): TelephonyCallbackState {
         return when (event) {
@@ -555,6 +579,8 @@
                 copy(onServiceStateChanged = event)
             }
             is CallbackEvent.OnSignalStrengthChanged -> copy(onSignalStrengthChanged = event)
+            is CallbackEvent.OnCarrierRoamingNtnSignalStrengthChanged ->
+                copy(onCarrierRoamingNtnSignalStrengthChanged = event)
         }
     }
 }
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 4ef328c..1bf14af 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
@@ -335,7 +335,11 @@
     // Satellite level is unaffected by the inflateSignalStrength property
     // See b/346904529 for details
     private val satelliteShownLevel: StateFlow<Int> =
-        combine(level, isInService) { level, isInService -> if (isInService) level else 0 }
+        if (Flags.carrierRoamingNbIotNtn()) {
+                connectionRepository.satelliteLevel
+            } else {
+                combine(level, isInService) { level, isInService -> if (isInService) level else 0 }
+            }
             .stateIn(scope, SharingStarted.WhileSubscribed(), 0)
 
     private val cellularIcon: Flow<SignalIconModel.Cellular> =
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt
index c3c3cce..dae66d4 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt
@@ -41,6 +41,7 @@
     override val isGsm = MutableStateFlow(false)
     override val cdmaLevel = MutableStateFlow(0)
     override val primaryLevel = MutableStateFlow(0)
+    override val satelliteLevel = MutableStateFlow(0)
     override val dataConnectionState = MutableStateFlow(DataConnectionState.Disconnected)
     override val dataActivityDirection =
         MutableStateFlow(DataActivityModel(hasActivityIn = false, hasActivityOut = false))