Merge "[SB][Sat] Update carrier text to display device-based satellite info." into main
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 27aa15f..b8e78a4 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1718,6 +1718,11 @@
<!-- Accessibility label for available satellite connection [CHAR LIMIT=NONE] -->
<string name="accessibility_status_bar_satellite_available">Satellite, connection available</string>
+ <!-- Text displayed indicating that the user is connected to a satellite signal. -->
+ <string name="satellite_connected_carrier_text">Connected to satellite</string>
+ <!-- Text displayed indicating that the user is not connected to a satellite signal. -->
+ <string name="satellite_not_connected_carrier_text">Not connected to satellite</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/keyguard/CarrierTextManager.java b/packages/SystemUI/src/com/android/keyguard/CarrierTextManager.java
index b9b8fbe..5647b0b 100644
--- a/packages/SystemUI/src/com/android/keyguard/CarrierTextManager.java
+++ b/packages/SystemUI/src/com/android/keyguard/CarrierTextManager.java
@@ -19,6 +19,7 @@
import static com.android.keyguard.logging.CarrierTextManagerLogger.REASON_ACTIVE_DATA_SUB_CHANGED;
import static com.android.keyguard.logging.CarrierTextManagerLogger.REASON_ON_TELEPHONY_CAPABLE;
import static com.android.keyguard.logging.CarrierTextManagerLogger.REASON_REFRESH_CARRIER_INFO;
+import static com.android.keyguard.logging.CarrierTextManagerLogger.REASON_SATELLITE_CHANGED;
import static com.android.keyguard.logging.CarrierTextManagerLogger.REASON_SIM_ERROR_STATE_CHANGED;
import android.content.Context;
@@ -43,17 +44,22 @@
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.res.R;
+import com.android.systemui.statusbar.pipeline.satellite.ui.viewmodel.DeviceBasedSatelliteViewModel;
import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository;
import com.android.systemui.telephony.TelephonyListenerManager;
+import com.android.systemui.util.kotlin.JavaAdapter;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
+import java.util.concurrent.CancellationException;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.inject.Inject;
+import kotlinx.coroutines.Job;
+
/**
* Controller that generates text including the carrier names and/or the status of all the SIM
* interfaces in the device. Through a callback, the updates can be retrieved either as a list or
@@ -77,10 +83,17 @@
protected KeyguardUpdateMonitor mKeyguardUpdateMonitor;
private final CarrierTextManagerLogger mLogger;
private final WifiRepository mWifiRepository;
+ private final DeviceBasedSatelliteViewModel mDeviceBasedSatelliteViewModel;
+ private final JavaAdapter mJavaAdapter;
private final boolean[] mSimErrorState;
private final int mSimSlotsNumber;
@Nullable // Check for nullability before dispatching
private CarrierTextCallback mCarrierTextCallback;
+ @Nullable
+ private Job mSatelliteConnectionJob;
+
+ @Nullable private String mSatelliteCarrierText;
+
private final Context mContext;
private final TelephonyManager mTelephonyManager;
private final CharSequence mSeparator;
@@ -178,6 +191,8 @@
boolean showAirplaneMode,
boolean showMissingSim,
WifiRepository wifiRepository,
+ DeviceBasedSatelliteViewModel deviceBasedSatelliteViewModel,
+ JavaAdapter javaAdapter,
TelephonyManager telephonyManager,
TelephonyListenerManager telephonyListenerManager,
WakefulnessLifecycle wakefulnessLifecycle,
@@ -192,6 +207,8 @@
mShowAirplaneMode = showAirplaneMode;
mShowMissingSim = showMissingSim;
mWifiRepository = wifiRepository;
+ mDeviceBasedSatelliteViewModel = deviceBasedSatelliteViewModel;
+ mJavaAdapter = javaAdapter;
mTelephonyManager = telephonyManager;
mSeparator = separator;
mTelephonyListenerManager = telephonyListenerManager;
@@ -282,6 +299,11 @@
mWakefulnessLifecycle.addObserver(mWakefulnessObserver);
});
mTelephonyListenerManager.addActiveDataSubscriptionIdListener(mPhoneStateListener);
+ cancelSatelliteCollectionJob(/* reason= */ "Starting new job");
+ mSatelliteConnectionJob =
+ mJavaAdapter.alwaysCollectFlow(
+ mDeviceBasedSatelliteViewModel.getCarrierText(),
+ this::onSatelliteCarrierTextChanged);
} else {
// Don't listen and clear out the text when the device isn't a phone.
mMainExecutor.execute(() -> callback.updateCarrierInfo(
@@ -294,6 +316,7 @@
mWakefulnessLifecycle.removeObserver(mWakefulnessObserver);
});
mTelephonyListenerManager.removeActiveDataSubscriptionIdListener(mPhoneStateListener);
+ cancelSatelliteCollectionJob(/* reason= */ "Stopping listening");
}
}
@@ -311,6 +334,12 @@
return mKeyguardUpdateMonitor.getFilteredSubscriptionInfo();
}
+ private void onSatelliteCarrierTextChanged(@Nullable String text) {
+ mLogger.logUpdateCarrierTextForReason(REASON_SATELLITE_CHANGED);
+ mSatelliteCarrierText = text;
+ updateCarrierText();
+ }
+
protected void updateCarrierText() {
Trace.beginSection("CarrierTextManager#updateCarrierText");
boolean allSimsMissing = true;
@@ -411,6 +440,12 @@
airplaneMode = true;
}
+ String currentSatelliteText = mSatelliteCarrierText;
+ if (currentSatelliteText != null) {
+ mLogger.logUsingSatelliteText(currentSatelliteText);
+ displayText = currentSatelliteText;
+ }
+
final CarrierTextCallbackInfo info = new CarrierTextCallbackInfo(
displayText,
carrierNames,
@@ -616,11 +651,20 @@
return list;
}
+ private void cancelSatelliteCollectionJob(String reason) {
+ Job job = mSatelliteConnectionJob;
+ if (job != null) {
+ job.cancel(new CancellationException(reason));
+ }
+ }
+
/** Injectable Buildeer for {@#link CarrierTextManager}. */
public static class Builder {
private final Context mContext;
private final String mSeparator;
private final WifiRepository mWifiRepository;
+ private final DeviceBasedSatelliteViewModel mDeviceBasedSatelliteViewModel;
+ private final JavaAdapter mJavaAdapter;
private final TelephonyManager mTelephonyManager;
private final TelephonyListenerManager mTelephonyListenerManager;
private final WakefulnessLifecycle mWakefulnessLifecycle;
@@ -637,6 +681,8 @@
Context context,
@Main Resources resources,
@Nullable WifiRepository wifiRepository,
+ DeviceBasedSatelliteViewModel deviceBasedSatelliteViewModel,
+ JavaAdapter javaAdapter,
TelephonyManager telephonyManager,
TelephonyListenerManager telephonyListenerManager,
WakefulnessLifecycle wakefulnessLifecycle,
@@ -648,6 +694,8 @@
mSeparator = resources.getString(
com.android.internal.R.string.kg_text_message_separator);
mWifiRepository = wifiRepository;
+ mDeviceBasedSatelliteViewModel = deviceBasedSatelliteViewModel;
+ mJavaAdapter = javaAdapter;
mTelephonyManager = telephonyManager;
mTelephonyListenerManager = telephonyListenerManager;
mWakefulnessLifecycle = wakefulnessLifecycle;
@@ -682,9 +730,20 @@
public CarrierTextManager build() {
mLogger.setLocation(mDebugLocation);
return new CarrierTextManager(
- mContext, mSeparator, mShowAirplaneMode, mShowMissingSim, mWifiRepository,
- mTelephonyManager, mTelephonyListenerManager, mWakefulnessLifecycle,
- mMainExecutor, mBgExecutor, mKeyguardUpdateMonitor, mLogger);
+ mContext,
+ mSeparator,
+ mShowAirplaneMode,
+ mShowMissingSim,
+ mWifiRepository,
+ mDeviceBasedSatelliteViewModel,
+ mJavaAdapter,
+ mTelephonyManager,
+ mTelephonyListenerManager,
+ mWakefulnessLifecycle,
+ mMainExecutor,
+ mBgExecutor,
+ mKeyguardUpdateMonitor,
+ mLogger);
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/logging/CarrierTextManagerLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/CarrierTextManagerLogger.kt
index cb474d3..48fea55 100644
--- a/packages/SystemUI/src/com/android/keyguard/logging/CarrierTextManagerLogger.kt
+++ b/packages/SystemUI/src/com/android/keyguard/logging/CarrierTextManagerLogger.kt
@@ -77,6 +77,15 @@
)
}
+ fun logUsingSatelliteText(satelliteText: String) {
+ buffer.log(
+ TAG,
+ LogLevel.VERBOSE,
+ { str1 = satelliteText },
+ { "┣ updateCarrierText: using satellite text. text=$str1" },
+ )
+ }
+
/** De-structures the info object so that we don't have to generate new strings */
fun logCallbackSentFromUpdate(info: CarrierTextCallbackInfo) {
buffer.log(
@@ -129,6 +138,7 @@
const val REASON_ON_TELEPHONY_CAPABLE = 2
const val REASON_SIM_ERROR_STATE_CHANGED = 3
const val REASON_ACTIVE_DATA_SUB_CHANGED = 4
+ const val REASON_SATELLITE_CHANGED = 5
@Retention(AnnotationRetention.SOURCE)
@IntDef(
@@ -138,6 +148,7 @@
REASON_ON_TELEPHONY_CAPABLE,
REASON_SIM_ERROR_STATE_CHANGED,
REASON_ACTIVE_DATA_SUB_CHANGED,
+ REASON_SATELLITE_CHANGED,
]
)
annotation class CarrierTextRefreshReason
@@ -148,6 +159,7 @@
REASON_ON_TELEPHONY_CAPABLE -> "ON_TELEPHONY_CAPABLE"
REASON_SIM_ERROR_STATE_CHANGED -> "SIM_ERROR_STATE_CHANGED"
REASON_ACTIVE_DATA_SUB_CHANGED -> "ACTIVE_DATA_SUB_CHANGED"
+ REASON_SATELLITE_CHANGED -> "SATELLITE_CHANGED"
else -> "unknown"
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
index 4129b3a..b80ff38 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
@@ -42,6 +42,8 @@
import com.android.systemui.statusbar.pipeline.mobile.util.SubscriptionManagerProxyImpl
import com.android.systemui.statusbar.pipeline.satellite.data.DeviceBasedSatelliteRepository
import com.android.systemui.statusbar.pipeline.satellite.data.prod.DeviceBasedSatelliteRepositoryImpl
+import com.android.systemui.statusbar.pipeline.satellite.ui.viewmodel.DeviceBasedSatelliteViewModel
+import com.android.systemui.statusbar.pipeline.satellite.ui.viewmodel.DeviceBasedSatelliteViewModelImpl
import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepository
import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepositoryImpl
import com.android.systemui.statusbar.pipeline.shared.ui.binder.CollapsedStatusBarViewBinder
@@ -85,6 +87,11 @@
impl: DeviceBasedSatelliteRepositoryImpl
): DeviceBasedSatelliteRepository
+ @Binds
+ abstract fun deviceBasedSatelliteViewModel(
+ impl: DeviceBasedSatelliteViewModelImpl
+ ): DeviceBasedSatelliteViewModel
+
@Binds abstract fun wifiRepository(impl: WifiRepositorySwitcher): WifiRepository
@Binds abstract fun wifiInteractor(impl: WifiInteractorImpl): WifiInteractor
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 a0291b8..f2255f3 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
@@ -16,13 +16,16 @@
package com.android.systemui.statusbar.pipeline.satellite.ui.viewmodel
+import android.content.Context
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.core.LogLevel
+import com.android.systemui.res.R
import com.android.systemui.statusbar.pipeline.airplane.data.repository.AirplaneModeRepository
import com.android.systemui.statusbar.pipeline.dagger.OemSatelliteInputLog
import com.android.systemui.statusbar.pipeline.satellite.domain.interactor.DeviceBasedSatelliteInteractor
+import com.android.systemui.statusbar.pipeline.satellite.shared.model.SatelliteConnectionState
import com.android.systemui.statusbar.pipeline.satellite.ui.model.SatelliteIconModel
import javax.inject.Inject
import kotlin.time.Duration.Companion.seconds
@@ -42,15 +45,30 @@
* View-Model for the device-based satellite icon. This icon will only show in the status bar if
* satellite is available AND all other service states are considered OOS.
*/
+interface DeviceBasedSatelliteViewModel {
+ /**
+ * The satellite icon that should be displayed, or null if no satellite icon should be
+ * displayed.
+ */
+ val icon: StateFlow<Icon?>
+
+ /**
+ * The satellite-related text that should be used as the carrier text string when satellite is
+ * active, or null if the carrier text string shouldn't include any satellite information.
+ */
+ val carrierText: StateFlow<String?>
+}
+
@OptIn(ExperimentalCoroutinesApi::class)
-class DeviceBasedSatelliteViewModel
+class DeviceBasedSatelliteViewModelImpl
@Inject
constructor(
+ context: Context,
interactor: DeviceBasedSatelliteInteractor,
@Application scope: CoroutineScope,
airplaneModeRepository: AirplaneModeRepository,
@OemSatelliteInputLog logBuffer: LogBuffer,
-) {
+) : DeviceBasedSatelliteViewModel {
private val shouldShowIcon: Flow<Boolean> =
interactor.areAllConnectionsOutOfService.flatMapLatest { allOos ->
if (!allOos) {
@@ -87,7 +105,7 @@
}
.stateIn(scope, SharingStarted.WhileSubscribed(), false)
- val icon: StateFlow<Icon?> =
+ override val icon: StateFlow<Icon?> =
combine(
shouldActuallyShowIcon,
interactor.connectionState,
@@ -101,6 +119,26 @@
}
.stateIn(scope, SharingStarted.WhileSubscribed(), null)
+ override val carrierText: StateFlow<String?> =
+ combine(
+ shouldActuallyShowIcon,
+ interactor.connectionState,
+ ) { shouldShow, connectionState ->
+ if (shouldShow) {
+ when (connectionState) {
+ SatelliteConnectionState.Connected ->
+ context.getString(R.string.satellite_connected_carrier_text)
+ SatelliteConnectionState.On ->
+ context.getString(R.string.satellite_not_connected_carrier_text)
+ SatelliteConnectionState.Off,
+ SatelliteConnectionState.Unknown -> null
+ }
+ } else {
+ null
+ }
+ }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), null)
+
companion object {
private const val TAG = "DeviceBasedSatelliteViewModel"
private val DELAY_DURATION = 10.seconds
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextManagerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextManagerTest.java
index 303ae97..11d31c6 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextManagerTest.java
@@ -58,12 +58,15 @@
import com.android.keyguard.logging.CarrierTextManagerLogger;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.keyguard.WakefulnessLifecycle;
+import com.android.systemui.kosmos.KosmosJavaAdapter;
import com.android.systemui.log.LogBufferHelperKt;
import com.android.systemui.res.R;
+import com.android.systemui.statusbar.pipeline.satellite.ui.viewmodel.FakeDeviceBasedSatelliteViewModel;
import com.android.systemui.statusbar.pipeline.wifi.data.repository.FakeWifiRepository;
import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel;
import com.android.systemui.telephony.TelephonyListenerManager;
import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.kotlin.JavaAdapter;
import com.android.systemui.util.time.FakeSystemClock;
import org.junit.Before;
@@ -78,6 +81,8 @@
import java.util.HashMap;
import java.util.List;
+import kotlinx.coroutines.test.TestScope;
+
@SmallTest
@RunWith(AndroidTestingRunner.class)
public class CarrierTextManagerTest extends SysuiTestCase {
@@ -99,6 +104,8 @@
TEST_CARRIER, TEST_CARRIER, NAME_SOURCE_CARRIER_ID, 0xFFFFFF, "",
DATA_ROAMING_ENABLE, null, null, null, null, false, null, "");
private FakeWifiRepository mWifiRepository = new FakeWifiRepository();
+ private final FakeDeviceBasedSatelliteViewModel mSatelliteViewModel =
+ new FakeDeviceBasedSatelliteViewModel();
@Mock
private WakefulnessLifecycle mWakefulnessLifecycle;
@Mock
@@ -124,6 +131,10 @@
new CarrierTextManagerLogger(
LogBufferHelperKt.logcatLogBuffer("CarrierTextManagerLog"));
+ private final KosmosJavaAdapter mKosmos = new KosmosJavaAdapter(this);
+ private final TestScope mTestScope = mKosmos.getTestScope();
+ private final JavaAdapter mJavaAdapter = new JavaAdapter(mTestScope.getBackgroundScope());
+
private Void checkMainThread(InvocationOnMock inv) {
assertThat(mMainExecutor.isExecuting()).isTrue();
assertThat(mBgExecutor.isExecuting()).isFalse();
@@ -156,9 +167,18 @@
when(mTelephonyManager.getActiveModemCount()).thenReturn(3);
mCarrierTextManager = new CarrierTextManager.Builder(
- mContext, mContext.getResources(), mWifiRepository,
- mTelephonyManager, mTelephonyListenerManager, mWakefulnessLifecycle, mMainExecutor,
- mBgExecutor, mKeyguardUpdateMonitor, mLogger)
+ mContext,
+ mContext.getResources(),
+ mWifiRepository,
+ mSatelliteViewModel,
+ mJavaAdapter,
+ mTelephonyManager,
+ mTelephonyListenerManager,
+ mWakefulnessLifecycle,
+ mMainExecutor,
+ mBgExecutor,
+ mKeyguardUpdateMonitor,
+ mLogger)
.setShowAirplaneMode(true)
.setShowMissingSim(true)
.build();
@@ -211,6 +231,8 @@
getContextSpyForStickyBroadcast(stickyIntent),
mContext.getResources(),
mWifiRepository,
+ mSatelliteViewModel,
+ mJavaAdapter,
mTelephonyManager,
mTelephonyListenerManager,
mWakefulnessLifecycle,
@@ -451,6 +473,107 @@
}
@Test
+ public void carrierText_satelliteTextNull_notUsed() {
+ reset(mCarrierTextCallback);
+ List<SubscriptionInfo> list = new ArrayList<>();
+ list.add(TEST_SUBSCRIPTION);
+ when(mKeyguardUpdateMonitor.getSimState(anyInt())).thenReturn(
+ TelephonyManager.SIM_STATE_READY);
+ when(mKeyguardUpdateMonitor.getFilteredSubscriptionInfo()).thenReturn(list);
+ mKeyguardUpdateMonitor.mServiceStates = new HashMap<>();
+
+ // WHEN the satellite text is null
+ mSatelliteViewModel.getCarrierText().setValue(null);
+ mTestScope.getTestScheduler().runCurrent();
+
+ ArgumentCaptor<CarrierTextManager.CarrierTextCallbackInfo> captor =
+ ArgumentCaptor.forClass(
+ CarrierTextManager.CarrierTextCallbackInfo.class);
+ FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor);
+
+ // THEN the default subscription carrier text is used
+ verify(mCarrierTextCallback).updateCarrierInfo(captor.capture());
+ assertThat(captor.getValue().carrierText).isEqualTo(TEST_CARRIER);
+ }
+
+ @Test
+ public void carrierText_satelliteTextUpdates_autoTriggersCallback() {
+ reset(mCarrierTextCallback);
+ List<SubscriptionInfo> list = new ArrayList<>();
+ list.add(TEST_SUBSCRIPTION);
+ when(mKeyguardUpdateMonitor.getSimState(anyInt())).thenReturn(
+ TelephonyManager.SIM_STATE_READY);
+ when(mKeyguardUpdateMonitor.getFilteredSubscriptionInfo()).thenReturn(list);
+ mKeyguardUpdateMonitor.mServiceStates = new HashMap<>();
+
+ // WHEN the satellite text is set
+ mSatelliteViewModel.getCarrierText().setValue("Test satellite text");
+ mTestScope.getTestScheduler().runCurrent();
+
+ // THEN we should automatically re-trigger #updateCarrierText and get callback info
+ ArgumentCaptor<CarrierTextManager.CarrierTextCallbackInfo> captor =
+ ArgumentCaptor.forClass(
+ CarrierTextManager.CarrierTextCallbackInfo.class);
+ FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor);
+ verify(mCarrierTextCallback).updateCarrierInfo(captor.capture());
+ // AND use the satellite text as the carrier text
+ assertThat(captor.getValue().carrierText).isEqualTo("Test satellite text");
+
+ // WHEN the satellite text is reset to null
+ reset(mCarrierTextCallback);
+ mSatelliteViewModel.getCarrierText().setValue(null);
+ mTestScope.getTestScheduler().runCurrent();
+
+ // THEN we should automatically re-trigger #updateCarrierText and get callback info
+ // that doesn't include the satellite info
+ FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor);
+ verify(mCarrierTextCallback).updateCarrierInfo(captor.capture());
+ assertThat(captor.getValue().carrierText).isEqualTo(TEST_CARRIER);
+ }
+
+ @Test
+ public void carrierText_updatedWhileNotListening_getsNewValueWhenListening() {
+ reset(mCarrierTextCallback);
+ List<SubscriptionInfo> list = new ArrayList<>();
+ list.add(TEST_SUBSCRIPTION);
+ when(mKeyguardUpdateMonitor.getSimState(anyInt())).thenReturn(
+ TelephonyManager.SIM_STATE_READY);
+ when(mKeyguardUpdateMonitor.getFilteredSubscriptionInfo()).thenReturn(list);
+ mKeyguardUpdateMonitor.mServiceStates = new HashMap<>();
+
+ mSatelliteViewModel.getCarrierText().setValue("Old satellite text");
+ mTestScope.getTestScheduler().runCurrent();
+
+ ArgumentCaptor<CarrierTextManager.CarrierTextCallbackInfo> captor =
+ ArgumentCaptor.forClass(
+ CarrierTextManager.CarrierTextCallbackInfo.class);
+ FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor);
+ verify(mCarrierTextCallback).updateCarrierInfo(captor.capture());
+ assertThat(captor.getValue().carrierText).isEqualTo("Old satellite text");
+
+ // WHEN we stop listening
+ reset(mCarrierTextCallback);
+ mCarrierTextManager.setListening(null);
+
+ // AND the satellite text updates
+ mSatelliteViewModel.getCarrierText().setValue("New satellite text");
+
+ // THEN we don't get new callback info because we aren't listening
+ verify(mCarrierTextCallback, never()).updateCarrierInfo(any());
+
+ // WHEN we start listening again
+ reset(mCarrierTextCallback);
+ mCarrierTextManager.setListening(mCarrierTextCallback);
+
+ // THEN we should automatically re-trigger #updateCarrierText and get callback info
+ // that includes the new satellite text
+ mTestScope.getTestScheduler().runCurrent();
+ FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor);
+ verify(mCarrierTextCallback).updateCarrierInfo(captor.capture());
+ assertThat(captor.getValue().carrierText).isEqualTo("New satellite text");
+ }
+
+ @Test
public void testCreateInfo_noSubscriptions() {
reset(mCarrierTextCallback);
when(mKeyguardUpdateMonitor.getFilteredSubscriptionInfo()).thenReturn(
@@ -471,6 +594,28 @@
}
@Test
+ public void testCarrierText_oneValidSubscription() {
+ reset(mCarrierTextCallback);
+ List<SubscriptionInfo> list = new ArrayList<>();
+ list.add(TEST_SUBSCRIPTION);
+ when(mKeyguardUpdateMonitor.getSimState(anyInt())).thenReturn(
+ TelephonyManager.SIM_STATE_READY);
+ when(mKeyguardUpdateMonitor.getFilteredSubscriptionInfo()).thenReturn(list);
+
+ mKeyguardUpdateMonitor.mServiceStates = new HashMap<>();
+
+ ArgumentCaptor<CarrierTextManager.CarrierTextCallbackInfo> captor =
+ ArgumentCaptor.forClass(
+ CarrierTextManager.CarrierTextCallbackInfo.class);
+
+ mCarrierTextManager.updateCarrierText();
+ FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor);
+ verify(mCarrierTextCallback).updateCarrierInfo(captor.capture());
+
+ assertThat(captor.getValue().carrierText).isEqualTo(TEST_CARRIER);
+ }
+
+ @Test
public void testCarrierText_twoValidSubscriptions() {
reset(mCarrierTextCallback);
List<SubscriptionInfo> list = new ArrayList<>();
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 64f19b6..8eea29b 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
@@ -21,11 +21,13 @@
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.log.core.FakeLogBuffer
+import com.android.systemui.res.R
import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconsInteractor
import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
import com.android.systemui.statusbar.pipeline.satellite.data.prod.FakeDeviceBasedSatelliteRepository
import com.android.systemui.statusbar.pipeline.satellite.domain.interactor.DeviceBasedSatelliteInteractor
+import com.android.systemui.statusbar.pipeline.satellite.shared.model.SatelliteConnectionState
import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
import com.android.systemui.statusbar.pipeline.wifi.data.repository.FakeWifiRepository
import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractorImpl
@@ -76,7 +78,8 @@
)
underTest =
- DeviceBasedSatelliteViewModel(
+ DeviceBasedSatelliteViewModelImpl(
+ context,
interactor,
testScope.backgroundScope,
airplaneModeRepository,
@@ -124,6 +127,7 @@
assertThat(latest).isNull()
}
+ @OptIn(ExperimentalCoroutinesApi::class)
@Test
fun icon_nullWhenShouldNotShow_isEmergencyOnly() =
testScope.runTest {
@@ -298,4 +302,313 @@
// THEN icon is set because the device lost wifi connection
assertThat(latest).isInstanceOf(Icon::class.java)
}
+
+ @Test
+ fun carrierText_nullWhenShouldNotShow_satelliteNotAllowed() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.carrierText)
+
+ // 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 apm is disabled
+ airplaneModeRepository.setIsAirplaneMode(false)
+
+ // THEN carrier text is null because we should not be showing it
+ assertThat(latest).isNull()
+ }
+
+ @Test
+ fun carrierText_nullWhenShouldNotShow_notAllOos() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.carrierText)
+
+ // GIVEN satellite is allowed + connected
+ repo.isSatelliteAllowedForCurrentLocation.value = true
+ repo.connectionState.value = SatelliteConnectionState.Connected
+
+ // GIVEN all icons are not OOS
+ val i1 = mobileIconsInteractor.getMobileConnectionInteractorForSubId(1)
+ i1.isInService.value = true
+ i1.isEmergencyOnly.value = false
+
+ // GIVEN apm is disabled
+ airplaneModeRepository.setIsAirplaneMode(false)
+
+ // THEN carrier text is null because we have service
+ assertThat(latest).isNull()
+ }
+
+ @OptIn(ExperimentalCoroutinesApi::class)
+ @Test
+ fun carrierText_nullWhenShouldNotShow_isEmergencyOnly() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.carrierText)
+
+ // GIVEN satellite is allowed + connected
+ repo.isSatelliteAllowedForCurrentLocation.value = true
+ repo.connectionState.value = SatelliteConnectionState.Connected
+
+ // 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)
+
+ // Wait for delay to be completed
+ advanceTimeBy(10.seconds)
+
+ // THEN carrier text is set because we don't have service
+ 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
+ fun carrierText_nullWhenShouldNotShow_apmIsEnabled() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.carrierText)
+
+ // GIVEN satellite is allowed + connected
+ repo.isSatelliteAllowedForCurrentLocation.value = true
+ repo.connectionState.value = SatelliteConnectionState.Connected
+
+ // GIVEN all icons are OOS
+ val i1 = mobileIconsInteractor.getMobileConnectionInteractorForSubId(1)
+ i1.isInService.value = false
+ i1.isEmergencyOnly.value = false
+
+ // GIVEN apm is enabled
+ airplaneModeRepository.setIsAirplaneMode(true)
+
+ // THEN carrier text is null because we should not be showing it
+ assertThat(latest).isNull()
+ }
+
+ @OptIn(ExperimentalCoroutinesApi::class)
+ @Test
+ fun carrierText_satelliteIsOn() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.carrierText)
+
+ // GIVEN satellite is allowed + connected
+ repo.isSatelliteAllowedForCurrentLocation.value = true
+ repo.connectionState.value = SatelliteConnectionState.Connected
+
+ // 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)
+
+ // Wait for delay to be completed
+ advanceTimeBy(10.seconds)
+
+ // THEN carrier text is set because we don't have service
+ assertThat(latest).isNotNull()
+ }
+
+ @OptIn(ExperimentalCoroutinesApi::class)
+ @Test
+ fun carrierText_hysteresisWhenEnablingText() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.carrierText)
+
+ // GIVEN satellite is allowed + connected
+ repo.isSatelliteAllowedForCurrentLocation.value = true
+ repo.connectionState.value = SatelliteConnectionState.Connected
+
+ // 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 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
+ 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 {
+ val latest by collectLastValue(underTest.carrierText)
+
+ // GIVEN satellite is allowed + connected
+ repo.isSatelliteAllowedForCurrentLocation.value = true
+ repo.connectionState.value = SatelliteConnectionState.Connected
+
+ // 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 device is not provisioned
+ deviceProvisionedRepository.setDeviceProvisioned(false)
+
+ // THEN carrier text is null because the device is not provisioned
+ assertThat(latest).isNull()
+
+ // GIVEN device becomes provisioned
+ deviceProvisionedRepository.setDeviceProvisioned(true)
+
+ // Wait for delay to be completed
+ advanceTimeBy(10.seconds)
+
+ // THEN carrier text is null because the device is not provisioned
+ assertThat(latest).isNotNull()
+ }
+
+ @OptIn(ExperimentalCoroutinesApi::class)
+ @Test
+ fun carrierText_wifiIsActive() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.carrierText)
+
+ // GIVEN satellite is allowed + connected
+ repo.isSatelliteAllowedForCurrentLocation.value = true
+ repo.connectionState.value = SatelliteConnectionState.Connected
+
+ // 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 device is provisioned
+ deviceProvisionedRepository.setDeviceProvisioned(true)
+
+ // GIVEN wifi network is active
+ wifiRepository.setWifiNetwork(WifiNetworkModel.Active(networkId = 0, level = 1))
+
+ // THEN carrier text is null because the device is connected to wifi
+ assertThat(latest).isNull()
+
+ // GIVEN device loses wifi connection
+ wifiRepository.setWifiNetwork(WifiNetworkModel.Invalid("test"))
+
+ // Wait for delay to be completed
+ advanceTimeBy(10.seconds)
+
+ // THEN carrier text is set because the device lost wifi connection
+ assertThat(latest).isNotNull()
+ }
+
+ @OptIn(ExperimentalCoroutinesApi::class)
+ @Test
+ fun carrierText_connectionStateUnknown_null() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.carrierText)
+
+ // Set up the conditions for satellite to be enabled
+ repo.isSatelliteAllowedForCurrentLocation.value = true
+ val i1 = mobileIconsInteractor.getMobileConnectionInteractorForSubId(1)
+ i1.isInService.value = false
+ i1.isEmergencyOnly.value = false
+ airplaneModeRepository.setIsAirplaneMode(false)
+
+ repo.connectionState.value = SatelliteConnectionState.Unknown
+
+ // Wait for delay to be completed
+ advanceTimeBy(10.seconds)
+
+ assertThat(latest).isNull()
+ }
+
+ @OptIn(ExperimentalCoroutinesApi::class)
+ @Test
+ fun carrierText_connectionStateOff_null() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.carrierText)
+
+ // Set up the conditions for satellite to be enabled
+ repo.isSatelliteAllowedForCurrentLocation.value = true
+ val i1 = mobileIconsInteractor.getMobileConnectionInteractorForSubId(1)
+ i1.isInService.value = false
+ i1.isEmergencyOnly.value = false
+ airplaneModeRepository.setIsAirplaneMode(false)
+
+ repo.connectionState.value = SatelliteConnectionState.Off
+
+ // Wait for delay to be completed
+ advanceTimeBy(10.seconds)
+
+ assertThat(latest).isNull()
+ }
+
+ @OptIn(ExperimentalCoroutinesApi::class)
+ @Test
+ fun carrierText_connectionStateOn_notConnectedString() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.carrierText)
+
+ // Set up the conditions for satellite to be enabled
+ repo.isSatelliteAllowedForCurrentLocation.value = true
+ val i1 = mobileIconsInteractor.getMobileConnectionInteractorForSubId(1)
+ i1.isInService.value = false
+ i1.isEmergencyOnly.value = false
+ airplaneModeRepository.setIsAirplaneMode(false)
+
+ repo.connectionState.value = SatelliteConnectionState.On
+
+ // Wait for delay to be completed
+ advanceTimeBy(10.seconds)
+
+ assertThat(latest)
+ .isEqualTo(context.getString(R.string.satellite_not_connected_carrier_text))
+ }
+
+ @OptIn(ExperimentalCoroutinesApi::class)
+ @Test
+ fun carrierText_connectionStateConnected_connectedString() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.carrierText)
+
+ // Set up the conditions for satellite to be enabled
+ repo.isSatelliteAllowedForCurrentLocation.value = true
+ val i1 = mobileIconsInteractor.getMobileConnectionInteractorForSubId(1)
+ i1.isInService.value = false
+ i1.isEmergencyOnly.value = false
+ airplaneModeRepository.setIsAirplaneMode(false)
+
+ repo.connectionState.value = SatelliteConnectionState.Connected
+
+ // Wait for delay to be completed
+ advanceTimeBy(10.seconds)
+
+ assertThat(latest)
+ .isEqualTo(context.getString(R.string.satellite_connected_carrier_text))
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/FakeDeviceBasedSatelliteViewModel.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/FakeDeviceBasedSatelliteViewModel.kt
new file mode 100644
index 0000000..f125ef12
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/FakeDeviceBasedSatelliteViewModel.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.satellite.ui.viewmodel
+
+import com.android.systemui.common.shared.model.Icon
+import kotlinx.coroutines.flow.MutableStateFlow
+
+class FakeDeviceBasedSatelliteViewModel : DeviceBasedSatelliteViewModel {
+ override val icon = MutableStateFlow<Icon?>(null)
+ override val carrierText = MutableStateFlow<String?>(null)
+}