diff options
| author | 2023-07-10 15:09:23 -0700 | |
|---|---|---|
| committer | 2023-08-18 21:34:27 +0000 | |
| commit | 062fbc4578dacfbe8a04d5299bc8bb9f7f1437fa (patch) | |
| tree | 30ee506a210489e6e47db779a01b05d6b8515e72 | |
| parent | 1685e6b9319bf2a08aaeeec245ed10f1f2e3af91 (diff) | |
[2/2] Migrating ShadeCarrierGroup mobile icons to modern status bar data pipeline
Hooks up the ShadeCarrierGroup to depend on only the new data pipeline. The UI is left mostly as-is for now, with the change being the ShadeCarrier no longer having its own version of the mobile icon layout, and the text view for the carrier name has been moved inside the ModernStatusBarView.
Bug: 288631206
Test: Manually verified no visual change with flag enabled, with both 1 and 2 sims
Test: Added/updated unit tests
Change-Id: Iaf91e5606640c5fad6e51ce3b6607e65f14d3bcb
17 files changed, 623 insertions, 35 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 b92bf6b61669..aec797498916 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/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/ui/viewmodel/MobileIconsViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelTest.kt index e42515e5871d..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 @@ -154,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 |