Merge changes I723cfc04,I1eba794e,I9d421a4a,Icd9c2b14,Idee192ff, ... into tm-qpr-dev am: d547dfa98e am: ea4878748f
Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/20067345
Change-Id: I9faa24e536c3bc43a44f47cdfd69de954d962736
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml
index 8084254..f22e797 100644
--- a/packages/SystemUI/res/values/ids.xml
+++ b/packages/SystemUI/res/values/ids.xml
@@ -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" />
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java
index b585961..ccaab1a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java
@@ -30,6 +30,7 @@
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 @@
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) -> {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt b/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt
index fe40d4c..d3ed474 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt
@@ -48,6 +48,7 @@
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 @@
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)
)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
index 827d0d0f..c35c5c5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
@@ -22,6 +22,7 @@
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.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 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 @@
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 @@
}
@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 @@
return mIconAppearAmount;
}
+ @StatusBarIconView.VisibleState
public int getVisibleState() {
return mVisibleState;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarMobileView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarMobileView.java
index 25c6dce..48c6e27 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarMobileView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarMobileView.java
@@ -59,7 +59,8 @@
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 @@
}
@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 @@
}
@Override
+ @StatusBarIconView.VisibleState
public int getVisibleState() {
return mVisibleState;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarWifiView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarWifiView.java
index 5aee62e..f3e74d9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarWifiView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarWifiView.java
@@ -55,7 +55,8 @@
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 @@
}
@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 @@
}
@Override
+ @StatusBarIconView.VisibleState
public int getVisibleState() {
return mVisibleState;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusIconDisplayable.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusIconDisplayable.java
index d541fae..1196211 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusIconDisplayable.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusIconDisplayable.java
@@ -22,14 +22,32 @@
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;
}
}
-
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
index ce2c9c2..0026b71 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
@@ -352,8 +352,8 @@
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);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
index bd99713..d6d021f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
@@ -56,7 +56,6 @@
import java.util.List;
import javax.inject.Inject;
-import javax.inject.Provider;
public interface StatusBarIconController {
@@ -139,13 +138,15 @@
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 @@
@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 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 @@
@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 @@
*/
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 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 @@
private ModernStatusBarWifiView onCreateModernStatusBarWifiView(String slot) {
return ModernStatusBarWifiView.constructAndBind(
- mContext, slot, mWifiViewModelProvider.get());
+ mContext, slot, mWifiViewModel, mLocation);
}
private StatusBarMobileView onCreateStatusBarMobileView(int subId, String slot) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiActivityModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarLocation.kt
similarity index 64%
copy from packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiActivityModel.kt
copy to packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarLocation.kt
index 44c0496..5ace226 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiActivityModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarLocation.kt
@@ -14,14 +14,14 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.pipeline.wifi.data.model
+package com.android.systemui.statusbar.phone
-/**
- * Provides information on the current wifi activity.
- */
-data class WifiActivityModel(
- /** True if the wifi has activity in (download). */
- val hasActivityIn: Boolean,
- /** True if the wifi has activity out (upload). */
- val hasActivityOut: Boolean,
-)
+/** 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,
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
index 891f657..b8bdc7d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
@@ -64,6 +64,7 @@
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;
@@ -235,7 +236,8 @@
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);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLogger.kt
index 88d8a86..dbb1aa5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLogger.kt
@@ -33,6 +33,20 @@
) {
/**
* 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.
*/
fun logInputChange(callbackName: String, changeInfo: String?) {
buffer.log(
@@ -128,12 +142,36 @@
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() }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt
index 103f3fc..681cf72 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt
@@ -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.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 @@
) {
logger.logOnCapabilitiesChanged(network, networkCapabilities)
+ wifiNetworkChangeEvents.tryEmit(Unit)
+
val wifiInfo = networkCapabilitiesToWifiInfo(networkCapabilities)
if (wifiInfo?.isPrimary == true) {
val wifiNetworkModel = createWifiNetworkModel(
@@ -104,6 +143,9 @@
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 @@
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 @@
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)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractor.kt
index 952525d..04b17ed 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractor.kt
@@ -22,9 +22,10 @@
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 @@
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 @@
}
}
+ /** 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
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/WifiConstants.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/WifiConstants.kt
index a19d1bd..0eb4b0d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/WifiConstants.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/WifiConstants.kt
@@ -41,9 +41,14 @@
/** 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")
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiActivityModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/model/WifiActivityModel.kt
similarity index 86%
rename from packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiActivityModel.kt
rename to packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/model/WifiActivityModel.kt
index 44c0496..5746106 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiActivityModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/model/WifiActivityModel.kt
@@ -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,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt
index 4fad327..273be63 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt
@@ -26,8 +26,15 @@
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 @@
*/
@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
+ }
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiView.kt
index c14a897..6c616ac 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiView.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiView.kt
@@ -19,10 +19,14 @@
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 @@
) : 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 @@
// 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)
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/HomeWifiViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/HomeWifiViewModel.kt
new file mode 100644
index 0000000..871b395
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/HomeWifiViewModel.kt
@@ -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,
+ )
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/KeyguardWifiViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/KeyguardWifiViewModel.kt
new file mode 100644
index 0000000..be1f3f2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/KeyguardWifiViewModel.kt
@@ -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,
+ )
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/LocationBasedWifiViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/LocationBasedWifiViewModel.kt
new file mode 100644
index 0000000..7243acf
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/LocationBasedWifiViewModel.kt
@@ -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
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/QsWifiViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/QsWifiViewModel.kt
new file mode 100644
index 0000000..d640d33
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/QsWifiViewModel.kt
@@ -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,
+ )
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt
index 3c243ac..47347a2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt
@@ -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.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.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
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickStatusBarHeaderControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickStatusBarHeaderControllerTest.kt
index eb907bd..39d89bf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickStatusBarHeaderControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickStatusBarHeaderControllerTest.kt
@@ -110,7 +110,7 @@
`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)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerCombinedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerCombinedTest.kt
index c448538..c76d9e7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerCombinedTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerCombinedTest.kt
@@ -176,7 +176,7 @@
}
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)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerTest.kt
index 5ecfc8eb..90ae693 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerTest.kt
@@ -97,7 +97,7 @@
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,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java
index ba5f503..cfaa470 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java
@@ -135,7 +135,7 @@
MockitoAnnotations.initMocks(this);
- when(mIconManagerFactory.create(any())).thenReturn(mIconManager);
+ when(mIconManagerFactory.create(any(), any())).thenReturn(mIconManager);
allowTestableLooperAsMainThread();
TestableLooper.get(this).runWithLooper(() -> {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerTest.java
index de7db74..34399b8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerTest.java
@@ -51,8 +51,6 @@
import org.junit.Test;
import org.junit.runner.RunWith;
-import javax.inject.Provider;
-
@RunWith(AndroidTestingRunner.class)
@RunWithLooper
@SmallTest
@@ -79,8 +77,9 @@
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 @@
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 @@
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);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
index faadd24..1ce4d61 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
@@ -428,7 +428,7 @@
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();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLoggerTest.kt
index 36be1be..0e75c74 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLoggerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLoggerTest.kt
@@ -23,9 +23,16 @@
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 @@
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()
}
- private val NET_1_CAPS = NetworkCapabilities.Builder()
- .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
- .addCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)
- .build()
+
+ @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
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/FakeWifiRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/FakeWifiRepository.kt
index 6b8d4aa..f751afc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/FakeWifiRepository.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/FakeWifiRepository.kt
@@ -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
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositoryImplTest.kt
index d070ba0..0ba0bd6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositoryImplTest.kt
@@ -28,15 +28,17 @@
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.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 @@
@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
@@ -88,6 +96,132 @@
}
@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
val job = underTest
@@ -509,13 +643,7 @@
@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 @@
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())
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorTest.kt
index e896749..39b886a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorTest.kt
@@ -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 @@
}
@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()
}
@@ -242,6 +200,32 @@
}
@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 @@
job.cancel()
}
-
- companion object {
- val VALID_WIFI_NETWORK_MODEL = WifiNetworkModel.Active(networkId = 1, ssid = "AB")
- }
}
private val IMMEDIATE = Dispatchers.Main.immediate
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt
index 3c200a5..c577db8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt
@@ -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
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt
index 43103a0..063072f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt
@@ -29,25 +29,29 @@
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 @@
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 @@
@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 @@
@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 @@
@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 @@
@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 @@
}
@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 @@
}
@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