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 0000000000000..952f056b30239
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/layout/shade_carrier_new.xml
@@ -0,0 +1,42 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index b92bf6b616696..aec7974989160 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 8586828af0cdd..8612cdf12c6e4 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 ad49b267c6ac9..98d8a53b83c8a 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 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 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 processSubIdList(List 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 5ace22695ec37..32e5c355889a2 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 6e51ed0eba378..c695773086082 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,10 +29,18 @@ 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/ui/MobileUiAdapter.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileUiAdapter.kt
index d7fcf4876c285..02e50a007f50d 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? = 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) {
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 cea6654a48de0..2af6795b39c41 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 55bc8d58be23e..4b2fb4373957a 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(R.id.mobile_group)
@@ -68,12 +69,12 @@ object MobileIconBinder {
// TODO(b/238425913): We should log this visibility state.
@StatusBarIconView.VisibleState
- val visibilityState: MutableStateFlow = MutableStateFlow(STATE_HIDDEN)
+ val visibilityState: MutableStateFlow = MutableStateFlow(initialVisibilityState)
val iconTint: MutableStateFlow = MutableStateFlow(viewModel.defaultColor)
val decorTint: MutableStateFlow = 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 0000000000000..081e1015e26e6
--- /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 0000000000000..f407127f3161e
--- /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(R.id.mobile_combo)
+ iconView.initView(slot) {
+ MobileIconBinder.bind(iconView, viewModel, STATE_ICON, logger)
+ }
+ logger.logNewViewBinding(it, viewModel)
+
+ val textView = it.requireViewById(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 4144293d5ccd4..68d02de51dc9f 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 a51982c41255b..e7c311d7ec74d 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 =
+ 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 5cf887e4d41ac..216afb987f919 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()
+ @VisibleForTesting
+ val mobileIconInteractorSubIdCache = mutableMapOf()
val subscriptionIdsFlow: StateFlow> =
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) {
+ @VisibleForTesting
+ fun mobileIconInteractorForSub(subId: Int): MobileIconInteractor {
+ return mobileIconInteractorSubIdCache[subId]
+ ?: interactor.createMobileConnectionInteractorForSubId(subId).also {
+ mobileIconInteractorSubIdCache[subId] = it
+ }
+ }
+
+ private fun invalidateCaches(subIds: List) {
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 cd5b92cf24c74..00bd616e04eac 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 31bfa3fdf8cba..5fa6b3a15d351 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 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 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 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 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 subIds = Arrays.asList(0, 1, -1);
+
+ List 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 subIds = Arrays.asList(0, 4);
+
+ List processedSubs =
+ mShadeCarrierGroupController.processSubIdList(subIds);
+ assertThat(processedSubs).hasSize(1);
+ assertThat(processedSubs.get(0).subId).isNotEqualTo(4);
+ }
+
+
@Test
public void testOnlyInternalViewsHaveClickableListener() {
ArgumentCaptor 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/ui/viewmodel/MobileIconsViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelTest.kt
index e42515e5871de..065dfbabf051c 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
@@ -153,6 +153,33 @@ class MobileIconsViewModelTest : SysuiTestCase() {
assertThat(underTest.mobileIconSubIdCache).containsExactly(2, model2.commonImpl)
}
+ @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 {