Merge changes Iaf91e560,Ib40dcf0a into udc-qpr-dev
* changes: [2/2] Migrating ShadeCarrierGroup mobile icons to modern status bar data pipeline [1/2] Update new status bar data pipeline to support names for multiple sims
This commit is contained in:
committed by
Android (Google) Code Review
commit
cac31846bb
42
packages/SystemUI/res-keyguard/layout/shade_carrier_new.xml
Normal file
42
packages/SystemUI/res-keyguard/layout/shade_carrier_new.xml
Normal file
@@ -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>
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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].
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
@@ -115,9 +115,17 @@ 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.
|
||||
*/
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -108,6 +108,8 @@ class CarrierMergedConnectionRepository(
|
||||
NetworkNameModel.SimDerived(telephonyManager.simOperatorName),
|
||||
)
|
||||
|
||||
override val carrierName: StateFlow<NetworkNameModel> = networkName
|
||||
|
||||
override val numberOfLevels: StateFlow<Int> =
|
||||
wifiRepository.wifiNetwork
|
||||
.map {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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" },
|
||||
)
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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 } }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
@@ -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) }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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 = "|"
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -637,13 +649,52 @@ class MobileConnectionRepositoryTest : SysuiTestCase() {
|
||||
job.cancel()
|
||||
}
|
||||
|
||||
@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"
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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)
|
||||
@@ -459,6 +499,21 @@ class MobileIconInteractorTest : SysuiTestCase() {
|
||||
job.cancel()
|
||||
}
|
||||
|
||||
@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 {
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -526,6 +526,57 @@ class MobileIconsInteractorTest : SysuiTestCase() {
|
||||
job.cancel()
|
||||
}
|
||||
|
||||
@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 {
|
||||
@@ -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())
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -137,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 {
|
||||
@@ -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",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user