Merge changes I723cfc04,I1eba794e,I9d421a4a,Icd9c2b14,Idee192ff, ... into tm-qpr-dev am: d547dfa98e
Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/20067345 Change-Id: Idb50bfb163a46b0e524ff579ae418b12a7ed7915 Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
@@ -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" />
|
||||
|
||||
@@ -30,6 +30,7 @@ import com.android.systemui.qs.carrier.QSCarrierGroupController;
|
||||
import com.android.systemui.qs.dagger.QSScope;
|
||||
import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider;
|
||||
import com.android.systemui.statusbar.phone.StatusBarIconController;
|
||||
import com.android.systemui.statusbar.phone.StatusBarLocation;
|
||||
import com.android.systemui.statusbar.phone.StatusIconContainer;
|
||||
import com.android.systemui.statusbar.policy.Clock;
|
||||
import com.android.systemui.statusbar.policy.VariableDateViewController;
|
||||
@@ -104,7 +105,7 @@ class QuickStatusBarHeaderController extends ViewController<QuickStatusBarHeader
|
||||
mView.requireViewById(R.id.date_clock)
|
||||
);
|
||||
|
||||
mIconManager = tintedIconManagerFactory.create(mIconContainer);
|
||||
mIconManager = tintedIconManagerFactory.create(mIconContainer, StatusBarLocation.QS);
|
||||
mDemoModeReceiver = new ClockDemoModeReceiver(mClockView);
|
||||
mColorExtractor = colorExtractor;
|
||||
mOnColorsChangedListener = (extractor, which) -> {
|
||||
|
||||
@@ -48,6 +48,7 @@ import com.android.systemui.shade.LargeScreenShadeHeaderController.Companion.QQS
|
||||
import com.android.systemui.shade.LargeScreenShadeHeaderController.Companion.QS_HEADER_CONSTRAINT
|
||||
import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider
|
||||
import com.android.systemui.statusbar.phone.StatusBarIconController
|
||||
import com.android.systemui.statusbar.phone.StatusBarLocation
|
||||
import com.android.systemui.statusbar.phone.StatusIconContainer
|
||||
import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent.CentralSurfacesScope
|
||||
import com.android.systemui.statusbar.phone.dagger.StatusBarViewModule.LARGE_SCREEN_BATTERY_CONTROLLER
|
||||
@@ -261,7 +262,7 @@ class LargeScreenShadeHeaderController @Inject constructor(
|
||||
batteryMeterViewController.ignoreTunerUpdates()
|
||||
batteryIcon.setPercentShowMode(BatteryMeterView.MODE_ESTIMATE)
|
||||
|
||||
iconManager = tintedIconManagerFactory.create(iconContainer)
|
||||
iconManager = tintedIconManagerFactory.create(iconContainer, StatusBarLocation.QS)
|
||||
iconManager.setTint(
|
||||
Utils.getColorAttrDefaultColor(header.context, android.R.attr.textColorPrimary)
|
||||
)
|
||||
|
||||
@@ -22,6 +22,7 @@ import android.animation.Animator;
|
||||
import android.animation.AnimatorListenerAdapter;
|
||||
import android.animation.ObjectAnimator;
|
||||
import android.animation.ValueAnimator;
|
||||
import android.annotation.IntDef;
|
||||
import android.app.ActivityManager;
|
||||
import android.app.Notification;
|
||||
import android.content.Context;
|
||||
@@ -59,6 +60,8 @@ import com.android.systemui.statusbar.notification.NotificationIconDozeHelper;
|
||||
import com.android.systemui.statusbar.notification.NotificationUtils;
|
||||
import com.android.systemui.util.drawable.DrawableSize;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.text.NumberFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
@@ -86,6 +89,10 @@ public class StatusBarIconView extends AnimatedImageView implements StatusIconDi
|
||||
public static final int STATE_DOT = 1;
|
||||
public static final int STATE_HIDDEN = 2;
|
||||
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@IntDef({STATE_ICON, STATE_DOT, STATE_HIDDEN})
|
||||
public @interface VisibleState { }
|
||||
|
||||
private static final String TAG = "StatusBarIconView";
|
||||
private static final Property<StatusBarIconView, Float> ICON_APPEAR_AMOUNT
|
||||
= new FloatProperty<StatusBarIconView>("iconAppearAmount") {
|
||||
@@ -133,6 +140,7 @@ public class StatusBarIconView extends AnimatedImageView implements StatusIconDi
|
||||
private final Paint mDotPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
private float mDotRadius;
|
||||
private int mStaticDotRadius;
|
||||
@StatusBarIconView.VisibleState
|
||||
private int mVisibleState = STATE_ICON;
|
||||
private float mIconAppearAmount = 1.0f;
|
||||
private ObjectAnimator mIconAppearAnimator;
|
||||
@@ -746,11 +754,12 @@ public class StatusBarIconView extends AnimatedImageView implements StatusIconDi
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setVisibleState(int state) {
|
||||
public void setVisibleState(@StatusBarIconView.VisibleState int state) {
|
||||
setVisibleState(state, true /* animate */, null /* endRunnable */);
|
||||
}
|
||||
|
||||
public void setVisibleState(int state, boolean animate) {
|
||||
@Override
|
||||
public void setVisibleState(@StatusBarIconView.VisibleState int state, boolean animate) {
|
||||
setVisibleState(state, animate, null);
|
||||
}
|
||||
|
||||
@@ -862,6 +871,7 @@ public class StatusBarIconView extends AnimatedImageView implements StatusIconDi
|
||||
return mIconAppearAmount;
|
||||
}
|
||||
|
||||
@StatusBarIconView.VisibleState
|
||||
public int getVisibleState() {
|
||||
return mVisibleState;
|
||||
}
|
||||
|
||||
@@ -59,7 +59,8 @@ public class StatusBarMobileView extends FrameLayout implements DarkReceiver,
|
||||
private ImageView mOut;
|
||||
private ImageView mMobile, mMobileType, mMobileRoaming;
|
||||
private View mMobileRoamingSpace;
|
||||
private int mVisibleState = -1;
|
||||
@StatusBarIconView.VisibleState
|
||||
private int mVisibleState = STATE_HIDDEN;
|
||||
private DualToneHandler mDualToneHandler;
|
||||
private boolean mForceHidden;
|
||||
|
||||
@@ -271,7 +272,7 @@ public class StatusBarMobileView extends FrameLayout implements DarkReceiver,
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setVisibleState(int state, boolean animate) {
|
||||
public void setVisibleState(@StatusBarIconView.VisibleState int state, boolean animate) {
|
||||
if (state == mVisibleState) {
|
||||
return;
|
||||
}
|
||||
@@ -312,6 +313,7 @@ public class StatusBarMobileView extends FrameLayout implements DarkReceiver,
|
||||
}
|
||||
|
||||
@Override
|
||||
@StatusBarIconView.VisibleState
|
||||
public int getVisibleState() {
|
||||
return mVisibleState;
|
||||
}
|
||||
|
||||
@@ -55,7 +55,8 @@ public class StatusBarWifiView extends BaseStatusBarWifiView implements DarkRece
|
||||
private View mAirplaneSpacer;
|
||||
private WifiIconState mState;
|
||||
private String mSlot;
|
||||
private int mVisibleState = -1;
|
||||
@StatusBarIconView.VisibleState
|
||||
private int mVisibleState = STATE_HIDDEN;
|
||||
|
||||
public static StatusBarWifiView fromContext(Context context, String slot) {
|
||||
LayoutInflater inflater = LayoutInflater.from(context);
|
||||
@@ -107,7 +108,7 @@ public class StatusBarWifiView extends BaseStatusBarWifiView implements DarkRece
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setVisibleState(int state, boolean animate) {
|
||||
public void setVisibleState(@StatusBarIconView.VisibleState int state, boolean animate) {
|
||||
if (state == mVisibleState) {
|
||||
return;
|
||||
}
|
||||
@@ -131,6 +132,7 @@ public class StatusBarWifiView extends BaseStatusBarWifiView implements DarkRece
|
||||
}
|
||||
|
||||
@Override
|
||||
@StatusBarIconView.VisibleState
|
||||
public int getVisibleState() {
|
||||
return mVisibleState;
|
||||
}
|
||||
|
||||
@@ -22,14 +22,32 @@ public interface StatusIconDisplayable extends DarkReceiver {
|
||||
String getSlot();
|
||||
void setStaticDrawableColor(int color);
|
||||
void setDecorColor(int color);
|
||||
default void setVisibleState(int state) {
|
||||
|
||||
/** Sets the visible state that this displayable should be. */
|
||||
default void setVisibleState(@StatusBarIconView.VisibleState int state) {
|
||||
setVisibleState(state, false);
|
||||
}
|
||||
void setVisibleState(int state, boolean animate);
|
||||
|
||||
/**
|
||||
* Sets the visible state that this displayable should be, and whether the change should
|
||||
* animate.
|
||||
*/
|
||||
void setVisibleState(@StatusBarIconView.VisibleState int state, boolean animate);
|
||||
|
||||
/** Returns the current visible state of this displayable. */
|
||||
@StatusBarIconView.VisibleState
|
||||
int getVisibleState();
|
||||
|
||||
/**
|
||||
* Returns true if this icon should be visible if there's space, and false otherwise.
|
||||
*
|
||||
* Note that this doesn't necessarily mean it *will* be visible. It's possible that there are
|
||||
* more icons than space, in which case this icon might just show a dot or might be completely
|
||||
* hidden. {@link #getVisibleState} will return the icon's actual visible status.
|
||||
*/
|
||||
boolean isIconVisible();
|
||||
|
||||
default boolean isIconBlocked() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -352,8 +352,8 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat
|
||||
mKeyguardUpdateMonitor.registerCallback(mKeyguardUpdateMonitorCallback);
|
||||
mDisableStateTracker.startTracking(mCommandQueue, mView.getDisplay().getDisplayId());
|
||||
if (mTintedIconManager == null) {
|
||||
mTintedIconManager =
|
||||
mTintedIconManagerFactory.create(mView.findViewById(R.id.statusIcons));
|
||||
mTintedIconManager = mTintedIconManagerFactory.create(
|
||||
mView.findViewById(R.id.statusIcons), StatusBarLocation.KEYGUARD);
|
||||
mTintedIconManager.setBlockList(getBlockedIcons());
|
||||
mStatusBarIconController.addIconGroup(mTintedIconManager);
|
||||
}
|
||||
|
||||
@@ -56,7 +56,6 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Provider;
|
||||
|
||||
public interface StatusBarIconController {
|
||||
|
||||
@@ -139,13 +138,15 @@ public interface StatusBarIconController {
|
||||
|
||||
public DarkIconManager(
|
||||
LinearLayout linearLayout,
|
||||
StatusBarLocation location,
|
||||
StatusBarPipelineFlags statusBarPipelineFlags,
|
||||
Provider<WifiViewModel> wifiViewModelProvider,
|
||||
WifiViewModel wifiViewModel,
|
||||
MobileContextProvider mobileContextProvider,
|
||||
DarkIconDispatcher darkIconDispatcher) {
|
||||
super(linearLayout,
|
||||
location,
|
||||
statusBarPipelineFlags,
|
||||
wifiViewModelProvider,
|
||||
wifiViewModel,
|
||||
mobileContextProvider);
|
||||
mIconHPadding = mContext.getResources().getDimensionPixelSize(
|
||||
R.dimen.status_bar_icon_padding);
|
||||
@@ -204,27 +205,28 @@ public interface StatusBarIconController {
|
||||
@SysUISingleton
|
||||
public static class Factory {
|
||||
private final StatusBarPipelineFlags mStatusBarPipelineFlags;
|
||||
private final Provider<WifiViewModel> mWifiViewModelProvider;
|
||||
private final WifiViewModel mWifiViewModel;
|
||||
private final MobileContextProvider mMobileContextProvider;
|
||||
private final DarkIconDispatcher mDarkIconDispatcher;
|
||||
|
||||
@Inject
|
||||
public Factory(
|
||||
StatusBarPipelineFlags statusBarPipelineFlags,
|
||||
Provider<WifiViewModel> wifiViewModelProvider,
|
||||
WifiViewModel wifiViewModel,
|
||||
MobileContextProvider mobileContextProvider,
|
||||
DarkIconDispatcher darkIconDispatcher) {
|
||||
mStatusBarPipelineFlags = statusBarPipelineFlags;
|
||||
mWifiViewModelProvider = wifiViewModelProvider;
|
||||
mWifiViewModel = wifiViewModel;
|
||||
mMobileContextProvider = mobileContextProvider;
|
||||
mDarkIconDispatcher = darkIconDispatcher;
|
||||
}
|
||||
|
||||
public DarkIconManager create(LinearLayout group) {
|
||||
public DarkIconManager create(LinearLayout group, StatusBarLocation location) {
|
||||
return new DarkIconManager(
|
||||
group,
|
||||
location,
|
||||
mStatusBarPipelineFlags,
|
||||
mWifiViewModelProvider,
|
||||
mWifiViewModel,
|
||||
mMobileContextProvider,
|
||||
mDarkIconDispatcher);
|
||||
}
|
||||
@@ -239,12 +241,14 @@ public interface StatusBarIconController {
|
||||
|
||||
public TintedIconManager(
|
||||
ViewGroup group,
|
||||
StatusBarLocation location,
|
||||
StatusBarPipelineFlags statusBarPipelineFlags,
|
||||
Provider<WifiViewModel> wifiViewModelProvider,
|
||||
WifiViewModel wifiViewModel,
|
||||
MobileContextProvider mobileContextProvider) {
|
||||
super(group,
|
||||
location,
|
||||
statusBarPipelineFlags,
|
||||
wifiViewModelProvider,
|
||||
wifiViewModel,
|
||||
mobileContextProvider);
|
||||
}
|
||||
|
||||
@@ -278,24 +282,25 @@ public interface StatusBarIconController {
|
||||
@SysUISingleton
|
||||
public static class Factory {
|
||||
private final StatusBarPipelineFlags mStatusBarPipelineFlags;
|
||||
private final Provider<WifiViewModel> mWifiViewModelProvider;
|
||||
private final WifiViewModel mWifiViewModel;
|
||||
private final MobileContextProvider mMobileContextProvider;
|
||||
|
||||
@Inject
|
||||
public Factory(
|
||||
StatusBarPipelineFlags statusBarPipelineFlags,
|
||||
Provider<WifiViewModel> wifiViewModelProvider,
|
||||
WifiViewModel wifiViewModel,
|
||||
MobileContextProvider mobileContextProvider) {
|
||||
mStatusBarPipelineFlags = statusBarPipelineFlags;
|
||||
mWifiViewModelProvider = wifiViewModelProvider;
|
||||
mWifiViewModel = wifiViewModel;
|
||||
mMobileContextProvider = mobileContextProvider;
|
||||
}
|
||||
|
||||
public TintedIconManager create(ViewGroup group) {
|
||||
public TintedIconManager create(ViewGroup group, StatusBarLocation location) {
|
||||
return new TintedIconManager(
|
||||
group,
|
||||
location,
|
||||
mStatusBarPipelineFlags,
|
||||
mWifiViewModelProvider,
|
||||
mWifiViewModel,
|
||||
mMobileContextProvider);
|
||||
}
|
||||
}
|
||||
@@ -306,8 +311,9 @@ public interface StatusBarIconController {
|
||||
*/
|
||||
class IconManager implements DemoModeCommandReceiver {
|
||||
protected final ViewGroup mGroup;
|
||||
private final StatusBarLocation mLocation;
|
||||
private final StatusBarPipelineFlags mStatusBarPipelineFlags;
|
||||
private final Provider<WifiViewModel> mWifiViewModelProvider;
|
||||
private final WifiViewModel mWifiViewModel;
|
||||
private final MobileContextProvider mMobileContextProvider;
|
||||
protected final Context mContext;
|
||||
protected final int mIconSize;
|
||||
@@ -324,12 +330,14 @@ public interface StatusBarIconController {
|
||||
|
||||
public IconManager(
|
||||
ViewGroup group,
|
||||
StatusBarLocation location,
|
||||
StatusBarPipelineFlags statusBarPipelineFlags,
|
||||
Provider<WifiViewModel> wifiViewModelProvider,
|
||||
WifiViewModel wifiViewModel,
|
||||
MobileContextProvider mobileContextProvider) {
|
||||
mGroup = group;
|
||||
mLocation = location;
|
||||
mStatusBarPipelineFlags = statusBarPipelineFlags;
|
||||
mWifiViewModelProvider = wifiViewModelProvider;
|
||||
mWifiViewModel = wifiViewModel;
|
||||
mMobileContextProvider = mobileContextProvider;
|
||||
mContext = group.getContext();
|
||||
mIconSize = mContext.getResources().getDimensionPixelSize(
|
||||
@@ -446,7 +454,7 @@ public interface StatusBarIconController {
|
||||
|
||||
private ModernStatusBarWifiView onCreateModernStatusBarWifiView(String slot) {
|
||||
return ModernStatusBarWifiView.constructAndBind(
|
||||
mContext, slot, mWifiViewModelProvider.get());
|
||||
mContext, slot, mWifiViewModel, mLocation);
|
||||
}
|
||||
|
||||
private StatusBarMobileView onCreateStatusBarMobileView(int subId, String slot) {
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright (C) 2022 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.systemui.statusbar.phone
|
||||
|
||||
/** An enumeration of the different locations that host a status bar. */
|
||||
enum class StatusBarLocation {
|
||||
/** Home screen or in-app. */
|
||||
HOME,
|
||||
/** Keyguard (aka lockscreen). */
|
||||
KEYGUARD,
|
||||
/** Quick settings (inside the shade). */
|
||||
QS,
|
||||
}
|
||||
@@ -68,6 +68,7 @@ import com.android.systemui.statusbar.phone.PhoneStatusBarView;
|
||||
import com.android.systemui.statusbar.phone.StatusBarHideIconsForBouncerManager;
|
||||
import com.android.systemui.statusbar.phone.StatusBarIconController;
|
||||
import com.android.systemui.statusbar.phone.StatusBarIconController.DarkIconManager;
|
||||
import com.android.systemui.statusbar.phone.StatusBarLocation;
|
||||
import com.android.systemui.statusbar.phone.StatusBarLocationPublisher;
|
||||
import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentComponent;
|
||||
import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentComponent.Startable;
|
||||
@@ -250,7 +251,8 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue
|
||||
mStatusBar.restoreHierarchyState(
|
||||
savedInstanceState.getSparseParcelableArray(EXTRA_PANEL_STATE));
|
||||
}
|
||||
mDarkIconManager = mDarkIconManagerFactory.create(view.findViewById(R.id.statusIcons));
|
||||
mDarkIconManager = mDarkIconManagerFactory.create(
|
||||
view.findViewById(R.id.statusIcons), StatusBarLocation.HOME);
|
||||
mDarkIconManager.setShouldLog(true);
|
||||
updateBlockedIcons();
|
||||
mStatusBarIconController.addIconGroup(mDarkIconManager);
|
||||
|
||||
@@ -31,6 +31,20 @@ import kotlinx.coroutines.flow.onEach
|
||||
class ConnectivityPipelineLogger @Inject constructor(
|
||||
@StatusBarConnectivityLog private val buffer: LogBuffer,
|
||||
) {
|
||||
/**
|
||||
* Logs a change in one of the **raw inputs** to the connectivity pipeline.
|
||||
*
|
||||
* Use this method for inputs that don't have any extra information besides their callback name.
|
||||
*/
|
||||
fun logInputChange(callbackName: String) {
|
||||
buffer.log(
|
||||
SB_LOGGING_TAG,
|
||||
LogLevel.INFO,
|
||||
{ str1 = callbackName },
|
||||
{ "Input: $str1" }
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs a change in one of the **raw inputs** to the connectivity pipeline.
|
||||
*/
|
||||
@@ -127,13 +141,37 @@ class ConnectivityPipelineLogger @Inject constructor(
|
||||
companion object {
|
||||
const val SB_LOGGING_TAG = "SbConnectivity"
|
||||
|
||||
/**
|
||||
* Log a change in one of the **inputs** to the connectivity pipeline.
|
||||
*/
|
||||
fun Flow<Unit>.logInputChange(
|
||||
logger: ConnectivityPipelineLogger,
|
||||
inputParamName: String,
|
||||
): Flow<Unit> {
|
||||
return this.onEach { logger.logInputChange(inputParamName) }
|
||||
}
|
||||
|
||||
/**
|
||||
* Log a change in one of the **inputs** to the connectivity pipeline.
|
||||
*
|
||||
* @param prettyPrint an optional function to transform the value into a readable string.
|
||||
* [toString] is used if no custom function is provided.
|
||||
*/
|
||||
fun <T> Flow<T>.logInputChange(
|
||||
logger: ConnectivityPipelineLogger,
|
||||
inputParamName: String,
|
||||
prettyPrint: (T) -> String = { it.toString() }
|
||||
): Flow<T> {
|
||||
return this.onEach {logger.logInputChange(inputParamName, prettyPrint(it)) }
|
||||
}
|
||||
|
||||
/**
|
||||
* Log a change in one of the **outputs** to the connectivity pipeline.
|
||||
*
|
||||
* @param prettyPrint an optional function to transform the value into a readable string.
|
||||
* [toString] is used if no custom function is provided.
|
||||
*/
|
||||
fun <T : Any> Flow<T>.logOutputChange(
|
||||
fun <T> Flow<T>.logOutputChange(
|
||||
logger: ConnectivityPipelineLogger,
|
||||
outputParamName: String,
|
||||
prettyPrint: (T) -> String = { it.toString() }
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
package com.android.systemui.statusbar.pipeline.wifi.data.repository
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.IntentFilter
|
||||
import android.net.ConnectivityManager
|
||||
import android.net.Network
|
||||
import android.net.NetworkCapabilities
|
||||
@@ -30,51 +31,87 @@ import android.net.wifi.WifiManager
|
||||
import android.net.wifi.WifiManager.TrafficStateCallback
|
||||
import android.util.Log
|
||||
import com.android.settingslib.Utils
|
||||
import com.android.systemui.broadcast.BroadcastDispatcher
|
||||
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
|
||||
import com.android.systemui.dagger.SysUISingleton
|
||||
import com.android.systemui.dagger.qualifiers.Application
|
||||
import com.android.systemui.dagger.qualifiers.Main
|
||||
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
|
||||
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.SB_LOGGING_TAG
|
||||
import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiActivityModel
|
||||
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.logInputChange
|
||||
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.logOutputChange
|
||||
import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel
|
||||
import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiActivityModel
|
||||
import java.util.concurrent.Executor
|
||||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.channels.awaitClose
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
import kotlinx.coroutines.flow.mapLatest
|
||||
import kotlinx.coroutines.flow.merge
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
|
||||
/**
|
||||
* Provides data related to the wifi state.
|
||||
*/
|
||||
/** Provides data related to the wifi state. */
|
||||
interface WifiRepository {
|
||||
/**
|
||||
* Observable for the current wifi network.
|
||||
*/
|
||||
val wifiNetwork: Flow<WifiNetworkModel>
|
||||
/** Observable for the current wifi enabled status. */
|
||||
val isWifiEnabled: StateFlow<Boolean>
|
||||
|
||||
/**
|
||||
* Observable for the current wifi network activity.
|
||||
*/
|
||||
val wifiActivity: Flow<WifiActivityModel>
|
||||
/** Observable for the current wifi network. */
|
||||
val wifiNetwork: StateFlow<WifiNetworkModel>
|
||||
|
||||
/** Observable for the current wifi network activity. */
|
||||
val wifiActivity: StateFlow<WifiActivityModel>
|
||||
}
|
||||
|
||||
/** Real implementation of [WifiRepository]. */
|
||||
@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
@SysUISingleton
|
||||
@SuppressLint("MissingPermission")
|
||||
class WifiRepositoryImpl @Inject constructor(
|
||||
broadcastDispatcher: BroadcastDispatcher,
|
||||
connectivityManager: ConnectivityManager,
|
||||
logger: ConnectivityPipelineLogger,
|
||||
@Main mainExecutor: Executor,
|
||||
@Application scope: CoroutineScope,
|
||||
wifiManager: WifiManager?,
|
||||
) : WifiRepository {
|
||||
override val wifiNetwork: Flow<WifiNetworkModel> = conflatedCallbackFlow {
|
||||
|
||||
private val wifiStateChangeEvents: Flow<Unit> = broadcastDispatcher.broadcastFlow(
|
||||
IntentFilter(WifiManager.WIFI_STATE_CHANGED_ACTION)
|
||||
)
|
||||
.logInputChange(logger, "WIFI_STATE_CHANGED_ACTION intent")
|
||||
|
||||
private val wifiNetworkChangeEvents: MutableSharedFlow<Unit> =
|
||||
MutableSharedFlow(extraBufferCapacity = 1)
|
||||
|
||||
override val isWifiEnabled: StateFlow<Boolean> =
|
||||
if (wifiManager == null) {
|
||||
MutableStateFlow(false).asStateFlow()
|
||||
} else {
|
||||
// Because [WifiManager] doesn't expose a wifi enabled change listener, we do it
|
||||
// internally by fetching [WifiManager.isWifiEnabled] whenever we think the state may
|
||||
// have changed.
|
||||
merge(wifiNetworkChangeEvents, wifiStateChangeEvents)
|
||||
.mapLatest { wifiManager.isWifiEnabled }
|
||||
.distinctUntilChanged()
|
||||
.logOutputChange(logger, "enabled")
|
||||
.stateIn(
|
||||
scope = scope,
|
||||
started = SharingStarted.WhileSubscribed(),
|
||||
initialValue = wifiManager.isWifiEnabled
|
||||
)
|
||||
}
|
||||
|
||||
override val wifiNetwork: StateFlow<WifiNetworkModel> = conflatedCallbackFlow {
|
||||
var currentWifi: WifiNetworkModel = WIFI_NETWORK_DEFAULT
|
||||
|
||||
val callback = object : ConnectivityManager.NetworkCallback(FLAG_INCLUDE_LOCATION_INFO) {
|
||||
@@ -84,6 +121,8 @@ class WifiRepositoryImpl @Inject constructor(
|
||||
) {
|
||||
logger.logOnCapabilitiesChanged(network, networkCapabilities)
|
||||
|
||||
wifiNetworkChangeEvents.tryEmit(Unit)
|
||||
|
||||
val wifiInfo = networkCapabilitiesToWifiInfo(networkCapabilities)
|
||||
if (wifiInfo?.isPrimary == true) {
|
||||
val wifiNetworkModel = createWifiNetworkModel(
|
||||
@@ -104,6 +143,9 @@ class WifiRepositoryImpl @Inject constructor(
|
||||
|
||||
override fun onLost(network: Network) {
|
||||
logger.logOnLost(network)
|
||||
|
||||
wifiNetworkChangeEvents.tryEmit(Unit)
|
||||
|
||||
val wifi = currentWifi
|
||||
if (wifi is WifiNetworkModel.Active && wifi.networkId == network.getNetId()) {
|
||||
val newNetworkModel = WifiNetworkModel.Inactive
|
||||
@@ -132,7 +174,7 @@ class WifiRepositoryImpl @Inject constructor(
|
||||
initialValue = WIFI_NETWORK_DEFAULT
|
||||
)
|
||||
|
||||
override val wifiActivity: Flow<WifiActivityModel> =
|
||||
override val wifiActivity: StateFlow<WifiActivityModel> =
|
||||
if (wifiManager == null) {
|
||||
Log.w(SB_LOGGING_TAG, "Null WifiManager; skipping activity callback")
|
||||
flowOf(ACTIVITY_DEFAULT)
|
||||
@@ -142,13 +184,15 @@ class WifiRepositoryImpl @Inject constructor(
|
||||
logger.logInputChange("onTrafficStateChange", prettyPrintActivity(state))
|
||||
trySend(trafficStateToWifiActivityModel(state))
|
||||
}
|
||||
|
||||
trySend(ACTIVITY_DEFAULT)
|
||||
wifiManager.registerTrafficStateCallback(mainExecutor, callback)
|
||||
|
||||
awaitClose { wifiManager.unregisterTrafficStateCallback(callback) }
|
||||
}
|
||||
}
|
||||
.stateIn(
|
||||
scope,
|
||||
started = SharingStarted.WhileSubscribed(),
|
||||
initialValue = ACTIVITY_DEFAULT
|
||||
)
|
||||
|
||||
companion object {
|
||||
val ACTIVITY_DEFAULT = WifiActivityModel(hasActivityIn = false, hasActivityOut = false)
|
||||
|
||||
@@ -22,9 +22,10 @@ import com.android.systemui.statusbar.pipeline.shared.data.model.ConnectivitySlo
|
||||
import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepository
|
||||
import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel
|
||||
import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository
|
||||
import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiActivityModel
|
||||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.map
|
||||
|
||||
/**
|
||||
@@ -38,7 +39,11 @@ class WifiInteractor @Inject constructor(
|
||||
connectivityRepository: ConnectivityRepository,
|
||||
wifiRepository: WifiRepository,
|
||||
) {
|
||||
private val ssid: Flow<String?> = wifiRepository.wifiNetwork.map { info ->
|
||||
/**
|
||||
* The SSID (service set identifier) of the wifi network. Null if we don't have a network, or
|
||||
* have a network but no valid SSID.
|
||||
*/
|
||||
val ssid: Flow<String?> = wifiRepository.wifiNetwork.map { info ->
|
||||
when (info) {
|
||||
is WifiNetworkModel.Inactive -> null
|
||||
is WifiNetworkModel.CarrierMerged -> null
|
||||
@@ -51,17 +56,17 @@ class WifiInteractor @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
/** Our current enabled status. */
|
||||
val isEnabled: Flow<Boolean> = wifiRepository.isWifiEnabled
|
||||
|
||||
/** Our current wifi network. See [WifiNetworkModel]. */
|
||||
val wifiNetwork: Flow<WifiNetworkModel> = wifiRepository.wifiNetwork
|
||||
|
||||
/** Our current wifi activity. See [WifiActivityModel]. */
|
||||
val activity: StateFlow<WifiActivityModel> = wifiRepository.wifiActivity
|
||||
|
||||
/** True if we're configured to force-hide the wifi icon and false otherwise. */
|
||||
val isForceHidden: Flow<Boolean> = connectivityRepository.forceHiddenSlots.map {
|
||||
it.contains(ConnectivitySlot.WIFI)
|
||||
}
|
||||
|
||||
/** True if our wifi network has activity in (download), and false otherwise. */
|
||||
val hasActivityIn: Flow<Boolean> =
|
||||
combine(wifiRepository.wifiActivity, ssid) { activity, ssid ->
|
||||
activity.hasActivityIn && ssid != null
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,9 +41,14 @@ class WifiConstants @Inject constructor(
|
||||
/** True if we should show the activityIn/activityOut icons and false otherwise. */
|
||||
val shouldShowActivityConfig = context.resources.getBoolean(R.bool.config_showActivity)
|
||||
|
||||
/** True if we should always show the wifi icon when wifi is enabled and false otherwise. */
|
||||
val alwaysShowIconIfEnabled =
|
||||
context.resources.getBoolean(R.bool.config_showWifiIndicatorWhenEnabled)
|
||||
|
||||
override fun dump(pw: PrintWriter, args: Array<out String>) {
|
||||
pw.apply {
|
||||
println("shouldShowActivityConfig=$shouldShowActivityConfig")
|
||||
println("alwaysShowIconIfEnabled=$alwaysShowIconIfEnabled")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
@@ -26,8 +26,15 @@ import androidx.lifecycle.repeatOnLifecycle
|
||||
import com.android.systemui.R
|
||||
import com.android.systemui.common.ui.binder.IconViewBinder
|
||||
import com.android.systemui.lifecycle.repeatWhenAttached
|
||||
import com.android.systemui.statusbar.StatusBarIconView
|
||||
import com.android.systemui.statusbar.StatusBarIconView.STATE_DOT
|
||||
import com.android.systemui.statusbar.StatusBarIconView.STATE_HIDDEN
|
||||
import com.android.systemui.statusbar.StatusBarIconView.STATE_ICON
|
||||
import com.android.systemui.statusbar.phone.StatusBarLocation
|
||||
import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.LocationBasedWifiViewModel
|
||||
import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.WifiViewModel
|
||||
import kotlinx.coroutines.InternalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.collect
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.launch
|
||||
@@ -41,40 +48,111 @@ import kotlinx.coroutines.launch
|
||||
*/
|
||||
@OptIn(InternalCoroutinesApi::class)
|
||||
object WifiViewBinder {
|
||||
/** Binds the view to the view-model, continuing to update the former based on the latter. */
|
||||
|
||||
/**
|
||||
* Defines interface for an object that acts as the binding between the view and its view-model.
|
||||
*
|
||||
* Users of the [WifiViewBinder] class should use this to control the binder after it is bound.
|
||||
*/
|
||||
interface Binding {
|
||||
/** Returns true if the wifi icon should be visible and false otherwise. */
|
||||
fun getShouldIconBeVisible(): Boolean
|
||||
|
||||
/** Notifies that the visibility state has changed. */
|
||||
fun onVisibilityStateChanged(@StatusBarIconView.VisibleState state: Int)
|
||||
}
|
||||
|
||||
/**
|
||||
* Binds the view to the appropriate view-model based on the given location. The view will
|
||||
* continue to be updated following updates from the view-model.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun bind(
|
||||
view: ViewGroup,
|
||||
viewModel: WifiViewModel,
|
||||
) {
|
||||
wifiViewModel: WifiViewModel,
|
||||
location: StatusBarLocation,
|
||||
): Binding {
|
||||
return when (location) {
|
||||
StatusBarLocation.HOME -> bind(view, wifiViewModel.home)
|
||||
StatusBarLocation.KEYGUARD -> bind(view, wifiViewModel.keyguard)
|
||||
StatusBarLocation.QS -> bind(view, wifiViewModel.qs)
|
||||
}
|
||||
}
|
||||
|
||||
/** Binds the view to the view-model, continuing to update the former based on the latter. */
|
||||
@JvmStatic
|
||||
private fun bind(
|
||||
view: ViewGroup,
|
||||
viewModel: LocationBasedWifiViewModel,
|
||||
): Binding {
|
||||
val groupView = view.requireViewById<ViewGroup>(R.id.wifi_group)
|
||||
val iconView = view.requireViewById<ImageView>(R.id.wifi_signal)
|
||||
val dotView = view.requireViewById<StatusBarIconView>(R.id.status_bar_dot)
|
||||
val activityInView = view.requireViewById<ImageView>(R.id.wifi_in)
|
||||
val activityOutView = view.requireViewById<ImageView>(R.id.wifi_out)
|
||||
val activityContainerView = view.requireViewById<View>(R.id.inout_container)
|
||||
|
||||
view.isVisible = true
|
||||
iconView.isVisible = true
|
||||
|
||||
// TODO(b/238425913): We should log this visibility state.
|
||||
@StatusBarIconView.VisibleState
|
||||
val visibilityState: MutableStateFlow<Int> = MutableStateFlow(STATE_HIDDEN)
|
||||
|
||||
view.repeatWhenAttached {
|
||||
repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||
launch {
|
||||
viewModel.wifiIcon.distinctUntilChanged().collect { wifiIcon ->
|
||||
// TODO(b/238425913): Right now, if !isVisible, there's just an empty space
|
||||
// where the wifi icon would be. We need to pipe isVisible through to
|
||||
// [ModernStatusBarWifiView.isIconVisible], which is what actually makes
|
||||
// the view GONE.
|
||||
visibilityState.collect { visibilityState ->
|
||||
groupView.isVisible = visibilityState == STATE_ICON
|
||||
dotView.isVisible = visibilityState == STATE_DOT
|
||||
}
|
||||
}
|
||||
|
||||
launch {
|
||||
viewModel.wifiIcon.collect { wifiIcon ->
|
||||
view.isVisible = wifiIcon != null
|
||||
wifiIcon?.let {
|
||||
IconViewBinder.bind(wifiIcon, iconView)
|
||||
}
|
||||
wifiIcon?.let { IconViewBinder.bind(wifiIcon, iconView) }
|
||||
}
|
||||
}
|
||||
|
||||
launch {
|
||||
viewModel.tint.collect { tint ->
|
||||
iconView.imageTintList = ColorStateList.valueOf(tint)
|
||||
val tintList = ColorStateList.valueOf(tint)
|
||||
iconView.imageTintList = tintList
|
||||
activityInView.imageTintList = tintList
|
||||
activityOutView.imageTintList = tintList
|
||||
dotView.setDecorColor(tint)
|
||||
}
|
||||
}
|
||||
|
||||
launch {
|
||||
viewModel.isActivityInViewVisible.distinctUntilChanged().collect { visible ->
|
||||
activityInView.isVisible = visible
|
||||
}
|
||||
}
|
||||
|
||||
launch {
|
||||
viewModel.isActivityOutViewVisible.distinctUntilChanged().collect { visible ->
|
||||
activityOutView.isVisible = visible
|
||||
}
|
||||
}
|
||||
|
||||
launch {
|
||||
viewModel.isActivityContainerVisible.distinctUntilChanged().collect { visible ->
|
||||
activityContainerView.isVisible = visible
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(b/238425913): Hook up to [viewModel] to render actual changes to the wifi icon.
|
||||
return object : Binding {
|
||||
override fun getShouldIconBeVisible(): Boolean {
|
||||
return viewModel.wifiIcon.value != null
|
||||
}
|
||||
|
||||
override fun onVisibilityStateChanged(@StatusBarIconView.VisibleState state: Int) {
|
||||
visibilityState.value = state
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,10 +19,14 @@ package com.android.systemui.statusbar.pipeline.wifi.ui.view
|
||||
import android.content.Context
|
||||
import android.graphics.Rect
|
||||
import android.util.AttributeSet
|
||||
import android.view.Gravity
|
||||
import android.view.LayoutInflater
|
||||
import com.android.systemui.R
|
||||
import com.android.systemui.statusbar.BaseStatusBarWifiView
|
||||
import com.android.systemui.statusbar.StatusBarIconView.STATE_ICON
|
||||
import com.android.systemui.statusbar.StatusBarIconView
|
||||
import com.android.systemui.statusbar.StatusBarIconView.STATE_DOT
|
||||
import com.android.systemui.statusbar.StatusBarIconView.STATE_HIDDEN
|
||||
import com.android.systemui.statusbar.phone.StatusBarLocation
|
||||
import com.android.systemui.statusbar.pipeline.wifi.ui.binder.WifiViewBinder
|
||||
import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.WifiViewModel
|
||||
|
||||
@@ -36,6 +40,17 @@ class ModernStatusBarWifiView(
|
||||
) : BaseStatusBarWifiView(context, attrs) {
|
||||
|
||||
private lateinit var slot: String
|
||||
private lateinit var binding: WifiViewBinder.Binding
|
||||
|
||||
@StatusBarIconView.VisibleState
|
||||
private var iconVisibleState: Int = STATE_HIDDEN
|
||||
set(value) {
|
||||
if (field == value) {
|
||||
return
|
||||
}
|
||||
field = value
|
||||
binding.onVisibilityStateChanged(value)
|
||||
}
|
||||
|
||||
override fun onDarkChanged(areas: ArrayList<Rect>?, darkIntensity: Float, tint: Int) {
|
||||
// TODO(b/238425913)
|
||||
@@ -51,42 +66,64 @@ class ModernStatusBarWifiView(
|
||||
// TODO(b/238425913)
|
||||
}
|
||||
|
||||
override fun setVisibleState(state: Int, animate: Boolean) {
|
||||
// TODO(b/238425913)
|
||||
override fun setVisibleState(@StatusBarIconView.VisibleState state: Int, animate: Boolean) {
|
||||
iconVisibleState = state
|
||||
}
|
||||
|
||||
@StatusBarIconView.VisibleState
|
||||
override fun getVisibleState(): Int {
|
||||
// TODO(b/238425913)
|
||||
return STATE_ICON
|
||||
return iconVisibleState
|
||||
}
|
||||
|
||||
override fun isIconVisible(): Boolean {
|
||||
// TODO(b/238425913)
|
||||
return true
|
||||
return binding.getShouldIconBeVisible()
|
||||
}
|
||||
|
||||
/** Set the slot name for this view. */
|
||||
private fun setSlot(slotName: String) {
|
||||
this.slot = slotName
|
||||
private fun initView(
|
||||
slotName: String,
|
||||
wifiViewModel: WifiViewModel,
|
||||
location: StatusBarLocation,
|
||||
) {
|
||||
slot = slotName
|
||||
initDotView()
|
||||
binding = WifiViewBinder.bind(this, wifiViewModel, location)
|
||||
}
|
||||
|
||||
// Mostly duplicated from [com.android.systemui.statusbar.StatusBarWifiView].
|
||||
private fun initDotView() {
|
||||
// TODO(b/238425913): Could we just have this dot view be part of
|
||||
// R.layout.new_status_bar_wifi_group with a dot drawable so we don't need to inflate it
|
||||
// manually? Would that not work with animations?
|
||||
val dotView = StatusBarIconView(mContext, slot, null).also {
|
||||
it.id = R.id.status_bar_dot
|
||||
// Hard-code this view to always be in the DOT state so that whenever it's visible it
|
||||
// will show a dot
|
||||
it.visibleState = STATE_DOT
|
||||
}
|
||||
|
||||
val width = mContext.resources.getDimensionPixelSize(R.dimen.status_bar_icon_size)
|
||||
val lp = LayoutParams(width, width)
|
||||
lp.gravity = Gravity.CENTER_VERTICAL or Gravity.START
|
||||
addView(dotView, lp)
|
||||
}
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* Inflates a new instance of [ModernStatusBarWifiView], binds it to [viewModel], and
|
||||
* Inflates a new instance of [ModernStatusBarWifiView], binds it to a view model, and
|
||||
* returns it.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun constructAndBind(
|
||||
context: Context,
|
||||
slot: String,
|
||||
viewModel: WifiViewModel,
|
||||
wifiViewModel: WifiViewModel,
|
||||
location: StatusBarLocation,
|
||||
): ModernStatusBarWifiView {
|
||||
return (
|
||||
LayoutInflater.from(context).inflate(R.layout.new_status_bar_wifi_group, null)
|
||||
as ModernStatusBarWifiView
|
||||
).also {
|
||||
it.setSlot(slot)
|
||||
WifiViewBinder.bind(it, viewModel)
|
||||
it.initView(slot, wifiViewModel, location)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
@@ -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,
|
||||
)
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
)
|
||||
@@ -17,7 +17,6 @@
|
||||
package com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Color
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.annotation.VisibleForTesting
|
||||
@@ -26,6 +25,8 @@ import com.android.settingslib.AccessibilityContentDescriptions.WIFI_NO_CONNECTI
|
||||
import com.android.systemui.R
|
||||
import com.android.systemui.common.shared.model.ContentDescription
|
||||
import com.android.systemui.common.shared.model.Icon
|
||||
import com.android.systemui.dagger.SysUISingleton
|
||||
import com.android.systemui.dagger.qualifiers.Application
|
||||
import com.android.systemui.statusbar.connectivity.WifiIcons.WIFI_FULL_ICONS
|
||||
import com.android.systemui.statusbar.connectivity.WifiIcons.WIFI_NO_INTERNET_ICONS
|
||||
import com.android.systemui.statusbar.connectivity.WifiIcons.WIFI_NO_NETWORK
|
||||
@@ -35,98 +36,171 @@ import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
|
||||
import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel
|
||||
import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractor
|
||||
import com.android.systemui.statusbar.pipeline.wifi.shared.WifiConstants
|
||||
import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiActivityModel
|
||||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.emptyFlow
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
|
||||
/**
|
||||
* Models the UI state for the status bar wifi icon.
|
||||
*
|
||||
* This class exposes three view models, one per status bar location:
|
||||
* - [home]
|
||||
* - [keyguard]
|
||||
* - [qs]
|
||||
* In order to get the UI state for the wifi icon, you must use one of those view models (whichever
|
||||
* is correct for your location).
|
||||
*
|
||||
* Internally, this class maintains the current state of the wifi icon and notifies those three
|
||||
* view models of any changes.
|
||||
*/
|
||||
class WifiViewModel @Inject constructor(
|
||||
statusBarPipelineFlags: StatusBarPipelineFlags,
|
||||
private val constants: WifiConstants,
|
||||
@SysUISingleton
|
||||
class WifiViewModel
|
||||
@Inject
|
||||
constructor(
|
||||
constants: WifiConstants,
|
||||
private val context: Context,
|
||||
private val logger: ConnectivityPipelineLogger,
|
||||
private val interactor: WifiInteractor,
|
||||
logger: ConnectivityPipelineLogger,
|
||||
interactor: WifiInteractor,
|
||||
@Application private val scope: CoroutineScope,
|
||||
statusBarPipelineFlags: StatusBarPipelineFlags,
|
||||
) {
|
||||
/**
|
||||
* The drawable resource ID to use for the wifi icon. Null if we shouldn't display any icon.
|
||||
* Returns the drawable resource ID to use for the wifi icon based on the given network.
|
||||
* Null if we can't compute the icon.
|
||||
*/
|
||||
@DrawableRes
|
||||
private val iconResId: Flow<Int?> = interactor.wifiNetwork.map {
|
||||
when (it) {
|
||||
private fun WifiNetworkModel.iconResId(): Int? {
|
||||
return when (this) {
|
||||
is WifiNetworkModel.CarrierMerged -> null
|
||||
is WifiNetworkModel.Inactive -> WIFI_NO_NETWORK
|
||||
is WifiNetworkModel.Active ->
|
||||
when {
|
||||
it.level == null -> null
|
||||
it.isValidated -> WIFI_FULL_ICONS[it.level]
|
||||
else -> WIFI_NO_INTERNET_ICONS[it.level]
|
||||
this.level == null -> null
|
||||
this.isValidated -> WIFI_FULL_ICONS[this.level]
|
||||
else -> WIFI_NO_INTERNET_ICONS[this.level]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** The content description for the wifi icon. */
|
||||
private val contentDescription: Flow<ContentDescription?> = interactor.wifiNetwork.map {
|
||||
when (it) {
|
||||
/**
|
||||
* Returns the content description for the wifi icon based on the given network.
|
||||
* Null if we can't compute the content description.
|
||||
*/
|
||||
private fun WifiNetworkModel.contentDescription(): ContentDescription? {
|
||||
return when (this) {
|
||||
is WifiNetworkModel.CarrierMerged -> null
|
||||
is WifiNetworkModel.Inactive ->
|
||||
ContentDescription.Loaded(
|
||||
"${context.getString(WIFI_NO_CONNECTION)},${context.getString(NO_INTERNET)}"
|
||||
)
|
||||
is WifiNetworkModel.Active ->
|
||||
when (it.level) {
|
||||
when (this.level) {
|
||||
null -> null
|
||||
else -> {
|
||||
val levelDesc = context.getString(WIFI_CONNECTION_STRENGTH[it.level])
|
||||
val levelDesc = context.getString(WIFI_CONNECTION_STRENGTH[this.level])
|
||||
when {
|
||||
it.isValidated -> ContentDescription.Loaded(levelDesc)
|
||||
else -> ContentDescription.Loaded(
|
||||
"$levelDesc,${context.getString(NO_INTERNET)}"
|
||||
)
|
||||
this.isValidated -> ContentDescription.Loaded(levelDesc)
|
||||
else ->
|
||||
ContentDescription.Loaded(
|
||||
"$levelDesc,${context.getString(NO_INTERNET)}"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The wifi icon that should be displayed. Null if we shouldn't display any icon.
|
||||
*/
|
||||
val wifiIcon: Flow<Icon?> = combine(
|
||||
/** The wifi icon that should be displayed. Null if we shouldn't display any icon. */
|
||||
private val wifiIcon: StateFlow<Icon?> =
|
||||
combine(
|
||||
interactor.isEnabled,
|
||||
interactor.isForceHidden,
|
||||
iconResId,
|
||||
contentDescription,
|
||||
) { isForceHidden, iconResId, contentDescription ->
|
||||
when {
|
||||
isForceHidden ||
|
||||
iconResId == null ||
|
||||
iconResId <= 0 -> null
|
||||
else -> Icon.Resource(iconResId, contentDescription)
|
||||
interactor.wifiNetwork,
|
||||
) { isEnabled, isForceHidden, wifiNetwork ->
|
||||
if (!isEnabled || isForceHidden || wifiNetwork is WifiNetworkModel.CarrierMerged) {
|
||||
return@combine null
|
||||
}
|
||||
|
||||
val iconResId = wifiNetwork.iconResId() ?: return@combine null
|
||||
val icon = Icon.Resource(iconResId, wifiNetwork.contentDescription())
|
||||
|
||||
return@combine when {
|
||||
constants.alwaysShowIconIfEnabled -> icon
|
||||
wifiNetwork is WifiNetworkModel.Active && wifiNetwork.isValidated -> icon
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
.stateIn(scope, started = SharingStarted.WhileSubscribed(), initialValue = null)
|
||||
|
||||
/**
|
||||
* True if the activity in icon should be displayed and false otherwise.
|
||||
*/
|
||||
val isActivityInVisible: Flow<Boolean>
|
||||
get() =
|
||||
if (!constants.shouldShowActivityConfig) {
|
||||
flowOf(false)
|
||||
} else {
|
||||
interactor.hasActivityIn
|
||||
/** The wifi activity status. Null if we shouldn't display the activity status. */
|
||||
private val activity: Flow<WifiActivityModel?> =
|
||||
if (!constants.shouldShowActivityConfig) {
|
||||
flowOf(null)
|
||||
} else {
|
||||
combine(interactor.activity, interactor.ssid) { activity, ssid ->
|
||||
when (ssid) {
|
||||
null -> null
|
||||
else -> activity
|
||||
}
|
||||
}
|
||||
.logOutputChange(logger, "activityInVisible")
|
||||
}
|
||||
.distinctUntilChanged()
|
||||
.logOutputChange(logger, "activity")
|
||||
.stateIn(scope, started = SharingStarted.WhileSubscribed(), initialValue = null)
|
||||
|
||||
/** The tint that should be applied to the icon. */
|
||||
val tint: Flow<Int> = if (!statusBarPipelineFlags.useNewPipelineDebugColoring()) {
|
||||
emptyFlow()
|
||||
} else {
|
||||
flowOf(Color.CYAN)
|
||||
}
|
||||
private val isActivityInViewVisible: Flow<Boolean> =
|
||||
activity
|
||||
.map { it?.hasActivityIn == true }
|
||||
.stateIn(scope, started = SharingStarted.WhileSubscribed(), initialValue = false)
|
||||
|
||||
private val isActivityOutViewVisible: Flow<Boolean> =
|
||||
activity
|
||||
.map { it?.hasActivityOut == true }
|
||||
.stateIn(scope, started = SharingStarted.WhileSubscribed(), initialValue = false)
|
||||
|
||||
private val isActivityContainerVisible: Flow<Boolean> =
|
||||
combine(isActivityInViewVisible, isActivityOutViewVisible) { activityIn, activityOut ->
|
||||
activityIn || activityOut
|
||||
}
|
||||
.stateIn(scope, started = SharingStarted.WhileSubscribed(), initialValue = false)
|
||||
|
||||
/** A view model for the status bar on the home screen. */
|
||||
val home: HomeWifiViewModel =
|
||||
HomeWifiViewModel(
|
||||
statusBarPipelineFlags,
|
||||
wifiIcon,
|
||||
isActivityInViewVisible,
|
||||
isActivityOutViewVisible,
|
||||
isActivityContainerVisible,
|
||||
)
|
||||
|
||||
/** A view model for the status bar on keyguard. */
|
||||
val keyguard: KeyguardWifiViewModel =
|
||||
KeyguardWifiViewModel(
|
||||
statusBarPipelineFlags,
|
||||
wifiIcon,
|
||||
isActivityInViewVisible,
|
||||
isActivityOutViewVisible,
|
||||
isActivityContainerVisible,
|
||||
)
|
||||
|
||||
/** A view model for the status bar in quick settings. */
|
||||
val qs: QsWifiViewModel =
|
||||
QsWifiViewModel(
|
||||
statusBarPipelineFlags,
|
||||
wifiIcon,
|
||||
isActivityInViewVisible,
|
||||
isActivityOutViewVisible,
|
||||
isActivityContainerVisible,
|
||||
)
|
||||
|
||||
companion object {
|
||||
@StringRes
|
||||
|
||||
@@ -110,7 +110,7 @@ class QuickStatusBarHeaderControllerTest : SysuiTestCase() {
|
||||
`when`(qsCarrierGroupControllerBuilder.build()).thenReturn(qsCarrierGroupController)
|
||||
`when`(variableDateViewControllerFactory.create(any()))
|
||||
.thenReturn(variableDateViewController)
|
||||
`when`(iconManagerFactory.create(any())).thenReturn(iconManager)
|
||||
`when`(iconManagerFactory.create(any(), any())).thenReturn(iconManager)
|
||||
`when`(view.resources).thenReturn(mContext.resources)
|
||||
`when`(view.isAttachedToWindow).thenReturn(true)
|
||||
`when`(view.context).thenReturn(context)
|
||||
|
||||
@@ -176,7 +176,7 @@ class LargeScreenShadeHeaderControllerCombinedTest : SysuiTestCase() {
|
||||
}
|
||||
whenever(view.visibility).thenAnswer { _ -> viewVisibility }
|
||||
|
||||
whenever(iconManagerFactory.create(any())).thenReturn(iconManager)
|
||||
whenever(iconManagerFactory.create(any(), any())).thenReturn(iconManager)
|
||||
|
||||
whenever(featureFlags.isEnabled(Flags.COMBINED_QS_HEADERS)).thenReturn(true)
|
||||
whenever(featureFlags.isEnabled(Flags.NEW_HEADER)).thenReturn(true)
|
||||
|
||||
@@ -97,7 +97,7 @@ class LargeScreenShadeHeaderControllerTest : SysuiTestCase() {
|
||||
whenever(view.visibility).thenAnswer { _ -> viewVisibility }
|
||||
whenever(variableDateViewControllerFactory.create(any()))
|
||||
.thenReturn(variableDateViewController)
|
||||
whenever(iconManagerFactory.create(any())).thenReturn(iconManager)
|
||||
whenever(iconManagerFactory.create(any(), any())).thenReturn(iconManager)
|
||||
whenever(featureFlags.isEnabled(Flags.COMBINED_QS_HEADERS)).thenReturn(false)
|
||||
mLargeScreenShadeHeaderController = LargeScreenShadeHeaderController(
|
||||
view,
|
||||
|
||||
@@ -135,7 +135,7 @@ public class KeyguardStatusBarViewControllerTest extends SysuiTestCase {
|
||||
|
||||
MockitoAnnotations.initMocks(this);
|
||||
|
||||
when(mIconManagerFactory.create(any())).thenReturn(mIconManager);
|
||||
when(mIconManagerFactory.create(any(), any())).thenReturn(mIconManager);
|
||||
|
||||
allowTestableLooperAsMainThread();
|
||||
TestableLooper.get(this).runWithLooper(() -> {
|
||||
|
||||
@@ -51,8 +51,6 @@ import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import javax.inject.Provider;
|
||||
|
||||
@RunWith(AndroidTestingRunner.class)
|
||||
@RunWithLooper
|
||||
@SmallTest
|
||||
@@ -79,8 +77,9 @@ public class StatusBarIconControllerTest extends LeakCheckedTest {
|
||||
LinearLayout layout = new LinearLayout(mContext);
|
||||
TestDarkIconManager manager = new TestDarkIconManager(
|
||||
layout,
|
||||
StatusBarLocation.HOME,
|
||||
mock(StatusBarPipelineFlags.class),
|
||||
() -> mock(WifiViewModel.class),
|
||||
mock(WifiViewModel.class),
|
||||
mMobileContextProvider,
|
||||
mock(DarkIconDispatcher.class));
|
||||
testCallOnAdd_forManager(manager);
|
||||
@@ -121,13 +120,15 @@ public class StatusBarIconControllerTest extends LeakCheckedTest {
|
||||
|
||||
TestDarkIconManager(
|
||||
LinearLayout group,
|
||||
StatusBarLocation location,
|
||||
StatusBarPipelineFlags statusBarPipelineFlags,
|
||||
Provider<WifiViewModel> wifiViewModelProvider,
|
||||
WifiViewModel wifiViewModel,
|
||||
MobileContextProvider contextProvider,
|
||||
DarkIconDispatcher darkIconDispatcher) {
|
||||
super(group,
|
||||
location,
|
||||
statusBarPipelineFlags,
|
||||
wifiViewModelProvider,
|
||||
wifiViewModel,
|
||||
contextProvider,
|
||||
darkIconDispatcher);
|
||||
}
|
||||
@@ -165,8 +166,9 @@ public class StatusBarIconControllerTest extends LeakCheckedTest {
|
||||
private static class TestIconManager extends IconManager implements TestableIconManager {
|
||||
TestIconManager(ViewGroup group, MobileContextProvider contextProvider) {
|
||||
super(group,
|
||||
StatusBarLocation.HOME,
|
||||
mock(StatusBarPipelineFlags.class),
|
||||
() -> mock(WifiViewModel.class),
|
||||
mock(WifiViewModel.class),
|
||||
contextProvider);
|
||||
}
|
||||
|
||||
|
||||
@@ -431,7 +431,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
|
||||
mOperatorNameViewControllerFactory = mock(OperatorNameViewController.Factory.class);
|
||||
when(mOperatorNameViewControllerFactory.create(any()))
|
||||
.thenReturn(mOperatorNameViewController);
|
||||
when(mIconManagerFactory.create(any())).thenReturn(mIconManager);
|
||||
when(mIconManagerFactory.create(any(), any())).thenReturn(mIconManager);
|
||||
mSecureSettings = mock(SecureSettings.class);
|
||||
|
||||
setUpNotificationIconAreaController();
|
||||
|
||||
@@ -23,9 +23,16 @@ import com.android.systemui.SysuiTestCase
|
||||
import com.android.systemui.dump.DumpManager
|
||||
import com.android.systemui.log.LogBufferFactory
|
||||
import com.android.systemui.log.LogcatEchoTracker
|
||||
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.logInputChange
|
||||
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.logOutputChange
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import java.io.PrintWriter
|
||||
import java.io.StringWriter
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.junit.Test
|
||||
import org.mockito.Mockito
|
||||
import org.mockito.Mockito.mock
|
||||
@@ -64,12 +71,70 @@ class ConnectivityPipelineLoggerTest : SysuiTestCase() {
|
||||
assertThat(actualString).contains(expectedNetId)
|
||||
}
|
||||
|
||||
private val NET_1_ID = 100
|
||||
private val NET_1 = com.android.systemui.util.mockito.mock<Network>().also {
|
||||
Mockito.`when`(it.getNetId()).thenReturn(NET_1_ID)
|
||||
@Test
|
||||
fun logOutputChange_printsValuesAndNulls() = runBlocking(IMMEDIATE) {
|
||||
val flow: Flow<Int?> = flowOf(1, null, 3)
|
||||
|
||||
val job = flow
|
||||
.logOutputChange(logger, "testInts")
|
||||
.launchIn(this)
|
||||
|
||||
val stringWriter = StringWriter()
|
||||
buffer.dump(PrintWriter(stringWriter), tailLength = 0)
|
||||
val actualString = stringWriter.toString()
|
||||
|
||||
assertThat(actualString).contains("1")
|
||||
assertThat(actualString).contains("null")
|
||||
assertThat(actualString).contains("3")
|
||||
|
||||
job.cancel()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun logInputChange_unit_printsInputName() = runBlocking(IMMEDIATE) {
|
||||
val flow: Flow<Unit> = flowOf(Unit, Unit)
|
||||
|
||||
val job = flow
|
||||
.logInputChange(logger, "testInputs")
|
||||
.launchIn(this)
|
||||
|
||||
val stringWriter = StringWriter()
|
||||
buffer.dump(PrintWriter(stringWriter), tailLength = 0)
|
||||
val actualString = stringWriter.toString()
|
||||
|
||||
assertThat(actualString).contains("testInputs")
|
||||
|
||||
job.cancel()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun logInputChange_any_printsValuesAndNulls() = runBlocking(IMMEDIATE) {
|
||||
val flow: Flow<Any?> = flowOf(null, 2, "threeString")
|
||||
|
||||
val job = flow
|
||||
.logInputChange(logger, "testInputs")
|
||||
.launchIn(this)
|
||||
|
||||
val stringWriter = StringWriter()
|
||||
buffer.dump(PrintWriter(stringWriter), tailLength = 0)
|
||||
val actualString = stringWriter.toString()
|
||||
|
||||
assertThat(actualString).contains("null")
|
||||
assertThat(actualString).contains("2")
|
||||
assertThat(actualString).contains("threeString")
|
||||
|
||||
job.cancel()
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val NET_1_ID = 100
|
||||
private val NET_1 = com.android.systemui.util.mockito.mock<Network>().also {
|
||||
Mockito.`when`(it.getNetId()).thenReturn(NET_1_ID)
|
||||
}
|
||||
private val NET_1_CAPS = NetworkCapabilities.Builder()
|
||||
.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
|
||||
.addCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)
|
||||
.build()
|
||||
private val IMMEDIATE = Dispatchers.Main.immediate
|
||||
}
|
||||
private val NET_1_CAPS = NetworkCapabilities.Builder()
|
||||
.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
|
||||
.addCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)
|
||||
.build()
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -28,15 +28,17 @@ import android.net.wifi.WifiManager
|
||||
import android.net.wifi.WifiManager.TrafficStateCallback
|
||||
import androidx.test.filters.SmallTest
|
||||
import com.android.systemui.SysuiTestCase
|
||||
import com.android.systemui.broadcast.BroadcastDispatcher
|
||||
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
|
||||
import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiActivityModel
|
||||
import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel
|
||||
import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepositoryImpl.Companion.ACTIVITY_DEFAULT
|
||||
import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepositoryImpl.Companion.WIFI_NETWORK_DEFAULT
|
||||
import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiActivityModel
|
||||
import com.android.systemui.util.concurrency.FakeExecutor
|
||||
import com.android.systemui.util.mockito.any
|
||||
import com.android.systemui.util.mockito.argumentCaptor
|
||||
import com.android.systemui.util.mockito.mock
|
||||
import com.android.systemui.util.mockito.nullable
|
||||
import com.android.systemui.util.time.FakeSystemClock
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import java.util.concurrent.Executor
|
||||
@@ -44,23 +46,28 @@ import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.cancel
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.junit.After
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.mockito.ArgumentMatchers.anyInt
|
||||
import org.mockito.Mock
|
||||
import org.mockito.Mockito.verify
|
||||
import org.mockito.Mockito.`when` as whenever
|
||||
import org.mockito.MockitoAnnotations
|
||||
|
||||
@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
@SmallTest
|
||||
class WifiRepositoryImplTest : SysuiTestCase() {
|
||||
|
||||
private lateinit var underTest: WifiRepositoryImpl
|
||||
|
||||
@Mock private lateinit var broadcastDispatcher: BroadcastDispatcher
|
||||
@Mock private lateinit var logger: ConnectivityPipelineLogger
|
||||
@Mock private lateinit var connectivityManager: ConnectivityManager
|
||||
@Mock private lateinit var wifiManager: WifiManager
|
||||
@@ -70,16 +77,17 @@ class WifiRepositoryImplTest : SysuiTestCase() {
|
||||
@Before
|
||||
fun setUp() {
|
||||
MockitoAnnotations.initMocks(this)
|
||||
whenever(
|
||||
broadcastDispatcher.broadcastFlow(
|
||||
any(),
|
||||
nullable(),
|
||||
anyInt(),
|
||||
nullable(),
|
||||
)
|
||||
).thenReturn(flowOf(Unit))
|
||||
executor = FakeExecutor(FakeSystemClock())
|
||||
scope = CoroutineScope(IMMEDIATE)
|
||||
|
||||
underTest = WifiRepositoryImpl(
|
||||
connectivityManager,
|
||||
logger,
|
||||
executor,
|
||||
scope,
|
||||
wifiManager,
|
||||
)
|
||||
underTest = createRepo()
|
||||
}
|
||||
|
||||
@After
|
||||
@@ -87,6 +95,132 @@ class WifiRepositoryImplTest : SysuiTestCase() {
|
||||
scope.cancel()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun isWifiEnabled_nullWifiManager_getsFalse() = runBlocking(IMMEDIATE) {
|
||||
underTest = createRepo(wifiManagerToUse = null)
|
||||
|
||||
assertThat(underTest.isWifiEnabled.value).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun isWifiEnabled_initiallyGetsWifiManagerValue() = runBlocking(IMMEDIATE) {
|
||||
whenever(wifiManager.isWifiEnabled).thenReturn(true)
|
||||
|
||||
underTest = createRepo()
|
||||
|
||||
assertThat(underTest.isWifiEnabled.value).isTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun isWifiEnabled_networkCapabilitiesChanged_valueUpdated() = runBlocking(IMMEDIATE) {
|
||||
// We need to call launch on the flows so that they start updating
|
||||
val networkJob = underTest.wifiNetwork.launchIn(this)
|
||||
val enabledJob = underTest.isWifiEnabled.launchIn(this)
|
||||
|
||||
whenever(wifiManager.isWifiEnabled).thenReturn(true)
|
||||
getNetworkCallback().onCapabilitiesChanged(
|
||||
NETWORK, createWifiNetworkCapabilities(PRIMARY_WIFI_INFO)
|
||||
)
|
||||
|
||||
assertThat(underTest.isWifiEnabled.value).isTrue()
|
||||
|
||||
whenever(wifiManager.isWifiEnabled).thenReturn(false)
|
||||
getNetworkCallback().onCapabilitiesChanged(
|
||||
NETWORK, createWifiNetworkCapabilities(PRIMARY_WIFI_INFO)
|
||||
)
|
||||
|
||||
assertThat(underTest.isWifiEnabled.value).isFalse()
|
||||
|
||||
networkJob.cancel()
|
||||
enabledJob.cancel()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun isWifiEnabled_networkLost_valueUpdated() = runBlocking(IMMEDIATE) {
|
||||
// We need to call launch on the flows so that they start updating
|
||||
val networkJob = underTest.wifiNetwork.launchIn(this)
|
||||
val enabledJob = underTest.isWifiEnabled.launchIn(this)
|
||||
|
||||
whenever(wifiManager.isWifiEnabled).thenReturn(true)
|
||||
getNetworkCallback().onLost(NETWORK)
|
||||
|
||||
assertThat(underTest.isWifiEnabled.value).isTrue()
|
||||
|
||||
whenever(wifiManager.isWifiEnabled).thenReturn(false)
|
||||
getNetworkCallback().onLost(NETWORK)
|
||||
|
||||
assertThat(underTest.isWifiEnabled.value).isFalse()
|
||||
|
||||
networkJob.cancel()
|
||||
enabledJob.cancel()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun isWifiEnabled_intentsReceived_valueUpdated() = runBlocking(IMMEDIATE) {
|
||||
val intentFlow = MutableSharedFlow<Unit>()
|
||||
whenever(
|
||||
broadcastDispatcher.broadcastFlow(
|
||||
any(),
|
||||
nullable(),
|
||||
anyInt(),
|
||||
nullable(),
|
||||
)
|
||||
).thenReturn(intentFlow)
|
||||
underTest = createRepo()
|
||||
|
||||
val job = underTest.isWifiEnabled.launchIn(this)
|
||||
|
||||
whenever(wifiManager.isWifiEnabled).thenReturn(true)
|
||||
intentFlow.emit(Unit)
|
||||
|
||||
assertThat(underTest.isWifiEnabled.value).isTrue()
|
||||
|
||||
whenever(wifiManager.isWifiEnabled).thenReturn(false)
|
||||
intentFlow.emit(Unit)
|
||||
|
||||
assertThat(underTest.isWifiEnabled.value).isFalse()
|
||||
|
||||
job.cancel()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun isWifiEnabled_bothIntentAndNetworkUpdates_valueAlwaysUpdated() = runBlocking(IMMEDIATE) {
|
||||
val intentFlow = MutableSharedFlow<Unit>()
|
||||
whenever(
|
||||
broadcastDispatcher.broadcastFlow(
|
||||
any(),
|
||||
nullable(),
|
||||
anyInt(),
|
||||
nullable(),
|
||||
)
|
||||
).thenReturn(intentFlow)
|
||||
underTest = createRepo()
|
||||
|
||||
val networkJob = underTest.wifiNetwork.launchIn(this)
|
||||
val enabledJob = underTest.isWifiEnabled.launchIn(this)
|
||||
|
||||
whenever(wifiManager.isWifiEnabled).thenReturn(false)
|
||||
intentFlow.emit(Unit)
|
||||
assertThat(underTest.isWifiEnabled.value).isFalse()
|
||||
|
||||
whenever(wifiManager.isWifiEnabled).thenReturn(true)
|
||||
getNetworkCallback().onLost(NETWORK)
|
||||
assertThat(underTest.isWifiEnabled.value).isTrue()
|
||||
|
||||
whenever(wifiManager.isWifiEnabled).thenReturn(false)
|
||||
getNetworkCallback().onCapabilitiesChanged(
|
||||
NETWORK, createWifiNetworkCapabilities(PRIMARY_WIFI_INFO)
|
||||
)
|
||||
assertThat(underTest.isWifiEnabled.value).isFalse()
|
||||
|
||||
whenever(wifiManager.isWifiEnabled).thenReturn(true)
|
||||
intentFlow.emit(Unit)
|
||||
assertThat(underTest.isWifiEnabled.value).isTrue()
|
||||
|
||||
networkJob.cancel()
|
||||
enabledJob.cancel()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun wifiNetwork_initiallyGetsDefault() = runBlocking(IMMEDIATE) {
|
||||
var latest: WifiNetworkModel? = null
|
||||
@@ -509,13 +643,7 @@ class WifiRepositoryImplTest : SysuiTestCase() {
|
||||
|
||||
@Test
|
||||
fun wifiActivity_nullWifiManager_receivesDefault() = runBlocking(IMMEDIATE) {
|
||||
underTest = WifiRepositoryImpl(
|
||||
connectivityManager,
|
||||
logger,
|
||||
executor,
|
||||
scope,
|
||||
wifiManager = null,
|
||||
)
|
||||
underTest = createRepo(wifiManagerToUse = null)
|
||||
|
||||
var latest: WifiActivityModel? = null
|
||||
val job = underTest
|
||||
@@ -594,6 +722,17 @@ class WifiRepositoryImplTest : SysuiTestCase() {
|
||||
job.cancel()
|
||||
}
|
||||
|
||||
private fun createRepo(wifiManagerToUse: WifiManager? = wifiManager): WifiRepositoryImpl {
|
||||
return WifiRepositoryImpl(
|
||||
broadcastDispatcher,
|
||||
connectivityManager,
|
||||
logger,
|
||||
executor,
|
||||
scope,
|
||||
wifiManagerToUse,
|
||||
)
|
||||
}
|
||||
|
||||
private fun getTrafficStateCallback(): TrafficStateCallback {
|
||||
val callbackCaptor = argumentCaptor<TrafficStateCallback>()
|
||||
verify(wifiManager).registerTrafficStateCallback(any(), callbackCaptor.capture())
|
||||
|
||||
@@ -16,13 +16,14 @@
|
||||
|
||||
package com.android.systemui.statusbar.pipeline.wifi.domain.interactor
|
||||
|
||||
import android.net.wifi.WifiManager
|
||||
import androidx.test.filters.SmallTest
|
||||
import com.android.systemui.SysuiTestCase
|
||||
import com.android.systemui.statusbar.pipeline.shared.data.model.ConnectivitySlot
|
||||
import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
|
||||
import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiActivityModel
|
||||
import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel
|
||||
import com.android.systemui.statusbar.pipeline.wifi.data.repository.FakeWifiRepository
|
||||
import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiActivityModel
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
@@ -50,172 +51,129 @@ class WifiInteractorTest : SysuiTestCase() {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun hasActivityIn_noInOrOut_outputsFalse() = runBlocking(IMMEDIATE) {
|
||||
wifiRepository.setWifiNetwork(VALID_WIFI_NETWORK_MODEL)
|
||||
wifiRepository.setWifiActivity(
|
||||
WifiActivityModel(hasActivityIn = false, hasActivityOut = false)
|
||||
)
|
||||
|
||||
var latest: Boolean? = null
|
||||
val job = underTest
|
||||
.hasActivityIn
|
||||
.onEach { latest = it }
|
||||
.launchIn(this)
|
||||
|
||||
assertThat(latest).isFalse()
|
||||
|
||||
job.cancel()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun hasActivityIn_onlyOut_outputsFalse() = runBlocking(IMMEDIATE) {
|
||||
wifiRepository.setWifiNetwork(VALID_WIFI_NETWORK_MODEL)
|
||||
wifiRepository.setWifiActivity(
|
||||
WifiActivityModel(hasActivityIn = false, hasActivityOut = true)
|
||||
)
|
||||
|
||||
var latest: Boolean? = null
|
||||
val job = underTest
|
||||
.hasActivityIn
|
||||
.onEach { latest = it }
|
||||
.launchIn(this)
|
||||
|
||||
assertThat(latest).isFalse()
|
||||
|
||||
job.cancel()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun hasActivityIn_onlyIn_outputsTrue() = runBlocking(IMMEDIATE) {
|
||||
wifiRepository.setWifiNetwork(VALID_WIFI_NETWORK_MODEL)
|
||||
wifiRepository.setWifiActivity(
|
||||
WifiActivityModel(hasActivityIn = true, hasActivityOut = false)
|
||||
)
|
||||
|
||||
var latest: Boolean? = null
|
||||
val job = underTest
|
||||
.hasActivityIn
|
||||
.onEach { latest = it }
|
||||
.launchIn(this)
|
||||
|
||||
assertThat(latest).isTrue()
|
||||
|
||||
job.cancel()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun hasActivityIn_inAndOut_outputsTrue() = runBlocking(IMMEDIATE) {
|
||||
wifiRepository.setWifiNetwork(VALID_WIFI_NETWORK_MODEL)
|
||||
wifiRepository.setWifiActivity(
|
||||
WifiActivityModel(hasActivityIn = true, hasActivityOut = true)
|
||||
)
|
||||
|
||||
var latest: Boolean? = null
|
||||
val job = underTest
|
||||
.hasActivityIn
|
||||
.onEach { latest = it }
|
||||
.launchIn(this)
|
||||
|
||||
assertThat(latest).isTrue()
|
||||
|
||||
job.cancel()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun hasActivityIn_ssidNull_outputsFalse() = runBlocking(IMMEDIATE) {
|
||||
wifiRepository.setWifiNetwork(WifiNetworkModel.Active(networkId = 1, ssid = null))
|
||||
wifiRepository.setWifiActivity(
|
||||
WifiActivityModel(hasActivityIn = true, hasActivityOut = true)
|
||||
)
|
||||
|
||||
var latest: Boolean? = null
|
||||
val job = underTest
|
||||
.hasActivityIn
|
||||
.onEach { latest = it }
|
||||
.launchIn(this)
|
||||
|
||||
assertThat(latest).isFalse()
|
||||
|
||||
job.cancel()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun hasActivityIn_inactiveNetwork_outputsFalse() = runBlocking(IMMEDIATE) {
|
||||
fun ssid_inactiveNetwork_outputsNull() = runBlocking(IMMEDIATE) {
|
||||
wifiRepository.setWifiNetwork(WifiNetworkModel.Inactive)
|
||||
wifiRepository.setWifiActivity(
|
||||
WifiActivityModel(hasActivityIn = true, hasActivityOut = true)
|
||||
)
|
||||
|
||||
var latest: Boolean? = null
|
||||
var latest: String? = "default"
|
||||
val job = underTest
|
||||
.hasActivityIn
|
||||
.ssid
|
||||
.onEach { latest = it }
|
||||
.launchIn(this)
|
||||
|
||||
assertThat(latest).isFalse()
|
||||
assertThat(latest).isNull()
|
||||
|
||||
job.cancel()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun hasActivityIn_carrierMergedNetwork_outputsFalse() = runBlocking(IMMEDIATE) {
|
||||
fun ssid_carrierMergedNetwork_outputsNull() = runBlocking(IMMEDIATE) {
|
||||
wifiRepository.setWifiNetwork(WifiNetworkModel.CarrierMerged)
|
||||
wifiRepository.setWifiActivity(
|
||||
WifiActivityModel(hasActivityIn = true, hasActivityOut = true)
|
||||
)
|
||||
|
||||
var latest: Boolean? = null
|
||||
var latest: String? = "default"
|
||||
val job = underTest
|
||||
.hasActivityIn
|
||||
.ssid
|
||||
.onEach { latest = it }
|
||||
.launchIn(this)
|
||||
|
||||
assertThat(latest).isFalse()
|
||||
assertThat(latest).isNull()
|
||||
|
||||
job.cancel()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun hasActivityIn_multipleChanges_multipleOutputChanges() = runBlocking(IMMEDIATE) {
|
||||
wifiRepository.setWifiNetwork(VALID_WIFI_NETWORK_MODEL)
|
||||
fun ssid_isPasspointAccessPoint_outputsPasspointName() = runBlocking(IMMEDIATE) {
|
||||
wifiRepository.setWifiNetwork(WifiNetworkModel.Active(
|
||||
networkId = 1,
|
||||
isPasspointAccessPoint = true,
|
||||
passpointProviderFriendlyName = "friendly",
|
||||
))
|
||||
|
||||
var latest: String? = null
|
||||
val job = underTest
|
||||
.ssid
|
||||
.onEach { latest = it }
|
||||
.launchIn(this)
|
||||
|
||||
assertThat(latest).isEqualTo("friendly")
|
||||
|
||||
job.cancel()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun ssid_isOnlineSignUpForPasspoint_outputsPasspointName() = runBlocking(IMMEDIATE) {
|
||||
wifiRepository.setWifiNetwork(WifiNetworkModel.Active(
|
||||
networkId = 1,
|
||||
isOnlineSignUpForPasspointAccessPoint = true,
|
||||
passpointProviderFriendlyName = "friendly",
|
||||
))
|
||||
|
||||
var latest: String? = null
|
||||
val job = underTest
|
||||
.ssid
|
||||
.onEach { latest = it }
|
||||
.launchIn(this)
|
||||
|
||||
assertThat(latest).isEqualTo("friendly")
|
||||
|
||||
job.cancel()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun ssid_unknownSsid_outputsNull() = runBlocking(IMMEDIATE) {
|
||||
wifiRepository.setWifiNetwork(WifiNetworkModel.Active(
|
||||
networkId = 1,
|
||||
ssid = WifiManager.UNKNOWN_SSID,
|
||||
))
|
||||
|
||||
var latest: String? = "default"
|
||||
val job = underTest
|
||||
.ssid
|
||||
.onEach { latest = it }
|
||||
.launchIn(this)
|
||||
|
||||
assertThat(latest).isNull()
|
||||
|
||||
job.cancel()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun ssid_validSsid_outputsSsid() = runBlocking(IMMEDIATE) {
|
||||
wifiRepository.setWifiNetwork(WifiNetworkModel.Active(
|
||||
networkId = 1,
|
||||
ssid = "MyAwesomeWifiNetwork",
|
||||
))
|
||||
|
||||
var latest: String? = null
|
||||
val job = underTest
|
||||
.ssid
|
||||
.onEach { latest = it }
|
||||
.launchIn(this)
|
||||
|
||||
assertThat(latest).isEqualTo("MyAwesomeWifiNetwork")
|
||||
|
||||
job.cancel()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun isEnabled_matchesRepoIsEnabled() = runBlocking(IMMEDIATE) {
|
||||
var latest: Boolean? = null
|
||||
val job = underTest
|
||||
.hasActivityIn
|
||||
.onEach { latest = it }
|
||||
.launchIn(this)
|
||||
.isEnabled
|
||||
.onEach { latest = it }
|
||||
.launchIn(this)
|
||||
|
||||
// Conduct a series of changes and verify we catch each of them in succession
|
||||
wifiRepository.setWifiActivity(
|
||||
WifiActivityModel(hasActivityIn = true, hasActivityOut = false)
|
||||
)
|
||||
wifiRepository.setIsWifiEnabled(true)
|
||||
yield()
|
||||
assertThat(latest).isTrue()
|
||||
|
||||
wifiRepository.setWifiActivity(
|
||||
WifiActivityModel(hasActivityIn = false, hasActivityOut = true)
|
||||
)
|
||||
wifiRepository.setIsWifiEnabled(false)
|
||||
yield()
|
||||
assertThat(latest).isFalse()
|
||||
|
||||
wifiRepository.setWifiActivity(
|
||||
WifiActivityModel(hasActivityIn = true, hasActivityOut = true)
|
||||
)
|
||||
wifiRepository.setIsWifiEnabled(true)
|
||||
yield()
|
||||
assertThat(latest).isTrue()
|
||||
|
||||
wifiRepository.setWifiActivity(
|
||||
WifiActivityModel(hasActivityIn = true, hasActivityOut = false)
|
||||
)
|
||||
yield()
|
||||
assertThat(latest).isTrue()
|
||||
|
||||
wifiRepository.setWifiActivity(
|
||||
WifiActivityModel(hasActivityIn = false, hasActivityOut = false)
|
||||
)
|
||||
yield()
|
||||
assertThat(latest).isFalse()
|
||||
|
||||
job.cancel()
|
||||
}
|
||||
|
||||
@@ -241,6 +199,32 @@ class WifiInteractorTest : SysuiTestCase() {
|
||||
job.cancel()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun activity_matchesRepoWifiActivity() = runBlocking(IMMEDIATE) {
|
||||
var latest: WifiActivityModel? = null
|
||||
val job = underTest
|
||||
.activity
|
||||
.onEach { latest = it }
|
||||
.launchIn(this)
|
||||
|
||||
val activity1 = WifiActivityModel(hasActivityIn = true, hasActivityOut = true)
|
||||
wifiRepository.setWifiActivity(activity1)
|
||||
yield()
|
||||
assertThat(latest).isEqualTo(activity1)
|
||||
|
||||
val activity2 = WifiActivityModel(hasActivityIn = false, hasActivityOut = false)
|
||||
wifiRepository.setWifiActivity(activity2)
|
||||
yield()
|
||||
assertThat(latest).isEqualTo(activity2)
|
||||
|
||||
val activity3 = WifiActivityModel(hasActivityIn = true, hasActivityOut = false)
|
||||
wifiRepository.setWifiActivity(activity3)
|
||||
yield()
|
||||
assertThat(latest).isEqualTo(activity3)
|
||||
|
||||
job.cancel()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun isForceHidden_repoHasWifiHidden_outputsTrue() = runBlocking(IMMEDIATE) {
|
||||
connectivityRepository.setForceHiddenIcons(setOf(ConnectivitySlot.WIFI))
|
||||
@@ -270,10 +254,6 @@ class WifiInteractorTest : SysuiTestCase() {
|
||||
|
||||
job.cancel()
|
||||
}
|
||||
|
||||
companion object {
|
||||
val VALID_WIFI_NETWORK_MODEL = WifiNetworkModel.Active(networkId = 1, ssid = "AB")
|
||||
}
|
||||
}
|
||||
|
||||
private val IMMEDIATE = Dispatchers.Main.immediate
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -29,25 +29,29 @@ import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
|
||||
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
|
||||
import com.android.systemui.statusbar.pipeline.shared.data.model.ConnectivitySlot
|
||||
import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
|
||||
import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiActivityModel
|
||||
import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel
|
||||
import com.android.systemui.statusbar.pipeline.wifi.data.repository.FakeWifiRepository
|
||||
import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractor
|
||||
import com.android.systemui.statusbar.pipeline.wifi.shared.WifiConstants
|
||||
import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiActivityModel
|
||||
import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.WifiViewModel.Companion.NO_INTERNET
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.cancel
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.yield
|
||||
import org.junit.After
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.mockito.Mock
|
||||
import org.mockito.Mockito.`when` as whenever
|
||||
import org.mockito.MockitoAnnotations
|
||||
|
||||
@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
@SmallTest
|
||||
class WifiViewModelTest : SysuiTestCase() {
|
||||
@@ -60,34 +64,64 @@ class WifiViewModelTest : SysuiTestCase() {
|
||||
private lateinit var connectivityRepository: FakeConnectivityRepository
|
||||
private lateinit var wifiRepository: FakeWifiRepository
|
||||
private lateinit var interactor: WifiInteractor
|
||||
private lateinit var scope: CoroutineScope
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
MockitoAnnotations.initMocks(this)
|
||||
connectivityRepository = FakeConnectivityRepository()
|
||||
wifiRepository = FakeWifiRepository()
|
||||
wifiRepository.setIsWifiEnabled(true)
|
||||
interactor = WifiInteractor(connectivityRepository, wifiRepository)
|
||||
scope = CoroutineScope(IMMEDIATE)
|
||||
createAndSetViewModel()
|
||||
}
|
||||
|
||||
underTest = WifiViewModel(
|
||||
statusBarPipelineFlags,
|
||||
constants,
|
||||
context,
|
||||
logger,
|
||||
interactor
|
||||
)
|
||||
@After
|
||||
fun tearDown() {
|
||||
scope.cancel()
|
||||
}
|
||||
|
||||
// Note on testing: [WifiViewModel] exposes 3 different instances of
|
||||
// [LocationBasedWifiViewModel]. In practice, these 3 different instances will get the exact
|
||||
// same data for icon, activity, etc. flows. So, most of these tests will test just one of the
|
||||
// instances. There are also some tests that verify all 3 instances received the same data.
|
||||
|
||||
@Test
|
||||
fun wifiIcon_notEnabled_outputsNull() = runBlocking(IMMEDIATE) {
|
||||
wifiRepository.setIsWifiEnabled(false)
|
||||
|
||||
// Start as non-null so we can verify we got the update
|
||||
var latest: Icon? = Icon.Resource(0, null)
|
||||
val job = underTest
|
||||
.home
|
||||
.wifiIcon
|
||||
.onEach { latest = it }
|
||||
.launchIn(this)
|
||||
|
||||
wifiRepository.setWifiNetwork(WifiNetworkModel.Active(NETWORK_ID, level = 2))
|
||||
yield()
|
||||
|
||||
assertThat(latest).isNull()
|
||||
|
||||
job.cancel()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun wifiIcon_forceHidden_outputsNull() = runBlocking(IMMEDIATE) {
|
||||
connectivityRepository.setForceHiddenIcons(setOf(ConnectivitySlot.WIFI))
|
||||
wifiRepository.setWifiNetwork(WifiNetworkModel.Active(NETWORK_ID, level = 2))
|
||||
|
||||
var latest: Icon? = null
|
||||
// Start as non-null so we can verify we got the update
|
||||
var latest: Icon? = Icon.Resource(0, null)
|
||||
val job = underTest
|
||||
.home
|
||||
.wifiIcon
|
||||
.onEach { latest = it }
|
||||
.launchIn(this)
|
||||
|
||||
wifiRepository.setWifiNetwork(WifiNetworkModel.Active(NETWORK_ID, level = 2))
|
||||
yield()
|
||||
|
||||
assertThat(latest).isNull()
|
||||
|
||||
job.cancel()
|
||||
@@ -96,28 +130,59 @@ class WifiViewModelTest : SysuiTestCase() {
|
||||
@Test
|
||||
fun wifiIcon_notForceHidden_outputsVisible() = runBlocking(IMMEDIATE) {
|
||||
connectivityRepository.setForceHiddenIcons(setOf())
|
||||
wifiRepository.setWifiNetwork(WifiNetworkModel.Active(NETWORK_ID, level = 2))
|
||||
|
||||
var latest: Icon? = null
|
||||
val job = underTest
|
||||
.home
|
||||
.wifiIcon
|
||||
.onEach { latest = it }
|
||||
.launchIn(this)
|
||||
|
||||
wifiRepository.setWifiNetwork(
|
||||
WifiNetworkModel.Active(NETWORK_ID, isValidated = true, level = 2)
|
||||
)
|
||||
yield()
|
||||
|
||||
assertThat(latest).isInstanceOf(Icon.Resource::class.java)
|
||||
|
||||
job.cancel()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun wifiIcon_inactiveNetwork_outputsNoNetworkIcon() = runBlocking(IMMEDIATE) {
|
||||
fun wifiIcon_inactiveNetwork_alwaysShowFalse_outputsNull() = runBlocking(IMMEDIATE) {
|
||||
whenever(constants.alwaysShowIconIfEnabled).thenReturn(false)
|
||||
createAndSetViewModel()
|
||||
|
||||
// Start as non-null so we can verify we got the update
|
||||
var latest: Icon? = Icon.Resource(0, null)
|
||||
val job = underTest
|
||||
.home
|
||||
.wifiIcon
|
||||
.onEach { latest = it }
|
||||
.launchIn(this)
|
||||
|
||||
wifiRepository.setWifiNetwork(WifiNetworkModel.Inactive)
|
||||
yield()
|
||||
|
||||
assertThat(latest).isNull()
|
||||
|
||||
job.cancel()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun wifiIcon_inactiveNetwork_alwaysShowTrue_outputsNoNetworkIcon() = runBlocking(IMMEDIATE) {
|
||||
whenever(constants.alwaysShowIconIfEnabled).thenReturn(true)
|
||||
createAndSetViewModel()
|
||||
|
||||
var latest: Icon? = null
|
||||
val job = underTest
|
||||
.wifiIcon
|
||||
.onEach { latest = it }
|
||||
.launchIn(this)
|
||||
.home
|
||||
.wifiIcon
|
||||
.onEach { latest = it }
|
||||
.launchIn(this)
|
||||
|
||||
wifiRepository.setWifiNetwork(WifiNetworkModel.Inactive)
|
||||
yield()
|
||||
|
||||
assertThat(latest).isInstanceOf(Icon.Resource::class.java)
|
||||
val icon = latest as Icon.Resource
|
||||
@@ -132,14 +197,22 @@ class WifiViewModelTest : SysuiTestCase() {
|
||||
|
||||
@Test
|
||||
fun wifiIcon_carrierMergedNetwork_outputsNull() = runBlocking(IMMEDIATE) {
|
||||
wifiRepository.setWifiNetwork(WifiNetworkModel.CarrierMerged)
|
||||
// Even when we should always show the icon
|
||||
whenever(constants.alwaysShowIconIfEnabled).thenReturn(true)
|
||||
createAndSetViewModel()
|
||||
|
||||
var latest: Icon? = null
|
||||
var latest: Icon? = Icon.Resource(0, null)
|
||||
val job = underTest
|
||||
.home
|
||||
.wifiIcon
|
||||
.onEach { latest = it }
|
||||
.launchIn(this)
|
||||
|
||||
// WHEN we have a carrier merged network
|
||||
wifiRepository.setWifiNetwork(WifiNetworkModel.CarrierMerged)
|
||||
yield()
|
||||
|
||||
// THEN we override the alwaysShow boolean and still don't show the icon
|
||||
assertThat(latest).isNull()
|
||||
|
||||
job.cancel()
|
||||
@@ -147,14 +220,22 @@ class WifiViewModelTest : SysuiTestCase() {
|
||||
|
||||
@Test
|
||||
fun wifiIcon_isActiveNullLevel_outputsNull() = runBlocking(IMMEDIATE) {
|
||||
wifiRepository.setWifiNetwork(WifiNetworkModel.Active(NETWORK_ID, level = null))
|
||||
// Even when we should always show the icon
|
||||
whenever(constants.alwaysShowIconIfEnabled).thenReturn(true)
|
||||
createAndSetViewModel()
|
||||
|
||||
var latest: Icon? = null
|
||||
var latest: Icon? = Icon.Resource(0, null)
|
||||
val job = underTest
|
||||
.home
|
||||
.wifiIcon
|
||||
.onEach { latest = it }
|
||||
.launchIn(this)
|
||||
|
||||
// WHEN we have a null level
|
||||
wifiRepository.setWifiNetwork(WifiNetworkModel.Active(NETWORK_ID, level = null))
|
||||
yield()
|
||||
|
||||
// THEN we override the alwaysShow boolean and still don't show the icon
|
||||
assertThat(latest).isNull()
|
||||
|
||||
job.cancel()
|
||||
@@ -162,22 +243,23 @@ class WifiViewModelTest : SysuiTestCase() {
|
||||
|
||||
@Test
|
||||
fun wifiIcon_isActiveAndValidated_level1_outputsFull1Icon() = runBlocking(IMMEDIATE) {
|
||||
val level = 1
|
||||
|
||||
wifiRepository.setWifiNetwork(
|
||||
WifiNetworkModel.Active(
|
||||
NETWORK_ID,
|
||||
isValidated = true,
|
||||
level = level
|
||||
)
|
||||
)
|
||||
|
||||
var latest: Icon? = null
|
||||
val job = underTest
|
||||
.home
|
||||
.wifiIcon
|
||||
.onEach { latest = it }
|
||||
.launchIn(this)
|
||||
|
||||
val level = 1
|
||||
wifiRepository.setWifiNetwork(
|
||||
WifiNetworkModel.Active(
|
||||
NETWORK_ID,
|
||||
isValidated = true,
|
||||
level,
|
||||
)
|
||||
)
|
||||
yield()
|
||||
|
||||
assertThat(latest).isInstanceOf(Icon.Resource::class.java)
|
||||
val icon = latest as Icon.Resource
|
||||
assertThat(icon.res).isEqualTo(WIFI_FULL_ICONS[level])
|
||||
@@ -190,23 +272,49 @@ class WifiViewModelTest : SysuiTestCase() {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun wifiIcon_isActiveAndNotValidated_level4_outputsEmpty4Icon() = runBlocking(IMMEDIATE) {
|
||||
val level = 4
|
||||
fun wifiIcon_isActiveAndNotValidated_alwaysShowFalse_outputsNull() = runBlocking(IMMEDIATE) {
|
||||
whenever(constants.alwaysShowIconIfEnabled).thenReturn(false)
|
||||
createAndSetViewModel()
|
||||
|
||||
wifiRepository.setWifiNetwork(
|
||||
WifiNetworkModel.Active(
|
||||
NETWORK_ID,
|
||||
isValidated = false,
|
||||
level = level
|
||||
)
|
||||
)
|
||||
|
||||
var latest: Icon? = null
|
||||
var latest: Icon? = Icon.Resource(0, null)
|
||||
val job = underTest
|
||||
.home
|
||||
.wifiIcon
|
||||
.onEach { latest = it }
|
||||
.launchIn(this)
|
||||
|
||||
wifiRepository.setWifiNetwork(
|
||||
WifiNetworkModel.Active(NETWORK_ID, isValidated = false, level = 4,)
|
||||
)
|
||||
yield()
|
||||
|
||||
assertThat(latest).isNull()
|
||||
|
||||
job.cancel()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun wifiIcon_isActiveAndNotValidated_alwaysShowTrue_outputsIcon() = runBlocking(IMMEDIATE) {
|
||||
whenever(constants.alwaysShowIconIfEnabled).thenReturn(true)
|
||||
createAndSetViewModel()
|
||||
|
||||
var latest: Icon? = null
|
||||
val job = underTest
|
||||
.home
|
||||
.wifiIcon
|
||||
.onEach { latest = it }
|
||||
.launchIn(this)
|
||||
|
||||
val level = 4
|
||||
wifiRepository.setWifiNetwork(
|
||||
WifiNetworkModel.Active(
|
||||
NETWORK_ID,
|
||||
isValidated = false,
|
||||
level,
|
||||
)
|
||||
)
|
||||
yield()
|
||||
|
||||
assertThat(latest).isInstanceOf(Icon.Resource::class.java)
|
||||
val icon = latest as Icon.Resource
|
||||
assertThat(icon.res).isEqualTo(WIFI_NO_INTERNET_ICONS[level])
|
||||
@@ -219,68 +327,398 @@ class WifiViewModelTest : SysuiTestCase() {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun activityInVisible_showActivityConfigFalse_outputsFalse() = runBlocking(IMMEDIATE) {
|
||||
whenever(constants.shouldShowActivityConfig).thenReturn(false)
|
||||
wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK)
|
||||
fun wifiIcon_allLocationViewModelsReceiveSameData() = runBlocking(IMMEDIATE) {
|
||||
var latestHome: Icon? = null
|
||||
val jobHome = underTest
|
||||
.home
|
||||
.wifiIcon
|
||||
.onEach { latestHome = it }
|
||||
.launchIn(this)
|
||||
|
||||
var latest: Boolean? = null
|
||||
val job = underTest
|
||||
.isActivityInVisible
|
||||
.onEach { latest = it }
|
||||
.launchIn(this)
|
||||
var latestKeyguard: Icon? = null
|
||||
val jobKeyguard = underTest
|
||||
.keyguard
|
||||
.wifiIcon
|
||||
.onEach { latestKeyguard = it }
|
||||
.launchIn(this)
|
||||
|
||||
// Verify that on launch, we receive a false.
|
||||
assertThat(latest).isFalse()
|
||||
var latestQs: Icon? = null
|
||||
val jobQs = underTest
|
||||
.qs
|
||||
.wifiIcon
|
||||
.onEach { latestQs = it }
|
||||
.launchIn(this)
|
||||
|
||||
job.cancel()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun activityInVisible_showActivityConfigFalse_noUpdatesReceived() = runBlocking(IMMEDIATE) {
|
||||
whenever(constants.shouldShowActivityConfig).thenReturn(false)
|
||||
wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK)
|
||||
|
||||
var latest: Boolean? = null
|
||||
val job = underTest
|
||||
.isActivityInVisible
|
||||
.onEach { latest = it }
|
||||
.launchIn(this)
|
||||
|
||||
// Update the repo to have activityIn
|
||||
wifiRepository.setWifiActivity(
|
||||
WifiActivityModel(hasActivityIn = true, hasActivityOut = false)
|
||||
wifiRepository.setWifiNetwork(
|
||||
WifiNetworkModel.Active(
|
||||
NETWORK_ID,
|
||||
isValidated = true,
|
||||
level = 1
|
||||
)
|
||||
)
|
||||
yield()
|
||||
|
||||
// Verify that we didn't update to activityIn=true (because our config is false)
|
||||
assertThat(latest).isFalse()
|
||||
assertThat(latestHome).isInstanceOf(Icon.Resource::class.java)
|
||||
assertThat(latestHome).isEqualTo(latestKeyguard)
|
||||
assertThat(latestKeyguard).isEqualTo(latestQs)
|
||||
|
||||
job.cancel()
|
||||
jobHome.cancel()
|
||||
jobKeyguard.cancel()
|
||||
jobQs.cancel()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun activityInVisible_showActivityConfigTrue_outputsUpdate() = runBlocking(IMMEDIATE) {
|
||||
fun activity_showActivityConfigFalse_outputsFalse() = runBlocking(IMMEDIATE) {
|
||||
whenever(constants.shouldShowActivityConfig).thenReturn(false)
|
||||
createAndSetViewModel()
|
||||
wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK)
|
||||
|
||||
var activityIn: Boolean? = null
|
||||
val activityInJob = underTest
|
||||
.home
|
||||
.isActivityInViewVisible
|
||||
.onEach { activityIn = it }
|
||||
.launchIn(this)
|
||||
|
||||
var activityOut: Boolean? = null
|
||||
val activityOutJob = underTest
|
||||
.home
|
||||
.isActivityOutViewVisible
|
||||
.onEach { activityOut = it }
|
||||
.launchIn(this)
|
||||
|
||||
var activityContainer: Boolean? = null
|
||||
val activityContainerJob = underTest
|
||||
.home
|
||||
.isActivityContainerVisible
|
||||
.onEach { activityContainer = it }
|
||||
.launchIn(this)
|
||||
|
||||
// Verify that on launch, we receive false.
|
||||
assertThat(activityIn).isFalse()
|
||||
assertThat(activityOut).isFalse()
|
||||
assertThat(activityContainer).isFalse()
|
||||
|
||||
activityInJob.cancel()
|
||||
activityOutJob.cancel()
|
||||
activityContainerJob.cancel()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun activity_showActivityConfigFalse_noUpdatesReceived() = runBlocking(IMMEDIATE) {
|
||||
whenever(constants.shouldShowActivityConfig).thenReturn(false)
|
||||
createAndSetViewModel()
|
||||
wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK)
|
||||
|
||||
var activityIn: Boolean? = null
|
||||
val activityInJob = underTest
|
||||
.home
|
||||
.isActivityInViewVisible
|
||||
.onEach { activityIn = it }
|
||||
.launchIn(this)
|
||||
|
||||
var activityOut: Boolean? = null
|
||||
val activityOutJob = underTest
|
||||
.home
|
||||
.isActivityOutViewVisible
|
||||
.onEach { activityOut = it }
|
||||
.launchIn(this)
|
||||
|
||||
var activityContainer: Boolean? = null
|
||||
val activityContainerJob = underTest
|
||||
.home
|
||||
.isActivityContainerVisible
|
||||
.onEach { activityContainer = it }
|
||||
.launchIn(this)
|
||||
|
||||
// WHEN we update the repo to have activity
|
||||
val activity = WifiActivityModel(hasActivityIn = true, hasActivityOut = true)
|
||||
wifiRepository.setWifiActivity(activity)
|
||||
yield()
|
||||
|
||||
// THEN we didn't update to the new activity (because our config is false)
|
||||
assertThat(activityIn).isFalse()
|
||||
assertThat(activityOut).isFalse()
|
||||
assertThat(activityContainer).isFalse()
|
||||
|
||||
activityInJob.cancel()
|
||||
activityOutJob.cancel()
|
||||
activityContainerJob.cancel()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun activity_nullSsid_outputsFalse() = runBlocking(IMMEDIATE) {
|
||||
whenever(constants.shouldShowActivityConfig).thenReturn(true)
|
||||
createAndSetViewModel()
|
||||
|
||||
wifiRepository.setWifiNetwork(WifiNetworkModel.Active(NETWORK_ID, ssid = null))
|
||||
|
||||
var activityIn: Boolean? = null
|
||||
val activityInJob = underTest
|
||||
.home
|
||||
.isActivityInViewVisible
|
||||
.onEach { activityIn = it }
|
||||
.launchIn(this)
|
||||
|
||||
var activityOut: Boolean? = null
|
||||
val activityOutJob = underTest
|
||||
.home
|
||||
.isActivityOutViewVisible
|
||||
.onEach { activityOut = it }
|
||||
.launchIn(this)
|
||||
|
||||
var activityContainer: Boolean? = null
|
||||
val activityContainerJob = underTest
|
||||
.home
|
||||
.isActivityContainerVisible
|
||||
.onEach { activityContainer = it }
|
||||
.launchIn(this)
|
||||
|
||||
// WHEN we update the repo to have activity
|
||||
val activity = WifiActivityModel(hasActivityIn = true, hasActivityOut = true)
|
||||
wifiRepository.setWifiActivity(activity)
|
||||
yield()
|
||||
|
||||
// THEN we still output false because our network's SSID is null
|
||||
assertThat(activityIn).isFalse()
|
||||
assertThat(activityOut).isFalse()
|
||||
assertThat(activityContainer).isFalse()
|
||||
|
||||
activityInJob.cancel()
|
||||
activityOutJob.cancel()
|
||||
activityContainerJob.cancel()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun activity_allLocationViewModelsReceiveSameData() = runBlocking(IMMEDIATE) {
|
||||
whenever(constants.shouldShowActivityConfig).thenReturn(true)
|
||||
createAndSetViewModel()
|
||||
wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK)
|
||||
|
||||
var latestHome: Boolean? = null
|
||||
val jobHome = underTest
|
||||
.home
|
||||
.isActivityInViewVisible
|
||||
.onEach { latestHome = it }
|
||||
.launchIn(this)
|
||||
|
||||
var latestKeyguard: Boolean? = null
|
||||
val jobKeyguard = underTest
|
||||
.keyguard
|
||||
.isActivityInViewVisible
|
||||
.onEach { latestKeyguard = it }
|
||||
.launchIn(this)
|
||||
|
||||
var latestQs: Boolean? = null
|
||||
val jobQs = underTest
|
||||
.qs
|
||||
.isActivityInViewVisible
|
||||
.onEach { latestQs = it }
|
||||
.launchIn(this)
|
||||
|
||||
val activity = WifiActivityModel(hasActivityIn = true, hasActivityOut = true)
|
||||
wifiRepository.setWifiActivity(activity)
|
||||
yield()
|
||||
|
||||
assertThat(latestHome).isTrue()
|
||||
assertThat(latestKeyguard).isTrue()
|
||||
assertThat(latestQs).isTrue()
|
||||
|
||||
jobHome.cancel()
|
||||
jobKeyguard.cancel()
|
||||
jobQs.cancel()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun activityIn_hasActivityInTrue_outputsTrue() = runBlocking(IMMEDIATE) {
|
||||
whenever(constants.shouldShowActivityConfig).thenReturn(true)
|
||||
createAndSetViewModel()
|
||||
wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK)
|
||||
|
||||
var latest: Boolean? = null
|
||||
val job = underTest
|
||||
.isActivityInVisible
|
||||
.onEach { latest = it }
|
||||
.launchIn(this)
|
||||
.home
|
||||
.isActivityInViewVisible
|
||||
.onEach { latest = it }
|
||||
.launchIn(this)
|
||||
|
||||
// Update the repo to have activityIn
|
||||
wifiRepository.setWifiActivity(
|
||||
WifiActivityModel(hasActivityIn = true, hasActivityOut = false)
|
||||
)
|
||||
val activity = WifiActivityModel(hasActivityIn = true, hasActivityOut = false)
|
||||
wifiRepository.setWifiActivity(activity)
|
||||
yield()
|
||||
|
||||
// Verify that we updated to activityIn=true
|
||||
assertThat(latest).isTrue()
|
||||
|
||||
job.cancel()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun activityIn_hasActivityInFalse_outputsFalse() = runBlocking(IMMEDIATE) {
|
||||
whenever(constants.shouldShowActivityConfig).thenReturn(true)
|
||||
createAndSetViewModel()
|
||||
wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK)
|
||||
|
||||
var latest: Boolean? = null
|
||||
val job = underTest
|
||||
.home
|
||||
.isActivityInViewVisible
|
||||
.onEach { latest = it }
|
||||
.launchIn(this)
|
||||
|
||||
val activity = WifiActivityModel(hasActivityIn = false, hasActivityOut = true)
|
||||
wifiRepository.setWifiActivity(activity)
|
||||
yield()
|
||||
|
||||
assertThat(latest).isFalse()
|
||||
|
||||
job.cancel()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun activityOut_hasActivityOutTrue_outputsTrue() = runBlocking(IMMEDIATE) {
|
||||
whenever(constants.shouldShowActivityConfig).thenReturn(true)
|
||||
createAndSetViewModel()
|
||||
wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK)
|
||||
|
||||
var latest: Boolean? = null
|
||||
val job = underTest
|
||||
.home
|
||||
.isActivityOutViewVisible
|
||||
.onEach { latest = it }
|
||||
.launchIn(this)
|
||||
|
||||
val activity = WifiActivityModel(hasActivityIn = false, hasActivityOut = true)
|
||||
wifiRepository.setWifiActivity(activity)
|
||||
yield()
|
||||
|
||||
assertThat(latest).isTrue()
|
||||
|
||||
job.cancel()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun activityOut_hasActivityOutFalse_outputsFalse() = runBlocking(IMMEDIATE) {
|
||||
whenever(constants.shouldShowActivityConfig).thenReturn(true)
|
||||
createAndSetViewModel()
|
||||
wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK)
|
||||
|
||||
var latest: Boolean? = null
|
||||
val job = underTest
|
||||
.home
|
||||
.isActivityOutViewVisible
|
||||
.onEach { latest = it }
|
||||
.launchIn(this)
|
||||
|
||||
val activity = WifiActivityModel(hasActivityIn = true, hasActivityOut = false)
|
||||
wifiRepository.setWifiActivity(activity)
|
||||
yield()
|
||||
|
||||
assertThat(latest).isFalse()
|
||||
|
||||
job.cancel()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun activityContainer_hasActivityInTrue_outputsTrue() = runBlocking(IMMEDIATE) {
|
||||
whenever(constants.shouldShowActivityConfig).thenReturn(true)
|
||||
createAndSetViewModel()
|
||||
wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK)
|
||||
|
||||
var latest: Boolean? = null
|
||||
val job = underTest
|
||||
.home
|
||||
.isActivityContainerVisible
|
||||
.onEach { latest = it }
|
||||
.launchIn(this)
|
||||
|
||||
val activity = WifiActivityModel(hasActivityIn = true, hasActivityOut = false)
|
||||
wifiRepository.setWifiActivity(activity)
|
||||
yield()
|
||||
|
||||
assertThat(latest).isTrue()
|
||||
|
||||
job.cancel()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun activityContainer_hasActivityOutTrue_outputsTrue() = runBlocking(IMMEDIATE) {
|
||||
whenever(constants.shouldShowActivityConfig).thenReturn(true)
|
||||
createAndSetViewModel()
|
||||
wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK)
|
||||
|
||||
var latest: Boolean? = null
|
||||
val job = underTest
|
||||
.home
|
||||
.isActivityContainerVisible
|
||||
.onEach { latest = it }
|
||||
.launchIn(this)
|
||||
|
||||
val activity = WifiActivityModel(hasActivityIn = false, hasActivityOut = true)
|
||||
wifiRepository.setWifiActivity(activity)
|
||||
yield()
|
||||
|
||||
assertThat(latest).isTrue()
|
||||
|
||||
job.cancel()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun activityContainer_inAndOutTrue_outputsTrue() = runBlocking(IMMEDIATE) {
|
||||
whenever(constants.shouldShowActivityConfig).thenReturn(true)
|
||||
createAndSetViewModel()
|
||||
wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK)
|
||||
|
||||
var latest: Boolean? = null
|
||||
val job = underTest
|
||||
.home
|
||||
.isActivityContainerVisible
|
||||
.onEach { latest = it }
|
||||
.launchIn(this)
|
||||
|
||||
val activity = WifiActivityModel(hasActivityIn = true, hasActivityOut = true)
|
||||
wifiRepository.setWifiActivity(activity)
|
||||
yield()
|
||||
|
||||
assertThat(latest).isTrue()
|
||||
|
||||
job.cancel()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun activityContainer_inAndOutFalse_outputsFalse() = runBlocking(IMMEDIATE) {
|
||||
whenever(constants.shouldShowActivityConfig).thenReturn(true)
|
||||
createAndSetViewModel()
|
||||
wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK)
|
||||
|
||||
var latest: Boolean? = null
|
||||
val job = underTest
|
||||
.home
|
||||
.isActivityContainerVisible
|
||||
.onEach { latest = it }
|
||||
.launchIn(this)
|
||||
|
||||
val activity = WifiActivityModel(hasActivityIn = false, hasActivityOut = false)
|
||||
wifiRepository.setWifiActivity(activity)
|
||||
yield()
|
||||
|
||||
assertThat(latest).isFalse()
|
||||
|
||||
job.cancel()
|
||||
}
|
||||
|
||||
private fun createAndSetViewModel() {
|
||||
// [WifiViewModel] creates its flows as soon as it's instantiated, and some of those flow
|
||||
// creations rely on certain config values that we mock out in individual tests. This method
|
||||
// allows tests to create the view model only after those configs are correctly set up.
|
||||
underTest = WifiViewModel(
|
||||
constants,
|
||||
context,
|
||||
logger,
|
||||
interactor,
|
||||
scope,
|
||||
statusBarPipelineFlags,
|
||||
)
|
||||
}
|
||||
|
||||
private fun ContentDescription.getAsString(): String? {
|
||||
return when (this) {
|
||||
is ContentDescription.Loaded -> this.description
|
||||
|
||||
Reference in New Issue
Block a user