Merge changes I723cfc04,I1eba794e,I9d421a4a,Icd9c2b14,Idee192ff, ... into tm-qpr-dev am: d547dfa98e

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/20067345

Change-Id: Idb50bfb163a46b0e524ff579ae418b12a7ed7915
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
Caitlin Shkuratov
2022-09-29 15:29:19 +00:00
committed by Automerger Merge Worker
35 changed files with 1753 additions and 400 deletions

View File

@@ -131,6 +131,9 @@
<!-- For StatusIconContainer to tag its icon views -->
<item type="id" name="status_bar_view_state_tag" />
<!-- Status bar -->
<item type="id" name="status_bar_dot" />
<!-- Default display cutout on the physical top of screen -->
<item type="id" name="display_cutout" />
<item type="id" name="display_cutout_left" />

View File

@@ -30,6 +30,7 @@ import com.android.systemui.qs.carrier.QSCarrierGroupController;
import com.android.systemui.qs.dagger.QSScope;
import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider;
import com.android.systemui.statusbar.phone.StatusBarIconController;
import com.android.systemui.statusbar.phone.StatusBarLocation;
import com.android.systemui.statusbar.phone.StatusIconContainer;
import com.android.systemui.statusbar.policy.Clock;
import com.android.systemui.statusbar.policy.VariableDateViewController;
@@ -104,7 +105,7 @@ class QuickStatusBarHeaderController extends ViewController<QuickStatusBarHeader
mView.requireViewById(R.id.date_clock)
);
mIconManager = tintedIconManagerFactory.create(mIconContainer);
mIconManager = tintedIconManagerFactory.create(mIconContainer, StatusBarLocation.QS);
mDemoModeReceiver = new ClockDemoModeReceiver(mClockView);
mColorExtractor = colorExtractor;
mOnColorsChangedListener = (extractor, which) -> {

View File

@@ -48,6 +48,7 @@ import com.android.systemui.shade.LargeScreenShadeHeaderController.Companion.QQS
import com.android.systemui.shade.LargeScreenShadeHeaderController.Companion.QS_HEADER_CONSTRAINT
import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider
import com.android.systemui.statusbar.phone.StatusBarIconController
import com.android.systemui.statusbar.phone.StatusBarLocation
import com.android.systemui.statusbar.phone.StatusIconContainer
import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent.CentralSurfacesScope
import com.android.systemui.statusbar.phone.dagger.StatusBarViewModule.LARGE_SCREEN_BATTERY_CONTROLLER
@@ -261,7 +262,7 @@ class LargeScreenShadeHeaderController @Inject constructor(
batteryMeterViewController.ignoreTunerUpdates()
batteryIcon.setPercentShowMode(BatteryMeterView.MODE_ESTIMATE)
iconManager = tintedIconManagerFactory.create(iconContainer)
iconManager = tintedIconManagerFactory.create(iconContainer, StatusBarLocation.QS)
iconManager.setTint(
Utils.getColorAttrDefaultColor(header.context, android.R.attr.textColorPrimary)
)

View File

@@ -22,6 +22,7 @@ import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.annotation.IntDef;
import android.app.ActivityManager;
import android.app.Notification;
import android.content.Context;
@@ -59,6 +60,8 @@ import com.android.systemui.statusbar.notification.NotificationIconDozeHelper;
import com.android.systemui.statusbar.notification.NotificationUtils;
import com.android.systemui.util.drawable.DrawableSize;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.Arrays;
@@ -86,6 +89,10 @@ public class StatusBarIconView extends AnimatedImageView implements StatusIconDi
public static final int STATE_DOT = 1;
public static final int STATE_HIDDEN = 2;
@Retention(RetentionPolicy.SOURCE)
@IntDef({STATE_ICON, STATE_DOT, STATE_HIDDEN})
public @interface VisibleState { }
private static final String TAG = "StatusBarIconView";
private static final Property<StatusBarIconView, Float> ICON_APPEAR_AMOUNT
= new FloatProperty<StatusBarIconView>("iconAppearAmount") {
@@ -133,6 +140,7 @@ public class StatusBarIconView extends AnimatedImageView implements StatusIconDi
private final Paint mDotPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
private float mDotRadius;
private int mStaticDotRadius;
@StatusBarIconView.VisibleState
private int mVisibleState = STATE_ICON;
private float mIconAppearAmount = 1.0f;
private ObjectAnimator mIconAppearAnimator;
@@ -746,11 +754,12 @@ public class StatusBarIconView extends AnimatedImageView implements StatusIconDi
}
@Override
public void setVisibleState(int state) {
public void setVisibleState(@StatusBarIconView.VisibleState int state) {
setVisibleState(state, true /* animate */, null /* endRunnable */);
}
public void setVisibleState(int state, boolean animate) {
@Override
public void setVisibleState(@StatusBarIconView.VisibleState int state, boolean animate) {
setVisibleState(state, animate, null);
}
@@ -862,6 +871,7 @@ public class StatusBarIconView extends AnimatedImageView implements StatusIconDi
return mIconAppearAmount;
}
@StatusBarIconView.VisibleState
public int getVisibleState() {
return mVisibleState;
}

View File

@@ -59,7 +59,8 @@ public class StatusBarMobileView extends FrameLayout implements DarkReceiver,
private ImageView mOut;
private ImageView mMobile, mMobileType, mMobileRoaming;
private View mMobileRoamingSpace;
private int mVisibleState = -1;
@StatusBarIconView.VisibleState
private int mVisibleState = STATE_HIDDEN;
private DualToneHandler mDualToneHandler;
private boolean mForceHidden;
@@ -271,7 +272,7 @@ public class StatusBarMobileView extends FrameLayout implements DarkReceiver,
}
@Override
public void setVisibleState(int state, boolean animate) {
public void setVisibleState(@StatusBarIconView.VisibleState int state, boolean animate) {
if (state == mVisibleState) {
return;
}
@@ -312,6 +313,7 @@ public class StatusBarMobileView extends FrameLayout implements DarkReceiver,
}
@Override
@StatusBarIconView.VisibleState
public int getVisibleState() {
return mVisibleState;
}

View File

@@ -55,7 +55,8 @@ public class StatusBarWifiView extends BaseStatusBarWifiView implements DarkRece
private View mAirplaneSpacer;
private WifiIconState mState;
private String mSlot;
private int mVisibleState = -1;
@StatusBarIconView.VisibleState
private int mVisibleState = STATE_HIDDEN;
public static StatusBarWifiView fromContext(Context context, String slot) {
LayoutInflater inflater = LayoutInflater.from(context);
@@ -107,7 +108,7 @@ public class StatusBarWifiView extends BaseStatusBarWifiView implements DarkRece
}
@Override
public void setVisibleState(int state, boolean animate) {
public void setVisibleState(@StatusBarIconView.VisibleState int state, boolean animate) {
if (state == mVisibleState) {
return;
}
@@ -131,6 +132,7 @@ public class StatusBarWifiView extends BaseStatusBarWifiView implements DarkRece
}
@Override
@StatusBarIconView.VisibleState
public int getVisibleState() {
return mVisibleState;
}

View File

@@ -22,14 +22,32 @@ public interface StatusIconDisplayable extends DarkReceiver {
String getSlot();
void setStaticDrawableColor(int color);
void setDecorColor(int color);
default void setVisibleState(int state) {
/** Sets the visible state that this displayable should be. */
default void setVisibleState(@StatusBarIconView.VisibleState int state) {
setVisibleState(state, false);
}
void setVisibleState(int state, boolean animate);
/**
* Sets the visible state that this displayable should be, and whether the change should
* animate.
*/
void setVisibleState(@StatusBarIconView.VisibleState int state, boolean animate);
/** Returns the current visible state of this displayable. */
@StatusBarIconView.VisibleState
int getVisibleState();
/**
* Returns true if this icon should be visible if there's space, and false otherwise.
*
* Note that this doesn't necessarily mean it *will* be visible. It's possible that there are
* more icons than space, in which case this icon might just show a dot or might be completely
* hidden. {@link #getVisibleState} will return the icon's actual visible status.
*/
boolean isIconVisible();
default boolean isIconBlocked() {
return false;
}
}

View File

@@ -352,8 +352,8 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat
mKeyguardUpdateMonitor.registerCallback(mKeyguardUpdateMonitorCallback);
mDisableStateTracker.startTracking(mCommandQueue, mView.getDisplay().getDisplayId());
if (mTintedIconManager == null) {
mTintedIconManager =
mTintedIconManagerFactory.create(mView.findViewById(R.id.statusIcons));
mTintedIconManager = mTintedIconManagerFactory.create(
mView.findViewById(R.id.statusIcons), StatusBarLocation.KEYGUARD);
mTintedIconManager.setBlockList(getBlockedIcons());
mStatusBarIconController.addIconGroup(mTintedIconManager);
}

View File

@@ -56,7 +56,6 @@ import java.util.ArrayList;
import java.util.List;
import javax.inject.Inject;
import javax.inject.Provider;
public interface StatusBarIconController {
@@ -139,13 +138,15 @@ public interface StatusBarIconController {
public DarkIconManager(
LinearLayout linearLayout,
StatusBarLocation location,
StatusBarPipelineFlags statusBarPipelineFlags,
Provider<WifiViewModel> wifiViewModelProvider,
WifiViewModel wifiViewModel,
MobileContextProvider mobileContextProvider,
DarkIconDispatcher darkIconDispatcher) {
super(linearLayout,
location,
statusBarPipelineFlags,
wifiViewModelProvider,
wifiViewModel,
mobileContextProvider);
mIconHPadding = mContext.getResources().getDimensionPixelSize(
R.dimen.status_bar_icon_padding);
@@ -204,27 +205,28 @@ public interface StatusBarIconController {
@SysUISingleton
public static class Factory {
private final StatusBarPipelineFlags mStatusBarPipelineFlags;
private final Provider<WifiViewModel> mWifiViewModelProvider;
private final WifiViewModel mWifiViewModel;
private final MobileContextProvider mMobileContextProvider;
private final DarkIconDispatcher mDarkIconDispatcher;
@Inject
public Factory(
StatusBarPipelineFlags statusBarPipelineFlags,
Provider<WifiViewModel> wifiViewModelProvider,
WifiViewModel wifiViewModel,
MobileContextProvider mobileContextProvider,
DarkIconDispatcher darkIconDispatcher) {
mStatusBarPipelineFlags = statusBarPipelineFlags;
mWifiViewModelProvider = wifiViewModelProvider;
mWifiViewModel = wifiViewModel;
mMobileContextProvider = mobileContextProvider;
mDarkIconDispatcher = darkIconDispatcher;
}
public DarkIconManager create(LinearLayout group) {
public DarkIconManager create(LinearLayout group, StatusBarLocation location) {
return new DarkIconManager(
group,
location,
mStatusBarPipelineFlags,
mWifiViewModelProvider,
mWifiViewModel,
mMobileContextProvider,
mDarkIconDispatcher);
}
@@ -239,12 +241,14 @@ public interface StatusBarIconController {
public TintedIconManager(
ViewGroup group,
StatusBarLocation location,
StatusBarPipelineFlags statusBarPipelineFlags,
Provider<WifiViewModel> wifiViewModelProvider,
WifiViewModel wifiViewModel,
MobileContextProvider mobileContextProvider) {
super(group,
location,
statusBarPipelineFlags,
wifiViewModelProvider,
wifiViewModel,
mobileContextProvider);
}
@@ -278,24 +282,25 @@ public interface StatusBarIconController {
@SysUISingleton
public static class Factory {
private final StatusBarPipelineFlags mStatusBarPipelineFlags;
private final Provider<WifiViewModel> mWifiViewModelProvider;
private final WifiViewModel mWifiViewModel;
private final MobileContextProvider mMobileContextProvider;
@Inject
public Factory(
StatusBarPipelineFlags statusBarPipelineFlags,
Provider<WifiViewModel> wifiViewModelProvider,
WifiViewModel wifiViewModel,
MobileContextProvider mobileContextProvider) {
mStatusBarPipelineFlags = statusBarPipelineFlags;
mWifiViewModelProvider = wifiViewModelProvider;
mWifiViewModel = wifiViewModel;
mMobileContextProvider = mobileContextProvider;
}
public TintedIconManager create(ViewGroup group) {
public TintedIconManager create(ViewGroup group, StatusBarLocation location) {
return new TintedIconManager(
group,
location,
mStatusBarPipelineFlags,
mWifiViewModelProvider,
mWifiViewModel,
mMobileContextProvider);
}
}
@@ -306,8 +311,9 @@ public interface StatusBarIconController {
*/
class IconManager implements DemoModeCommandReceiver {
protected final ViewGroup mGroup;
private final StatusBarLocation mLocation;
private final StatusBarPipelineFlags mStatusBarPipelineFlags;
private final Provider<WifiViewModel> mWifiViewModelProvider;
private final WifiViewModel mWifiViewModel;
private final MobileContextProvider mMobileContextProvider;
protected final Context mContext;
protected final int mIconSize;
@@ -324,12 +330,14 @@ public interface StatusBarIconController {
public IconManager(
ViewGroup group,
StatusBarLocation location,
StatusBarPipelineFlags statusBarPipelineFlags,
Provider<WifiViewModel> wifiViewModelProvider,
WifiViewModel wifiViewModel,
MobileContextProvider mobileContextProvider) {
mGroup = group;
mLocation = location;
mStatusBarPipelineFlags = statusBarPipelineFlags;
mWifiViewModelProvider = wifiViewModelProvider;
mWifiViewModel = wifiViewModel;
mMobileContextProvider = mobileContextProvider;
mContext = group.getContext();
mIconSize = mContext.getResources().getDimensionPixelSize(
@@ -446,7 +454,7 @@ public interface StatusBarIconController {
private ModernStatusBarWifiView onCreateModernStatusBarWifiView(String slot) {
return ModernStatusBarWifiView.constructAndBind(
mContext, slot, mWifiViewModelProvider.get());
mContext, slot, mWifiViewModel, mLocation);
}
private StatusBarMobileView onCreateStatusBarMobileView(int subId, String slot) {

View File

@@ -0,0 +1,27 @@
/*
* 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.phone
/** An enumeration of the different locations that host a status bar. */
enum class StatusBarLocation {
/** Home screen or in-app. */
HOME,
/** Keyguard (aka lockscreen). */
KEYGUARD,
/** Quick settings (inside the shade). */
QS,
}

View File

@@ -68,6 +68,7 @@ import com.android.systemui.statusbar.phone.PhoneStatusBarView;
import com.android.systemui.statusbar.phone.StatusBarHideIconsForBouncerManager;
import com.android.systemui.statusbar.phone.StatusBarIconController;
import com.android.systemui.statusbar.phone.StatusBarIconController.DarkIconManager;
import com.android.systemui.statusbar.phone.StatusBarLocation;
import com.android.systemui.statusbar.phone.StatusBarLocationPublisher;
import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentComponent;
import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentComponent.Startable;
@@ -250,7 +251,8 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue
mStatusBar.restoreHierarchyState(
savedInstanceState.getSparseParcelableArray(EXTRA_PANEL_STATE));
}
mDarkIconManager = mDarkIconManagerFactory.create(view.findViewById(R.id.statusIcons));
mDarkIconManager = mDarkIconManagerFactory.create(
view.findViewById(R.id.statusIcons), StatusBarLocation.HOME);
mDarkIconManager.setShouldLog(true);
updateBlockedIcons();
mStatusBarIconController.addIconGroup(mDarkIconManager);

View File

@@ -31,6 +31,20 @@ import kotlinx.coroutines.flow.onEach
class ConnectivityPipelineLogger @Inject constructor(
@StatusBarConnectivityLog private val buffer: LogBuffer,
) {
/**
* Logs a change in one of the **raw inputs** to the connectivity pipeline.
*
* Use this method for inputs that don't have any extra information besides their callback name.
*/
fun logInputChange(callbackName: String) {
buffer.log(
SB_LOGGING_TAG,
LogLevel.INFO,
{ str1 = callbackName },
{ "Input: $str1" }
)
}
/**
* Logs a change in one of the **raw inputs** to the connectivity pipeline.
*/
@@ -127,13 +141,37 @@ class ConnectivityPipelineLogger @Inject constructor(
companion object {
const val SB_LOGGING_TAG = "SbConnectivity"
/**
* Log a change in one of the **inputs** to the connectivity pipeline.
*/
fun Flow<Unit>.logInputChange(
logger: ConnectivityPipelineLogger,
inputParamName: String,
): Flow<Unit> {
return this.onEach { logger.logInputChange(inputParamName) }
}
/**
* Log a change in one of the **inputs** to the connectivity pipeline.
*
* @param prettyPrint an optional function to transform the value into a readable string.
* [toString] is used if no custom function is provided.
*/
fun <T> Flow<T>.logInputChange(
logger: ConnectivityPipelineLogger,
inputParamName: String,
prettyPrint: (T) -> String = { it.toString() }
): Flow<T> {
return this.onEach {logger.logInputChange(inputParamName, prettyPrint(it)) }
}
/**
* Log a change in one of the **outputs** to the connectivity pipeline.
*
* @param prettyPrint an optional function to transform the value into a readable string.
* [toString] is used if no custom function is provided.
*/
fun <T : Any> Flow<T>.logOutputChange(
fun <T> Flow<T>.logOutputChange(
logger: ConnectivityPipelineLogger,
outputParamName: String,
prettyPrint: (T) -> String = { it.toString() }

View File

@@ -17,6 +17,7 @@
package com.android.systemui.statusbar.pipeline.wifi.data.repository
import android.annotation.SuppressLint
import android.content.IntentFilter
import android.net.ConnectivityManager
import android.net.Network
import android.net.NetworkCapabilities
@@ -30,51 +31,87 @@ import android.net.wifi.WifiManager
import android.net.wifi.WifiManager.TrafficStateCallback
import android.util.Log
import com.android.settingslib.Utils
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.SB_LOGGING_TAG
import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiActivityModel
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.logInputChange
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.logOutputChange
import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel
import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiActivityModel
import java.util.concurrent.Executor
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.flow.stateIn
/**
* Provides data related to the wifi state.
*/
/** Provides data related to the wifi state. */
interface WifiRepository {
/**
* Observable for the current wifi network.
*/
val wifiNetwork: Flow<WifiNetworkModel>
/** Observable for the current wifi enabled status. */
val isWifiEnabled: StateFlow<Boolean>
/**
* Observable for the current wifi network activity.
*/
val wifiActivity: Flow<WifiActivityModel>
/** Observable for the current wifi network. */
val wifiNetwork: StateFlow<WifiNetworkModel>
/** Observable for the current wifi network activity. */
val wifiActivity: StateFlow<WifiActivityModel>
}
/** Real implementation of [WifiRepository]. */
@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
@OptIn(ExperimentalCoroutinesApi::class)
@SysUISingleton
@SuppressLint("MissingPermission")
class WifiRepositoryImpl @Inject constructor(
broadcastDispatcher: BroadcastDispatcher,
connectivityManager: ConnectivityManager,
logger: ConnectivityPipelineLogger,
@Main mainExecutor: Executor,
@Application scope: CoroutineScope,
wifiManager: WifiManager?,
) : WifiRepository {
override val wifiNetwork: Flow<WifiNetworkModel> = conflatedCallbackFlow {
private val wifiStateChangeEvents: Flow<Unit> = broadcastDispatcher.broadcastFlow(
IntentFilter(WifiManager.WIFI_STATE_CHANGED_ACTION)
)
.logInputChange(logger, "WIFI_STATE_CHANGED_ACTION intent")
private val wifiNetworkChangeEvents: MutableSharedFlow<Unit> =
MutableSharedFlow(extraBufferCapacity = 1)
override val isWifiEnabled: StateFlow<Boolean> =
if (wifiManager == null) {
MutableStateFlow(false).asStateFlow()
} else {
// Because [WifiManager] doesn't expose a wifi enabled change listener, we do it
// internally by fetching [WifiManager.isWifiEnabled] whenever we think the state may
// have changed.
merge(wifiNetworkChangeEvents, wifiStateChangeEvents)
.mapLatest { wifiManager.isWifiEnabled }
.distinctUntilChanged()
.logOutputChange(logger, "enabled")
.stateIn(
scope = scope,
started = SharingStarted.WhileSubscribed(),
initialValue = wifiManager.isWifiEnabled
)
}
override val wifiNetwork: StateFlow<WifiNetworkModel> = conflatedCallbackFlow {
var currentWifi: WifiNetworkModel = WIFI_NETWORK_DEFAULT
val callback = object : ConnectivityManager.NetworkCallback(FLAG_INCLUDE_LOCATION_INFO) {
@@ -84,6 +121,8 @@ class WifiRepositoryImpl @Inject constructor(
) {
logger.logOnCapabilitiesChanged(network, networkCapabilities)
wifiNetworkChangeEvents.tryEmit(Unit)
val wifiInfo = networkCapabilitiesToWifiInfo(networkCapabilities)
if (wifiInfo?.isPrimary == true) {
val wifiNetworkModel = createWifiNetworkModel(
@@ -104,6 +143,9 @@ class WifiRepositoryImpl @Inject constructor(
override fun onLost(network: Network) {
logger.logOnLost(network)
wifiNetworkChangeEvents.tryEmit(Unit)
val wifi = currentWifi
if (wifi is WifiNetworkModel.Active && wifi.networkId == network.getNetId()) {
val newNetworkModel = WifiNetworkModel.Inactive
@@ -132,7 +174,7 @@ class WifiRepositoryImpl @Inject constructor(
initialValue = WIFI_NETWORK_DEFAULT
)
override val wifiActivity: Flow<WifiActivityModel> =
override val wifiActivity: StateFlow<WifiActivityModel> =
if (wifiManager == null) {
Log.w(SB_LOGGING_TAG, "Null WifiManager; skipping activity callback")
flowOf(ACTIVITY_DEFAULT)
@@ -142,13 +184,15 @@ class WifiRepositoryImpl @Inject constructor(
logger.logInputChange("onTrafficStateChange", prettyPrintActivity(state))
trySend(trafficStateToWifiActivityModel(state))
}
trySend(ACTIVITY_DEFAULT)
wifiManager.registerTrafficStateCallback(mainExecutor, callback)
awaitClose { wifiManager.unregisterTrafficStateCallback(callback) }
}
}
.stateIn(
scope,
started = SharingStarted.WhileSubscribed(),
initialValue = ACTIVITY_DEFAULT
)
companion object {
val ACTIVITY_DEFAULT = WifiActivityModel(hasActivityIn = false, hasActivityOut = false)

View File

@@ -22,9 +22,10 @@ import com.android.systemui.statusbar.pipeline.shared.data.model.ConnectivitySlo
import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepository
import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel
import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository
import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiActivityModel
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.map
/**
@@ -38,7 +39,11 @@ class WifiInteractor @Inject constructor(
connectivityRepository: ConnectivityRepository,
wifiRepository: WifiRepository,
) {
private val ssid: Flow<String?> = wifiRepository.wifiNetwork.map { info ->
/**
* The SSID (service set identifier) of the wifi network. Null if we don't have a network, or
* have a network but no valid SSID.
*/
val ssid: Flow<String?> = wifiRepository.wifiNetwork.map { info ->
when (info) {
is WifiNetworkModel.Inactive -> null
is WifiNetworkModel.CarrierMerged -> null
@@ -51,17 +56,17 @@ class WifiInteractor @Inject constructor(
}
}
/** Our current enabled status. */
val isEnabled: Flow<Boolean> = wifiRepository.isWifiEnabled
/** Our current wifi network. See [WifiNetworkModel]. */
val wifiNetwork: Flow<WifiNetworkModel> = wifiRepository.wifiNetwork
/** Our current wifi activity. See [WifiActivityModel]. */
val activity: StateFlow<WifiActivityModel> = wifiRepository.wifiActivity
/** True if we're configured to force-hide the wifi icon and false otherwise. */
val isForceHidden: Flow<Boolean> = connectivityRepository.forceHiddenSlots.map {
it.contains(ConnectivitySlot.WIFI)
}
/** True if our wifi network has activity in (download), and false otherwise. */
val hasActivityIn: Flow<Boolean> =
combine(wifiRepository.wifiActivity, ssid) { activity, ssid ->
activity.hasActivityIn && ssid != null
}
}

View File

@@ -41,9 +41,14 @@ class WifiConstants @Inject constructor(
/** True if we should show the activityIn/activityOut icons and false otherwise. */
val shouldShowActivityConfig = context.resources.getBoolean(R.bool.config_showActivity)
/** True if we should always show the wifi icon when wifi is enabled and false otherwise. */
val alwaysShowIconIfEnabled =
context.resources.getBoolean(R.bool.config_showWifiIndicatorWhenEnabled)
override fun dump(pw: PrintWriter, args: Array<out String>) {
pw.apply {
println("shouldShowActivityConfig=$shouldShowActivityConfig")
println("alwaysShowIconIfEnabled=$alwaysShowIconIfEnabled")
}
}
}

View File

@@ -14,11 +14,9 @@
* limitations under the License.
*/
package com.android.systemui.statusbar.pipeline.wifi.data.model
package com.android.systemui.statusbar.pipeline.wifi.shared.model
/**
* Provides information on the current wifi activity.
*/
/** Provides information on the current wifi activity. */
data class WifiActivityModel(
/** True if the wifi has activity in (download). */
val hasActivityIn: Boolean,

View File

@@ -26,8 +26,15 @@ import androidx.lifecycle.repeatOnLifecycle
import com.android.systemui.R
import com.android.systemui.common.ui.binder.IconViewBinder
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.statusbar.StatusBarIconView
import com.android.systemui.statusbar.StatusBarIconView.STATE_DOT
import com.android.systemui.statusbar.StatusBarIconView.STATE_HIDDEN
import com.android.systemui.statusbar.StatusBarIconView.STATE_ICON
import com.android.systemui.statusbar.phone.StatusBarLocation
import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.LocationBasedWifiViewModel
import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.WifiViewModel
import kotlinx.coroutines.InternalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.launch
@@ -41,40 +48,111 @@ import kotlinx.coroutines.launch
*/
@OptIn(InternalCoroutinesApi::class)
object WifiViewBinder {
/** Binds the view to the view-model, continuing to update the former based on the latter. */
/**
* Defines interface for an object that acts as the binding between the view and its view-model.
*
* Users of the [WifiViewBinder] class should use this to control the binder after it is bound.
*/
interface Binding {
/** Returns true if the wifi icon should be visible and false otherwise. */
fun getShouldIconBeVisible(): Boolean
/** Notifies that the visibility state has changed. */
fun onVisibilityStateChanged(@StatusBarIconView.VisibleState state: Int)
}
/**
* Binds the view to the appropriate view-model based on the given location. The view will
* continue to be updated following updates from the view-model.
*/
@JvmStatic
fun bind(
view: ViewGroup,
viewModel: WifiViewModel,
) {
wifiViewModel: WifiViewModel,
location: StatusBarLocation,
): Binding {
return when (location) {
StatusBarLocation.HOME -> bind(view, wifiViewModel.home)
StatusBarLocation.KEYGUARD -> bind(view, wifiViewModel.keyguard)
StatusBarLocation.QS -> bind(view, wifiViewModel.qs)
}
}
/** Binds the view to the view-model, continuing to update the former based on the latter. */
@JvmStatic
private fun bind(
view: ViewGroup,
viewModel: LocationBasedWifiViewModel,
): Binding {
val groupView = view.requireViewById<ViewGroup>(R.id.wifi_group)
val iconView = view.requireViewById<ImageView>(R.id.wifi_signal)
val dotView = view.requireViewById<StatusBarIconView>(R.id.status_bar_dot)
val activityInView = view.requireViewById<ImageView>(R.id.wifi_in)
val activityOutView = view.requireViewById<ImageView>(R.id.wifi_out)
val activityContainerView = view.requireViewById<View>(R.id.inout_container)
view.isVisible = true
iconView.isVisible = true
// TODO(b/238425913): We should log this visibility state.
@StatusBarIconView.VisibleState
val visibilityState: MutableStateFlow<Int> = MutableStateFlow(STATE_HIDDEN)
view.repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.STARTED) {
launch {
viewModel.wifiIcon.distinctUntilChanged().collect { wifiIcon ->
// TODO(b/238425913): Right now, if !isVisible, there's just an empty space
// where the wifi icon would be. We need to pipe isVisible through to
// [ModernStatusBarWifiView.isIconVisible], which is what actually makes
// the view GONE.
visibilityState.collect { visibilityState ->
groupView.isVisible = visibilityState == STATE_ICON
dotView.isVisible = visibilityState == STATE_DOT
}
}
launch {
viewModel.wifiIcon.collect { wifiIcon ->
view.isVisible = wifiIcon != null
wifiIcon?.let {
IconViewBinder.bind(wifiIcon, iconView)
}
wifiIcon?.let { IconViewBinder.bind(wifiIcon, iconView) }
}
}
launch {
viewModel.tint.collect { tint ->
iconView.imageTintList = ColorStateList.valueOf(tint)
val tintList = ColorStateList.valueOf(tint)
iconView.imageTintList = tintList
activityInView.imageTintList = tintList
activityOutView.imageTintList = tintList
dotView.setDecorColor(tint)
}
}
launch {
viewModel.isActivityInViewVisible.distinctUntilChanged().collect { visible ->
activityInView.isVisible = visible
}
}
launch {
viewModel.isActivityOutViewVisible.distinctUntilChanged().collect { visible ->
activityOutView.isVisible = visible
}
}
launch {
viewModel.isActivityContainerVisible.distinctUntilChanged().collect { visible ->
activityContainerView.isVisible = visible
}
}
}
}
// TODO(b/238425913): Hook up to [viewModel] to render actual changes to the wifi icon.
return object : Binding {
override fun getShouldIconBeVisible(): Boolean {
return viewModel.wifiIcon.value != null
}
override fun onVisibilityStateChanged(@StatusBarIconView.VisibleState state: Int) {
visibilityState.value = state
}
}
}
}

View File

@@ -19,10 +19,14 @@ package com.android.systemui.statusbar.pipeline.wifi.ui.view
import android.content.Context
import android.graphics.Rect
import android.util.AttributeSet
import android.view.Gravity
import android.view.LayoutInflater
import com.android.systemui.R
import com.android.systemui.statusbar.BaseStatusBarWifiView
import com.android.systemui.statusbar.StatusBarIconView.STATE_ICON
import com.android.systemui.statusbar.StatusBarIconView
import com.android.systemui.statusbar.StatusBarIconView.STATE_DOT
import com.android.systemui.statusbar.StatusBarIconView.STATE_HIDDEN
import com.android.systemui.statusbar.phone.StatusBarLocation
import com.android.systemui.statusbar.pipeline.wifi.ui.binder.WifiViewBinder
import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.WifiViewModel
@@ -36,6 +40,17 @@ class ModernStatusBarWifiView(
) : BaseStatusBarWifiView(context, attrs) {
private lateinit var slot: String
private lateinit var binding: WifiViewBinder.Binding
@StatusBarIconView.VisibleState
private var iconVisibleState: Int = STATE_HIDDEN
set(value) {
if (field == value) {
return
}
field = value
binding.onVisibilityStateChanged(value)
}
override fun onDarkChanged(areas: ArrayList<Rect>?, darkIntensity: Float, tint: Int) {
// TODO(b/238425913)
@@ -51,42 +66,64 @@ class ModernStatusBarWifiView(
// TODO(b/238425913)
}
override fun setVisibleState(state: Int, animate: Boolean) {
// TODO(b/238425913)
override fun setVisibleState(@StatusBarIconView.VisibleState state: Int, animate: Boolean) {
iconVisibleState = state
}
@StatusBarIconView.VisibleState
override fun getVisibleState(): Int {
// TODO(b/238425913)
return STATE_ICON
return iconVisibleState
}
override fun isIconVisible(): Boolean {
// TODO(b/238425913)
return true
return binding.getShouldIconBeVisible()
}
/** Set the slot name for this view. */
private fun setSlot(slotName: String) {
this.slot = slotName
private fun initView(
slotName: String,
wifiViewModel: WifiViewModel,
location: StatusBarLocation,
) {
slot = slotName
initDotView()
binding = WifiViewBinder.bind(this, wifiViewModel, location)
}
// Mostly duplicated from [com.android.systemui.statusbar.StatusBarWifiView].
private fun initDotView() {
// TODO(b/238425913): Could we just have this dot view be part of
// R.layout.new_status_bar_wifi_group with a dot drawable so we don't need to inflate it
// manually? Would that not work with animations?
val dotView = StatusBarIconView(mContext, slot, null).also {
it.id = R.id.status_bar_dot
// Hard-code this view to always be in the DOT state so that whenever it's visible it
// will show a dot
it.visibleState = STATE_DOT
}
val width = mContext.resources.getDimensionPixelSize(R.dimen.status_bar_icon_size)
val lp = LayoutParams(width, width)
lp.gravity = Gravity.CENTER_VERTICAL or Gravity.START
addView(dotView, lp)
}
companion object {
/**
* Inflates a new instance of [ModernStatusBarWifiView], binds it to [viewModel], and
* Inflates a new instance of [ModernStatusBarWifiView], binds it to a view model, and
* returns it.
*/
@JvmStatic
fun constructAndBind(
context: Context,
slot: String,
viewModel: WifiViewModel,
wifiViewModel: WifiViewModel,
location: StatusBarLocation,
): ModernStatusBarWifiView {
return (
LayoutInflater.from(context).inflate(R.layout.new_status_bar_wifi_group, null)
as ModernStatusBarWifiView
).also {
it.setSlot(slot)
WifiViewBinder.bind(it, viewModel)
it.initView(slot, wifiViewModel, location)
}
}
}

View File

@@ -0,0 +1,43 @@
/*
* 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.wifi.ui.viewmodel
import android.graphics.Color
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow
/**
* A view model for the wifi icon shown on the "home" page (aka, when the device is unlocked and not
* showing the shade, so the user is on the home-screen, or in an app).
*/
class HomeWifiViewModel(
statusBarPipelineFlags: StatusBarPipelineFlags,
wifiIcon: StateFlow<Icon?>,
isActivityInViewVisible: Flow<Boolean>,
isActivityOutViewVisible: Flow<Boolean>,
isActivityContainerVisible: Flow<Boolean>,
) :
LocationBasedWifiViewModel(
statusBarPipelineFlags,
debugTint = Color.CYAN,
wifiIcon,
isActivityInViewVisible,
isActivityOutViewVisible,
isActivityContainerVisible,
)

View File

@@ -0,0 +1,40 @@
/*
* 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.wifi.ui.viewmodel
import android.graphics.Color
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow
/** A view model for the wifi icon shown on keyguard (lockscreen). */
class KeyguardWifiViewModel(
statusBarPipelineFlags: StatusBarPipelineFlags,
wifiIcon: StateFlow<Icon?>,
isActivityInViewVisible: Flow<Boolean>,
isActivityOutViewVisible: Flow<Boolean>,
isActivityContainerVisible: Flow<Boolean>,
) :
LocationBasedWifiViewModel(
statusBarPipelineFlags,
debugTint = Color.MAGENTA,
wifiIcon,
isActivityInViewVisible,
isActivityOutViewVisible,
isActivityContainerVisible,
)

View File

@@ -0,0 +1,68 @@
/*
* 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.wifi.ui.viewmodel
import android.graphics.Color
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.flowOf
/**
* A view model for a wifi icon in a specific location. This allows us to control parameters that
* are location-specific (for example, different tints of the icon in different locations).
*
* Must be subclassed for each distinct location.
*/
abstract class LocationBasedWifiViewModel(
statusBarPipelineFlags: StatusBarPipelineFlags,
debugTint: Int,
/** The wifi icon that should be displayed. Null if we shouldn't display any icon. */
val wifiIcon: StateFlow<Icon?>,
/** True if the activity in view should be visible. */
val isActivityInViewVisible: Flow<Boolean>,
/** True if the activity out view should be visible. */
val isActivityOutViewVisible: Flow<Boolean>,
/** True if the activity container view should be visible. */
val isActivityContainerVisible: Flow<Boolean>,
) {
/** The color that should be used to tint the icon. */
val tint: Flow<Int> =
flowOf(
if (statusBarPipelineFlags.useNewPipelineDebugColoring()) {
debugTint
} else {
DEFAULT_TINT
}
)
companion object {
/**
* A default icon tint.
*
* TODO(b/238425913): The tint is actually controlled by
* [com.android.systemui.statusbar.phone.StatusBarIconController.TintedIconManager]. We
* should use that logic instead of white as a default.
*/
private const val DEFAULT_TINT = Color.WHITE
}
}

View File

@@ -0,0 +1,40 @@
/*
* 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.wifi.ui.viewmodel
import android.graphics.Color
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow
/** A view model for the wifi icon shown in quick settings (when the shade is pulled down). */
class QsWifiViewModel(
statusBarPipelineFlags: StatusBarPipelineFlags,
wifiIcon: StateFlow<Icon?>,
isActivityInViewVisible: Flow<Boolean>,
isActivityOutViewVisible: Flow<Boolean>,
isActivityContainerVisible: Flow<Boolean>,
) :
LocationBasedWifiViewModel(
statusBarPipelineFlags,
debugTint = Color.GREEN,
wifiIcon,
isActivityInViewVisible,
isActivityOutViewVisible,
isActivityContainerVisible,
)

View File

@@ -17,7 +17,6 @@
package com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel
import android.content.Context
import android.graphics.Color
import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
import androidx.annotation.VisibleForTesting
@@ -26,6 +25,8 @@ import com.android.settingslib.AccessibilityContentDescriptions.WIFI_NO_CONNECTI
import com.android.systemui.R
import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.statusbar.connectivity.WifiIcons.WIFI_FULL_ICONS
import com.android.systemui.statusbar.connectivity.WifiIcons.WIFI_NO_INTERNET_ICONS
import com.android.systemui.statusbar.connectivity.WifiIcons.WIFI_NO_NETWORK
@@ -35,98 +36,171 @@ import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel
import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractor
import com.android.systemui.statusbar.pipeline.wifi.shared.WifiConstants
import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiActivityModel
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
/**
* Models the UI state for the status bar wifi icon.
*
* This class exposes three view models, one per status bar location:
* - [home]
* - [keyguard]
* - [qs]
* In order to get the UI state for the wifi icon, you must use one of those view models (whichever
* is correct for your location).
*
* Internally, this class maintains the current state of the wifi icon and notifies those three
* view models of any changes.
*/
class WifiViewModel @Inject constructor(
statusBarPipelineFlags: StatusBarPipelineFlags,
private val constants: WifiConstants,
@SysUISingleton
class WifiViewModel
@Inject
constructor(
constants: WifiConstants,
private val context: Context,
private val logger: ConnectivityPipelineLogger,
private val interactor: WifiInteractor,
logger: ConnectivityPipelineLogger,
interactor: WifiInteractor,
@Application private val scope: CoroutineScope,
statusBarPipelineFlags: StatusBarPipelineFlags,
) {
/**
* The drawable resource ID to use for the wifi icon. Null if we shouldn't display any icon.
* Returns the drawable resource ID to use for the wifi icon based on the given network.
* Null if we can't compute the icon.
*/
@DrawableRes
private val iconResId: Flow<Int?> = interactor.wifiNetwork.map {
when (it) {
private fun WifiNetworkModel.iconResId(): Int? {
return when (this) {
is WifiNetworkModel.CarrierMerged -> null
is WifiNetworkModel.Inactive -> WIFI_NO_NETWORK
is WifiNetworkModel.Active ->
when {
it.level == null -> null
it.isValidated -> WIFI_FULL_ICONS[it.level]
else -> WIFI_NO_INTERNET_ICONS[it.level]
this.level == null -> null
this.isValidated -> WIFI_FULL_ICONS[this.level]
else -> WIFI_NO_INTERNET_ICONS[this.level]
}
}
}
/** The content description for the wifi icon. */
private val contentDescription: Flow<ContentDescription?> = interactor.wifiNetwork.map {
when (it) {
/**
* Returns the content description for the wifi icon based on the given network.
* Null if we can't compute the content description.
*/
private fun WifiNetworkModel.contentDescription(): ContentDescription? {
return when (this) {
is WifiNetworkModel.CarrierMerged -> null
is WifiNetworkModel.Inactive ->
ContentDescription.Loaded(
"${context.getString(WIFI_NO_CONNECTION)},${context.getString(NO_INTERNET)}"
)
is WifiNetworkModel.Active ->
when (it.level) {
when (this.level) {
null -> null
else -> {
val levelDesc = context.getString(WIFI_CONNECTION_STRENGTH[it.level])
val levelDesc = context.getString(WIFI_CONNECTION_STRENGTH[this.level])
when {
it.isValidated -> ContentDescription.Loaded(levelDesc)
else -> ContentDescription.Loaded(
"$levelDesc,${context.getString(NO_INTERNET)}"
)
this.isValidated -> ContentDescription.Loaded(levelDesc)
else ->
ContentDescription.Loaded(
"$levelDesc,${context.getString(NO_INTERNET)}"
)
}
}
}
}
}
/**
* The wifi icon that should be displayed. Null if we shouldn't display any icon.
*/
val wifiIcon: Flow<Icon?> = combine(
/** The wifi icon that should be displayed. Null if we shouldn't display any icon. */
private val wifiIcon: StateFlow<Icon?> =
combine(
interactor.isEnabled,
interactor.isForceHidden,
iconResId,
contentDescription,
) { isForceHidden, iconResId, contentDescription ->
when {
isForceHidden ||
iconResId == null ||
iconResId <= 0 -> null
else -> Icon.Resource(iconResId, contentDescription)
interactor.wifiNetwork,
) { isEnabled, isForceHidden, wifiNetwork ->
if (!isEnabled || isForceHidden || wifiNetwork is WifiNetworkModel.CarrierMerged) {
return@combine null
}
val iconResId = wifiNetwork.iconResId() ?: return@combine null
val icon = Icon.Resource(iconResId, wifiNetwork.contentDescription())
return@combine when {
constants.alwaysShowIconIfEnabled -> icon
wifiNetwork is WifiNetworkModel.Active && wifiNetwork.isValidated -> icon
else -> null
}
}
.stateIn(scope, started = SharingStarted.WhileSubscribed(), initialValue = null)
/**
* True if the activity in icon should be displayed and false otherwise.
*/
val isActivityInVisible: Flow<Boolean>
get() =
if (!constants.shouldShowActivityConfig) {
flowOf(false)
} else {
interactor.hasActivityIn
/** The wifi activity status. Null if we shouldn't display the activity status. */
private val activity: Flow<WifiActivityModel?> =
if (!constants.shouldShowActivityConfig) {
flowOf(null)
} else {
combine(interactor.activity, interactor.ssid) { activity, ssid ->
when (ssid) {
null -> null
else -> activity
}
}
.logOutputChange(logger, "activityInVisible")
}
.distinctUntilChanged()
.logOutputChange(logger, "activity")
.stateIn(scope, started = SharingStarted.WhileSubscribed(), initialValue = null)
/** The tint that should be applied to the icon. */
val tint: Flow<Int> = if (!statusBarPipelineFlags.useNewPipelineDebugColoring()) {
emptyFlow()
} else {
flowOf(Color.CYAN)
}
private val isActivityInViewVisible: Flow<Boolean> =
activity
.map { it?.hasActivityIn == true }
.stateIn(scope, started = SharingStarted.WhileSubscribed(), initialValue = false)
private val isActivityOutViewVisible: Flow<Boolean> =
activity
.map { it?.hasActivityOut == true }
.stateIn(scope, started = SharingStarted.WhileSubscribed(), initialValue = false)
private val isActivityContainerVisible: Flow<Boolean> =
combine(isActivityInViewVisible, isActivityOutViewVisible) { activityIn, activityOut ->
activityIn || activityOut
}
.stateIn(scope, started = SharingStarted.WhileSubscribed(), initialValue = false)
/** A view model for the status bar on the home screen. */
val home: HomeWifiViewModel =
HomeWifiViewModel(
statusBarPipelineFlags,
wifiIcon,
isActivityInViewVisible,
isActivityOutViewVisible,
isActivityContainerVisible,
)
/** A view model for the status bar on keyguard. */
val keyguard: KeyguardWifiViewModel =
KeyguardWifiViewModel(
statusBarPipelineFlags,
wifiIcon,
isActivityInViewVisible,
isActivityOutViewVisible,
isActivityContainerVisible,
)
/** A view model for the status bar in quick settings. */
val qs: QsWifiViewModel =
QsWifiViewModel(
statusBarPipelineFlags,
wifiIcon,
isActivityInViewVisible,
isActivityOutViewVisible,
isActivityContainerVisible,
)
companion object {
@StringRes

View File

@@ -110,7 +110,7 @@ class QuickStatusBarHeaderControllerTest : SysuiTestCase() {
`when`(qsCarrierGroupControllerBuilder.build()).thenReturn(qsCarrierGroupController)
`when`(variableDateViewControllerFactory.create(any()))
.thenReturn(variableDateViewController)
`when`(iconManagerFactory.create(any())).thenReturn(iconManager)
`when`(iconManagerFactory.create(any(), any())).thenReturn(iconManager)
`when`(view.resources).thenReturn(mContext.resources)
`when`(view.isAttachedToWindow).thenReturn(true)
`when`(view.context).thenReturn(context)

View File

@@ -176,7 +176,7 @@ class LargeScreenShadeHeaderControllerCombinedTest : SysuiTestCase() {
}
whenever(view.visibility).thenAnswer { _ -> viewVisibility }
whenever(iconManagerFactory.create(any())).thenReturn(iconManager)
whenever(iconManagerFactory.create(any(), any())).thenReturn(iconManager)
whenever(featureFlags.isEnabled(Flags.COMBINED_QS_HEADERS)).thenReturn(true)
whenever(featureFlags.isEnabled(Flags.NEW_HEADER)).thenReturn(true)

View File

@@ -97,7 +97,7 @@ class LargeScreenShadeHeaderControllerTest : SysuiTestCase() {
whenever(view.visibility).thenAnswer { _ -> viewVisibility }
whenever(variableDateViewControllerFactory.create(any()))
.thenReturn(variableDateViewController)
whenever(iconManagerFactory.create(any())).thenReturn(iconManager)
whenever(iconManagerFactory.create(any(), any())).thenReturn(iconManager)
whenever(featureFlags.isEnabled(Flags.COMBINED_QS_HEADERS)).thenReturn(false)
mLargeScreenShadeHeaderController = LargeScreenShadeHeaderController(
view,

View File

@@ -135,7 +135,7 @@ public class KeyguardStatusBarViewControllerTest extends SysuiTestCase {
MockitoAnnotations.initMocks(this);
when(mIconManagerFactory.create(any())).thenReturn(mIconManager);
when(mIconManagerFactory.create(any(), any())).thenReturn(mIconManager);
allowTestableLooperAsMainThread();
TestableLooper.get(this).runWithLooper(() -> {

View File

@@ -51,8 +51,6 @@ import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import javax.inject.Provider;
@RunWith(AndroidTestingRunner.class)
@RunWithLooper
@SmallTest
@@ -79,8 +77,9 @@ public class StatusBarIconControllerTest extends LeakCheckedTest {
LinearLayout layout = new LinearLayout(mContext);
TestDarkIconManager manager = new TestDarkIconManager(
layout,
StatusBarLocation.HOME,
mock(StatusBarPipelineFlags.class),
() -> mock(WifiViewModel.class),
mock(WifiViewModel.class),
mMobileContextProvider,
mock(DarkIconDispatcher.class));
testCallOnAdd_forManager(manager);
@@ -121,13 +120,15 @@ public class StatusBarIconControllerTest extends LeakCheckedTest {
TestDarkIconManager(
LinearLayout group,
StatusBarLocation location,
StatusBarPipelineFlags statusBarPipelineFlags,
Provider<WifiViewModel> wifiViewModelProvider,
WifiViewModel wifiViewModel,
MobileContextProvider contextProvider,
DarkIconDispatcher darkIconDispatcher) {
super(group,
location,
statusBarPipelineFlags,
wifiViewModelProvider,
wifiViewModel,
contextProvider,
darkIconDispatcher);
}
@@ -165,8 +166,9 @@ public class StatusBarIconControllerTest extends LeakCheckedTest {
private static class TestIconManager extends IconManager implements TestableIconManager {
TestIconManager(ViewGroup group, MobileContextProvider contextProvider) {
super(group,
StatusBarLocation.HOME,
mock(StatusBarPipelineFlags.class),
() -> mock(WifiViewModel.class),
mock(WifiViewModel.class),
contextProvider);
}

View File

@@ -431,7 +431,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
mOperatorNameViewControllerFactory = mock(OperatorNameViewController.Factory.class);
when(mOperatorNameViewControllerFactory.create(any()))
.thenReturn(mOperatorNameViewController);
when(mIconManagerFactory.create(any())).thenReturn(mIconManager);
when(mIconManagerFactory.create(any(), any())).thenReturn(mIconManager);
mSecureSettings = mock(SecureSettings.class);
setUpNotificationIconAreaController();

View File

@@ -23,9 +23,16 @@ import com.android.systemui.SysuiTestCase
import com.android.systemui.dump.DumpManager
import com.android.systemui.log.LogBufferFactory
import com.android.systemui.log.LogcatEchoTracker
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.logInputChange
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.logOutputChange
import com.google.common.truth.Truth.assertThat
import java.io.PrintWriter
import java.io.StringWriter
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.runBlocking
import org.junit.Test
import org.mockito.Mockito
import org.mockito.Mockito.mock
@@ -64,12 +71,70 @@ class ConnectivityPipelineLoggerTest : SysuiTestCase() {
assertThat(actualString).contains(expectedNetId)
}
private val NET_1_ID = 100
private val NET_1 = com.android.systemui.util.mockito.mock<Network>().also {
Mockito.`when`(it.getNetId()).thenReturn(NET_1_ID)
@Test
fun logOutputChange_printsValuesAndNulls() = runBlocking(IMMEDIATE) {
val flow: Flow<Int?> = flowOf(1, null, 3)
val job = flow
.logOutputChange(logger, "testInts")
.launchIn(this)
val stringWriter = StringWriter()
buffer.dump(PrintWriter(stringWriter), tailLength = 0)
val actualString = stringWriter.toString()
assertThat(actualString).contains("1")
assertThat(actualString).contains("null")
assertThat(actualString).contains("3")
job.cancel()
}
@Test
fun logInputChange_unit_printsInputName() = runBlocking(IMMEDIATE) {
val flow: Flow<Unit> = flowOf(Unit, Unit)
val job = flow
.logInputChange(logger, "testInputs")
.launchIn(this)
val stringWriter = StringWriter()
buffer.dump(PrintWriter(stringWriter), tailLength = 0)
val actualString = stringWriter.toString()
assertThat(actualString).contains("testInputs")
job.cancel()
}
@Test
fun logInputChange_any_printsValuesAndNulls() = runBlocking(IMMEDIATE) {
val flow: Flow<Any?> = flowOf(null, 2, "threeString")
val job = flow
.logInputChange(logger, "testInputs")
.launchIn(this)
val stringWriter = StringWriter()
buffer.dump(PrintWriter(stringWriter), tailLength = 0)
val actualString = stringWriter.toString()
assertThat(actualString).contains("null")
assertThat(actualString).contains("2")
assertThat(actualString).contains("threeString")
job.cancel()
}
companion object {
private const val NET_1_ID = 100
private val NET_1 = com.android.systemui.util.mockito.mock<Network>().also {
Mockito.`when`(it.getNetId()).thenReturn(NET_1_ID)
}
private val NET_1_CAPS = NetworkCapabilities.Builder()
.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
.addCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)
.build()
private val IMMEDIATE = Dispatchers.Main.immediate
}
private val NET_1_CAPS = NetworkCapabilities.Builder()
.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
.addCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)
.build()
}

View File

@@ -16,20 +16,27 @@
package com.android.systemui.statusbar.pipeline.wifi.data.repository
import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiActivityModel
import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel
import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepositoryImpl.Companion.ACTIVITY_DEFAULT
import kotlinx.coroutines.flow.Flow
import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiActivityModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
/** Fake implementation of [WifiRepository] exposing set methods for all the flows. */
class FakeWifiRepository : WifiRepository {
private val _isWifiEnabled: MutableStateFlow<Boolean> = MutableStateFlow(false)
override val isWifiEnabled: StateFlow<Boolean> = _isWifiEnabled
private val _wifiNetwork: MutableStateFlow<WifiNetworkModel> =
MutableStateFlow(WifiNetworkModel.Inactive)
override val wifiNetwork: Flow<WifiNetworkModel> = _wifiNetwork
override val wifiNetwork: StateFlow<WifiNetworkModel> = _wifiNetwork
private val _wifiActivity = MutableStateFlow(ACTIVITY_DEFAULT)
override val wifiActivity: Flow<WifiActivityModel> = _wifiActivity
override val wifiActivity: StateFlow<WifiActivityModel> = _wifiActivity
fun setIsWifiEnabled(enabled: Boolean) {
_isWifiEnabled.value = enabled
}
fun setWifiNetwork(wifiNetworkModel: WifiNetworkModel) {
_wifiNetwork.value = wifiNetworkModel

View File

@@ -28,15 +28,17 @@ import android.net.wifi.WifiManager
import android.net.wifi.WifiManager.TrafficStateCallback
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiActivityModel
import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel
import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepositoryImpl.Companion.ACTIVITY_DEFAULT
import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepositoryImpl.Companion.WIFI_NETWORK_DEFAULT
import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiActivityModel
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.nullable
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
import java.util.concurrent.Executor
@@ -44,23 +46,28 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.runBlocking
import org.junit.After
import org.junit.Before
import org.junit.Test
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Mock
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when` as whenever
import org.mockito.MockitoAnnotations
@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
class WifiRepositoryImplTest : SysuiTestCase() {
private lateinit var underTest: WifiRepositoryImpl
@Mock private lateinit var broadcastDispatcher: BroadcastDispatcher
@Mock private lateinit var logger: ConnectivityPipelineLogger
@Mock private lateinit var connectivityManager: ConnectivityManager
@Mock private lateinit var wifiManager: WifiManager
@@ -70,16 +77,17 @@ class WifiRepositoryImplTest : SysuiTestCase() {
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
whenever(
broadcastDispatcher.broadcastFlow(
any(),
nullable(),
anyInt(),
nullable(),
)
).thenReturn(flowOf(Unit))
executor = FakeExecutor(FakeSystemClock())
scope = CoroutineScope(IMMEDIATE)
underTest = WifiRepositoryImpl(
connectivityManager,
logger,
executor,
scope,
wifiManager,
)
underTest = createRepo()
}
@After
@@ -87,6 +95,132 @@ class WifiRepositoryImplTest : SysuiTestCase() {
scope.cancel()
}
@Test
fun isWifiEnabled_nullWifiManager_getsFalse() = runBlocking(IMMEDIATE) {
underTest = createRepo(wifiManagerToUse = null)
assertThat(underTest.isWifiEnabled.value).isFalse()
}
@Test
fun isWifiEnabled_initiallyGetsWifiManagerValue() = runBlocking(IMMEDIATE) {
whenever(wifiManager.isWifiEnabled).thenReturn(true)
underTest = createRepo()
assertThat(underTest.isWifiEnabled.value).isTrue()
}
@Test
fun isWifiEnabled_networkCapabilitiesChanged_valueUpdated() = runBlocking(IMMEDIATE) {
// We need to call launch on the flows so that they start updating
val networkJob = underTest.wifiNetwork.launchIn(this)
val enabledJob = underTest.isWifiEnabled.launchIn(this)
whenever(wifiManager.isWifiEnabled).thenReturn(true)
getNetworkCallback().onCapabilitiesChanged(
NETWORK, createWifiNetworkCapabilities(PRIMARY_WIFI_INFO)
)
assertThat(underTest.isWifiEnabled.value).isTrue()
whenever(wifiManager.isWifiEnabled).thenReturn(false)
getNetworkCallback().onCapabilitiesChanged(
NETWORK, createWifiNetworkCapabilities(PRIMARY_WIFI_INFO)
)
assertThat(underTest.isWifiEnabled.value).isFalse()
networkJob.cancel()
enabledJob.cancel()
}
@Test
fun isWifiEnabled_networkLost_valueUpdated() = runBlocking(IMMEDIATE) {
// We need to call launch on the flows so that they start updating
val networkJob = underTest.wifiNetwork.launchIn(this)
val enabledJob = underTest.isWifiEnabled.launchIn(this)
whenever(wifiManager.isWifiEnabled).thenReturn(true)
getNetworkCallback().onLost(NETWORK)
assertThat(underTest.isWifiEnabled.value).isTrue()
whenever(wifiManager.isWifiEnabled).thenReturn(false)
getNetworkCallback().onLost(NETWORK)
assertThat(underTest.isWifiEnabled.value).isFalse()
networkJob.cancel()
enabledJob.cancel()
}
@Test
fun isWifiEnabled_intentsReceived_valueUpdated() = runBlocking(IMMEDIATE) {
val intentFlow = MutableSharedFlow<Unit>()
whenever(
broadcastDispatcher.broadcastFlow(
any(),
nullable(),
anyInt(),
nullable(),
)
).thenReturn(intentFlow)
underTest = createRepo()
val job = underTest.isWifiEnabled.launchIn(this)
whenever(wifiManager.isWifiEnabled).thenReturn(true)
intentFlow.emit(Unit)
assertThat(underTest.isWifiEnabled.value).isTrue()
whenever(wifiManager.isWifiEnabled).thenReturn(false)
intentFlow.emit(Unit)
assertThat(underTest.isWifiEnabled.value).isFalse()
job.cancel()
}
@Test
fun isWifiEnabled_bothIntentAndNetworkUpdates_valueAlwaysUpdated() = runBlocking(IMMEDIATE) {
val intentFlow = MutableSharedFlow<Unit>()
whenever(
broadcastDispatcher.broadcastFlow(
any(),
nullable(),
anyInt(),
nullable(),
)
).thenReturn(intentFlow)
underTest = createRepo()
val networkJob = underTest.wifiNetwork.launchIn(this)
val enabledJob = underTest.isWifiEnabled.launchIn(this)
whenever(wifiManager.isWifiEnabled).thenReturn(false)
intentFlow.emit(Unit)
assertThat(underTest.isWifiEnabled.value).isFalse()
whenever(wifiManager.isWifiEnabled).thenReturn(true)
getNetworkCallback().onLost(NETWORK)
assertThat(underTest.isWifiEnabled.value).isTrue()
whenever(wifiManager.isWifiEnabled).thenReturn(false)
getNetworkCallback().onCapabilitiesChanged(
NETWORK, createWifiNetworkCapabilities(PRIMARY_WIFI_INFO)
)
assertThat(underTest.isWifiEnabled.value).isFalse()
whenever(wifiManager.isWifiEnabled).thenReturn(true)
intentFlow.emit(Unit)
assertThat(underTest.isWifiEnabled.value).isTrue()
networkJob.cancel()
enabledJob.cancel()
}
@Test
fun wifiNetwork_initiallyGetsDefault() = runBlocking(IMMEDIATE) {
var latest: WifiNetworkModel? = null
@@ -509,13 +643,7 @@ class WifiRepositoryImplTest : SysuiTestCase() {
@Test
fun wifiActivity_nullWifiManager_receivesDefault() = runBlocking(IMMEDIATE) {
underTest = WifiRepositoryImpl(
connectivityManager,
logger,
executor,
scope,
wifiManager = null,
)
underTest = createRepo(wifiManagerToUse = null)
var latest: WifiActivityModel? = null
val job = underTest
@@ -594,6 +722,17 @@ class WifiRepositoryImplTest : SysuiTestCase() {
job.cancel()
}
private fun createRepo(wifiManagerToUse: WifiManager? = wifiManager): WifiRepositoryImpl {
return WifiRepositoryImpl(
broadcastDispatcher,
connectivityManager,
logger,
executor,
scope,
wifiManagerToUse,
)
}
private fun getTrafficStateCallback(): TrafficStateCallback {
val callbackCaptor = argumentCaptor<TrafficStateCallback>()
verify(wifiManager).registerTrafficStateCallback(any(), callbackCaptor.capture())

View File

@@ -16,13 +16,14 @@
package com.android.systemui.statusbar.pipeline.wifi.domain.interactor
import android.net.wifi.WifiManager
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.statusbar.pipeline.shared.data.model.ConnectivitySlot
import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiActivityModel
import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel
import com.android.systemui.statusbar.pipeline.wifi.data.repository.FakeWifiRepository
import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiActivityModel
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -50,172 +51,129 @@ class WifiInteractorTest : SysuiTestCase() {
}
@Test
fun hasActivityIn_noInOrOut_outputsFalse() = runBlocking(IMMEDIATE) {
wifiRepository.setWifiNetwork(VALID_WIFI_NETWORK_MODEL)
wifiRepository.setWifiActivity(
WifiActivityModel(hasActivityIn = false, hasActivityOut = false)
)
var latest: Boolean? = null
val job = underTest
.hasActivityIn
.onEach { latest = it }
.launchIn(this)
assertThat(latest).isFalse()
job.cancel()
}
@Test
fun hasActivityIn_onlyOut_outputsFalse() = runBlocking(IMMEDIATE) {
wifiRepository.setWifiNetwork(VALID_WIFI_NETWORK_MODEL)
wifiRepository.setWifiActivity(
WifiActivityModel(hasActivityIn = false, hasActivityOut = true)
)
var latest: Boolean? = null
val job = underTest
.hasActivityIn
.onEach { latest = it }
.launchIn(this)
assertThat(latest).isFalse()
job.cancel()
}
@Test
fun hasActivityIn_onlyIn_outputsTrue() = runBlocking(IMMEDIATE) {
wifiRepository.setWifiNetwork(VALID_WIFI_NETWORK_MODEL)
wifiRepository.setWifiActivity(
WifiActivityModel(hasActivityIn = true, hasActivityOut = false)
)
var latest: Boolean? = null
val job = underTest
.hasActivityIn
.onEach { latest = it }
.launchIn(this)
assertThat(latest).isTrue()
job.cancel()
}
@Test
fun hasActivityIn_inAndOut_outputsTrue() = runBlocking(IMMEDIATE) {
wifiRepository.setWifiNetwork(VALID_WIFI_NETWORK_MODEL)
wifiRepository.setWifiActivity(
WifiActivityModel(hasActivityIn = true, hasActivityOut = true)
)
var latest: Boolean? = null
val job = underTest
.hasActivityIn
.onEach { latest = it }
.launchIn(this)
assertThat(latest).isTrue()
job.cancel()
}
@Test
fun hasActivityIn_ssidNull_outputsFalse() = runBlocking(IMMEDIATE) {
wifiRepository.setWifiNetwork(WifiNetworkModel.Active(networkId = 1, ssid = null))
wifiRepository.setWifiActivity(
WifiActivityModel(hasActivityIn = true, hasActivityOut = true)
)
var latest: Boolean? = null
val job = underTest
.hasActivityIn
.onEach { latest = it }
.launchIn(this)
assertThat(latest).isFalse()
job.cancel()
}
@Test
fun hasActivityIn_inactiveNetwork_outputsFalse() = runBlocking(IMMEDIATE) {
fun ssid_inactiveNetwork_outputsNull() = runBlocking(IMMEDIATE) {
wifiRepository.setWifiNetwork(WifiNetworkModel.Inactive)
wifiRepository.setWifiActivity(
WifiActivityModel(hasActivityIn = true, hasActivityOut = true)
)
var latest: Boolean? = null
var latest: String? = "default"
val job = underTest
.hasActivityIn
.ssid
.onEach { latest = it }
.launchIn(this)
assertThat(latest).isFalse()
assertThat(latest).isNull()
job.cancel()
}
@Test
fun hasActivityIn_carrierMergedNetwork_outputsFalse() = runBlocking(IMMEDIATE) {
fun ssid_carrierMergedNetwork_outputsNull() = runBlocking(IMMEDIATE) {
wifiRepository.setWifiNetwork(WifiNetworkModel.CarrierMerged)
wifiRepository.setWifiActivity(
WifiActivityModel(hasActivityIn = true, hasActivityOut = true)
)
var latest: Boolean? = null
var latest: String? = "default"
val job = underTest
.hasActivityIn
.ssid
.onEach { latest = it }
.launchIn(this)
assertThat(latest).isFalse()
assertThat(latest).isNull()
job.cancel()
}
@Test
fun hasActivityIn_multipleChanges_multipleOutputChanges() = runBlocking(IMMEDIATE) {
wifiRepository.setWifiNetwork(VALID_WIFI_NETWORK_MODEL)
fun ssid_isPasspointAccessPoint_outputsPasspointName() = runBlocking(IMMEDIATE) {
wifiRepository.setWifiNetwork(WifiNetworkModel.Active(
networkId = 1,
isPasspointAccessPoint = true,
passpointProviderFriendlyName = "friendly",
))
var latest: String? = null
val job = underTest
.ssid
.onEach { latest = it }
.launchIn(this)
assertThat(latest).isEqualTo("friendly")
job.cancel()
}
@Test
fun ssid_isOnlineSignUpForPasspoint_outputsPasspointName() = runBlocking(IMMEDIATE) {
wifiRepository.setWifiNetwork(WifiNetworkModel.Active(
networkId = 1,
isOnlineSignUpForPasspointAccessPoint = true,
passpointProviderFriendlyName = "friendly",
))
var latest: String? = null
val job = underTest
.ssid
.onEach { latest = it }
.launchIn(this)
assertThat(latest).isEqualTo("friendly")
job.cancel()
}
@Test
fun ssid_unknownSsid_outputsNull() = runBlocking(IMMEDIATE) {
wifiRepository.setWifiNetwork(WifiNetworkModel.Active(
networkId = 1,
ssid = WifiManager.UNKNOWN_SSID,
))
var latest: String? = "default"
val job = underTest
.ssid
.onEach { latest = it }
.launchIn(this)
assertThat(latest).isNull()
job.cancel()
}
@Test
fun ssid_validSsid_outputsSsid() = runBlocking(IMMEDIATE) {
wifiRepository.setWifiNetwork(WifiNetworkModel.Active(
networkId = 1,
ssid = "MyAwesomeWifiNetwork",
))
var latest: String? = null
val job = underTest
.ssid
.onEach { latest = it }
.launchIn(this)
assertThat(latest).isEqualTo("MyAwesomeWifiNetwork")
job.cancel()
}
@Test
fun isEnabled_matchesRepoIsEnabled() = runBlocking(IMMEDIATE) {
var latest: Boolean? = null
val job = underTest
.hasActivityIn
.onEach { latest = it }
.launchIn(this)
.isEnabled
.onEach { latest = it }
.launchIn(this)
// Conduct a series of changes and verify we catch each of them in succession
wifiRepository.setWifiActivity(
WifiActivityModel(hasActivityIn = true, hasActivityOut = false)
)
wifiRepository.setIsWifiEnabled(true)
yield()
assertThat(latest).isTrue()
wifiRepository.setWifiActivity(
WifiActivityModel(hasActivityIn = false, hasActivityOut = true)
)
wifiRepository.setIsWifiEnabled(false)
yield()
assertThat(latest).isFalse()
wifiRepository.setWifiActivity(
WifiActivityModel(hasActivityIn = true, hasActivityOut = true)
)
wifiRepository.setIsWifiEnabled(true)
yield()
assertThat(latest).isTrue()
wifiRepository.setWifiActivity(
WifiActivityModel(hasActivityIn = true, hasActivityOut = false)
)
yield()
assertThat(latest).isTrue()
wifiRepository.setWifiActivity(
WifiActivityModel(hasActivityIn = false, hasActivityOut = false)
)
yield()
assertThat(latest).isFalse()
job.cancel()
}
@@ -241,6 +199,32 @@ class WifiInteractorTest : SysuiTestCase() {
job.cancel()
}
@Test
fun activity_matchesRepoWifiActivity() = runBlocking(IMMEDIATE) {
var latest: WifiActivityModel? = null
val job = underTest
.activity
.onEach { latest = it }
.launchIn(this)
val activity1 = WifiActivityModel(hasActivityIn = true, hasActivityOut = true)
wifiRepository.setWifiActivity(activity1)
yield()
assertThat(latest).isEqualTo(activity1)
val activity2 = WifiActivityModel(hasActivityIn = false, hasActivityOut = false)
wifiRepository.setWifiActivity(activity2)
yield()
assertThat(latest).isEqualTo(activity2)
val activity3 = WifiActivityModel(hasActivityIn = true, hasActivityOut = false)
wifiRepository.setWifiActivity(activity3)
yield()
assertThat(latest).isEqualTo(activity3)
job.cancel()
}
@Test
fun isForceHidden_repoHasWifiHidden_outputsTrue() = runBlocking(IMMEDIATE) {
connectivityRepository.setForceHiddenIcons(setOf(ConnectivitySlot.WIFI))
@@ -270,10 +254,6 @@ class WifiInteractorTest : SysuiTestCase() {
job.cancel()
}
companion object {
val VALID_WIFI_NETWORK_MODEL = WifiNetworkModel.Active(networkId = 1, ssid = "AB")
}
}
private val IMMEDIATE = Dispatchers.Main.immediate

View File

@@ -16,38 +16,216 @@
package com.android.systemui.statusbar.pipeline.wifi.ui.view
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import android.testing.TestableLooper.RunWithLooper
import android.testing.ViewUtils
import android.view.View
import androidx.test.filters.SmallTest
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.lifecycle.InstantTaskExecutorRule
import com.android.systemui.util.Assert
import com.android.systemui.util.mockito.mock
import com.android.systemui.statusbar.StatusBarIconView.STATE_DOT
import com.android.systemui.statusbar.StatusBarIconView.STATE_HIDDEN
import com.android.systemui.statusbar.StatusBarIconView.STATE_ICON
import com.android.systemui.statusbar.phone.StatusBarLocation
import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel
import com.android.systemui.statusbar.pipeline.wifi.data.repository.FakeWifiRepository
import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractor
import com.android.systemui.statusbar.pipeline.wifi.shared.WifiConstants
import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.WifiViewModel
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
import org.mockito.Mock
import org.mockito.MockitoAnnotations
@SmallTest
@RunWith(JUnit4::class)
@RunWithLooper
@RunWith(AndroidTestingRunner::class)
@RunWithLooper(setAsMainLooper = true)
class ModernStatusBarWifiViewTest : SysuiTestCase() {
private lateinit var testableLooper: TestableLooper
@Mock
private lateinit var statusBarPipelineFlags: StatusBarPipelineFlags
@Mock
private lateinit var logger: ConnectivityPipelineLogger
@Mock
private lateinit var constants: WifiConstants
private lateinit var connectivityRepository: FakeConnectivityRepository
private lateinit var wifiRepository: FakeWifiRepository
private lateinit var interactor: WifiInteractor
private lateinit var viewModel: WifiViewModel
private lateinit var scope: CoroutineScope
@JvmField @Rule
val instantTaskExecutor = InstantTaskExecutorRule()
@Before
fun setUp() {
Assert.setTestThread(Thread.currentThread())
MockitoAnnotations.initMocks(this)
testableLooper = TestableLooper.get(this)
connectivityRepository = FakeConnectivityRepository()
wifiRepository = FakeWifiRepository()
wifiRepository.setIsWifiEnabled(true)
interactor = WifiInteractor(connectivityRepository, wifiRepository)
scope = CoroutineScope(Dispatchers.Unconfined)
viewModel = WifiViewModel(
constants, context, logger, interactor, scope, statusBarPipelineFlags
)
}
@Test
fun constructAndBind_hasCorrectSlot() {
val view = ModernStatusBarWifiView.constructAndBind(
context, "slotName", mock()
context, "slotName", viewModel, StatusBarLocation.HOME
)
assertThat(view.slot).isEqualTo("slotName")
}
@Test
fun getVisibleState_icon_returnsIcon() {
val view = ModernStatusBarWifiView.constructAndBind(
context, SLOT_NAME, viewModel, StatusBarLocation.HOME
)
view.setVisibleState(STATE_ICON, /* animate= */ false)
assertThat(view.visibleState).isEqualTo(STATE_ICON)
}
@Test
fun getVisibleState_dot_returnsDot() {
val view = ModernStatusBarWifiView.constructAndBind(
context, SLOT_NAME, viewModel, StatusBarLocation.HOME
)
view.setVisibleState(STATE_DOT, /* animate= */ false)
assertThat(view.visibleState).isEqualTo(STATE_DOT)
}
@Test
fun getVisibleState_hidden_returnsHidden() {
val view = ModernStatusBarWifiView.constructAndBind(
context, SLOT_NAME, viewModel, StatusBarLocation.HOME
)
view.setVisibleState(STATE_HIDDEN, /* animate= */ false)
assertThat(view.visibleState).isEqualTo(STATE_HIDDEN)
}
// Note: The following tests are more like integration tests, since they stand up a full
// [WifiViewModel] and test the interactions between the view, view-binder, and view-model.
@Test
fun setVisibleState_icon_iconShownDotHidden() {
val view = ModernStatusBarWifiView.constructAndBind(
context, SLOT_NAME, viewModel, StatusBarLocation.HOME
)
view.setVisibleState(STATE_ICON, /* animate= */ false)
ViewUtils.attachView(view)
testableLooper.processAllMessages()
assertThat(view.getIconGroupView().visibility).isEqualTo(View.VISIBLE)
assertThat(view.getDotView().visibility).isEqualTo(View.GONE)
ViewUtils.detachView(view)
}
@Test
fun setVisibleState_dot_iconHiddenDotShown() {
val view = ModernStatusBarWifiView.constructAndBind(
context, SLOT_NAME, viewModel, StatusBarLocation.HOME
)
view.setVisibleState(STATE_DOT, /* animate= */ false)
ViewUtils.attachView(view)
testableLooper.processAllMessages()
assertThat(view.getIconGroupView().visibility).isEqualTo(View.GONE)
assertThat(view.getDotView().visibility).isEqualTo(View.VISIBLE)
ViewUtils.detachView(view)
}
@Test
fun setVisibleState_hidden_iconAndDotHidden() {
val view = ModernStatusBarWifiView.constructAndBind(
context, SLOT_NAME, viewModel, StatusBarLocation.HOME
)
view.setVisibleState(STATE_HIDDEN, /* animate= */ false)
ViewUtils.attachView(view)
testableLooper.processAllMessages()
assertThat(view.getIconGroupView().visibility).isEqualTo(View.GONE)
assertThat(view.getDotView().visibility).isEqualTo(View.GONE)
ViewUtils.detachView(view)
}
@Test
fun isIconVisible_notEnabled_outputsFalse() {
wifiRepository.setIsWifiEnabled(false)
wifiRepository.setWifiNetwork(
WifiNetworkModel.Active(NETWORK_ID, isValidated = true, level = 2)
)
val view = ModernStatusBarWifiView.constructAndBind(
context, SLOT_NAME, viewModel, StatusBarLocation.HOME
)
ViewUtils.attachView(view)
testableLooper.processAllMessages()
assertThat(view.isIconVisible).isFalse()
ViewUtils.detachView(view)
}
@Test
fun isIconVisible_enabled_outputsTrue() {
wifiRepository.setIsWifiEnabled(true)
wifiRepository.setWifiNetwork(
WifiNetworkModel.Active(NETWORK_ID, isValidated = true, level = 2)
)
val view = ModernStatusBarWifiView.constructAndBind(
context, SLOT_NAME, viewModel, StatusBarLocation.HOME
)
ViewUtils.attachView(view)
testableLooper.processAllMessages()
assertThat(view.isIconVisible).isTrue()
ViewUtils.detachView(view)
}
private fun View.getIconGroupView(): View {
return this.requireViewById(R.id.wifi_group)
}
private fun View.getDotView(): View {
return this.requireViewById(R.id.status_bar_dot)
}
}
private const val SLOT_NAME = "TestSlotName"
private const val NETWORK_ID = 200

View File

@@ -29,25 +29,29 @@ import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
import com.android.systemui.statusbar.pipeline.shared.data.model.ConnectivitySlot
import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiActivityModel
import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel
import com.android.systemui.statusbar.pipeline.wifi.data.repository.FakeWifiRepository
import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractor
import com.android.systemui.statusbar.pipeline.wifi.shared.WifiConstants
import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiActivityModel
import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.WifiViewModel.Companion.NO_INTERNET
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.yield
import org.junit.After
import org.junit.Before
import org.junit.Test
import org.mockito.Mock
import org.mockito.Mockito.`when` as whenever
import org.mockito.MockitoAnnotations
@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
class WifiViewModelTest : SysuiTestCase() {
@@ -60,34 +64,64 @@ class WifiViewModelTest : SysuiTestCase() {
private lateinit var connectivityRepository: FakeConnectivityRepository
private lateinit var wifiRepository: FakeWifiRepository
private lateinit var interactor: WifiInteractor
private lateinit var scope: CoroutineScope
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
connectivityRepository = FakeConnectivityRepository()
wifiRepository = FakeWifiRepository()
wifiRepository.setIsWifiEnabled(true)
interactor = WifiInteractor(connectivityRepository, wifiRepository)
scope = CoroutineScope(IMMEDIATE)
createAndSetViewModel()
}
underTest = WifiViewModel(
statusBarPipelineFlags,
constants,
context,
logger,
interactor
)
@After
fun tearDown() {
scope.cancel()
}
// Note on testing: [WifiViewModel] exposes 3 different instances of
// [LocationBasedWifiViewModel]. In practice, these 3 different instances will get the exact
// same data for icon, activity, etc. flows. So, most of these tests will test just one of the
// instances. There are also some tests that verify all 3 instances received the same data.
@Test
fun wifiIcon_notEnabled_outputsNull() = runBlocking(IMMEDIATE) {
wifiRepository.setIsWifiEnabled(false)
// Start as non-null so we can verify we got the update
var latest: Icon? = Icon.Resource(0, null)
val job = underTest
.home
.wifiIcon
.onEach { latest = it }
.launchIn(this)
wifiRepository.setWifiNetwork(WifiNetworkModel.Active(NETWORK_ID, level = 2))
yield()
assertThat(latest).isNull()
job.cancel()
}
@Test
fun wifiIcon_forceHidden_outputsNull() = runBlocking(IMMEDIATE) {
connectivityRepository.setForceHiddenIcons(setOf(ConnectivitySlot.WIFI))
wifiRepository.setWifiNetwork(WifiNetworkModel.Active(NETWORK_ID, level = 2))
var latest: Icon? = null
// Start as non-null so we can verify we got the update
var latest: Icon? = Icon.Resource(0, null)
val job = underTest
.home
.wifiIcon
.onEach { latest = it }
.launchIn(this)
wifiRepository.setWifiNetwork(WifiNetworkModel.Active(NETWORK_ID, level = 2))
yield()
assertThat(latest).isNull()
job.cancel()
@@ -96,28 +130,59 @@ class WifiViewModelTest : SysuiTestCase() {
@Test
fun wifiIcon_notForceHidden_outputsVisible() = runBlocking(IMMEDIATE) {
connectivityRepository.setForceHiddenIcons(setOf())
wifiRepository.setWifiNetwork(WifiNetworkModel.Active(NETWORK_ID, level = 2))
var latest: Icon? = null
val job = underTest
.home
.wifiIcon
.onEach { latest = it }
.launchIn(this)
wifiRepository.setWifiNetwork(
WifiNetworkModel.Active(NETWORK_ID, isValidated = true, level = 2)
)
yield()
assertThat(latest).isInstanceOf(Icon.Resource::class.java)
job.cancel()
}
@Test
fun wifiIcon_inactiveNetwork_outputsNoNetworkIcon() = runBlocking(IMMEDIATE) {
fun wifiIcon_inactiveNetwork_alwaysShowFalse_outputsNull() = runBlocking(IMMEDIATE) {
whenever(constants.alwaysShowIconIfEnabled).thenReturn(false)
createAndSetViewModel()
// Start as non-null so we can verify we got the update
var latest: Icon? = Icon.Resource(0, null)
val job = underTest
.home
.wifiIcon
.onEach { latest = it }
.launchIn(this)
wifiRepository.setWifiNetwork(WifiNetworkModel.Inactive)
yield()
assertThat(latest).isNull()
job.cancel()
}
@Test
fun wifiIcon_inactiveNetwork_alwaysShowTrue_outputsNoNetworkIcon() = runBlocking(IMMEDIATE) {
whenever(constants.alwaysShowIconIfEnabled).thenReturn(true)
createAndSetViewModel()
var latest: Icon? = null
val job = underTest
.wifiIcon
.onEach { latest = it }
.launchIn(this)
.home
.wifiIcon
.onEach { latest = it }
.launchIn(this)
wifiRepository.setWifiNetwork(WifiNetworkModel.Inactive)
yield()
assertThat(latest).isInstanceOf(Icon.Resource::class.java)
val icon = latest as Icon.Resource
@@ -132,14 +197,22 @@ class WifiViewModelTest : SysuiTestCase() {
@Test
fun wifiIcon_carrierMergedNetwork_outputsNull() = runBlocking(IMMEDIATE) {
wifiRepository.setWifiNetwork(WifiNetworkModel.CarrierMerged)
// Even when we should always show the icon
whenever(constants.alwaysShowIconIfEnabled).thenReturn(true)
createAndSetViewModel()
var latest: Icon? = null
var latest: Icon? = Icon.Resource(0, null)
val job = underTest
.home
.wifiIcon
.onEach { latest = it }
.launchIn(this)
// WHEN we have a carrier merged network
wifiRepository.setWifiNetwork(WifiNetworkModel.CarrierMerged)
yield()
// THEN we override the alwaysShow boolean and still don't show the icon
assertThat(latest).isNull()
job.cancel()
@@ -147,14 +220,22 @@ class WifiViewModelTest : SysuiTestCase() {
@Test
fun wifiIcon_isActiveNullLevel_outputsNull() = runBlocking(IMMEDIATE) {
wifiRepository.setWifiNetwork(WifiNetworkModel.Active(NETWORK_ID, level = null))
// Even when we should always show the icon
whenever(constants.alwaysShowIconIfEnabled).thenReturn(true)
createAndSetViewModel()
var latest: Icon? = null
var latest: Icon? = Icon.Resource(0, null)
val job = underTest
.home
.wifiIcon
.onEach { latest = it }
.launchIn(this)
// WHEN we have a null level
wifiRepository.setWifiNetwork(WifiNetworkModel.Active(NETWORK_ID, level = null))
yield()
// THEN we override the alwaysShow boolean and still don't show the icon
assertThat(latest).isNull()
job.cancel()
@@ -162,22 +243,23 @@ class WifiViewModelTest : SysuiTestCase() {
@Test
fun wifiIcon_isActiveAndValidated_level1_outputsFull1Icon() = runBlocking(IMMEDIATE) {
val level = 1
wifiRepository.setWifiNetwork(
WifiNetworkModel.Active(
NETWORK_ID,
isValidated = true,
level = level
)
)
var latest: Icon? = null
val job = underTest
.home
.wifiIcon
.onEach { latest = it }
.launchIn(this)
val level = 1
wifiRepository.setWifiNetwork(
WifiNetworkModel.Active(
NETWORK_ID,
isValidated = true,
level,
)
)
yield()
assertThat(latest).isInstanceOf(Icon.Resource::class.java)
val icon = latest as Icon.Resource
assertThat(icon.res).isEqualTo(WIFI_FULL_ICONS[level])
@@ -190,23 +272,49 @@ class WifiViewModelTest : SysuiTestCase() {
}
@Test
fun wifiIcon_isActiveAndNotValidated_level4_outputsEmpty4Icon() = runBlocking(IMMEDIATE) {
val level = 4
fun wifiIcon_isActiveAndNotValidated_alwaysShowFalse_outputsNull() = runBlocking(IMMEDIATE) {
whenever(constants.alwaysShowIconIfEnabled).thenReturn(false)
createAndSetViewModel()
wifiRepository.setWifiNetwork(
WifiNetworkModel.Active(
NETWORK_ID,
isValidated = false,
level = level
)
)
var latest: Icon? = null
var latest: Icon? = Icon.Resource(0, null)
val job = underTest
.home
.wifiIcon
.onEach { latest = it }
.launchIn(this)
wifiRepository.setWifiNetwork(
WifiNetworkModel.Active(NETWORK_ID, isValidated = false, level = 4,)
)
yield()
assertThat(latest).isNull()
job.cancel()
}
@Test
fun wifiIcon_isActiveAndNotValidated_alwaysShowTrue_outputsIcon() = runBlocking(IMMEDIATE) {
whenever(constants.alwaysShowIconIfEnabled).thenReturn(true)
createAndSetViewModel()
var latest: Icon? = null
val job = underTest
.home
.wifiIcon
.onEach { latest = it }
.launchIn(this)
val level = 4
wifiRepository.setWifiNetwork(
WifiNetworkModel.Active(
NETWORK_ID,
isValidated = false,
level,
)
)
yield()
assertThat(latest).isInstanceOf(Icon.Resource::class.java)
val icon = latest as Icon.Resource
assertThat(icon.res).isEqualTo(WIFI_NO_INTERNET_ICONS[level])
@@ -219,68 +327,398 @@ class WifiViewModelTest : SysuiTestCase() {
}
@Test
fun activityInVisible_showActivityConfigFalse_outputsFalse() = runBlocking(IMMEDIATE) {
whenever(constants.shouldShowActivityConfig).thenReturn(false)
wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK)
fun wifiIcon_allLocationViewModelsReceiveSameData() = runBlocking(IMMEDIATE) {
var latestHome: Icon? = null
val jobHome = underTest
.home
.wifiIcon
.onEach { latestHome = it }
.launchIn(this)
var latest: Boolean? = null
val job = underTest
.isActivityInVisible
.onEach { latest = it }
.launchIn(this)
var latestKeyguard: Icon? = null
val jobKeyguard = underTest
.keyguard
.wifiIcon
.onEach { latestKeyguard = it }
.launchIn(this)
// Verify that on launch, we receive a false.
assertThat(latest).isFalse()
var latestQs: Icon? = null
val jobQs = underTest
.qs
.wifiIcon
.onEach { latestQs = it }
.launchIn(this)
job.cancel()
}
@Test
fun activityInVisible_showActivityConfigFalse_noUpdatesReceived() = runBlocking(IMMEDIATE) {
whenever(constants.shouldShowActivityConfig).thenReturn(false)
wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK)
var latest: Boolean? = null
val job = underTest
.isActivityInVisible
.onEach { latest = it }
.launchIn(this)
// Update the repo to have activityIn
wifiRepository.setWifiActivity(
WifiActivityModel(hasActivityIn = true, hasActivityOut = false)
wifiRepository.setWifiNetwork(
WifiNetworkModel.Active(
NETWORK_ID,
isValidated = true,
level = 1
)
)
yield()
// Verify that we didn't update to activityIn=true (because our config is false)
assertThat(latest).isFalse()
assertThat(latestHome).isInstanceOf(Icon.Resource::class.java)
assertThat(latestHome).isEqualTo(latestKeyguard)
assertThat(latestKeyguard).isEqualTo(latestQs)
job.cancel()
jobHome.cancel()
jobKeyguard.cancel()
jobQs.cancel()
}
@Test
fun activityInVisible_showActivityConfigTrue_outputsUpdate() = runBlocking(IMMEDIATE) {
fun activity_showActivityConfigFalse_outputsFalse() = runBlocking(IMMEDIATE) {
whenever(constants.shouldShowActivityConfig).thenReturn(false)
createAndSetViewModel()
wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK)
var activityIn: Boolean? = null
val activityInJob = underTest
.home
.isActivityInViewVisible
.onEach { activityIn = it }
.launchIn(this)
var activityOut: Boolean? = null
val activityOutJob = underTest
.home
.isActivityOutViewVisible
.onEach { activityOut = it }
.launchIn(this)
var activityContainer: Boolean? = null
val activityContainerJob = underTest
.home
.isActivityContainerVisible
.onEach { activityContainer = it }
.launchIn(this)
// Verify that on launch, we receive false.
assertThat(activityIn).isFalse()
assertThat(activityOut).isFalse()
assertThat(activityContainer).isFalse()
activityInJob.cancel()
activityOutJob.cancel()
activityContainerJob.cancel()
}
@Test
fun activity_showActivityConfigFalse_noUpdatesReceived() = runBlocking(IMMEDIATE) {
whenever(constants.shouldShowActivityConfig).thenReturn(false)
createAndSetViewModel()
wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK)
var activityIn: Boolean? = null
val activityInJob = underTest
.home
.isActivityInViewVisible
.onEach { activityIn = it }
.launchIn(this)
var activityOut: Boolean? = null
val activityOutJob = underTest
.home
.isActivityOutViewVisible
.onEach { activityOut = it }
.launchIn(this)
var activityContainer: Boolean? = null
val activityContainerJob = underTest
.home
.isActivityContainerVisible
.onEach { activityContainer = it }
.launchIn(this)
// WHEN we update the repo to have activity
val activity = WifiActivityModel(hasActivityIn = true, hasActivityOut = true)
wifiRepository.setWifiActivity(activity)
yield()
// THEN we didn't update to the new activity (because our config is false)
assertThat(activityIn).isFalse()
assertThat(activityOut).isFalse()
assertThat(activityContainer).isFalse()
activityInJob.cancel()
activityOutJob.cancel()
activityContainerJob.cancel()
}
@Test
fun activity_nullSsid_outputsFalse() = runBlocking(IMMEDIATE) {
whenever(constants.shouldShowActivityConfig).thenReturn(true)
createAndSetViewModel()
wifiRepository.setWifiNetwork(WifiNetworkModel.Active(NETWORK_ID, ssid = null))
var activityIn: Boolean? = null
val activityInJob = underTest
.home
.isActivityInViewVisible
.onEach { activityIn = it }
.launchIn(this)
var activityOut: Boolean? = null
val activityOutJob = underTest
.home
.isActivityOutViewVisible
.onEach { activityOut = it }
.launchIn(this)
var activityContainer: Boolean? = null
val activityContainerJob = underTest
.home
.isActivityContainerVisible
.onEach { activityContainer = it }
.launchIn(this)
// WHEN we update the repo to have activity
val activity = WifiActivityModel(hasActivityIn = true, hasActivityOut = true)
wifiRepository.setWifiActivity(activity)
yield()
// THEN we still output false because our network's SSID is null
assertThat(activityIn).isFalse()
assertThat(activityOut).isFalse()
assertThat(activityContainer).isFalse()
activityInJob.cancel()
activityOutJob.cancel()
activityContainerJob.cancel()
}
@Test
fun activity_allLocationViewModelsReceiveSameData() = runBlocking(IMMEDIATE) {
whenever(constants.shouldShowActivityConfig).thenReturn(true)
createAndSetViewModel()
wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK)
var latestHome: Boolean? = null
val jobHome = underTest
.home
.isActivityInViewVisible
.onEach { latestHome = it }
.launchIn(this)
var latestKeyguard: Boolean? = null
val jobKeyguard = underTest
.keyguard
.isActivityInViewVisible
.onEach { latestKeyguard = it }
.launchIn(this)
var latestQs: Boolean? = null
val jobQs = underTest
.qs
.isActivityInViewVisible
.onEach { latestQs = it }
.launchIn(this)
val activity = WifiActivityModel(hasActivityIn = true, hasActivityOut = true)
wifiRepository.setWifiActivity(activity)
yield()
assertThat(latestHome).isTrue()
assertThat(latestKeyguard).isTrue()
assertThat(latestQs).isTrue()
jobHome.cancel()
jobKeyguard.cancel()
jobQs.cancel()
}
@Test
fun activityIn_hasActivityInTrue_outputsTrue() = runBlocking(IMMEDIATE) {
whenever(constants.shouldShowActivityConfig).thenReturn(true)
createAndSetViewModel()
wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK)
var latest: Boolean? = null
val job = underTest
.isActivityInVisible
.onEach { latest = it }
.launchIn(this)
.home
.isActivityInViewVisible
.onEach { latest = it }
.launchIn(this)
// Update the repo to have activityIn
wifiRepository.setWifiActivity(
WifiActivityModel(hasActivityIn = true, hasActivityOut = false)
)
val activity = WifiActivityModel(hasActivityIn = true, hasActivityOut = false)
wifiRepository.setWifiActivity(activity)
yield()
// Verify that we updated to activityIn=true
assertThat(latest).isTrue()
job.cancel()
}
@Test
fun activityIn_hasActivityInFalse_outputsFalse() = runBlocking(IMMEDIATE) {
whenever(constants.shouldShowActivityConfig).thenReturn(true)
createAndSetViewModel()
wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK)
var latest: Boolean? = null
val job = underTest
.home
.isActivityInViewVisible
.onEach { latest = it }
.launchIn(this)
val activity = WifiActivityModel(hasActivityIn = false, hasActivityOut = true)
wifiRepository.setWifiActivity(activity)
yield()
assertThat(latest).isFalse()
job.cancel()
}
@Test
fun activityOut_hasActivityOutTrue_outputsTrue() = runBlocking(IMMEDIATE) {
whenever(constants.shouldShowActivityConfig).thenReturn(true)
createAndSetViewModel()
wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK)
var latest: Boolean? = null
val job = underTest
.home
.isActivityOutViewVisible
.onEach { latest = it }
.launchIn(this)
val activity = WifiActivityModel(hasActivityIn = false, hasActivityOut = true)
wifiRepository.setWifiActivity(activity)
yield()
assertThat(latest).isTrue()
job.cancel()
}
@Test
fun activityOut_hasActivityOutFalse_outputsFalse() = runBlocking(IMMEDIATE) {
whenever(constants.shouldShowActivityConfig).thenReturn(true)
createAndSetViewModel()
wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK)
var latest: Boolean? = null
val job = underTest
.home
.isActivityOutViewVisible
.onEach { latest = it }
.launchIn(this)
val activity = WifiActivityModel(hasActivityIn = true, hasActivityOut = false)
wifiRepository.setWifiActivity(activity)
yield()
assertThat(latest).isFalse()
job.cancel()
}
@Test
fun activityContainer_hasActivityInTrue_outputsTrue() = runBlocking(IMMEDIATE) {
whenever(constants.shouldShowActivityConfig).thenReturn(true)
createAndSetViewModel()
wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK)
var latest: Boolean? = null
val job = underTest
.home
.isActivityContainerVisible
.onEach { latest = it }
.launchIn(this)
val activity = WifiActivityModel(hasActivityIn = true, hasActivityOut = false)
wifiRepository.setWifiActivity(activity)
yield()
assertThat(latest).isTrue()
job.cancel()
}
@Test
fun activityContainer_hasActivityOutTrue_outputsTrue() = runBlocking(IMMEDIATE) {
whenever(constants.shouldShowActivityConfig).thenReturn(true)
createAndSetViewModel()
wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK)
var latest: Boolean? = null
val job = underTest
.home
.isActivityContainerVisible
.onEach { latest = it }
.launchIn(this)
val activity = WifiActivityModel(hasActivityIn = false, hasActivityOut = true)
wifiRepository.setWifiActivity(activity)
yield()
assertThat(latest).isTrue()
job.cancel()
}
@Test
fun activityContainer_inAndOutTrue_outputsTrue() = runBlocking(IMMEDIATE) {
whenever(constants.shouldShowActivityConfig).thenReturn(true)
createAndSetViewModel()
wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK)
var latest: Boolean? = null
val job = underTest
.home
.isActivityContainerVisible
.onEach { latest = it }
.launchIn(this)
val activity = WifiActivityModel(hasActivityIn = true, hasActivityOut = true)
wifiRepository.setWifiActivity(activity)
yield()
assertThat(latest).isTrue()
job.cancel()
}
@Test
fun activityContainer_inAndOutFalse_outputsFalse() = runBlocking(IMMEDIATE) {
whenever(constants.shouldShowActivityConfig).thenReturn(true)
createAndSetViewModel()
wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK)
var latest: Boolean? = null
val job = underTest
.home
.isActivityContainerVisible
.onEach { latest = it }
.launchIn(this)
val activity = WifiActivityModel(hasActivityIn = false, hasActivityOut = false)
wifiRepository.setWifiActivity(activity)
yield()
assertThat(latest).isFalse()
job.cancel()
}
private fun createAndSetViewModel() {
// [WifiViewModel] creates its flows as soon as it's instantiated, and some of those flow
// creations rely on certain config values that we mock out in individual tests. This method
// allows tests to create the view model only after those configs are correctly set up.
underTest = WifiViewModel(
constants,
context,
logger,
interactor,
scope,
statusBarPipelineFlags,
)
}
private fun ContentDescription.getAsString(): String? {
return when (this) {
is ContentDescription.Loaded -> this.description