Merge changes I4977aa47,I87686213,If4bd3e31,I727ad3ca,Ib02a1eb6 into tm-qpr-dev

* changes:
  [Status bar refactor] Fix cache removal exception
  Add a SubscriptionModel
  Rename MobileSubscriptionModel to MobileConnectionModel
  Better mapping lookup for demo mode
  Support new pipeline demo mode in the old view presenter
This commit is contained in:
Evan Laird
2022-12-08 04:43:54 +00:00
committed by Android (Google) Code Review
27 changed files with 580 additions and 352 deletions

View File

@@ -40,6 +40,8 @@ import com.android.systemui.statusbar.StatusIconDisplayable;
import com.android.systemui.statusbar.connectivity.ui.MobileContextProvider; import com.android.systemui.statusbar.connectivity.ui.MobileContextProvider;
import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.MobileIconState; import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.MobileIconState;
import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.WifiIconState; import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.WifiIconState;
import com.android.systemui.statusbar.pipeline.mobile.ui.view.ModernStatusBarMobileView;
import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsViewModel;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@@ -50,20 +52,25 @@ public class DemoStatusIcons extends StatusIconContainer implements DemoMode, Da
private final LinearLayout mStatusIcons; private final LinearLayout mStatusIcons;
private final ArrayList<StatusBarMobileView> mMobileViews = new ArrayList<>(); private final ArrayList<StatusBarMobileView> mMobileViews = new ArrayList<>();
private final ArrayList<ModernStatusBarMobileView> mModernMobileViews = new ArrayList<>();
private final int mIconSize; private final int mIconSize;
private StatusBarWifiView mWifiView; private StatusBarWifiView mWifiView;
private boolean mDemoMode; private boolean mDemoMode;
private int mColor; private int mColor;
private final MobileIconsViewModel mMobileIconsViewModel;
public DemoStatusIcons( public DemoStatusIcons(
LinearLayout statusIcons, LinearLayout statusIcons,
MobileIconsViewModel mobileIconsViewModel,
int iconSize int iconSize
) { ) {
super(statusIcons.getContext()); super(statusIcons.getContext());
mStatusIcons = statusIcons; mStatusIcons = statusIcons;
mIconSize = iconSize; mIconSize = iconSize;
mColor = DarkIconDispatcher.DEFAULT_ICON_TINT; mColor = DarkIconDispatcher.DEFAULT_ICON_TINT;
mMobileIconsViewModel = mobileIconsViewModel;
if (statusIcons instanceof StatusIconContainer) { if (statusIcons instanceof StatusIconContainer) {
setShouldRestrictIcons(((StatusIconContainer) statusIcons).isRestrictingIcons()); setShouldRestrictIcons(((StatusIconContainer) statusIcons).isRestrictingIcons());
@@ -71,7 +78,7 @@ public class DemoStatusIcons extends StatusIconContainer implements DemoMode, Da
setShouldRestrictIcons(false); setShouldRestrictIcons(false);
} }
setLayoutParams(mStatusIcons.getLayoutParams()); setLayoutParams(mStatusIcons.getLayoutParams());
setPadding(mStatusIcons.getPaddingLeft(),mStatusIcons.getPaddingTop(), setPadding(mStatusIcons.getPaddingLeft(), mStatusIcons.getPaddingTop(),
mStatusIcons.getPaddingRight(), mStatusIcons.getPaddingBottom()); mStatusIcons.getPaddingRight(), mStatusIcons.getPaddingBottom());
setOrientation(mStatusIcons.getOrientation()); setOrientation(mStatusIcons.getOrientation());
setGravity(Gravity.CENTER_VERTICAL); // no LL.getGravity() setGravity(Gravity.CENTER_VERTICAL); // no LL.getGravity()
@@ -115,6 +122,8 @@ public class DemoStatusIcons extends StatusIconContainer implements DemoMode, Da
public void onDemoModeFinished() { public void onDemoModeFinished() {
mDemoMode = false; mDemoMode = false;
mStatusIcons.setVisibility(View.VISIBLE); mStatusIcons.setVisibility(View.VISIBLE);
mModernMobileViews.clear();
mMobileViews.clear();
setVisibility(View.GONE); setVisibility(View.GONE);
} }
@@ -268,6 +277,24 @@ public class DemoStatusIcons extends StatusIconContainer implements DemoMode, Da
addView(view, getChildCount(), createLayoutParams()); addView(view, getChildCount(), createLayoutParams());
} }
/**
* Add a {@link ModernStatusBarMobileView}
* @param mobileContext possibly mcc/mnc overridden mobile context
* @param subId the subscriptionId for this mobile view
*/
public void addModernMobileView(Context mobileContext, int subId) {
Log.d(TAG, "addModernMobileView (subId=" + subId + ")");
ModernStatusBarMobileView view = ModernStatusBarMobileView.constructAndBind(
mobileContext,
"mobile",
mMobileIconsViewModel.viewModelForSub(subId)
);
// mobile always goes at the end
mModernMobileViews.add(view);
addView(view, getChildCount(), createLayoutParams());
}
/** /**
* Apply an update to a mobile icon view for the given {@link MobileIconState}. For * Apply an update to a mobile icon view for the given {@link MobileIconState}. For
* compatibility with {@link MobileContextProvider}, we have to recreate the view every time we * compatibility with {@link MobileContextProvider}, we have to recreate the view every time we
@@ -292,12 +319,19 @@ public class DemoStatusIcons extends StatusIconContainer implements DemoMode, Da
if (view.getSlot().equals("wifi")) { if (view.getSlot().equals("wifi")) {
removeView(mWifiView); removeView(mWifiView);
mWifiView = null; mWifiView = null;
} else { } else if (view instanceof StatusBarMobileView) {
StatusBarMobileView mobileView = matchingMobileView(view); StatusBarMobileView mobileView = matchingMobileView(view);
if (mobileView != null) { if (mobileView != null) {
removeView(mobileView); removeView(mobileView);
mMobileViews.remove(mobileView); mMobileViews.remove(mobileView);
} }
} else if (view instanceof ModernStatusBarMobileView) {
ModernStatusBarMobileView mobileView = matchingModernMobileView(
(ModernStatusBarMobileView) view);
if (mobileView != null) {
removeView(mobileView);
mModernMobileViews.remove(mobileView);
}
} }
} }
@@ -316,6 +350,16 @@ public class DemoStatusIcons extends StatusIconContainer implements DemoMode, Da
return null; return null;
} }
private ModernStatusBarMobileView matchingModernMobileView(ModernStatusBarMobileView other) {
for (ModernStatusBarMobileView v : mModernMobileViews) {
if (v.getSubId() == other.getSubId()) {
return v;
}
}
return null;
}
private LayoutParams createLayoutParams() { private LayoutParams createLayoutParams() {
return new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, mIconSize); return new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, mIconSize);
} }

View File

@@ -536,8 +536,7 @@ public interface StatusBarIconController {
mGroup.addView(view, index, onCreateLayoutParams()); mGroup.addView(view, index, onCreateLayoutParams());
if (mIsInDemoMode) { if (mIsInDemoMode) {
// TODO (b/249790009): demo mode should be handled at the data layer in the mDemoStatusIcons.addModernMobileView(mContext, subId);
// new pipeline
} }
return view; return view;
@@ -565,11 +564,13 @@ public interface StatusBarIconController {
private ModernStatusBarMobileView onCreateModernStatusBarMobileView( private ModernStatusBarMobileView onCreateModernStatusBarMobileView(
String slot, int subId) { String slot, int subId) {
Context mobileContext = mMobileContextProvider.getMobileContextForSub(subId, mContext);
return ModernStatusBarMobileView return ModernStatusBarMobileView
.constructAndBind( .constructAndBind(
mContext, mobileContext,
slot, slot,
mMobileIconsViewModel.viewModelForSub(subId)); mMobileIconsViewModel.viewModelForSub(subId)
);
} }
protected LinearLayout.LayoutParams onCreateLayoutParams() { protected LinearLayout.LayoutParams onCreateLayoutParams() {
@@ -704,7 +705,7 @@ public interface StatusBarIconController {
} }
protected DemoStatusIcons createDemoStatusIcons() { protected DemoStatusIcons createDemoStatusIcons() {
return new DemoStatusIcons((LinearLayout) mGroup, mIconSize); return new DemoStatusIcons((LinearLayout) mGroup, mMobileIconsViewModel, mIconSize);
} }
} }
} }

View File

@@ -276,6 +276,11 @@ public class StatusBarIconControllerImpl implements Tunable,
String slotName = mContext.getString(com.android.internal.R.string.status_bar_mobile); String slotName = mContext.getString(com.android.internal.R.string.status_bar_mobile);
Slot mobileSlot = mStatusBarIconList.getSlot(slotName); Slot mobileSlot = mStatusBarIconList.getSlot(slotName);
// Because of the way we cache the icon holders, we need to remove everything any time
// we get a new set of subscriptions. This might change in the future, but is required
// to support demo mode for now
removeAllIconsForSlot(slotName);
Collections.reverse(subIds); Collections.reverse(subIds);
for (Integer subId : subIds) { for (Integer subId : subIds) {

View File

@@ -27,7 +27,6 @@ import android.telephony.TelephonyCallback.ServiceStateListener
import android.telephony.TelephonyCallback.SignalStrengthsListener import android.telephony.TelephonyCallback.SignalStrengthsListener
import android.telephony.TelephonyDisplayInfo import android.telephony.TelephonyDisplayInfo
import android.telephony.TelephonyManager import android.telephony.TelephonyManager
import android.telephony.TelephonyManager.NETWORK_TYPE_UNKNOWN
import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState.Disconnected import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState.Disconnected
/** /**
@@ -39,7 +38,7 @@ import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionS
* any new field that needs to be tracked should be copied into this data class rather than * any new field that needs to be tracked should be copied into this data class rather than
* threading complex system objects through the pipeline. * threading complex system objects through the pipeline.
*/ */
data class MobileSubscriptionModel( data class MobileConnectionModel(
/** From [ServiceStateListener.onServiceStateChanged] */ /** From [ServiceStateListener.onServiceStateChanged] */
val isEmergencyOnly: Boolean = false, val isEmergencyOnly: Boolean = false,
@@ -65,5 +64,5 @@ data class MobileSubscriptionModel(
* [resolvedNetworkType] is the [TelephonyDisplayInfo.getOverrideNetworkType] if it exists or * [resolvedNetworkType] is the [TelephonyDisplayInfo.getOverrideNetworkType] if it exists or
* [TelephonyDisplayInfo.getNetworkType]. This is used to look up the proper network type icon * [TelephonyDisplayInfo.getNetworkType]. This is used to look up the proper network type icon
*/ */
val resolvedNetworkType: ResolvedNetworkType = DefaultNetworkType(NETWORK_TYPE_UNKNOWN), val resolvedNetworkType: ResolvedNetworkType = ResolvedNetworkType.UnknownNetworkType,
) )

View File

@@ -17,6 +17,7 @@
package com.android.systemui.statusbar.pipeline.mobile.data.model package com.android.systemui.statusbar.pipeline.mobile.data.model
import android.telephony.Annotation.NetworkType import android.telephony.Annotation.NetworkType
import android.telephony.TelephonyManager.NETWORK_TYPE_UNKNOWN
import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy
/** /**
@@ -26,8 +27,20 @@ import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy
*/ */
sealed interface ResolvedNetworkType { sealed interface ResolvedNetworkType {
@NetworkType val type: Int @NetworkType val type: Int
val lookupKey: String
object UnknownNetworkType : ResolvedNetworkType {
override val type: Int = NETWORK_TYPE_UNKNOWN
override val lookupKey: String = "unknown"
}
data class DefaultNetworkType(
@NetworkType override val type: Int,
override val lookupKey: String,
) : ResolvedNetworkType
data class OverrideNetworkType(
@NetworkType override val type: Int,
override val lookupKey: String,
) : ResolvedNetworkType
} }
data class DefaultNetworkType(@NetworkType override val type: Int) : ResolvedNetworkType
data class OverrideNetworkType(@NetworkType override val type: Int) : ResolvedNetworkType

View File

@@ -0,0 +1,32 @@
/*
* Copyright (C) 2022 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.data.model
/**
* SystemUI representation of [SubscriptionInfo]. Currently we only use two fields on the
* subscriptions themselves: subscriptionId and isOpportunistic. Any new fields that we need can be
* added below and provided in the repository classes
*/
data class SubscriptionModel(
val subscriptionId: Int,
/**
* True if the subscription that this model represents has [SubscriptionInfo.isOpportunistic].
* Opportunistic networks are networks with limited coverage, and we use this bit to determine
* filtering in certain cases. See [MobileIconsInteractor] for the filtering logic
*/
val isOpportunistic: Boolean = false,
)

View File

@@ -20,7 +20,7 @@ import android.telephony.SubscriptionInfo
import android.telephony.SubscriptionManager import android.telephony.SubscriptionManager
import android.telephony.TelephonyCallback import android.telephony.TelephonyCallback
import android.telephony.TelephonyManager import android.telephony.TelephonyManager
import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileSubscriptionModel import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
@@ -36,11 +36,13 @@ import kotlinx.coroutines.flow.StateFlow
* eventually becomes a single icon in the status bar. * eventually becomes a single icon in the status bar.
*/ */
interface MobileConnectionRepository { interface MobileConnectionRepository {
/** The subscriptionId that this connection represents */
val subId: Int
/** /**
* A flow that aggregates all necessary callbacks from [TelephonyCallback] into a single * A flow that aggregates all necessary callbacks from [TelephonyCallback] into a single
* listener + model. * listener + model.
*/ */
val subscriptionModelFlow: Flow<MobileSubscriptionModel> val connectionInfo: Flow<MobileConnectionModel>
/** Observable tracking [TelephonyManager.isDataConnectionAllowed] */ /** Observable tracking [TelephonyManager.isDataConnectionAllowed] */
val dataEnabled: StateFlow<Boolean> val dataEnabled: StateFlow<Boolean>
/** /**

View File

@@ -17,11 +17,10 @@
package com.android.systemui.statusbar.pipeline.mobile.data.repository package com.android.systemui.statusbar.pipeline.mobile.data.repository
import android.provider.Settings import android.provider.Settings
import android.telephony.SubscriptionInfo
import android.telephony.SubscriptionManager import android.telephony.SubscriptionManager
import com.android.settingslib.mobile.MobileMappings import com.android.settingslib.SignalIcon.MobileIconGroup
import com.android.settingslib.mobile.MobileMappings.Config
import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectivityModel import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectivityModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
@@ -31,14 +30,11 @@ import kotlinx.coroutines.flow.StateFlow
*/ */
interface MobileConnectionsRepository { interface MobileConnectionsRepository {
/** Observable list of current mobile subscriptions */ /** Observable list of current mobile subscriptions */
val subscriptionsFlow: Flow<List<SubscriptionInfo>> val subscriptions: StateFlow<List<SubscriptionModel>>
/** Observable for the subscriptionId of the current mobile data connection */ /** Observable for the subscriptionId of the current mobile data connection */
val activeMobileDataSubscriptionId: StateFlow<Int> val activeMobileDataSubscriptionId: StateFlow<Int>
/** Observable for [MobileMappings.Config] tracking the defaults */
val defaultDataSubRatConfig: StateFlow<Config>
/** Tracks [SubscriptionManager.getDefaultDataSubscriptionId] */ /** Tracks [SubscriptionManager.getDefaultDataSubscriptionId] */
val defaultDataSubId: StateFlow<Int> val defaultDataSubId: StateFlow<Int>
@@ -50,4 +46,10 @@ interface MobileConnectionsRepository {
/** Observe changes to the [Settings.Global.MOBILE_DATA] setting */ /** Observe changes to the [Settings.Global.MOBILE_DATA] setting */
val globalMobileDataSettingChangedEvent: Flow<Unit> val globalMobileDataSettingChangedEvent: Flow<Unit>
/** The icon mapping from network type to [MobileIconGroup] for the default subscription */
val defaultMobileIconMapping: Flow<Map<String, MobileIconGroup>>
/** Fallback [MobileIconGroup] in the case where there is no icon in the mapping */
val defaultMobileIconGroup: Flow<MobileIconGroup>
} }

View File

@@ -17,14 +17,14 @@
package com.android.systemui.statusbar.pipeline.mobile.data.repository package com.android.systemui.statusbar.pipeline.mobile.data.repository
import android.os.Bundle import android.os.Bundle
import android.telephony.SubscriptionInfo
import androidx.annotation.VisibleForTesting import androidx.annotation.VisibleForTesting
import com.android.settingslib.mobile.MobileMappings import com.android.settingslib.SignalIcon
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.demomode.DemoMode import com.android.systemui.demomode.DemoMode
import com.android.systemui.demomode.DemoModeController import com.android.systemui.demomode.DemoModeController
import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectivityModel import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectivityModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.DemoMobileConnectionsRepository import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.DemoMobileConnectionsRepository
import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.MobileConnectionsRepositoryImpl import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.MobileConnectionsRepositoryImpl
import javax.inject.Inject import javax.inject.Inject
@@ -109,14 +109,10 @@ constructor(
} }
.stateIn(scope, SharingStarted.WhileSubscribed(), realRepository) .stateIn(scope, SharingStarted.WhileSubscribed(), realRepository)
override val subscriptionsFlow: StateFlow<List<SubscriptionInfo>> = override val subscriptions: StateFlow<List<SubscriptionModel>> =
activeRepo activeRepo
.flatMapLatest { it.subscriptionsFlow } .flatMapLatest { it.subscriptions }
.stateIn( .stateIn(scope, SharingStarted.WhileSubscribed(), realRepository.subscriptions.value)
scope,
SharingStarted.WhileSubscribed(),
realRepository.subscriptionsFlow.value
)
override val activeMobileDataSubscriptionId: StateFlow<Int> = override val activeMobileDataSubscriptionId: StateFlow<Int> =
activeRepo activeRepo
@@ -127,14 +123,11 @@ constructor(
realRepository.activeMobileDataSubscriptionId.value realRepository.activeMobileDataSubscriptionId.value
) )
override val defaultDataSubRatConfig: StateFlow<MobileMappings.Config> = override val defaultMobileIconMapping: Flow<Map<String, SignalIcon.MobileIconGroup>> =
activeRepo activeRepo.flatMapLatest { it.defaultMobileIconMapping }
.flatMapLatest { it.defaultDataSubRatConfig }
.stateIn( override val defaultMobileIconGroup: Flow<SignalIcon.MobileIconGroup> =
scope, activeRepo.flatMapLatest { it.defaultMobileIconGroup }
SharingStarted.WhileSubscribed(),
realRepository.defaultDataSubRatConfig.value
)
override val defaultDataSubId: StateFlow<Int> = override val defaultDataSubId: StateFlow<Int> =
activeRepo activeRepo

View File

@@ -17,26 +17,18 @@
package com.android.systemui.statusbar.pipeline.mobile.data.repository.demo package com.android.systemui.statusbar.pipeline.mobile.data.repository.demo
import android.content.Context import android.content.Context
import android.telephony.Annotation
import android.telephony.SubscriptionInfo
import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
import android.telephony.TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_ADVANCED
import android.telephony.TelephonyManager.NETWORK_TYPE_GSM
import android.telephony.TelephonyManager.NETWORK_TYPE_LTE
import android.telephony.TelephonyManager.NETWORK_TYPE_NR
import android.telephony.TelephonyManager.NETWORK_TYPE_UMTS
import android.telephony.TelephonyManager.NETWORK_TYPE_UNKNOWN
import android.util.Log import android.util.Log
import com.android.settingslib.SignalIcon import com.android.settingslib.SignalIcon
import com.android.settingslib.mobile.MobileMappings import com.android.settingslib.mobile.MobileMappings
import com.android.settingslib.mobile.TelephonyIcons import com.android.settingslib.mobile.TelephonyIcons
import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState
import com.android.systemui.statusbar.pipeline.mobile.data.model.DefaultNetworkType import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectivityModel import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectivityModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileSubscriptionModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.OverrideNetworkType
import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType
import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType.DefaultNetworkType
import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepository import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepository
import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.model.FakeNetworkEventModel import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.model.FakeNetworkEventModel
@@ -49,7 +41,9 @@ import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.mapLatest import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.stateIn
@@ -67,61 +61,40 @@ constructor(
private var demoCommandJob: Job? = null private var demoCommandJob: Job? = null
private val connectionRepoCache = mutableMapOf<Int, DemoMobileConnectionRepository>() private var connectionRepoCache = mutableMapOf<Int, DemoMobileConnectionRepository>()
private val subscriptionInfoCache = mutableMapOf<Int, SubscriptionInfo>() private val subscriptionInfoCache = mutableMapOf<Int, SubscriptionModel>()
val demoModeFinishedEvent = MutableSharedFlow<Unit>(extraBufferCapacity = 1) val demoModeFinishedEvent = MutableSharedFlow<Unit>(extraBufferCapacity = 1)
private val _subscriptions = MutableStateFlow<List<SubscriptionInfo>>(listOf()) private val _subscriptions = MutableStateFlow<List<SubscriptionModel>>(listOf())
override val subscriptionsFlow = override val subscriptions =
_subscriptions _subscriptions
.onEach { infos -> dropUnusedReposFromCache(infos) } .onEach { infos -> dropUnusedReposFromCache(infos) }
.stateIn(scope, SharingStarted.WhileSubscribed(), _subscriptions.value) .stateIn(scope, SharingStarted.WhileSubscribed(), _subscriptions.value)
private fun dropUnusedReposFromCache(newInfos: List<SubscriptionInfo>) { private fun dropUnusedReposFromCache(newInfos: List<SubscriptionModel>) {
// Remove any connection repository from the cache that isn't in the new set of IDs. They // Remove any connection repository from the cache that isn't in the new set of IDs. They
// will get garbage collected once their subscribers go away // will get garbage collected once their subscribers go away
val currentValidSubscriptionIds = newInfos.map { it.subscriptionId } val currentValidSubscriptionIds = newInfos.map { it.subscriptionId }
connectionRepoCache.keys.forEach { connectionRepoCache =
if (!currentValidSubscriptionIds.contains(it)) { connectionRepoCache
connectionRepoCache.remove(it) .filter { currentValidSubscriptionIds.contains(it.key) }
} .toMutableMap()
}
} }
private fun maybeCreateSubscription(subId: Int) { private fun maybeCreateSubscription(subId: Int) {
if (!subscriptionInfoCache.containsKey(subId)) { if (!subscriptionInfoCache.containsKey(subId)) {
createSubscriptionForSubId(subId, subId).also { subscriptionInfoCache[subId] = it } SubscriptionModel(subscriptionId = subId, isOpportunistic = false).also {
subscriptionInfoCache[subId] = it
}
_subscriptions.value = subscriptionInfoCache.values.toList() _subscriptions.value = subscriptionInfoCache.values.toList()
} }
} }
/** Mimics the old NetworkControllerImpl for now */
private fun createSubscriptionForSubId(subId: Int, slotIndex: Int): SubscriptionInfo {
return SubscriptionInfo(
subId,
"",
slotIndex,
"",
"",
0,
0,
"",
0,
null,
null,
null,
"",
false,
null,
null,
)
}
// TODO(b/261029387): add a command for this value // TODO(b/261029387): add a command for this value
override val activeMobileDataSubscriptionId = override val activeMobileDataSubscriptionId =
subscriptionsFlow subscriptions
.mapLatest { infos -> .mapLatest { infos ->
// For now, active is just the first in the list // For now, active is just the first in the list
infos.firstOrNull()?.subscriptionId ?: INVALID_SUBSCRIPTION_ID infos.firstOrNull()?.subscriptionId ?: INVALID_SUBSCRIPTION_ID
@@ -129,12 +102,35 @@ constructor(
.stateIn( .stateIn(
scope, scope,
SharingStarted.WhileSubscribed(), SharingStarted.WhileSubscribed(),
subscriptionsFlow.value.firstOrNull()?.subscriptionId ?: INVALID_SUBSCRIPTION_ID subscriptions.value.firstOrNull()?.subscriptionId ?: INVALID_SUBSCRIPTION_ID
) )
/** Demo mode doesn't currently support modifications to the mobile mappings */ /** Demo mode doesn't currently support modifications to the mobile mappings */
override val defaultDataSubRatConfig = val defaultDataSubRatConfig = MutableStateFlow(MobileMappings.Config.readConfig(context))
MutableStateFlow(MobileMappings.Config.readConfig(context))
override val defaultMobileIconGroup = flowOf(TelephonyIcons.THREE_G)
override val defaultMobileIconMapping = MutableStateFlow(TelephonyIcons.ICON_NAME_TO_ICON)
/**
* In order to maintain compatibility with the old demo mode shell command API, reverse the
* [MobileMappings] lookup from (NetworkType: String -> Icon: MobileIconGroup), so that we can
* parse the string from the command line into a preferred icon group, and send _a_ valid
* network type for that icon through the pipeline.
*
* Note: collisions don't matter here, because the data source (the command line) only cares
* about the resulting icon, not the underlying network type.
*/
private val mobileMappingsReverseLookup: StateFlow<Map<SignalIcon.MobileIconGroup, String>> =
defaultMobileIconMapping
.mapLatest { networkToIconMap -> networkToIconMap.reverse() }
.stateIn(
scope,
SharingStarted.WhileSubscribed(),
defaultMobileIconMapping.value.reverse()
)
private fun <K, V> Map<K, V>.reverse() = entries.associateBy({ it.value }) { it.key }
// TODO(b/261029387): add a command for this value // TODO(b/261029387): add a command for this value
override val defaultDataSubId = override val defaultDataSubId =
@@ -189,7 +185,7 @@ constructor(
connection.dataEnabled.value = true connection.dataEnabled.value = true
connection.isDefaultDataSubscription.value = state.dataType != null connection.isDefaultDataSubscription.value = state.dataType != null
connection.subscriptionModelFlow.value = state.toMobileSubscriptionModel() connection.connectionInfo.value = state.toMobileConnectionModel()
} }
private fun processDisabledMobileState(state: MobileDisabled) { private fun processDisabledMobileState(state: MobileDisabled) {
@@ -229,47 +225,36 @@ constructor(
private fun subIdsString(): String = private fun subIdsString(): String =
_subscriptions.value.joinToString(",") { it.subscriptionId.toString() } _subscriptions.value.joinToString(",") { it.subscriptionId.toString() }
companion object { private fun Mobile.toMobileConnectionModel(): MobileConnectionModel {
private const val TAG = "DemoMobileConnectionsRepo" return MobileConnectionModel(
private const val DEFAULT_SUB_ID = 1
}
}
private fun Mobile.toMobileSubscriptionModel(): MobileSubscriptionModel {
return MobileSubscriptionModel(
isEmergencyOnly = false, // TODO(b/261029387): not yet supported isEmergencyOnly = false, // TODO(b/261029387): not yet supported
isGsm = false, // TODO(b/261029387): not yet supported isGsm = false, // TODO(b/261029387): not yet supported
cdmaLevel = level ?: 0, cdmaLevel = level ?: 0,
primaryLevel = level ?: 0, primaryLevel = level ?: 0,
dataConnectionState = DataConnectionState.Connected, // TODO(b/261029387): not yet supported dataConnectionState =
DataConnectionState.Connected, // TODO(b/261029387): not yet supported
dataActivityDirection = activity, dataActivityDirection = activity,
carrierNetworkChangeActive = carrierNetworkChange, carrierNetworkChangeActive = carrierNetworkChange,
// TODO(b/261185097): once mobile mappings can be mocked at this layer, we can build our
// own demo map
resolvedNetworkType = dataType.toResolvedNetworkType() resolvedNetworkType = dataType.toResolvedNetworkType()
) )
}
private fun SignalIcon.MobileIconGroup?.toResolvedNetworkType(): ResolvedNetworkType {
val key = mobileMappingsReverseLookup.value[this] ?: "dis"
return DefaultNetworkType(DEMO_NET_TYPE, key)
}
companion object {
private const val TAG = "DemoMobileConnectionsRepo"
private const val DEFAULT_SUB_ID = 1
private const val DEMO_NET_TYPE = 1234
}
} }
@Annotation.NetworkType class DemoMobileConnectionRepository(override val subId: Int) : MobileConnectionRepository {
private fun SignalIcon.MobileIconGroup?.toNetworkType(): Int = override val connectionInfo = MutableStateFlow(MobileConnectionModel())
when (this) {
TelephonyIcons.THREE_G -> NETWORK_TYPE_GSM
TelephonyIcons.LTE -> NETWORK_TYPE_LTE
TelephonyIcons.FOUR_G -> NETWORK_TYPE_UMTS
TelephonyIcons.NR_5G -> NETWORK_TYPE_NR
TelephonyIcons.NR_5G_PLUS -> OVERRIDE_NETWORK_TYPE_NR_ADVANCED
else -> NETWORK_TYPE_UNKNOWN
}
private fun SignalIcon.MobileIconGroup?.toResolvedNetworkType(): ResolvedNetworkType =
when (this) {
TelephonyIcons.NR_5G_PLUS -> OverrideNetworkType(toNetworkType())
else -> DefaultNetworkType(toNetworkType())
}
class DemoMobileConnectionRepository(val subId: Int) : MobileConnectionRepository {
override val subscriptionModelFlow = MutableStateFlow(MobileSubscriptionModel())
override val dataEnabled = MutableStateFlow(true) override val dataEnabled = MutableStateFlow(true)

View File

@@ -27,18 +27,20 @@ import android.telephony.TelephonyCallback
import android.telephony.TelephonyDisplayInfo import android.telephony.TelephonyDisplayInfo
import android.telephony.TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE import android.telephony.TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE
import android.telephony.TelephonyManager import android.telephony.TelephonyManager
import android.telephony.TelephonyManager.NETWORK_TYPE_UNKNOWN
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.statusbar.pipeline.mobile.data.model.DefaultNetworkType import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileSubscriptionModel import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType.DefaultNetworkType
import com.android.systemui.statusbar.pipeline.mobile.data.model.OverrideNetworkType 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.toDataConnectionType import com.android.systemui.statusbar.pipeline.mobile.data.model.toDataConnectionType
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository
import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.logOutputChange import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.logOutputChange
import com.android.systemui.util.settings.GlobalSettings import com.android.systemui.util.settings.GlobalSettings
import java.lang.IllegalStateException
import javax.inject.Inject import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
@@ -58,11 +60,12 @@ import kotlinx.coroutines.flow.stateIn
@OptIn(ExperimentalCoroutinesApi::class) @OptIn(ExperimentalCoroutinesApi::class)
class MobileConnectionRepositoryImpl( class MobileConnectionRepositoryImpl(
private val context: Context, private val context: Context,
private val subId: Int, override val subId: Int,
private val telephonyManager: TelephonyManager, private val telephonyManager: TelephonyManager,
private val globalSettings: GlobalSettings, private val globalSettings: GlobalSettings,
defaultDataSubId: StateFlow<Int>, defaultDataSubId: StateFlow<Int>,
globalMobileDataSettingChangedEvent: Flow<Unit>, globalMobileDataSettingChangedEvent: Flow<Unit>,
mobileMappingsProxy: MobileMappingsProxy,
bgDispatcher: CoroutineDispatcher, bgDispatcher: CoroutineDispatcher,
logger: ConnectivityPipelineLogger, logger: ConnectivityPipelineLogger,
scope: CoroutineScope, scope: CoroutineScope,
@@ -78,8 +81,8 @@ class MobileConnectionRepositoryImpl(
private val telephonyCallbackEvent = MutableSharedFlow<Unit>(extraBufferCapacity = 1) private val telephonyCallbackEvent = MutableSharedFlow<Unit>(extraBufferCapacity = 1)
override val subscriptionModelFlow: StateFlow<MobileSubscriptionModel> = run { override val connectionInfo: StateFlow<MobileConnectionModel> = run {
var state = MobileSubscriptionModel() var state = MobileConnectionModel()
conflatedCallbackFlow { conflatedCallbackFlow {
// TODO (b/240569788): log all of these into the connectivity logger // TODO (b/240569788): log all of these into the connectivity logger
val callback = val callback =
@@ -141,14 +144,27 @@ class MobileConnectionRepositoryImpl(
override fun onDisplayInfoChanged( override fun onDisplayInfoChanged(
telephonyDisplayInfo: TelephonyDisplayInfo telephonyDisplayInfo: TelephonyDisplayInfo
) { ) {
val networkType = val networkType =
if ( if (telephonyDisplayInfo.networkType == NETWORK_TYPE_UNKNOWN) {
UnknownNetworkType
} else if (
telephonyDisplayInfo.overrideNetworkType == telephonyDisplayInfo.overrideNetworkType ==
OVERRIDE_NETWORK_TYPE_NONE OVERRIDE_NETWORK_TYPE_NONE
) { ) {
DefaultNetworkType(telephonyDisplayInfo.networkType) DefaultNetworkType(
telephonyDisplayInfo.networkType,
mobileMappingsProxy.toIconKey(
telephonyDisplayInfo.networkType
)
)
} else { } else {
OverrideNetworkType(telephonyDisplayInfo.overrideNetworkType) OverrideNetworkType(
telephonyDisplayInfo.overrideNetworkType,
mobileMappingsProxy.toIconKeyOverride(
telephonyDisplayInfo.overrideNetworkType
)
)
} }
state = state.copy(resolvedNetworkType = networkType) state = state.copy(resolvedNetworkType = networkType)
trySend(state) trySend(state)
@@ -211,6 +227,7 @@ class MobileConnectionRepositoryImpl(
private val telephonyManager: TelephonyManager, private val telephonyManager: TelephonyManager,
private val logger: ConnectivityPipelineLogger, private val logger: ConnectivityPipelineLogger,
private val globalSettings: GlobalSettings, private val globalSettings: GlobalSettings,
private val mobileMappingsProxy: MobileMappingsProxy,
@Background private val bgDispatcher: CoroutineDispatcher, @Background private val bgDispatcher: CoroutineDispatcher,
@Application private val scope: CoroutineScope, @Application private val scope: CoroutineScope,
) { ) {
@@ -226,6 +243,7 @@ class MobileConnectionRepositoryImpl(
globalSettings, globalSettings,
defaultDataSubId, defaultDataSubId,
globalMobileDataSettingChangedEvent, globalMobileDataSettingChangedEvent,
mobileMappingsProxy,
bgDispatcher, bgDispatcher,
logger, logger,
scope, scope,

View File

@@ -36,6 +36,7 @@ import android.telephony.TelephonyCallback.ActiveDataSubscriptionIdListener
import android.telephony.TelephonyManager import android.telephony.TelephonyManager
import androidx.annotation.VisibleForTesting import androidx.annotation.VisibleForTesting
import com.android.internal.telephony.PhoneConstants import com.android.internal.telephony.PhoneConstants
import com.android.settingslib.SignalIcon.MobileIconGroup
import com.android.settingslib.mobile.MobileMappings import com.android.settingslib.mobile.MobileMappings
import com.android.settingslib.mobile.MobileMappings.Config import com.android.settingslib.mobile.MobileMappings.Config
import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.broadcast.BroadcastDispatcher
@@ -44,8 +45,10 @@ import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectivityModel import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectivityModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepository import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepository
import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
import com.android.systemui.util.settings.GlobalSettings import com.android.systemui.util.settings.GlobalSettings
import javax.inject.Inject import javax.inject.Inject
@@ -59,6 +62,7 @@ import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.mapLatest import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.flow.merge import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
@@ -75,6 +79,7 @@ constructor(
private val subscriptionManager: SubscriptionManager, private val subscriptionManager: SubscriptionManager,
private val telephonyManager: TelephonyManager, private val telephonyManager: TelephonyManager,
private val logger: ConnectivityPipelineLogger, private val logger: ConnectivityPipelineLogger,
mobileMappingsProxy: MobileMappingsProxy,
broadcastDispatcher: BroadcastDispatcher, broadcastDispatcher: BroadcastDispatcher,
private val globalSettings: GlobalSettings, private val globalSettings: GlobalSettings,
private val context: Context, private val context: Context,
@@ -82,14 +87,14 @@ constructor(
@Application private val scope: CoroutineScope, @Application private val scope: CoroutineScope,
private val mobileConnectionRepositoryFactory: MobileConnectionRepositoryImpl.Factory private val mobileConnectionRepositoryFactory: MobileConnectionRepositoryImpl.Factory
) : MobileConnectionsRepository { ) : MobileConnectionsRepository {
private val subIdRepositoryCache: MutableMap<Int, MobileConnectionRepository> = mutableMapOf() private var subIdRepositoryCache: MutableMap<Int, MobileConnectionRepository> = mutableMapOf()
/** /**
* State flow that emits the set of mobile data subscriptions, each represented by its own * State flow that emits the set of mobile data subscriptions, each represented by its own
* [SubscriptionInfo]. We probably only need the [SubscriptionInfo.getSubscriptionId] of each * [SubscriptionInfo]. We probably only need the [SubscriptionInfo.getSubscriptionId] of each
* info object, but for now we keep track of the infos themselves. * info object, but for now we keep track of the infos themselves.
*/ */
override val subscriptionsFlow: StateFlow<List<SubscriptionInfo>> = override val subscriptions: StateFlow<List<SubscriptionModel>> =
conflatedCallbackFlow { conflatedCallbackFlow {
val callback = val callback =
object : SubscriptionManager.OnSubscriptionsChangedListener() { object : SubscriptionManager.OnSubscriptionsChangedListener() {
@@ -105,7 +110,7 @@ constructor(
awaitClose { subscriptionManager.removeOnSubscriptionsChangedListener(callback) } awaitClose { subscriptionManager.removeOnSubscriptionsChangedListener(callback) }
} }
.mapLatest { fetchSubscriptionsList() } .mapLatest { fetchSubscriptionsList().map { it.toSubscriptionModel() } }
.onEach { infos -> dropUnusedReposFromCache(infos) } .onEach { infos -> dropUnusedReposFromCache(infos) }
.stateIn(scope, started = SharingStarted.WhileSubscribed(), listOf()) .stateIn(scope, started = SharingStarted.WhileSubscribed(), listOf())
@@ -157,7 +162,7 @@ constructor(
* *
* This flow will produce whenever the default data subscription or the carrier config changes. * This flow will produce whenever the default data subscription or the carrier config changes.
*/ */
override val defaultDataSubRatConfig: StateFlow<Config> = private val defaultDataSubRatConfig: StateFlow<Config> =
merge(defaultDataSubIdChangeEvent, carrierConfigChangedEvent) merge(defaultDataSubIdChangeEvent, carrierConfigChangedEvent)
.mapLatest { Config.readConfig(context) } .mapLatest { Config.readConfig(context) }
.stateIn( .stateIn(
@@ -166,6 +171,12 @@ constructor(
initialValue = Config.readConfig(context) initialValue = Config.readConfig(context)
) )
override val defaultMobileIconMapping: Flow<Map<String, MobileIconGroup>> =
defaultDataSubRatConfig.map { mobileMappingsProxy.mapIconSets(it) }
override val defaultMobileIconGroup: Flow<MobileIconGroup> =
defaultDataSubRatConfig.map { mobileMappingsProxy.getDefaultIcons(it) }
override fun getRepoForSubId(subId: Int): MobileConnectionRepository { override fun getRepoForSubId(subId: Int): MobileConnectionRepository {
if (!isValidSubId(subId)) { if (!isValidSubId(subId)) {
throw IllegalArgumentException( throw IllegalArgumentException(
@@ -229,7 +240,7 @@ constructor(
.stateIn(scope, SharingStarted.WhileSubscribed(), MobileConnectivityModel()) .stateIn(scope, SharingStarted.WhileSubscribed(), MobileConnectivityModel())
private fun isValidSubId(subId: Int): Boolean { private fun isValidSubId(subId: Int): Boolean {
subscriptionsFlow.value.forEach { subscriptions.value.forEach {
if (it.subscriptionId == subId) { if (it.subscriptionId == subId) {
return true return true
} }
@@ -248,18 +259,23 @@ constructor(
) )
} }
private fun dropUnusedReposFromCache(newInfos: List<SubscriptionInfo>) { private fun dropUnusedReposFromCache(newInfos: List<SubscriptionModel>) {
// Remove any connection repository from the cache that isn't in the new set of IDs. They // Remove any connection repository from the cache that isn't in the new set of IDs. They
// will get garbage collected once their subscribers go away // will get garbage collected once their subscribers go away
val currentValidSubscriptionIds = newInfos.map { it.subscriptionId } val currentValidSubscriptionIds = newInfos.map { it.subscriptionId }
subIdRepositoryCache.keys.forEach { subIdRepositoryCache =
if (!currentValidSubscriptionIds.contains(it)) { subIdRepositoryCache
subIdRepositoryCache.remove(it) .filter { currentValidSubscriptionIds.contains(it.key) }
} .toMutableMap()
}
} }
private suspend fun fetchSubscriptionsList(): List<SubscriptionInfo> = private suspend fun fetchSubscriptionsList(): List<SubscriptionInfo> =
withContext(bgDispatcher) { subscriptionManager.completeActiveSubscriptionInfoList } withContext(bgDispatcher) { subscriptionManager.completeActiveSubscriptionInfoList }
private fun SubscriptionInfo.toSubscriptionModel(): SubscriptionModel =
SubscriptionModel(
subscriptionId = subscriptionId,
isOpportunistic = isOpportunistic,
)
} }

View File

@@ -20,10 +20,7 @@ import android.telephony.CarrierConfigManager
import com.android.settingslib.SignalIcon.MobileIconGroup import com.android.settingslib.SignalIcon.MobileIconGroup
import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState.Connected import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState.Connected
import com.android.systemui.statusbar.pipeline.mobile.data.model.DefaultNetworkType
import com.android.systemui.statusbar.pipeline.mobile.data.model.OverrideNetworkType
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository
import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy
import com.android.systemui.util.CarrierConfigTracker import com.android.systemui.util.CarrierConfigTracker
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -70,10 +67,9 @@ class MobileIconInteractorImpl(
defaultMobileIconMapping: StateFlow<Map<String, MobileIconGroup>>, defaultMobileIconMapping: StateFlow<Map<String, MobileIconGroup>>,
defaultMobileIconGroup: StateFlow<MobileIconGroup>, defaultMobileIconGroup: StateFlow<MobileIconGroup>,
override val isDefaultConnectionFailed: StateFlow<Boolean>, override val isDefaultConnectionFailed: StateFlow<Boolean>,
mobileMappingsProxy: MobileMappingsProxy,
connectionRepository: MobileConnectionRepository, connectionRepository: MobileConnectionRepository,
) : MobileIconInteractor { ) : MobileIconInteractor {
private val mobileStatusInfo = connectionRepository.subscriptionModelFlow private val connectionInfo = connectionRepository.connectionInfo
override val isDataEnabled: StateFlow<Boolean> = connectionRepository.dataEnabled override val isDataEnabled: StateFlow<Boolean> = connectionRepository.dataEnabled
@@ -82,33 +78,27 @@ class MobileIconInteractorImpl(
/** Observable for the current RAT indicator icon ([MobileIconGroup]) */ /** Observable for the current RAT indicator icon ([MobileIconGroup]) */
override val networkTypeIconGroup: StateFlow<MobileIconGroup> = override val networkTypeIconGroup: StateFlow<MobileIconGroup> =
combine( combine(
mobileStatusInfo, connectionInfo,
defaultMobileIconMapping, defaultMobileIconMapping,
defaultMobileIconGroup, defaultMobileIconGroup,
) { info, mapping, defaultGroup -> ) { info, mapping, defaultGroup ->
val lookupKey = mapping[info.resolvedNetworkType.lookupKey] ?: defaultGroup
when (val resolved = info.resolvedNetworkType) {
is DefaultNetworkType -> mobileMappingsProxy.toIconKey(resolved.type)
is OverrideNetworkType ->
mobileMappingsProxy.toIconKeyOverride(resolved.type)
}
mapping[lookupKey] ?: defaultGroup
} }
.stateIn(scope, SharingStarted.WhileSubscribed(), defaultMobileIconGroup.value) .stateIn(scope, SharingStarted.WhileSubscribed(), defaultMobileIconGroup.value)
override val isEmergencyOnly: StateFlow<Boolean> = override val isEmergencyOnly: StateFlow<Boolean> =
mobileStatusInfo connectionInfo
.mapLatest { it.isEmergencyOnly } .mapLatest { it.isEmergencyOnly }
.stateIn(scope, SharingStarted.WhileSubscribed(), false) .stateIn(scope, SharingStarted.WhileSubscribed(), false)
override val level: StateFlow<Int> = override val level: StateFlow<Int> =
mobileStatusInfo connectionInfo
.mapLatest { mobileModel -> .mapLatest { connection ->
// TODO: incorporate [MobileMappings.Config.alwaysShowCdmaRssi] // TODO: incorporate [MobileMappings.Config.alwaysShowCdmaRssi]
if (mobileModel.isGsm) { if (connection.isGsm) {
mobileModel.primaryLevel connection.primaryLevel
} else { } else {
mobileModel.cdmaLevel connection.cdmaLevel
} }
} }
.stateIn(scope, SharingStarted.WhileSubscribed(), 0) .stateIn(scope, SharingStarted.WhileSubscribed(), 0)
@@ -120,7 +110,7 @@ class MobileIconInteractorImpl(
override val numberOfLevels: StateFlow<Int> = MutableStateFlow(4) override val numberOfLevels: StateFlow<Int> = MutableStateFlow(4)
override val isDataConnected: StateFlow<Boolean> = override val isDataConnected: StateFlow<Boolean> =
mobileStatusInfo connectionInfo
.mapLatest { subscriptionModel -> subscriptionModel.dataConnectionState == Connected } .mapLatest { connection -> connection.dataConnectionState == Connected }
.stateIn(scope, SharingStarted.WhileSubscribed(), false) .stateIn(scope, SharingStarted.WhileSubscribed(), false)
} }

View File

@@ -17,17 +17,16 @@
package com.android.systemui.statusbar.pipeline.mobile.domain.interactor package com.android.systemui.statusbar.pipeline.mobile.domain.interactor
import android.telephony.CarrierConfigManager import android.telephony.CarrierConfigManager
import android.telephony.SubscriptionInfo
import android.telephony.SubscriptionManager import android.telephony.SubscriptionManager
import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
import com.android.settingslib.SignalIcon.MobileIconGroup import com.android.settingslib.SignalIcon.MobileIconGroup
import com.android.settingslib.mobile.TelephonyIcons import com.android.settingslib.mobile.TelephonyIcons
import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepository import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepository
import com.android.systemui.statusbar.pipeline.mobile.data.repository.UserSetupRepository import com.android.systemui.statusbar.pipeline.mobile.data.repository.UserSetupRepository
import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy
import com.android.systemui.util.CarrierConfigTracker import com.android.systemui.util.CarrierConfigTracker
import javax.inject.Inject import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
@@ -53,7 +52,7 @@ import kotlinx.coroutines.flow.stateIn
*/ */
interface MobileIconsInteractor { interface MobileIconsInteractor {
/** List of subscriptions, potentially filtered for CBRS */ /** List of subscriptions, potentially filtered for CBRS */
val filteredSubscriptions: Flow<List<SubscriptionInfo>> val filteredSubscriptions: Flow<List<SubscriptionModel>>
/** True if the active mobile data subscription has data enabled */ /** True if the active mobile data subscription has data enabled */
val activeDataConnectionHasDataEnabled: StateFlow<Boolean> val activeDataConnectionHasDataEnabled: StateFlow<Boolean>
/** The icon mapping from network type to [MobileIconGroup] for the default subscription */ /** The icon mapping from network type to [MobileIconGroup] for the default subscription */
@@ -79,7 +78,6 @@ class MobileIconsInteractorImpl
constructor( constructor(
private val mobileConnectionsRepo: MobileConnectionsRepository, private val mobileConnectionsRepo: MobileConnectionsRepository,
private val carrierConfigTracker: CarrierConfigTracker, private val carrierConfigTracker: CarrierConfigTracker,
private val mobileMappingsProxy: MobileMappingsProxy,
userSetupRepo: UserSetupRepository, userSetupRepo: UserSetupRepository,
@Application private val scope: CoroutineScope, @Application private val scope: CoroutineScope,
) : MobileIconsInteractor { ) : MobileIconsInteractor {
@@ -102,8 +100,8 @@ constructor(
.flatMapLatest { it?.dataEnabled ?: flowOf(false) } .flatMapLatest { it?.dataEnabled ?: flowOf(false) }
.stateIn(scope, SharingStarted.WhileSubscribed(), false) .stateIn(scope, SharingStarted.WhileSubscribed(), false)
private val unfilteredSubscriptions: Flow<List<SubscriptionInfo>> = private val unfilteredSubscriptions: Flow<List<SubscriptionModel>> =
mobileConnectionsRepo.subscriptionsFlow mobileConnectionsRepo.subscriptions
/** /**
* Generally, SystemUI wants to show iconography for each subscription that is listed by * Generally, SystemUI wants to show iconography for each subscription that is listed by
@@ -118,7 +116,7 @@ constructor(
* [CarrierConfigManager.KEY_ALWAYS_SHOW_PRIMARY_SIGNAL_BAR_IN_OPPORTUNISTIC_NETWORK_BOOLEAN], * [CarrierConfigManager.KEY_ALWAYS_SHOW_PRIMARY_SIGNAL_BAR_IN_OPPORTUNISTIC_NETWORK_BOOLEAN],
* and by checking which subscription is opportunistic, or which one is active. * and by checking which subscription is opportunistic, or which one is active.
*/ */
override val filteredSubscriptions: Flow<List<SubscriptionInfo>> = override val filteredSubscriptions: Flow<List<SubscriptionModel>> =
combine(unfilteredSubscriptions, activeMobileDataSubscriptionId) { unfilteredSubs, activeId combine(unfilteredSubscriptions, activeMobileDataSubscriptionId) { unfilteredSubs, activeId
-> ->
// Based on the old logic, // Based on the old logic,
@@ -154,15 +152,19 @@ constructor(
* subscription Id. This mapping is the same for every subscription. * subscription Id. This mapping is the same for every subscription.
*/ */
override val defaultMobileIconMapping: StateFlow<Map<String, MobileIconGroup>> = override val defaultMobileIconMapping: StateFlow<Map<String, MobileIconGroup>> =
mobileConnectionsRepo.defaultDataSubRatConfig mobileConnectionsRepo.defaultMobileIconMapping.stateIn(
.mapLatest { mobileMappingsProxy.mapIconSets(it) } scope,
.stateIn(scope, SharingStarted.WhileSubscribed(), initialValue = mapOf()) SharingStarted.WhileSubscribed(),
initialValue = mapOf()
)
/** If there is no mapping in [defaultMobileIconMapping], then use this default icon group */ /** If there is no mapping in [defaultMobileIconMapping], then use this default icon group */
override val defaultMobileIconGroup: StateFlow<MobileIconGroup> = override val defaultMobileIconGroup: StateFlow<MobileIconGroup> =
mobileConnectionsRepo.defaultDataSubRatConfig mobileConnectionsRepo.defaultMobileIconGroup.stateIn(
.mapLatest { mobileMappingsProxy.getDefaultIcons(it) } scope,
.stateIn(scope, SharingStarted.WhileSubscribed(), initialValue = TelephonyIcons.G) SharingStarted.WhileSubscribed(),
initialValue = TelephonyIcons.G
)
/** /**
* We want to show an error state when cellular has actually failed to validate, but not if some * We want to show an error state when cellular has actually failed to validate, but not if some
@@ -189,7 +191,6 @@ constructor(
defaultMobileIconMapping, defaultMobileIconMapping,
defaultMobileIconGroup, defaultMobileIconGroup,
isDefaultConnectionFailed, isDefaultConnectionFailed,
mobileMappingsProxy,
mobileConnectionsRepo.getRepoForSubId(subId), mobileConnectionsRepo.getRepoForSubId(subId),
) )
} }

View File

@@ -56,8 +56,8 @@ constructor(
private val statusBarPipelineFlags: StatusBarPipelineFlags, private val statusBarPipelineFlags: StatusBarPipelineFlags,
) : CoreStartable { ) : CoreStartable {
private val mobileSubIds: Flow<List<Int>> = private val mobileSubIds: Flow<List<Int>> =
interactor.filteredSubscriptions.mapLatest { infos -> interactor.filteredSubscriptions.mapLatest { subscriptions ->
infos.map { subscriptionInfo -> subscriptionInfo.subscriptionId } subscriptions.map { subscriptionModel -> subscriptionModel.subscriptionId }
} }
/** /**

View File

@@ -32,6 +32,8 @@ class ModernStatusBarMobileView(
attrs: AttributeSet?, attrs: AttributeSet?,
) : BaseStatusBarFrameLayout(context, attrs) { ) : BaseStatusBarFrameLayout(context, attrs) {
var subId: Int = -1
private lateinit var slot: String private lateinit var slot: String
override fun getSlot() = slot override fun getSlot() = slot
@@ -76,6 +78,7 @@ class ModernStatusBarMobileView(
as ModernStatusBarMobileView) as ModernStatusBarMobileView)
.also { .also {
it.slot = slot it.slot = slot
it.subId = viewModel.subscriptionId
MobileIconBinder.bind(it, viewModel) MobileIconBinder.bind(it, viewModel)
} }
} }

View File

@@ -23,7 +23,7 @@ import com.android.systemui.statusbar.pipeline.mobile.ui.view.ModernStatusBarMob
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
import javax.inject.Inject import javax.inject.Inject
import kotlinx.coroutines.InternalCoroutinesApi import kotlinx.coroutines.InternalCoroutinesApi
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.StateFlow
/** /**
* View model for describing the system's current mobile cellular connections. The result is a list * View model for describing the system's current mobile cellular connections. The result is a list
@@ -33,7 +33,7 @@ import kotlinx.coroutines.flow.Flow
class MobileIconsViewModel class MobileIconsViewModel
@Inject @Inject
constructor( constructor(
val subscriptionIdsFlow: Flow<List<Int>>, val subscriptionIdsFlow: StateFlow<List<Int>>,
private val interactor: MobileIconsInteractor, private val interactor: MobileIconsInteractor,
private val logger: ConnectivityPipelineLogger, private val logger: ConnectivityPipelineLogger,
) { ) {
@@ -51,7 +51,7 @@ constructor(
private val interactor: MobileIconsInteractor, private val interactor: MobileIconsInteractor,
private val logger: ConnectivityPipelineLogger, private val logger: ConnectivityPipelineLogger,
) { ) {
fun create(subscriptionIdsFlow: Flow<List<Int>>): MobileIconsViewModel { fun create(subscriptionIdsFlow: StateFlow<List<Int>>): MobileIconsViewModel {
return MobileIconsViewModel( return MobileIconsViewModel(
subscriptionIdsFlow, subscriptionIdsFlow,
interactor, interactor,

View File

@@ -16,12 +16,13 @@
package com.android.systemui.statusbar.pipeline.mobile.data.repository package com.android.systemui.statusbar.pipeline.mobile.data.repository
import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileSubscriptionModel import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
class FakeMobileConnectionRepository : MobileConnectionRepository { // TODO(b/261632894): remove this in favor of the real impl or DemoMobileConnectionRepository
private val _subscriptionsModelFlow = MutableStateFlow(MobileSubscriptionModel()) class FakeMobileConnectionRepository(override val subId: Int) : MobileConnectionRepository {
override val subscriptionModelFlow = _subscriptionsModelFlow private val _connectionInfo = MutableStateFlow(MobileConnectionModel())
override val connectionInfo = _connectionInfo
private val _dataEnabled = MutableStateFlow(true) private val _dataEnabled = MutableStateFlow(true)
override val dataEnabled = _dataEnabled override val dataEnabled = _dataEnabled
@@ -29,8 +30,8 @@ class FakeMobileConnectionRepository : MobileConnectionRepository {
private val _isDefaultDataSubscription = MutableStateFlow(true) private val _isDefaultDataSubscription = MutableStateFlow(true)
override val isDefaultDataSubscription = _isDefaultDataSubscription override val isDefaultDataSubscription = _isDefaultDataSubscription
fun setMobileSubscriptionModel(model: MobileSubscriptionModel) { fun setConnectionInfo(model: MobileConnectionModel) {
_subscriptionsModelFlow.value = model _connectionInfo.value = model
} }
fun setDataEnabled(enabled: Boolean) { fun setDataEnabled(enabled: Boolean) {

View File

@@ -16,23 +16,43 @@
package com.android.systemui.statusbar.pipeline.mobile.data.repository package com.android.systemui.statusbar.pipeline.mobile.data.repository
import android.telephony.SubscriptionInfo
import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
import com.android.settingslib.mobile.MobileMappings.Config import android.telephony.TelephonyDisplayInfo
import android.telephony.TelephonyManager
import com.android.settingslib.SignalIcon
import com.android.settingslib.mobile.TelephonyIcons
import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectivityModel import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectivityModel
import kotlinx.coroutines.flow.Flow import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
class FakeMobileConnectionsRepository : MobileConnectionsRepository { // TODO(b/261632894): remove this in favor of the real impl or DemoMobileConnectionsRepository
private val _subscriptionsFlow = MutableStateFlow<List<SubscriptionInfo>>(listOf()) class FakeMobileConnectionsRepository(mobileMappings: MobileMappingsProxy) :
override val subscriptionsFlow: Flow<List<SubscriptionInfo>> = _subscriptionsFlow MobileConnectionsRepository {
val GSM_KEY = mobileMappings.toIconKey(GSM)
val LTE_KEY = mobileMappings.toIconKey(LTE)
val UMTS_KEY = mobileMappings.toIconKey(UMTS)
val LTE_ADVANCED_KEY = mobileMappings.toIconKeyOverride(LTE_ADVANCED_PRO)
/**
* To avoid a reliance on [MobileMappings], we'll build a simpler map from network type to
* mobile icon. See TelephonyManager.NETWORK_TYPES for a list of types and [TelephonyIcons] for
* the exhaustive set of icons
*/
val TEST_MAPPING: Map<String, SignalIcon.MobileIconGroup> =
mapOf(
GSM_KEY to TelephonyIcons.THREE_G,
LTE_KEY to TelephonyIcons.LTE,
UMTS_KEY to TelephonyIcons.FOUR_G,
LTE_ADVANCED_KEY to TelephonyIcons.NR_5G,
)
private val _subscriptions = MutableStateFlow<List<SubscriptionModel>>(listOf())
override val subscriptions = _subscriptions
private val _activeMobileDataSubscriptionId = MutableStateFlow(INVALID_SUBSCRIPTION_ID) private val _activeMobileDataSubscriptionId = MutableStateFlow(INVALID_SUBSCRIPTION_ID)
override val activeMobileDataSubscriptionId = _activeMobileDataSubscriptionId override val activeMobileDataSubscriptionId = _activeMobileDataSubscriptionId
private val _defaultDataSubRatConfig = MutableStateFlow(Config())
override val defaultDataSubRatConfig = _defaultDataSubRatConfig
private val _defaultDataSubId = MutableStateFlow(INVALID_SUBSCRIPTION_ID) private val _defaultDataSubId = MutableStateFlow(INVALID_SUBSCRIPTION_ID)
override val defaultDataSubId = _defaultDataSubId override val defaultDataSubId = _defaultDataSubId
@@ -41,18 +61,21 @@ class FakeMobileConnectionsRepository : MobileConnectionsRepository {
private val subIdRepos = mutableMapOf<Int, MobileConnectionRepository>() private val subIdRepos = mutableMapOf<Int, MobileConnectionRepository>()
override fun getRepoForSubId(subId: Int): MobileConnectionRepository { override fun getRepoForSubId(subId: Int): MobileConnectionRepository {
return subIdRepos[subId] ?: FakeMobileConnectionRepository().also { subIdRepos[subId] = it } return subIdRepos[subId]
?: FakeMobileConnectionRepository(subId).also { subIdRepos[subId] = it }
} }
private val _globalMobileDataSettingChangedEvent = MutableStateFlow(Unit) private val _globalMobileDataSettingChangedEvent = MutableStateFlow(Unit)
override val globalMobileDataSettingChangedEvent = _globalMobileDataSettingChangedEvent override val globalMobileDataSettingChangedEvent = _globalMobileDataSettingChangedEvent
fun setSubscriptions(subs: List<SubscriptionInfo>) { private val _defaultMobileIconMapping = MutableStateFlow(TEST_MAPPING)
_subscriptionsFlow.value = subs override val defaultMobileIconMapping = _defaultMobileIconMapping
}
fun setDefaultDataSubRatConfig(config: Config) { private val _defaultMobileIconGroup = MutableStateFlow(DEFAULT_ICON)
_defaultDataSubRatConfig.value = config override val defaultMobileIconGroup = _defaultMobileIconGroup
fun setSubscriptions(subs: List<SubscriptionModel>) {
_subscriptions.value = subs
} }
fun setDefaultDataSubId(id: Int) { fun setDefaultDataSubId(id: Int) {
@@ -74,4 +97,14 @@ class FakeMobileConnectionsRepository : MobileConnectionsRepository {
fun setMobileConnectionRepositoryMap(connections: Map<Int, MobileConnectionRepository>) { fun setMobileConnectionRepositoryMap(connections: Map<Int, MobileConnectionRepository>) {
connections.forEach { entry -> subIdRepos[entry.key] = entry.value } connections.forEach { entry -> subIdRepos[entry.key] = entry.value }
} }
companion object {
val DEFAULT_ICON = TelephonyIcons.G
// Use [MobileMappings] to define some simple definitions
const val GSM = TelephonyManager.NETWORK_TYPE_GSM
const val LTE = TelephonyManager.NETWORK_TYPE_LTE
const val UMTS = TelephonyManager.NETWORK_TYPE_UMTS
const val LTE_ADVANCED_PRO = TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_LTE_ADVANCED_PRO
}
} }

View File

@@ -24,11 +24,13 @@ import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase import com.android.systemui.SysuiTestCase
import com.android.systemui.demomode.DemoMode import com.android.systemui.demomode.DemoMode
import com.android.systemui.demomode.DemoModeController import com.android.systemui.demomode.DemoModeController
import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.DemoMobileConnectionsRepository import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.DemoMobileConnectionsRepository
import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.DemoModeMobileConnectionDataSource import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.DemoModeMobileConnectionDataSource
import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.model.FakeNetworkEventModel import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.model.FakeNetworkEventModel
import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.validMobileEvent import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.validMobileEvent
import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.MobileConnectionsRepositoryImpl import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.MobileConnectionsRepositoryImpl
import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.kotlinArgumentCaptor import com.android.systemui.util.mockito.kotlinArgumentCaptor
@@ -76,6 +78,7 @@ class MobileRepositorySwitcherTest : SysuiTestCase() {
private val globalSettings = FakeSettings() private val globalSettings = FakeSettings()
private val fakeNetworkEventsFlow = MutableStateFlow<FakeNetworkEventModel?>(null) private val fakeNetworkEventsFlow = MutableStateFlow<FakeNetworkEventModel?>(null)
private val mobileMappings = FakeMobileMappingsProxy()
private val scope = CoroutineScope(IMMEDIATE) private val scope = CoroutineScope(IMMEDIATE)
@@ -97,6 +100,7 @@ class MobileRepositorySwitcherTest : SysuiTestCase() {
subscriptionManager, subscriptionManager,
telephonyManager, telephonyManager,
logger, logger,
mobileMappings,
fakeBroadcastDispatcher, fakeBroadcastDispatcher,
globalSettings, globalSettings,
context, context,
@@ -155,15 +159,15 @@ class MobileRepositorySwitcherTest : SysuiTestCase() {
whenever(subscriptionManager.completeActiveSubscriptionInfoList) whenever(subscriptionManager.completeActiveSubscriptionInfoList)
.thenReturn(listOf(SUB_1, SUB_2)) .thenReturn(listOf(SUB_1, SUB_2))
var latest: List<SubscriptionInfo>? = null var latest: List<SubscriptionModel>? = null
val job = underTest.subscriptionsFlow.onEach { latest = it }.launchIn(this) val job = underTest.subscriptions.onEach { latest = it }.launchIn(this)
// The real subscriptions has 2 subs // The real subscriptions has 2 subs
whenever(subscriptionManager.completeActiveSubscriptionInfoList) whenever(subscriptionManager.completeActiveSubscriptionInfoList)
.thenReturn(listOf(SUB_1, SUB_2)) .thenReturn(listOf(SUB_1, SUB_2))
getSubscriptionCallback().onSubscriptionsChanged() getSubscriptionCallback().onSubscriptionsChanged()
assertThat(latest).isEqualTo(listOf(SUB_1, SUB_2)) assertThat(latest).isEqualTo(listOf(MODEL_1, MODEL_2))
// Demo mode turns on, and we should see only the demo subscriptions // Demo mode turns on, and we should see only the demo subscriptions
startDemoMode() startDemoMode()
@@ -176,7 +180,7 @@ class MobileRepositorySwitcherTest : SysuiTestCase() {
finishDemoMode() finishDemoMode()
assertThat(latest).isEqualTo(listOf(SUB_1, SUB_2)) assertThat(latest).isEqualTo(listOf(MODEL_1, MODEL_2))
job.cancel() job.cancel()
} }
@@ -211,9 +215,11 @@ class MobileRepositorySwitcherTest : SysuiTestCase() {
private const val SUB_1_ID = 1 private const val SUB_1_ID = 1
private val SUB_1 = private val SUB_1 =
mock<SubscriptionInfo>().also { whenever(it.subscriptionId).thenReturn(SUB_1_ID) } mock<SubscriptionInfo>().also { whenever(it.subscriptionId).thenReturn(SUB_1_ID) }
private val MODEL_1 = SubscriptionModel(subscriptionId = SUB_1_ID)
private const val SUB_2_ID = 2 private const val SUB_2_ID = 2
private val SUB_2 = private val SUB_2 =
mock<SubscriptionInfo>().also { whenever(it.subscriptionId).thenReturn(SUB_2_ID) } mock<SubscriptionInfo>().also { whenever(it.subscriptionId).thenReturn(SUB_2_ID) }
private val MODEL_2 = SubscriptionModel(subscriptionId = SUB_2_ID)
} }
} }

View File

@@ -23,7 +23,7 @@ import com.android.settingslib.SignalIcon
import com.android.settingslib.mobile.TelephonyIcons import com.android.settingslib.mobile.TelephonyIcons
import com.android.systemui.SysuiTestCase import com.android.systemui.SysuiTestCase
import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState
import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileSubscriptionModel import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel
import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.model.FakeNetworkEventModel import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.model.FakeNetworkEventModel
import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever import com.android.systemui.util.mockito.whenever
@@ -109,18 +109,18 @@ internal class DemoMobileConnectionParameterizedTest(private val testCase: TestC
) { ) {
when (model) { when (model) {
is FakeNetworkEventModel.Mobile -> { is FakeNetworkEventModel.Mobile -> {
val subscriptionModel: MobileSubscriptionModel = conn.subscriptionModelFlow.value val connectionInfo: MobileConnectionModel = conn.connectionInfo.value
assertThat(conn.subId).isEqualTo(model.subId) assertThat(conn.subId).isEqualTo(model.subId)
assertThat(subscriptionModel.cdmaLevel).isEqualTo(model.level) assertThat(connectionInfo.cdmaLevel).isEqualTo(model.level)
assertThat(subscriptionModel.primaryLevel).isEqualTo(model.level) assertThat(connectionInfo.primaryLevel).isEqualTo(model.level)
assertThat(subscriptionModel.dataActivityDirection).isEqualTo(model.activity) assertThat(connectionInfo.dataActivityDirection).isEqualTo(model.activity)
assertThat(subscriptionModel.carrierNetworkChangeActive) assertThat(connectionInfo.carrierNetworkChangeActive)
.isEqualTo(model.carrierNetworkChange) .isEqualTo(model.carrierNetworkChange)
// TODO(b/261029387): check these once we start handling them // TODO(b/261029387): check these once we start handling them
assertThat(subscriptionModel.isEmergencyOnly).isFalse() assertThat(connectionInfo.isEmergencyOnly).isFalse()
assertThat(subscriptionModel.isGsm).isFalse() assertThat(connectionInfo.isGsm).isFalse()
assertThat(subscriptionModel.dataConnectionState) assertThat(connectionInfo.dataConnectionState)
.isEqualTo(DataConnectionState.Connected) .isEqualTo(DataConnectionState.Connected)
} }
// MobileDisabled isn't combinatorial in nature, and is tested in // MobileDisabled isn't combinatorial in nature, and is tested in

View File

@@ -16,7 +16,6 @@
package com.android.systemui.statusbar.pipeline.mobile.data.repository.demo package com.android.systemui.statusbar.pipeline.mobile.data.repository.demo
import android.telephony.SubscriptionInfo
import android.telephony.TelephonyManager.DATA_ACTIVITY_INOUT import android.telephony.TelephonyManager.DATA_ACTIVITY_INOUT
import android.telephony.TelephonyManager.UNKNOWN_CARRIER_ID import android.telephony.TelephonyManager.UNKNOWN_CARRIER_ID
import androidx.test.filters.SmallTest import androidx.test.filters.SmallTest
@@ -24,7 +23,8 @@ import com.android.settingslib.SignalIcon
import com.android.settingslib.mobile.TelephonyIcons.THREE_G import com.android.settingslib.mobile.TelephonyIcons.THREE_G
import com.android.systemui.SysuiTestCase import com.android.systemui.SysuiTestCase
import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState
import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileSubscriptionModel import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.model.FakeNetworkEventModel import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.model.FakeNetworkEventModel
import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.model.FakeNetworkEventModel.MobileDisabled import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.model.FakeNetworkEventModel.MobileDisabled
import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.mock
@@ -73,8 +73,8 @@ class DemoMobileConnectionsRepositoryTest : SysuiTestCase() {
@Test @Test
fun `network event - create new subscription`() = fun `network event - create new subscription`() =
testScope.runTest { testScope.runTest {
var latest: List<SubscriptionInfo>? = null var latest: List<SubscriptionModel>? = null
val job = underTest.subscriptionsFlow.onEach { latest = it }.launchIn(this) val job = underTest.subscriptions.onEach { latest = it }.launchIn(this)
assertThat(latest).isEmpty() assertThat(latest).isEmpty()
@@ -89,8 +89,8 @@ class DemoMobileConnectionsRepositoryTest : SysuiTestCase() {
@Test @Test
fun `network event - reuses subscription when same Id`() = fun `network event - reuses subscription when same Id`() =
testScope.runTest { testScope.runTest {
var latest: List<SubscriptionInfo>? = null var latest: List<SubscriptionModel>? = null
val job = underTest.subscriptionsFlow.onEach { latest = it }.launchIn(this) val job = underTest.subscriptions.onEach { latest = it }.launchIn(this)
assertThat(latest).isEmpty() assertThat(latest).isEmpty()
@@ -111,8 +111,8 @@ class DemoMobileConnectionsRepositoryTest : SysuiTestCase() {
@Test @Test
fun `multiple subscriptions`() = fun `multiple subscriptions`() =
testScope.runTest { testScope.runTest {
var latest: List<SubscriptionInfo>? = null var latest: List<SubscriptionModel>? = null
val job = underTest.subscriptionsFlow.onEach { latest = it }.launchIn(this) val job = underTest.subscriptions.onEach { latest = it }.launchIn(this)
fakeNetworkEventFlow.value = validMobileEvent(subId = 1) fakeNetworkEventFlow.value = validMobileEvent(subId = 1)
fakeNetworkEventFlow.value = validMobileEvent(subId = 2) fakeNetworkEventFlow.value = validMobileEvent(subId = 2)
@@ -125,8 +125,8 @@ class DemoMobileConnectionsRepositoryTest : SysuiTestCase() {
@Test @Test
fun `mobile disabled event - disables connection - subId specified - single conn`() = fun `mobile disabled event - disables connection - subId specified - single conn`() =
testScope.runTest { testScope.runTest {
var latest: List<SubscriptionInfo>? = null var latest: List<SubscriptionModel>? = null
val job = underTest.subscriptionsFlow.onEach { latest = it }.launchIn(this) val job = underTest.subscriptions.onEach { latest = it }.launchIn(this)
fakeNetworkEventFlow.value = validMobileEvent(subId = 1, level = 1) fakeNetworkEventFlow.value = validMobileEvent(subId = 1, level = 1)
@@ -140,8 +140,8 @@ class DemoMobileConnectionsRepositoryTest : SysuiTestCase() {
@Test @Test
fun `mobile disabled event - disables connection - subId not specified - single conn`() = fun `mobile disabled event - disables connection - subId not specified - single conn`() =
testScope.runTest { testScope.runTest {
var latest: List<SubscriptionInfo>? = null var latest: List<SubscriptionModel>? = null
val job = underTest.subscriptionsFlow.onEach { latest = it }.launchIn(this) val job = underTest.subscriptions.onEach { latest = it }.launchIn(this)
fakeNetworkEventFlow.value = validMobileEvent(subId = 1, level = 1) fakeNetworkEventFlow.value = validMobileEvent(subId = 1, level = 1)
@@ -155,8 +155,8 @@ class DemoMobileConnectionsRepositoryTest : SysuiTestCase() {
@Test @Test
fun `mobile disabled event - disables connection - subId specified - multiple conn`() = fun `mobile disabled event - disables connection - subId specified - multiple conn`() =
testScope.runTest { testScope.runTest {
var latest: List<SubscriptionInfo>? = null var latest: List<SubscriptionModel>? = null
val job = underTest.subscriptionsFlow.onEach { latest = it }.launchIn(this) val job = underTest.subscriptions.onEach { latest = it }.launchIn(this)
fakeNetworkEventFlow.value = validMobileEvent(subId = 1, level = 1) fakeNetworkEventFlow.value = validMobileEvent(subId = 1, level = 1)
fakeNetworkEventFlow.value = validMobileEvent(subId = 2, level = 1) fakeNetworkEventFlow.value = validMobileEvent(subId = 2, level = 1)
@@ -171,8 +171,8 @@ class DemoMobileConnectionsRepositoryTest : SysuiTestCase() {
@Test @Test
fun `mobile disabled event - subId not specified - multiple conn - ignores command`() = fun `mobile disabled event - subId not specified - multiple conn - ignores command`() =
testScope.runTest { testScope.runTest {
var latest: List<SubscriptionInfo>? = null var latest: List<SubscriptionModel>? = null
val job = underTest.subscriptionsFlow.onEach { latest = it }.launchIn(this) val job = underTest.subscriptions.onEach { latest = it }.launchIn(this)
fakeNetworkEventFlow.value = validMobileEvent(subId = 1, level = 1) fakeNetworkEventFlow.value = validMobileEvent(subId = 1, level = 1)
fakeNetworkEventFlow.value = validMobileEvent(subId = 2, level = 1) fakeNetworkEventFlow.value = validMobileEvent(subId = 2, level = 1)
@@ -184,13 +184,32 @@ class DemoMobileConnectionsRepositoryTest : SysuiTestCase() {
job.cancel() job.cancel()
} }
/** Regression test for b/261706421 */
@Test
fun `multiple connections - remove all - does not throw`() =
testScope.runTest {
var latest: List<SubscriptionModel>? = null
val job = underTest.subscriptions.onEach { latest = it }.launchIn(this)
// Two subscriptions are added
fakeNetworkEventFlow.value = validMobileEvent(subId = 1, level = 1)
fakeNetworkEventFlow.value = validMobileEvent(subId = 2, level = 1)
// Then both are removed by turning off demo mode
underTest.stopProcessingCommands()
assertThat(latest).isEmpty()
job.cancel()
}
@Test @Test
fun `demo connection - single subscription`() = fun `demo connection - single subscription`() =
testScope.runTest { testScope.runTest {
var currentEvent: FakeNetworkEventModel = validMobileEvent(subId = 1) var currentEvent: FakeNetworkEventModel = validMobileEvent(subId = 1)
var connections: List<DemoMobileConnectionRepository>? = null var connections: List<DemoMobileConnectionRepository>? = null
val job = val job =
underTest.subscriptionsFlow underTest.subscriptions
.onEach { infos -> .onEach { infos ->
connections = connections =
infos.map { info -> underTest.getRepoForSubId(info.subscriptionId) } infos.map { info -> underTest.getRepoForSubId(info.subscriptionId) }
@@ -222,7 +241,7 @@ class DemoMobileConnectionsRepositoryTest : SysuiTestCase() {
var connection2: DemoMobileConnectionRepository? = null var connection2: DemoMobileConnectionRepository? = null
var connections: List<DemoMobileConnectionRepository>? = null var connections: List<DemoMobileConnectionRepository>? = null
val job = val job =
underTest.subscriptionsFlow underTest.subscriptions
.onEach { infos -> .onEach { infos ->
connections = connections =
infos.map { info -> underTest.getRepoForSubId(info.subscriptionId) } infos.map { info -> underTest.getRepoForSubId(info.subscriptionId) }
@@ -266,18 +285,18 @@ class DemoMobileConnectionsRepositoryTest : SysuiTestCase() {
) { ) {
when (model) { when (model) {
is FakeNetworkEventModel.Mobile -> { is FakeNetworkEventModel.Mobile -> {
val subscriptionModel: MobileSubscriptionModel = conn.subscriptionModelFlow.value val connectionInfo: MobileConnectionModel = conn.connectionInfo.value
assertThat(conn.subId).isEqualTo(model.subId) assertThat(conn.subId).isEqualTo(model.subId)
assertThat(subscriptionModel.cdmaLevel).isEqualTo(model.level) assertThat(connectionInfo.cdmaLevel).isEqualTo(model.level)
assertThat(subscriptionModel.primaryLevel).isEqualTo(model.level) assertThat(connectionInfo.primaryLevel).isEqualTo(model.level)
assertThat(subscriptionModel.dataActivityDirection).isEqualTo(model.activity) assertThat(connectionInfo.dataActivityDirection).isEqualTo(model.activity)
assertThat(subscriptionModel.carrierNetworkChangeActive) assertThat(connectionInfo.carrierNetworkChangeActive)
.isEqualTo(model.carrierNetworkChange) .isEqualTo(model.carrierNetworkChange)
// TODO(b/261029387) check these once we start handling them // TODO(b/261029387) check these once we start handling them
assertThat(subscriptionModel.isEmergencyOnly).isFalse() assertThat(connectionInfo.isEmergencyOnly).isFalse()
assertThat(subscriptionModel.isGsm).isFalse() assertThat(connectionInfo.isGsm).isFalse()
assertThat(subscriptionModel.dataConnectionState) assertThat(connectionInfo.dataConnectionState)
.isEqualTo(DataConnectionState.Connected) .isEqualTo(DataConnectionState.Connected)
} }
else -> {} else -> {}

View File

@@ -37,10 +37,12 @@ import android.telephony.TelephonyManager.NETWORK_TYPE_UNKNOWN
import androidx.test.filters.SmallTest import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase import com.android.systemui.SysuiTestCase
import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState
import com.android.systemui.statusbar.pipeline.mobile.data.model.DefaultNetworkType import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileSubscriptionModel import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType.DefaultNetworkType
import com.android.systemui.statusbar.pipeline.mobile.data.model.OverrideNetworkType 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.repository.FakeMobileConnectionsRepository import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository
import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.argumentCaptor import com.android.systemui.util.mockito.argumentCaptor
@@ -72,8 +74,9 @@ class MobileConnectionRepositoryTest : SysuiTestCase() {
@Mock private lateinit var logger: ConnectivityPipelineLogger @Mock private lateinit var logger: ConnectivityPipelineLogger
private val scope = CoroutineScope(IMMEDIATE) private val scope = CoroutineScope(IMMEDIATE)
private val mobileMappings = FakeMobileMappingsProxy()
private val globalSettings = FakeSettings() private val globalSettings = FakeSettings()
private val connectionsRepo = FakeMobileConnectionsRepository() private val connectionsRepo = FakeMobileConnectionsRepository(mobileMappings)
@Before @Before
fun setUp() { fun setUp() {
@@ -89,6 +92,7 @@ class MobileConnectionRepositoryTest : SysuiTestCase() {
globalSettings, globalSettings,
connectionsRepo.defaultDataSubId, connectionsRepo.defaultDataSubId,
connectionsRepo.globalMobileDataSettingChangedEvent, connectionsRepo.globalMobileDataSettingChangedEvent,
mobileMappings,
IMMEDIATE, IMMEDIATE,
logger, logger,
scope, scope,
@@ -103,10 +107,10 @@ class MobileConnectionRepositoryTest : SysuiTestCase() {
@Test @Test
fun testFlowForSubId_default() = fun testFlowForSubId_default() =
runBlocking(IMMEDIATE) { runBlocking(IMMEDIATE) {
var latest: MobileSubscriptionModel? = null var latest: MobileConnectionModel? = null
val job = underTest.subscriptionModelFlow.onEach { latest = it }.launchIn(this) val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
assertThat(latest).isEqualTo(MobileSubscriptionModel()) assertThat(latest).isEqualTo(MobileConnectionModel())
job.cancel() job.cancel()
} }
@@ -114,8 +118,8 @@ class MobileConnectionRepositoryTest : SysuiTestCase() {
@Test @Test
fun testFlowForSubId_emergencyOnly() = fun testFlowForSubId_emergencyOnly() =
runBlocking(IMMEDIATE) { runBlocking(IMMEDIATE) {
var latest: MobileSubscriptionModel? = null var latest: MobileConnectionModel? = null
val job = underTest.subscriptionModelFlow.onEach { latest = it }.launchIn(this) val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
val serviceState = ServiceState() val serviceState = ServiceState()
serviceState.isEmergencyOnly = true serviceState.isEmergencyOnly = true
@@ -130,8 +134,8 @@ class MobileConnectionRepositoryTest : SysuiTestCase() {
@Test @Test
fun testFlowForSubId_emergencyOnly_toggles() = fun testFlowForSubId_emergencyOnly_toggles() =
runBlocking(IMMEDIATE) { runBlocking(IMMEDIATE) {
var latest: MobileSubscriptionModel? = null var latest: MobileConnectionModel? = null
val job = underTest.subscriptionModelFlow.onEach { latest = it }.launchIn(this) val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
val callback = getTelephonyCallbackForType<ServiceStateListener>() val callback = getTelephonyCallbackForType<ServiceStateListener>()
val serviceState = ServiceState() val serviceState = ServiceState()
@@ -148,8 +152,8 @@ class MobileConnectionRepositoryTest : SysuiTestCase() {
@Test @Test
fun testFlowForSubId_signalStrengths_levelsUpdate() = fun testFlowForSubId_signalStrengths_levelsUpdate() =
runBlocking(IMMEDIATE) { runBlocking(IMMEDIATE) {
var latest: MobileSubscriptionModel? = null var latest: MobileConnectionModel? = null
val job = underTest.subscriptionModelFlow.onEach { latest = it }.launchIn(this) val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
val callback = getTelephonyCallbackForType<TelephonyCallback.SignalStrengthsListener>() val callback = getTelephonyCallbackForType<TelephonyCallback.SignalStrengthsListener>()
val strength = signalStrength(gsmLevel = 1, cdmaLevel = 2, isGsm = true) val strength = signalStrength(gsmLevel = 1, cdmaLevel = 2, isGsm = true)
@@ -165,8 +169,8 @@ class MobileConnectionRepositoryTest : SysuiTestCase() {
@Test @Test
fun testFlowForSubId_dataConnectionState_connected() = fun testFlowForSubId_dataConnectionState_connected() =
runBlocking(IMMEDIATE) { runBlocking(IMMEDIATE) {
var latest: MobileSubscriptionModel? = null var latest: MobileConnectionModel? = null
val job = underTest.subscriptionModelFlow.onEach { latest = it }.launchIn(this) val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
val callback = val callback =
getTelephonyCallbackForType<TelephonyCallback.DataConnectionStateListener>() getTelephonyCallbackForType<TelephonyCallback.DataConnectionStateListener>()
@@ -180,8 +184,8 @@ class MobileConnectionRepositoryTest : SysuiTestCase() {
@Test @Test
fun testFlowForSubId_dataConnectionState_connecting() = fun testFlowForSubId_dataConnectionState_connecting() =
runBlocking(IMMEDIATE) { runBlocking(IMMEDIATE) {
var latest: MobileSubscriptionModel? = null var latest: MobileConnectionModel? = null
val job = underTest.subscriptionModelFlow.onEach { latest = it }.launchIn(this) val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
val callback = val callback =
getTelephonyCallbackForType<TelephonyCallback.DataConnectionStateListener>() getTelephonyCallbackForType<TelephonyCallback.DataConnectionStateListener>()
@@ -195,8 +199,8 @@ class MobileConnectionRepositoryTest : SysuiTestCase() {
@Test @Test
fun testFlowForSubId_dataConnectionState_disconnected() = fun testFlowForSubId_dataConnectionState_disconnected() =
runBlocking(IMMEDIATE) { runBlocking(IMMEDIATE) {
var latest: MobileSubscriptionModel? = null var latest: MobileConnectionModel? = null
val job = underTest.subscriptionModelFlow.onEach { latest = it }.launchIn(this) val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
val callback = val callback =
getTelephonyCallbackForType<TelephonyCallback.DataConnectionStateListener>() getTelephonyCallbackForType<TelephonyCallback.DataConnectionStateListener>()
@@ -210,8 +214,8 @@ class MobileConnectionRepositoryTest : SysuiTestCase() {
@Test @Test
fun testFlowForSubId_dataConnectionState_disconnecting() = fun testFlowForSubId_dataConnectionState_disconnecting() =
runBlocking(IMMEDIATE) { runBlocking(IMMEDIATE) {
var latest: MobileSubscriptionModel? = null var latest: MobileConnectionModel? = null
val job = underTest.subscriptionModelFlow.onEach { latest = it }.launchIn(this) val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
val callback = val callback =
getTelephonyCallbackForType<TelephonyCallback.DataConnectionStateListener>() getTelephonyCallbackForType<TelephonyCallback.DataConnectionStateListener>()
@@ -225,8 +229,8 @@ class MobileConnectionRepositoryTest : SysuiTestCase() {
@Test @Test
fun testFlowForSubId_dataConnectionState_unknown() = fun testFlowForSubId_dataConnectionState_unknown() =
runBlocking(IMMEDIATE) { runBlocking(IMMEDIATE) {
var latest: MobileSubscriptionModel? = null var latest: MobileConnectionModel? = null
val job = underTest.subscriptionModelFlow.onEach { latest = it }.launchIn(this) val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
val callback = val callback =
getTelephonyCallbackForType<TelephonyCallback.DataConnectionStateListener>() getTelephonyCallbackForType<TelephonyCallback.DataConnectionStateListener>()
@@ -240,8 +244,8 @@ class MobileConnectionRepositoryTest : SysuiTestCase() {
@Test @Test
fun testFlowForSubId_dataActivity() = fun testFlowForSubId_dataActivity() =
runBlocking(IMMEDIATE) { runBlocking(IMMEDIATE) {
var latest: MobileSubscriptionModel? = null var latest: MobileConnectionModel? = null
val job = underTest.subscriptionModelFlow.onEach { latest = it }.launchIn(this) val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
val callback = getTelephonyCallbackForType<TelephonyCallback.DataActivityListener>() val callback = getTelephonyCallbackForType<TelephonyCallback.DataActivityListener>()
callback.onDataActivity(3) callback.onDataActivity(3)
@@ -254,8 +258,8 @@ class MobileConnectionRepositoryTest : SysuiTestCase() {
@Test @Test
fun testFlowForSubId_carrierNetworkChange() = fun testFlowForSubId_carrierNetworkChange() =
runBlocking(IMMEDIATE) { runBlocking(IMMEDIATE) {
var latest: MobileSubscriptionModel? = null var latest: MobileConnectionModel? = null
val job = underTest.subscriptionModelFlow.onEach { latest = it }.launchIn(this) val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
val callback = getTelephonyCallbackForType<TelephonyCallback.CarrierNetworkListener>() val callback = getTelephonyCallbackForType<TelephonyCallback.CarrierNetworkListener>()
callback.onCarrierNetworkChange(true) callback.onCarrierNetworkChange(true)
@@ -268,11 +272,11 @@ class MobileConnectionRepositoryTest : SysuiTestCase() {
@Test @Test
fun subscriptionFlow_networkType_default() = fun subscriptionFlow_networkType_default() =
runBlocking(IMMEDIATE) { runBlocking(IMMEDIATE) {
var latest: MobileSubscriptionModel? = null var latest: MobileConnectionModel? = null
val job = underTest.subscriptionModelFlow.onEach { latest = it }.launchIn(this) val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
val type = NETWORK_TYPE_UNKNOWN val type = NETWORK_TYPE_UNKNOWN
val expected = DefaultNetworkType(type) val expected = UnknownNetworkType
assertThat(latest?.resolvedNetworkType).isEqualTo(expected) assertThat(latest?.resolvedNetworkType).isEqualTo(expected)
@@ -282,12 +286,12 @@ class MobileConnectionRepositoryTest : SysuiTestCase() {
@Test @Test
fun subscriptionFlow_networkType_updatesUsingDefault() = fun subscriptionFlow_networkType_updatesUsingDefault() =
runBlocking(IMMEDIATE) { runBlocking(IMMEDIATE) {
var latest: MobileSubscriptionModel? = null var latest: MobileConnectionModel? = null
val job = underTest.subscriptionModelFlow.onEach { latest = it }.launchIn(this) val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
val callback = getTelephonyCallbackForType<TelephonyCallback.DisplayInfoListener>() val callback = getTelephonyCallbackForType<TelephonyCallback.DisplayInfoListener>()
val type = NETWORK_TYPE_LTE val type = NETWORK_TYPE_LTE
val expected = DefaultNetworkType(type) val expected = DefaultNetworkType(type, mobileMappings.toIconKey(type))
val ti = mock<TelephonyDisplayInfo>().also { whenever(it.networkType).thenReturn(type) } val ti = mock<TelephonyDisplayInfo>().also { whenever(it.networkType).thenReturn(type) }
callback.onDisplayInfoChanged(ti) callback.onDisplayInfoChanged(ti)
@@ -299,14 +303,15 @@ class MobileConnectionRepositoryTest : SysuiTestCase() {
@Test @Test
fun subscriptionFlow_networkType_updatesUsingOverride() = fun subscriptionFlow_networkType_updatesUsingOverride() =
runBlocking(IMMEDIATE) { runBlocking(IMMEDIATE) {
var latest: MobileSubscriptionModel? = null var latest: MobileConnectionModel? = null
val job = underTest.subscriptionModelFlow.onEach { latest = it }.launchIn(this) val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
val callback = getTelephonyCallbackForType<TelephonyCallback.DisplayInfoListener>() val callback = getTelephonyCallbackForType<TelephonyCallback.DisplayInfoListener>()
val type = OVERRIDE_NETWORK_TYPE_LTE_CA val type = OVERRIDE_NETWORK_TYPE_LTE_CA
val expected = OverrideNetworkType(type) val expected = OverrideNetworkType(type, mobileMappings.toIconKeyOverride(type))
val ti = val ti =
mock<TelephonyDisplayInfo>().also { mock<TelephonyDisplayInfo>().also {
whenever(it.networkType).thenReturn(type)
whenever(it.overrideNetworkType).thenReturn(type) whenever(it.overrideNetworkType).thenReturn(type)
} }
callback.onDisplayInfoChanged(ti) callback.onDisplayInfoChanged(ti)

View File

@@ -32,6 +32,8 @@ import androidx.test.filters.SmallTest
import com.android.internal.telephony.PhoneConstants import com.android.internal.telephony.PhoneConstants
import com.android.systemui.SysuiTestCase import com.android.systemui.SysuiTestCase
import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectivityModel import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectivityModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.argumentCaptor import com.android.systemui.util.mockito.argumentCaptor
@@ -50,6 +52,7 @@ import org.junit.After
import org.junit.Assert.assertThrows import org.junit.Assert.assertThrows
import org.junit.Before import org.junit.Before
import org.junit.Test import org.junit.Test
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Mock import org.mockito.Mock
import org.mockito.Mockito.verify import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations import org.mockito.MockitoAnnotations
@@ -65,6 +68,8 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() {
@Mock private lateinit var telephonyManager: TelephonyManager @Mock private lateinit var telephonyManager: TelephonyManager
@Mock private lateinit var logger: ConnectivityPipelineLogger @Mock private lateinit var logger: ConnectivityPipelineLogger
private val mobileMappings = FakeMobileMappingsProxy()
private val scope = CoroutineScope(IMMEDIATE) private val scope = CoroutineScope(IMMEDIATE)
private val globalSettings = FakeSettings() private val globalSettings = FakeSettings()
@@ -72,18 +77,37 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() {
fun setUp() { fun setUp() {
MockitoAnnotations.initMocks(this) MockitoAnnotations.initMocks(this)
// Set up so the individual connection repositories
whenever(telephonyManager.createForSubscriptionId(anyInt())).thenAnswer { invocation ->
telephonyManager.also {
whenever(telephonyManager.subscriptionId).thenReturn(invocation.getArgument(0))
}
}
val connectionFactory: MobileConnectionRepositoryImpl.Factory =
MobileConnectionRepositoryImpl.Factory(
context = context,
telephonyManager = telephonyManager,
bgDispatcher = IMMEDIATE,
globalSettings = globalSettings,
logger = logger,
mobileMappingsProxy = mobileMappings,
scope = scope,
)
underTest = underTest =
MobileConnectionsRepositoryImpl( MobileConnectionsRepositoryImpl(
connectivityManager, connectivityManager,
subscriptionManager, subscriptionManager,
telephonyManager, telephonyManager,
logger, logger,
mobileMappings,
fakeBroadcastDispatcher, fakeBroadcastDispatcher,
globalSettings, globalSettings,
context, context,
IMMEDIATE, IMMEDIATE,
scope, scope,
mock(), connectionFactory,
) )
} }
@@ -95,21 +119,21 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() {
@Test @Test
fun testSubscriptions_initiallyEmpty() = fun testSubscriptions_initiallyEmpty() =
runBlocking(IMMEDIATE) { runBlocking(IMMEDIATE) {
assertThat(underTest.subscriptionsFlow.value).isEqualTo(listOf<SubscriptionInfo>()) assertThat(underTest.subscriptions.value).isEqualTo(listOf<SubscriptionModel>())
} }
@Test @Test
fun testSubscriptions_listUpdates() = fun testSubscriptions_listUpdates() =
runBlocking(IMMEDIATE) { runBlocking(IMMEDIATE) {
var latest: List<SubscriptionInfo>? = null var latest: List<SubscriptionModel>? = null
val job = underTest.subscriptionsFlow.onEach { latest = it }.launchIn(this) val job = underTest.subscriptions.onEach { latest = it }.launchIn(this)
whenever(subscriptionManager.completeActiveSubscriptionInfoList) whenever(subscriptionManager.completeActiveSubscriptionInfoList)
.thenReturn(listOf(SUB_1, SUB_2)) .thenReturn(listOf(SUB_1, SUB_2))
getSubscriptionCallback().onSubscriptionsChanged() getSubscriptionCallback().onSubscriptionsChanged()
assertThat(latest).isEqualTo(listOf(SUB_1, SUB_2)) assertThat(latest).isEqualTo(listOf(MODEL_1, MODEL_2))
job.cancel() job.cancel()
} }
@@ -117,9 +141,9 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() {
@Test @Test
fun testSubscriptions_removingSub_updatesList() = fun testSubscriptions_removingSub_updatesList() =
runBlocking(IMMEDIATE) { runBlocking(IMMEDIATE) {
var latest: List<SubscriptionInfo>? = null var latest: List<SubscriptionModel>? = null
val job = underTest.subscriptionsFlow.onEach { latest = it }.launchIn(this) val job = underTest.subscriptions.onEach { latest = it }.launchIn(this)
// WHEN 2 networks show up // WHEN 2 networks show up
whenever(subscriptionManager.completeActiveSubscriptionInfoList) whenever(subscriptionManager.completeActiveSubscriptionInfoList)
@@ -132,7 +156,7 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() {
getSubscriptionCallback().onSubscriptionsChanged() getSubscriptionCallback().onSubscriptionsChanged()
// THEN the subscriptions list represents the newest change // THEN the subscriptions list represents the newest change
assertThat(latest).isEqualTo(listOf(SUB_2)) assertThat(latest).isEqualTo(listOf(MODEL_2))
job.cancel() job.cancel()
} }
@@ -162,7 +186,7 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() {
@Test @Test
fun testConnectionRepository_validSubId_isCached() = fun testConnectionRepository_validSubId_isCached() =
runBlocking(IMMEDIATE) { runBlocking(IMMEDIATE) {
val job = underTest.subscriptionsFlow.launchIn(this) val job = underTest.subscriptions.launchIn(this)
whenever(subscriptionManager.completeActiveSubscriptionInfoList) whenever(subscriptionManager.completeActiveSubscriptionInfoList)
.thenReturn(listOf(SUB_1)) .thenReturn(listOf(SUB_1))
@@ -179,7 +203,7 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() {
@Test @Test
fun testConnectionCache_clearsInvalidSubscriptions() = fun testConnectionCache_clearsInvalidSubscriptions() =
runBlocking(IMMEDIATE) { runBlocking(IMMEDIATE) {
val job = underTest.subscriptionsFlow.launchIn(this) val job = underTest.subscriptions.launchIn(this)
whenever(subscriptionManager.completeActiveSubscriptionInfoList) whenever(subscriptionManager.completeActiveSubscriptionInfoList)
.thenReturn(listOf(SUB_1, SUB_2)) .thenReturn(listOf(SUB_1, SUB_2))
@@ -202,10 +226,36 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() {
job.cancel() job.cancel()
} }
/** Regression test for b/261706421 */
@Test
fun testConnectionsCache_clearMultipleSubscriptionsAtOnce_doesNotThrow() =
runBlocking(IMMEDIATE) {
val job = underTest.subscriptions.launchIn(this)
whenever(subscriptionManager.completeActiveSubscriptionInfoList)
.thenReturn(listOf(SUB_1, SUB_2))
getSubscriptionCallback().onSubscriptionsChanged()
// Get repos to trigger caching
val repo1 = underTest.getRepoForSubId(SUB_1_ID)
val repo2 = underTest.getRepoForSubId(SUB_2_ID)
assertThat(underTest.getSubIdRepoCache())
.containsExactly(SUB_1_ID, repo1, SUB_2_ID, repo2)
// All subscriptions disappear
whenever(subscriptionManager.completeActiveSubscriptionInfoList).thenReturn(listOf())
getSubscriptionCallback().onSubscriptionsChanged()
assertThat(underTest.getSubIdRepoCache()).isEmpty()
job.cancel()
}
@Test @Test
fun testConnectionRepository_invalidSubId_throws() = fun testConnectionRepository_invalidSubId_throws() =
runBlocking(IMMEDIATE) { runBlocking(IMMEDIATE) {
val job = underTest.subscriptionsFlow.launchIn(this) val job = underTest.subscriptions.launchIn(this)
assertThrows(IllegalArgumentException::class.java) { assertThrows(IllegalArgumentException::class.java) {
underTest.getRepoForSubId(SUB_1_ID) underTest.getRepoForSubId(SUB_1_ID)
@@ -371,10 +421,12 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() {
private const val SUB_1_ID = 1 private const val SUB_1_ID = 1
private val SUB_1 = private val SUB_1 =
mock<SubscriptionInfo>().also { whenever(it.subscriptionId).thenReturn(SUB_1_ID) } mock<SubscriptionInfo>().also { whenever(it.subscriptionId).thenReturn(SUB_1_ID) }
private val MODEL_1 = SubscriptionModel(subscriptionId = SUB_1_ID)
private const val SUB_2_ID = 2 private const val SUB_2_ID = 2
private val SUB_2 = private val SUB_2 =
mock<SubscriptionInfo>().also { whenever(it.subscriptionId).thenReturn(SUB_2_ID) } mock<SubscriptionInfo>().also { whenever(it.subscriptionId).thenReturn(SUB_2_ID) }
private val MODEL_2 = SubscriptionModel(subscriptionId = SUB_2_ID)
private const val NET_ID = 123 private const val NET_ID = 123
private val NETWORK = mock<Network>().apply { whenever(getNetId()).thenReturn(NET_ID) } private val NETWORK = mock<Network>().apply { whenever(getNetId()).thenReturn(NET_ID) }

View File

@@ -16,14 +16,15 @@
package com.android.systemui.statusbar.pipeline.mobile.domain.interactor package com.android.systemui.statusbar.pipeline.mobile.domain.interactor
import android.telephony.SubscriptionInfo
import android.telephony.TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_LTE_ADVANCED_PRO import android.telephony.TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_LTE_ADVANCED_PRO
import android.telephony.TelephonyManager.NETWORK_TYPE_GSM import android.telephony.TelephonyManager.NETWORK_TYPE_GSM
import android.telephony.TelephonyManager.NETWORK_TYPE_LTE import android.telephony.TelephonyManager.NETWORK_TYPE_LTE
import android.telephony.TelephonyManager.NETWORK_TYPE_UMTS import android.telephony.TelephonyManager.NETWORK_TYPE_UMTS
import com.android.settingslib.SignalIcon.MobileIconGroup import com.android.settingslib.SignalIcon.MobileIconGroup
import com.android.settingslib.mobile.TelephonyIcons import com.android.settingslib.mobile.TelephonyIcons
import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
class FakeMobileIconsInteractor(mobileMappings: MobileMappingsProxy) : MobileIconsInteractor { class FakeMobileIconsInteractor(mobileMappings: MobileMappingsProxy) : MobileIconsInteractor {
@@ -47,8 +48,8 @@ class FakeMobileIconsInteractor(mobileMappings: MobileMappingsProxy) : MobileIco
override val isDefaultConnectionFailed = MutableStateFlow(false) override val isDefaultConnectionFailed = MutableStateFlow(false)
private val _filteredSubscriptions = MutableStateFlow<List<SubscriptionInfo>>(listOf()) private val _filteredSubscriptions = MutableStateFlow<List<SubscriptionModel>>(listOf())
override val filteredSubscriptions = _filteredSubscriptions override val filteredSubscriptions: Flow<List<SubscriptionModel>> = _filteredSubscriptions
private val _activeDataConnectionHasDataEnabled = MutableStateFlow(false) private val _activeDataConnectionHasDataEnabled = MutableStateFlow(false)
override val activeDataConnectionHasDataEnabled = _activeDataConnectionHasDataEnabled override val activeDataConnectionHasDataEnabled = _activeDataConnectionHasDataEnabled

View File

@@ -24,9 +24,9 @@ import com.android.settingslib.SignalIcon.MobileIconGroup
import com.android.settingslib.mobile.TelephonyIcons import com.android.settingslib.mobile.TelephonyIcons
import com.android.systemui.SysuiTestCase import com.android.systemui.SysuiTestCase
import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState
import com.android.systemui.statusbar.pipeline.mobile.data.model.DefaultNetworkType import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileSubscriptionModel import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType.DefaultNetworkType
import com.android.systemui.statusbar.pipeline.mobile.data.model.OverrideNetworkType import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType.OverrideNetworkType
import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionRepository 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.FIVE_G_OVERRIDE
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconsInteractor.Companion.FOUR_G import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconsInteractor.Companion.FOUR_G
@@ -49,7 +49,7 @@ class MobileIconInteractorTest : SysuiTestCase() {
private lateinit var underTest: MobileIconInteractor private lateinit var underTest: MobileIconInteractor
private val mobileMappingsProxy = FakeMobileMappingsProxy() private val mobileMappingsProxy = FakeMobileMappingsProxy()
private val mobileIconsInteractor = FakeMobileIconsInteractor(mobileMappingsProxy) private val mobileIconsInteractor = FakeMobileIconsInteractor(mobileMappingsProxy)
private val connectionRepository = FakeMobileConnectionRepository() private val connectionRepository = FakeMobileConnectionRepository(SUB_1_ID)
private val scope = CoroutineScope(IMMEDIATE) private val scope = CoroutineScope(IMMEDIATE)
@@ -62,7 +62,6 @@ class MobileIconInteractorTest : SysuiTestCase() {
mobileIconsInteractor.defaultMobileIconMapping, mobileIconsInteractor.defaultMobileIconMapping,
mobileIconsInteractor.defaultMobileIconGroup, mobileIconsInteractor.defaultMobileIconGroup,
mobileIconsInteractor.isDefaultConnectionFailed, mobileIconsInteractor.isDefaultConnectionFailed,
mobileMappingsProxy,
connectionRepository, connectionRepository,
) )
} }
@@ -70,8 +69,8 @@ class MobileIconInteractorTest : SysuiTestCase() {
@Test @Test
fun gsm_level_default_unknown() = fun gsm_level_default_unknown() =
runBlocking(IMMEDIATE) { runBlocking(IMMEDIATE) {
connectionRepository.setMobileSubscriptionModel( connectionRepository.setConnectionInfo(
MobileSubscriptionModel(isGsm = true), MobileConnectionModel(isGsm = true),
) )
var latest: Int? = null var latest: Int? = null
@@ -85,8 +84,8 @@ class MobileIconInteractorTest : SysuiTestCase() {
@Test @Test
fun gsm_usesGsmLevel() = fun gsm_usesGsmLevel() =
runBlocking(IMMEDIATE) { runBlocking(IMMEDIATE) {
connectionRepository.setMobileSubscriptionModel( connectionRepository.setConnectionInfo(
MobileSubscriptionModel( MobileConnectionModel(
isGsm = true, isGsm = true,
primaryLevel = GSM_LEVEL, primaryLevel = GSM_LEVEL,
cdmaLevel = CDMA_LEVEL cdmaLevel = CDMA_LEVEL
@@ -104,8 +103,8 @@ class MobileIconInteractorTest : SysuiTestCase() {
@Test @Test
fun cdma_level_default_unknown() = fun cdma_level_default_unknown() =
runBlocking(IMMEDIATE) { runBlocking(IMMEDIATE) {
connectionRepository.setMobileSubscriptionModel( connectionRepository.setConnectionInfo(
MobileSubscriptionModel(isGsm = false), MobileConnectionModel(isGsm = false),
) )
var latest: Int? = null var latest: Int? = null
@@ -118,8 +117,8 @@ class MobileIconInteractorTest : SysuiTestCase() {
@Test @Test
fun cdma_usesCdmaLevel() = fun cdma_usesCdmaLevel() =
runBlocking(IMMEDIATE) { runBlocking(IMMEDIATE) {
connectionRepository.setMobileSubscriptionModel( connectionRepository.setConnectionInfo(
MobileSubscriptionModel( MobileConnectionModel(
isGsm = false, isGsm = false,
primaryLevel = GSM_LEVEL, primaryLevel = GSM_LEVEL,
cdmaLevel = CDMA_LEVEL cdmaLevel = CDMA_LEVEL
@@ -137,8 +136,11 @@ class MobileIconInteractorTest : SysuiTestCase() {
@Test @Test
fun iconGroup_three_g() = fun iconGroup_three_g() =
runBlocking(IMMEDIATE) { runBlocking(IMMEDIATE) {
connectionRepository.setMobileSubscriptionModel( connectionRepository.setConnectionInfo(
MobileSubscriptionModel(resolvedNetworkType = DefaultNetworkType(THREE_G)), MobileConnectionModel(
resolvedNetworkType =
DefaultNetworkType(THREE_G, mobileMappingsProxy.toIconKey(THREE_G))
),
) )
var latest: MobileIconGroup? = null var latest: MobileIconGroup? = null
@@ -152,16 +154,23 @@ class MobileIconInteractorTest : SysuiTestCase() {
@Test @Test
fun iconGroup_updates_on_change() = fun iconGroup_updates_on_change() =
runBlocking(IMMEDIATE) { runBlocking(IMMEDIATE) {
connectionRepository.setMobileSubscriptionModel( connectionRepository.setConnectionInfo(
MobileSubscriptionModel(resolvedNetworkType = DefaultNetworkType(THREE_G)), MobileConnectionModel(
resolvedNetworkType =
DefaultNetworkType(THREE_G, mobileMappingsProxy.toIconKey(THREE_G))
),
) )
var latest: MobileIconGroup? = null var latest: MobileIconGroup? = null
val job = underTest.networkTypeIconGroup.onEach { latest = it }.launchIn(this) val job = underTest.networkTypeIconGroup.onEach { latest = it }.launchIn(this)
connectionRepository.setMobileSubscriptionModel( connectionRepository.setConnectionInfo(
MobileSubscriptionModel( MobileConnectionModel(
resolvedNetworkType = DefaultNetworkType(FOUR_G), resolvedNetworkType =
DefaultNetworkType(
FOUR_G,
mobileMappingsProxy.toIconKey(FOUR_G),
),
), ),
) )
yield() yield()
@@ -174,8 +183,14 @@ class MobileIconInteractorTest : SysuiTestCase() {
@Test @Test
fun iconGroup_5g_override_type() = fun iconGroup_5g_override_type() =
runBlocking(IMMEDIATE) { runBlocking(IMMEDIATE) {
connectionRepository.setMobileSubscriptionModel( connectionRepository.setConnectionInfo(
MobileSubscriptionModel(resolvedNetworkType = OverrideNetworkType(FIVE_G_OVERRIDE)), MobileConnectionModel(
resolvedNetworkType =
OverrideNetworkType(
FIVE_G_OVERRIDE,
mobileMappingsProxy.toIconKeyOverride(FIVE_G_OVERRIDE)
)
),
) )
var latest: MobileIconGroup? = null var latest: MobileIconGroup? = null
@@ -189,9 +204,13 @@ class MobileIconInteractorTest : SysuiTestCase() {
@Test @Test
fun iconGroup_default_if_no_lookup() = fun iconGroup_default_if_no_lookup() =
runBlocking(IMMEDIATE) { runBlocking(IMMEDIATE) {
connectionRepository.setMobileSubscriptionModel( connectionRepository.setConnectionInfo(
MobileSubscriptionModel( MobileConnectionModel(
resolvedNetworkType = DefaultNetworkType(NETWORK_TYPE_UNKNOWN), resolvedNetworkType =
DefaultNetworkType(
NETWORK_TYPE_UNKNOWN,
mobileMappingsProxy.toIconKey(NETWORK_TYPE_UNKNOWN)
),
), ),
) )
@@ -238,8 +257,8 @@ class MobileIconInteractorTest : SysuiTestCase() {
var latest: Boolean? = null var latest: Boolean? = null
val job = underTest.isDataConnected.onEach { latest = it }.launchIn(this) val job = underTest.isDataConnected.onEach { latest = it }.launchIn(this)
connectionRepository.setMobileSubscriptionModel( connectionRepository.setConnectionInfo(
MobileSubscriptionModel(dataConnectionState = DataConnectionState.Connected) MobileConnectionModel(dataConnectionState = DataConnectionState.Connected)
) )
yield() yield()
@@ -254,8 +273,8 @@ class MobileIconInteractorTest : SysuiTestCase() {
var latest: Boolean? = null var latest: Boolean? = null
val job = underTest.isDataConnected.onEach { latest = it }.launchIn(this) val job = underTest.isDataConnected.onEach { latest = it }.launchIn(this)
connectionRepository.setMobileSubscriptionModel( connectionRepository.setConnectionInfo(
MobileSubscriptionModel(dataConnectionState = DataConnectionState.Disconnected) MobileConnectionModel(dataConnectionState = DataConnectionState.Disconnected)
) )
assertThat(latest).isFalse() assertThat(latest).isFalse()

View File

@@ -16,17 +16,16 @@
package com.android.systemui.statusbar.pipeline.mobile.domain.interactor package com.android.systemui.statusbar.pipeline.mobile.domain.interactor
import android.telephony.SubscriptionInfo
import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
import androidx.test.filters.SmallTest import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase import com.android.systemui.SysuiTestCase
import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectivityModel import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectivityModel
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.FakeMobileConnectionRepository
import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository
import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeUserSetupRepository import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeUserSetupRepository
import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
import com.android.systemui.util.CarrierConfigTracker import com.android.systemui.util.CarrierConfigTracker
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
@@ -45,8 +44,8 @@ import org.mockito.MockitoAnnotations
class MobileIconsInteractorTest : SysuiTestCase() { class MobileIconsInteractorTest : SysuiTestCase() {
private lateinit var underTest: MobileIconsInteractor private lateinit var underTest: MobileIconsInteractor
private val userSetupRepository = FakeUserSetupRepository() private val userSetupRepository = FakeUserSetupRepository()
private val connectionsRepository = FakeMobileConnectionsRepository()
private val mobileMappingsProxy = FakeMobileMappingsProxy() private val mobileMappingsProxy = FakeMobileMappingsProxy()
private val connectionsRepository = FakeMobileConnectionsRepository(mobileMappingsProxy)
private val scope = CoroutineScope(IMMEDIATE) private val scope = CoroutineScope(IMMEDIATE)
@Mock private lateinit var carrierConfigTracker: CarrierConfigTracker @Mock private lateinit var carrierConfigTracker: CarrierConfigTracker
@@ -69,7 +68,6 @@ class MobileIconsInteractorTest : SysuiTestCase() {
MobileIconsInteractorImpl( MobileIconsInteractorImpl(
connectionsRepository, connectionsRepository,
carrierConfigTracker, carrierConfigTracker,
mobileMappingsProxy,
userSetupRepository, userSetupRepository,
scope scope
) )
@@ -80,10 +78,10 @@ class MobileIconsInteractorTest : SysuiTestCase() {
@Test @Test
fun filteredSubscriptions_default() = fun filteredSubscriptions_default() =
runBlocking(IMMEDIATE) { runBlocking(IMMEDIATE) {
var latest: List<SubscriptionInfo>? = null var latest: List<SubscriptionModel>? = null
val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this) val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this)
assertThat(latest).isEqualTo(listOf<SubscriptionInfo>()) assertThat(latest).isEqualTo(listOf<SubscriptionModel>())
job.cancel() job.cancel()
} }
@@ -93,7 +91,7 @@ class MobileIconsInteractorTest : SysuiTestCase() {
runBlocking(IMMEDIATE) { runBlocking(IMMEDIATE) {
connectionsRepository.setSubscriptions(listOf(SUB_1, SUB_2)) connectionsRepository.setSubscriptions(listOf(SUB_1, SUB_2))
var latest: List<SubscriptionInfo>? = null var latest: List<SubscriptionModel>? = null
val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this) val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this)
assertThat(latest).isEqualTo(listOf(SUB_1, SUB_2)) assertThat(latest).isEqualTo(listOf(SUB_1, SUB_2))
@@ -109,7 +107,7 @@ class MobileIconsInteractorTest : SysuiTestCase() {
whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault) whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault)
.thenReturn(false) .thenReturn(false)
var latest: List<SubscriptionInfo>? = null var latest: List<SubscriptionModel>? = null
val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this) val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this)
// Filtered subscriptions should show the active one when the config is false // Filtered subscriptions should show the active one when the config is false
@@ -126,7 +124,7 @@ class MobileIconsInteractorTest : SysuiTestCase() {
whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault) whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault)
.thenReturn(false) .thenReturn(false)
var latest: List<SubscriptionInfo>? = null var latest: List<SubscriptionModel>? = null
val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this) val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this)
// Filtered subscriptions should show the active one when the config is false // Filtered subscriptions should show the active one when the config is false
@@ -143,7 +141,7 @@ class MobileIconsInteractorTest : SysuiTestCase() {
whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault) whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault)
.thenReturn(true) .thenReturn(true)
var latest: List<SubscriptionInfo>? = null var latest: List<SubscriptionModel>? = null
val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this) val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this)
// Filtered subscriptions should show the primary (non-opportunistic) if the config is // Filtered subscriptions should show the primary (non-opportunistic) if the config is
@@ -161,7 +159,7 @@ class MobileIconsInteractorTest : SysuiTestCase() {
whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault) whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault)
.thenReturn(true) .thenReturn(true)
var latest: List<SubscriptionInfo>? = null var latest: List<SubscriptionModel>? = null
val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this) val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this)
// Filtered subscriptions should show the primary (non-opportunistic) if the config is // Filtered subscriptions should show the primary (non-opportunistic) if the config is
@@ -261,29 +259,19 @@ class MobileIconsInteractorTest : SysuiTestCase() {
private val IMMEDIATE = Dispatchers.Main.immediate private val IMMEDIATE = Dispatchers.Main.immediate
private const val SUB_1_ID = 1 private const val SUB_1_ID = 1
private val SUB_1 = private val SUB_1 = SubscriptionModel(subscriptionId = SUB_1_ID)
mock<SubscriptionInfo>().also { whenever(it.subscriptionId).thenReturn(SUB_1_ID) } private val CONNECTION_1 = FakeMobileConnectionRepository(SUB_1_ID)
private val CONNECTION_1 = FakeMobileConnectionRepository()
private const val SUB_2_ID = 2 private const val SUB_2_ID = 2
private val SUB_2 = private val SUB_2 = SubscriptionModel(subscriptionId = SUB_2_ID)
mock<SubscriptionInfo>().also { whenever(it.subscriptionId).thenReturn(SUB_2_ID) } private val CONNECTION_2 = FakeMobileConnectionRepository(SUB_2_ID)
private val CONNECTION_2 = FakeMobileConnectionRepository()
private const val SUB_3_ID = 3 private const val SUB_3_ID = 3
private val SUB_3_OPP = private val SUB_3_OPP = SubscriptionModel(subscriptionId = SUB_3_ID, isOpportunistic = true)
mock<SubscriptionInfo>().also { private val CONNECTION_3 = FakeMobileConnectionRepository(SUB_3_ID)
whenever(it.subscriptionId).thenReturn(SUB_3_ID)
whenever(it.isOpportunistic).thenReturn(true)
}
private val CONNECTION_3 = FakeMobileConnectionRepository()
private const val SUB_4_ID = 4 private const val SUB_4_ID = 4
private val SUB_4_OPP = private val SUB_4_OPP = SubscriptionModel(subscriptionId = SUB_4_ID, isOpportunistic = true)
mock<SubscriptionInfo>().also { private val CONNECTION_4 = FakeMobileConnectionRepository(SUB_4_ID)
whenever(it.subscriptionId).thenReturn(SUB_4_ID)
whenever(it.isOpportunistic).thenReturn(true)
}
private val CONNECTION_4 = FakeMobileConnectionRepository()
} }
} }