Merge changes I723cfc04,I1eba794e,I9d421a4a,Icd9c2b14,Idee192ff, ... into tm-qpr-dev
* changes: [SB Refactor] Only show wifi icon if (1) the network is active and validated; or (2) we're configured to always show it when enabled. [SB Refactor] Listen to the WIFI_STATE_CHANGED_ACTION broadcasts and re-fetch `isWifiEnabled` whenever it happens. [SB Refactor] Connect the old pipeline and new pipeline visibility calculations together for wifi. [SB Refactor] Add an annotation for the visibility states. [SB Refactor] Add tracking for wifi enabled state and pipe it through to the UI. [SB Refactor] Turn the wifi ViewModel into an @SysUISingleton and instead create separate view models per location. [SB Refactor] Display the activity in and out icons using the new pipeline. [SB Refactor] Expose the wifi repository flows as StateFlows.
This commit is contained in:
committed by
Android (Google) Code Review
commit
d547dfa98e
@@ -131,6 +131,9 @@
|
|||||||
<!-- For StatusIconContainer to tag its icon views -->
|
<!-- For StatusIconContainer to tag its icon views -->
|
||||||
<item type="id" name="status_bar_view_state_tag" />
|
<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 -->
|
<!-- Default display cutout on the physical top of screen -->
|
||||||
<item type="id" name="display_cutout" />
|
<item type="id" name="display_cutout" />
|
||||||
<item type="id" name="display_cutout_left" />
|
<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.qs.dagger.QSScope;
|
||||||
import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider;
|
import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider;
|
||||||
import com.android.systemui.statusbar.phone.StatusBarIconController;
|
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.StatusIconContainer;
|
||||||
import com.android.systemui.statusbar.policy.Clock;
|
import com.android.systemui.statusbar.policy.Clock;
|
||||||
import com.android.systemui.statusbar.policy.VariableDateViewController;
|
import com.android.systemui.statusbar.policy.VariableDateViewController;
|
||||||
@@ -104,7 +105,7 @@ class QuickStatusBarHeaderController extends ViewController<QuickStatusBarHeader
|
|||||||
mView.requireViewById(R.id.date_clock)
|
mView.requireViewById(R.id.date_clock)
|
||||||
);
|
);
|
||||||
|
|
||||||
mIconManager = tintedIconManagerFactory.create(mIconContainer);
|
mIconManager = tintedIconManagerFactory.create(mIconContainer, StatusBarLocation.QS);
|
||||||
mDemoModeReceiver = new ClockDemoModeReceiver(mClockView);
|
mDemoModeReceiver = new ClockDemoModeReceiver(mClockView);
|
||||||
mColorExtractor = colorExtractor;
|
mColorExtractor = colorExtractor;
|
||||||
mOnColorsChangedListener = (extractor, which) -> {
|
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.shade.LargeScreenShadeHeaderController.Companion.QS_HEADER_CONSTRAINT
|
||||||
import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider
|
import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider
|
||||||
import com.android.systemui.statusbar.phone.StatusBarIconController
|
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.StatusIconContainer
|
||||||
import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent.CentralSurfacesScope
|
import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent.CentralSurfacesScope
|
||||||
import com.android.systemui.statusbar.phone.dagger.StatusBarViewModule.LARGE_SCREEN_BATTERY_CONTROLLER
|
import com.android.systemui.statusbar.phone.dagger.StatusBarViewModule.LARGE_SCREEN_BATTERY_CONTROLLER
|
||||||
@@ -261,7 +262,7 @@ class LargeScreenShadeHeaderController @Inject constructor(
|
|||||||
batteryMeterViewController.ignoreTunerUpdates()
|
batteryMeterViewController.ignoreTunerUpdates()
|
||||||
batteryIcon.setPercentShowMode(BatteryMeterView.MODE_ESTIMATE)
|
batteryIcon.setPercentShowMode(BatteryMeterView.MODE_ESTIMATE)
|
||||||
|
|
||||||
iconManager = tintedIconManagerFactory.create(iconContainer)
|
iconManager = tintedIconManagerFactory.create(iconContainer, StatusBarLocation.QS)
|
||||||
iconManager.setTint(
|
iconManager.setTint(
|
||||||
Utils.getColorAttrDefaultColor(header.context, android.R.attr.textColorPrimary)
|
Utils.getColorAttrDefaultColor(header.context, android.R.attr.textColorPrimary)
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import android.animation.Animator;
|
|||||||
import android.animation.AnimatorListenerAdapter;
|
import android.animation.AnimatorListenerAdapter;
|
||||||
import android.animation.ObjectAnimator;
|
import android.animation.ObjectAnimator;
|
||||||
import android.animation.ValueAnimator;
|
import android.animation.ValueAnimator;
|
||||||
|
import android.annotation.IntDef;
|
||||||
import android.app.ActivityManager;
|
import android.app.ActivityManager;
|
||||||
import android.app.Notification;
|
import android.app.Notification;
|
||||||
import android.content.Context;
|
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.statusbar.notification.NotificationUtils;
|
||||||
import com.android.systemui.util.drawable.DrawableSize;
|
import com.android.systemui.util.drawable.DrawableSize;
|
||||||
|
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
import java.text.NumberFormat;
|
import java.text.NumberFormat;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
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_DOT = 1;
|
||||||
public static final int STATE_HIDDEN = 2;
|
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 String TAG = "StatusBarIconView";
|
||||||
private static final Property<StatusBarIconView, Float> ICON_APPEAR_AMOUNT
|
private static final Property<StatusBarIconView, Float> ICON_APPEAR_AMOUNT
|
||||||
= new FloatProperty<StatusBarIconView>("iconAppearAmount") {
|
= 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 final Paint mDotPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||||
private float mDotRadius;
|
private float mDotRadius;
|
||||||
private int mStaticDotRadius;
|
private int mStaticDotRadius;
|
||||||
|
@StatusBarIconView.VisibleState
|
||||||
private int mVisibleState = STATE_ICON;
|
private int mVisibleState = STATE_ICON;
|
||||||
private float mIconAppearAmount = 1.0f;
|
private float mIconAppearAmount = 1.0f;
|
||||||
private ObjectAnimator mIconAppearAnimator;
|
private ObjectAnimator mIconAppearAnimator;
|
||||||
@@ -746,11 +754,12 @@ public class StatusBarIconView extends AnimatedImageView implements StatusIconDi
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setVisibleState(int state) {
|
public void setVisibleState(@StatusBarIconView.VisibleState int state) {
|
||||||
setVisibleState(state, true /* animate */, null /* endRunnable */);
|
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);
|
setVisibleState(state, animate, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -862,6 +871,7 @@ public class StatusBarIconView extends AnimatedImageView implements StatusIconDi
|
|||||||
return mIconAppearAmount;
|
return mIconAppearAmount;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@StatusBarIconView.VisibleState
|
||||||
public int getVisibleState() {
|
public int getVisibleState() {
|
||||||
return mVisibleState;
|
return mVisibleState;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -59,7 +59,8 @@ public class StatusBarMobileView extends FrameLayout implements DarkReceiver,
|
|||||||
private ImageView mOut;
|
private ImageView mOut;
|
||||||
private ImageView mMobile, mMobileType, mMobileRoaming;
|
private ImageView mMobile, mMobileType, mMobileRoaming;
|
||||||
private View mMobileRoamingSpace;
|
private View mMobileRoamingSpace;
|
||||||
private int mVisibleState = -1;
|
@StatusBarIconView.VisibleState
|
||||||
|
private int mVisibleState = STATE_HIDDEN;
|
||||||
private DualToneHandler mDualToneHandler;
|
private DualToneHandler mDualToneHandler;
|
||||||
private boolean mForceHidden;
|
private boolean mForceHidden;
|
||||||
|
|
||||||
@@ -271,7 +272,7 @@ public class StatusBarMobileView extends FrameLayout implements DarkReceiver,
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setVisibleState(int state, boolean animate) {
|
public void setVisibleState(@StatusBarIconView.VisibleState int state, boolean animate) {
|
||||||
if (state == mVisibleState) {
|
if (state == mVisibleState) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -312,6 +313,7 @@ public class StatusBarMobileView extends FrameLayout implements DarkReceiver,
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@StatusBarIconView.VisibleState
|
||||||
public int getVisibleState() {
|
public int getVisibleState() {
|
||||||
return mVisibleState;
|
return mVisibleState;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -55,7 +55,8 @@ public class StatusBarWifiView extends BaseStatusBarWifiView implements DarkRece
|
|||||||
private View mAirplaneSpacer;
|
private View mAirplaneSpacer;
|
||||||
private WifiIconState mState;
|
private WifiIconState mState;
|
||||||
private String mSlot;
|
private String mSlot;
|
||||||
private int mVisibleState = -1;
|
@StatusBarIconView.VisibleState
|
||||||
|
private int mVisibleState = STATE_HIDDEN;
|
||||||
|
|
||||||
public static StatusBarWifiView fromContext(Context context, String slot) {
|
public static StatusBarWifiView fromContext(Context context, String slot) {
|
||||||
LayoutInflater inflater = LayoutInflater.from(context);
|
LayoutInflater inflater = LayoutInflater.from(context);
|
||||||
@@ -107,7 +108,7 @@ public class StatusBarWifiView extends BaseStatusBarWifiView implements DarkRece
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setVisibleState(int state, boolean animate) {
|
public void setVisibleState(@StatusBarIconView.VisibleState int state, boolean animate) {
|
||||||
if (state == mVisibleState) {
|
if (state == mVisibleState) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -131,6 +132,7 @@ public class StatusBarWifiView extends BaseStatusBarWifiView implements DarkRece
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@StatusBarIconView.VisibleState
|
||||||
public int getVisibleState() {
|
public int getVisibleState() {
|
||||||
return mVisibleState;
|
return mVisibleState;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,14 +22,32 @@ public interface StatusIconDisplayable extends DarkReceiver {
|
|||||||
String getSlot();
|
String getSlot();
|
||||||
void setStaticDrawableColor(int color);
|
void setStaticDrawableColor(int color);
|
||||||
void setDecorColor(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);
|
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();
|
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();
|
boolean isIconVisible();
|
||||||
|
|
||||||
default boolean isIconBlocked() {
|
default boolean isIconBlocked() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -352,8 +352,8 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat
|
|||||||
mKeyguardUpdateMonitor.registerCallback(mKeyguardUpdateMonitorCallback);
|
mKeyguardUpdateMonitor.registerCallback(mKeyguardUpdateMonitorCallback);
|
||||||
mDisableStateTracker.startTracking(mCommandQueue, mView.getDisplay().getDisplayId());
|
mDisableStateTracker.startTracking(mCommandQueue, mView.getDisplay().getDisplayId());
|
||||||
if (mTintedIconManager == null) {
|
if (mTintedIconManager == null) {
|
||||||
mTintedIconManager =
|
mTintedIconManager = mTintedIconManagerFactory.create(
|
||||||
mTintedIconManagerFactory.create(mView.findViewById(R.id.statusIcons));
|
mView.findViewById(R.id.statusIcons), StatusBarLocation.KEYGUARD);
|
||||||
mTintedIconManager.setBlockList(getBlockedIcons());
|
mTintedIconManager.setBlockList(getBlockedIcons());
|
||||||
mStatusBarIconController.addIconGroup(mTintedIconManager);
|
mStatusBarIconController.addIconGroup(mTintedIconManager);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,7 +56,6 @@ import java.util.ArrayList;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.inject.Provider;
|
|
||||||
|
|
||||||
public interface StatusBarIconController {
|
public interface StatusBarIconController {
|
||||||
|
|
||||||
@@ -139,13 +138,15 @@ public interface StatusBarIconController {
|
|||||||
|
|
||||||
public DarkIconManager(
|
public DarkIconManager(
|
||||||
LinearLayout linearLayout,
|
LinearLayout linearLayout,
|
||||||
|
StatusBarLocation location,
|
||||||
StatusBarPipelineFlags statusBarPipelineFlags,
|
StatusBarPipelineFlags statusBarPipelineFlags,
|
||||||
Provider<WifiViewModel> wifiViewModelProvider,
|
WifiViewModel wifiViewModel,
|
||||||
MobileContextProvider mobileContextProvider,
|
MobileContextProvider mobileContextProvider,
|
||||||
DarkIconDispatcher darkIconDispatcher) {
|
DarkIconDispatcher darkIconDispatcher) {
|
||||||
super(linearLayout,
|
super(linearLayout,
|
||||||
|
location,
|
||||||
statusBarPipelineFlags,
|
statusBarPipelineFlags,
|
||||||
wifiViewModelProvider,
|
wifiViewModel,
|
||||||
mobileContextProvider);
|
mobileContextProvider);
|
||||||
mIconHPadding = mContext.getResources().getDimensionPixelSize(
|
mIconHPadding = mContext.getResources().getDimensionPixelSize(
|
||||||
R.dimen.status_bar_icon_padding);
|
R.dimen.status_bar_icon_padding);
|
||||||
@@ -204,27 +205,28 @@ public interface StatusBarIconController {
|
|||||||
@SysUISingleton
|
@SysUISingleton
|
||||||
public static class Factory {
|
public static class Factory {
|
||||||
private final StatusBarPipelineFlags mStatusBarPipelineFlags;
|
private final StatusBarPipelineFlags mStatusBarPipelineFlags;
|
||||||
private final Provider<WifiViewModel> mWifiViewModelProvider;
|
private final WifiViewModel mWifiViewModel;
|
||||||
private final MobileContextProvider mMobileContextProvider;
|
private final MobileContextProvider mMobileContextProvider;
|
||||||
private final DarkIconDispatcher mDarkIconDispatcher;
|
private final DarkIconDispatcher mDarkIconDispatcher;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public Factory(
|
public Factory(
|
||||||
StatusBarPipelineFlags statusBarPipelineFlags,
|
StatusBarPipelineFlags statusBarPipelineFlags,
|
||||||
Provider<WifiViewModel> wifiViewModelProvider,
|
WifiViewModel wifiViewModel,
|
||||||
MobileContextProvider mobileContextProvider,
|
MobileContextProvider mobileContextProvider,
|
||||||
DarkIconDispatcher darkIconDispatcher) {
|
DarkIconDispatcher darkIconDispatcher) {
|
||||||
mStatusBarPipelineFlags = statusBarPipelineFlags;
|
mStatusBarPipelineFlags = statusBarPipelineFlags;
|
||||||
mWifiViewModelProvider = wifiViewModelProvider;
|
mWifiViewModel = wifiViewModel;
|
||||||
mMobileContextProvider = mobileContextProvider;
|
mMobileContextProvider = mobileContextProvider;
|
||||||
mDarkIconDispatcher = darkIconDispatcher;
|
mDarkIconDispatcher = darkIconDispatcher;
|
||||||
}
|
}
|
||||||
|
|
||||||
public DarkIconManager create(LinearLayout group) {
|
public DarkIconManager create(LinearLayout group, StatusBarLocation location) {
|
||||||
return new DarkIconManager(
|
return new DarkIconManager(
|
||||||
group,
|
group,
|
||||||
|
location,
|
||||||
mStatusBarPipelineFlags,
|
mStatusBarPipelineFlags,
|
||||||
mWifiViewModelProvider,
|
mWifiViewModel,
|
||||||
mMobileContextProvider,
|
mMobileContextProvider,
|
||||||
mDarkIconDispatcher);
|
mDarkIconDispatcher);
|
||||||
}
|
}
|
||||||
@@ -239,12 +241,14 @@ public interface StatusBarIconController {
|
|||||||
|
|
||||||
public TintedIconManager(
|
public TintedIconManager(
|
||||||
ViewGroup group,
|
ViewGroup group,
|
||||||
|
StatusBarLocation location,
|
||||||
StatusBarPipelineFlags statusBarPipelineFlags,
|
StatusBarPipelineFlags statusBarPipelineFlags,
|
||||||
Provider<WifiViewModel> wifiViewModelProvider,
|
WifiViewModel wifiViewModel,
|
||||||
MobileContextProvider mobileContextProvider) {
|
MobileContextProvider mobileContextProvider) {
|
||||||
super(group,
|
super(group,
|
||||||
|
location,
|
||||||
statusBarPipelineFlags,
|
statusBarPipelineFlags,
|
||||||
wifiViewModelProvider,
|
wifiViewModel,
|
||||||
mobileContextProvider);
|
mobileContextProvider);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -278,24 +282,25 @@ public interface StatusBarIconController {
|
|||||||
@SysUISingleton
|
@SysUISingleton
|
||||||
public static class Factory {
|
public static class Factory {
|
||||||
private final StatusBarPipelineFlags mStatusBarPipelineFlags;
|
private final StatusBarPipelineFlags mStatusBarPipelineFlags;
|
||||||
private final Provider<WifiViewModel> mWifiViewModelProvider;
|
private final WifiViewModel mWifiViewModel;
|
||||||
private final MobileContextProvider mMobileContextProvider;
|
private final MobileContextProvider mMobileContextProvider;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public Factory(
|
public Factory(
|
||||||
StatusBarPipelineFlags statusBarPipelineFlags,
|
StatusBarPipelineFlags statusBarPipelineFlags,
|
||||||
Provider<WifiViewModel> wifiViewModelProvider,
|
WifiViewModel wifiViewModel,
|
||||||
MobileContextProvider mobileContextProvider) {
|
MobileContextProvider mobileContextProvider) {
|
||||||
mStatusBarPipelineFlags = statusBarPipelineFlags;
|
mStatusBarPipelineFlags = statusBarPipelineFlags;
|
||||||
mWifiViewModelProvider = wifiViewModelProvider;
|
mWifiViewModel = wifiViewModel;
|
||||||
mMobileContextProvider = mobileContextProvider;
|
mMobileContextProvider = mobileContextProvider;
|
||||||
}
|
}
|
||||||
|
|
||||||
public TintedIconManager create(ViewGroup group) {
|
public TintedIconManager create(ViewGroup group, StatusBarLocation location) {
|
||||||
return new TintedIconManager(
|
return new TintedIconManager(
|
||||||
group,
|
group,
|
||||||
|
location,
|
||||||
mStatusBarPipelineFlags,
|
mStatusBarPipelineFlags,
|
||||||
mWifiViewModelProvider,
|
mWifiViewModel,
|
||||||
mMobileContextProvider);
|
mMobileContextProvider);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -306,8 +311,9 @@ public interface StatusBarIconController {
|
|||||||
*/
|
*/
|
||||||
class IconManager implements DemoModeCommandReceiver {
|
class IconManager implements DemoModeCommandReceiver {
|
||||||
protected final ViewGroup mGroup;
|
protected final ViewGroup mGroup;
|
||||||
|
private final StatusBarLocation mLocation;
|
||||||
private final StatusBarPipelineFlags mStatusBarPipelineFlags;
|
private final StatusBarPipelineFlags mStatusBarPipelineFlags;
|
||||||
private final Provider<WifiViewModel> mWifiViewModelProvider;
|
private final WifiViewModel mWifiViewModel;
|
||||||
private final MobileContextProvider mMobileContextProvider;
|
private final MobileContextProvider mMobileContextProvider;
|
||||||
protected final Context mContext;
|
protected final Context mContext;
|
||||||
protected final int mIconSize;
|
protected final int mIconSize;
|
||||||
@@ -324,12 +330,14 @@ public interface StatusBarIconController {
|
|||||||
|
|
||||||
public IconManager(
|
public IconManager(
|
||||||
ViewGroup group,
|
ViewGroup group,
|
||||||
|
StatusBarLocation location,
|
||||||
StatusBarPipelineFlags statusBarPipelineFlags,
|
StatusBarPipelineFlags statusBarPipelineFlags,
|
||||||
Provider<WifiViewModel> wifiViewModelProvider,
|
WifiViewModel wifiViewModel,
|
||||||
MobileContextProvider mobileContextProvider) {
|
MobileContextProvider mobileContextProvider) {
|
||||||
mGroup = group;
|
mGroup = group;
|
||||||
|
mLocation = location;
|
||||||
mStatusBarPipelineFlags = statusBarPipelineFlags;
|
mStatusBarPipelineFlags = statusBarPipelineFlags;
|
||||||
mWifiViewModelProvider = wifiViewModelProvider;
|
mWifiViewModel = wifiViewModel;
|
||||||
mMobileContextProvider = mobileContextProvider;
|
mMobileContextProvider = mobileContextProvider;
|
||||||
mContext = group.getContext();
|
mContext = group.getContext();
|
||||||
mIconSize = mContext.getResources().getDimensionPixelSize(
|
mIconSize = mContext.getResources().getDimensionPixelSize(
|
||||||
@@ -446,7 +454,7 @@ public interface StatusBarIconController {
|
|||||||
|
|
||||||
private ModernStatusBarWifiView onCreateModernStatusBarWifiView(String slot) {
|
private ModernStatusBarWifiView onCreateModernStatusBarWifiView(String slot) {
|
||||||
return ModernStatusBarWifiView.constructAndBind(
|
return ModernStatusBarWifiView.constructAndBind(
|
||||||
mContext, slot, mWifiViewModelProvider.get());
|
mContext, slot, mWifiViewModel, mLocation);
|
||||||
}
|
}
|
||||||
|
|
||||||
private StatusBarMobileView onCreateStatusBarMobileView(int subId, String slot) {
|
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.StatusBarHideIconsForBouncerManager;
|
||||||
import com.android.systemui.statusbar.phone.StatusBarIconController;
|
import com.android.systemui.statusbar.phone.StatusBarIconController;
|
||||||
import com.android.systemui.statusbar.phone.StatusBarIconController.DarkIconManager;
|
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.StatusBarLocationPublisher;
|
||||||
import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentComponent;
|
import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentComponent;
|
||||||
import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentComponent.Startable;
|
import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentComponent.Startable;
|
||||||
@@ -250,7 +251,8 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue
|
|||||||
mStatusBar.restoreHierarchyState(
|
mStatusBar.restoreHierarchyState(
|
||||||
savedInstanceState.getSparseParcelableArray(EXTRA_PANEL_STATE));
|
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);
|
mDarkIconManager.setShouldLog(true);
|
||||||
updateBlockedIcons();
|
updateBlockedIcons();
|
||||||
mStatusBarIconController.addIconGroup(mDarkIconManager);
|
mStatusBarIconController.addIconGroup(mDarkIconManager);
|
||||||
|
|||||||
@@ -31,6 +31,20 @@ import kotlinx.coroutines.flow.onEach
|
|||||||
class ConnectivityPipelineLogger @Inject constructor(
|
class ConnectivityPipelineLogger @Inject constructor(
|
||||||
@StatusBarConnectivityLog private val buffer: LogBuffer,
|
@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.
|
* Logs a change in one of the **raw inputs** to the connectivity pipeline.
|
||||||
*/
|
*/
|
||||||
@@ -127,13 +141,37 @@ class ConnectivityPipelineLogger @Inject constructor(
|
|||||||
companion object {
|
companion object {
|
||||||
const val SB_LOGGING_TAG = "SbConnectivity"
|
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.
|
* 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.
|
* @param prettyPrint an optional function to transform the value into a readable string.
|
||||||
* [toString] is used if no custom function is provided.
|
* [toString] is used if no custom function is provided.
|
||||||
*/
|
*/
|
||||||
fun <T : Any> Flow<T>.logOutputChange(
|
fun <T> Flow<T>.logOutputChange(
|
||||||
logger: ConnectivityPipelineLogger,
|
logger: ConnectivityPipelineLogger,
|
||||||
outputParamName: String,
|
outputParamName: String,
|
||||||
prettyPrint: (T) -> String = { it.toString() }
|
prettyPrint: (T) -> String = { it.toString() }
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
package com.android.systemui.statusbar.pipeline.wifi.data.repository
|
package com.android.systemui.statusbar.pipeline.wifi.data.repository
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
|
import android.content.IntentFilter
|
||||||
import android.net.ConnectivityManager
|
import android.net.ConnectivityManager
|
||||||
import android.net.Network
|
import android.net.Network
|
||||||
import android.net.NetworkCapabilities
|
import android.net.NetworkCapabilities
|
||||||
@@ -30,51 +31,87 @@ import android.net.wifi.WifiManager
|
|||||||
import android.net.wifi.WifiManager.TrafficStateCallback
|
import android.net.wifi.WifiManager.TrafficStateCallback
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import com.android.settingslib.Utils
|
import com.android.settingslib.Utils
|
||||||
|
import com.android.systemui.broadcast.BroadcastDispatcher
|
||||||
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
|
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
|
||||||
import com.android.systemui.dagger.SysUISingleton
|
import com.android.systemui.dagger.SysUISingleton
|
||||||
import com.android.systemui.dagger.qualifiers.Application
|
import com.android.systemui.dagger.qualifiers.Application
|
||||||
import com.android.systemui.dagger.qualifiers.Main
|
import com.android.systemui.dagger.qualifiers.Main
|
||||||
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
|
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
|
||||||
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.SB_LOGGING_TAG
|
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.data.model.WifiNetworkModel
|
||||||
|
import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiActivityModel
|
||||||
import java.util.concurrent.Executor
|
import java.util.concurrent.Executor
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
import kotlinx.coroutines.channels.awaitClose
|
import kotlinx.coroutines.channels.awaitClose
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.SharingStarted
|
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.flowOf
|
||||||
|
import kotlinx.coroutines.flow.mapLatest
|
||||||
|
import kotlinx.coroutines.flow.merge
|
||||||
import kotlinx.coroutines.flow.stateIn
|
import kotlinx.coroutines.flow.stateIn
|
||||||
|
|
||||||
/**
|
/** Provides data related to the wifi state. */
|
||||||
* Provides data related to the wifi state.
|
|
||||||
*/
|
|
||||||
interface WifiRepository {
|
interface WifiRepository {
|
||||||
/**
|
/** Observable for the current wifi enabled status. */
|
||||||
* Observable for the current wifi network.
|
val isWifiEnabled: StateFlow<Boolean>
|
||||||
*/
|
|
||||||
val wifiNetwork: Flow<WifiNetworkModel>
|
|
||||||
|
|
||||||
/**
|
/** Observable for the current wifi network. */
|
||||||
* Observable for the current wifi network activity.
|
val wifiNetwork: StateFlow<WifiNetworkModel>
|
||||||
*/
|
|
||||||
val wifiActivity: Flow<WifiActivityModel>
|
/** Observable for the current wifi network activity. */
|
||||||
|
val wifiActivity: StateFlow<WifiActivityModel>
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Real implementation of [WifiRepository]. */
|
/** Real implementation of [WifiRepository]. */
|
||||||
|
@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
|
||||||
@OptIn(ExperimentalCoroutinesApi::class)
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
@SysUISingleton
|
@SysUISingleton
|
||||||
@SuppressLint("MissingPermission")
|
@SuppressLint("MissingPermission")
|
||||||
class WifiRepositoryImpl @Inject constructor(
|
class WifiRepositoryImpl @Inject constructor(
|
||||||
|
broadcastDispatcher: BroadcastDispatcher,
|
||||||
connectivityManager: ConnectivityManager,
|
connectivityManager: ConnectivityManager,
|
||||||
logger: ConnectivityPipelineLogger,
|
logger: ConnectivityPipelineLogger,
|
||||||
@Main mainExecutor: Executor,
|
@Main mainExecutor: Executor,
|
||||||
@Application scope: CoroutineScope,
|
@Application scope: CoroutineScope,
|
||||||
wifiManager: WifiManager?,
|
wifiManager: WifiManager?,
|
||||||
) : WifiRepository {
|
) : 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
|
var currentWifi: WifiNetworkModel = WIFI_NETWORK_DEFAULT
|
||||||
|
|
||||||
val callback = object : ConnectivityManager.NetworkCallback(FLAG_INCLUDE_LOCATION_INFO) {
|
val callback = object : ConnectivityManager.NetworkCallback(FLAG_INCLUDE_LOCATION_INFO) {
|
||||||
@@ -84,6 +121,8 @@ class WifiRepositoryImpl @Inject constructor(
|
|||||||
) {
|
) {
|
||||||
logger.logOnCapabilitiesChanged(network, networkCapabilities)
|
logger.logOnCapabilitiesChanged(network, networkCapabilities)
|
||||||
|
|
||||||
|
wifiNetworkChangeEvents.tryEmit(Unit)
|
||||||
|
|
||||||
val wifiInfo = networkCapabilitiesToWifiInfo(networkCapabilities)
|
val wifiInfo = networkCapabilitiesToWifiInfo(networkCapabilities)
|
||||||
if (wifiInfo?.isPrimary == true) {
|
if (wifiInfo?.isPrimary == true) {
|
||||||
val wifiNetworkModel = createWifiNetworkModel(
|
val wifiNetworkModel = createWifiNetworkModel(
|
||||||
@@ -104,6 +143,9 @@ class WifiRepositoryImpl @Inject constructor(
|
|||||||
|
|
||||||
override fun onLost(network: Network) {
|
override fun onLost(network: Network) {
|
||||||
logger.logOnLost(network)
|
logger.logOnLost(network)
|
||||||
|
|
||||||
|
wifiNetworkChangeEvents.tryEmit(Unit)
|
||||||
|
|
||||||
val wifi = currentWifi
|
val wifi = currentWifi
|
||||||
if (wifi is WifiNetworkModel.Active && wifi.networkId == network.getNetId()) {
|
if (wifi is WifiNetworkModel.Active && wifi.networkId == network.getNetId()) {
|
||||||
val newNetworkModel = WifiNetworkModel.Inactive
|
val newNetworkModel = WifiNetworkModel.Inactive
|
||||||
@@ -132,7 +174,7 @@ class WifiRepositoryImpl @Inject constructor(
|
|||||||
initialValue = WIFI_NETWORK_DEFAULT
|
initialValue = WIFI_NETWORK_DEFAULT
|
||||||
)
|
)
|
||||||
|
|
||||||
override val wifiActivity: Flow<WifiActivityModel> =
|
override val wifiActivity: StateFlow<WifiActivityModel> =
|
||||||
if (wifiManager == null) {
|
if (wifiManager == null) {
|
||||||
Log.w(SB_LOGGING_TAG, "Null WifiManager; skipping activity callback")
|
Log.w(SB_LOGGING_TAG, "Null WifiManager; skipping activity callback")
|
||||||
flowOf(ACTIVITY_DEFAULT)
|
flowOf(ACTIVITY_DEFAULT)
|
||||||
@@ -142,13 +184,15 @@ class WifiRepositoryImpl @Inject constructor(
|
|||||||
logger.logInputChange("onTrafficStateChange", prettyPrintActivity(state))
|
logger.logInputChange("onTrafficStateChange", prettyPrintActivity(state))
|
||||||
trySend(trafficStateToWifiActivityModel(state))
|
trySend(trafficStateToWifiActivityModel(state))
|
||||||
}
|
}
|
||||||
|
|
||||||
trySend(ACTIVITY_DEFAULT)
|
|
||||||
wifiManager.registerTrafficStateCallback(mainExecutor, callback)
|
wifiManager.registerTrafficStateCallback(mainExecutor, callback)
|
||||||
|
|
||||||
awaitClose { wifiManager.unregisterTrafficStateCallback(callback) }
|
awaitClose { wifiManager.unregisterTrafficStateCallback(callback) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.stateIn(
|
||||||
|
scope,
|
||||||
|
started = SharingStarted.WhileSubscribed(),
|
||||||
|
initialValue = ACTIVITY_DEFAULT
|
||||||
|
)
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val ACTIVITY_DEFAULT = WifiActivityModel(hasActivityIn = false, hasActivityOut = false)
|
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.shared.data.repository.ConnectivityRepository
|
||||||
import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel
|
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.data.repository.WifiRepository
|
||||||
|
import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiActivityModel
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.combine
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -38,7 +39,11 @@ class WifiInteractor @Inject constructor(
|
|||||||
connectivityRepository: ConnectivityRepository,
|
connectivityRepository: ConnectivityRepository,
|
||||||
wifiRepository: WifiRepository,
|
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) {
|
when (info) {
|
||||||
is WifiNetworkModel.Inactive -> null
|
is WifiNetworkModel.Inactive -> null
|
||||||
is WifiNetworkModel.CarrierMerged -> 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]. */
|
/** Our current wifi network. See [WifiNetworkModel]. */
|
||||||
val wifiNetwork: Flow<WifiNetworkModel> = wifiRepository.wifiNetwork
|
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. */
|
/** True if we're configured to force-hide the wifi icon and false otherwise. */
|
||||||
val isForceHidden: Flow<Boolean> = connectivityRepository.forceHiddenSlots.map {
|
val isForceHidden: Flow<Boolean> = connectivityRepository.forceHiddenSlots.map {
|
||||||
it.contains(ConnectivitySlot.WIFI)
|
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. */
|
/** True if we should show the activityIn/activityOut icons and false otherwise. */
|
||||||
val shouldShowActivityConfig = context.resources.getBoolean(R.bool.config_showActivity)
|
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>) {
|
override fun dump(pw: PrintWriter, args: Array<out String>) {
|
||||||
pw.apply {
|
pw.apply {
|
||||||
println("shouldShowActivityConfig=$shouldShowActivityConfig")
|
println("shouldShowActivityConfig=$shouldShowActivityConfig")
|
||||||
|
println("alwaysShowIconIfEnabled=$alwaysShowIconIfEnabled")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,11 +14,9 @@
|
|||||||
* limitations under the License.
|
* 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(
|
data class WifiActivityModel(
|
||||||
/** True if the wifi has activity in (download). */
|
/** True if the wifi has activity in (download). */
|
||||||
val hasActivityIn: Boolean,
|
val hasActivityIn: Boolean,
|
||||||
@@ -26,8 +26,15 @@ import androidx.lifecycle.repeatOnLifecycle
|
|||||||
import com.android.systemui.R
|
import com.android.systemui.R
|
||||||
import com.android.systemui.common.ui.binder.IconViewBinder
|
import com.android.systemui.common.ui.binder.IconViewBinder
|
||||||
import com.android.systemui.lifecycle.repeatWhenAttached
|
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 com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.WifiViewModel
|
||||||
import kotlinx.coroutines.InternalCoroutinesApi
|
import kotlinx.coroutines.InternalCoroutinesApi
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.collect
|
import kotlinx.coroutines.flow.collect
|
||||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
@@ -41,40 +48,111 @@ import kotlinx.coroutines.launch
|
|||||||
*/
|
*/
|
||||||
@OptIn(InternalCoroutinesApi::class)
|
@OptIn(InternalCoroutinesApi::class)
|
||||||
object WifiViewBinder {
|
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
|
@JvmStatic
|
||||||
fun bind(
|
fun bind(
|
||||||
view: ViewGroup,
|
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 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
|
view.isVisible = true
|
||||||
iconView.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 {
|
view.repeatWhenAttached {
|
||||||
repeatOnLifecycle(Lifecycle.State.STARTED) {
|
repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||||
launch {
|
launch {
|
||||||
viewModel.wifiIcon.distinctUntilChanged().collect { wifiIcon ->
|
visibilityState.collect { visibilityState ->
|
||||||
// TODO(b/238425913): Right now, if !isVisible, there's just an empty space
|
groupView.isVisible = visibilityState == STATE_ICON
|
||||||
// where the wifi icon would be. We need to pipe isVisible through to
|
dotView.isVisible = visibilityState == STATE_DOT
|
||||||
// [ModernStatusBarWifiView.isIconVisible], which is what actually makes
|
|
||||||
// the view GONE.
|
|
||||||
view.isVisible = wifiIcon != null
|
|
||||||
wifiIcon?.let {
|
|
||||||
IconViewBinder.bind(wifiIcon, iconView)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
launch {
|
||||||
|
viewModel.wifiIcon.collect { wifiIcon ->
|
||||||
|
view.isVisible = wifiIcon != null
|
||||||
|
wifiIcon?.let { IconViewBinder.bind(wifiIcon, iconView) }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
launch {
|
launch {
|
||||||
viewModel.tint.collect { tint ->
|
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.content.Context
|
||||||
import android.graphics.Rect
|
import android.graphics.Rect
|
||||||
import android.util.AttributeSet
|
import android.util.AttributeSet
|
||||||
|
import android.view.Gravity
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import com.android.systemui.R
|
import com.android.systemui.R
|
||||||
import com.android.systemui.statusbar.BaseStatusBarWifiView
|
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.binder.WifiViewBinder
|
||||||
import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.WifiViewModel
|
import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.WifiViewModel
|
||||||
|
|
||||||
@@ -36,6 +40,17 @@ class ModernStatusBarWifiView(
|
|||||||
) : BaseStatusBarWifiView(context, attrs) {
|
) : BaseStatusBarWifiView(context, attrs) {
|
||||||
|
|
||||||
private lateinit var slot: String
|
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) {
|
override fun onDarkChanged(areas: ArrayList<Rect>?, darkIntensity: Float, tint: Int) {
|
||||||
// TODO(b/238425913)
|
// TODO(b/238425913)
|
||||||
@@ -51,42 +66,64 @@ class ModernStatusBarWifiView(
|
|||||||
// TODO(b/238425913)
|
// TODO(b/238425913)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun setVisibleState(state: Int, animate: Boolean) {
|
override fun setVisibleState(@StatusBarIconView.VisibleState state: Int, animate: Boolean) {
|
||||||
// TODO(b/238425913)
|
iconVisibleState = state
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@StatusBarIconView.VisibleState
|
||||||
override fun getVisibleState(): Int {
|
override fun getVisibleState(): Int {
|
||||||
// TODO(b/238425913)
|
return iconVisibleState
|
||||||
return STATE_ICON
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun isIconVisible(): Boolean {
|
override fun isIconVisible(): Boolean {
|
||||||
// TODO(b/238425913)
|
return binding.getShouldIconBeVisible()
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Set the slot name for this view. */
|
private fun initView(
|
||||||
private fun setSlot(slotName: String) {
|
slotName: String,
|
||||||
this.slot = slotName
|
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 {
|
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.
|
* returns it.
|
||||||
*/
|
*/
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun constructAndBind(
|
fun constructAndBind(
|
||||||
context: Context,
|
context: Context,
|
||||||
slot: String,
|
slot: String,
|
||||||
viewModel: WifiViewModel,
|
wifiViewModel: WifiViewModel,
|
||||||
|
location: StatusBarLocation,
|
||||||
): ModernStatusBarWifiView {
|
): ModernStatusBarWifiView {
|
||||||
return (
|
return (
|
||||||
LayoutInflater.from(context).inflate(R.layout.new_status_bar_wifi_group, null)
|
LayoutInflater.from(context).inflate(R.layout.new_status_bar_wifi_group, null)
|
||||||
as ModernStatusBarWifiView
|
as ModernStatusBarWifiView
|
||||||
).also {
|
).also {
|
||||||
it.setSlot(slot)
|
it.initView(slot, wifiViewModel, location)
|
||||||
WifiViewBinder.bind(it, viewModel)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
package com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.graphics.Color
|
|
||||||
import androidx.annotation.DrawableRes
|
import androidx.annotation.DrawableRes
|
||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
import androidx.annotation.VisibleForTesting
|
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.R
|
||||||
import com.android.systemui.common.shared.model.ContentDescription
|
import com.android.systemui.common.shared.model.ContentDescription
|
||||||
import com.android.systemui.common.shared.model.Icon
|
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_FULL_ICONS
|
||||||
import com.android.systemui.statusbar.connectivity.WifiIcons.WIFI_NO_INTERNET_ICONS
|
import com.android.systemui.statusbar.connectivity.WifiIcons.WIFI_NO_INTERNET_ICONS
|
||||||
import com.android.systemui.statusbar.connectivity.WifiIcons.WIFI_NO_NETWORK
|
import com.android.systemui.statusbar.connectivity.WifiIcons.WIFI_NO_NETWORK
|
||||||
@@ -35,56 +36,80 @@ 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.data.model.WifiNetworkModel
|
||||||
import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractor
|
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.WifiConstants
|
||||||
|
import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiActivityModel
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.SharingStarted
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.flow.combine
|
import kotlinx.coroutines.flow.combine
|
||||||
import kotlinx.coroutines.flow.emptyFlow
|
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||||
import kotlinx.coroutines.flow.flowOf
|
import kotlinx.coroutines.flow.flowOf
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
|
import kotlinx.coroutines.flow.stateIn
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Models the UI state for the status bar wifi icon.
|
* 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(
|
@SysUISingleton
|
||||||
statusBarPipelineFlags: StatusBarPipelineFlags,
|
class WifiViewModel
|
||||||
private val constants: WifiConstants,
|
@Inject
|
||||||
|
constructor(
|
||||||
|
constants: WifiConstants,
|
||||||
private val context: Context,
|
private val context: Context,
|
||||||
private val logger: ConnectivityPipelineLogger,
|
logger: ConnectivityPipelineLogger,
|
||||||
private val interactor: WifiInteractor,
|
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
|
@DrawableRes
|
||||||
private val iconResId: Flow<Int?> = interactor.wifiNetwork.map {
|
private fun WifiNetworkModel.iconResId(): Int? {
|
||||||
when (it) {
|
return when (this) {
|
||||||
is WifiNetworkModel.CarrierMerged -> null
|
is WifiNetworkModel.CarrierMerged -> null
|
||||||
is WifiNetworkModel.Inactive -> WIFI_NO_NETWORK
|
is WifiNetworkModel.Inactive -> WIFI_NO_NETWORK
|
||||||
is WifiNetworkModel.Active ->
|
is WifiNetworkModel.Active ->
|
||||||
when {
|
when {
|
||||||
it.level == null -> null
|
this.level == null -> null
|
||||||
it.isValidated -> WIFI_FULL_ICONS[it.level]
|
this.isValidated -> WIFI_FULL_ICONS[this.level]
|
||||||
else -> WIFI_NO_INTERNET_ICONS[it.level]
|
else -> WIFI_NO_INTERNET_ICONS[this.level]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** The content description for the wifi icon. */
|
/**
|
||||||
private val contentDescription: Flow<ContentDescription?> = interactor.wifiNetwork.map {
|
* Returns the content description for the wifi icon based on the given network.
|
||||||
when (it) {
|
* Null if we can't compute the content description.
|
||||||
|
*/
|
||||||
|
private fun WifiNetworkModel.contentDescription(): ContentDescription? {
|
||||||
|
return when (this) {
|
||||||
is WifiNetworkModel.CarrierMerged -> null
|
is WifiNetworkModel.CarrierMerged -> null
|
||||||
is WifiNetworkModel.Inactive ->
|
is WifiNetworkModel.Inactive ->
|
||||||
ContentDescription.Loaded(
|
ContentDescription.Loaded(
|
||||||
"${context.getString(WIFI_NO_CONNECTION)},${context.getString(NO_INTERNET)}"
|
"${context.getString(WIFI_NO_CONNECTION)},${context.getString(NO_INTERNET)}"
|
||||||
)
|
)
|
||||||
is WifiNetworkModel.Active ->
|
is WifiNetworkModel.Active ->
|
||||||
when (it.level) {
|
when (this.level) {
|
||||||
null -> null
|
null -> null
|
||||||
else -> {
|
else -> {
|
||||||
val levelDesc = context.getString(WIFI_CONNECTION_STRENGTH[it.level])
|
val levelDesc = context.getString(WIFI_CONNECTION_STRENGTH[this.level])
|
||||||
when {
|
when {
|
||||||
it.isValidated -> ContentDescription.Loaded(levelDesc)
|
this.isValidated -> ContentDescription.Loaded(levelDesc)
|
||||||
else -> ContentDescription.Loaded(
|
else ->
|
||||||
|
ContentDescription.Loaded(
|
||||||
"$levelDesc,${context.getString(NO_INTERNET)}"
|
"$levelDesc,${context.getString(NO_INTERNET)}"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -93,40 +118,89 @@ class WifiViewModel @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** The wifi icon that should be displayed. Null if we shouldn't display any icon. */
|
||||||
* The wifi icon that should be displayed. Null if we shouldn't display any icon.
|
private val wifiIcon: StateFlow<Icon?> =
|
||||||
*/
|
combine(
|
||||||
val wifiIcon: Flow<Icon?> = combine(
|
interactor.isEnabled,
|
||||||
interactor.isForceHidden,
|
interactor.isForceHidden,
|
||||||
iconResId,
|
interactor.wifiNetwork,
|
||||||
contentDescription,
|
) { isEnabled, isForceHidden, wifiNetwork ->
|
||||||
) { isForceHidden, iconResId, contentDescription ->
|
if (!isEnabled || isForceHidden || wifiNetwork is WifiNetworkModel.CarrierMerged) {
|
||||||
when {
|
return@combine null
|
||||||
isForceHidden ||
|
|
||||||
iconResId == null ||
|
|
||||||
iconResId <= 0 -> null
|
|
||||||
else -> Icon.Resource(iconResId, contentDescription)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
val iconResId = wifiNetwork.iconResId() ?: return@combine null
|
||||||
* True if the activity in icon should be displayed and false otherwise.
|
val icon = Icon.Resource(iconResId, wifiNetwork.contentDescription())
|
||||||
*/
|
|
||||||
val isActivityInVisible: Flow<Boolean>
|
return@combine when {
|
||||||
get() =
|
constants.alwaysShowIconIfEnabled -> icon
|
||||||
|
wifiNetwork is WifiNetworkModel.Active && wifiNetwork.isValidated -> icon
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.stateIn(scope, started = SharingStarted.WhileSubscribed(), initialValue = null)
|
||||||
|
|
||||||
|
/** The wifi activity status. Null if we shouldn't display the activity status. */
|
||||||
|
private val activity: Flow<WifiActivityModel?> =
|
||||||
if (!constants.shouldShowActivityConfig) {
|
if (!constants.shouldShowActivityConfig) {
|
||||||
flowOf(false)
|
flowOf(null)
|
||||||
} else {
|
} else {
|
||||||
interactor.hasActivityIn
|
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. */
|
private val isActivityInViewVisible: Flow<Boolean> =
|
||||||
val tint: Flow<Int> = if (!statusBarPipelineFlags.useNewPipelineDebugColoring()) {
|
activity
|
||||||
emptyFlow()
|
.map { it?.hasActivityIn == true }
|
||||||
} else {
|
.stateIn(scope, started = SharingStarted.WhileSubscribed(), initialValue = false)
|
||||||
flowOf(Color.CYAN)
|
|
||||||
|
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 {
|
companion object {
|
||||||
@StringRes
|
@StringRes
|
||||||
|
|||||||
@@ -110,7 +110,7 @@ class QuickStatusBarHeaderControllerTest : SysuiTestCase() {
|
|||||||
`when`(qsCarrierGroupControllerBuilder.build()).thenReturn(qsCarrierGroupController)
|
`when`(qsCarrierGroupControllerBuilder.build()).thenReturn(qsCarrierGroupController)
|
||||||
`when`(variableDateViewControllerFactory.create(any()))
|
`when`(variableDateViewControllerFactory.create(any()))
|
||||||
.thenReturn(variableDateViewController)
|
.thenReturn(variableDateViewController)
|
||||||
`when`(iconManagerFactory.create(any())).thenReturn(iconManager)
|
`when`(iconManagerFactory.create(any(), any())).thenReturn(iconManager)
|
||||||
`when`(view.resources).thenReturn(mContext.resources)
|
`when`(view.resources).thenReturn(mContext.resources)
|
||||||
`when`(view.isAttachedToWindow).thenReturn(true)
|
`when`(view.isAttachedToWindow).thenReturn(true)
|
||||||
`when`(view.context).thenReturn(context)
|
`when`(view.context).thenReturn(context)
|
||||||
|
|||||||
@@ -176,7 +176,7 @@ class LargeScreenShadeHeaderControllerCombinedTest : SysuiTestCase() {
|
|||||||
}
|
}
|
||||||
whenever(view.visibility).thenAnswer { _ -> viewVisibility }
|
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.COMBINED_QS_HEADERS)).thenReturn(true)
|
||||||
whenever(featureFlags.isEnabled(Flags.NEW_HEADER)).thenReturn(true)
|
whenever(featureFlags.isEnabled(Flags.NEW_HEADER)).thenReturn(true)
|
||||||
|
|||||||
@@ -97,7 +97,7 @@ class LargeScreenShadeHeaderControllerTest : SysuiTestCase() {
|
|||||||
whenever(view.visibility).thenAnswer { _ -> viewVisibility }
|
whenever(view.visibility).thenAnswer { _ -> viewVisibility }
|
||||||
whenever(variableDateViewControllerFactory.create(any()))
|
whenever(variableDateViewControllerFactory.create(any()))
|
||||||
.thenReturn(variableDateViewController)
|
.thenReturn(variableDateViewController)
|
||||||
whenever(iconManagerFactory.create(any())).thenReturn(iconManager)
|
whenever(iconManagerFactory.create(any(), any())).thenReturn(iconManager)
|
||||||
whenever(featureFlags.isEnabled(Flags.COMBINED_QS_HEADERS)).thenReturn(false)
|
whenever(featureFlags.isEnabled(Flags.COMBINED_QS_HEADERS)).thenReturn(false)
|
||||||
mLargeScreenShadeHeaderController = LargeScreenShadeHeaderController(
|
mLargeScreenShadeHeaderController = LargeScreenShadeHeaderController(
|
||||||
view,
|
view,
|
||||||
|
|||||||
@@ -135,7 +135,7 @@ public class KeyguardStatusBarViewControllerTest extends SysuiTestCase {
|
|||||||
|
|
||||||
MockitoAnnotations.initMocks(this);
|
MockitoAnnotations.initMocks(this);
|
||||||
|
|
||||||
when(mIconManagerFactory.create(any())).thenReturn(mIconManager);
|
when(mIconManagerFactory.create(any(), any())).thenReturn(mIconManager);
|
||||||
|
|
||||||
allowTestableLooperAsMainThread();
|
allowTestableLooperAsMainThread();
|
||||||
TestableLooper.get(this).runWithLooper(() -> {
|
TestableLooper.get(this).runWithLooper(() -> {
|
||||||
|
|||||||
@@ -51,8 +51,6 @@ import org.junit.Before;
|
|||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
|
|
||||||
import javax.inject.Provider;
|
|
||||||
|
|
||||||
@RunWith(AndroidTestingRunner.class)
|
@RunWith(AndroidTestingRunner.class)
|
||||||
@RunWithLooper
|
@RunWithLooper
|
||||||
@SmallTest
|
@SmallTest
|
||||||
@@ -79,8 +77,9 @@ public class StatusBarIconControllerTest extends LeakCheckedTest {
|
|||||||
LinearLayout layout = new LinearLayout(mContext);
|
LinearLayout layout = new LinearLayout(mContext);
|
||||||
TestDarkIconManager manager = new TestDarkIconManager(
|
TestDarkIconManager manager = new TestDarkIconManager(
|
||||||
layout,
|
layout,
|
||||||
|
StatusBarLocation.HOME,
|
||||||
mock(StatusBarPipelineFlags.class),
|
mock(StatusBarPipelineFlags.class),
|
||||||
() -> mock(WifiViewModel.class),
|
mock(WifiViewModel.class),
|
||||||
mMobileContextProvider,
|
mMobileContextProvider,
|
||||||
mock(DarkIconDispatcher.class));
|
mock(DarkIconDispatcher.class));
|
||||||
testCallOnAdd_forManager(manager);
|
testCallOnAdd_forManager(manager);
|
||||||
@@ -121,13 +120,15 @@ public class StatusBarIconControllerTest extends LeakCheckedTest {
|
|||||||
|
|
||||||
TestDarkIconManager(
|
TestDarkIconManager(
|
||||||
LinearLayout group,
|
LinearLayout group,
|
||||||
|
StatusBarLocation location,
|
||||||
StatusBarPipelineFlags statusBarPipelineFlags,
|
StatusBarPipelineFlags statusBarPipelineFlags,
|
||||||
Provider<WifiViewModel> wifiViewModelProvider,
|
WifiViewModel wifiViewModel,
|
||||||
MobileContextProvider contextProvider,
|
MobileContextProvider contextProvider,
|
||||||
DarkIconDispatcher darkIconDispatcher) {
|
DarkIconDispatcher darkIconDispatcher) {
|
||||||
super(group,
|
super(group,
|
||||||
|
location,
|
||||||
statusBarPipelineFlags,
|
statusBarPipelineFlags,
|
||||||
wifiViewModelProvider,
|
wifiViewModel,
|
||||||
contextProvider,
|
contextProvider,
|
||||||
darkIconDispatcher);
|
darkIconDispatcher);
|
||||||
}
|
}
|
||||||
@@ -165,8 +166,9 @@ public class StatusBarIconControllerTest extends LeakCheckedTest {
|
|||||||
private static class TestIconManager extends IconManager implements TestableIconManager {
|
private static class TestIconManager extends IconManager implements TestableIconManager {
|
||||||
TestIconManager(ViewGroup group, MobileContextProvider contextProvider) {
|
TestIconManager(ViewGroup group, MobileContextProvider contextProvider) {
|
||||||
super(group,
|
super(group,
|
||||||
|
StatusBarLocation.HOME,
|
||||||
mock(StatusBarPipelineFlags.class),
|
mock(StatusBarPipelineFlags.class),
|
||||||
() -> mock(WifiViewModel.class),
|
mock(WifiViewModel.class),
|
||||||
contextProvider);
|
contextProvider);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -431,7 +431,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
|
|||||||
mOperatorNameViewControllerFactory = mock(OperatorNameViewController.Factory.class);
|
mOperatorNameViewControllerFactory = mock(OperatorNameViewController.Factory.class);
|
||||||
when(mOperatorNameViewControllerFactory.create(any()))
|
when(mOperatorNameViewControllerFactory.create(any()))
|
||||||
.thenReturn(mOperatorNameViewController);
|
.thenReturn(mOperatorNameViewController);
|
||||||
when(mIconManagerFactory.create(any())).thenReturn(mIconManager);
|
when(mIconManagerFactory.create(any(), any())).thenReturn(mIconManager);
|
||||||
mSecureSettings = mock(SecureSettings.class);
|
mSecureSettings = mock(SecureSettings.class);
|
||||||
|
|
||||||
setUpNotificationIconAreaController();
|
setUpNotificationIconAreaController();
|
||||||
|
|||||||
@@ -23,9 +23,16 @@ import com.android.systemui.SysuiTestCase
|
|||||||
import com.android.systemui.dump.DumpManager
|
import com.android.systemui.dump.DumpManager
|
||||||
import com.android.systemui.log.LogBufferFactory
|
import com.android.systemui.log.LogBufferFactory
|
||||||
import com.android.systemui.log.LogcatEchoTracker
|
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 com.google.common.truth.Truth.assertThat
|
||||||
import java.io.PrintWriter
|
import java.io.PrintWriter
|
||||||
import java.io.StringWriter
|
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.junit.Test
|
||||||
import org.mockito.Mockito
|
import org.mockito.Mockito
|
||||||
import org.mockito.Mockito.mock
|
import org.mockito.Mockito.mock
|
||||||
@@ -64,7 +71,63 @@ class ConnectivityPipelineLoggerTest : SysuiTestCase() {
|
|||||||
assertThat(actualString).contains(expectedNetId)
|
assertThat(actualString).contains(expectedNetId)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val NET_1_ID = 100
|
@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 {
|
private val NET_1 = com.android.systemui.util.mockito.mock<Network>().also {
|
||||||
Mockito.`when`(it.getNetId()).thenReturn(NET_1_ID)
|
Mockito.`when`(it.getNetId()).thenReturn(NET_1_ID)
|
||||||
}
|
}
|
||||||
@@ -72,4 +135,6 @@ class ConnectivityPipelineLoggerTest : SysuiTestCase() {
|
|||||||
.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
|
.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
|
||||||
.addCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)
|
.addCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)
|
||||||
.build()
|
.build()
|
||||||
|
private val IMMEDIATE = Dispatchers.Main.immediate
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,20 +16,27 @@
|
|||||||
|
|
||||||
package com.android.systemui.statusbar.pipeline.wifi.data.repository
|
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.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.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.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
|
||||||
/** Fake implementation of [WifiRepository] exposing set methods for all the flows. */
|
/** Fake implementation of [WifiRepository] exposing set methods for all the flows. */
|
||||||
class FakeWifiRepository : WifiRepository {
|
class FakeWifiRepository : WifiRepository {
|
||||||
|
private val _isWifiEnabled: MutableStateFlow<Boolean> = MutableStateFlow(false)
|
||||||
|
override val isWifiEnabled: StateFlow<Boolean> = _isWifiEnabled
|
||||||
|
|
||||||
private val _wifiNetwork: MutableStateFlow<WifiNetworkModel> =
|
private val _wifiNetwork: MutableStateFlow<WifiNetworkModel> =
|
||||||
MutableStateFlow(WifiNetworkModel.Inactive)
|
MutableStateFlow(WifiNetworkModel.Inactive)
|
||||||
override val wifiNetwork: Flow<WifiNetworkModel> = _wifiNetwork
|
override val wifiNetwork: StateFlow<WifiNetworkModel> = _wifiNetwork
|
||||||
|
|
||||||
private val _wifiActivity = MutableStateFlow(ACTIVITY_DEFAULT)
|
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) {
|
fun setWifiNetwork(wifiNetworkModel: WifiNetworkModel) {
|
||||||
_wifiNetwork.value = wifiNetworkModel
|
_wifiNetwork.value = wifiNetworkModel
|
||||||
|
|||||||
@@ -28,15 +28,17 @@ import android.net.wifi.WifiManager
|
|||||||
import android.net.wifi.WifiManager.TrafficStateCallback
|
import android.net.wifi.WifiManager.TrafficStateCallback
|
||||||
import androidx.test.filters.SmallTest
|
import androidx.test.filters.SmallTest
|
||||||
import com.android.systemui.SysuiTestCase
|
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.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.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.ACTIVITY_DEFAULT
|
||||||
import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepositoryImpl.Companion.WIFI_NETWORK_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.concurrency.FakeExecutor
|
||||||
import com.android.systemui.util.mockito.any
|
import com.android.systemui.util.mockito.any
|
||||||
import com.android.systemui.util.mockito.argumentCaptor
|
import com.android.systemui.util.mockito.argumentCaptor
|
||||||
import com.android.systemui.util.mockito.mock
|
import com.android.systemui.util.mockito.mock
|
||||||
|
import com.android.systemui.util.mockito.nullable
|
||||||
import com.android.systemui.util.time.FakeSystemClock
|
import com.android.systemui.util.time.FakeSystemClock
|
||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
import java.util.concurrent.Executor
|
import java.util.concurrent.Executor
|
||||||
@@ -44,23 +46,28 @@ import kotlinx.coroutines.CoroutineScope
|
|||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
import kotlinx.coroutines.cancel
|
import kotlinx.coroutines.cancel
|
||||||
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
|
import kotlinx.coroutines.flow.flowOf
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import org.junit.After
|
import org.junit.After
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
import org.mockito.ArgumentMatchers.anyInt
|
||||||
import org.mockito.Mock
|
import org.mockito.Mock
|
||||||
import org.mockito.Mockito.verify
|
import org.mockito.Mockito.verify
|
||||||
import org.mockito.Mockito.`when` as whenever
|
import org.mockito.Mockito.`when` as whenever
|
||||||
import org.mockito.MockitoAnnotations
|
import org.mockito.MockitoAnnotations
|
||||||
|
|
||||||
|
@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
|
||||||
@OptIn(ExperimentalCoroutinesApi::class)
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
@SmallTest
|
@SmallTest
|
||||||
class WifiRepositoryImplTest : SysuiTestCase() {
|
class WifiRepositoryImplTest : SysuiTestCase() {
|
||||||
|
|
||||||
private lateinit var underTest: WifiRepositoryImpl
|
private lateinit var underTest: WifiRepositoryImpl
|
||||||
|
|
||||||
|
@Mock private lateinit var broadcastDispatcher: BroadcastDispatcher
|
||||||
@Mock private lateinit var logger: ConnectivityPipelineLogger
|
@Mock private lateinit var logger: ConnectivityPipelineLogger
|
||||||
@Mock private lateinit var connectivityManager: ConnectivityManager
|
@Mock private lateinit var connectivityManager: ConnectivityManager
|
||||||
@Mock private lateinit var wifiManager: WifiManager
|
@Mock private lateinit var wifiManager: WifiManager
|
||||||
@@ -70,16 +77,17 @@ class WifiRepositoryImplTest : SysuiTestCase() {
|
|||||||
@Before
|
@Before
|
||||||
fun setUp() {
|
fun setUp() {
|
||||||
MockitoAnnotations.initMocks(this)
|
MockitoAnnotations.initMocks(this)
|
||||||
|
whenever(
|
||||||
|
broadcastDispatcher.broadcastFlow(
|
||||||
|
any(),
|
||||||
|
nullable(),
|
||||||
|
anyInt(),
|
||||||
|
nullable(),
|
||||||
|
)
|
||||||
|
).thenReturn(flowOf(Unit))
|
||||||
executor = FakeExecutor(FakeSystemClock())
|
executor = FakeExecutor(FakeSystemClock())
|
||||||
scope = CoroutineScope(IMMEDIATE)
|
scope = CoroutineScope(IMMEDIATE)
|
||||||
|
underTest = createRepo()
|
||||||
underTest = WifiRepositoryImpl(
|
|
||||||
connectivityManager,
|
|
||||||
logger,
|
|
||||||
executor,
|
|
||||||
scope,
|
|
||||||
wifiManager,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@After
|
@After
|
||||||
@@ -87,6 +95,132 @@ class WifiRepositoryImplTest : SysuiTestCase() {
|
|||||||
scope.cancel()
|
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
|
@Test
|
||||||
fun wifiNetwork_initiallyGetsDefault() = runBlocking(IMMEDIATE) {
|
fun wifiNetwork_initiallyGetsDefault() = runBlocking(IMMEDIATE) {
|
||||||
var latest: WifiNetworkModel? = null
|
var latest: WifiNetworkModel? = null
|
||||||
@@ -509,13 +643,7 @@ class WifiRepositoryImplTest : SysuiTestCase() {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun wifiActivity_nullWifiManager_receivesDefault() = runBlocking(IMMEDIATE) {
|
fun wifiActivity_nullWifiManager_receivesDefault() = runBlocking(IMMEDIATE) {
|
||||||
underTest = WifiRepositoryImpl(
|
underTest = createRepo(wifiManagerToUse = null)
|
||||||
connectivityManager,
|
|
||||||
logger,
|
|
||||||
executor,
|
|
||||||
scope,
|
|
||||||
wifiManager = null,
|
|
||||||
)
|
|
||||||
|
|
||||||
var latest: WifiActivityModel? = null
|
var latest: WifiActivityModel? = null
|
||||||
val job = underTest
|
val job = underTest
|
||||||
@@ -594,6 +722,17 @@ class WifiRepositoryImplTest : SysuiTestCase() {
|
|||||||
job.cancel()
|
job.cancel()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun createRepo(wifiManagerToUse: WifiManager? = wifiManager): WifiRepositoryImpl {
|
||||||
|
return WifiRepositoryImpl(
|
||||||
|
broadcastDispatcher,
|
||||||
|
connectivityManager,
|
||||||
|
logger,
|
||||||
|
executor,
|
||||||
|
scope,
|
||||||
|
wifiManagerToUse,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
private fun getTrafficStateCallback(): TrafficStateCallback {
|
private fun getTrafficStateCallback(): TrafficStateCallback {
|
||||||
val callbackCaptor = argumentCaptor<TrafficStateCallback>()
|
val callbackCaptor = argumentCaptor<TrafficStateCallback>()
|
||||||
verify(wifiManager).registerTrafficStateCallback(any(), callbackCaptor.capture())
|
verify(wifiManager).registerTrafficStateCallback(any(), callbackCaptor.capture())
|
||||||
|
|||||||
@@ -16,13 +16,14 @@
|
|||||||
|
|
||||||
package com.android.systemui.statusbar.pipeline.wifi.domain.interactor
|
package com.android.systemui.statusbar.pipeline.wifi.domain.interactor
|
||||||
|
|
||||||
|
import android.net.wifi.WifiManager
|
||||||
import androidx.test.filters.SmallTest
|
import androidx.test.filters.SmallTest
|
||||||
import com.android.systemui.SysuiTestCase
|
import com.android.systemui.SysuiTestCase
|
||||||
import com.android.systemui.statusbar.pipeline.shared.data.model.ConnectivitySlot
|
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.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.model.WifiNetworkModel
|
||||||
import com.android.systemui.statusbar.pipeline.wifi.data.repository.FakeWifiRepository
|
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 com.google.common.truth.Truth.assertThat
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
@@ -50,172 +51,129 @@ class WifiInteractorTest : SysuiTestCase() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun hasActivityIn_noInOrOut_outputsFalse() = runBlocking(IMMEDIATE) {
|
fun ssid_inactiveNetwork_outputsNull() = 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) {
|
|
||||||
wifiRepository.setWifiNetwork(WifiNetworkModel.Inactive)
|
wifiRepository.setWifiNetwork(WifiNetworkModel.Inactive)
|
||||||
wifiRepository.setWifiActivity(
|
|
||||||
WifiActivityModel(hasActivityIn = true, hasActivityOut = true)
|
|
||||||
)
|
|
||||||
|
|
||||||
var latest: Boolean? = null
|
var latest: String? = "default"
|
||||||
val job = underTest
|
val job = underTest
|
||||||
.hasActivityIn
|
.ssid
|
||||||
.onEach { latest = it }
|
.onEach { latest = it }
|
||||||
.launchIn(this)
|
.launchIn(this)
|
||||||
|
|
||||||
assertThat(latest).isFalse()
|
assertThat(latest).isNull()
|
||||||
|
|
||||||
job.cancel()
|
job.cancel()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun hasActivityIn_carrierMergedNetwork_outputsFalse() = runBlocking(IMMEDIATE) {
|
fun ssid_carrierMergedNetwork_outputsNull() = runBlocking(IMMEDIATE) {
|
||||||
wifiRepository.setWifiNetwork(WifiNetworkModel.CarrierMerged)
|
wifiRepository.setWifiNetwork(WifiNetworkModel.CarrierMerged)
|
||||||
wifiRepository.setWifiActivity(
|
|
||||||
WifiActivityModel(hasActivityIn = true, hasActivityOut = true)
|
|
||||||
)
|
|
||||||
|
|
||||||
var latest: Boolean? = null
|
var latest: String? = "default"
|
||||||
val job = underTest
|
val job = underTest
|
||||||
.hasActivityIn
|
.ssid
|
||||||
.onEach { latest = it }
|
.onEach { latest = it }
|
||||||
.launchIn(this)
|
.launchIn(this)
|
||||||
|
|
||||||
assertThat(latest).isFalse()
|
assertThat(latest).isNull()
|
||||||
|
|
||||||
job.cancel()
|
job.cancel()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun hasActivityIn_multipleChanges_multipleOutputChanges() = runBlocking(IMMEDIATE) {
|
fun ssid_isPasspointAccessPoint_outputsPasspointName() = runBlocking(IMMEDIATE) {
|
||||||
wifiRepository.setWifiNetwork(VALID_WIFI_NETWORK_MODEL)
|
wifiRepository.setWifiNetwork(WifiNetworkModel.Active(
|
||||||
|
networkId = 1,
|
||||||
|
isPasspointAccessPoint = true,
|
||||||
|
passpointProviderFriendlyName = "friendly",
|
||||||
|
))
|
||||||
|
|
||||||
var latest: Boolean? = null
|
var latest: String? = null
|
||||||
val job = underTest
|
val job = underTest
|
||||||
.hasActivityIn
|
.ssid
|
||||||
.onEach { latest = it }
|
.onEach { latest = it }
|
||||||
.launchIn(this)
|
.launchIn(this)
|
||||||
|
|
||||||
// Conduct a series of changes and verify we catch each of them in succession
|
assertThat(latest).isEqualTo("friendly")
|
||||||
wifiRepository.setWifiActivity(
|
|
||||||
WifiActivityModel(hasActivityIn = true, hasActivityOut = false)
|
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
|
||||||
|
.isEnabled
|
||||||
|
.onEach { latest = it }
|
||||||
|
.launchIn(this)
|
||||||
|
|
||||||
|
wifiRepository.setIsWifiEnabled(true)
|
||||||
yield()
|
yield()
|
||||||
assertThat(latest).isTrue()
|
assertThat(latest).isTrue()
|
||||||
|
|
||||||
wifiRepository.setWifiActivity(
|
wifiRepository.setIsWifiEnabled(false)
|
||||||
WifiActivityModel(hasActivityIn = false, hasActivityOut = true)
|
|
||||||
)
|
|
||||||
yield()
|
yield()
|
||||||
assertThat(latest).isFalse()
|
assertThat(latest).isFalse()
|
||||||
|
|
||||||
wifiRepository.setWifiActivity(
|
wifiRepository.setIsWifiEnabled(true)
|
||||||
WifiActivityModel(hasActivityIn = true, hasActivityOut = true)
|
|
||||||
)
|
|
||||||
yield()
|
yield()
|
||||||
assertThat(latest).isTrue()
|
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()
|
job.cancel()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -241,6 +199,32 @@ class WifiInteractorTest : SysuiTestCase() {
|
|||||||
job.cancel()
|
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
|
@Test
|
||||||
fun isForceHidden_repoHasWifiHidden_outputsTrue() = runBlocking(IMMEDIATE) {
|
fun isForceHidden_repoHasWifiHidden_outputsTrue() = runBlocking(IMMEDIATE) {
|
||||||
connectivityRepository.setForceHiddenIcons(setOf(ConnectivitySlot.WIFI))
|
connectivityRepository.setForceHiddenIcons(setOf(ConnectivitySlot.WIFI))
|
||||||
@@ -270,10 +254,6 @@ class WifiInteractorTest : SysuiTestCase() {
|
|||||||
|
|
||||||
job.cancel()
|
job.cancel()
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
|
||||||
val VALID_WIFI_NETWORK_MODEL = WifiNetworkModel.Active(networkId = 1, ssid = "AB")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private val IMMEDIATE = Dispatchers.Main.immediate
|
private val IMMEDIATE = Dispatchers.Main.immediate
|
||||||
|
|||||||
@@ -16,38 +16,216 @@
|
|||||||
|
|
||||||
package com.android.systemui.statusbar.pipeline.wifi.ui.view
|
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.TestableLooper.RunWithLooper
|
||||||
|
import android.testing.ViewUtils
|
||||||
|
import android.view.View
|
||||||
import androidx.test.filters.SmallTest
|
import androidx.test.filters.SmallTest
|
||||||
|
import com.android.systemui.R
|
||||||
import com.android.systemui.SysuiTestCase
|
import com.android.systemui.SysuiTestCase
|
||||||
import com.android.systemui.lifecycle.InstantTaskExecutorRule
|
import com.android.systemui.lifecycle.InstantTaskExecutorRule
|
||||||
import com.android.systemui.util.Assert
|
import com.android.systemui.statusbar.StatusBarIconView.STATE_DOT
|
||||||
import com.android.systemui.util.mockito.mock
|
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 com.google.common.truth.Truth.assertThat
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
import org.junit.Rule
|
import org.junit.Rule
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
import org.junit.runners.JUnit4
|
import org.mockito.Mock
|
||||||
|
import org.mockito.MockitoAnnotations
|
||||||
|
|
||||||
@SmallTest
|
@SmallTest
|
||||||
@RunWith(JUnit4::class)
|
@RunWith(AndroidTestingRunner::class)
|
||||||
@RunWithLooper
|
@RunWithLooper(setAsMainLooper = true)
|
||||||
class ModernStatusBarWifiViewTest : SysuiTestCase() {
|
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
|
@JvmField @Rule
|
||||||
val instantTaskExecutor = InstantTaskExecutorRule()
|
val instantTaskExecutor = InstantTaskExecutorRule()
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
fun setUp() {
|
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
|
@Test
|
||||||
fun constructAndBind_hasCorrectSlot() {
|
fun constructAndBind_hasCorrectSlot() {
|
||||||
val view = ModernStatusBarWifiView.constructAndBind(
|
val view = ModernStatusBarWifiView.constructAndBind(
|
||||||
context, "slotName", mock()
|
context, "slotName", viewModel, StatusBarLocation.HOME
|
||||||
)
|
)
|
||||||
|
|
||||||
assertThat(view.slot).isEqualTo("slotName")
|
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.ConnectivityPipelineLogger
|
||||||
import com.android.systemui.statusbar.pipeline.shared.data.model.ConnectivitySlot
|
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.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.model.WifiNetworkModel
|
||||||
import com.android.systemui.statusbar.pipeline.wifi.data.repository.FakeWifiRepository
|
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.domain.interactor.WifiInteractor
|
||||||
import com.android.systemui.statusbar.pipeline.wifi.shared.WifiConstants
|
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.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.WifiViewModel.Companion.NO_INTERNET
|
||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
|
import kotlinx.coroutines.cancel
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import kotlinx.coroutines.yield
|
import kotlinx.coroutines.yield
|
||||||
|
import org.junit.After
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.mockito.Mock
|
import org.mockito.Mock
|
||||||
import org.mockito.Mockito.`when` as whenever
|
import org.mockito.Mockito.`when` as whenever
|
||||||
import org.mockito.MockitoAnnotations
|
import org.mockito.MockitoAnnotations
|
||||||
|
|
||||||
|
@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
|
||||||
@OptIn(ExperimentalCoroutinesApi::class)
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
@SmallTest
|
@SmallTest
|
||||||
class WifiViewModelTest : SysuiTestCase() {
|
class WifiViewModelTest : SysuiTestCase() {
|
||||||
@@ -60,34 +64,64 @@ class WifiViewModelTest : SysuiTestCase() {
|
|||||||
private lateinit var connectivityRepository: FakeConnectivityRepository
|
private lateinit var connectivityRepository: FakeConnectivityRepository
|
||||||
private lateinit var wifiRepository: FakeWifiRepository
|
private lateinit var wifiRepository: FakeWifiRepository
|
||||||
private lateinit var interactor: WifiInteractor
|
private lateinit var interactor: WifiInteractor
|
||||||
|
private lateinit var scope: CoroutineScope
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
fun setUp() {
|
fun setUp() {
|
||||||
MockitoAnnotations.initMocks(this)
|
MockitoAnnotations.initMocks(this)
|
||||||
connectivityRepository = FakeConnectivityRepository()
|
connectivityRepository = FakeConnectivityRepository()
|
||||||
wifiRepository = FakeWifiRepository()
|
wifiRepository = FakeWifiRepository()
|
||||||
|
wifiRepository.setIsWifiEnabled(true)
|
||||||
interactor = WifiInteractor(connectivityRepository, wifiRepository)
|
interactor = WifiInteractor(connectivityRepository, wifiRepository)
|
||||||
|
scope = CoroutineScope(IMMEDIATE)
|
||||||
|
createAndSetViewModel()
|
||||||
|
}
|
||||||
|
|
||||||
underTest = WifiViewModel(
|
@After
|
||||||
statusBarPipelineFlags,
|
fun tearDown() {
|
||||||
constants,
|
scope.cancel()
|
||||||
context,
|
}
|
||||||
logger,
|
|
||||||
interactor
|
// 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
|
@Test
|
||||||
fun wifiIcon_forceHidden_outputsNull() = runBlocking(IMMEDIATE) {
|
fun wifiIcon_forceHidden_outputsNull() = runBlocking(IMMEDIATE) {
|
||||||
connectivityRepository.setForceHiddenIcons(setOf(ConnectivitySlot.WIFI))
|
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
|
val job = underTest
|
||||||
|
.home
|
||||||
.wifiIcon
|
.wifiIcon
|
||||||
.onEach { latest = it }
|
.onEach { latest = it }
|
||||||
.launchIn(this)
|
.launchIn(this)
|
||||||
|
|
||||||
|
wifiRepository.setWifiNetwork(WifiNetworkModel.Active(NETWORK_ID, level = 2))
|
||||||
|
yield()
|
||||||
|
|
||||||
assertThat(latest).isNull()
|
assertThat(latest).isNull()
|
||||||
|
|
||||||
job.cancel()
|
job.cancel()
|
||||||
@@ -96,29 +130,60 @@ class WifiViewModelTest : SysuiTestCase() {
|
|||||||
@Test
|
@Test
|
||||||
fun wifiIcon_notForceHidden_outputsVisible() = runBlocking(IMMEDIATE) {
|
fun wifiIcon_notForceHidden_outputsVisible() = runBlocking(IMMEDIATE) {
|
||||||
connectivityRepository.setForceHiddenIcons(setOf())
|
connectivityRepository.setForceHiddenIcons(setOf())
|
||||||
wifiRepository.setWifiNetwork(WifiNetworkModel.Active(NETWORK_ID, level = 2))
|
|
||||||
|
|
||||||
var latest: Icon? = null
|
var latest: Icon? = null
|
||||||
val job = underTest
|
val job = underTest
|
||||||
|
.home
|
||||||
.wifiIcon
|
.wifiIcon
|
||||||
.onEach { latest = it }
|
.onEach { latest = it }
|
||||||
.launchIn(this)
|
.launchIn(this)
|
||||||
|
|
||||||
|
wifiRepository.setWifiNetwork(
|
||||||
|
WifiNetworkModel.Active(NETWORK_ID, isValidated = true, level = 2)
|
||||||
|
)
|
||||||
|
yield()
|
||||||
|
|
||||||
assertThat(latest).isInstanceOf(Icon.Resource::class.java)
|
assertThat(latest).isInstanceOf(Icon.Resource::class.java)
|
||||||
|
|
||||||
job.cancel()
|
job.cancel()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun wifiIcon_inactiveNetwork_outputsNoNetworkIcon() = runBlocking(IMMEDIATE) {
|
fun wifiIcon_inactiveNetwork_alwaysShowFalse_outputsNull() = runBlocking(IMMEDIATE) {
|
||||||
wifiRepository.setWifiNetwork(WifiNetworkModel.Inactive)
|
whenever(constants.alwaysShowIconIfEnabled).thenReturn(false)
|
||||||
|
createAndSetViewModel()
|
||||||
|
|
||||||
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
|
val job = underTest
|
||||||
|
.home
|
||||||
.wifiIcon
|
.wifiIcon
|
||||||
.onEach { latest = it }
|
.onEach { latest = it }
|
||||||
.launchIn(this)
|
.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
|
||||||
|
.home
|
||||||
|
.wifiIcon
|
||||||
|
.onEach { latest = it }
|
||||||
|
.launchIn(this)
|
||||||
|
|
||||||
|
wifiRepository.setWifiNetwork(WifiNetworkModel.Inactive)
|
||||||
|
yield()
|
||||||
|
|
||||||
assertThat(latest).isInstanceOf(Icon.Resource::class.java)
|
assertThat(latest).isInstanceOf(Icon.Resource::class.java)
|
||||||
val icon = latest as Icon.Resource
|
val icon = latest as Icon.Resource
|
||||||
assertThat(icon.res).isEqualTo(WIFI_NO_NETWORK)
|
assertThat(icon.res).isEqualTo(WIFI_NO_NETWORK)
|
||||||
@@ -132,14 +197,22 @@ class WifiViewModelTest : SysuiTestCase() {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun wifiIcon_carrierMergedNetwork_outputsNull() = runBlocking(IMMEDIATE) {
|
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
|
val job = underTest
|
||||||
|
.home
|
||||||
.wifiIcon
|
.wifiIcon
|
||||||
.onEach { latest = it }
|
.onEach { latest = it }
|
||||||
.launchIn(this)
|
.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()
|
assertThat(latest).isNull()
|
||||||
|
|
||||||
job.cancel()
|
job.cancel()
|
||||||
@@ -147,14 +220,22 @@ class WifiViewModelTest : SysuiTestCase() {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun wifiIcon_isActiveNullLevel_outputsNull() = runBlocking(IMMEDIATE) {
|
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
|
val job = underTest
|
||||||
|
.home
|
||||||
.wifiIcon
|
.wifiIcon
|
||||||
.onEach { latest = it }
|
.onEach { latest = it }
|
||||||
.launchIn(this)
|
.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()
|
assertThat(latest).isNull()
|
||||||
|
|
||||||
job.cancel()
|
job.cancel()
|
||||||
@@ -162,21 +243,22 @@ class WifiViewModelTest : SysuiTestCase() {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun wifiIcon_isActiveAndValidated_level1_outputsFull1Icon() = runBlocking(IMMEDIATE) {
|
fun wifiIcon_isActiveAndValidated_level1_outputsFull1Icon() = runBlocking(IMMEDIATE) {
|
||||||
val level = 1
|
var latest: Icon? = null
|
||||||
|
val job = underTest
|
||||||
|
.home
|
||||||
|
.wifiIcon
|
||||||
|
.onEach { latest = it }
|
||||||
|
.launchIn(this)
|
||||||
|
|
||||||
|
val level = 1
|
||||||
wifiRepository.setWifiNetwork(
|
wifiRepository.setWifiNetwork(
|
||||||
WifiNetworkModel.Active(
|
WifiNetworkModel.Active(
|
||||||
NETWORK_ID,
|
NETWORK_ID,
|
||||||
isValidated = true,
|
isValidated = true,
|
||||||
level = level
|
level,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
yield()
|
||||||
var latest: Icon? = null
|
|
||||||
val job = underTest
|
|
||||||
.wifiIcon
|
|
||||||
.onEach { latest = it }
|
|
||||||
.launchIn(this)
|
|
||||||
|
|
||||||
assertThat(latest).isInstanceOf(Icon.Resource::class.java)
|
assertThat(latest).isInstanceOf(Icon.Resource::class.java)
|
||||||
val icon = latest as Icon.Resource
|
val icon = latest as Icon.Resource
|
||||||
@@ -190,22 +272,48 @@ class WifiViewModelTest : SysuiTestCase() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun wifiIcon_isActiveAndNotValidated_level4_outputsEmpty4Icon() = runBlocking(IMMEDIATE) {
|
fun wifiIcon_isActiveAndNotValidated_alwaysShowFalse_outputsNull() = runBlocking(IMMEDIATE) {
|
||||||
val level = 4
|
whenever(constants.alwaysShowIconIfEnabled).thenReturn(false)
|
||||||
|
createAndSetViewModel()
|
||||||
|
|
||||||
|
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(
|
wifiRepository.setWifiNetwork(
|
||||||
WifiNetworkModel.Active(
|
WifiNetworkModel.Active(
|
||||||
NETWORK_ID,
|
NETWORK_ID,
|
||||||
isValidated = false,
|
isValidated = false,
|
||||||
level = level
|
level,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
yield()
|
||||||
var latest: Icon? = null
|
|
||||||
val job = underTest
|
|
||||||
.wifiIcon
|
|
||||||
.onEach { latest = it }
|
|
||||||
.launchIn(this)
|
|
||||||
|
|
||||||
assertThat(latest).isInstanceOf(Icon.Resource::class.java)
|
assertThat(latest).isInstanceOf(Icon.Resource::class.java)
|
||||||
val icon = latest as Icon.Resource
|
val icon = latest as Icon.Resource
|
||||||
@@ -219,68 +327,398 @@ class WifiViewModelTest : SysuiTestCase() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun activityInVisible_showActivityConfigFalse_outputsFalse() = runBlocking(IMMEDIATE) {
|
fun wifiIcon_allLocationViewModelsReceiveSameData() = runBlocking(IMMEDIATE) {
|
||||||
whenever(constants.shouldShowActivityConfig).thenReturn(false)
|
var latestHome: Icon? = null
|
||||||
wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK)
|
val jobHome = underTest
|
||||||
|
.home
|
||||||
var latest: Boolean? = null
|
.wifiIcon
|
||||||
val job = underTest
|
.onEach { latestHome = it }
|
||||||
.isActivityInVisible
|
|
||||||
.onEach { latest = it }
|
|
||||||
.launchIn(this)
|
.launchIn(this)
|
||||||
|
|
||||||
// Verify that on launch, we receive a false.
|
var latestKeyguard: Icon? = null
|
||||||
assertThat(latest).isFalse()
|
val jobKeyguard = underTest
|
||||||
|
.keyguard
|
||||||
job.cancel()
|
.wifiIcon
|
||||||
}
|
.onEach { latestKeyguard = it }
|
||||||
|
|
||||||
@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)
|
.launchIn(this)
|
||||||
|
|
||||||
// Update the repo to have activityIn
|
var latestQs: Icon? = null
|
||||||
wifiRepository.setWifiActivity(
|
val jobQs = underTest
|
||||||
WifiActivityModel(hasActivityIn = true, hasActivityOut = false)
|
.qs
|
||||||
|
.wifiIcon
|
||||||
|
.onEach { latestQs = it }
|
||||||
|
.launchIn(this)
|
||||||
|
|
||||||
|
wifiRepository.setWifiNetwork(
|
||||||
|
WifiNetworkModel.Active(
|
||||||
|
NETWORK_ID,
|
||||||
|
isValidated = true,
|
||||||
|
level = 1
|
||||||
|
)
|
||||||
)
|
)
|
||||||
yield()
|
yield()
|
||||||
|
|
||||||
// Verify that we didn't update to activityIn=true (because our config is false)
|
assertThat(latestHome).isInstanceOf(Icon.Resource::class.java)
|
||||||
assertThat(latest).isFalse()
|
assertThat(latestHome).isEqualTo(latestKeyguard)
|
||||||
|
assertThat(latestKeyguard).isEqualTo(latestQs)
|
||||||
|
|
||||||
job.cancel()
|
jobHome.cancel()
|
||||||
|
jobKeyguard.cancel()
|
||||||
|
jobQs.cancel()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@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)
|
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)
|
wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK)
|
||||||
|
|
||||||
var latest: Boolean? = null
|
var latest: Boolean? = null
|
||||||
val job = underTest
|
val job = underTest
|
||||||
.isActivityInVisible
|
.home
|
||||||
|
.isActivityInViewVisible
|
||||||
.onEach { latest = it }
|
.onEach { latest = it }
|
||||||
.launchIn(this)
|
.launchIn(this)
|
||||||
|
|
||||||
// Update the repo to have activityIn
|
val activity = WifiActivityModel(hasActivityIn = true, hasActivityOut = false)
|
||||||
wifiRepository.setWifiActivity(
|
wifiRepository.setWifiActivity(activity)
|
||||||
WifiActivityModel(hasActivityIn = true, hasActivityOut = false)
|
|
||||||
)
|
|
||||||
yield()
|
yield()
|
||||||
|
|
||||||
// Verify that we updated to activityIn=true
|
|
||||||
assertThat(latest).isTrue()
|
assertThat(latest).isTrue()
|
||||||
|
|
||||||
job.cancel()
|
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? {
|
private fun ContentDescription.getAsString(): String? {
|
||||||
return when (this) {
|
return when (this) {
|
||||||
is ContentDescription.Loaded -> this.description
|
is ContentDescription.Loaded -> this.description
|
||||||
|
|||||||
Reference in New Issue
Block a user