diff options
| author | 2023-08-19 00:24:09 +0000 | |
|---|---|---|
| committer | 2023-08-19 00:24:09 +0000 | |
| commit | e7f0c5bb4fd56160d9b03f624a81cade42439cc7 (patch) | |
| tree | 210dd9b61bc2725c96d15593f4016784ec467d32 | |
| parent | 548763b683dac4cae5e4ab7a80ab6d552bd5e1a5 (diff) | |
| parent | 744c7ffd912cf37f886c0396b97e6059b83aac85 (diff) | |
Merge changes Iaf91e560,Ib40dcf0a into udc-qpr-dev am: cac31846bb am: 744c7ffd91
Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/24346595
Change-Id: I331f3b864c551e054295006c167b905f45c8927a
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
41 files changed, 1095 insertions, 89 deletions
diff --git a/packages/SystemUI/res-keyguard/layout/shade_carrier_new.xml b/packages/SystemUI/res-keyguard/layout/shade_carrier_new.xml new file mode 100644 index 000000000000..952f056b3023 --- /dev/null +++ b/packages/SystemUI/res-keyguard/layout/shade_carrier_new.xml @@ -0,0 +1,42 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +** +** Copyright 2023, 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. +*/ +--> +<com.android.systemui.statusbar.pipeline.mobile.ui.view.ModernShadeCarrierGroupMobileView + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/carrier_combo" + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:gravity="center_vertical" + android:orientation="horizontal" > + + <com.android.systemui.util.AutoMarqueeTextView + android:id="@+id/mobile_carrier_text" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_weight="1" + android:layout_marginEnd="@dimen/qs_carrier_margin_width" + android:visibility="gone" + android:textDirection="locale" + android:marqueeRepeatLimit="marquee_forever" + android:singleLine="true" + android:maxEms="7"/> + + <include layout="@layout/status_bar_mobile_signal_group_new" /> + +</com.android.systemui.statusbar.pipeline.mobile.ui.view.ModernShadeCarrierGroupMobileView> + diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt index 1a8e64240fba..53e8281d417d 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt @@ -398,6 +398,10 @@ object Flags { // TODO(b/294588085): Tracking Bug val WIFI_SECONDARY_NETWORKS = releasedFlag("wifi_secondary_networks") + // TODO(b/290676905): Tracking Bug + val NEW_SHADE_CARRIER_GROUP_MOBILE_ICONS = + unreleasedFlag("new_shade_carrier_group_mobile_icons") + // 700 - dialer/calls // TODO(b/254512734): Tracking Bug val ONGOING_CALL_STATUS_BAR_CHIP = releasedFlag("ongoing_call_status_bar_chip") diff --git a/packages/SystemUI/src/com/android/systemui/shade/carrier/ShadeCarrier.java b/packages/SystemUI/src/com/android/systemui/shade/carrier/ShadeCarrier.java index 8586828af0cd..8612cdf12c6e 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/carrier/ShadeCarrier.java +++ b/packages/SystemUI/src/com/android/systemui/shade/carrier/ShadeCarrier.java @@ -34,6 +34,7 @@ import com.android.settingslib.Utils; import com.android.settingslib.graph.SignalDrawable; import com.android.systemui.FontSizeUtils; import com.android.systemui.R; +import com.android.systemui.statusbar.pipeline.mobile.ui.view.ModernShadeCarrierGroupMobileView; import com.android.systemui.util.LargeScreenUtils; import java.util.Objects; @@ -44,6 +45,7 @@ public class ShadeCarrier extends LinearLayout { private TextView mCarrierText; private ImageView mMobileSignal; private ImageView mMobileRoaming; + private ModernShadeCarrierGroupMobileView mModernMobileView; private View mSpacer; @Nullable private CellSignalState mLastSignalState; @@ -77,6 +79,23 @@ public class ShadeCarrier extends LinearLayout { updateResources(); } + /** Removes a ModernStatusBarMobileView from the ViewGroup. */ + public void removeModernMobileView() { + if (mModernMobileView != null) { + removeView(mModernMobileView); + mModernMobileView = null; + } + } + + /** Adds a ModernStatusBarMobileView to the ViewGroup. */ + public void addModernMobileView(ModernShadeCarrierGroupMobileView mobileView) { + mModernMobileView = mobileView; + mMobileGroup.setVisibility(View.GONE); + mSpacer.setVisibility(View.GONE); + mCarrierText.setVisibility(View.GONE); + addView(mobileView); + } + /** * Update the state of this view * @param state the current state of the signal for this view diff --git a/packages/SystemUI/src/com/android/systemui/shade/carrier/ShadeCarrierGroupController.java b/packages/SystemUI/src/com/android/systemui/shade/carrier/ShadeCarrierGroupController.java index ad49b267c6ac..98d8a53b83c8 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/carrier/ShadeCarrierGroupController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/carrier/ShadeCarrierGroupController.java @@ -16,6 +16,7 @@ package com.android.systemui.shade.carrier; +import static android.telephony.SubscriptionManager.INVALID_SIM_SLOT_INDEX; import static android.view.View.IMPORTANT_FOR_ACCESSIBILITY_YES; import android.annotation.MainThread; @@ -46,8 +47,17 @@ import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.statusbar.connectivity.MobileDataIndicators; import com.android.systemui.statusbar.connectivity.NetworkController; import com.android.systemui.statusbar.connectivity.SignalCallback; +import com.android.systemui.statusbar.connectivity.ui.MobileContextProvider; +import com.android.systemui.statusbar.phone.StatusBarLocation; +import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags; +import com.android.systemui.statusbar.pipeline.mobile.ui.MobileUiAdapter; +import com.android.systemui.statusbar.pipeline.mobile.ui.binder.MobileIconsBinder; +import com.android.systemui.statusbar.pipeline.mobile.ui.view.ModernShadeCarrierGroupMobileView; +import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsViewModel; +import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.ShadeCarrierGroupMobileIconViewModel; import com.android.systemui.util.CarrierConfigTracker; +import java.util.List; import java.util.function.Consumer; import javax.inject.Inject; @@ -62,12 +72,16 @@ public class ShadeCarrierGroupController { private final ActivityStarter mActivityStarter; private final Handler mBgHandler; + private final Context mContext; private final NetworkController mNetworkController; private final CarrierTextManager mCarrierTextManager; private final TextView mNoSimTextView; // Non final for testing private H mMainHandler; private final Callback mCallback; + private final MobileIconsViewModel mMobileIconsViewModel; + private final MobileContextProvider mMobileContextProvider; + private final StatusBarPipelineFlags mStatusBarPipelineFlags; private boolean mListening; private final CellSignalState[] mInfos = new CellSignalState[SIM_SLOTS]; @@ -91,7 +105,7 @@ public class ShadeCarrierGroupController { Log.w(TAG, "setMobileDataIndicators - slot: " + slotIndex); return; } - if (slotIndex == SubscriptionManager.INVALID_SIM_SLOT_INDEX) { + if (slotIndex == INVALID_SIM_SLOT_INDEX) { Log.e(TAG, "Invalid SIM slot index for subscription: " + indicators.subId); return; } @@ -129,15 +143,25 @@ public class ShadeCarrierGroupController { } } - private ShadeCarrierGroupController(ShadeCarrierGroup view, ActivityStarter activityStarter, - @Background Handler bgHandler, @Main Looper mainLooper, + private ShadeCarrierGroupController( + ShadeCarrierGroup view, + ActivityStarter activityStarter, + @Background Handler bgHandler, + @Main Looper mainLooper, NetworkController networkController, - CarrierTextManager.Builder carrierTextManagerBuilder, Context context, - CarrierConfigTracker carrierConfigTracker, SlotIndexResolver slotIndexResolver) { - + CarrierTextManager.Builder carrierTextManagerBuilder, + Context context, + CarrierConfigTracker carrierConfigTracker, + SlotIndexResolver slotIndexResolver, + MobileUiAdapter mobileUiAdapter, + MobileContextProvider mobileContextProvider, + StatusBarPipelineFlags statusBarPipelineFlags + ) { + mContext = context; mActivityStarter = activityStarter; mBgHandler = bgHandler; mNetworkController = networkController; + mStatusBarPipelineFlags = statusBarPipelineFlags; mCarrierTextManager = carrierTextManagerBuilder .setShowAirplaneMode(false) .setShowMissingSim(false) @@ -162,6 +186,14 @@ public class ShadeCarrierGroupController { mCarrierGroups[1] = view.getCarrier2View(); mCarrierGroups[2] = view.getCarrier3View(); + mMobileContextProvider = mobileContextProvider; + mMobileIconsViewModel = mobileUiAdapter.getMobileIconsViewModel(); + + if (mStatusBarPipelineFlags.useNewShadeCarrierGroupMobileIcons()) { + mobileUiAdapter.setShadeCarrierGroupController(this); + MobileIconsBinder.bind(view, mMobileIconsViewModel); + } + mCarrierDividers[0] = view.getCarrierDivider1(); mCarrierDividers[1] = view.getCarrierDivider2(); @@ -193,6 +225,50 @@ public class ShadeCarrierGroupController { }); } + /** Updates the number of visible mobile icons using the new pipeline. */ + public void updateModernMobileIcons(List<Integer> subIds) { + if (!mStatusBarPipelineFlags.useNewShadeCarrierGroupMobileIcons()) { + Log.d(TAG, "ignoring new pipeline callback because new mobile icon is disabled"); + return; + } + + for (ShadeCarrier carrier : mCarrierGroups) { + carrier.removeModernMobileView(); + } + + List<IconData> iconDataList = processSubIdList(subIds); + + for (IconData iconData : iconDataList) { + ShadeCarrier carrier = mCarrierGroups[iconData.slotIndex]; + + Context mobileContext = + mMobileContextProvider.getMobileContextForSub(iconData.subId, mContext); + ModernShadeCarrierGroupMobileView modernMobileView = ModernShadeCarrierGroupMobileView + .constructAndBind( + mobileContext, + mMobileIconsViewModel.getLogger(), + "mobile_carrier_shade_group", + (ShadeCarrierGroupMobileIconViewModel) mMobileIconsViewModel + .viewModelForSub(iconData.subId, + StatusBarLocation.SHADE_CARRIER_GROUP) + ); + carrier.addModernMobileView(modernMobileView); + } + } + + @VisibleForTesting + List<IconData> processSubIdList(List<Integer> subIds) { + return subIds + .stream() + .limit(SIM_SLOTS) + .map(subId -> new IconData(subId, getSlotIndex(subId))) + .filter(iconData -> + iconData.slotIndex < SIM_SLOTS + && iconData.slotIndex != INVALID_SIM_SLOT_INDEX + ) + .toList(); + } + @VisibleForTesting protected int getSlotIndex(int subscriptionId) { return mSlotIndexResolver.getSlotIndex(subscriptionId); @@ -269,8 +345,12 @@ public class ShadeCarrierGroupController { } } - for (int i = 0; i < SIM_SLOTS; i++) { - mCarrierGroups[i].updateState(mInfos[i], singleCarrier); + if (mStatusBarPipelineFlags.useNewShadeCarrierGroupMobileIcons()) { + Log.d(TAG, "ignoring old pipeline callback because new mobile icon is enabled"); + } else { + for (int i = 0; i < SIM_SLOTS; i++) { + mCarrierGroups[i].updateState(mInfos[i], singleCarrier); + } } mCarrierDividers[0].setVisibility( @@ -306,7 +386,7 @@ public class ShadeCarrierGroupController { Log.w(TAG, "updateInfoCarrier - slot: " + slot); continue; } - if (slot == SubscriptionManager.INVALID_SIM_SLOT_INDEX) { + if (slot == INVALID_SIM_SLOT_INDEX) { Log.e(TAG, "Invalid SIM slot index for subscription: " + info.subscriptionIds[i]); @@ -385,12 +465,24 @@ public class ShadeCarrierGroupController { private final Context mContext; private final CarrierConfigTracker mCarrierConfigTracker; private final SlotIndexResolver mSlotIndexResolver; + private final MobileUiAdapter mMobileUiAdapter; + private final MobileContextProvider mMobileContextProvider; + private final StatusBarPipelineFlags mStatusBarPipelineFlags; @Inject - public Builder(ActivityStarter activityStarter, @Background Handler handler, - @Main Looper looper, NetworkController networkController, - CarrierTextManager.Builder carrierTextControllerBuilder, Context context, - CarrierConfigTracker carrierConfigTracker, SlotIndexResolver slotIndexResolver) { + public Builder( + ActivityStarter activityStarter, + @Background Handler handler, + @Main Looper looper, + NetworkController networkController, + CarrierTextManager.Builder carrierTextControllerBuilder, + Context context, + CarrierConfigTracker carrierConfigTracker, + SlotIndexResolver slotIndexResolver, + MobileUiAdapter mobileUiAdapter, + MobileContextProvider mobileContextProvider, + StatusBarPipelineFlags statusBarPipelineFlags + ) { mActivityStarter = activityStarter; mHandler = handler; mLooper = looper; @@ -399,6 +491,9 @@ public class ShadeCarrierGroupController { mContext = context; mCarrierConfigTracker = carrierConfigTracker; mSlotIndexResolver = slotIndexResolver; + mMobileUiAdapter = mobileUiAdapter; + mMobileContextProvider = mobileContextProvider; + mStatusBarPipelineFlags = statusBarPipelineFlags; } public Builder setShadeCarrierGroup(ShadeCarrierGroup view) { @@ -407,9 +502,20 @@ public class ShadeCarrierGroupController { } public ShadeCarrierGroupController build() { - return new ShadeCarrierGroupController(mView, mActivityStarter, mHandler, mLooper, - mNetworkController, mCarrierTextControllerBuilder, mContext, - mCarrierConfigTracker, mSlotIndexResolver); + return new ShadeCarrierGroupController( + mView, + mActivityStarter, + mHandler, + mLooper, + mNetworkController, + mCarrierTextControllerBuilder, + mContext, + mCarrierConfigTracker, + mSlotIndexResolver, + mMobileUiAdapter, + mMobileContextProvider, + mStatusBarPipelineFlags + ); } } @@ -448,4 +554,15 @@ public class ShadeCarrierGroupController { return SubscriptionManager.getSlotIndex(subscriptionId); } } + + @VisibleForTesting + static class IconData { + public final int subId; + public final int slotIndex; + + IconData(int subId, int slotIndex) { + this.subId = subId; + this.slotIndex = slotIndex; + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarLocation.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarLocation.kt index 5ace22695ec3..32e5c355889a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarLocation.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarLocation.kt @@ -24,4 +24,6 @@ enum class StatusBarLocation { KEYGUARD, /** Quick settings (inside the shade). */ QS, + /** ShadeCarrierGroup (above QS status bar in expanded mode). */ + SHADE_CARRIER_GROUP, } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/StatusBarPipelineFlags.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/StatusBarPipelineFlags.kt index 6e51ed0eba37..c69577308608 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/StatusBarPipelineFlags.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/StatusBarPipelineFlags.kt @@ -18,6 +18,9 @@ package com.android.systemui.statusbar.pipeline import android.content.Context import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.flags.FeatureFlags +import com.android.systemui.flags.Flags +import com.android.systemui.shade.carrier.ShadeCarrierGroup import javax.inject.Inject /** All flagging methods related to the new status bar pipeline (see b/238425913). */ @@ -26,11 +29,19 @@ class StatusBarPipelineFlags @Inject constructor( context: Context, + private val featureFlags: FeatureFlags, ) { private val mobileSlot = context.getString(com.android.internal.R.string.status_bar_mobile) private val wifiSlot = context.getString(com.android.internal.R.string.status_bar_wifi) /** + * True if we should display the mobile icons in the [ShadeCarrierGroup] using the new status + * bar Data pipeline. + */ + fun useNewShadeCarrierGroupMobileIcons(): Boolean = + featureFlags.isEnabled(Flags.NEW_SHADE_CARRIER_GROUP_MOBILE_ICONS) + + /** * For convenience in the StatusBarIconController, we want to gate some actions based on slot * name and the flag together. * diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/NetworkNameModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/NetworkNameModel.kt index 78231e28803e..99ed2d99c749 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/NetworkNameModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/NetworkNameModel.kt @@ -60,6 +60,19 @@ sealed interface NetworkNameModel : Diffable<NetworkNameModel> { } } + /** This name has been derived from SubscriptionModel. see [SubscriptionModel] */ + data class SubscriptionDerived(override val name: String) : NetworkNameModel { + override fun logDiffs(prevVal: NetworkNameModel, row: TableRowLogger) { + if (prevVal !is SubscriptionDerived || prevVal.name != name) { + row.logChange(COL_NETWORK_NAME, "SubscriptionDerived($name)") + } + } + + override fun logFull(row: TableRowLogger) { + row.logChange(COL_NETWORK_NAME, "SubscriptionDerived($name)") + } + } + /** * This name has been derived from the sim via * [android.telephony.TelephonyManager.getSimOperatorName]. diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/SubscriptionModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/SubscriptionModel.kt index 16c4027ef645..27f6df4c26e1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/SubscriptionModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/SubscriptionModel.kt @@ -34,4 +34,7 @@ data class SubscriptionModel( /** Subscriptions in the same group may be filtered or treated as a single subscription */ val groupUuid: ParcelUuid? = null, + + /** Text representing the name for this connection */ + val carrierName: String, ) 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 c1af6df12bd1..a89b1b2db6b3 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 @@ -115,10 +115,18 @@ interface MobileConnectionRepository { */ val cdmaRoaming: StateFlow<Boolean> - /** The service provider name for this network connection, or the default name */ + /** The service provider name for this network connection, or the default name. */ val networkName: StateFlow<NetworkNameModel> /** + * The service provider name for this network connection, or the default name. + * + * TODO(b/296600321): De-duplicate this field with [networkName] after determining the data + * provided is identical + */ + val carrierName: StateFlow<NetworkNameModel> + + /** * True if this type of connection is allowed while airplane mode is on, and false otherwise. */ val isAllowedDuringAirplaneMode: StateFlow<Boolean> 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 17d20c297861..c576b822da15 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 @@ -184,7 +184,10 @@ class DemoMobileConnectionRepository( override val cdmaRoaming = MutableStateFlow(false) - override val networkName = MutableStateFlow(NetworkNameModel.IntentDerived("demo network")) + override val networkName = MutableStateFlow(NetworkNameModel.IntentDerived(DEMO_CARRIER_NAME)) + + override val carrierName = + MutableStateFlow(NetworkNameModel.SubscriptionDerived(DEMO_CARRIER_NAME)) override val isAllowedDuringAirplaneMode = MutableStateFlow(false) @@ -200,6 +203,7 @@ class DemoMobileConnectionRepository( // This is always true here, because we split out disabled states at the data-source level dataEnabled.value = true networkName.value = NetworkNameModel.IntentDerived(event.name) + carrierName.value = NetworkNameModel.SubscriptionDerived("${event.name} ${event.subId}") _carrierId.value = event.carrierId ?: INVALID_SUBSCRIPTION_ID @@ -227,6 +231,7 @@ class DemoMobileConnectionRepository( // This is always true here, because we split out disabled states at the data-source level dataEnabled.value = true networkName.value = NetworkNameModel.IntentDerived(CARRIER_MERGED_NAME) + carrierName.value = NetworkNameModel.SubscriptionDerived(CARRIER_MERGED_NAME) // TODO(b/276943904): is carrierId a thing with carrier merged networks? _carrierId.value = INVALID_SUBSCRIPTION_ID numberOfLevels.value = event.numberOfLevels @@ -248,6 +253,7 @@ class DemoMobileConnectionRepository( } companion object { + private const val DEMO_CARRIER_NAME = "Demo Carrier" private const val CARRIER_MERGED_NAME = "Carrier Merged Network" } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt index 0e4ceebcc854..ee13d93e735d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt @@ -92,9 +92,12 @@ constructor( private fun maybeCreateSubscription(subId: Int) { if (!subscriptionInfoCache.containsKey(subId)) { - SubscriptionModel(subscriptionId = subId, isOpportunistic = false).also { - subscriptionInfoCache[subId] = it - } + SubscriptionModel( + subscriptionId = subId, + isOpportunistic = false, + carrierName = DEFAULT_CARRIER_NAME, + ) + .also { subscriptionInfoCache[subId] = it } _subscriptions.value = subscriptionInfoCache.values.toList() } @@ -327,6 +330,7 @@ constructor( private const val TAG = "DemoMobileConnectionsRepo" private const val DEFAULT_SUB_ID = 1 + private const val DEFAULT_CARRIER_NAME = "demo carrier" } } 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 65f486683837..28be3be28928 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 @@ -108,6 +108,8 @@ class CarrierMergedConnectionRepository( NetworkNameModel.SimDerived(telephonyManager.simOperatorName), ) + override val carrierName: StateFlow<NetworkNameModel> = networkName + override val numberOfLevels: StateFlow<Int> = wifiRepository.wifiNetwork .map { 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 8ba7d2197c14..ee11c06ef3f5 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 @@ -22,6 +22,7 @@ import com.android.systemui.log.table.TableLogBuffer import com.android.systemui.log.table.TableLogBufferFactory import com.android.systemui.log.table.logDiffsForTable import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel +import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository import javax.inject.Inject import kotlinx.coroutines.CoroutineScope @@ -47,6 +48,7 @@ class FullMobileConnectionRepository( override val subId: Int, startingIsCarrierMerged: Boolean, override val tableLogBuffer: TableLogBuffer, + subscriptionModel: StateFlow<SubscriptionModel?>, private val defaultNetworkName: NetworkNameModel, private val networkNameSeparator: String, @Application scope: CoroutineScope, @@ -80,6 +82,7 @@ class FullMobileConnectionRepository( mobileRepoFactory.build( subId, tableLogBuffer, + subscriptionModel, defaultNetworkName, networkNameSeparator, ) @@ -287,6 +290,16 @@ class FullMobileConnectionRepository( ) .stateIn(scope, SharingStarted.WhileSubscribed(), activeRepo.value.networkName.value) + override val carrierName = + activeRepo + .flatMapLatest { it.carrierName } + .logDiffsForTable( + tableLogBuffer, + columnPrefix = "", + initialValue = activeRepo.value.carrierName.value, + ) + .stateIn(scope, SharingStarted.WhileSubscribed(), activeRepo.value.carrierName.value) + override val isAllowedDuringAirplaneMode = activeRepo .flatMapLatest { it.isAllowedDuringAirplaneMode } @@ -307,6 +320,7 @@ class FullMobileConnectionRepository( fun build( subId: Int, startingIsCarrierMerged: Boolean, + subscriptionModel: StateFlow<SubscriptionModel?>, defaultNetworkName: NetworkNameModel, networkNameSeparator: String, ): FullMobileConnectionRepository { @@ -317,6 +331,7 @@ class FullMobileConnectionRepository( subId, startingIsCarrierMerged, mobileLogger, + subscriptionModel, defaultNetworkName, networkNameSeparator, scope, 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 aadc975a10de..1f1ac92b3956 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 @@ -43,6 +43,7 @@ import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameMode import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType.DefaultNetworkType import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType.OverrideNetworkType import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType.UnknownNetworkType +import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel import com.android.systemui.statusbar.pipeline.mobile.data.model.SystemUiCarrierConfig import com.android.systemui.statusbar.pipeline.mobile.data.model.toDataConnectionType import com.android.systemui.statusbar.pipeline.mobile.data.model.toNetworkNameModel @@ -80,6 +81,7 @@ import kotlinx.coroutines.flow.stateIn @OptIn(ExperimentalCoroutinesApi::class) class MobileConnectionRepositoryImpl( override val subId: Int, + subscriptionModel: StateFlow<SubscriptionModel?>, defaultNetworkName: NetworkNameModel, networkNameSeparator: String, private val telephonyManager: TelephonyManager, @@ -281,6 +283,14 @@ class MobileConnectionRepositoryImpl( } .stateIn(scope, SharingStarted.WhileSubscribed(), DEFAULT_NUM_LEVELS) + override val carrierName = + subscriptionModel + .map { + it?.let { model -> NetworkNameModel.SubscriptionDerived(model.carrierName) } + ?: defaultNetworkName + } + .stateIn(scope, SharingStarted.WhileSubscribed(), defaultNetworkName) + /** * There are a few cases where we will need to poll [TelephonyManager] so we can update some * internal state where callbacks aren't provided. Any of those events should be merged into @@ -350,11 +360,13 @@ class MobileConnectionRepositoryImpl( fun build( subId: Int, mobileLogger: TableLogBuffer, + subscriptionModel: StateFlow<SubscriptionModel?>, defaultNetworkName: NetworkNameModel, networkNameSeparator: String, ): MobileConnectionRepository { return MobileConnectionRepositoryImpl( subId, + subscriptionModel, defaultNetworkName, networkNameSeparator, telephonyManager.createForSubscriptionId(subId), diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt index 54948a4a41c8..67b04db64463 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt @@ -319,10 +319,17 @@ constructor( @VisibleForTesting fun getSubIdRepoCache() = subIdRepositoryCache + private fun subscriptionModelForSubId(subId: Int): StateFlow<SubscriptionModel?> { + return subscriptions + .map { list -> list.firstOrNull { model -> model.subscriptionId == subId } } + .stateIn(scope, SharingStarted.WhileSubscribed(), null) + } + private fun createRepositoryForSubId(subId: Int): FullMobileConnectionRepository { return fullMobileRepoFactory.build( subId, isCarrierMerged(subId), + subscriptionModelForSubId(subId), defaultNetworkName, networkNameSeparator, ) @@ -373,6 +380,7 @@ constructor( subscriptionId = subscriptionId, isOpportunistic = isOpportunistic, groupUuid = groupUuid, + carrierName = carrierName.toString(), ) companion object { 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 1a138272d67c..4cfde5bd5622 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 @@ -92,6 +92,22 @@ interface MobileIconInteractor { */ val networkName: StateFlow<NetworkNameModel> + /** + * Provider name for this network connection. The name can be one of 3 values: + * 1. The default network name, if one is configured + * 2. A name provided by the [SubscriptionModel] of this network connection + * 3. Or, in the case where the repository sends us the default network name, we check for an + * override in [connectionInfo.operatorAlphaShort], a value that is derived from + * [ServiceState] + * + * TODO(b/296600321): De-duplicate this field with [networkName] after determining the data + * provided is identical + */ + val carrierName: StateFlow<String> + + /** True if there is only one active subscription. */ + val isSingleCarrier: StateFlow<Boolean> + /** True if this line of service is emergency-only */ val isEmergencyOnly: StateFlow<Boolean> @@ -126,6 +142,7 @@ class MobileIconInteractorImpl( defaultSubscriptionHasDataEnabled: StateFlow<Boolean>, override val alwaysShowDataRatIcon: StateFlow<Boolean>, override val alwaysUseCdmaLevel: StateFlow<Boolean>, + override val isSingleCarrier: StateFlow<Boolean>, override val mobileIsDefault: StateFlow<Boolean>, defaultMobileIconMapping: StateFlow<Map<String, MobileIconGroup>>, defaultMobileIconGroup: StateFlow<MobileIconGroup>, @@ -171,6 +188,22 @@ class MobileIconInteractorImpl( connectionRepository.networkName.value ) + override val carrierName = + combine(connectionRepository.operatorAlphaShort, connectionRepository.carrierName) { + operatorAlphaShort, + networkName -> + if (networkName is NetworkNameModel.Default && operatorAlphaShort != null) { + operatorAlphaShort + } else { + networkName.name + } + } + .stateIn( + scope, + SharingStarted.WhileSubscribed(), + connectionRepository.carrierName.value.name + ) + /** What the mobile icon would be before carrierId overrides */ private val defaultNetworkType: StateFlow<MobileIconGroup> = combine( diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt index e90f40c74cc5..d08808b65399 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt @@ -76,6 +76,9 @@ interface MobileIconsInteractor { /** True if the CDMA level should be preferred over the primary level. */ val alwaysUseCdmaLevel: StateFlow<Boolean> + /** True if there is only one active subscription. */ + val isSingleCarrier: StateFlow<Boolean> + /** The icon mapping from network type to [MobileIconGroup] for the default subscription */ val defaultMobileIconMapping: StateFlow<Map<String, MobileIconGroup>> @@ -252,6 +255,17 @@ constructor( .mapLatest { it.alwaysShowCdmaRssi } .stateIn(scope, SharingStarted.WhileSubscribed(), false) + override val isSingleCarrier: StateFlow<Boolean> = + mobileConnectionsRepo.subscriptions + .map { it.size == 1 } + .logDiffsForTable( + tableLogger, + columnPrefix = LOGGING_PREFIX, + columnName = "isSingleCarrier", + initialValue = false, + ) + .stateIn(scope, SharingStarted.WhileSubscribed(), false) + /** If there is no mapping in [defaultMobileIconMapping], then use this default icon group */ override val defaultMobileIconGroup: StateFlow<MobileIconGroup> = mobileConnectionsRepo.defaultMobileIconGroup.stateIn( @@ -298,6 +312,7 @@ constructor( activeDataConnectionHasDataEnabled, alwaysShowDataRatIcon, alwaysUseCdmaLevel, + isSingleCarrier, mobileIsDefault, defaultMobileIconMapping, defaultMobileIconGroup, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileUiAdapter.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileUiAdapter.kt index d7fcf4876c28..02e50a007f50 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileUiAdapter.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileUiAdapter.kt @@ -19,6 +19,7 @@ package com.android.systemui.statusbar.pipeline.mobile.ui import com.android.systemui.CoreStartable import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.shade.carrier.ShadeCarrierGroupController import com.android.systemui.statusbar.phone.StatusBarIconController import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsViewModel @@ -49,6 +50,8 @@ constructor( private var isCollecting: Boolean = false private var lastValue: List<Int>? = null + private var shadeCarrierGroupController: ShadeCarrierGroupController? = null + override fun start() { // Start notifying the icon controller of subscriptions scope.launch { @@ -57,10 +60,16 @@ constructor( logger.logUiAdapterSubIdsSentToIconController(it) lastValue = it iconController.setNewMobileIconSubIds(it) + shadeCarrierGroupController?.updateModernMobileIcons(it) } } } + /** Set the [ShadeCarrierGroupController] to notify of subscription updates */ + fun setShadeCarrierGroupController(controller: ShadeCarrierGroupController) { + shadeCarrierGroupController = controller + } + override fun dump(pw: PrintWriter, args: Array<out String>) { pw.println("isCollecting=$isCollecting") pw.println("Last values sent to icon controller: $lastValue") diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileViewLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileViewLogger.kt index cea6654a48de..2af6795b39c4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileViewLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileViewLogger.kt @@ -57,7 +57,7 @@ constructor( { str1 = view.getIdForLogging() str2 = viewModel.getIdForLogging() - str3 = viewModel.locationName + str3 = viewModel.location.name }, { "New view binding. viewId=$str1, viewModelId=$str2, viewModelLocation=$str3" }, ) @@ -71,7 +71,7 @@ constructor( { str1 = view.getIdForLogging() str2 = viewModel.getIdForLogging() - str3 = viewModel.locationName + str3 = viewModel.location.name }, { "Collection started. viewId=$str1, viewModelId=$str2, viewModelLocation=$str3" }, ) @@ -85,7 +85,7 @@ constructor( { str1 = view.getIdForLogging() str2 = viewModel.getIdForLogging() - str3 = viewModel.locationName + str3 = viewModel.location.name }, { "Collection stopped. viewId=$str1, viewModelId=$str2, viewModelLocation=$str3" }, ) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt index 55bc8d58be23..4b2fb4373957 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt @@ -50,6 +50,7 @@ object MobileIconBinder { fun bind( view: ViewGroup, viewModel: LocationBasedMobileViewModel, + @StatusBarIconView.VisibleState initialVisibilityState: Int = STATE_HIDDEN, logger: MobileViewLogger, ): ModernStatusBarViewBinding { val mobileGroupView = view.requireViewById<ViewGroup>(R.id.mobile_group) @@ -68,12 +69,12 @@ object MobileIconBinder { // TODO(b/238425913): We should log this visibility state. @StatusBarIconView.VisibleState - val visibilityState: MutableStateFlow<Int> = MutableStateFlow(STATE_HIDDEN) + val visibilityState: MutableStateFlow<Int> = MutableStateFlow(initialVisibilityState) val iconTint: MutableStateFlow<Int> = MutableStateFlow(viewModel.defaultColor) val decorTint: MutableStateFlow<Int> = MutableStateFlow(viewModel.defaultColor) - var isCollecting: Boolean = false + var isCollecting = false view.repeatWhenAttached { repeatOnLifecycle(Lifecycle.State.STARTED) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/ShadeCarrierBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/ShadeCarrierBinder.kt new file mode 100644 index 000000000000..081e1015e26e --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/ShadeCarrierBinder.kt @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2023 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.mobile.ui.binder + +import androidx.core.view.isVisible +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.repeatOnLifecycle +import com.android.systemui.lifecycle.repeatWhenAttached +import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.ShadeCarrierGroupMobileIconViewModel +import com.android.systemui.util.AutoMarqueeTextView +import kotlinx.coroutines.launch + +object ShadeCarrierBinder { + /** Binds the view to the view-model, continuing to update the former based on the latter */ + @JvmStatic + fun bind( + carrierTextView: AutoMarqueeTextView, + viewModel: ShadeCarrierGroupMobileIconViewModel, + ) { + carrierTextView.isVisible = true + + carrierTextView.repeatWhenAttached { + repeatOnLifecycle(Lifecycle.State.STARTED) { + launch { viewModel.carrierName.collect { carrierTextView.text = it } } + } + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernShadeCarrierGroupMobileView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernShadeCarrierGroupMobileView.kt new file mode 100644 index 000000000000..f407127f3161 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernShadeCarrierGroupMobileView.kt @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2023 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.mobile.ui.view + +import android.content.Context +import android.util.AttributeSet +import android.view.LayoutInflater +import android.widget.LinearLayout +import com.android.systemui.R +import com.android.systemui.statusbar.StatusBarIconView.STATE_ICON +import com.android.systemui.statusbar.pipeline.mobile.ui.MobileViewLogger +import com.android.systemui.statusbar.pipeline.mobile.ui.binder.MobileIconBinder +import com.android.systemui.statusbar.pipeline.mobile.ui.binder.ShadeCarrierBinder +import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.ShadeCarrierGroupMobileIconViewModel +import com.android.systemui.util.AutoMarqueeTextView + +/** + * ViewGroup containing a mobile carrier name and icon in the Shade Header. Can be multiple + * instances as children under [ShadeCarrierGroup] + */ +class ModernShadeCarrierGroupMobileView( + context: Context, + attrs: AttributeSet?, +) : LinearLayout(context, attrs) { + + var subId: Int = -1 + + override fun toString(): String { + return "ModernShadeCarrierGroupMobileView(" + + "subId=$subId, " + + "viewString=${super.toString()}" + } + + companion object { + + /** + * Inflates a new instance of [ModernShadeCarrierGroupMobileView], binds it to [viewModel], + * and returns it. + */ + @JvmStatic + fun constructAndBind( + context: Context, + logger: MobileViewLogger, + slot: String, + viewModel: ShadeCarrierGroupMobileIconViewModel, + ): ModernShadeCarrierGroupMobileView { + return (LayoutInflater.from(context).inflate(R.layout.shade_carrier_new, null) + as ModernShadeCarrierGroupMobileView) + .also { + it.subId = viewModel.subscriptionId + + val iconView = it.requireViewById<ModernStatusBarMobileView>(R.id.mobile_combo) + iconView.initView(slot) { + MobileIconBinder.bind(iconView, viewModel, STATE_ICON, logger) + } + logger.logNewViewBinding(it, viewModel) + + val textView = it.requireViewById<AutoMarqueeTextView>(R.id.mobile_carrier_text) + ShadeCarrierBinder.bind(textView, viewModel) + } + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileView.kt index 4144293d5ccd..68d02de51dc9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileView.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileView.kt @@ -60,7 +60,9 @@ class ModernStatusBarMobileView( as ModernStatusBarMobileView) .also { it.subId = viewModel.subscriptionId - it.initView(slot) { MobileIconBinder.bind(it, viewModel, logger) } + it.initView(slot) { + MobileIconBinder.bind(view = it, viewModel = viewModel, logger = logger) + } logger.logNewViewBinding(it, viewModel) } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileViewModel.kt index a51982c41255..e7c311d7ec74 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileViewModel.kt @@ -18,7 +18,13 @@ package com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel import android.graphics.Color import com.android.systemui.statusbar.phone.StatusBarLocation +import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconInteractor import com.android.systemui.statusbar.pipeline.mobile.ui.VerboseMobileViewLogger +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.stateIn /** * A view model for an individual mobile icon that embeds the notion of a [StatusBarLocation]. This @@ -26,12 +32,12 @@ import com.android.systemui.statusbar.pipeline.mobile.ui.VerboseMobileViewLogger * * @param commonImpl for convenience, this class wraps a base interface that can provides all of the * common implementations between locations. See [MobileIconViewModel] - * @property locationName the name of the location of this VM, used for logging. + * @property location the [StatusBarLocation] of this VM. * @property verboseLogger an optional logger to log extremely verbose view updates. */ abstract class LocationBasedMobileViewModel( val commonImpl: MobileIconViewModelCommon, - val locationName: String, + val location: StatusBarLocation, val verboseLogger: VerboseMobileViewLogger?, ) : MobileIconViewModelCommon by commonImpl { val defaultColor: Int = Color.WHITE @@ -39,10 +45,12 @@ abstract class LocationBasedMobileViewModel( companion object { fun viewModelForLocation( commonImpl: MobileIconViewModelCommon, + interactor: MobileIconInteractor, verboseMobileViewLogger: VerboseMobileViewLogger, - loc: StatusBarLocation, + location: StatusBarLocation, + scope: CoroutineScope, ): LocationBasedMobileViewModel = - when (loc) { + when (location) { StatusBarLocation.HOME -> HomeMobileIconViewModel( commonImpl, @@ -50,6 +58,12 @@ abstract class LocationBasedMobileViewModel( ) StatusBarLocation.KEYGUARD -> KeyguardMobileIconViewModel(commonImpl) StatusBarLocation.QS -> QsMobileIconViewModel(commonImpl) + StatusBarLocation.SHADE_CARRIER_GROUP -> + ShadeCarrierGroupMobileIconViewModel( + commonImpl, + interactor, + scope, + ) } } } @@ -61,7 +75,7 @@ class HomeMobileIconViewModel( MobileIconViewModelCommon, LocationBasedMobileViewModel( commonImpl, - locationName = "Home", + location = StatusBarLocation.HOME, verboseMobileViewLogger, ) @@ -71,18 +85,40 @@ class QsMobileIconViewModel( MobileIconViewModelCommon, LocationBasedMobileViewModel( commonImpl, - locationName = "QS", + location = StatusBarLocation.QS, // Only do verbose logging for the Home location. verboseLogger = null, ) +class ShadeCarrierGroupMobileIconViewModel( + commonImpl: MobileIconViewModelCommon, + interactor: MobileIconInteractor, + scope: CoroutineScope, +) : + MobileIconViewModelCommon, + LocationBasedMobileViewModel( + commonImpl, + location = StatusBarLocation.SHADE_CARRIER_GROUP, + // Only do verbose logging for the Home location. + verboseLogger = null, + ) { + private val isSingleCarrier = interactor.isSingleCarrier + val carrierName = interactor.carrierName + + override val isVisible: StateFlow<Boolean> = + combine(super.isVisible, isSingleCarrier) { isVisible, isSingleCarrier -> + if (isSingleCarrier) false else isVisible + } + .stateIn(scope, SharingStarted.WhileSubscribed(), super.isVisible.value) +} + class KeyguardMobileIconViewModel( commonImpl: MobileIconViewModelCommon, ) : MobileIconViewModelCommon, LocationBasedMobileViewModel( commonImpl, - locationName = "Keyguard", + location = StatusBarLocation.KEYGUARD, // Only do verbose logging for the Home location. verboseLogger = null, ) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt index 5cf887e4d41a..216afb987f91 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt @@ -22,6 +22,7 @@ import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.statusbar.phone.StatusBarLocation import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor +import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconInteractor import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor import com.android.systemui.statusbar.pipeline.mobile.ui.MobileViewLogger import com.android.systemui.statusbar.pipeline.mobile.ui.VerboseMobileViewLogger @@ -58,6 +59,8 @@ constructor( private val statusBarPipelineFlags: StatusBarPipelineFlags, ) { @VisibleForTesting val mobileIconSubIdCache = mutableMapOf<Int, MobileIconViewModel>() + @VisibleForTesting + val mobileIconInteractorSubIdCache = mutableMapOf<Int, MobileIconInteractor>() val subscriptionIdsFlow: StateFlow<List<Int>> = interactor.filteredSubscriptions @@ -91,15 +94,17 @@ constructor( .stateIn(scope, SharingStarted.WhileSubscribed(), false) init { - scope.launch { subscriptionIdsFlow.collect { removeInvalidModelsFromCache(it) } } + scope.launch { subscriptionIdsFlow.collect { invalidateCaches(it) } } } fun viewModelForSub(subId: Int, location: StatusBarLocation): LocationBasedMobileViewModel { val common = commonViewModelForSub(subId) return LocationBasedMobileViewModel.viewModelForLocation( common, + mobileIconInteractorForSub(subId), verboseLogger, location, + scope, ) } @@ -107,7 +112,7 @@ constructor( return mobileIconSubIdCache[subId] ?: MobileIconViewModel( subId, - interactor.createMobileConnectionInteractorForSubId(subId), + mobileIconInteractorForSub(subId), airplaneModeInteractor, constants, scope, @@ -115,8 +120,20 @@ constructor( .also { mobileIconSubIdCache[subId] = it } } - private fun removeInvalidModelsFromCache(subIds: List<Int>) { + @VisibleForTesting + fun mobileIconInteractorForSub(subId: Int): MobileIconInteractor { + return mobileIconInteractorSubIdCache[subId] + ?: interactor.createMobileConnectionInteractorForSubId(subId).also { + mobileIconInteractorSubIdCache[subId] = it + } + } + + private fun invalidateCaches(subIds: List<Int>) { val subIdsToRemove = mobileIconSubIdCache.keys.filter { !subIds.contains(it) } subIdsToRemove.forEach { mobileIconSubIdCache.remove(it) } + + mobileIconInteractorSubIdCache.keys + .filter { !subIds.contains(it) } + .forEach { subId -> mobileIconInteractorSubIdCache.remove(subId) } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/LocationBasedWifiViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/LocationBasedWifiViewModel.kt index cd5b92cf24c7..00bd616e04ea 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/LocationBasedWifiViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/LocationBasedWifiViewModel.kt @@ -18,6 +18,7 @@ package com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel import android.graphics.Color import com.android.systemui.statusbar.phone.StatusBarLocation +import java.lang.IllegalArgumentException /** * A view model for a wifi icon in a specific location. This allows us to control parameters that @@ -43,6 +44,8 @@ abstract class LocationBasedWifiViewModel( StatusBarLocation.HOME -> HomeWifiViewModel(commonImpl) StatusBarLocation.KEYGUARD -> KeyguardWifiViewModel(commonImpl) StatusBarLocation.QS -> QsWifiViewModel(commonImpl) + StatusBarLocation.SHADE_CARRIER_GROUP -> + throw IllegalArgumentException("invalid location for WifiViewModel: $location") } } } @@ -64,3 +67,11 @@ class KeyguardWifiViewModel( class QsWifiViewModel( commonImpl: WifiViewModelCommon, ) : WifiViewModelCommon, LocationBasedWifiViewModel(commonImpl) + +/** + * A view model for the wifi icon in the shade carrier group (visible when quick settings is fully + * expanded, and in large screen shade). Currently unused. + */ +class ShadeCarrierGroupWifiViewModel( + commonImpl: WifiViewModelCommon, +) : WifiViewModelCommon, LocationBasedWifiViewModel(commonImpl) diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/carrier/ShadeCarrierGroupControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/carrier/ShadeCarrierGroupControllerTest.java index 31bfa3fdf8cb..5fa6b3a15d35 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/carrier/ShadeCarrierGroupControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/carrier/ShadeCarrierGroupControllerTest.java @@ -29,6 +29,7 @@ import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyZeroInteractions; import static org.mockito.Mockito.when; @@ -50,6 +51,12 @@ import com.android.systemui.statusbar.connectivity.IconState; import com.android.systemui.statusbar.connectivity.MobileDataIndicators; import com.android.systemui.statusbar.connectivity.NetworkController; import com.android.systemui.statusbar.connectivity.SignalCallback; +import com.android.systemui.statusbar.connectivity.ui.MobileContextProvider; +import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags; +import com.android.systemui.statusbar.pipeline.mobile.ui.MobileUiAdapter; +import com.android.systemui.statusbar.pipeline.mobile.ui.MobileViewLogger; +import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsViewModel; +import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.ShadeCarrierGroupMobileIconViewModel; import com.android.systemui.util.CarrierConfigTracker; import com.android.systemui.utils.leaks.LeakCheckedTest; import com.android.systemui.utils.os.FakeHandler; @@ -61,6 +68,10 @@ import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + @RunWith(AndroidTestingRunner.class) @TestableLooper.RunWithLooper @SmallTest @@ -95,6 +106,18 @@ public class ShadeCarrierGroupControllerTest extends LeakCheckedTest { private TestableLooper mTestableLooper; @Mock private ShadeCarrierGroupController.OnSingleCarrierChangedListener mOnSingleCarrierChangedListener; + @Mock + private MobileUiAdapter mMobileUiAdapter; + @Mock + private MobileIconsViewModel mMobileIconsViewModel; + @Mock + private ShadeCarrierGroupMobileIconViewModel mShadeCarrierGroupMobileIconViewModel; + @Mock + private MobileViewLogger mMobileViewLogger; + @Mock + private MobileContextProvider mMobileContextProvider; + @Mock + private StatusBarPipelineFlags mStatusBarPipelineFlags; private FakeSlotIndexResolver mSlotIndexResolver; private ClickListenerTextView mNoCarrierTextView; @@ -133,16 +156,35 @@ public class ShadeCarrierGroupControllerTest extends LeakCheckedTest { mSlotIndexResolver = new FakeSlotIndexResolver(); + when(mMobileUiAdapter.getMobileIconsViewModel()).thenReturn(mMobileIconsViewModel); + mShadeCarrierGroupController = new ShadeCarrierGroupController.Builder( - mActivityStarter, handler, TestableLooper.get(this).getLooper(), - mNetworkController, mCarrierTextControllerBuilder, mContext, mCarrierConfigTracker, - mSlotIndexResolver) + mActivityStarter, + handler, + TestableLooper.get(this).getLooper(), + mNetworkController, + mCarrierTextControllerBuilder, + mContext, + mCarrierConfigTracker, + mSlotIndexResolver, + mMobileUiAdapter, + mMobileContextProvider, + mStatusBarPipelineFlags + ) .setShadeCarrierGroup(mShadeCarrierGroup) .build(); mShadeCarrierGroupController.setListening(true); } + private void setupWithNewPipeline() { + when(mStatusBarPipelineFlags.useNewShadeCarrierGroupMobileIcons()).thenReturn(true); + when(mMobileContextProvider.getMobileContextForSub(anyInt(), any())).thenReturn(mContext); + when(mMobileIconsViewModel.getLogger()).thenReturn(mMobileViewLogger); + when(mMobileIconsViewModel.viewModelForSub(anyInt(), any())) + .thenReturn(mShadeCarrierGroupMobileIconViewModel); + } + @Test public void testInitiallyMultiCarrier() { assertFalse(mShadeCarrierGroupController.isSingleCarrier()); @@ -406,6 +448,129 @@ public class ShadeCarrierGroupControllerTest extends LeakCheckedTest { verify(mOnSingleCarrierChangedListener, never()).onSingleCarrierChanged(anyBoolean()); } + @TestableLooper.RunWithLooper(setAsMainLooper = true) + @Test + public void testUpdateModernMobileIcons_addSubscription() { + setupWithNewPipeline(); + + mShadeCarrier1.setVisibility(View.GONE); + mShadeCarrier2.setVisibility(View.GONE); + mShadeCarrier3.setVisibility(View.GONE); + + List<Integer> subIds = new ArrayList<>(); + subIds.add(0); + mShadeCarrierGroupController.updateModernMobileIcons(subIds); + + verify(mShadeCarrier1).addModernMobileView(any()); + verify(mShadeCarrier2, never()).addModernMobileView(any()); + verify(mShadeCarrier3, never()).addModernMobileView(any()); + + resetShadeCarriers(); + + subIds.add(1); + mShadeCarrierGroupController.updateModernMobileIcons(subIds); + + verify(mShadeCarrier1, times(1)).removeModernMobileView(); + + verify(mShadeCarrier1).addModernMobileView(any()); + verify(mShadeCarrier2).addModernMobileView(any()); + verify(mShadeCarrier3, never()).addModernMobileView(any()); + } + + @TestableLooper.RunWithLooper(setAsMainLooper = true) + @Test + public void testUpdateModernMobileIcons_removeSubscription() { + setupWithNewPipeline(); + + List<Integer> subIds = new ArrayList<>(); + subIds.add(0); + subIds.add(1); + mShadeCarrierGroupController.updateModernMobileIcons(subIds); + + verify(mShadeCarrier1).addModernMobileView(any()); + verify(mShadeCarrier2).addModernMobileView(any()); + verify(mShadeCarrier3, never()).addModernMobileView(any()); + + resetShadeCarriers(); + + subIds.remove(1); + mShadeCarrierGroupController.updateModernMobileIcons(subIds); + + verify(mShadeCarrier1, times(1)).removeModernMobileView(); + verify(mShadeCarrier2, times(1)).removeModernMobileView(); + + verify(mShadeCarrier1).addModernMobileView(any()); + verify(mShadeCarrier2, never()).addModernMobileView(any()); + verify(mShadeCarrier3, never()).addModernMobileView(any()); + } + + @TestableLooper.RunWithLooper(setAsMainLooper = true) + @Test + public void testUpdateModernMobileIcons_removeSubscriptionOutOfOrder() { + setupWithNewPipeline(); + + List<Integer> subIds = new ArrayList<>(); + subIds.add(0); + subIds.add(1); + subIds.add(2); + mShadeCarrierGroupController.updateModernMobileIcons(subIds); + + verify(mShadeCarrier1).addModernMobileView(any()); + verify(mShadeCarrier2).addModernMobileView(any()); + verify(mShadeCarrier3).addModernMobileView(any()); + + resetShadeCarriers(); + + subIds.remove(1); + mShadeCarrierGroupController.updateModernMobileIcons(subIds); + + verify(mShadeCarrier1).removeModernMobileView(); + verify(mShadeCarrier2).removeModernMobileView(); + verify(mShadeCarrier3).removeModernMobileView(); + + verify(mShadeCarrier1).addModernMobileView(any()); + verify(mShadeCarrier2, never()).addModernMobileView(any()); + verify(mShadeCarrier3).addModernMobileView(any()); + } + + @TestableLooper.RunWithLooper(setAsMainLooper = true) + @Test + public void testProcessSubIdList_moreSubsThanSimSlots_listLimitedToMax() { + setupWithNewPipeline(); + + List<Integer> subIds = Arrays.asList(0, 1, 2, 2); + + assertThat(mShadeCarrierGroupController.processSubIdList(subIds).size()).isEqualTo(3); + } + + @TestableLooper.RunWithLooper(setAsMainLooper = true) + @Test + public void testProcessSubIdList_invalidSimSlotIndexFilteredOut() { + setupWithNewPipeline(); + + List<Integer> subIds = Arrays.asList(0, 1, -1); + + List<ShadeCarrierGroupController.IconData> processedSubs = + mShadeCarrierGroupController.processSubIdList(subIds); + assertThat(processedSubs).hasSize(2); + assertThat(processedSubs.get(0).subId).isNotEqualTo(-1); + assertThat(processedSubs.get(1).subId).isNotEqualTo(-1); + } + + @TestableLooper.RunWithLooper(setAsMainLooper = true) + @Test + public void testProcessSubIdList_indexGreaterThanSimSlotsFilteredOut() { + setupWithNewPipeline(); + + List<Integer> subIds = Arrays.asList(0, 4); + + List<ShadeCarrierGroupController.IconData> processedSubs = + mShadeCarrierGroupController.processSubIdList(subIds); + assertThat(processedSubs).hasSize(1); + assertThat(processedSubs.get(0).subId).isNotEqualTo(4); + } + + @Test public void testOnlyInternalViewsHaveClickableListener() { ArgumentCaptor<View.OnClickListener> captor = @@ -447,6 +612,12 @@ public class ShadeCarrierGroupControllerTest extends LeakCheckedTest { .isEqualTo(Settings.ACTION_WIRELESS_SETTINGS); } + private void resetShadeCarriers() { + reset(mShadeCarrier1); + reset(mShadeCarrier2); + reset(mShadeCarrier3); + } + private class FakeSlotIndexResolver implements ShadeCarrierGroupController.SlotIndexResolver { public boolean overrideInvalid; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt index 50ee6a31d389..ff2875355a6a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt @@ -52,12 +52,19 @@ class FakeMobileConnectionRepository( override val cdmaRoaming = MutableStateFlow(false) - override val networkName = - MutableStateFlow<NetworkNameModel>(NetworkNameModel.Default("default")) + override val networkName: MutableStateFlow<NetworkNameModel> = + MutableStateFlow(NetworkNameModel.Default(DEFAULT_NETWORK_NAME)) + + override val carrierName: MutableStateFlow<NetworkNameModel> = + MutableStateFlow(NetworkNameModel.Default(DEFAULT_NETWORK_NAME)) override val isAllowedDuringAirplaneMode = MutableStateFlow(false) fun setDataEnabled(enabled: Boolean) { _dataEnabled.value = enabled } + + companion object { + const val DEFAULT_NETWORK_NAME = "default name" + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt index 3591c1740329..99e4030e1192 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt @@ -75,7 +75,11 @@ class FakeMobileConnectionsRepository( override fun getRepoForSubId(subId: Int): MobileConnectionRepository { return subIdRepos[subId] - ?: FakeMobileConnectionRepository(subId, tableLogBuffer).also { subIdRepos[subId] = it } + ?: FakeMobileConnectionRepository( + subId, + tableLogBuffer, + ) + .also { subIdRepos[subId] = it } } override val defaultDataSubRatConfig = MutableStateFlow(MobileMappings.Config()) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt index 5a887ebcee80..d005972043d7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt @@ -243,13 +243,29 @@ class MobileRepositorySwitcherTest : SysuiTestCase() { private val IMMEDIATE = Dispatchers.Main.immediate private const val SUB_1_ID = 1 + private const val SUB_1_NAME = "Carrier $SUB_1_ID" private val SUB_1 = - mock<SubscriptionInfo>().also { whenever(it.subscriptionId).thenReturn(SUB_1_ID) } - private val MODEL_1 = SubscriptionModel(subscriptionId = SUB_1_ID) + mock<SubscriptionInfo>().also { + whenever(it.subscriptionId).thenReturn(SUB_1_ID) + whenever(it.carrierName).thenReturn(SUB_1_NAME) + } + private val MODEL_1 = + SubscriptionModel( + subscriptionId = SUB_1_ID, + carrierName = SUB_1_NAME, + ) private const val SUB_2_ID = 2 + private const val SUB_2_NAME = "Carrier $SUB_2_ID" private val SUB_2 = - mock<SubscriptionInfo>().also { whenever(it.subscriptionId).thenReturn(SUB_2_ID) } - private val MODEL_2 = SubscriptionModel(subscriptionId = SUB_2_ID) + mock<SubscriptionInfo>().also { + whenever(it.subscriptionId).thenReturn(SUB_2_ID) + whenever(it.carrierName).thenReturn(SUB_2_NAME) + } + private val MODEL_2 = + SubscriptionModel( + subscriptionId = SUB_2_ID, + carrierName = SUB_2_NAME, + ) } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionParameterizedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionParameterizedTest.kt index 7573b28c8a7f..57f97ec66a00 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionParameterizedTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionParameterizedTest.kt @@ -38,7 +38,6 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.Job import kotlinx.coroutines.cancel import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.collect import kotlinx.coroutines.launch import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.UnconfinedTestDispatcher @@ -140,6 +139,7 @@ internal class DemoMobileConnectionParameterizedTest(private val testCase: TestC launch { conn.carrierNetworkChangeActive.collect {} } launch { conn.isRoaming.collect {} } launch { conn.networkName.collect {} } + launch { conn.carrierName.collect {} } launch { conn.isEmergencyOnly.collect {} } launch { conn.dataConnectionState.collect {} } } @@ -163,6 +163,8 @@ internal class DemoMobileConnectionParameterizedTest(private val testCase: TestC assertThat(conn.isRoaming.value).isEqualTo(model.roaming) assertThat(conn.networkName.value) .isEqualTo(NetworkNameModel.IntentDerived(model.name)) + assertThat(conn.carrierName.value) + .isEqualTo(NetworkNameModel.SubscriptionDerived("${model.name} ${model.subId}")) // TODO(b/261029387): check these once we start handling them assertThat(conn.isEmergencyOnly.value).isFalse() diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepositoryTest.kt index efaf15235b46..2712b70a9745 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepositoryTest.kt @@ -546,6 +546,7 @@ class DemoMobileConnectionsRepositoryTest : SysuiTestCase() { launch { conn.carrierNetworkChangeActive.collect {} } launch { conn.isRoaming.collect {} } launch { conn.networkName.collect {} } + launch { conn.carrierName.collect {} } launch { conn.isEmergencyOnly.collect {} } launch { conn.dataConnectionState.collect {} } } @@ -571,6 +572,8 @@ class DemoMobileConnectionsRepositoryTest : SysuiTestCase() { assertThat(conn.isRoaming.value).isEqualTo(model.roaming) assertThat(conn.networkName.value) .isEqualTo(NetworkNameModel.IntentDerived(model.name)) + assertThat(conn.carrierName.value) + .isEqualTo(NetworkNameModel.SubscriptionDerived("${model.name} ${model.subId}")) // TODO(b/261029387) check these once we start handling them assertThat(conn.isEmergencyOnly.value).isFalse() diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt index 3dd2eaff7bce..9c0cb17700a6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt @@ -26,6 +26,7 @@ import com.android.systemui.coroutines.collectLastValue import com.android.systemui.log.table.TableLogBuffer import com.android.systemui.log.table.TableLogBufferFactory import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel +import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionRepository import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.FullMobileConnectionRepository.Companion.COL_EMERGENCY @@ -43,6 +44,7 @@ import com.google.common.truth.Truth.assertThat import java.io.PrintWriter import java.io.StringWriter import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.test.TestScope @@ -79,28 +81,51 @@ class FullMobileConnectionRepositoryTest : SysuiTestCase() { private val mobileFactory = mock<MobileConnectionRepositoryImpl.Factory>() private val carrierMergedFactory = mock<CarrierMergedConnectionRepository.Factory>() + private val subscriptionModel = + MutableStateFlow( + SubscriptionModel( + subscriptionId = SUB_ID, + carrierName = DEFAULT_NAME, + ) + ) + private lateinit var mobileRepo: FakeMobileConnectionRepository private lateinit var carrierMergedRepo: FakeMobileConnectionRepository @Before fun setUp() { - mobileRepo = FakeMobileConnectionRepository(SUB_ID, tableLogBuffer) + mobileRepo = + FakeMobileConnectionRepository( + SUB_ID, + tableLogBuffer, + ) carrierMergedRepo = - FakeMobileConnectionRepository(SUB_ID, tableLogBuffer).apply { - // Mimicks the real carrier merged repository - this.isAllowedDuringAirplaneMode.value = true - } + FakeMobileConnectionRepository( + SUB_ID, + tableLogBuffer, + ) + .apply { + // Mimicks the real carrier merged repository + this.isAllowedDuringAirplaneMode.value = true + } whenever( mobileFactory.build( eq(SUB_ID), any(), - eq(DEFAULT_NAME), + any(), + eq(DEFAULT_NAME_MODEL), eq(SEP), ) ) .thenReturn(mobileRepo) - whenever(carrierMergedFactory.build(eq(SUB_ID), any())).thenReturn(carrierMergedRepo) + whenever( + carrierMergedFactory.build( + eq(SUB_ID), + any(), + ) + ) + .thenReturn(carrierMergedRepo) } @Test @@ -120,7 +145,8 @@ class FullMobileConnectionRepositoryTest : SysuiTestCase() { .build( SUB_ID, tableLogBuffer, - DEFAULT_NAME, + subscriptionModel, + DEFAULT_NAME_MODEL, SEP, ) } @@ -138,7 +164,11 @@ class FullMobileConnectionRepositoryTest : SysuiTestCase() { assertThat(underTest.activeRepo.value).isEqualTo(mobileRepo) assertThat(underTest.operatorAlphaShort.value).isEqualTo(nonCarrierMergedName) - verify(carrierMergedFactory, never()).build(SUB_ID, tableLogBuffer) + verify(carrierMergedFactory, never()) + .build( + SUB_ID, + tableLogBuffer, + ) } @Test @@ -348,7 +378,8 @@ class FullMobileConnectionRepositoryTest : SysuiTestCase() { factory.build( SUB_ID, startingIsCarrierMerged = false, - DEFAULT_NAME, + subscriptionModel, + DEFAULT_NAME_MODEL, SEP, ) @@ -356,7 +387,8 @@ class FullMobileConnectionRepositoryTest : SysuiTestCase() { factory.build( SUB_ID, startingIsCarrierMerged = false, - DEFAULT_NAME, + subscriptionModel, + DEFAULT_NAME_MODEL, SEP, ) @@ -388,7 +420,8 @@ class FullMobileConnectionRepositoryTest : SysuiTestCase() { factory.build( SUB_ID, startingIsCarrierMerged = false, - DEFAULT_NAME, + subscriptionModel, + DEFAULT_NAME_MODEL, SEP, ) @@ -397,7 +430,8 @@ class FullMobileConnectionRepositoryTest : SysuiTestCase() { factory.build( SUB_ID, startingIsCarrierMerged = true, - DEFAULT_NAME, + subscriptionModel, + DEFAULT_NAME_MODEL, SEP, ) @@ -623,7 +657,8 @@ class FullMobileConnectionRepositoryTest : SysuiTestCase() { SUB_ID, startingIsCarrierMerged, tableLogBuffer, - DEFAULT_NAME, + subscriptionModel, + DEFAULT_NAME_MODEL, SEP, testScope.backgroundScope, mobileFactory, @@ -639,8 +674,9 @@ class FullMobileConnectionRepositoryTest : SysuiTestCase() { val realRepo = MobileConnectionRepositoryImpl( SUB_ID, - defaultNetworkName = NetworkNameModel.Default("default"), - networkNameSeparator = SEP, + subscriptionModel, + DEFAULT_NAME_MODEL, + SEP, telephonyManager, systemUiCarrierConfig = mock(), fakeBroadcastDispatcher, @@ -654,7 +690,8 @@ class FullMobileConnectionRepositoryTest : SysuiTestCase() { mobileFactory.build( eq(SUB_ID), any(), - eq(DEFAULT_NAME), + any(), + eq(DEFAULT_NAME_MODEL), eq(SEP), ) ) @@ -677,7 +714,13 @@ class FullMobileConnectionRepositoryTest : SysuiTestCase() { testScope.backgroundScope, wifiRepository, ) - whenever(carrierMergedFactory.build(eq(SUB_ID), any())).thenReturn(realRepo) + whenever( + carrierMergedFactory.build( + eq(SUB_ID), + any(), + ) + ) + .thenReturn(realRepo) return realRepo } @@ -690,7 +733,8 @@ class FullMobileConnectionRepositoryTest : SysuiTestCase() { private companion object { const val SUB_ID = 42 - private val DEFAULT_NAME = NetworkNameModel.Default("default name") + private val DEFAULT_NAME = "default name" + private val DEFAULT_NAME_MODEL = NetworkNameModel.Default(DEFAULT_NAME) private const val SEP = "-" private const val BUFFER_SEPARATOR = "|" } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt index 1ff737bfc137..e50e5e31a786 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt @@ -62,6 +62,7 @@ import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetwork import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType.DefaultNetworkType import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType.OverrideNetworkType import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType.UnknownNetworkType +import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel import com.android.systemui.statusbar.pipeline.mobile.data.model.SystemUiCarrierConfig import com.android.systemui.statusbar.pipeline.mobile.data.model.SystemUiCarrierConfigTest.Companion.configWithOverride import com.android.systemui.statusbar.pipeline.mobile.data.model.SystemUiCarrierConfigTest.Companion.createTestConfig @@ -78,6 +79,7 @@ import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.test.TestScope @@ -109,6 +111,14 @@ class MobileConnectionRepositoryTest : SysuiTestCase() { private val testDispatcher = UnconfinedTestDispatcher() private val testScope = TestScope(testDispatcher) + private val subscriptionModel: MutableStateFlow<SubscriptionModel?> = + MutableStateFlow( + SubscriptionModel( + subscriptionId = SUB_1_ID, + carrierName = DEFAULT_NAME, + ) + ) + @Before fun setUp() { MockitoAnnotations.initMocks(this) @@ -119,7 +129,8 @@ class MobileConnectionRepositoryTest : SysuiTestCase() { underTest = MobileConnectionRepositoryImpl( SUB_1_ID, - DEFAULT_NAME, + subscriptionModel, + DEFAULT_NAME_MODEL, SEP, telephonyManager, systemUiCarrierConfig, @@ -179,6 +190,7 @@ class MobileConnectionRepositoryTest : SysuiTestCase() { // gsmLevel updates, no change to cdmaLevel strength = signalStrength(gsmLevel = 3, cdmaLevel = 2, isGsm = true) + callback.onSignalStrengthsChanged(strength) assertThat(latest).isEqualTo(2) @@ -638,12 +650,51 @@ class MobileConnectionRepositoryTest : SysuiTestCase() { } @Test + fun networkNameForSubId_updates() = + testScope.runTest { + var latest: NetworkNameModel? = null + val job = underTest.carrierName.onEach { latest = it }.launchIn(this) + + subscriptionModel.value = + SubscriptionModel( + subscriptionId = SUB_1_ID, + carrierName = DEFAULT_NAME, + ) + + assertThat(latest?.name).isEqualTo(DEFAULT_NAME) + + val updatedName = "Derived Carrier" + subscriptionModel.value = + SubscriptionModel( + subscriptionId = SUB_1_ID, + carrierName = updatedName, + ) + + assertThat(latest?.name).isEqualTo(updatedName) + + job.cancel() + } + + @Test + fun networkNameForSubId_defaultWhenSubscriptionModelNull() = + testScope.runTest { + var latest: NetworkNameModel? = null + val job = underTest.carrierName.onEach { latest = it }.launchIn(this) + + subscriptionModel.value = null + + assertThat(latest?.name).isEqualTo(DEFAULT_NAME) + + job.cancel() + } + + @Test fun networkName_default() = testScope.runTest { var latest: NetworkNameModel? = null val job = underTest.networkName.onEach { latest = it }.launchIn(this) - assertThat(latest).isEqualTo(DEFAULT_NAME) + assertThat(latest).isEqualTo(DEFAULT_NAME_MODEL) job.cancel() } @@ -701,7 +752,7 @@ class MobileConnectionRepositoryTest : SysuiTestCase() { fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(context, intentWithoutInfo) - assertThat(latest).isEqualTo(DEFAULT_NAME) + assertThat(latest).isEqualTo(DEFAULT_NAME_MODEL) job.cancel() } @@ -852,8 +903,9 @@ class MobileConnectionRepositoryTest : SysuiTestCase() { companion object { private const val SUB_1_ID = 1 - private val DEFAULT_NAME = NetworkNameModel.Default("default name") - private const val SEP = "-" + private val DEFAULT_NAME = "Fake Mobile Network" + private val DEFAULT_NAME_MODEL = NetworkNameModel.Default(DEFAULT_NAME) + private val SEP = "-" private const val SPN = "testSpn" private const val PLMN = "testPlmn" diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionTelephonySmokeTests.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionTelephonySmokeTests.kt index 4f15aed00230..ea60aa74f12b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionTelephonySmokeTests.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionTelephonySmokeTests.kt @@ -36,6 +36,7 @@ import com.android.systemui.statusbar.pipeline.mobile.data.MobileInputLogger import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType +import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel import com.android.systemui.statusbar.pipeline.mobile.data.model.SystemUiCarrierConfig import com.android.systemui.statusbar.pipeline.mobile.data.model.SystemUiCarrierConfigTest import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository @@ -47,6 +48,7 @@ import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.test.TestScope @@ -97,6 +99,7 @@ class MobileConnectionTelephonySmokeTests : SysuiTestCase() { @Mock private lateinit var telephonyManager: TelephonyManager @Mock private lateinit var logger: MobileInputLogger @Mock private lateinit var tableLogger: TableLogBuffer + @Mock private lateinit var subscriptionModel: StateFlow<SubscriptionModel?> private val mobileMappings = FakeMobileMappingsProxy() private val systemUiCarrierConfig = @@ -113,11 +116,16 @@ class MobileConnectionTelephonySmokeTests : SysuiTestCase() { MockitoAnnotations.initMocks(this) whenever(telephonyManager.subscriptionId).thenReturn(SUB_1_ID) - connectionsRepo = FakeMobileConnectionsRepository(mobileMappings, tableLogger) + connectionsRepo = + FakeMobileConnectionsRepository( + mobileMappings, + tableLogger, + ) underTest = MobileConnectionRepositoryImpl( SUB_1_ID, + subscriptionModel, DEFAULT_NAME, SEP, telephonyManager, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt index c8b6f13d6902..fd05cc495692 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt @@ -1190,30 +1190,36 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() { companion object { // Subscription 1 private const val SUB_1_ID = 1 + private const val SUB_1_NAME = "Carrier $SUB_1_ID" private val GROUP_1 = ParcelUuid(UUID.randomUUID()) private val SUB_1 = mock<SubscriptionInfo>().also { whenever(it.subscriptionId).thenReturn(SUB_1_ID) whenever(it.groupUuid).thenReturn(GROUP_1) + whenever(it.carrierName).thenReturn(SUB_1_NAME) } private val MODEL_1 = SubscriptionModel( subscriptionId = SUB_1_ID, groupUuid = GROUP_1, + carrierName = SUB_1_NAME, ) // Subscription 2 private const val SUB_2_ID = 2 + private const val SUB_2_NAME = "Carrier $SUB_2_ID" private val GROUP_2 = ParcelUuid(UUID.randomUUID()) private val SUB_2 = mock<SubscriptionInfo>().also { whenever(it.subscriptionId).thenReturn(SUB_2_ID) whenever(it.groupUuid).thenReturn(GROUP_2) + whenever(it.carrierName).thenReturn(SUB_2_NAME) } private val MODEL_2 = SubscriptionModel( subscriptionId = SUB_2_ID, groupUuid = GROUP_2, + carrierName = SUB_2_NAME, ) // Subs 3 and 4 are considered to be in the same group ------------------------------------ @@ -1242,9 +1248,14 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() { // Carrier merged subscription private const val SUB_CM_ID = 5 + private const val SUB_CM_NAME = "Carrier $SUB_CM_ID" private val SUB_CM = - mock<SubscriptionInfo>().also { whenever(it.subscriptionId).thenReturn(SUB_CM_ID) } - private val MODEL_CM = SubscriptionModel(subscriptionId = SUB_CM_ID) + mock<SubscriptionInfo>().also { + whenever(it.subscriptionId).thenReturn(SUB_CM_ID) + whenever(it.carrierName).thenReturn(SUB_CM_NAME) + } + private val MODEL_CM = + SubscriptionModel(subscriptionId = SUB_CM_ID, carrierName = SUB_CM_NAME) private val WIFI_INFO_CM = mock<WifiInfo>().apply { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt index 8d1da69d6877..a3df785c5dae 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt @@ -44,6 +44,8 @@ class FakeMobileIconInteractor( override val mobileIsDefault = MutableStateFlow(true) + override val isSingleCarrier = MutableStateFlow(true) + override val networkTypeIconGroup = MutableStateFlow<NetworkTypeIconModel>( NetworkTypeIconModel.DefaultIcon(TelephonyIcons.THREE_G) @@ -51,6 +53,8 @@ class FakeMobileIconInteractor( override val networkName = MutableStateFlow(NetworkNameModel.IntentDerived("demo mode")) + override val carrierName = MutableStateFlow("demo mode") + private val _isEmergencyOnly = MutableStateFlow(false) override val isEmergencyOnly = _isEmergencyOnly diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt index b2bbcfd3d6ef..82b7ec41d148 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt @@ -64,6 +64,8 @@ class FakeMobileIconsInteractor( override val mobileIsDefault = MutableStateFlow(false) + override val isSingleCarrier = MutableStateFlow(true) + private val _defaultMobileIconMapping = MutableStateFlow(TEST_MAPPING) override val defaultMobileIconMapping = _defaultMobileIconMapping diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt index 58d3804b7155..e3c59adef529 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt @@ -29,6 +29,7 @@ import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameMode import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType.CarrierMergedNetworkType import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType.DefaultNetworkType import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType.OverrideNetworkType +import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionRepository import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconsInteractor.Companion.FIVE_G_OVERRIDE import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconsInteractor.Companion.FOUR_G @@ -40,6 +41,7 @@ import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.test.TestScope @@ -56,6 +58,15 @@ class MobileIconInteractorTest : SysuiTestCase() { private lateinit var underTest: MobileIconInteractor private val mobileMappingsProxy = FakeMobileMappingsProxy() private val mobileIconsInteractor = FakeMobileIconsInteractor(mobileMappingsProxy, mock()) + + private val subscriptionModel = + MutableStateFlow( + SubscriptionModel( + subscriptionId = SUB_1_ID, + carrierName = DEFAULT_NAME, + ) + ) + private val connectionRepository = FakeMobileConnectionRepository(SUB_1_ID, mock()) private val testDispatcher = UnconfinedTestDispatcher() @@ -432,7 +443,7 @@ class MobileIconInteractorTest : SysuiTestCase() { } @Test - fun networkName_usesOperatorAlphaShotWhenNonNullAndRepoIsDefault() = + fun networkName_usesOperatorAlphaShortWhenNonNullAndRepoIsDefault() = testScope.runTest { var latest: NetworkNameModel? = null val job = underTest.networkName.onEach { latest = it }.launchIn(this) @@ -440,7 +451,7 @@ class MobileIconInteractorTest : SysuiTestCase() { val testOperatorName = "operatorAlphaShort" // Default network name, operator name is non-null, uses the operator name - connectionRepository.networkName.value = DEFAULT_NAME + connectionRepository.networkName.value = DEFAULT_NAME_MODEL connectionRepository.operatorAlphaShort.value = testOperatorName assertThat(latest).isEqualTo(NetworkNameModel.IntentDerived(testOperatorName)) @@ -448,10 +459,39 @@ class MobileIconInteractorTest : SysuiTestCase() { // Default network name, operator name is null, uses the default connectionRepository.operatorAlphaShort.value = null + assertThat(latest).isEqualTo(DEFAULT_NAME_MODEL) + + // Derived network name, operator name non-null, uses the derived name + connectionRepository.networkName.value = DERIVED_NAME_MODEL + connectionRepository.operatorAlphaShort.value = testOperatorName + + assertThat(latest).isEqualTo(DERIVED_NAME_MODEL) + + job.cancel() + } + + @Test + fun networkNameForSubId_usesOperatorAlphaShortWhenNonNullAndRepoIsDefault() = + testScope.runTest { + var latest: String? = null + val job = underTest.carrierName.onEach { latest = it }.launchIn(this) + + val testOperatorName = "operatorAlphaShort" + + // Default network name, operator name is non-null, uses the operator name + connectionRepository.carrierName.value = DEFAULT_NAME_MODEL + connectionRepository.operatorAlphaShort.value = testOperatorName + + assertThat(latest).isEqualTo(testOperatorName) + + // Default network name, operator name is null, uses the default + connectionRepository.operatorAlphaShort.value = null + assertThat(latest).isEqualTo(DEFAULT_NAME) // Derived network name, operator name non-null, uses the derived name - connectionRepository.networkName.value = DERIVED_NAME + connectionRepository.carrierName.value = + NetworkNameModel.SubscriptionDerived(DERIVED_NAME) connectionRepository.operatorAlphaShort.value = testOperatorName assertThat(latest).isEqualTo(DERIVED_NAME) @@ -460,6 +500,21 @@ class MobileIconInteractorTest : SysuiTestCase() { } @Test + fun isSingleCarrier_matchesParent() = + testScope.runTest { + var latest: Boolean? = null + val job = underTest.isSingleCarrier.onEach { latest = it }.launchIn(this) + + mobileIconsInteractor.isSingleCarrier.value = true + assertThat(latest).isTrue() + + mobileIconsInteractor.isSingleCarrier.value = false + assertThat(latest).isFalse() + + job.cancel() + } + + @Test fun isForceHidden_matchesParent() = testScope.runTest { var latest: Boolean? = null @@ -494,6 +549,7 @@ class MobileIconInteractorTest : SysuiTestCase() { mobileIconsInteractor.activeDataConnectionHasDataEnabled, mobileIconsInteractor.alwaysShowDataRatIcon, mobileIconsInteractor.alwaysUseCdmaLevel, + mobileIconsInteractor.isSingleCarrier, mobileIconsInteractor.mobileIsDefault, mobileIconsInteractor.defaultMobileIconMapping, mobileIconsInteractor.defaultMobileIconGroup, @@ -510,7 +566,9 @@ class MobileIconInteractorTest : SysuiTestCase() { private const val SUB_1_ID = 1 - private val DEFAULT_NAME = NetworkNameModel.Default("test default name") - private val DERIVED_NAME = NetworkNameModel.IntentDerived("test derived name") + private val DEFAULT_NAME = "test default name" + private val DEFAULT_NAME_MODEL = NetworkNameModel.Default(DEFAULT_NAME) + private val DERIVED_NAME = "test derived name" + private val DERIVED_NAME_MODEL = NetworkNameModel.IntentDerived(DERIVED_NAME) } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt index 1fb76b048d47..3e6f90931b87 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt @@ -527,6 +527,57 @@ class MobileIconsInteractorTest : SysuiTestCase() { } @Test + fun isSingleCarrier_zeroSubscriptions_false() = + testScope.runTest { + var latest: Boolean? = true + val job = underTest.isSingleCarrier.onEach { latest = it }.launchIn(this) + + connectionsRepository.setSubscriptions(emptyList()) + assertThat(latest).isFalse() + + job.cancel() + } + + @Test + fun isSingleCarrier_oneSubscription_true() = + testScope.runTest { + var latest: Boolean? = false + val job = underTest.isSingleCarrier.onEach { latest = it }.launchIn(this) + + connectionsRepository.setSubscriptions(listOf(SUB_1)) + assertThat(latest).isTrue() + + job.cancel() + } + + @Test + fun isSingleCarrier_twoSubscriptions_false() = + testScope.runTest { + var latest: Boolean? = true + val job = underTest.isSingleCarrier.onEach { latest = it }.launchIn(this) + + connectionsRepository.setSubscriptions(listOf(SUB_1, SUB_2)) + assertThat(latest).isFalse() + + job.cancel() + } + + @Test + fun isSingleCarrier_updates() = + testScope.runTest { + var latest: Boolean? = false + val job = underTest.isSingleCarrier.onEach { latest = it }.launchIn(this) + + connectionsRepository.setSubscriptions(listOf(SUB_1)) + assertThat(latest).isTrue() + + connectionsRepository.setSubscriptions(listOf(SUB_1, SUB_2)) + assertThat(latest).isFalse() + + job.cancel() + } + + @Test fun mobileIsDefault_mobileFalseAndCarrierMergedFalse_false() = testScope.runTest { var latest: Boolean? = null @@ -745,6 +796,7 @@ class MobileIconsInteractorTest : SysuiTestCase() { subscriptionId = subscriptionIds.first, isOpportunistic = opportunistic.first, groupUuid = groupUuid, + carrierName = "Carrier ${subscriptionIds.first}" ) val sub2 = @@ -752,6 +804,7 @@ class MobileIconsInteractorTest : SysuiTestCase() { subscriptionId = subscriptionIds.second, isOpportunistic = opportunistic.second, groupUuid = groupUuid, + carrierName = "Carrier ${opportunistic.second}" ) return Pair(sub1, sub2) @@ -760,11 +813,13 @@ class MobileIconsInteractorTest : SysuiTestCase() { companion object { private const val SUB_1_ID = 1 - private val SUB_1 = SubscriptionModel(subscriptionId = SUB_1_ID) + private val SUB_1 = + SubscriptionModel(subscriptionId = SUB_1_ID, carrierName = "Carrier $SUB_1_ID") private val CONNECTION_1 = FakeMobileConnectionRepository(SUB_1_ID, mock()) private const val SUB_2_ID = 2 - private val SUB_2 = SubscriptionModel(subscriptionId = SUB_2_ID) + private val SUB_2 = + SubscriptionModel(subscriptionId = SUB_2_ID, carrierName = "Carrier $SUB_2_ID") private val CONNECTION_2 = FakeMobileConnectionRepository(SUB_2_ID, mock()) private const val SUB_3_ID = 3 @@ -773,6 +828,7 @@ class MobileIconsInteractorTest : SysuiTestCase() { subscriptionId = SUB_3_ID, isOpportunistic = true, groupUuid = ParcelUuid(UUID.randomUUID()), + carrierName = "Carrier $SUB_3_ID" ) private val CONNECTION_3 = FakeMobileConnectionRepository(SUB_3_ID, mock()) @@ -782,6 +838,7 @@ class MobileIconsInteractorTest : SysuiTestCase() { subscriptionId = SUB_4_ID, isOpportunistic = true, groupUuid = ParcelUuid(UUID.randomUUID()), + carrierName = "Carrier $SUB_4_ID" ) private val CONNECTION_4 = FakeMobileConnectionRepository(SUB_4_ID, mock()) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelTest.kt index f0458fa83d38..065dfbabf051 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelTest.kt @@ -92,15 +92,31 @@ class MobileIconsViewModelTest : SysuiTestCase() { interactor.filteredSubscriptions.value = listOf( - SubscriptionModel(subscriptionId = 1, isOpportunistic = false), + SubscriptionModel( + subscriptionId = 1, + isOpportunistic = false, + carrierName = "Carrier 1", + ), ) assertThat(latest).isEqualTo(listOf(1)) interactor.filteredSubscriptions.value = listOf( - SubscriptionModel(subscriptionId = 2, isOpportunistic = false), - SubscriptionModel(subscriptionId = 5, isOpportunistic = true), - SubscriptionModel(subscriptionId = 7, isOpportunistic = true), + SubscriptionModel( + subscriptionId = 2, + isOpportunistic = false, + carrierName = "Carrier 2", + ), + SubscriptionModel( + subscriptionId = 5, + isOpportunistic = true, + carrierName = "Carrier 5", + ), + SubscriptionModel( + subscriptionId = 7, + isOpportunistic = true, + carrierName = "Carrier 7", + ), ) assertThat(latest).isEqualTo(listOf(2, 5, 7)) @@ -138,6 +154,33 @@ class MobileIconsViewModelTest : SysuiTestCase() { } @Test + fun caching_mobileIconInteractorIsReusedForSameSubId() = + testScope.runTest { + val interactor1 = underTest.mobileIconInteractorForSub(1) + val interactor2 = underTest.mobileIconInteractorForSub(1) + + assertThat(interactor1).isSameInstanceAs(interactor2) + } + + @Test + fun caching_invalidInteractorssAreRemovedFromCacheWhenSubDisappears() = + testScope.runTest { + // Retrieve interactors to trigger caching + val interactor1 = underTest.mobileIconInteractorForSub(1) + val interactor2 = underTest.mobileIconInteractorForSub(2) + + // Both impls are cached + assertThat(underTest.mobileIconInteractorSubIdCache) + .containsExactly(1, interactor1, 2, interactor2) + + // SUB_1 is removed from the list... + interactor.filteredSubscriptions.value = listOf(SUB_2) + + // ... and dropped from the cache + assertThat(underTest.mobileIconInteractorSubIdCache).containsExactly(2, interactor2) + } + + @Test fun firstMobileSubShowingNetworkTypeIcon_noSubs_false() = testScope.runTest { var latest: Boolean? = null @@ -308,8 +351,23 @@ class MobileIconsViewModelTest : SysuiTestCase() { } companion object { - private val SUB_1 = SubscriptionModel(subscriptionId = 1, isOpportunistic = false) - private val SUB_2 = SubscriptionModel(subscriptionId = 2, isOpportunistic = false) - private val SUB_3 = SubscriptionModel(subscriptionId = 3, isOpportunistic = false) + private val SUB_1 = + SubscriptionModel( + subscriptionId = 1, + isOpportunistic = false, + carrierName = "Carrier 1", + ) + private val SUB_2 = + SubscriptionModel( + subscriptionId = 2, + isOpportunistic = false, + carrierName = "Carrier 2", + ) + private val SUB_3 = + SubscriptionModel( + subscriptionId = 3, + isOpportunistic = false, + carrierName = "Carrier 3", + ) } } |