Merge "Split up KeyguardBottomArea" into udc-qpr-dev
diff --git a/core/java/android/service/dreams/DreamService.java b/core/java/android/service/dreams/DreamService.java
index 9b19937..7d9c0a3 100644
--- a/core/java/android/service/dreams/DreamService.java
+++ b/core/java/android/service/dreams/DreamService.java
@@ -244,13 +244,7 @@
private DreamOverlayConnectionHandler mOverlayConnection;
- private final IDreamOverlayCallback mOverlayCallback = new IDreamOverlayCallback.Stub() {
- @Override
- public void onExitRequested() {
- // Simply finish dream when exit is requested.
- mHandler.post(() -> finish());
- }
- };
+ private IDreamOverlayCallback mOverlayCallback;
public DreamService() {
@@ -877,6 +871,13 @@
mDreamComponent = new ComponentName(this, getClass());
mShouldShowComplications = fetchShouldShowComplications(this /*context*/,
fetchServiceInfo(this /*context*/, mDreamComponent));
+ mOverlayCallback = new IDreamOverlayCallback.Stub() {
+ @Override
+ public void onExitRequested() {
+ // Simply finish dream when exit is requested.
+ mHandler.post(() -> finish());
+ }
+ };
super.onCreate();
}
@@ -1083,7 +1084,7 @@
// Just in case destroy came in before detach, let's take care of that now
detach();
-
+ mOverlayCallback = null;
super.onDestroy();
}
diff --git a/core/java/android/view/ViewConfiguration.java b/core/java/android/view/ViewConfiguration.java
index d80819f..0e7c6d1 100644
--- a/core/java/android/view/ViewConfiguration.java
+++ b/core/java/android/view/ViewConfiguration.java
@@ -181,7 +181,7 @@
private static final int TOUCH_SLOP = 8;
/** Distance a stylus touch can wander before we think the user is handwriting in dips. */
- private static final int HANDWRITING_SLOP = 4;
+ private static final int HANDWRITING_SLOP = 2;
/**
* Defines the minimum size of the touch target for a scrollbar in dips
diff --git a/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java b/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java
index 34eac35..8028b14 100644
--- a/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java
+++ b/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java
@@ -75,7 +75,7 @@
private static final int HW_BOUNDS_OFFSETS_TOP_PX = 20;
private static final int HW_BOUNDS_OFFSETS_RIGHT_PX = 30;
private static final int HW_BOUNDS_OFFSETS_BOTTOM_PX = 40;
- private int mHandwritingSlop = 4;
+ private int mHandwritingSlop = 2;
private static final Rect sHwArea1;
private static final Rect sHwArea2;
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index e2599a3..17037b0 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -307,6 +307,8 @@
"tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositorySwitcherTest.kt",
"tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/DisabledWifiRepositoryTest.kt",
"tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt",
+ "tests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorImplTest.kt",
+ "tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt",
],
path: "tests/src",
}
diff --git a/packages/SystemUI/TEST_MAPPING b/packages/SystemUI/TEST_MAPPING
index bb8002a..2711dad 100644
--- a/packages/SystemUI/TEST_MAPPING
+++ b/packages/SystemUI/TEST_MAPPING
@@ -35,6 +35,9 @@
},
{
"exclude-annotation": "android.platform.test.annotations.FlakyTest"
+ },
+ {
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
}
]
}
@@ -142,6 +145,9 @@
},
{
"exclude-annotation": "android.platform.test.annotations.FlakyTest"
+ },
+ {
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
}
]
}
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/DarkIconDispatcher.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/DarkIconDispatcher.java
index b33c544..a5e5aaa 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/DarkIconDispatcher.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/DarkIconDispatcher.java
@@ -26,6 +26,7 @@
import com.android.systemui.plugins.annotations.ProvidesInterface;
import java.util.ArrayList;
+import java.util.Collection;
/**
* Dispatches events to {@link DarkReceiver}s about changes in darkness, tint area and dark
@@ -78,7 +79,7 @@
* @return the tint to apply to view depending on the desired tint color and
* the screen tintArea in which to apply that tint
*/
- static int getTint(ArrayList<Rect> tintAreas, View view, int color) {
+ static int getTint(Collection<Rect> tintAreas, View view, int color) {
if (isInAreas(tintAreas, view)) {
return color;
} else {
@@ -90,7 +91,7 @@
* @return true if more than half of the view area are in any of the given
* areas, false otherwise
*/
- static boolean isInAreas(ArrayList<Rect> areas, View view) {
+ static boolean isInAreas(Collection<Rect> areas, View view) {
if (areas.isEmpty()) {
return true;
}
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index ab75498..9f4fc39 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -26,6 +26,8 @@
<color name="qs_detail_button_white">#B3FFFFFF</color><!-- 70% white -->
<color name="status_bar_clock_color">#FFFFFFFF</color>
<color name="qs_tile_disabled_color">#9E9E9E</color> <!-- 38% black -->
+ <color name="status_bar_icons_hover_color_light">#38FFFFFF</color> <!-- 22% white -->
+ <color name="status_bar_icons_hover_color_dark">#38000000</color> <!-- 22% black -->
<!-- The color of the background in the separated list of the Global Actions menu -->
<color name="global_actions_separated_background">#F5F5F5</color>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index d7a4b25..de8287e 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -475,6 +475,8 @@
<!-- Margin start of the system icons super container -->
<dimen name="system_icons_super_container_margin_start">16dp</dimen>
+ <dimen name="status_icons_hover_state_background_radius">16dp</dimen>
+
<!-- Width for the notification panel and related windows -->
<dimen name="match_parent">-1px</dimen>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 983b09f..ee9b132 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -439,6 +439,8 @@
<string name="face_reenroll_failure_dialog_content">Couldn\u2019t set up face unlock. Go to Settings to try again.</string>
<!-- Message shown when the system-provided fingerprint dialog is shown, asking for authentication -->
<string name="fingerprint_dialog_touch_sensor">Touch the fingerprint sensor</string>
+ <!-- Content description after successful auth when confirmation required -->
+ <string name="fingerprint_dialog_authenticated_confirmation">Press the unlock icon to continue</string>
<!-- Message shown to inform the user a face cannot be recognized and fingerprint should instead be used.[CHAR LIMIT=50] -->
<string name="fingerprint_dialog_use_fingerprint_instead">Can\u2019t recognize face. Use fingerprint instead.</string>
<!-- Message shown to inform the user a face cannot be recognized and fingerprint should instead be used.[CHAR LIMIT=50] -->
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index f1cb37c..53229b9 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -3563,6 +3563,7 @@
*/
@VisibleForTesting
void handleUserSwitching(int userId, CountDownLatch latch) {
+ mLogger.logUserSwitching(userId, "from UserTracker");
Assert.isMainThread();
clearBiometricRecognized();
boolean trustUsuallyManaged = mTrustManager.isTrustUsuallyManaged(userId);
@@ -3583,6 +3584,7 @@
*/
@VisibleForTesting
void handleUserSwitchComplete(int userId) {
+ mLogger.logUserSwitchComplete(userId, "from UserTracker");
Assert.isMainThread();
for (int i = 0; i < mCallbacks.size(); i++) {
KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
@@ -4036,6 +4038,11 @@
@AnyThread
public void setSwitchingUser(boolean switching) {
+ if (switching) {
+ mLogger.logUserSwitching(getCurrentUser(), "from setSwitchingUser");
+ } else {
+ mLogger.logUserSwitchComplete(getCurrentUser(), "from setSwitchingUser");
+ }
mSwitchingUser = switching;
// Since this comes in on a binder thread, we need to post it first
mHandler.post(() -> updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE,
diff --git a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
index eec5b3e..c26e546 100644
--- a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
+++ b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
@@ -602,10 +602,10 @@
fun allowFingerprintOnCurrentOccludingActivityChanged(allow: Boolean) {
logBuffer.log(
- TAG,
- VERBOSE,
- { bool1 = allow },
- { "allowFingerprintOnCurrentOccludingActivityChanged: $bool1" }
+ TAG,
+ VERBOSE,
+ { bool1 = allow },
+ { "allowFingerprintOnCurrentOccludingActivityChanged: $bool1" }
)
}
@@ -727,4 +727,28 @@
{ "notifying about enrollments changed: $str1" }
)
}
+
+ fun logUserSwitching(userId: Int, context: String) {
+ logBuffer.log(
+ TAG,
+ DEBUG,
+ {
+ int1 = userId
+ str1 = context
+ },
+ { "userCurrentlySwitching: $str1, userId: $int1" }
+ )
+ }
+
+ fun logUserSwitchComplete(userId: Int, context: String) {
+ logBuffer.log(
+ TAG,
+ DEBUG,
+ {
+ int1 = userId
+ str1 = context
+ },
+ { "userSwitchComplete: $str1, userId: $int1" }
+ )
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt
index 9807b9e..a1b15f44 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt
@@ -142,13 +142,18 @@
STATE_IDLE,
STATE_AUTHENTICATING_ANIMATING_IN,
STATE_AUTHENTICATING,
- STATE_PENDING_CONFIRMATION,
STATE_AUTHENTICATED ->
if (isSideFps) {
R.string.security_settings_sfps_enroll_find_sensor_message
} else {
R.string.fingerprint_dialog_touch_sensor
}
+ STATE_PENDING_CONFIRMATION ->
+ if (isSideFps) {
+ R.string.security_settings_sfps_enroll_find_sensor_message
+ } else {
+ R.string.fingerprint_dialog_authenticated_confirmation
+ }
STATE_ERROR,
STATE_HELP -> R.string.biometric_dialog_try_again
else -> null
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt
index 37ce444..11418e6 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt
@@ -50,7 +50,9 @@
import com.airbnb.lottie.LottieAnimationView
import com.airbnb.lottie.LottieProperty
import com.airbnb.lottie.model.KeyPath
+import com.android.app.animation.Interpolators
import com.android.internal.annotations.VisibleForTesting
+import com.android.keyguard.KeyguardPINView
import com.android.systemui.Dumpable
import com.android.systemui.R
import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor
@@ -112,7 +114,7 @@
private val isReverseDefaultRotation =
context.resources.getBoolean(com.android.internal.R.bool.config_reverseDefaultRotation)
- private var overlayHideAnimator: ViewPropertyAnimator? = null
+ private var overlayShowAnimator: ViewPropertyAnimator? = null
private var overlayView: View? = null
set(value) {
@@ -122,13 +124,23 @@
windowManager.removeView(oldView)
orientationListener.disable()
}
- overlayHideAnimator?.cancel()
- overlayHideAnimator = null
+ overlayShowAnimator?.cancel()
+ overlayShowAnimator = null
field = value
field?.let { newView ->
+ if (requests.contains(SideFpsUiRequestSource.PRIMARY_BOUNCER)) {
+ newView.alpha = 0f
+ overlayShowAnimator =
+ newView
+ .animate()
+ .alpha(1f)
+ .setDuration(KeyguardPINView.ANIMATION_DURATION)
+ .setInterpolator(Interpolators.ALPHA_IN)
+ }
windowManager.addView(newView, overlayViewParams)
orientationListener.enable()
+ overlayShowAnimator?.start()
}
}
@VisibleForTesting var overlayOffsets: SensorLocationInternal = SensorLocationInternal.DEFAULT
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/PluginModule.java b/packages/SystemUI/src/com/android/systemui/dagger/PluginModule.java
index a3e26b8..d727a70 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/PluginModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/PluginModule.java
@@ -28,6 +28,7 @@
import com.android.systemui.statusbar.StatusBarStateControllerImpl;
import com.android.systemui.statusbar.phone.ActivityStarterImpl;
import com.android.systemui.statusbar.phone.DarkIconDispatcherImpl;
+import com.android.systemui.statusbar.phone.SysuiDarkIconDispatcher;
import com.android.systemui.volume.VolumeDialogControllerImpl;
import dagger.Binds;
@@ -49,6 +50,10 @@
@Binds
abstract DarkIconDispatcher provideDarkIconDispatcher(DarkIconDispatcherImpl controllerImpl);
+ @Binds
+ abstract SysuiDarkIconDispatcher provideSysuiDarkIconDispatcher(
+ DarkIconDispatcherImpl controllerImpl);
+
/** */
@Binds
abstract FalsingManager provideFalsingManager(FalsingManagerProxy falsingManagerImpl);
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 66813f9..f3339c0 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -354,6 +354,10 @@
// TODO(b/280426085): Tracking Bug
@JvmField val NEW_BLUETOOTH_REPOSITORY = releasedFlag(612, "new_bluetooth_repository")
+ // TODO(b/292533677): Tracking Bug
+ val WIFI_TRACKER_LIB_FOR_WIFI_ICON =
+ unreleasedFlag(613, "wifi_tracker_lib_for_wifi_icon")
+
// 700 - dialer/calls
// TODO(b/254512734): Tracking Bug
val ONGOING_CALL_STATUS_BAR_CHIP = releasedFlag(700, "ongoing_call_status_bar_chip")
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BiometricMessageInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BiometricMessageInteractor.kt
index c849b84..508f71a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BiometricMessageInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BiometricMessageInteractor.kt
@@ -20,6 +20,7 @@
import android.content.res.Resources
import android.hardware.biometrics.BiometricSourceType
import android.hardware.biometrics.BiometricSourceType.FINGERPRINT
+import android.hardware.fingerprint.FingerprintManager
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.keyguard.KeyguardUpdateMonitor.BIOMETRIC_HELP_FINGERPRINT_NOT_RECOGNIZED
import com.android.systemui.biometrics.data.repository.FingerprintPropertyRepository
@@ -129,7 +130,14 @@
val type: BiometricMessageType,
val id: Int,
val message: String?,
-)
+) {
+ fun isFingerprintLockoutMessage(): Boolean {
+ return source == FINGERPRINT &&
+ type == BiometricMessageType.ERROR &&
+ (id == FingerprintManager.FINGERPRINT_ERROR_LOCKOUT ||
+ id == FingerprintManager.FINGERPRINT_ERROR_LOCKOUT_PERMANENT)
+ }
+}
enum class BiometricMessageType {
HELP,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/OccludingAppDeviceEntryInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/OccludingAppDeviceEntryInteractor.kt
index a2287c7..ff8d5c9 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/OccludingAppDeviceEntryInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/OccludingAppDeviceEntryInteractor.kt
@@ -18,7 +18,6 @@
import android.content.Context
import android.content.Intent
-import android.hardware.fingerprint.FingerprintManager
import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
import com.android.systemui.dagger.SysUISingleton
@@ -35,6 +34,7 @@
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.filterNot
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
@@ -74,15 +74,13 @@
private val fingerprintLockoutEvents: Flow<Unit> =
fingerprintAuthRepository.authenticationStatus
.ifKeyguardOccludedByApp()
- .filter {
- it is ErrorFingerprintAuthenticationStatus &&
- (it.msgId == FingerprintManager.FINGERPRINT_ERROR_LOCKOUT ||
- it.msgId == FingerprintManager.FINGERPRINT_ERROR_LOCKOUT_PERMANENT)
- }
+ .filter { it is ErrorFingerprintAuthenticationStatus && it.isLockoutMessage() }
.map {} // maps FingerprintAuthenticationStatus => Unit
val message: Flow<BiometricMessage?> =
merge(
- biometricMessageInteractor.fingerprintErrorMessage,
+ biometricMessageInteractor.fingerprintErrorMessage.filterNot {
+ it.isFingerprintLockoutMessage()
+ },
biometricMessageInteractor.fingerprintFailMessage,
biometricMessageInteractor.fingerprintHelpMessage,
)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FingerprintAuthenticationModels.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FingerprintAuthenticationModels.kt
index 7fc6016..ae18681 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FingerprintAuthenticationModels.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FingerprintAuthenticationModels.kt
@@ -16,6 +16,7 @@
package com.android.systemui.keyguard.shared.model
+import android.hardware.fingerprint.FingerprintManager
import android.os.SystemClock.elapsedRealtime
/**
@@ -49,4 +50,9 @@
val msg: String? = null,
// present to break equality check if the same error occurs repeatedly.
val createdAt: Long = elapsedRealtime(),
-) : FingerprintAuthenticationStatus()
+) : FingerprintAuthenticationStatus() {
+ fun isLockoutMessage(): Boolean {
+ return msgId == FingerprintManager.FINGERPRINT_ERROR_LOCKOUT ||
+ msgId == FingerprintManager.FINGERPRINT_ERROR_LOCKOUT_PERMANENT
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootView.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootView.kt
index b891792..8601b3d 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootView.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootView.kt
@@ -24,8 +24,10 @@
viewModel: SceneContainerViewModel,
containerConfig: SceneContainerConfig,
scenes: Set<Scene>,
+ layoutInsetController: LayoutInsetsController,
) {
this.viewModel = viewModel
+ setLayoutInsetsController(layoutInsetController)
SceneWindowRootViewBinder.bind(
view = this@SceneWindowRootView,
viewModel = viewModel,
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/view/WindowRootView.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/view/WindowRootView.kt
index 11e1eb9..4afe679 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/ui/view/WindowRootView.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/ui/view/WindowRootView.kt
@@ -1,9 +1,15 @@
package com.android.systemui.scene.ui.view
+import android.annotation.SuppressLint
import android.content.Context
import android.util.AttributeSet
+import android.util.Pair
+import android.view.DisplayCutout
import android.view.View
+import android.view.WindowInsets
import android.widget.FrameLayout
+import androidx.core.view.updateMargins
+import com.android.systemui.R
import com.android.systemui.compose.ComposeFacade
/**
@@ -25,6 +31,10 @@
attrs,
) {
+ private lateinit var layoutInsetsController: LayoutInsetsController
+ private var leftInset = 0
+ private var rightInset = 0
+
override fun onAttachedToWindow() {
super.onAttachedToWindow()
@@ -41,6 +51,67 @@
}
}
+ override fun generateLayoutParams(attrs: AttributeSet?): FrameLayout.LayoutParams? {
+ return LayoutParams(context, attrs)
+ }
+
+ override fun generateDefaultLayoutParams(): FrameLayout.LayoutParams? {
+ return LayoutParams(
+ FrameLayout.LayoutParams.MATCH_PARENT,
+ FrameLayout.LayoutParams.MATCH_PARENT
+ )
+ }
+
+ override fun onApplyWindowInsets(windowInsets: WindowInsets): WindowInsets? {
+ val insets = windowInsets.getInsetsIgnoringVisibility(WindowInsets.Type.systemBars())
+ if (fitsSystemWindows) {
+ val paddingChanged = insets.top != paddingTop || insets.bottom != paddingBottom
+
+ // Drop top inset, and pass through bottom inset.
+ if (paddingChanged) {
+ setPadding(0, 0, 0, 0)
+ }
+ } else {
+ val changed =
+ paddingLeft != 0 || paddingRight != 0 || paddingTop != 0 || paddingBottom != 0
+ if (changed) {
+ setPadding(0, 0, 0, 0)
+ }
+ }
+ leftInset = 0
+ rightInset = 0
+
+ val displayCutout = rootWindowInsets.displayCutout
+ val pairInsets: Pair<Int, Int> =
+ layoutInsetsController.getinsets(windowInsets, displayCutout)
+ leftInset = pairInsets.first
+ rightInset = pairInsets.second
+ applyMargins()
+ return windowInsets
+ }
+
+ fun setLayoutInsetsController(layoutInsetsController: LayoutInsetsController) {
+ this.layoutInsetsController = layoutInsetsController
+ }
+
+ private fun applyMargins() {
+ val count = childCount
+ for (i in 0 until count) {
+ val child = getChildAt(i)
+ if (child.layoutParams is LayoutParams) {
+ val layoutParams = child.layoutParams as LayoutParams
+ if (
+ !layoutParams.ignoreRightInset &&
+ (layoutParams.rightMargin != rightInset ||
+ layoutParams.leftMargin != leftInset)
+ ) {
+ layoutParams.updateMargins(left = leftInset, right = rightInset)
+ child.requestLayout()
+ }
+ }
+ }
+ }
+
/**
* Returns `true` if this view is the true root of the view-hierarchy; `false` otherwise.
*
@@ -50,4 +121,44 @@
// TODO(b/283300105): remove this check once there's only one subclass of WindowRootView.
return parent.let { it !is View || it.id == android.R.id.content }
}
+
+ /** Controller responsible for calculating insets for the shade window. */
+ interface LayoutInsetsController {
+
+ /** Update the insets and calculate them accordingly. */
+ fun getinsets(
+ windowInsets: WindowInsets?,
+ displayCutout: DisplayCutout?,
+ ): Pair<Int, Int>
+ }
+
+ private class LayoutParams : FrameLayout.LayoutParams {
+ var ignoreRightInset = false
+
+ constructor(
+ width: Int,
+ height: Int,
+ ) : super(
+ width,
+ height,
+ )
+
+ @SuppressLint("CustomViewStyleable")
+ constructor(
+ context: Context,
+ attrs: AttributeSet?,
+ ) : super(
+ context,
+ attrs,
+ ) {
+ val obtainedAttributes =
+ context.obtainStyledAttributes(attrs, R.styleable.StatusBarWindowView_Layout)
+ ignoreRightInset =
+ obtainedAttributes.getBoolean(
+ R.styleable.StatusBarWindowView_Layout_ignoreRightInset,
+ false
+ )
+ obtainedAttributes.recycle()
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java
index 2b62b7d..a9c4aeb 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java
@@ -17,19 +17,15 @@
package com.android.systemui.shade;
import static android.os.Trace.TRACE_TAG_APP;
-import static android.view.WindowInsets.Type.systemBars;
import static com.android.systemui.statusbar.phone.CentralSurfaces.DEBUG;
import android.annotation.ColorInt;
import android.annotation.DrawableRes;
import android.annotation.LayoutRes;
-import android.annotation.Nullable;
import android.content.Context;
import android.content.res.Configuration;
-import android.content.res.TypedArray;
import android.graphics.Canvas;
-import android.graphics.Insets;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
@@ -37,9 +33,7 @@
import android.os.Bundle;
import android.os.Trace;
import android.util.AttributeSet;
-import android.util.Pair;
import android.view.ActionMode;
-import android.view.DisplayCutout;
import android.view.InputQueue;
import android.view.KeyEvent;
import android.view.LayoutInflater;
@@ -51,13 +45,10 @@
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.view.Window;
-import android.view.WindowInsets;
import android.view.WindowInsetsController;
-import android.widget.FrameLayout;
import com.android.internal.view.FloatingActionMode;
import com.android.internal.widget.floatingtoolbar.FloatingToolbar;
-import com.android.systemui.R;
import com.android.systemui.scene.ui.view.WindowRootView;
/**
@@ -68,9 +59,6 @@
public class NotificationShadeWindowView extends WindowRootView {
public static final String TAG = "NotificationShadeWindowView";
- private int mRightInset = 0;
- private int mLeftInset = 0;
-
// Implements the floating action mode for TextView's Cut/Copy/Past menu. Normally provided by
// DecorView, but since this is a special window we have to roll our own.
private View mFloatingActionModeOriginatingView;
@@ -79,7 +67,6 @@
private ViewTreeObserver.OnPreDrawListener mFloatingToolbarPreDrawListener;
private InteractionEventHandler mInteractionEventHandler;
- private LayoutInsetsController mLayoutInsetProvider;
public NotificationShadeWindowView(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -87,64 +74,6 @@
}
@Override
- public WindowInsets onApplyWindowInsets(WindowInsets windowInsets) {
- final Insets insets = windowInsets.getInsetsIgnoringVisibility(systemBars());
- if (getFitsSystemWindows()) {
- boolean paddingChanged = insets.top != getPaddingTop()
- || insets.bottom != getPaddingBottom();
-
- // Drop top inset, and pass through bottom inset.
- if (paddingChanged) {
- setPadding(0, 0, 0, 0);
- }
- } else {
- boolean changed = getPaddingLeft() != 0
- || getPaddingRight() != 0
- || getPaddingTop() != 0
- || getPaddingBottom() != 0;
- if (changed) {
- setPadding(0, 0, 0, 0);
- }
- }
-
- mLeftInset = 0;
- mRightInset = 0;
- DisplayCutout displayCutout = getRootWindowInsets().getDisplayCutout();
- Pair<Integer, Integer> pairInsets = mLayoutInsetProvider
- .getinsets(windowInsets, displayCutout);
- mLeftInset = pairInsets.first;
- mRightInset = pairInsets.second;
- applyMargins();
- return windowInsets;
- }
-
- private void applyMargins() {
- final int count = getChildCount();
- for (int i = 0; i < count; i++) {
- View child = getChildAt(i);
- if (child.getLayoutParams() instanceof LayoutParams) {
- LayoutParams lp = (LayoutParams) child.getLayoutParams();
- if (!lp.ignoreRightInset
- && (lp.rightMargin != mRightInset || lp.leftMargin != mLeftInset)) {
- lp.rightMargin = mRightInset;
- lp.leftMargin = mLeftInset;
- child.requestLayout();
- }
- }
- }
- }
-
- @Override
- public FrameLayout.LayoutParams generateLayoutParams(AttributeSet attrs) {
- return new LayoutParams(getContext(), attrs);
- }
-
- @Override
- protected FrameLayout.LayoutParams generateDefaultLayoutParams() {
- return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
- }
-
- @Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
setWillNotDraw(!DEBUG);
@@ -172,10 +101,6 @@
mInteractionEventHandler = listener;
}
- protected void setLayoutInsetsController(LayoutInsetsController provider) {
- mLayoutInsetProvider = provider;
- }
-
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
Boolean result = mInteractionEventHandler.handleDispatchTouchEvent(ev);
@@ -227,24 +152,6 @@
}
}
- private static class LayoutParams extends FrameLayout.LayoutParams {
-
- public boolean ignoreRightInset;
-
- LayoutParams(int width, int height) {
- super(width, height);
- }
-
- LayoutParams(Context c, AttributeSet attrs) {
- super(c, attrs);
-
- TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.StatusBarWindowView_Layout);
- ignoreRightInset = a.getBoolean(
- R.styleable.StatusBarWindowView_Layout_ignoreRightInset, false);
- a.recycle();
- }
- }
-
@Override
public ActionMode startActionModeForChild(View originalView, ActionMode.Callback callback,
int type) {
@@ -357,18 +264,6 @@
}
}
- /**
- * Controller responsible for calculating insets for the shade window.
- */
- public interface LayoutInsetsController {
-
- /**
- * Update the insets and calculate them accordingly.
- */
- Pair<Integer, Integer> getinsets(@Nullable WindowInsets windowInsets,
- @Nullable DisplayCutout displayCutout);
- }
-
interface InteractionEventHandler {
/**
* Returns a result for {@link ViewGroup#dispatchTouchEvent(MotionEvent)} or null to defer
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt
index 529f12e..c6cb9c4 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt
@@ -61,6 +61,7 @@
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.StatusOverlayHoverListenerFactory
import com.android.systemui.statusbar.policy.Clock
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.statusbar.policy.NextAlarmController
@@ -99,6 +100,7 @@
private val qsBatteryModeController: QsBatteryModeController,
private val nextAlarmController: NextAlarmController,
private val activityStarter: ActivityStarter,
+ private val statusOverlayHoverListenerFactory: StatusOverlayHoverListenerFactory,
) : ViewController<View>(header), Dumpable {
companion object {
@@ -326,6 +328,9 @@
demoModeController.addCallback(demoModeReceiver)
statusBarIconController.addIconGroup(iconManager)
nextAlarmController.addCallback(nextAlarmCallback)
+ systemIcons.setOnHoverListener(
+ statusOverlayHoverListenerFactory.createListener(systemIcons)
+ )
}
override fun onViewDetached() {
@@ -336,6 +341,7 @@
demoModeController.removeCallback(demoModeReceiver)
statusBarIconController.removeIconGroup(iconManager)
nextAlarmController.removeCallback(nextAlarmCallback)
+ systemIcons.setOnHoverListener(null)
}
fun disable(state1: Int, state2: Int, animate: Boolean) {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt
index fc6479e..e02c427 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt
@@ -42,6 +42,7 @@
import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel
import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.LightRevealScrim
+import com.android.systemui.statusbar.NotificationInsetsController
import com.android.systemui.statusbar.NotificationShelf
import com.android.systemui.statusbar.NotificationShelfController
import com.android.systemui.statusbar.notification.row.dagger.NotificationShelfComponent
@@ -78,6 +79,7 @@
containerConfigProvider: Provider<SceneContainerConfig>,
@Named(SceneContainerNames.SYSTEM_UI_DEFAULT)
scenesProvider: Provider<Set<@JvmSuppressWildcards Scene>>,
+ layoutInsetController: NotificationInsetsController,
): WindowRootView {
return if (
featureFlags.isEnabled(Flags.SCENE_CONTAINER) && ComposeFacade.isComposeAvailable()
@@ -88,6 +90,7 @@
viewModel = viewModelProvider.get(),
containerConfig = containerConfigProvider.get(),
scenes = scenesProvider.get(),
+ layoutInsetController = layoutInsetController,
)
sceneWindowRootView
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInsetsController.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInsetsController.java
index 39d7d66..27f42c6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInsetsController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInsetsController.java
@@ -16,11 +16,11 @@
package com.android.systemui.statusbar;
-import com.android.systemui.shade.NotificationShadeWindowView;
+import com.android.systemui.scene.ui.view.WindowRootView;
/**
* Calculates insets for the notification shade window view.
*/
public abstract class NotificationInsetsController
- implements NotificationShadeWindowView.LayoutInsetsController {
+ implements WindowRootView.LayoutInsetsController {
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/AccessPointControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/AccessPointControllerImpl.java
index c9ce12a..ff40f70 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/AccessPointControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/AccessPointControllerImpl.java
@@ -16,13 +16,7 @@
package com.android.systemui.statusbar.connectivity;
-import android.content.Context;
import android.content.Intent;
-import android.net.ConnectivityManager;
-import android.net.wifi.WifiManager;
-import android.os.Handler;
-import android.os.SimpleClock;
-import android.os.SystemClock;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
@@ -35,25 +29,18 @@
import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.LifecycleRegistry;
-import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.dagger.qualifiers.Background;
-import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.settings.UserTracker;
import com.android.wifitrackerlib.MergedCarrierEntry;
import com.android.wifitrackerlib.WifiEntry;
import com.android.wifitrackerlib.WifiPickerTracker;
import java.io.PrintWriter;
-import java.time.Clock;
-import java.time.ZoneOffset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.Executor;
-import javax.inject.Inject;
-
/** */
public class AccessPointControllerImpl implements AccessPointController,
WifiPickerTracker.WifiPickerTrackerCallback,
@@ -272,77 +259,4 @@
}
}
};
-
- /**
- * Factory for creating {@link WifiPickerTracker}.
- *
- * Uses the same time intervals as the Settings page for Wifi.
- */
- @SysUISingleton
- public static class WifiPickerTrackerFactory {
-
- // Max age of tracked WifiEntries
- private static final long MAX_SCAN_AGE_MILLIS = 15_000;
- // Interval between initiating WifiPickerTracker scans
- private static final long SCAN_INTERVAL_MILLIS = 10_000;
-
- private final Context mContext;
- private final @Nullable WifiManager mWifiManager;
- private final ConnectivityManager mConnectivityManager;
- private final Handler mMainHandler;
- private final Handler mWorkerHandler;
- private final Clock mClock = new SimpleClock(ZoneOffset.UTC) {
- @Override
- public long millis() {
- return SystemClock.elapsedRealtime();
- }
- };
-
- @Inject
- public WifiPickerTrackerFactory(
- Context context,
- @Nullable WifiManager wifiManager,
- ConnectivityManager connectivityManager,
- @Main Handler mainHandler,
- @Background Handler workerHandler
- ) {
- mContext = context;
- mWifiManager = wifiManager;
- mConnectivityManager = connectivityManager;
- mMainHandler = mainHandler;
- mWorkerHandler = workerHandler;
- }
-
- private boolean isSupported() {
- return mWifiManager != null;
- }
-
- /**
- * Create a {@link WifiPickerTracker}
- *
- * @param lifecycle
- * @param listener
- * @return a new {@link WifiPickerTracker} or {@code null} if {@link WifiManager} is null.
- */
- public @Nullable WifiPickerTracker create(
- Lifecycle lifecycle,
- WifiPickerTracker.WifiPickerTrackerCallback listener
- ) {
- if (mWifiManager == null) {
- return null;
- }
- return new WifiPickerTracker(
- lifecycle,
- mContext,
- mWifiManager,
- mConnectivityManager,
- mMainHandler,
- mWorkerHandler,
- mClock,
- MAX_SCAN_AGE_MILLIS,
- SCAN_INTERVAL_MILLIS,
- listener
- );
- }
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/WifiPickerTrackerFactory.kt b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/WifiPickerTrackerFactory.kt
new file mode 100644
index 0000000..ddbfcef
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/WifiPickerTrackerFactory.kt
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2023 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.connectivity
+
+import android.content.Context
+import android.net.ConnectivityManager
+import android.net.wifi.WifiManager
+import android.os.Handler
+import android.os.SimpleClock
+import androidx.lifecycle.Lifecycle
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.util.time.SystemClock
+import com.android.wifitrackerlib.WifiPickerTracker
+import com.android.wifitrackerlib.WifiPickerTracker.WifiPickerTrackerCallback
+import java.time.Clock
+import java.time.ZoneOffset
+import javax.inject.Inject
+
+/**
+ * Factory for creating [WifiPickerTracker] for SysUI.
+ *
+ * Uses the same time intervals as the Settings page for Wifi.
+ */
+@SysUISingleton
+class WifiPickerTrackerFactory
+@Inject
+constructor(
+ private val context: Context,
+ private val wifiManager: WifiManager?,
+ private val connectivityManager: ConnectivityManager,
+ private val systemClock: SystemClock,
+ @Main private val mainHandler: Handler,
+ @Background private val workerHandler: Handler,
+) {
+ private val clock: Clock =
+ object : SimpleClock(ZoneOffset.UTC) {
+ override fun millis(): Long {
+ return systemClock.elapsedRealtime()
+ }
+ }
+ val isSupported: Boolean
+ get() = wifiManager != null
+
+ /**
+ * Creates a [WifiPickerTracker] instance.
+ *
+ * @return a new [WifiPickerTracker] or null if [WifiManager] is null.
+ */
+ fun create(
+ lifecycle: Lifecycle,
+ listener: WifiPickerTrackerCallback,
+ ): WifiPickerTracker? {
+ return if (wifiManager == null) {
+ null
+ } else
+ WifiPickerTracker(
+ lifecycle,
+ context,
+ wifiManager,
+ connectivityManager,
+ mainHandler,
+ workerHandler,
+ clock,
+ MAX_SCAN_AGE_MILLIS,
+ SCAN_INTERVAL_MILLIS,
+ listener,
+ )
+ }
+
+ companion object {
+ /** Max age of tracked WifiEntries. */
+ private const val MAX_SCAN_AGE_MILLIS: Long = 15000
+ /** Interval between initiating WifiPickerTracker scans. */
+ private const val SCAN_INTERVAL_MILLIS: Long = 10000
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
index 1c7a186..f726c4e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
@@ -72,6 +72,7 @@
import com.android.systemui.statusbar.phone.StatusBarIconController;
import com.android.systemui.statusbar.phone.StatusBarIconControllerImpl;
import com.android.systemui.statusbar.phone.StatusBarIconList;
+import com.android.systemui.statusbar.phone.StatusBarNotificationPresenterModule;
import com.android.systemui.statusbar.phone.StatusBarRemoteInputCallback;
import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController;
import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallFlags;
@@ -96,7 +97,7 @@
* their own version of CentralSurfaces can include just dependencies, without injecting
* CentralSurfaces itself.
*/
-@Module
+@Module(includes = {StatusBarNotificationPresenterModule.class})
public interface CentralSurfacesDependenciesModule {
/** */
@SysUISingleton
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index 6eeb25f..f96fb26 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -623,7 +623,7 @@
private final ActivityLaunchAnimator mActivityLaunchAnimator;
private NotificationLaunchAnimatorControllerProvider mNotificationAnimationProvider;
- protected NotificationPresenter mPresenter;
+ private final NotificationPresenter mPresenter;
private NotificationActivityStarter mNotificationActivityStarter;
private final Lazy<NotificationShadeDepthController> mNotificationShadeDepthControllerLazy;
private final Optional<Bubbles> mBubblesOptional;
@@ -724,6 +724,7 @@
Lazy<NotificationShadeWindowViewController> notificationShadeWindowViewControllerLazy,
NotificationShelfController notificationShelfController,
NotificationStackScrollLayoutController notificationStackScrollLayoutController,
+ NotificationPresenter notificationPresenter,
DozeParameters dozeParameters,
ScrimController scrimController,
Lazy<LockscreenWallpaper> lockscreenWallpaperLazy,
@@ -830,6 +831,7 @@
mStackScrollerController = notificationStackScrollLayoutController;
mStackScroller = mStackScrollerController.getView();
mNotifListContainer = mStackScrollerController.getNotificationListContainer();
+ mPresenter = notificationPresenter;
mDozeServiceHost = dozeServiceHost;
mPowerManager = powerManager;
mDozeParameters = dozeParameters;
@@ -1604,7 +1606,6 @@
mShadeController.setNotificationShadeWindowViewController(
getNotificationShadeWindowViewController());
mBackActionInteractor.setup(mQsController, mShadeSurface);
- mPresenter = mCentralSurfacesComponent.getNotificationPresenter();
mNotificationActivityStarter = mCentralSurfacesComponent.getNotificationActivityStarter();
mHeadsUpManager.addListener(mCentralSurfacesComponent.getStatusBarHeadsUpChangeListener());
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DarkIconDispatcherImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DarkIconDispatcherImpl.java
index 2677c3f2..c4495ed 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DarkIconDispatcherImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DarkIconDispatcherImpl.java
@@ -32,6 +32,11 @@
import javax.inject.Inject;
+import kotlinx.coroutines.flow.FlowKt;
+import kotlinx.coroutines.flow.MutableStateFlow;
+import kotlinx.coroutines.flow.StateFlow;
+import kotlinx.coroutines.flow.StateFlowKt;
+
/**
*/
@SysUISingleton
@@ -47,6 +52,9 @@
private int mDarkModeIconColorSingleTone;
private int mLightModeIconColorSingleTone;
+ private final MutableStateFlow<DarkChange> mDarkChangeFlow = StateFlowKt.MutableStateFlow(
+ DarkChange.EMPTY);
+
/**
*/
@Inject
@@ -66,6 +74,11 @@
return mTransitionsController;
}
+ @Override
+ public StateFlow<DarkChange> darkChangeFlow() {
+ return FlowKt.asStateFlow(mDarkChangeFlow);
+ }
+
public void addDarkReceiver(DarkReceiver receiver) {
mReceivers.put(receiver, receiver);
receiver.onDarkChanged(mTintAreas, mDarkIntensity, mIconTint);
@@ -122,6 +135,7 @@
}
private void applyIconTint() {
+ mDarkChangeFlow.setValue(new DarkChange(mTintAreas, mDarkIntensity, mIconTint));
for (int i = 0; i < mReceivers.size(); i++) {
mReceivers.valueAt(i).onDarkChanged(mTintAreas, mDarkIntensity, mIconTint);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
index 720eeba..5c1f824b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
@@ -34,7 +34,6 @@
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
-import android.view.ViewTreeObserver;
import android.view.WindowInsets;
import android.widget.ImageView;
import android.widget.LinearLayout;
@@ -43,11 +42,11 @@
import androidx.annotation.VisibleForTesting;
-import com.android.app.animation.Interpolators;
import com.android.settingslib.Utils;
import com.android.systemui.R;
import com.android.systemui.battery.BatteryMeterView;
import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
+import com.android.systemui.statusbar.phone.SysuiDarkIconDispatcher.DarkChange;
import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherContainer;
import com.android.systemui.user.ui.binder.StatusBarUserChipViewBinder;
import com.android.systemui.user.ui.viewmodel.StatusBarUserChipViewModel;
@@ -55,6 +54,11 @@
import java.io.PrintWriter;
import java.util.ArrayList;
+import kotlinx.coroutines.flow.FlowKt;
+import kotlinx.coroutines.flow.MutableStateFlow;
+import kotlinx.coroutines.flow.StateFlow;
+import kotlinx.coroutines.flow.StateFlowKt;
+
/**
* The header group on Keyguard.
*/
@@ -83,6 +87,8 @@
private int mStatusBarPaddingEnd;
private int mMinDotWidth;
private View mSystemIconsContainer;
+ private final MutableStateFlow<DarkChange> mDarkChange = StateFlowKt.MutableStateFlow(
+ DarkChange.EMPTY);
private View mCutoutSpace;
private ViewGroup mStatusIconArea;
@@ -374,49 +380,6 @@
return mKeyguardUserAvatarEnabled;
}
- private void animateNextLayoutChange() {
- final int systemIconsCurrentX = mSystemIconsContainer.getLeft();
- final boolean userAvatarVisible = mMultiUserAvatar.getParent() == mStatusIconArea;
- getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
- @Override
- public boolean onPreDraw() {
- getViewTreeObserver().removeOnPreDrawListener(this);
- boolean userAvatarHiding = userAvatarVisible
- && mMultiUserAvatar.getParent() != mStatusIconArea;
- mSystemIconsContainer.setX(systemIconsCurrentX);
- mSystemIconsContainer.animate()
- .translationX(0)
- .setDuration(400)
- .setStartDelay(userAvatarHiding ? 300 : 0)
- .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
- .start();
- if (userAvatarHiding) {
- getOverlay().add(mMultiUserAvatar);
- mMultiUserAvatar.animate()
- .alpha(0f)
- .setDuration(300)
- .setStartDelay(0)
- .setInterpolator(Interpolators.ALPHA_OUT)
- .withEndAction(() -> {
- mMultiUserAvatar.setAlpha(1f);
- getOverlay().remove(mMultiUserAvatar);
- })
- .start();
-
- } else {
- mMultiUserAvatar.setAlpha(0f);
- mMultiUserAvatar.animate()
- .alpha(1f)
- .setDuration(300)
- .setStartDelay(200)
- .setInterpolator(Interpolators.ALPHA_IN);
- }
- return true;
- }
- });
-
- }
-
@Override
public void setVisibility(int visibility) {
super.setVisibility(visibility);
@@ -474,6 +437,7 @@
iconManager.setTint(iconColor);
}
+ mDarkChange.setValue(new DarkChange(mEmptyTintRect, intensity, iconColor));
applyDarkness(R.id.battery, mEmptyTintRect, intensity, iconColor);
applyDarkness(R.id.clock, mEmptyTintRect, intensity, iconColor);
}
@@ -536,4 +500,8 @@
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
Trace.endSection();
}
+
+ public StateFlow<DarkChange> darkChangeFlow() {
+ return FlowKt.asStateFlow(mDarkChange);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
index 680f19a..be336e5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
@@ -69,8 +69,6 @@
import com.android.systemui.util.ViewController;
import com.android.systemui.util.settings.SecureSettings;
-import kotlin.Unit;
-
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
@@ -78,6 +76,8 @@
import javax.inject.Inject;
+import kotlin.Unit;
+
/** View Controller for {@link com.android.systemui.statusbar.phone.KeyguardStatusBarView}. */
public class KeyguardStatusBarViewController extends ViewController<KeyguardStatusBarView> {
private static final String TAG = "KeyguardStatusBarViewController";
@@ -119,6 +119,9 @@
private final Object mLock = new Object();
private final KeyguardLogger mLogger;
+ private View mSystemIconsContainer;
+ private final StatusOverlayHoverListenerFactory mStatusOverlayHoverListenerFactory;
+
// TODO(b/273443374): remove
private NotificationMediaManager mNotificationMediaManager;
@@ -286,7 +289,8 @@
CommandQueue commandQueue,
@Main Executor mainExecutor,
KeyguardLogger logger,
- NotificationMediaManager notificationMediaManager
+ NotificationMediaManager notificationMediaManager,
+ StatusOverlayHoverListenerFactory statusOverlayHoverListenerFactory
) {
super(view);
mCarrierTextController = carrierTextController;
@@ -339,6 +343,7 @@
this::updateViewState
);
mNotificationMediaManager = notificationMediaManager;
+ mStatusOverlayHoverListenerFactory = statusOverlayHoverListenerFactory;
}
@Override
@@ -363,6 +368,10 @@
mTintedIconManager.setBlockList(getBlockedIcons());
mStatusBarIconController.addIconGroup(mTintedIconManager);
}
+ mSystemIconsContainer = mView.findViewById(R.id.system_icons);
+ StatusOverlayHoverListener hoverListener = mStatusOverlayHoverListenerFactory
+ .createDarkAwareListener(mSystemIconsContainer, mView.darkChangeFlow());
+ mSystemIconsContainer.setOnHoverListener(hoverListener);
mView.setOnApplyWindowInsetsListener(
(view, windowInsets) -> mView.updateWindowInsets(windowInsets, mInsetsProvider));
mSecureSettings.registerContentObserverForUser(
@@ -376,6 +385,7 @@
@Override
protected void onViewDetached() {
+ mSystemIconsContainer.setOnHoverListener(null);
mConfigurationController.removeCallback(mConfigurationListener);
mAnimationScheduler.removeCallback(mAnimationCallback);
mUserInfoController.removeCallback(mOnUserInfoChangedListener);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
index 4e136de..8c3050d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
@@ -63,9 +63,12 @@
private val userChipViewModel: StatusBarUserChipViewModel,
private val viewUtil: ViewUtil,
private val featureFlags: FeatureFlags,
- private val configurationController: ConfigurationController
+ private val configurationController: ConfigurationController,
+ private val statusOverlayHoverListenerFactory: StatusOverlayHoverListenerFactory,
) : ViewController<PhoneStatusBarView>(view) {
+ private lateinit var statusContainer: View
+
private val configurationListener = object : ConfigurationController.ConfigurationListener {
override fun onConfigChanged(newConfig: Configuration?) {
mView.updateResources()
@@ -73,6 +76,9 @@
}
override fun onViewAttached() {
+ statusContainer = mView.findViewById(R.id.system_icons)
+ statusContainer.setOnHoverListener(
+ statusOverlayHoverListenerFactory.createDarkAwareListener(statusContainer))
if (moveFromCenterAnimationController == null) return
val statusBarLeftSide: View = mView.findViewById(R.id.status_bar_start_side_except_heads_up)
@@ -104,6 +110,7 @@
}
override fun onViewDetached() {
+ statusContainer.setOnHoverListener(null)
progressProvider?.setReadyToHandleTransition(false)
moveFromCenterAnimationController?.onViewDetached()
configurationController.removeCallback(configurationListener)
@@ -245,6 +252,7 @@
private val shadeLogger: ShadeLogger,
private val viewUtil: ViewUtil,
private val configurationController: ConfigurationController,
+ private val statusOverlayHoverListenerFactory: StatusOverlayHoverListenerFactory,
) {
fun create(
view: PhoneStatusBarView
@@ -268,7 +276,8 @@
userChipViewModel,
viewUtil,
featureFlags,
- configurationController
+ configurationController,
+ statusOverlayHoverListenerFactory,
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
index ec0c00e..8de213f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
@@ -215,8 +215,7 @@
public void onNotificationClicked(NotificationEntry entry, ExpandableNotificationRow row) {
mLogger.logStartingActivityFromClick(entry);
- if (mRemoteInputManager.isRemoteInputActive(entry)
- && !TextUtils.isEmpty(row.getActiveRemoteInputText())) {
+ if (mRemoteInputManager.isRemoteInputActive(entry)) {
// We have an active remote input typed and the user clicked on the notification.
// this was probably unintentional, so we're closing the edit text instead.
mRemoteInputManager.closeRemoteInputs();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
index ad8530d..ecc996c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
@@ -32,6 +32,7 @@
import com.android.internal.statusbar.IStatusBarService;
import com.android.systemui.InitController;
import com.android.systemui.R;
+import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.ActivityStarter.OnDismissAction;
import com.android.systemui.power.domain.interactor.PowerInteractor;
@@ -59,12 +60,11 @@
import com.android.systemui.statusbar.notification.row.NotificationGutsManager.OnSettingsClickListener;
import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
-import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import javax.inject.Inject;
-@CentralSurfacesComponent.CentralSurfacesScope
+@SysUISingleton
class StatusBarNotificationPresenter implements NotificationPresenter, CommandQueue.Callbacks {
private static final String TAG = "StatusBarNotificationPresenter";
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusOverlayHoverListener.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusOverlayHoverListener.kt
new file mode 100644
index 0000000..881741a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusOverlayHoverListener.kt
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2023 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
+
+import android.content.res.Configuration
+import android.content.res.Resources
+import android.graphics.Color
+import android.graphics.drawable.PaintDrawable
+import android.view.MotionEvent
+import android.view.View
+import android.view.View.OnHoverListener
+import androidx.annotation.ColorInt
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.lifecycleScope
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.systemui.R
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.plugins.DarkIconDispatcher
+import com.android.systemui.statusbar.phone.SysuiDarkIconDispatcher.DarkChange
+import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.launch
+
+class StatusOverlayHoverListenerFactory
+@Inject
+constructor(
+ @Main private val resources: Resources,
+ private val configurationController: ConfigurationController,
+ private val darkIconDispatcher: SysuiDarkIconDispatcher,
+) {
+
+ /** Creates listener always using the same light color for overlay */
+ fun createListener(view: View) =
+ StatusOverlayHoverListener(
+ view,
+ configurationController,
+ resources,
+ flowOf(HoverTheme.LIGHT),
+ )
+
+ /**
+ * Creates listener using [DarkIconDispatcher] to determine light or dark color of the overlay
+ */
+ fun createDarkAwareListener(view: View) =
+ createDarkAwareListener(view, darkIconDispatcher.darkChangeFlow())
+
+ /**
+ * Creates listener using provided [DarkChange] producer to determine light or dark color of the
+ * overlay
+ */
+ fun createDarkAwareListener(view: View, darkFlow: StateFlow<DarkChange>) =
+ StatusOverlayHoverListener(
+ view,
+ configurationController,
+ resources,
+ darkFlow.map { toHoverTheme(view, it) },
+ )
+
+ private fun toHoverTheme(view: View, darkChange: DarkChange): HoverTheme {
+ val calculatedTint = DarkIconDispatcher.getTint(darkChange.areas, view, darkChange.tint)
+ // currently calculated tint is either white or some shade of black.
+ // So checking for Color.WHITE is deterministic compared to checking for Color.BLACK.
+ // In the future checking Color.luminance() might be more appropriate.
+ return if (calculatedTint == Color.WHITE) HoverTheme.LIGHT else HoverTheme.DARK
+ }
+}
+
+/**
+ * theme of hover drawable - it's different from device theme. This theme depends on view's
+ * background and/or dark value returned from [DarkIconDispatcher]
+ */
+enum class HoverTheme {
+ LIGHT,
+ DARK
+}
+
+/**
+ * [OnHoverListener] that adds [Drawable] overlay on top of the status icons when cursor/stylus
+ * starts hovering over them and removes overlay when status icons are no longer hovered
+ */
+class StatusOverlayHoverListener(
+ view: View,
+ configurationController: ConfigurationController,
+ private val resources: Resources,
+ private val themeFlow: Flow<HoverTheme>,
+) : OnHoverListener {
+
+ @ColorInt private var darkColor: Int = 0
+ @ColorInt private var lightColor: Int = 0
+ private var cornerRadius = 0f
+
+ private var lastTheme = HoverTheme.LIGHT
+
+ val backgroundColor
+ get() = if (lastTheme == HoverTheme.LIGHT) lightColor else darkColor
+
+ init {
+ view.repeatWhenAttached {
+ lifecycleScope.launch {
+ val configurationListener =
+ object : ConfigurationListener {
+ override fun onConfigChanged(newConfig: Configuration?) {
+ updateResources()
+ }
+ }
+ repeatOnLifecycle(Lifecycle.State.CREATED) {
+ configurationController.addCallback(configurationListener)
+ }
+ configurationController.removeCallback(configurationListener)
+ }
+ lifecycleScope.launch { themeFlow.collect { lastTheme = it } }
+ }
+ updateResources()
+ }
+
+ override fun onHover(v: View, event: MotionEvent): Boolean {
+ if (event.action == MotionEvent.ACTION_HOVER_ENTER) {
+ val drawable =
+ PaintDrawable(backgroundColor).apply {
+ setCornerRadius(cornerRadius)
+ setBounds(0, 0, v.width, v.height)
+ }
+ v.overlay.add(drawable)
+ } else if (event.action == MotionEvent.ACTION_HOVER_EXIT) {
+ v.overlay.clear()
+ }
+ return true
+ }
+
+ private fun updateResources() {
+ lightColor = resources.getColor(R.color.status_bar_icons_hover_color_light)
+ darkColor = resources.getColor(R.color.status_bar_icons_hover_color_dark)
+ cornerRadius = resources.getDimension(R.dimen.status_icons_hover_state_background_radius)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SysuiDarkIconDispatcher.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SysuiDarkIconDispatcher.java
index d537721..f5e9034 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SysuiDarkIconDispatcher.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SysuiDarkIconDispatcher.java
@@ -16,9 +16,16 @@
package com.android.systemui.statusbar.phone;
+import android.graphics.Rect;
+
import com.android.systemui.Dumpable;
import com.android.systemui.plugins.DarkIconDispatcher;
+import java.util.ArrayList;
+import java.util.Collection;
+
+import kotlinx.coroutines.flow.StateFlow;
+
/**
* Dispatches events to {@link DarkReceiver}s about changes in darkness, tint area
* and dark intensity.
@@ -29,4 +36,26 @@
* @return LightBarTransitionsController
*/
LightBarTransitionsController getTransitionsController();
+
+ /**
+ * Flow equivalent of registering {@link DarkReceiver} using
+ * {@link DarkIconDispatcher#addDarkReceiver(DarkReceiver)}
+ */
+ StateFlow<DarkChange> darkChangeFlow();
+
+ /** Model for {@link #darkChangeFlow()} */
+ class DarkChange {
+
+ public static final DarkChange EMPTY = new DarkChange(new ArrayList<>(), 0, 0);
+
+ public DarkChange(Collection<Rect> areas, float darkIntensity, int tint) {
+ this.areas = areas;
+ this.darkIntensity = darkIntensity;
+ this.tint = tint;
+ }
+
+ public final Collection<Rect> areas;
+ public final float darkIntensity;
+ public final int tint;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/CentralSurfacesComponent.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/CentralSurfacesComponent.java
index e77f419..3a3663d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/CentralSurfacesComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/CentralSurfacesComponent.java
@@ -22,13 +22,11 @@
import com.android.systemui.scene.ui.view.WindowRootView;
import com.android.systemui.shade.ShadeHeaderController;
-import com.android.systemui.statusbar.NotificationPresenter;
import com.android.systemui.statusbar.notification.NotificationActivityStarter;
import com.android.systemui.statusbar.phone.CentralSurfacesCommandQueueCallbacks;
import com.android.systemui.statusbar.phone.CentralSurfacesImpl;
import com.android.systemui.statusbar.phone.StatusBarHeadsUpChangeListener;
import com.android.systemui.statusbar.phone.StatusBarNotificationActivityStarterModule;
-import com.android.systemui.statusbar.phone.StatusBarNotificationPresenterModule;
import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragment;
import dagger.Subcomponent;
@@ -51,7 +49,6 @@
@Subcomponent(modules = {
StatusBarViewModule.class,
StatusBarNotificationActivityStarterModule.class,
- StatusBarNotificationPresenterModule.class,
})
@CentralSurfacesComponent.CentralSurfacesScope
public interface CentralSurfacesComponent {
@@ -97,6 +94,4 @@
CollapsedStatusBarFragment createCollapsedStatusBarFragment();
NotificationActivityStarter getNotificationActivityStarter();
-
- NotificationPresenter getNotificationPresenter();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
index 0e99c67..e1fd37f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
@@ -19,6 +19,8 @@
import android.net.wifi.WifiManager
import com.android.systemui.CoreStartable
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.LogBufferFactory
import com.android.systemui.log.table.TableLogBuffer
@@ -48,9 +50,12 @@
import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.CollapsedStatusBarViewModelImpl
import com.android.systemui.statusbar.pipeline.wifi.data.repository.RealWifiRepository
import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepositoryDagger
import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepositorySwitcher
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepositoryViaTrackerLibDagger
import com.android.systemui.statusbar.pipeline.wifi.data.repository.prod.DisabledWifiRepository
import com.android.systemui.statusbar.pipeline.wifi.data.repository.prod.WifiRepositoryImpl
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.prod.WifiRepositoryViaTrackerLib
import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractor
import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractorImpl
import dagger.Binds
@@ -114,14 +119,19 @@
impl: CollapsedStatusBarViewBinderImpl
): CollapsedStatusBarViewBinder
+ @Binds
+ @IntoMap
+ @ClassKey(WifiRepositoryDagger::class)
+ abstract fun bindWifiRepositoryDagger(impl: WifiRepositoryDagger): CoreStartable
+
companion object {
@Provides
@SysUISingleton
- fun provideRealWifiRepository(
+ fun provideWifiRepositoryDagger(
wifiManager: WifiManager?,
disabledWifiRepository: DisabledWifiRepository,
wifiRepositoryImplFactory: WifiRepositoryImpl.Factory,
- ): RealWifiRepository {
+ ): WifiRepositoryDagger {
// If we have a null [WifiManager], then the wifi repository should be permanently
// disabled.
return if (wifiManager == null) {
@@ -133,6 +143,36 @@
@Provides
@SysUISingleton
+ fun provideWifiRepositoryViaTrackerLibDagger(
+ wifiManager: WifiManager?,
+ disabledWifiRepository: DisabledWifiRepository,
+ wifiRepositoryFromTrackerLibFactory: WifiRepositoryViaTrackerLib.Factory,
+ ): WifiRepositoryViaTrackerLibDagger {
+ // If we have a null [WifiManager], then the wifi repository should be permanently
+ // disabled.
+ return if (wifiManager == null) {
+ disabledWifiRepository
+ } else {
+ wifiRepositoryFromTrackerLibFactory.create(wifiManager)
+ }
+ }
+
+ @Provides
+ @SysUISingleton
+ fun provideRealWifiRepository(
+ wifiRepository: WifiRepositoryDagger,
+ wifiRepositoryFromTrackerLib: WifiRepositoryViaTrackerLibDagger,
+ flags: FeatureFlags,
+ ): RealWifiRepository {
+ return if (flags.isEnabled(Flags.WIFI_TRACKER_LIB_FOR_WIFI_ICON)) {
+ wifiRepositoryFromTrackerLib
+ } else {
+ wifiRepository
+ }
+ }
+
+ @Provides
+ @SysUISingleton
@Named(FIRST_MOBILE_SUB_SHOWING_NETWORK_TYPE_ICON)
fun provideFirstMobileSubShowingNetworkTypeIconProvider(
mobileIconsViewModel: MobileIconsViewModel,
@@ -151,6 +191,14 @@
@Provides
@SysUISingleton
+ @WifiTrackerLibInputLog
+ fun provideWifiTrackerLibInputLogBuffer(factory: LogBufferFactory): LogBuffer {
+ // WifiTrackerLib is pretty noisy, so give it more room than WifiInputLog.
+ return factory.create("WifiTrackerLibInputLog", 200)
+ }
+
+ @Provides
+ @SysUISingleton
@WifiTableLog
fun provideWifiTableLogBuffer(factory: TableLogBufferFactory): TableLogBuffer {
return factory.create("WifiTableLog", 100)
@@ -158,6 +206,13 @@
@Provides
@SysUISingleton
+ @WifiTrackerLibTableLog
+ fun provideWifiTrackerLibTableLogBuffer(factory: TableLogBufferFactory): TableLogBuffer {
+ return factory.create("WifiTrackerLibTableLog", 100)
+ }
+
+ @Provides
+ @SysUISingleton
@AirplaneTableLog
fun provideAirplaneTableLogBuffer(factory: TableLogBufferFactory): TableLogBuffer {
return factory.create("AirplaneTableLog", 30)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/WifiTrackerLibInputLog.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/WifiTrackerLibInputLog.kt
new file mode 100644
index 0000000..b84b01e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/WifiTrackerLibInputLog.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2023 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.dagger
+
+import javax.inject.Qualifier
+
+/** Wifi logs for inputs into [WifiRepositoryViaTrackerLib]. */
+@Qualifier
+@MustBeDocumented
+@kotlin.annotation.Retention(AnnotationRetention.RUNTIME)
+annotation class WifiTrackerLibInputLog
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/WifiTrackerLibTableLog.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/WifiTrackerLibTableLog.kt
new file mode 100644
index 0000000..7ca7030
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/WifiTrackerLibTableLog.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2023 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.dagger
+
+import javax.inject.Qualifier
+
+/** Wifi logs from [WifiRepositoryViaTrackerLib] in table format. */
+@Qualifier
+@MustBeDocumented
+@Retention(AnnotationRetention.RUNTIME)
+annotation class WifiTrackerLibTableLog
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt
index f800cf4..b11b472 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.pipeline.wifi.data.repository
+import com.android.systemui.CoreStartable
import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
import kotlinx.coroutines.flow.StateFlow
@@ -42,6 +43,13 @@
val currentNetwork = wifiNetwork.value
return currentNetwork is WifiNetworkModel.Active && currentNetwork.hasValidSsid()
}
+
+ companion object {
+ /** Column name to use for [isWifiEnabled] for table logging. */
+ const val COL_NAME_IS_ENABLED = "isEnabled"
+ /** Column name to use for [isWifiDefault] for table logging. */
+ const val COL_NAME_IS_DEFAULT = "isDefault"
+ }
}
/**
@@ -53,3 +61,8 @@
* repository.
*/
interface RealWifiRepository : WifiRepository
+
+/** Used only by Dagger to bind [WifiRepositoryImpl]. */
+interface WifiRepositoryDagger : RealWifiRepository, CoreStartable
+/** Used only by Dagger to bind [WifiRepositoryViaTrackerLib]. */
+interface WifiRepositoryViaTrackerLibDagger : RealWifiRepository
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/DisabledWifiRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/DisabledWifiRepository.kt
index 86a668a..9ed7c6a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/DisabledWifiRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/DisabledWifiRepository.kt
@@ -18,7 +18,8 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
-import com.android.systemui.statusbar.pipeline.wifi.data.repository.RealWifiRepository
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepositoryDagger
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepositoryViaTrackerLibDagger
import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
import javax.inject.Inject
import kotlinx.coroutines.flow.MutableStateFlow
@@ -32,7 +33,10 @@
* wifi information.
*/
@SysUISingleton
-class DisabledWifiRepository @Inject constructor() : RealWifiRepository {
+class DisabledWifiRepository @Inject constructor() :
+ WifiRepositoryDagger, WifiRepositoryViaTrackerLibDagger {
+ override fun start() {}
+
override val isWifiEnabled: StateFlow<Boolean> = MutableStateFlow(false).asStateFlow()
override val isWifiDefault: StateFlow<Boolean> = MutableStateFlow(false).asStateFlow()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt
index 7f35dfb..995de6d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt
@@ -43,8 +43,10 @@
import com.android.systemui.statusbar.pipeline.shared.data.model.toWifiDataActivityModel
import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepository
import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepositoryImpl.Companion.getMainOrUnderlyingWifiInfo
-import com.android.systemui.statusbar.pipeline.wifi.data.repository.RealWifiRepository
import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository.Companion.COL_NAME_IS_DEFAULT
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository.Companion.COL_NAME_IS_ENABLED
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepositoryDagger
import com.android.systemui.statusbar.pipeline.wifi.shared.WifiInputLogger
import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
import java.util.concurrent.Executor
@@ -64,6 +66,7 @@
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
/** Real implementation of [WifiRepository]. */
@@ -81,9 +84,21 @@
@WifiTableLog wifiTableLogBuffer: TableLogBuffer,
@Main mainExecutor: Executor,
@Background private val bgDispatcher: CoroutineDispatcher,
- @Application scope: CoroutineScope,
+ @Application private val scope: CoroutineScope,
private val wifiManager: WifiManager,
-) : RealWifiRepository {
+) : WifiRepositoryDagger {
+
+ override fun start() {
+ // There are two possible [WifiRepository] implementations: This class (old) and
+ // [WifiRepositoryFromTrackerLib] (new). While we migrate to the new class, we want this old
+ // class to still be running in the background so that we can collect logs and compare
+ // discrepancies. This #start method collects on the flows to ensure that the logs are
+ // collected.
+ scope.launch { isWifiEnabled.collect {} }
+ scope.launch { isWifiDefault.collect {} }
+ scope.launch { wifiNetwork.collect {} }
+ scope.launch { wifiActivity.collect {} }
+ }
private val wifiStateChangeEvents: Flow<Unit> =
broadcastDispatcher
@@ -104,7 +119,7 @@
.logDiffsForTable(
wifiTableLogBuffer,
columnPrefix = "",
- columnName = "isEnabled",
+ columnName = COL_NAME_IS_ENABLED,
initialValue = false,
)
.stateIn(
@@ -125,7 +140,7 @@
.logDiffsForTable(
wifiTableLogBuffer,
columnPrefix = "",
- columnName = "isDefault",
+ columnName = COL_NAME_IS_DEFAULT,
initialValue = false,
)
.stateIn(scope, started = SharingStarted.WhileSubscribed(), initialValue = false)
@@ -234,6 +249,8 @@
// NetworkCallback inside [wifiNetwork] for our wifi network information.
val WIFI_NETWORK_DEFAULT = WifiNetworkModel.Inactive
+ const val WIFI_STATE_DEFAULT = WifiManager.WIFI_STATE_DISABLED
+
private fun createWifiNetworkModel(
wifiInfo: WifiInfo,
network: Network,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLib.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLib.kt
new file mode 100644
index 0000000..1271367
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLib.kt
@@ -0,0 +1,295 @@
+/*
+ * Copyright (C) 2023 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.data.repository.prod
+
+import android.net.wifi.WifiManager
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.LifecycleRegistry
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.LogLevel
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.log.table.logDiffsForTable
+import com.android.systemui.statusbar.connectivity.WifiPickerTrackerFactory
+import com.android.systemui.statusbar.pipeline.dagger.WifiTrackerLibInputLog
+import com.android.systemui.statusbar.pipeline.dagger.WifiTrackerLibTableLog
+import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository.Companion.COL_NAME_IS_DEFAULT
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository.Companion.COL_NAME_IS_ENABLED
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepositoryViaTrackerLibDagger
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.prod.WifiRepositoryImpl.Companion.WIFI_NETWORK_DEFAULT
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.prod.WifiRepositoryImpl.Companion.WIFI_STATE_DEFAULT
+import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
+import com.android.wifitrackerlib.MergedCarrierEntry
+import com.android.wifitrackerlib.WifiEntry
+import com.android.wifitrackerlib.WifiPickerTracker
+import java.util.concurrent.Executor
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.callbackFlow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
+
+/**
+ * An implementation of [WifiRepository] that uses [com.android.wifitrackerlib] as the source of
+ * truth for wifi information.
+ *
+ * Serves as a possible replacement for [WifiRepositoryImpl]. See b/292534484.
+ */
+@SysUISingleton
+class WifiRepositoryViaTrackerLib
+@Inject
+constructor(
+ @Application private val scope: CoroutineScope,
+ @Main private val mainExecutor: Executor,
+ private val wifiPickerTrackerFactory: WifiPickerTrackerFactory,
+ private val wifiManager: WifiManager,
+ @WifiTrackerLibInputLog private val inputLogger: LogBuffer,
+ @WifiTrackerLibTableLog private val wifiTrackerLibTableLogBuffer: TableLogBuffer,
+) : WifiRepositoryViaTrackerLibDagger, LifecycleOwner {
+
+ override val lifecycle =
+ LifecycleRegistry(this).also {
+ mainExecutor.execute { it.currentState = Lifecycle.State.CREATED }
+ }
+
+ private var wifiPickerTracker: WifiPickerTracker? = null
+
+ private val wifiPickerTrackerInfo: StateFlow<WifiPickerTrackerInfo> = run {
+ var current =
+ WifiPickerTrackerInfo(
+ state = WIFI_STATE_DEFAULT,
+ isDefault = false,
+ network = WIFI_NETWORK_DEFAULT,
+ )
+ callbackFlow {
+ val callback =
+ object : WifiPickerTracker.WifiPickerTrackerCallback {
+ override fun onWifiEntriesChanged() {
+ val connectedEntry = wifiPickerTracker?.connectedWifiEntry
+ logOnWifiEntriesChanged(connectedEntry)
+
+ // [WifiPickerTracker.connectedWifiEntry] will return the same instance
+ // but with updated internals. For example, when its validation status
+ // changes from false to true, the same instance is re-used but with the
+ // validated field updated.
+ //
+ // Because it's the same instance, the flow won't re-emit the value
+ // (even though the internals have changed). So, we need to transform it
+ // into our internal model immediately. [toWifiNetworkModel] always
+ // returns a new instance, so the flow is guaranteed to emit.
+ send(
+ newNetwork = connectedEntry?.toWifiNetworkModel()
+ ?: WIFI_NETWORK_DEFAULT,
+ newIsDefault = connectedEntry?.isDefaultNetwork ?: false,
+ )
+ }
+
+ override fun onWifiStateChanged() {
+ val state = wifiPickerTracker?.wifiState
+ logOnWifiStateChanged(state)
+ send(newState = state ?: WIFI_STATE_DEFAULT)
+ }
+
+ override fun onNumSavedNetworksChanged() {}
+
+ override fun onNumSavedSubscriptionsChanged() {}
+
+ private fun send(
+ newState: Int = current.state,
+ newIsDefault: Boolean = current.isDefault,
+ newNetwork: WifiNetworkModel = current.network,
+ ) {
+ val new = WifiPickerTrackerInfo(newState, newIsDefault, newNetwork)
+ current = new
+ trySend(new)
+ }
+ }
+
+ // TODO(b/292591403): [WifiPickerTrackerFactory] currently scans to see all
+ // available wifi networks every 10s. Because SysUI only needs to display the
+ // **connected** network, we don't need scans to be running. We should disable these
+ // scans (ideal) or at least run them very infrequently.
+ wifiPickerTracker = wifiPickerTrackerFactory.create(lifecycle, callback)
+ // The lifecycle must be STARTED in order for the callback to receive events.
+ mainExecutor.execute { lifecycle.currentState = Lifecycle.State.STARTED }
+ awaitClose {
+ mainExecutor.execute { lifecycle.currentState = Lifecycle.State.CREATED }
+ }
+ }
+ // TODO(b/292534484): Update to Eagerly once scans are disabled. (Here and other flows)
+ .stateIn(scope, SharingStarted.WhileSubscribed(), current)
+ }
+
+ override val isWifiEnabled: StateFlow<Boolean> =
+ wifiPickerTrackerInfo
+ .map { it.state == WifiManager.WIFI_STATE_ENABLED }
+ .distinctUntilChanged()
+ .logDiffsForTable(
+ wifiTrackerLibTableLogBuffer,
+ columnPrefix = "",
+ columnName = COL_NAME_IS_ENABLED,
+ initialValue = false,
+ )
+ .stateIn(scope, SharingStarted.WhileSubscribed(), false)
+
+ override val wifiNetwork: StateFlow<WifiNetworkModel> =
+ wifiPickerTrackerInfo
+ .map { it.network }
+ .distinctUntilChanged()
+ .logDiffsForTable(
+ wifiTrackerLibTableLogBuffer,
+ columnPrefix = "",
+ initialValue = WIFI_NETWORK_DEFAULT,
+ )
+ .stateIn(scope, SharingStarted.WhileSubscribed(), WIFI_NETWORK_DEFAULT)
+
+ /** Converts WifiTrackerLib's [WifiEntry] into our internal model. */
+ private fun WifiEntry.toWifiNetworkModel(): WifiNetworkModel {
+ if (!this.isPrimaryNetwork) {
+ return WIFI_NETWORK_DEFAULT
+ }
+ return if (this is MergedCarrierEntry) {
+ WifiNetworkModel.CarrierMerged(
+ networkId = NETWORK_ID,
+ // TODO(b/292534484): Fetch the real subscription ID from [MergedCarrierEntry].
+ subscriptionId = TEMP_SUB_ID,
+ level = this.level,
+ // WifiManager APIs to calculate the signal level start from 0, so
+ // maxSignalLevel + 1 represents the total level buckets count.
+ numberOfLevels = wifiManager.maxSignalLevel + 1,
+ )
+ } else {
+ WifiNetworkModel.Active(
+ networkId = NETWORK_ID,
+ isValidated = this.hasInternetAccess(),
+ level = this.level,
+ ssid = this.ssid,
+ // TODO(b/292534484): Fetch the real values from [WifiEntry] (#getTitle might be
+ // appropriate).
+ isPasspointAccessPoint = false,
+ isOnlineSignUpForPasspointAccessPoint = false,
+ passpointProviderFriendlyName = null,
+ )
+ }
+ }
+
+ override val isWifiDefault: StateFlow<Boolean> =
+ wifiPickerTrackerInfo
+ .map { it.isDefault }
+ .distinctUntilChanged()
+ .logDiffsForTable(
+ wifiTrackerLibTableLogBuffer,
+ columnPrefix = "",
+ columnName = COL_NAME_IS_DEFAULT,
+ initialValue = false,
+ )
+ .stateIn(scope, SharingStarted.WhileSubscribed(), false)
+
+ // TODO(b/292534484): Re-use WifiRepositoryImpl code to implement wifi activity since
+ // WifiTrackerLib doesn't expose activity details.
+ override val wifiActivity: StateFlow<DataActivityModel> =
+ MutableStateFlow(DataActivityModel(false, false))
+
+ private fun logOnWifiEntriesChanged(connectedEntry: WifiEntry?) {
+ inputLogger.log(
+ TAG,
+ LogLevel.DEBUG,
+ { str1 = connectedEntry.toString() },
+ { "onWifiEntriesChanged. ConnectedEntry=$str1" },
+ )
+ }
+
+ private fun logOnWifiStateChanged(state: Int?) {
+ inputLogger.log(
+ TAG,
+ LogLevel.DEBUG,
+ { int1 = state ?: -1 },
+ { "onWifiStateChanged. State=${if (int1 == -1) null else int1}" },
+ )
+ }
+
+ /**
+ * Data class storing all the information fetched from [WifiPickerTracker].
+ *
+ * Used so that we only register a single callback on [WifiPickerTracker].
+ */
+ data class WifiPickerTrackerInfo(
+ /** The current wifi state. See [WifiManager.getWifiState]. */
+ val state: Int,
+ /** True if wifi is currently the default connection and false otherwise. */
+ val isDefault: Boolean,
+ /** The currently primary wifi network. */
+ val network: WifiNetworkModel,
+ )
+
+ @SysUISingleton
+ class Factory
+ @Inject
+ constructor(
+ @Application private val scope: CoroutineScope,
+ @Main private val mainExecutor: Executor,
+ private val wifiPickerTrackerFactory: WifiPickerTrackerFactory,
+ @WifiTrackerLibInputLog private val inputLogger: LogBuffer,
+ @WifiTrackerLibTableLog private val wifiTrackerLibTableLogBuffer: TableLogBuffer,
+ ) {
+ fun create(wifiManager: WifiManager): WifiRepositoryViaTrackerLib {
+ return WifiRepositoryViaTrackerLib(
+ scope,
+ mainExecutor,
+ wifiPickerTrackerFactory,
+ wifiManager,
+ inputLogger,
+ wifiTrackerLibTableLogBuffer,
+ )
+ }
+ }
+
+ companion object {
+ private const val TAG = "WifiTrackerLibInputLog"
+
+ /**
+ * [WifiNetworkModel.Active.networkId] is only used at the repository layer. It's used by
+ * [WifiRepositoryImpl], which tracks the ID in order to correctly apply the framework
+ * callbacks within the repository.
+ *
+ * Since this class does not need to manually apply framework callbacks and since the
+ * network ID is not used beyond the repository, it's safe to use an invalid ID in this
+ * repository.
+ *
+ * The [WifiNetworkModel.Active.networkId] field should be deleted once we've fully migrated
+ * to [WifiRepositoryViaTrackerLib].
+ */
+ private const val NETWORK_ID = -1
+
+ /**
+ * A temporary subscription ID until WifiTrackerLib exposes a method to fetch the
+ * subscription ID.
+ *
+ * Use -2 because [SubscriptionManager.INVALID_SUBSCRIPTION_ID] is -1.
+ */
+ private const val TEMP_SUB_ID = -2
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java
index e1a7b6d..c2a8e70 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java
@@ -29,6 +29,7 @@
import com.android.systemui.statusbar.connectivity.AccessPointControllerImpl;
import com.android.systemui.statusbar.connectivity.NetworkController;
import com.android.systemui.statusbar.connectivity.NetworkControllerImpl;
+import com.android.systemui.statusbar.connectivity.WifiPickerTrackerFactory;
import com.android.systemui.statusbar.phone.ConfigurationControllerImpl;
import com.android.systemui.statusbar.policy.BluetoothController;
import com.android.systemui.statusbar.policy.BluetoothControllerImpl;
@@ -165,7 +166,7 @@
UserManager userManager,
UserTracker userTracker,
@Main Executor mainExecutor,
- AccessPointControllerImpl.WifiPickerTrackerFactory wifiPickerTrackerFactory
+ WifiPickerTrackerFactory wifiPickerTrackerFactory
) {
AccessPointControllerImpl controller = new AccessPointControllerImpl(
userManager,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt
index a6ad4b2..a86937f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt
@@ -89,6 +89,22 @@
utils.authenticationRepository.setLockscreenEnabled(false)
val isUnlocked by collectLastValue(underTest.isUnlocked)
+ // Toggle isUnlocked, twice.
+ //
+ // This is done because the underTest.isUnlocked flow doesn't receive values from
+ // just changing the state above; the actual isUnlocked state needs to change to
+ // cause the logic under test to "pick up" the current state again.
+ //
+ // It is done twice to make sure that we don't actually change the isUnlocked
+ // state from what it originally was.
+ utils.authenticationRepository.setUnlocked(
+ !utils.authenticationRepository.isUnlocked.value
+ )
+ runCurrent()
+ utils.authenticationRepository.setUnlocked(
+ !utils.authenticationRepository.isUnlocked.value
+ )
+ runCurrent()
assertThat(isUnlocked).isTrue()
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt
index 3169b09..2b08c66 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt
@@ -840,6 +840,27 @@
assertThat((lpFlags and PRIVATE_FLAG_TRUSTED_OVERLAY) != 0).isTrue()
}
+
+ @Test
+ fun primaryBouncerRequestAnimatesAlphaIn() = testWithDisplay {
+ sideFpsController.show(SideFpsUiRequestSource.PRIMARY_BOUNCER, REASON_AUTH_KEYGUARD)
+ executor.runAllReady()
+ verify(sideFpsView).animate()
+ }
+
+ @Test
+ fun alternateBouncerRequestsDoesNotAnimateAlphaIn() = testWithDisplay {
+ sideFpsController.show(SideFpsUiRequestSource.ALTERNATE_BOUNCER, REASON_AUTH_KEYGUARD)
+ executor.runAllReady()
+ verify(sideFpsView, never()).animate()
+ }
+
+ @Test
+ fun autoShowRequestsDoesNotAnimateAlphaIn() = testWithDisplay {
+ sideFpsController.show(SideFpsUiRequestSource.AUTO_SHOW, REASON_AUTH_KEYGUARD)
+ executor.runAllReady()
+ verify(sideFpsView, never()).animate()
+ }
}
private fun insetsForSmallNavbar() = insetsWithBottom(60)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/OccludingAppDeviceEntryInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/OccludingAppDeviceEntryInteractorTest.kt
index 6e52d1a..baa5ee8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/OccludingAppDeviceEntryInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/OccludingAppDeviceEntryInteractorTest.kt
@@ -233,12 +233,12 @@
// ERROR message
fingerprintAuthRepository.setAuthenticationStatus(
ErrorFingerprintAuthenticationStatus(
- FingerprintManager.FINGERPRINT_ERROR_LOCKOUT,
+ FingerprintManager.FINGERPRINT_ERROR_CANCELED,
"testError",
)
)
assertThat(message?.source).isEqualTo(BiometricSourceType.FINGERPRINT)
- assertThat(message?.id).isEqualTo(FingerprintManager.FINGERPRINT_ERROR_LOCKOUT)
+ assertThat(message?.id).isEqualTo(FingerprintManager.FINGERPRINT_ERROR_CANCELED)
assertThat(message?.message).isEqualTo("testError")
assertThat(message?.type).isEqualTo(BiometricMessageType.ERROR)
@@ -262,6 +262,34 @@
assertThat(message?.type).isEqualTo(BiometricMessageType.FAIL)
}
+ @Test
+ fun message_fpError_lockoutFilteredOut() =
+ testScope.runTest {
+ val message by collectLastValue(underTest.message)
+
+ givenOnOccludingApp(true)
+ givenPrimaryAuthRequired(false)
+ runCurrent()
+
+ // permanent lockout error message
+ fingerprintAuthRepository.setAuthenticationStatus(
+ ErrorFingerprintAuthenticationStatus(
+ FingerprintManager.FINGERPRINT_ERROR_LOCKOUT_PERMANENT,
+ "testPermanentLockoutMessageFiltered",
+ )
+ )
+ assertThat(message).isNull()
+
+ // temporary lockout error message
+ fingerprintAuthRepository.setAuthenticationStatus(
+ ErrorFingerprintAuthenticationStatus(
+ FingerprintManager.FINGERPRINT_ERROR_LOCKOUT,
+ "testLockoutMessageFiltered",
+ )
+ )
+ assertThat(message).isNull()
+ }
+
private fun givenOnOccludingApp(isOnOccludingApp: Boolean) {
keyguardRepository.setKeyguardOccluded(isOnOccludingApp)
keyguardRepository.setKeyguardShowing(isOnOccludingApp)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeHeaderControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeHeaderControllerTest.kt
index bf25f29..2501f85 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeHeaderControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeHeaderControllerTest.kt
@@ -55,6 +55,7 @@
import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider
import com.android.systemui.statusbar.phone.StatusBarIconController
import com.android.systemui.statusbar.phone.StatusIconContainer
+import com.android.systemui.statusbar.phone.StatusOverlayHoverListenerFactory
import com.android.systemui.statusbar.policy.Clock
import com.android.systemui.statusbar.policy.FakeConfigurationController
import com.android.systemui.statusbar.policy.NextAlarmController
@@ -123,6 +124,7 @@
@Mock private lateinit var qsBatteryModeController: QsBatteryModeController
@Mock private lateinit var nextAlarmController: NextAlarmController
@Mock private lateinit var activityStarter: ActivityStarter
+ @Mock private lateinit var mStatusOverlayHoverListenerFactory: StatusOverlayHoverListenerFactory
@JvmField @Rule val mockitoRule = MockitoJUnit.rule()
var viewVisibility = View.GONE
@@ -194,6 +196,7 @@
qsBatteryModeController,
nextAlarmController,
activityStarter,
+ mStatusOverlayHoverListenerFactory
)
whenever(view.isAttachedToWindow).thenReturn(true)
shadeHeaderController.init()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/AccessPointControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/AccessPointControllerImplTest.kt
index c57b64d..5b919d9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/AccessPointControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/AccessPointControllerImplTest.kt
@@ -23,6 +23,7 @@
import androidx.lifecycle.Lifecycle
import com.android.systemui.SysuiTestCase
import com.android.systemui.settings.UserTracker
+import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.capture
import com.android.wifitrackerlib.WifiEntry
import com.android.wifitrackerlib.WifiPickerTracker
@@ -31,7 +32,6 @@
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
-import org.mockito.ArgumentMatchers.any
import org.mockito.ArgumentMatchers.anyList
import org.mockito.Captor
import org.mockito.Mock
@@ -52,7 +52,7 @@
private lateinit var userTracker: UserTracker
@Mock
private lateinit var wifiPickerTrackerFactory:
- AccessPointControllerImpl.WifiPickerTrackerFactory
+ WifiPickerTrackerFactory
@Mock
private lateinit var wifiPickerTracker: WifiPickerTracker
@Mock
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
index 5300851..4872deb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
@@ -513,6 +513,7 @@
mNotificationShadeWindowViewControllerLazy,
mNotificationShelfController,
mStackScrollerController,
+ mNotificationPresenter,
mDozeParameters,
mScrimController,
mLockscreenWallpaperLazy,
@@ -590,7 +591,6 @@
// TODO(b/277764509): we should be able to call mCentralSurfaces.start() and have all the
// below values initialized automatically.
mCentralSurfaces.mDozeScrimController = mDozeScrimController;
- mCentralSurfaces.mPresenter = mNotificationPresenter;
mCentralSurfaces.mKeyguardIndicationController = mKeyguardIndicationController;
mCentralSurfaces.mBarService = mBarService;
mCentralSurfaces.mGestureWakeLock = mPowerManager.newWakeLock(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java
index e838a480..d100c687 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java
@@ -122,6 +122,7 @@
@Mock private KeyguardLogger mLogger;
@Mock private NotificationMediaManager mNotificationMediaManager;
+ @Mock private StatusOverlayHoverListenerFactory mStatusOverlayHoverListenerFactory;
private TestShadeViewStateProvider mShadeViewStateProvider;
private KeyguardStatusBarView mKeyguardStatusBarView;
@@ -171,7 +172,8 @@
mCommandQueue,
mFakeExecutor,
mLogger,
- mNotificationMediaManager
+ mNotificationMediaManager,
+ mStatusOverlayHoverListenerFactory
);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
index 7de0075..2e92bb9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
@@ -69,6 +69,8 @@
@Mock
private lateinit var configurationController: ConfigurationController
@Mock
+ private lateinit var mStatusOverlayHoverListenerFactory: StatusOverlayHoverListenerFactory
+ @Mock
private lateinit var userChipViewModel: StatusBarUserChipViewModel
@Mock
private lateinit var centralSurfacesImpl: CentralSurfacesImpl
@@ -204,7 +206,8 @@
sceneInteractor,
shadeLogger,
viewUtil,
- configurationController
+ configurationController,
+ mStatusOverlayHoverListenerFactory
).create(view).also {
it.init()
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusOverlayHoverListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusOverlayHoverListenerTest.kt
new file mode 100644
index 0000000..63508e1
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusOverlayHoverListenerTest.kt
@@ -0,0 +1,192 @@
+/*
+ * Copyright (C) 2023 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
+
+import android.content.Context
+import android.graphics.Color
+import android.graphics.drawable.Drawable
+import android.graphics.drawable.PaintDrawable
+import android.os.SystemClock
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.testing.TestableLooper.RunWithLooper
+import android.testing.ViewUtils
+import android.view.MotionEvent
+import android.view.View
+import android.view.ViewGroupOverlay
+import android.widget.LinearLayout
+import androidx.annotation.ColorInt
+import androidx.test.filters.SmallTest
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.phone.SysuiDarkIconDispatcher.DarkChange
+import com.android.systemui.statusbar.policy.FakeConfigurationController
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.flow.MutableStateFlow
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.verify
+
+@RunWith(AndroidTestingRunner::class)
+@RunWithLooper(setAsMainLooper = true)
+@SmallTest
+class StatusOverlayHoverListenerTest : SysuiTestCase() {
+
+ private val viewOverlay = mock<ViewGroupOverlay>()
+ private val overlayCaptor = argumentCaptor<Drawable>()
+ private val darkDispatcher = mock<SysuiDarkIconDispatcher>()
+ private val darkChange: MutableStateFlow<DarkChange> = MutableStateFlow(DarkChange.EMPTY)
+
+ private val factory =
+ StatusOverlayHoverListenerFactory(
+ context.resources,
+ FakeConfigurationController(),
+ darkDispatcher
+ )
+ private val view = TestableStatusContainer(context, viewOverlay)
+
+ private lateinit var looper: TestableLooper
+
+ @Before
+ fun setUp() {
+ looper = TestableLooper.get(this)
+ whenever(darkDispatcher.darkChangeFlow()).thenReturn(darkChange)
+ }
+
+ @Test
+ fun onHoverStarted_addsOverlay() {
+ view.setUpHoverListener()
+
+ view.hoverStarted()
+
+ assertThat(overlayDrawable).isNotNull()
+ }
+
+ @Test
+ fun onHoverEnded_removesOverlay() {
+ view.setUpHoverListener()
+
+ view.hoverStarted() // stopped callback will be called only if hover has started
+ view.hoverStopped()
+
+ verify(viewOverlay).clear()
+ }
+
+ @Test
+ fun onHoverStarted_overlayHasLightColor() {
+ view.setUpHoverListener()
+
+ view.hoverStarted()
+
+ assertThat(overlayColor)
+ .isEqualTo(context.resources.getColor(R.color.status_bar_icons_hover_color_light))
+ }
+
+ @Test
+ fun onDarkAwareHoverStarted_withBlackIcons_overlayHasDarkColor() {
+ view.setUpDarkAwareHoverListener()
+ setIconsTint(Color.BLACK)
+
+ view.hoverStarted()
+
+ assertThat(overlayColor)
+ .isEqualTo(context.resources.getColor(R.color.status_bar_icons_hover_color_dark))
+ }
+
+ @Test
+ fun onHoverStarted_withBlackIcons_overlayHasLightColor() {
+ view.setUpHoverListener()
+ setIconsTint(Color.BLACK)
+
+ view.hoverStarted()
+
+ assertThat(overlayColor)
+ .isEqualTo(context.resources.getColor(R.color.status_bar_icons_hover_color_light))
+ }
+
+ @Test
+ fun onDarkAwareHoverStarted_withWhiteIcons_overlayHasLightColor() {
+ view.setUpDarkAwareHoverListener()
+ setIconsTint(Color.WHITE)
+
+ view.hoverStarted()
+
+ assertThat(overlayColor)
+ .isEqualTo(context.resources.getColor(R.color.status_bar_icons_hover_color_light))
+ }
+
+ private fun View.setUpHoverListener() {
+ setOnHoverListener(factory.createListener(view))
+ attachView(view)
+ }
+
+ private fun View.setUpDarkAwareHoverListener() {
+ setOnHoverListener(factory.createDarkAwareListener(view))
+ attachView(view)
+ }
+
+ private fun attachView(view: View) {
+ ViewUtils.attachView(view)
+ // attaching is async so processAllMessages is required for view.repeatWhenAttached to run
+ looper.processAllMessages()
+ }
+
+ private val overlayDrawable: Drawable
+ get() {
+ verify(viewOverlay).add(overlayCaptor.capture())
+ return overlayCaptor.value
+ }
+
+ private val overlayColor
+ get() = (overlayDrawable as PaintDrawable).paint.color
+
+ private fun setIconsTint(@ColorInt color: Int) {
+ // passing empty ArrayList is equivalent to just accepting passed color as icons color
+ darkChange.value = DarkChange(/* areas= */ ArrayList(), /* darkIntensity= */ 1f, color)
+ }
+
+ private fun TestableStatusContainer.hoverStarted() {
+ injectHoverEvent(hoverEvent(MotionEvent.ACTION_HOVER_ENTER))
+ }
+
+ private fun TestableStatusContainer.hoverStopped() {
+ injectHoverEvent(hoverEvent(MotionEvent.ACTION_HOVER_EXIT))
+ }
+
+ class TestableStatusContainer(context: Context, private val mockOverlay: ViewGroupOverlay) :
+ LinearLayout(context) {
+
+ fun injectHoverEvent(event: MotionEvent) = dispatchHoverEvent(event)
+
+ override fun getOverlay() = mockOverlay
+ }
+
+ private fun hoverEvent(action: Int): MotionEvent {
+ return MotionEvent.obtain(
+ /* downTime= */ SystemClock.uptimeMillis(),
+ /* eventTime= */ SystemClock.uptimeMillis(),
+ /* action= */ action,
+ /* x= */ 0f,
+ /* y= */ 0f,
+ /* metaState= */ 0
+ )
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt
index 3cf5f52..fef042b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt
@@ -36,6 +36,7 @@
import androidx.test.filters.SmallTest
import com.android.systemui.RoboPilotTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.statusbar.pipeline.shared.data.model.ConnectivitySlots
import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
@@ -48,23 +49,25 @@
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
import java.util.concurrent.Executor
import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.flow.launchIn
-import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when` as whenever
import org.mockito.MockitoAnnotations
+/**
+ * Note: Any new tests added here may also need to be added to [WifiRepositoryViaTrackerLibTest].
+ */
@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@@ -81,7 +84,7 @@
private lateinit var executor: Executor
private lateinit var connectivityRepository: ConnectivityRepository
- private val dispatcher = UnconfinedTestDispatcher()
+ private val dispatcher = StandardTestDispatcher()
private val testScope = TestScope(dispatcher)
@Before
@@ -109,6 +112,7 @@
whenever(wifiManager.isWifiEnabled).thenReturn(true)
underTest = createRepo()
+ testScope.runCurrent()
assertThat(underTest.isWifiEnabled.value).isTrue()
}
@@ -116,53 +120,43 @@
@Test
fun isWifiEnabled_networkCapabilitiesChanged_valueUpdated() =
testScope.runTest {
- // 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)
+ collectLastValue(underTest.wifiNetwork)
+ val latest by collectLastValue(underTest.isWifiEnabled)
whenever(wifiManager.isWifiEnabled).thenReturn(true)
getNetworkCallback()
.onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(PRIMARY_WIFI_INFO))
- assertThat(underTest.isWifiEnabled.value).isTrue()
+ assertThat(latest).isTrue()
whenever(wifiManager.isWifiEnabled).thenReturn(false)
getNetworkCallback()
.onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(PRIMARY_WIFI_INFO))
- assertThat(underTest.isWifiEnabled.value).isFalse()
-
- networkJob.cancel()
- enabledJob.cancel()
+ assertThat(latest).isFalse()
}
@Test
fun isWifiEnabled_networkLost_valueUpdated() =
testScope.runTest {
- // 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)
+ collectLastValue(underTest.wifiNetwork)
+ val latest by collectLastValue(underTest.isWifiEnabled)
whenever(wifiManager.isWifiEnabled).thenReturn(true)
getNetworkCallback().onLost(NETWORK)
- assertThat(underTest.isWifiEnabled.value).isTrue()
+ assertThat(latest).isTrue()
whenever(wifiManager.isWifiEnabled).thenReturn(false)
getNetworkCallback().onLost(NETWORK)
- assertThat(underTest.isWifiEnabled.value).isFalse()
-
- networkJob.cancel()
- enabledJob.cancel()
+ assertThat(latest).isFalse()
}
@Test
fun isWifiEnabled_intentsReceived_valueUpdated() =
testScope.runTest {
- underTest = createRepo()
-
- val job = underTest.isWifiEnabled.launchIn(this)
+ val latest by collectLastValue(underTest.isWifiEnabled)
whenever(wifiManager.isWifiEnabled).thenReturn(true)
fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
@@ -170,7 +164,7 @@
Intent(WifiManager.WIFI_STATE_CHANGED_ACTION),
)
- assertThat(underTest.isWifiEnabled.value).isTrue()
+ assertThat(latest).isTrue()
whenever(wifiManager.isWifiEnabled).thenReturn(false)
fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
@@ -178,76 +172,61 @@
Intent(WifiManager.WIFI_STATE_CHANGED_ACTION),
)
- assertThat(underTest.isWifiEnabled.value).isFalse()
-
- job.cancel()
+ assertThat(latest).isFalse()
}
@Test
fun isWifiEnabled_bothIntentAndNetworkUpdates_valueAlwaysUpdated() =
testScope.runTest {
- underTest = createRepo()
-
- val networkJob = underTest.wifiNetwork.launchIn(this)
- val enabledJob = underTest.isWifiEnabled.launchIn(this)
+ collectLastValue(underTest.wifiNetwork)
+ val latest by collectLastValue(underTest.isWifiEnabled)
whenever(wifiManager.isWifiEnabled).thenReturn(false)
fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
context,
Intent(WifiManager.WIFI_STATE_CHANGED_ACTION),
)
- assertThat(underTest.isWifiEnabled.value).isFalse()
+ assertThat(latest).isFalse()
whenever(wifiManager.isWifiEnabled).thenReturn(true)
getNetworkCallback().onLost(NETWORK)
- assertThat(underTest.isWifiEnabled.value).isTrue()
+ assertThat(latest).isTrue()
whenever(wifiManager.isWifiEnabled).thenReturn(false)
getNetworkCallback()
.onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(PRIMARY_WIFI_INFO))
- assertThat(underTest.isWifiEnabled.value).isFalse()
+ assertThat(latest).isFalse()
whenever(wifiManager.isWifiEnabled).thenReturn(true)
fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
context,
Intent(WifiManager.WIFI_STATE_CHANGED_ACTION),
)
- assertThat(underTest.isWifiEnabled.value).isTrue()
-
- networkJob.cancel()
- enabledJob.cancel()
+ assertThat(latest).isTrue()
}
@Test
fun isWifiDefault_initiallyGetsDefault() =
- testScope.runTest {
- val job = underTest.isWifiDefault.launchIn(this)
-
- assertThat(underTest.isWifiDefault.value).isFalse()
-
- job.cancel()
- }
+ testScope.runTest { assertThat(underTest.isWifiDefault.value).isFalse() }
@Test
fun isWifiDefault_wifiNetwork_isTrue() =
testScope.runTest {
- val job = underTest.isWifiDefault.launchIn(this)
+ val latest by collectLastValue(underTest.isWifiDefault)
val wifiInfo = mock<WifiInfo>().apply { whenever(this.ssid).thenReturn(SSID) }
getDefaultNetworkCallback()
.onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(wifiInfo))
- assertThat(underTest.isWifiDefault.value).isTrue()
-
- job.cancel()
+ assertThat(latest).isTrue()
}
/** Regression test for b/266628069. */
@Test
fun isWifiDefault_transportInfoIsNotWifi_andNoWifiTransport_false() =
testScope.runTest {
- val job = underTest.isWifiDefault.launchIn(this)
+ val latest by collectLastValue(underTest.isWifiDefault)
val transportInfo =
VpnTransportInfo(
@@ -264,16 +243,14 @@
getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, networkCapabilities)
- assertThat(underTest.isWifiDefault.value).isFalse()
-
- job.cancel()
+ assertThat(latest).isFalse()
}
/** Regression test for b/266628069. */
@Test
fun isWifiDefault_transportInfoIsNotWifi_butHasWifiTransport_true() =
testScope.runTest {
- val job = underTest.isWifiDefault.launchIn(this)
+ val latest by collectLastValue(underTest.isWifiDefault)
val transportInfo =
VpnTransportInfo(
@@ -290,15 +267,13 @@
getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, networkCapabilities)
- assertThat(underTest.isWifiDefault.value).isTrue()
-
- job.cancel()
+ assertThat(latest).isTrue()
}
@Test
fun isWifiDefault_carrierMergedViaCellular_isTrue() =
testScope.runTest {
- val job = underTest.isWifiDefault.launchIn(this)
+ val latest by collectLastValue(underTest.isWifiDefault)
val carrierMergedInfo =
mock<WifiInfo>().apply { whenever(this.isCarrierMerged).thenReturn(true) }
@@ -312,15 +287,13 @@
getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities)
- assertThat(underTest.isWifiDefault.value).isTrue()
-
- job.cancel()
+ assertThat(latest).isTrue()
}
@Test
fun isWifiDefault_carrierMergedViaCellular_withVcnTransport_isTrue() =
testScope.runTest {
- val job = underTest.isWifiDefault.launchIn(this)
+ val latest by collectLastValue(underTest.isWifiDefault)
val capabilities =
mock<NetworkCapabilities>().apply {
@@ -331,15 +304,13 @@
getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities)
- assertThat(underTest.isWifiDefault.value).isTrue()
-
- job.cancel()
+ assertThat(latest).isTrue()
}
@Test
fun isWifiDefault_carrierMergedViaWifi_isTrue() =
testScope.runTest {
- val job = underTest.isWifiDefault.launchIn(this)
+ val latest by collectLastValue(underTest.isWifiDefault)
val carrierMergedInfo =
mock<WifiInfo>().apply { whenever(this.isCarrierMerged).thenReturn(true) }
@@ -353,15 +324,13 @@
getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities)
- assertThat(underTest.isWifiDefault.value).isTrue()
-
- job.cancel()
+ assertThat(latest).isTrue()
}
@Test
fun isWifiDefault_carrierMergedViaWifi_withVcnTransport_isTrue() =
testScope.runTest {
- val job = underTest.isWifiDefault.launchIn(this)
+ val latest by collectLastValue(underTest.isWifiDefault)
val capabilities =
mock<NetworkCapabilities>().apply {
@@ -372,15 +341,13 @@
getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities)
- assertThat(underTest.isWifiDefault.value).isTrue()
-
- job.cancel()
+ assertThat(latest).isTrue()
}
@Test
fun isWifiDefault_cellularAndWifiTransports_usesCellular_isTrue() =
testScope.runTest {
- val job = underTest.isWifiDefault.launchIn(this)
+ val latest by collectLastValue(underTest.isWifiDefault)
val capabilities =
mock<NetworkCapabilities>().apply {
@@ -391,15 +358,13 @@
getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities)
- assertThat(underTest.isWifiDefault.value).isTrue()
-
- job.cancel()
+ assertThat(latest).isTrue()
}
@Test
fun isWifiDefault_cellularNotVcnNetwork_isFalse() =
testScope.runTest {
- val job = underTest.isWifiDefault.launchIn(this)
+ val latest by collectLastValue(underTest.isWifiDefault)
val capabilities =
mock<NetworkCapabilities>().apply {
@@ -409,15 +374,13 @@
getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities)
- assertThat(underTest.isWifiDefault.value).isFalse()
-
- job.cancel()
+ assertThat(latest).isFalse()
}
@Test
fun isWifiDefault_isCarrierMergedViaUnderlyingWifi_isTrue() =
testScope.runTest {
- val job = underTest.isWifiDefault.launchIn(this)
+ val latest by collectLastValue(underTest.isWifiDefault)
val underlyingNetwork = mock<Network>()
val carrierMergedInfo =
@@ -444,15 +407,13 @@
getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, mainCapabilities)
// THEN the wifi network is carrier merged, so wifi is default
- assertThat(underTest.isWifiDefault.value).isTrue()
-
- job.cancel()
+ assertThat(latest).isTrue()
}
@Test
fun isWifiDefault_isCarrierMergedViaUnderlyingCellular_isTrue() =
testScope.runTest {
- val job = underTest.isWifiDefault.launchIn(this)
+ val latest by collectLastValue(underTest.isWifiDefault)
val underlyingCarrierMergedNetwork = mock<Network>()
val carrierMergedInfo =
@@ -478,46 +439,38 @@
getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, mainCapabilities)
// THEN the wifi network is carrier merged, so wifi is default
- assertThat(underTest.isWifiDefault.value).isTrue()
-
- job.cancel()
+ assertThat(latest).isTrue()
}
@Test
fun isWifiDefault_wifiNetworkLost_isFalse() =
testScope.runTest {
- val job = underTest.isWifiDefault.launchIn(this)
+ val latest by collectLastValue(underTest.isWifiDefault)
// First, add a network
getDefaultNetworkCallback()
.onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(PRIMARY_WIFI_INFO))
- assertThat(underTest.isWifiDefault.value).isTrue()
+ assertThat(latest).isTrue()
// WHEN the network is lost
getDefaultNetworkCallback().onLost(NETWORK)
// THEN we update to false
- assertThat(underTest.isWifiDefault.value).isFalse()
-
- job.cancel()
+ assertThat(latest).isFalse()
}
@Test
fun wifiNetwork_initiallyGetsDefault() =
testScope.runTest {
- var latest: WifiNetworkModel? = null
- val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this)
+ val latest by collectLastValue(underTest.wifiNetwork)
assertThat(latest).isEqualTo(WIFI_NETWORK_DEFAULT)
-
- job.cancel()
}
@Test
fun wifiNetwork_primaryWifiNetworkAdded_flowHasNetwork() =
testScope.runTest {
- var latest: WifiNetworkModel? = null
- val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this)
+ val latest by collectLastValue(underTest.wifiNetwork)
val wifiInfo =
mock<WifiInfo>().apply {
@@ -533,15 +486,12 @@
val latestActive = latest as WifiNetworkModel.Active
assertThat(latestActive.networkId).isEqualTo(NETWORK_ID)
assertThat(latestActive.ssid).isEqualTo(SSID)
-
- job.cancel()
}
@Test
fun wifiNetwork_isCarrierMerged_flowHasCarrierMerged() =
testScope.runTest {
- var latest: WifiNetworkModel? = null
- val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this)
+ val latest by collectLastValue(underTest.wifiNetwork)
val wifiInfo =
mock<WifiInfo>().apply {
@@ -553,15 +503,12 @@
.onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(wifiInfo))
assertThat(latest is WifiNetworkModel.CarrierMerged).isTrue()
-
- job.cancel()
}
@Test
fun wifiNetwork_isCarrierMergedViaUnderlyingWifi_flowHasCarrierMerged() =
testScope.runTest {
- var latest: WifiNetworkModel? = null
- val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this)
+ val latest by collectLastValue(underTest.wifiNetwork)
val underlyingNetwork = mock<Network>()
val carrierMergedInfo =
@@ -590,15 +537,12 @@
// THEN the wifi network is carrier merged
assertThat(latest is WifiNetworkModel.CarrierMerged).isTrue()
-
- job.cancel()
}
@Test
fun wifiNetwork_isCarrierMergedViaUnderlyingCellular_flowHasCarrierMerged() =
testScope.runTest {
- var latest: WifiNetworkModel? = null
- val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this)
+ val latest by collectLastValue(underTest.wifiNetwork)
val underlyingCarrierMergedNetwork = mock<Network>()
val carrierMergedInfo =
@@ -628,15 +572,12 @@
// THEN the wifi network is carrier merged
assertThat(latest is WifiNetworkModel.CarrierMerged).isTrue()
-
- job.cancel()
}
@Test
fun wifiNetwork_carrierMergedButInvalidSubId_flowHasInvalid() =
testScope.runTest {
- var latest: WifiNetworkModel? = null
- val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this)
+ val latest by collectLastValue(underTest.wifiNetwork)
val wifiInfo =
mock<WifiInfo>().apply {
@@ -652,15 +593,12 @@
)
assertThat(latest).isInstanceOf(WifiNetworkModel.Invalid::class.java)
-
- job.cancel()
}
@Test
fun wifiNetwork_isCarrierMerged_getsCorrectValues() =
testScope.runTest {
- var latest: WifiNetworkModel? = null
- val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this)
+ val latest by collectLastValue(underTest.wifiNetwork)
val rssi = -57
val wifiInfo =
@@ -687,15 +625,12 @@
assertThat(latestCarrierMerged.level).isEqualTo(2)
// numberOfLevels = maxSignalLevel + 1
assertThat(latestCarrierMerged.numberOfLevels).isEqualTo(6)
-
- job.cancel()
}
@Test
fun wifiNetwork_notValidated_networkNotValidated() =
testScope.runTest {
- var latest: WifiNetworkModel? = null
- val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this)
+ val latest by collectLastValue(underTest.wifiNetwork)
getNetworkCallback()
.onCapabilitiesChanged(
@@ -704,15 +639,12 @@
)
assertThat((latest as WifiNetworkModel.Active).isValidated).isFalse()
-
- job.cancel()
}
@Test
fun wifiNetwork_validated_networkValidated() =
testScope.runTest {
- var latest: WifiNetworkModel? = null
- val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this)
+ val latest by collectLastValue(underTest.wifiNetwork)
getNetworkCallback()
.onCapabilitiesChanged(
@@ -721,15 +653,12 @@
)
assertThat((latest as WifiNetworkModel.Active).isValidated).isTrue()
-
- job.cancel()
}
@Test
fun wifiNetwork_nonPrimaryWifiNetworkAdded_flowHasNoNetwork() =
testScope.runTest {
- var latest: WifiNetworkModel? = null
- val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this)
+ val latest by collectLastValue(underTest.wifiNetwork)
val wifiInfo =
mock<WifiInfo>().apply {
@@ -741,16 +670,13 @@
.onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(wifiInfo))
assertThat(latest is WifiNetworkModel.Inactive).isTrue()
-
- job.cancel()
}
/** Regression test for b/266628069. */
@Test
fun wifiNetwork_transportInfoIsNotWifi_flowHasNoNetwork() =
testScope.runTest {
- var latest: WifiNetworkModel? = null
- val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this)
+ val latest by collectLastValue(underTest.wifiNetwork)
val transportInfo =
VpnTransportInfo(
@@ -761,15 +687,12 @@
.onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(transportInfo))
assertThat(latest is WifiNetworkModel.Inactive).isTrue()
-
- job.cancel()
}
@Test
fun wifiNetwork_cellularVcnNetworkAdded_flowHasNetwork() =
testScope.runTest {
- var latest: WifiNetworkModel? = null
- val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this)
+ val latest by collectLastValue(underTest.wifiNetwork)
val capabilities =
mock<NetworkCapabilities>().apply {
@@ -783,15 +706,12 @@
val latestActive = latest as WifiNetworkModel.Active
assertThat(latestActive.networkId).isEqualTo(NETWORK_ID)
assertThat(latestActive.ssid).isEqualTo(SSID)
-
- job.cancel()
}
@Test
fun wifiNetwork_nonPrimaryCellularVcnNetworkAdded_flowHasNoNetwork() =
testScope.runTest {
- var latest: WifiNetworkModel? = null
- val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this)
+ val latest by collectLastValue(underTest.wifiNetwork)
val wifiInfo =
mock<WifiInfo>().apply {
@@ -807,15 +727,12 @@
getNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities)
assertThat(latest is WifiNetworkModel.Inactive).isTrue()
-
- job.cancel()
}
@Test
fun wifiNetwork_cellularNotVcnNetworkAdded_flowHasNoNetwork() =
testScope.runTest {
- var latest: WifiNetworkModel? = null
- val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this)
+ val latest by collectLastValue(underTest.wifiNetwork)
val capabilities =
mock<NetworkCapabilities>().apply {
@@ -826,15 +743,12 @@
getNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities)
assertThat(latest is WifiNetworkModel.Inactive).isTrue()
-
- job.cancel()
}
@Test
fun wifiNetwork_cellularAndWifiTransports_usesCellular() =
testScope.runTest {
- var latest: WifiNetworkModel? = null
- val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this)
+ val latest by collectLastValue(underTest.wifiNetwork)
val capabilities =
mock<NetworkCapabilities>().apply {
@@ -849,15 +763,12 @@
val latestActive = latest as WifiNetworkModel.Active
assertThat(latestActive.networkId).isEqualTo(NETWORK_ID)
assertThat(latestActive.ssid).isEqualTo(SSID)
-
- job.cancel()
}
@Test
fun wifiNetwork_newPrimaryWifiNetwork_flowHasNewNetwork() =
testScope.runTest {
- var latest: WifiNetworkModel? = null
- val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this)
+ val latest by collectLastValue(underTest.wifiNetwork)
// Start with the original network
getNetworkCallback()
@@ -882,15 +793,12 @@
val latestActive = latest as WifiNetworkModel.Active
assertThat(latestActive.networkId).isEqualTo(newNetworkId)
assertThat(latestActive.ssid).isEqualTo(newSsid)
-
- job.cancel()
}
@Test
fun wifiNetwork_newNonPrimaryWifiNetwork_flowHasOldNetwork() =
testScope.runTest {
- var latest: WifiNetworkModel? = null
- val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this)
+ val latest by collectLastValue(underTest.wifiNetwork)
// Start with the original network
getNetworkCallback()
@@ -915,15 +823,12 @@
val latestActive = latest as WifiNetworkModel.Active
assertThat(latestActive.networkId).isEqualTo(NETWORK_ID)
assertThat(latestActive.ssid).isEqualTo(SSID)
-
- job.cancel()
}
@Test
fun wifiNetwork_newNetworkCapabilities_flowHasNewData() =
testScope.runTest {
- var latest: WifiNetworkModel? = null
- val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this)
+ val latest by collectLastValue(underTest.wifiNetwork)
val wifiInfo =
mock<WifiInfo>().apply {
@@ -958,30 +863,24 @@
assertThat(latestActive.networkId).isEqualTo(NETWORK_ID)
assertThat(latestActive.ssid).isEqualTo(newSsid)
assertThat(latestActive.isValidated).isFalse()
-
- job.cancel()
}
@Test
fun wifiNetwork_noCurrentNetwork_networkLost_flowHasNoNetwork() =
testScope.runTest {
- var latest: WifiNetworkModel? = null
- val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this)
+ val latest by collectLastValue(underTest.wifiNetwork)
// WHEN we receive #onLost without any #onCapabilitiesChanged beforehand
getNetworkCallback().onLost(NETWORK)
// THEN there's no crash and we still have no network
assertThat(latest is WifiNetworkModel.Inactive).isTrue()
-
- job.cancel()
}
@Test
fun wifiNetwork_currentActiveNetworkLost_flowHasNoNetwork() =
testScope.runTest {
- var latest: WifiNetworkModel? = null
- val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this)
+ val latest by collectLastValue(underTest.wifiNetwork)
getNetworkCallback()
.onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(PRIMARY_WIFI_INFO))
@@ -992,16 +891,13 @@
// THEN we update to no network
assertThat(latest is WifiNetworkModel.Inactive).isTrue()
-
- job.cancel()
}
/** Possible regression test for b/278618530. */
@Test
fun wifiNetwork_currentCarrierMergedNetworkLost_flowHasNoNetwork() =
testScope.runTest {
- var latest: WifiNetworkModel? = null
- val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this)
+ val latest by collectLastValue(underTest.wifiNetwork)
val wifiInfo =
mock<WifiInfo>().apply {
@@ -1019,15 +915,12 @@
// THEN we update to no network
assertThat(latest is WifiNetworkModel.Inactive).isTrue()
-
- job.cancel()
}
@Test
fun wifiNetwork_unknownNetworkLost_flowHasPreviousNetwork() =
testScope.runTest {
- var latest: WifiNetworkModel? = null
- val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this)
+ val latest by collectLastValue(underTest.wifiNetwork)
getNetworkCallback()
.onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(PRIMARY_WIFI_INFO))
@@ -1042,15 +935,12 @@
val latestActive = latest as WifiNetworkModel.Active
assertThat(latestActive.networkId).isEqualTo(NETWORK_ID)
assertThat(latestActive.ssid).isEqualTo(SSID)
-
- job.cancel()
}
@Test
fun wifiNetwork_notCurrentNetworkLost_flowHasCurrentNetwork() =
testScope.runTest {
- var latest: WifiNetworkModel? = null
- val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this)
+ val latest by collectLastValue(underTest.wifiNetwork)
getNetworkCallback()
.onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(PRIMARY_WIFI_INFO))
@@ -1067,16 +957,13 @@
// THEN we still have the new network
assertThat((latest as WifiNetworkModel.Active).networkId).isEqualTo(newNetworkId)
-
- job.cancel()
}
/** Regression test for b/244173280. */
@Test
fun wifiNetwork_multipleSubscribers_newSubscribersGetCurrentValue() =
testScope.runTest {
- var latest1: WifiNetworkModel? = null
- val job1 = underTest.wifiNetwork.onEach { latest1 = it }.launchIn(this)
+ val latest1 by collectLastValue(underTest.wifiNetwork)
getNetworkCallback()
.onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(PRIMARY_WIFI_INFO))
@@ -1087,23 +974,19 @@
assertThat(latest1Active.ssid).isEqualTo(SSID)
// WHEN we add a second subscriber after having already emitted a value
- var latest2: WifiNetworkModel? = null
- val job2 = underTest.wifiNetwork.onEach { latest2 = it }.launchIn(this)
+ val latest2 by collectLastValue(underTest.wifiNetwork)
// THEN the second subscribe receives the already-emitted value
assertThat(latest2 is WifiNetworkModel.Active).isTrue()
val latest2Active = latest2 as WifiNetworkModel.Active
assertThat(latest2Active.networkId).isEqualTo(NETWORK_ID)
assertThat(latest2Active.ssid).isEqualTo(SSID)
-
- job1.cancel()
- job2.cancel()
}
@Test
fun isWifiConnectedWithValidSsid_inactiveNetwork_false() =
testScope.runTest {
- val job = underTest.wifiNetwork.launchIn(this)
+ collectLastValue(underTest.wifiNetwork)
val wifiInfo =
mock<WifiInfo>().apply {
@@ -1116,14 +999,12 @@
.onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(wifiInfo))
assertThat(underTest.isWifiConnectedWithValidSsid()).isFalse()
-
- job.cancel()
}
@Test
fun isWifiConnectedWithValidSsid_carrierMergedNetwork_false() =
testScope.runTest {
- val job = underTest.wifiNetwork.launchIn(this)
+ collectLastValue(underTest.wifiNetwork)
val wifiInfo =
mock<WifiInfo>().apply {
@@ -1133,16 +1014,15 @@
getNetworkCallback()
.onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(wifiInfo))
+ testScope.runCurrent()
assertThat(underTest.isWifiConnectedWithValidSsid()).isFalse()
-
- job.cancel()
}
@Test
fun isWifiConnectedWithValidSsid_invalidNetwork_false() =
testScope.runTest {
- val job = underTest.wifiNetwork.launchIn(this)
+ collectLastValue(underTest.wifiNetwork)
val wifiInfo =
mock<WifiInfo>().apply {
@@ -1156,16 +1036,15 @@
NETWORK,
createWifiNetworkCapabilities(wifiInfo),
)
+ testScope.runCurrent()
assertThat(underTest.isWifiConnectedWithValidSsid()).isFalse()
-
- job.cancel()
}
@Test
fun isWifiConnectedWithValidSsid_activeNetwork_nullSsid_false() =
testScope.runTest {
- val job = underTest.wifiNetwork.launchIn(this)
+ collectLastValue(underTest.wifiNetwork)
val wifiInfo =
mock<WifiInfo>().apply {
@@ -1175,16 +1054,15 @@
getNetworkCallback()
.onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(wifiInfo))
+ testScope.runCurrent()
assertThat(underTest.isWifiConnectedWithValidSsid()).isFalse()
-
- job.cancel()
}
@Test
fun isWifiConnectedWithValidSsid_activeNetwork_unknownSsid_false() =
testScope.runTest {
- val job = underTest.wifiNetwork.launchIn(this)
+ collectLastValue(underTest.wifiNetwork)
val wifiInfo =
mock<WifiInfo>().apply {
@@ -1194,16 +1072,15 @@
getNetworkCallback()
.onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(wifiInfo))
+ testScope.runCurrent()
assertThat(underTest.isWifiConnectedWithValidSsid()).isFalse()
-
- job.cancel()
}
@Test
fun isWifiConnectedWithValidSsid_activeNetwork_validSsid_true() =
testScope.runTest {
- val job = underTest.wifiNetwork.launchIn(this)
+ collectLastValue(underTest.wifiNetwork)
val wifiInfo =
mock<WifiInfo>().apply {
@@ -1213,16 +1090,15 @@
getNetworkCallback()
.onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(wifiInfo))
+ testScope.runCurrent()
assertThat(underTest.isWifiConnectedWithValidSsid()).isTrue()
-
- job.cancel()
}
@Test
fun isWifiConnectedWithValidSsid_activeToInactive_trueToFalse() =
testScope.runTest {
- val job = underTest.wifiNetwork.launchIn(this)
+ collectLastValue(underTest.wifiNetwork)
// Start with active
val wifiInfo =
@@ -1232,71 +1108,60 @@
}
getNetworkCallback()
.onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(wifiInfo))
+ testScope.runCurrent()
+
assertThat(underTest.isWifiConnectedWithValidSsid()).isTrue()
// WHEN the network is lost
getNetworkCallback().onLost(NETWORK)
+ testScope.runCurrent()
// THEN the isWifiConnected updates
assertThat(underTest.isWifiConnectedWithValidSsid()).isFalse()
-
- job.cancel()
}
@Test
fun wifiActivity_callbackGivesNone_activityFlowHasNone() =
testScope.runTest {
- var latest: DataActivityModel? = null
- val job = underTest.wifiActivity.onEach { latest = it }.launchIn(this)
+ val latest by collectLastValue(underTest.wifiActivity)
getTrafficStateCallback().onStateChanged(TrafficStateCallback.DATA_ACTIVITY_NONE)
assertThat(latest)
.isEqualTo(DataActivityModel(hasActivityIn = false, hasActivityOut = false))
-
- job.cancel()
}
@Test
fun wifiActivity_callbackGivesIn_activityFlowHasIn() =
testScope.runTest {
- var latest: DataActivityModel? = null
- val job = underTest.wifiActivity.onEach { latest = it }.launchIn(this)
+ val latest by collectLastValue(underTest.wifiActivity)
getTrafficStateCallback().onStateChanged(TrafficStateCallback.DATA_ACTIVITY_IN)
assertThat(latest)
.isEqualTo(DataActivityModel(hasActivityIn = true, hasActivityOut = false))
-
- job.cancel()
}
@Test
fun wifiActivity_callbackGivesOut_activityFlowHasOut() =
testScope.runTest {
- var latest: DataActivityModel? = null
- val job = underTest.wifiActivity.onEach { latest = it }.launchIn(this)
+ val latest by collectLastValue(underTest.wifiActivity)
getTrafficStateCallback().onStateChanged(TrafficStateCallback.DATA_ACTIVITY_OUT)
assertThat(latest)
.isEqualTo(DataActivityModel(hasActivityIn = false, hasActivityOut = true))
-
- job.cancel()
}
@Test
fun wifiActivity_callbackGivesInout_activityFlowHasInAndOut() =
testScope.runTest {
- var latest: DataActivityModel? = null
- val job = underTest.wifiActivity.onEach { latest = it }.launchIn(this)
+ val latest by collectLastValue(underTest.wifiActivity)
getTrafficStateCallback().onStateChanged(TrafficStateCallback.DATA_ACTIVITY_INOUT)
assertThat(latest)
.isEqualTo(DataActivityModel(hasActivityIn = true, hasActivityOut = true))
-
- job.cancel()
}
private fun createRepo(): WifiRepositoryImpl {
@@ -1314,18 +1179,21 @@
}
private fun getTrafficStateCallback(): TrafficStateCallback {
+ testScope.runCurrent()
val callbackCaptor = argumentCaptor<TrafficStateCallback>()
verify(wifiManager).registerTrafficStateCallback(any(), callbackCaptor.capture())
return callbackCaptor.value!!
}
private fun getNetworkCallback(): ConnectivityManager.NetworkCallback {
+ testScope.runCurrent()
val callbackCaptor = argumentCaptor<ConnectivityManager.NetworkCallback>()
verify(connectivityManager).registerNetworkCallback(any(), callbackCaptor.capture())
return callbackCaptor.value!!
}
private fun getDefaultNetworkCallback(): ConnectivityManager.NetworkCallback {
+ testScope.runCurrent()
val callbackCaptor = argumentCaptor<ConnectivityManager.NetworkCallback>()
verify(connectivityManager).registerDefaultNetworkCallback(callbackCaptor.capture())
return callbackCaptor.value!!
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLibTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLibTest.kt
new file mode 100644
index 0000000..7002cbb
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLibTest.kt
@@ -0,0 +1,665 @@
+/*
+ * Copyright (C) 2023 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.data.repository.prod
+
+import android.net.wifi.WifiManager
+import android.net.wifi.WifiManager.UNKNOWN_SSID
+import android.testing.TestableLooper
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.statusbar.connectivity.WifiPickerTrackerFactory
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.prod.WifiRepositoryImpl.Companion.WIFI_NETWORK_DEFAULT
+import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.capture
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.time.FakeSystemClock
+import com.android.wifitrackerlib.MergedCarrierEntry
+import com.android.wifitrackerlib.WifiEntry
+import com.android.wifitrackerlib.WifiPickerTracker
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+
+/**
+ * Note: Most of these tests are duplicates of [WifiRepositoryImplTest] tests.
+ *
+ * Any new tests added here may also need to be added to [WifiRepositoryImplTest].
+ */
+@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+class WifiRepositoryViaTrackerLibTest : SysuiTestCase() {
+
+ private lateinit var underTest: WifiRepositoryViaTrackerLib
+
+ private val executor = FakeExecutor(FakeSystemClock())
+ private val logger = LogBuffer("name", maxSize = 100, logcatEchoTracker = mock())
+ private val tableLogger = mock<TableLogBuffer>()
+ private val wifiManager =
+ mock<WifiManager>().apply { whenever(this.maxSignalLevel).thenReturn(10) }
+ private val wifiPickerTrackerFactory = mock<WifiPickerTrackerFactory>()
+ private val wifiPickerTracker = mock<WifiPickerTracker>()
+
+ private val callbackCaptor = argumentCaptor<WifiPickerTracker.WifiPickerTrackerCallback>()
+
+ private val dispatcher = StandardTestDispatcher()
+ private val testScope = TestScope(dispatcher)
+
+ @Before
+ fun setUp() {
+ whenever(wifiPickerTrackerFactory.create(any(), capture(callbackCaptor)))
+ .thenReturn(wifiPickerTracker)
+ underTest = createRepo()
+ }
+
+ @Test
+ fun isWifiEnabled_enabled_true() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.isWifiEnabled)
+
+ whenever(wifiPickerTracker.wifiState).thenReturn(WifiManager.WIFI_STATE_ENABLED)
+ getCallback().onWifiStateChanged()
+
+ assertThat(latest).isTrue()
+ }
+
+ @Test
+ fun isWifiEnabled_enabling_false() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.isWifiEnabled)
+
+ whenever(wifiPickerTracker.wifiState).thenReturn(WifiManager.WIFI_STATE_ENABLING)
+ getCallback().onWifiStateChanged()
+
+ assertThat(latest).isFalse()
+ }
+
+ @Test
+ fun isWifiEnabled_disabling_true() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.isWifiEnabled)
+
+ whenever(wifiPickerTracker.wifiState).thenReturn(WifiManager.WIFI_STATE_DISABLING)
+ getCallback().onWifiStateChanged()
+
+ assertThat(latest).isFalse()
+ }
+
+ @Test
+ fun isWifiEnabled_disabled_false() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.isWifiEnabled)
+
+ whenever(wifiPickerTracker.wifiState).thenReturn(WifiManager.WIFI_STATE_DISABLED)
+ getCallback().onWifiStateChanged()
+
+ assertThat(latest).isFalse()
+ }
+
+ @Test
+ fun isWifiEnabled_respondsToUpdates() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.isWifiEnabled)
+ executor.runAllReady()
+
+ whenever(wifiPickerTracker.wifiState).thenReturn(WifiManager.WIFI_STATE_ENABLED)
+ getCallback().onWifiStateChanged()
+
+ assertThat(latest).isTrue()
+
+ whenever(wifiPickerTracker.wifiState).thenReturn(WifiManager.WIFI_STATE_DISABLED)
+ getCallback().onWifiStateChanged()
+
+ assertThat(latest).isFalse()
+ }
+
+ @Test
+ fun isWifiDefault_initiallyGetsDefault() =
+ testScope.runTest { assertThat(underTest.isWifiDefault.value).isFalse() }
+
+ @Test
+ fun isWifiDefault_wifiNetwork_isTrue() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.isWifiDefault)
+
+ val wifiEntry =
+ mock<WifiEntry>().apply { whenever(this.isDefaultNetwork).thenReturn(true) }
+ whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
+ getCallback().onWifiEntriesChanged()
+
+ assertThat(latest).isTrue()
+ }
+
+ @Test
+ fun isWifiDefault_carrierMerged_isTrue() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.isWifiDefault)
+
+ val wifiEntry =
+ mock<MergedCarrierEntry>().apply {
+ whenever(this.isDefaultNetwork).thenReturn(true)
+ }
+ whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
+ getCallback().onWifiEntriesChanged()
+
+ assertThat(latest).isTrue()
+ }
+
+ @Test
+ fun isWifiDefault_wifiNetworkNotDefault_isFalse() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.isWifiDefault)
+
+ val wifiEntry =
+ mock<WifiEntry>().apply { whenever(this.isDefaultNetwork).thenReturn(false) }
+ whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
+ getCallback().onWifiEntriesChanged()
+
+ assertThat(latest).isFalse()
+ }
+
+ @Test
+ fun isWifiDefault_carrierMergedNotDefault_isFalse() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.isWifiDefault)
+
+ val wifiEntry =
+ mock<MergedCarrierEntry>().apply {
+ whenever(this.isDefaultNetwork).thenReturn(false)
+ }
+ whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
+ getCallback().onWifiEntriesChanged()
+
+ assertThat(latest).isFalse()
+ }
+
+ @Test
+ fun isWifiDefault_noWifiNetwork_isFalse() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.isWifiDefault)
+
+ // First, add a network
+ val wifiEntry =
+ mock<WifiEntry>().apply { whenever(this.isDefaultNetwork).thenReturn(true) }
+ whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
+ getCallback().onWifiEntriesChanged()
+
+ assertThat(latest).isTrue()
+
+ // WHEN the network is lost
+ whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(null)
+ getCallback().onWifiEntriesChanged()
+
+ // THEN we update to false
+ assertThat(latest).isFalse()
+ }
+
+ @Test
+ fun wifiNetwork_initiallyGetsDefault() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.wifiNetwork)
+
+ assertThat(latest).isEqualTo(WIFI_NETWORK_DEFAULT)
+ }
+
+ @Test
+ fun wifiNetwork_primaryWifiNetworkAdded_flowHasNetwork() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.wifiNetwork)
+
+ val wifiEntry =
+ mock<WifiEntry>().apply {
+ whenever(this.isPrimaryNetwork).thenReturn(true)
+ whenever(this.level).thenReturn(3)
+ whenever(this.ssid).thenReturn(SSID)
+ }
+ whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
+ getCallback().onWifiEntriesChanged()
+
+ assertThat(latest is WifiNetworkModel.Active).isTrue()
+ val latestActive = latest as WifiNetworkModel.Active
+ assertThat(latestActive.level).isEqualTo(3)
+ assertThat(latestActive.ssid).isEqualTo(SSID)
+ }
+
+ @Test
+ fun wifiNetwork_isCarrierMerged_flowHasCarrierMerged() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.wifiNetwork)
+
+ val wifiEntry =
+ mock<MergedCarrierEntry>().apply {
+ whenever(this.isPrimaryNetwork).thenReturn(true)
+ whenever(this.level).thenReturn(3)
+ }
+ whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
+ getCallback().onWifiEntriesChanged()
+
+ assertThat(latest is WifiNetworkModel.CarrierMerged).isTrue()
+ val latestMerged = latest as WifiNetworkModel.CarrierMerged
+ assertThat(latestMerged.level).isEqualTo(3)
+ // numberOfLevels = maxSignalLevel + 1
+ }
+
+ @Test
+ fun wifiNetwork_isCarrierMerged_getsMaxSignalLevel() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.wifiNetwork)
+
+ val wifiEntry =
+ mock<MergedCarrierEntry>().apply {
+ whenever(this.isPrimaryNetwork).thenReturn(true)
+ }
+ whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
+ whenever(wifiManager.maxSignalLevel).thenReturn(5)
+
+ getCallback().onWifiEntriesChanged()
+
+ assertThat(latest is WifiNetworkModel.CarrierMerged).isTrue()
+ val latestMerged = latest as WifiNetworkModel.CarrierMerged
+ // numberOfLevels = maxSignalLevel + 1
+ assertThat(latestMerged.numberOfLevels).isEqualTo(6)
+ }
+
+ /* TODO(b/292534484): Re-enable this test once WifiTrackerLib gives us the subscription ID.
+ @Test
+ fun wifiNetwork_carrierMergedButInvalidSubId_flowHasInvalid() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.wifiNetwork)
+
+ val wifiInfo =
+ mock<WifiInfo>().apply {
+ whenever(this.isPrimary).thenReturn(true)
+ whenever(this.isCarrierMerged).thenReturn(true)
+ whenever(this.subscriptionId).thenReturn(INVALID_SUBSCRIPTION_ID)
+ }
+
+ getNetworkCallback()
+ .onCapabilitiesChanged(
+ NETWORK,
+ createWifiNetworkCapabilities(wifiInfo),
+ )
+
+ assertThat(latest).isInstanceOf(WifiNetworkModel.Invalid::class.java)
+ }
+
+ */
+
+ @Test
+ fun wifiNetwork_notValidated_networkNotValidated() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.wifiNetwork)
+
+ val wifiEntry =
+ mock<WifiEntry>().apply {
+ whenever(this.isPrimaryNetwork).thenReturn(true)
+ whenever(this.hasInternetAccess()).thenReturn(false)
+ }
+ whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
+ getCallback().onWifiEntriesChanged()
+
+ assertThat((latest as WifiNetworkModel.Active).isValidated).isFalse()
+ }
+
+ @Test
+ fun wifiNetwork_validated_networkValidated() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.wifiNetwork)
+
+ val wifiEntry =
+ mock<WifiEntry>().apply {
+ whenever(this.isPrimaryNetwork).thenReturn(true)
+ whenever(this.hasInternetAccess()).thenReturn(true)
+ }
+ whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
+ getCallback().onWifiEntriesChanged()
+
+ assertThat((latest as WifiNetworkModel.Active).isValidated).isTrue()
+ }
+
+ @Test
+ fun wifiNetwork_nonPrimaryWifiNetworkAdded_flowHasNoNetwork() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.wifiNetwork)
+
+ val wifiEntry =
+ mock<WifiEntry>().apply { whenever(this.isPrimaryNetwork).thenReturn(false) }
+ whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
+ getCallback().onWifiEntriesChanged()
+
+ assertThat(latest).isEqualTo(WifiNetworkModel.Inactive)
+ }
+
+ @Test
+ fun wifiNetwork_nonPrimaryCarrierMergedNetworkAdded_flowHasNoNetwork() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.wifiNetwork)
+
+ val wifiEntry =
+ mock<MergedCarrierEntry>().apply {
+ whenever(this.isPrimaryNetwork).thenReturn(false)
+ }
+ whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
+ getCallback().onWifiEntriesChanged()
+
+ assertThat(latest).isEqualTo(WifiNetworkModel.Inactive)
+ }
+
+ @Test
+ fun wifiNetwork_newPrimaryWifiNetwork_flowHasNewNetwork() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.wifiNetwork)
+
+ // Start with the original network
+ val wifiEntry =
+ mock<WifiEntry>().apply {
+ whenever(this.isPrimaryNetwork).thenReturn(true)
+ whenever(this.level).thenReturn(3)
+ whenever(this.ssid).thenReturn("AB")
+ }
+ whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
+ getCallback().onWifiEntriesChanged()
+
+ assertThat(latest is WifiNetworkModel.Active).isTrue()
+ var latestActive = latest as WifiNetworkModel.Active
+ assertThat(latestActive.level).isEqualTo(3)
+ assertThat(latestActive.ssid).isEqualTo("AB")
+
+ // WHEN we update to a new primary network
+ val newWifiEntry =
+ mock<WifiEntry>().apply {
+ whenever(this.isPrimaryNetwork).thenReturn(true)
+ whenever(this.level).thenReturn(4)
+ whenever(this.ssid).thenReturn("CD")
+ }
+ whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(newWifiEntry)
+ getCallback().onWifiEntriesChanged()
+
+ // THEN we use the new network
+ assertThat(latest is WifiNetworkModel.Active).isTrue()
+ latestActive = latest as WifiNetworkModel.Active
+ assertThat(latestActive.level).isEqualTo(4)
+ assertThat(latestActive.ssid).isEqualTo("CD")
+ }
+
+ @Test
+ fun wifiNetwork_noCurrentNetwork_networkLost_flowHasNoNetwork() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.wifiNetwork)
+
+ // WHEN we receive a null network without any networks beforehand
+ whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(null)
+ getCallback().onWifiEntriesChanged()
+
+ // THEN there's no crash and we still have no network
+ assertThat(latest is WifiNetworkModel.Inactive).isTrue()
+ }
+
+ @Test
+ fun wifiNetwork_currentActiveNetworkLost_flowHasNoNetwork() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.wifiNetwork)
+
+ val wifiEntry =
+ mock<WifiEntry>().apply {
+ whenever(this.isPrimaryNetwork).thenReturn(true)
+ whenever(this.ssid).thenReturn(SSID)
+ }
+ whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
+ getCallback().onWifiEntriesChanged()
+
+ assertThat((latest as WifiNetworkModel.Active).ssid).isEqualTo(SSID)
+
+ // WHEN we lose our current network
+ whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(null)
+ getCallback().onWifiEntriesChanged()
+
+ // THEN we update to no network
+ assertThat(latest is WifiNetworkModel.Inactive).isTrue()
+ }
+
+ /** Possible regression test for b/278618530. */
+ @Test
+ fun wifiNetwork_currentCarrierMergedNetworkLost_flowHasNoNetwork() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.wifiNetwork)
+
+ val wifiEntry =
+ mock<MergedCarrierEntry>().apply {
+ whenever(this.isPrimaryNetwork).thenReturn(true)
+ whenever(this.level).thenReturn(3)
+ }
+ whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
+ getCallback().onWifiEntriesChanged()
+
+ assertThat(latest is WifiNetworkModel.CarrierMerged).isTrue()
+ assertThat((latest as WifiNetworkModel.CarrierMerged).level).isEqualTo(3)
+
+ // WHEN we lose our current network
+ whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(null)
+ getCallback().onWifiEntriesChanged()
+
+ // THEN we update to no network
+ assertThat(latest is WifiNetworkModel.Inactive).isTrue()
+ }
+
+ /** Regression test for b/244173280. */
+ @Test
+ fun wifiNetwork_multipleSubscribers_newSubscribersGetCurrentValue() =
+ testScope.runTest {
+ val latest1 by collectLastValue(underTest.wifiNetwork)
+
+ val wifiEntry =
+ mock<WifiEntry>().apply {
+ whenever(this.isPrimaryNetwork).thenReturn(true)
+ whenever(this.level).thenReturn(1)
+ whenever(this.ssid).thenReturn(SSID)
+ }
+ whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
+ getCallback().onWifiEntriesChanged()
+
+ assertThat(latest1 is WifiNetworkModel.Active).isTrue()
+ val latest1Active = latest1 as WifiNetworkModel.Active
+ assertThat(latest1Active.level).isEqualTo(1)
+ assertThat(latest1Active.ssid).isEqualTo(SSID)
+
+ // WHEN we add a second subscriber after having already emitted a value
+ val latest2 by collectLastValue(underTest.wifiNetwork)
+
+ // THEN the second subscribe receives the already-emitted value
+ assertThat(latest2 is WifiNetworkModel.Active).isTrue()
+ val latest2Active = latest2 as WifiNetworkModel.Active
+ assertThat(latest2Active.level).isEqualTo(1)
+ assertThat(latest2Active.ssid).isEqualTo(SSID)
+ }
+
+ @Test
+ fun isWifiConnectedWithValidSsid_inactiveNetwork_false() =
+ testScope.runTest {
+ collectLastValue(underTest.wifiNetwork)
+
+ whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(null)
+ getCallback().onWifiEntriesChanged()
+
+ assertThat(underTest.isWifiConnectedWithValidSsid()).isFalse()
+ }
+
+ @Test
+ fun isWifiConnectedWithValidSsid_nonPrimaryNetwork_false() =
+ testScope.runTest {
+ collectLastValue(underTest.wifiNetwork)
+
+ val wifiEntry =
+ mock<WifiEntry>().apply { whenever(this.isPrimaryNetwork).thenReturn(false) }
+ whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
+ getCallback().onWifiEntriesChanged()
+ testScope.runCurrent()
+
+ assertThat(underTest.isWifiConnectedWithValidSsid()).isFalse()
+ }
+
+ @Test
+ fun isWifiConnectedWithValidSsid_carrierMergedNetwork_false() =
+ testScope.runTest {
+ collectLastValue(underTest.wifiNetwork)
+
+ val wifiEntry =
+ mock<MergedCarrierEntry>().apply {
+ whenever(this.isPrimaryNetwork).thenReturn(true)
+ }
+ whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
+ getCallback().onWifiEntriesChanged()
+ testScope.runCurrent()
+
+ assertThat(underTest.isWifiConnectedWithValidSsid()).isFalse()
+ }
+
+ /* TODO(b/292534484): Re-enable this test once WifiTrackerLib gives us the subscription ID.
+ @Test
+ fun isWifiConnectedWithValidSsid_invalidNetwork_false() =
+ testScope.runTest {
+ collectLastValue(underTest.wifiNetwork)
+
+ val wifiInfo =
+ mock<WifiInfo>().apply {
+ whenever(this.isPrimary).thenReturn(true)
+ whenever(this.isCarrierMerged).thenReturn(true)
+ whenever(this.subscriptionId).thenReturn(INVALID_SUBSCRIPTION_ID)
+ }
+
+ getNetworkCallback()
+ .onCapabilitiesChanged(
+ NETWORK,
+ createWifiNetworkCapabilities(wifiInfo),
+ )
+ testScope.runCurrent()
+
+ assertThat(underTest.isWifiConnectedWithValidSsid()).isFalse()
+ }
+
+ */
+
+ @Test
+ fun isWifiConnectedWithValidSsid_activeNetwork_nullSsid_false() =
+ testScope.runTest {
+ collectLastValue(underTest.wifiNetwork)
+
+ val wifiEntry =
+ mock<WifiEntry>().apply {
+ whenever(this.isPrimaryNetwork).thenReturn(true)
+ whenever(this.ssid).thenReturn(null)
+ }
+ whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
+ getCallback().onWifiEntriesChanged()
+ testScope.runCurrent()
+
+ assertThat(underTest.isWifiConnectedWithValidSsid()).isFalse()
+ }
+
+ @Test
+ fun isWifiConnectedWithValidSsid_activeNetwork_unknownSsid_false() =
+ testScope.runTest {
+ collectLastValue(underTest.wifiNetwork)
+
+ val wifiEntry =
+ mock<WifiEntry>().apply {
+ whenever(this.isPrimaryNetwork).thenReturn(true)
+ whenever(this.ssid).thenReturn(UNKNOWN_SSID)
+ }
+ whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
+ getCallback().onWifiEntriesChanged()
+ testScope.runCurrent()
+
+ assertThat(underTest.isWifiConnectedWithValidSsid()).isFalse()
+ }
+
+ @Test
+ fun isWifiConnectedWithValidSsid_activeNetwork_validSsid_true() =
+ testScope.runTest {
+ collectLastValue(underTest.wifiNetwork)
+
+ val wifiEntry =
+ mock<WifiEntry>().apply {
+ whenever(this.isPrimaryNetwork).thenReturn(true)
+ whenever(this.ssid).thenReturn("fakeSsid")
+ }
+ whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
+ getCallback().onWifiEntriesChanged()
+ testScope.runCurrent()
+
+ assertThat(underTest.isWifiConnectedWithValidSsid()).isTrue()
+ }
+
+ @Test
+ fun isWifiConnectedWithValidSsid_activeToInactive_trueToFalse() =
+ testScope.runTest {
+ collectLastValue(underTest.wifiNetwork)
+
+ // Start with active
+ val wifiEntry =
+ mock<WifiEntry>().apply {
+ whenever(this.isPrimaryNetwork).thenReturn(true)
+ whenever(this.ssid).thenReturn("fakeSsid")
+ }
+ whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
+ getCallback().onWifiEntriesChanged()
+ testScope.runCurrent()
+
+ assertThat(underTest.isWifiConnectedWithValidSsid()).isTrue()
+
+ // WHEN the network is lost
+ whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(null)
+ getCallback().onWifiEntriesChanged()
+ testScope.runCurrent()
+
+ // THEN the isWifiConnected updates
+ assertThat(underTest.isWifiConnectedWithValidSsid()).isFalse()
+ }
+
+ private fun getCallback(): WifiPickerTracker.WifiPickerTrackerCallback {
+ testScope.runCurrent()
+ return callbackCaptor.value
+ }
+
+ private fun createRepo(): WifiRepositoryViaTrackerLib {
+ return WifiRepositoryViaTrackerLib(
+ testScope.backgroundScope,
+ executor,
+ wifiPickerTrackerFactory,
+ wifiManager,
+ logger,
+ tableLogger,
+ )
+ }
+
+ private companion object {
+ const val SSID = "AB"
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorImplTest.kt
index fc2277b..6fe88c1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorImplTest.kt
@@ -17,7 +17,9 @@
package com.android.systemui.statusbar.pipeline.wifi.domain.interactor
import android.net.wifi.WifiManager
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.systemui.RoboPilotTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.statusbar.pipeline.shared.data.model.ConnectivitySlot
import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
@@ -25,18 +27,22 @@
import com.android.systemui.statusbar.pipeline.wifi.data.repository.FakeWifiRepository
import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
-import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
import kotlinx.coroutines.yield
import org.junit.Before
import org.junit.Test
+import org.junit.runner.RunWith
@OptIn(ExperimentalCoroutinesApi::class)
@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
@SmallTest
+@RoboPilotTest
+@RunWith(AndroidJUnit4::class)
class WifiInteractorImplTest : SysuiTestCase() {
private lateinit var underTest: WifiInteractor
@@ -44,6 +50,8 @@
private lateinit var connectivityRepository: FakeConnectivityRepository
private lateinit var wifiRepository: FakeWifiRepository
+ private val testScope = TestScope()
+
@Before
fun setUp() {
connectivityRepository = FakeConnectivityRepository()
@@ -53,11 +61,12 @@
@Test
fun ssid_unavailableNetwork_outputsNull() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
wifiRepository.setWifiNetwork(WifiNetworkModel.Unavailable)
var latest: String? = "default"
val job = underTest.ssid.onEach { latest = it }.launchIn(this)
+ runCurrent()
assertThat(latest).isNull()
@@ -66,11 +75,12 @@
@Test
fun ssid_inactiveNetwork_outputsNull() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
wifiRepository.setWifiNetwork(WifiNetworkModel.Inactive)
var latest: String? = "default"
val job = underTest.ssid.onEach { latest = it }.launchIn(this)
+ runCurrent()
assertThat(latest).isNull()
@@ -79,13 +89,14 @@
@Test
fun ssid_carrierMergedNetwork_outputsNull() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
wifiRepository.setWifiNetwork(
WifiNetworkModel.CarrierMerged(networkId = 1, subscriptionId = 2, level = 1)
)
var latest: String? = "default"
val job = underTest.ssid.onEach { latest = it }.launchIn(this)
+ runCurrent()
assertThat(latest).isNull()
@@ -94,7 +105,7 @@
@Test
fun ssid_isPasspointAccessPoint_outputsPasspointName() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
wifiRepository.setWifiNetwork(
WifiNetworkModel.Active(
networkId = 1,
@@ -106,6 +117,7 @@
var latest: String? = null
val job = underTest.ssid.onEach { latest = it }.launchIn(this)
+ runCurrent()
assertThat(latest).isEqualTo("friendly")
@@ -114,7 +126,7 @@
@Test
fun ssid_isOnlineSignUpForPasspoint_outputsPasspointName() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
wifiRepository.setWifiNetwork(
WifiNetworkModel.Active(
networkId = 1,
@@ -126,6 +138,7 @@
var latest: String? = null
val job = underTest.ssid.onEach { latest = it }.launchIn(this)
+ runCurrent()
assertThat(latest).isEqualTo("friendly")
@@ -134,7 +147,7 @@
@Test
fun ssid_unknownSsid_outputsNull() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
wifiRepository.setWifiNetwork(
WifiNetworkModel.Active(
networkId = 1,
@@ -145,6 +158,7 @@
var latest: String? = "default"
val job = underTest.ssid.onEach { latest = it }.launchIn(this)
+ runCurrent()
assertThat(latest).isNull()
@@ -153,7 +167,7 @@
@Test
fun ssid_validSsid_outputsSsid() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
wifiRepository.setWifiNetwork(
WifiNetworkModel.Active(
networkId = 1,
@@ -164,6 +178,7 @@
var latest: String? = null
val job = underTest.ssid.onEach { latest = it }.launchIn(this)
+ runCurrent()
assertThat(latest).isEqualTo("MyAwesomeWifiNetwork")
@@ -172,7 +187,7 @@
@Test
fun isEnabled_matchesRepoIsEnabled() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
var latest: Boolean? = null
val job = underTest.isEnabled.onEach { latest = it }.launchIn(this)
@@ -193,7 +208,7 @@
@Test
fun isDefault_matchesRepoIsDefault() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
var latest: Boolean? = null
val job = underTest.isDefault.onEach { latest = it }.launchIn(this)
@@ -214,7 +229,7 @@
@Test
fun wifiNetwork_matchesRepoWifiNetwork() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
val wifiNetwork =
WifiNetworkModel.Active(
networkId = 45,
@@ -227,6 +242,7 @@
var latest: WifiNetworkModel? = null
val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this)
+ runCurrent()
assertThat(latest).isEqualTo(wifiNetwork)
@@ -235,7 +251,7 @@
@Test
fun activity_matchesRepoWifiActivity() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
var latest: DataActivityModel? = null
val job = underTest.activity.onEach { latest = it }.launchIn(this)
@@ -259,11 +275,12 @@
@Test
fun isForceHidden_repoHasWifiHidden_outputsTrue() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
connectivityRepository.setForceHiddenIcons(setOf(ConnectivitySlot.WIFI))
var latest: Boolean? = null
val job = underTest.isForceHidden.onEach { latest = it }.launchIn(this)
+ runCurrent()
assertThat(latest).isTrue()
@@ -272,16 +289,15 @@
@Test
fun isForceHidden_repoDoesNotHaveWifiHidden_outputsFalse() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
connectivityRepository.setForceHiddenIcons(setOf())
var latest: Boolean? = null
val job = underTest.isForceHidden.onEach { latest = it }.launchIn(this)
+ runCurrent()
assertThat(latest).isFalse()
job.cancel()
}
}
-
-private val IMMEDIATE = Dispatchers.Main.immediate
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt
index cb469ea..bdeba2a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt
@@ -16,8 +16,11 @@
package com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.systemui.RoboPilotTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.statusbar.phone.StatusBarLocation
import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
@@ -36,23 +39,19 @@
import com.android.systemui.statusbar.pipeline.wifi.ui.model.WifiIcon
import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.LocationBasedWifiViewModel.Companion.viewModelForLocation
import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.launchIn
-import kotlinx.coroutines.flow.onEach
-import kotlinx.coroutines.runBlocking
-import kotlinx.coroutines.yield
-import org.junit.After
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
+import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.Mockito.`when` as whenever
import org.mockito.MockitoAnnotations
-@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
@SmallTest
+@RoboPilotTest
+@RunWith(AndroidJUnit4::class)
class WifiViewModelTest : SysuiTestCase() {
private lateinit var underTest: WifiViewModel
@@ -66,7 +65,7 @@
private lateinit var interactor: WifiInteractor
private lateinit var airplaneModeViewModel: AirplaneModeViewModel
private val shouldShowSignalSpacerProviderFlow = MutableStateFlow(false)
- private lateinit var scope: CoroutineScope
+ private val testScope = TestScope()
@Before
fun setUp() {
@@ -76,7 +75,6 @@
wifiRepository = FakeWifiRepository()
wifiRepository.setIsWifiEnabled(true)
interactor = WifiInteractorImpl(connectivityRepository, wifiRepository)
- scope = CoroutineScope(IMMEDIATE)
airplaneModeViewModel =
AirplaneModeViewModelImpl(
AirplaneModeInteractor(
@@ -84,17 +82,12 @@
connectivityRepository,
),
tableLogBuffer,
- scope,
+ testScope.backgroundScope,
)
createAndSetViewModel()
}
- @After
- fun tearDown() {
- scope.cancel()
- }
-
// See [WifiViewModelIconParameterizedTest] for additional view model tests.
// Note on testing: [WifiViewModel] exposes 3 different instances of
@@ -104,104 +97,65 @@
@Test
fun wifiIcon_allLocationViewModelsReceiveSameData() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
val home = viewModelForLocation(underTest, StatusBarLocation.HOME)
val keyguard = viewModelForLocation(underTest, StatusBarLocation.KEYGUARD)
val qs = viewModelForLocation(underTest, StatusBarLocation.QS)
- var latestHome: WifiIcon? = null
- val jobHome = home.wifiIcon.onEach { latestHome = it }.launchIn(this)
-
- var latestKeyguard: WifiIcon? = null
- val jobKeyguard = keyguard.wifiIcon.onEach { latestKeyguard = it }.launchIn(this)
-
- var latestQs: WifiIcon? = null
- val jobQs = qs.wifiIcon.onEach { latestQs = it }.launchIn(this)
+ val latestHome by collectLastValue(home.wifiIcon)
+ val latestKeyguard by collectLastValue(keyguard.wifiIcon)
+ val latestQs by collectLastValue(qs.wifiIcon)
wifiRepository.setWifiNetwork(
WifiNetworkModel.Active(NETWORK_ID, isValidated = true, level = 1)
)
- yield()
assertThat(latestHome).isInstanceOf(WifiIcon.Visible::class.java)
assertThat(latestHome).isEqualTo(latestKeyguard)
assertThat(latestKeyguard).isEqualTo(latestQs)
-
- jobHome.cancel()
- jobKeyguard.cancel()
- jobQs.cancel()
}
@Test
fun activity_showActivityConfigFalse_outputsFalse() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
whenever(connectivityConstants.shouldShowActivityConfig).thenReturn(false)
createAndSetViewModel()
wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK)
- var activityIn: Boolean? = null
- val activityInJob =
- underTest.isActivityInViewVisible.onEach { activityIn = it }.launchIn(this)
-
- var activityOut: Boolean? = null
- val activityOutJob =
- underTest.isActivityOutViewVisible.onEach { activityOut = it }.launchIn(this)
-
- var activityContainer: Boolean? = null
- val activityContainerJob =
- underTest.isActivityContainerVisible
- .onEach { activityContainer = it }
- .launchIn(this)
+ val activityIn by collectLastValue(underTest.isActivityInViewVisible)
+ val activityOut by collectLastValue(underTest.isActivityOutViewVisible)
+ val activityContainer by collectLastValue(underTest.isActivityContainerVisible)
// 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) {
+ testScope.runTest {
whenever(connectivityConstants.shouldShowActivityConfig).thenReturn(false)
createAndSetViewModel()
wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK)
- var activityIn: Boolean? = null
- val activityInJob =
- underTest.isActivityInViewVisible.onEach { activityIn = it }.launchIn(this)
-
- var activityOut: Boolean? = null
- val activityOutJob =
- underTest.isActivityOutViewVisible.onEach { activityOut = it }.launchIn(this)
-
- var activityContainer: Boolean? = null
- val activityContainerJob =
- underTest.isActivityContainerVisible
- .onEach { activityContainer = it }
- .launchIn(this)
+ val activityIn by collectLastValue(underTest.isActivityInViewVisible)
+ val activityOut by collectLastValue(underTest.isActivityOutViewVisible)
+ val activityContainer by collectLastValue(underTest.isActivityContainerVisible)
// WHEN we update the repo to have activity
val activity = DataActivityModel(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) {
+ testScope.runTest {
whenever(connectivityConstants.shouldShowActivityConfig).thenReturn(true)
createAndSetViewModel()
@@ -209,38 +163,23 @@
WifiNetworkModel.Active(NETWORK_ID, ssid = null, level = 1)
)
- var activityIn: Boolean? = null
- val activityInJob =
- underTest.isActivityInViewVisible.onEach { activityIn = it }.launchIn(this)
-
- var activityOut: Boolean? = null
- val activityOutJob =
- underTest.isActivityOutViewVisible.onEach { activityOut = it }.launchIn(this)
-
- var activityContainer: Boolean? = null
- val activityContainerJob =
- underTest.isActivityContainerVisible
- .onEach { activityContainer = it }
- .launchIn(this)
+ val activityIn by collectLastValue(underTest.isActivityInViewVisible)
+ val activityOut by collectLastValue(underTest.isActivityOutViewVisible)
+ val activityContainer by collectLastValue(underTest.isActivityContainerVisible)
// WHEN we update the repo to have activity
val activity = DataActivityModel(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) {
+ testScope.runTest {
whenever(connectivityConstants.shouldShowActivityConfig).thenReturn(true)
createAndSetViewModel()
wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK)
@@ -249,250 +188,187 @@
val keyguard = viewModelForLocation(underTest, StatusBarLocation.KEYGUARD)
val qs = viewModelForLocation(underTest, StatusBarLocation.QS)
- var latestHome: Boolean? = null
- val jobHome = home.isActivityInViewVisible.onEach { latestHome = it }.launchIn(this)
-
- var latestKeyguard: Boolean? = null
- val jobKeyguard =
- keyguard.isActivityInViewVisible.onEach { latestKeyguard = it }.launchIn(this)
-
- var latestQs: Boolean? = null
- val jobQs = qs.isActivityInViewVisible.onEach { latestQs = it }.launchIn(this)
+ val latestHome by collectLastValue(home.isActivityInViewVisible)
+ val latestKeyguard by collectLastValue(keyguard.isActivityInViewVisible)
+ val latestQs by collectLastValue(qs.isActivityInViewVisible)
val activity = DataActivityModel(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) {
+ testScope.runTest {
whenever(connectivityConstants.shouldShowActivityConfig).thenReturn(true)
createAndSetViewModel()
wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK)
- var latest: Boolean? = null
- val job = underTest.isActivityInViewVisible.onEach { latest = it }.launchIn(this)
+ val latest by collectLastValue(underTest.isActivityInViewVisible)
val activity = DataActivityModel(hasActivityIn = true, hasActivityOut = false)
wifiRepository.setWifiActivity(activity)
- yield()
assertThat(latest).isTrue()
-
- job.cancel()
}
@Test
fun activityIn_hasActivityInFalse_outputsFalse() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
whenever(connectivityConstants.shouldShowActivityConfig).thenReturn(true)
createAndSetViewModel()
wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK)
- var latest: Boolean? = null
- val job = underTest.isActivityInViewVisible.onEach { latest = it }.launchIn(this)
+ val latest by collectLastValue(underTest.isActivityInViewVisible)
val activity = DataActivityModel(hasActivityIn = false, hasActivityOut = true)
wifiRepository.setWifiActivity(activity)
- yield()
assertThat(latest).isFalse()
-
- job.cancel()
}
@Test
fun activityOut_hasActivityOutTrue_outputsTrue() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
whenever(connectivityConstants.shouldShowActivityConfig).thenReturn(true)
createAndSetViewModel()
wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK)
- var latest: Boolean? = null
- val job = underTest.isActivityOutViewVisible.onEach { latest = it }.launchIn(this)
+ val latest by collectLastValue(underTest.isActivityOutViewVisible)
val activity = DataActivityModel(hasActivityIn = false, hasActivityOut = true)
wifiRepository.setWifiActivity(activity)
- yield()
assertThat(latest).isTrue()
-
- job.cancel()
}
@Test
fun activityOut_hasActivityOutFalse_outputsFalse() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
whenever(connectivityConstants.shouldShowActivityConfig).thenReturn(true)
createAndSetViewModel()
wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK)
- var latest: Boolean? = null
- val job = underTest.isActivityOutViewVisible.onEach { latest = it }.launchIn(this)
+ val latest by collectLastValue(underTest.isActivityOutViewVisible)
val activity = DataActivityModel(hasActivityIn = true, hasActivityOut = false)
wifiRepository.setWifiActivity(activity)
- yield()
assertThat(latest).isFalse()
-
- job.cancel()
}
@Test
fun activityContainer_hasActivityInTrue_outputsTrue() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
whenever(connectivityConstants.shouldShowActivityConfig).thenReturn(true)
createAndSetViewModel()
wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK)
- var latest: Boolean? = null
- val job = underTest.isActivityContainerVisible.onEach { latest = it }.launchIn(this)
+ val latest by collectLastValue(underTest.isActivityContainerVisible)
val activity = DataActivityModel(hasActivityIn = true, hasActivityOut = false)
wifiRepository.setWifiActivity(activity)
- yield()
assertThat(latest).isTrue()
-
- job.cancel()
}
@Test
fun activityContainer_hasActivityOutTrue_outputsTrue() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
whenever(connectivityConstants.shouldShowActivityConfig).thenReturn(true)
createAndSetViewModel()
wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK)
- var latest: Boolean? = null
- val job = underTest.isActivityContainerVisible.onEach { latest = it }.launchIn(this)
+ val latest by collectLastValue(underTest.isActivityContainerVisible)
val activity = DataActivityModel(hasActivityIn = false, hasActivityOut = true)
wifiRepository.setWifiActivity(activity)
- yield()
assertThat(latest).isTrue()
-
- job.cancel()
}
@Test
fun activityContainer_inAndOutTrue_outputsTrue() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
whenever(connectivityConstants.shouldShowActivityConfig).thenReturn(true)
createAndSetViewModel()
wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK)
- var latest: Boolean? = null
- val job = underTest.isActivityContainerVisible.onEach { latest = it }.launchIn(this)
+ val latest by collectLastValue(underTest.isActivityContainerVisible)
val activity = DataActivityModel(hasActivityIn = true, hasActivityOut = true)
wifiRepository.setWifiActivity(activity)
- yield()
assertThat(latest).isTrue()
-
- job.cancel()
}
@Test
fun activityContainer_inAndOutFalse_outputsFalse() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
whenever(connectivityConstants.shouldShowActivityConfig).thenReturn(true)
createAndSetViewModel()
wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK)
- var latest: Boolean? = null
- val job = underTest.isActivityContainerVisible.onEach { latest = it }.launchIn(this)
+ val latest by collectLastValue(underTest.isActivityContainerVisible)
val activity = DataActivityModel(hasActivityIn = false, hasActivityOut = false)
wifiRepository.setWifiActivity(activity)
- yield()
assertThat(latest).isFalse()
-
- job.cancel()
}
@Test
fun airplaneSpacer_notAirplaneMode_outputsFalse() =
- runBlocking(IMMEDIATE) {
- var latest: Boolean? = null
- val job = underTest.isAirplaneSpacerVisible.onEach { latest = it }.launchIn(this)
+ testScope.runTest {
+ val latest by collectLastValue(underTest.isAirplaneSpacerVisible)
airplaneModeRepository.setIsAirplaneMode(false)
- yield()
assertThat(latest).isFalse()
-
- job.cancel()
}
@Test
fun airplaneSpacer_airplaneForceHidden_outputsFalse() =
- runBlocking(IMMEDIATE) {
- var latest: Boolean? = null
- val job = underTest.isAirplaneSpacerVisible.onEach { latest = it }.launchIn(this)
+ testScope.runTest {
+ val latest by collectLastValue(underTest.isAirplaneSpacerVisible)
airplaneModeRepository.setIsAirplaneMode(true)
connectivityRepository.setForceHiddenIcons(setOf(ConnectivitySlot.AIRPLANE))
- yield()
assertThat(latest).isFalse()
-
- job.cancel()
}
@Test
fun airplaneSpacer_airplaneIconVisible_outputsTrue() =
- runBlocking(IMMEDIATE) {
- var latest: Boolean? = null
- val job = underTest.isAirplaneSpacerVisible.onEach { latest = it }.launchIn(this)
+ testScope.runTest {
+ val latest by collectLastValue(underTest.isAirplaneSpacerVisible)
airplaneModeRepository.setIsAirplaneMode(true)
- yield()
assertThat(latest).isTrue()
-
- job.cancel()
}
@Test
fun signalSpacer_firstSubNotShowingNetworkTypeIcon_outputsFalse() =
- runBlocking(IMMEDIATE) {
- var latest: Boolean? = null
- val job = underTest.isSignalSpacerVisible.onEach { latest = it }.launchIn(this)
+ testScope.runTest {
+ val latest by collectLastValue(underTest.isSignalSpacerVisible)
shouldShowSignalSpacerProviderFlow.value = false
- yield()
assertThat(latest).isFalse()
-
- job.cancel()
}
@Test
fun signalSpacer_firstSubIsShowingNetworkTypeIcon_outputsTrue() =
- runBlocking(IMMEDIATE) {
- var latest: Boolean? = null
- val job = underTest.isSignalSpacerVisible.onEach { latest = it }.launchIn(this)
+ testScope.runTest {
+ val latest by collectLastValue(underTest.isSignalSpacerVisible)
shouldShowSignalSpacerProviderFlow.value = true
- yield()
assertThat(latest).isTrue()
-
- job.cancel()
}
private fun createAndSetViewModel() {
@@ -507,7 +383,7 @@
context,
tableLogBuffer,
interactor,
- scope,
+ testScope.backgroundScope,
wifiConstants,
)
}
@@ -518,5 +394,3 @@
WifiNetworkModel.Active(NETWORK_ID, ssid = "AB", level = 1)
}
}
-
-private val IMMEDIATE = Dispatchers.Main.immediate