summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Steve Elliott <steell@google.com> 2023-10-26 15:53:19 -0400
committer Steve Elliott <steell@google.com> 2023-11-01 15:38:18 -0400
commitfc8397a68e48d21c20491dd1b001b75b2307c3d8 (patch)
tree2d1848c6186c1047b850d8ea883be977a97dff98
parent7c947d640ae4a4cd97ff9128526d9ad3c268ccf6 (diff)
Move aod icon vis handling to parent view-binder
Flag: ACONFIG com.android.systemui.notifications_icon_container_refactor DEVELOPMENT Bug: 278765923 Test: atest SystemUITests Change-Id: I8ab7c62725b799606a008c500d77260415877f03
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java42
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt13
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt164
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt15
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt98
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt116
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModel.kt93
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerBaseTest.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt204
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModelTest.kt139
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/util/mockito/KotlinMockitoHelpers.kt51
12 files changed, 528 insertions, 415 deletions
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
index 7bf3e8f140a0..54f14572cfa5 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
@@ -45,6 +45,8 @@ import com.android.systemui.flags.FeatureFlagsClassic;
import com.android.systemui.flags.Flags;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
+import com.android.systemui.keyguard.ui.binder.KeyguardRootViewBinder;
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel;
import com.android.systemui.log.LogBuffer;
import com.android.systemui.log.core.LogLevel;
import com.android.systemui.log.dagger.KeyguardClockLog;
@@ -95,6 +97,7 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS
private final ClockEventController mClockEventController;
private final LogBuffer mLogBuffer;
private final NotificationIconContainerAlwaysOnDisplayViewModel mAodIconsViewModel;
+ private final KeyguardRootViewModel mKeyguardRootViewModel;
private final ConfigurationState mConfigurationState;
private final ConfigurationController mConfigurationController;
private final DozeParameters mDozeParameters;
@@ -127,7 +130,7 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS
private KeyguardInteractor mKeyguardInteractor;
private final DelayableExecutor mUiExecutor;
private boolean mCanShowDoubleLineClock = true;
- private DisposableHandle mAodIconsBindJob;
+ private DisposableHandle mAodIconsBindHandle;
@Nullable private NotificationIconContainer mAodIconContainer;
@VisibleForTesting
@@ -179,6 +182,7 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS
ClockEventController clockEventController,
@KeyguardClockLog LogBuffer logBuffer,
NotificationIconContainerAlwaysOnDisplayViewModel aodIconsViewModel,
+ KeyguardRootViewModel keyguardRootViewModel,
ConfigurationState configurationState,
DozeParameters dozeParameters,
AlwaysOnDisplayNotificationIconViewStore aodIconViewStore,
@@ -199,6 +203,7 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS
mClockEventController = clockEventController;
mLogBuffer = logBuffer;
mAodIconsViewModel = aodIconsViewModel;
+ mKeyguardRootViewModel = keyguardRootViewModel;
mConfigurationState = configurationState;
mDozeParameters = dozeParameters;
mAodIconViewStore = aodIconViewStore;
@@ -567,21 +572,32 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS
mView.findViewById(
com.android.systemui.res.R.id.left_aligned_notification_icon_container);
if (NotificationIconContainerRefactor.isEnabled()) {
- if (mAodIconsBindJob != null) {
- mAodIconsBindJob.dispose();
+ if (mAodIconsBindHandle != null) {
+ mAodIconsBindHandle.dispose();
}
if (nic != null) {
nic.setOnLockScreen(true);
- mAodIconsBindJob = NotificationIconContainerViewBinder.bind(
- nic,
- mAodIconsViewModel,
- mConfigurationState,
- mConfigurationController,
- mDozeParameters,
- mFeatureFlags,
- mScreenOffAnimationController,
- mAodIconViewStore
- );
+ final DisposableHandle viewHandle = NotificationIconContainerViewBinder.bind(
+ nic,
+ mAodIconsViewModel,
+ mConfigurationState,
+ mConfigurationController,
+ mDozeParameters,
+ mAodIconViewStore);
+ final DisposableHandle visHandle = KeyguardRootViewBinder.bindAodIconVisibility(
+ nic,
+ mKeyguardRootViewModel.isNotifIconContainerVisible(),
+ mConfigurationState,
+ mFeatureFlags,
+ mScreenOffAnimationController);
+ if (visHandle == null) {
+ mAodIconsBindHandle = viewHandle;
+ } else {
+ mAodIconsBindHandle = () -> {
+ viewHandle.dispose();
+ visHandle.dispose();
+ };
+ }
mAodIconContainer = nic;
}
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
index c56dfde86573..61c8e1bbc1d8 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
@@ -27,9 +27,10 @@ import com.android.keyguard.LockIconView
import com.android.keyguard.LockIconViewController
import com.android.keyguard.dagger.KeyguardStatusViewComponent
import com.android.systemui.CoreStartable
+import com.android.systemui.common.ui.ConfigurationState
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryHapticsInteractor
-import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.FeatureFlagsClassic
import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.ui.binder.KeyguardBlueprintViewBinder
import com.android.systemui.keyguard.ui.binder.KeyguardIndicationAreaBinder
@@ -46,7 +47,7 @@ import com.android.systemui.shade.NotificationShadeWindowView
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.statusbar.KeyguardIndicationController
import com.android.systemui.statusbar.VibratorHelper
-import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.statusbar.phone.ScreenOffAnimationController
import com.android.systemui.temporarydisplay.chipbar.ChipbarCoordinator
import dagger.Lazy
import javax.inject.Inject
@@ -63,14 +64,15 @@ constructor(
private val keyguardRootViewModel: KeyguardRootViewModel,
private val keyguardIndicationAreaViewModel: KeyguardIndicationAreaViewModel,
private val notificationShadeWindowView: NotificationShadeWindowView,
- private val featureFlags: FeatureFlags,
+ private val featureFlags: FeatureFlagsClassic,
private val indicationController: KeyguardIndicationController,
- private val keyguardStateController: KeyguardStateController,
+ private val screenOffAnimationController: ScreenOffAnimationController,
private val occludingAppDeviceEntryMessageViewModel: OccludingAppDeviceEntryMessageViewModel,
private val chipbarCoordinator: ChipbarCoordinator,
private val keyguardBlueprintCommandListener: KeyguardBlueprintCommandListener,
private val keyguardBlueprintViewModel: KeyguardBlueprintViewModel,
private val keyguardStatusViewComponentFactory: KeyguardStatusViewComponent.Factory,
+ private val configuration: ConfigurationState,
private val context: Context,
private val keyguardIndicationController: KeyguardIndicationController,
private val lockIconViewController: Lazy<LockIconViewController>,
@@ -143,10 +145,11 @@ constructor(
KeyguardRootViewBinder.bind(
keyguardRootView,
keyguardRootViewModel,
+ configuration,
featureFlags,
occludingAppDeviceEntryMessageViewModel,
chipbarCoordinator,
- keyguardStateController,
+ screenOffAnimationController,
shadeInteractor,
{ keyguardStatusViewController!!.getClockController() },
interactionJankMonitor,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
index 67a12b06de0f..ad4895734709 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
@@ -16,25 +16,31 @@
package com.android.systemui.keyguard.ui.binder
+import android.animation.Animator
+import android.animation.AnimatorListenerAdapter
import android.annotation.DrawableRes
import android.view.HapticFeedbackConstants
import android.view.View
import android.view.View.OnLayoutChangeListener
import android.view.ViewGroup
import android.view.ViewGroup.OnHierarchyChangeListener
+import android.view.ViewPropertyAnimator
import android.view.WindowInsets
-import android.view.WindowInsets.Type
import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
+import com.android.app.animation.Interpolators
import com.android.internal.jank.InteractionJankMonitor
import com.android.internal.jank.InteractionJankMonitor.CUJ_SCREEN_OFF_SHOW_AOD
import com.android.keyguard.KeyguardClockSwitch.MISSING_CLOCK_ID
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.common.shared.model.Text
import com.android.systemui.common.shared.model.TintedIcon
+import com.android.systemui.common.ui.ConfigurationState
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryHapticsInteractor
-import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.FeatureFlagsClassic
import com.android.systemui.flags.Flags
+import com.android.systemui.flags.RefactorFlag
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel
import com.android.systemui.keyguard.ui.viewmodel.OccludingAppDeviceEntryMessageViewModel
@@ -42,29 +48,38 @@ import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.plugins.ClockController
import com.android.systemui.res.R
import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.statusbar.CrossFadeHelper
import com.android.systemui.statusbar.VibratorHelper
-import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor
+import com.android.systemui.statusbar.phone.ScreenOffAnimationController
import com.android.systemui.temporarydisplay.ViewPriority
import com.android.systemui.temporarydisplay.chipbar.ChipbarCoordinator
import com.android.systemui.temporarydisplay.chipbar.ChipbarInfo
+import com.android.systemui.util.ui.AnimatedValue
+import com.android.systemui.util.ui.isAnimating
+import com.android.systemui.util.ui.stopAnimating
+import com.android.systemui.util.ui.value
import javax.inject.Provider
import kotlinx.coroutines.DisposableHandle
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
/** Bind occludingAppDeviceEntryMessageViewModel to run whenever the keyguard view is attached. */
-@ExperimentalCoroutinesApi
+@OptIn(ExperimentalCoroutinesApi::class)
object KeyguardRootViewBinder {
@JvmStatic
fun bind(
view: ViewGroup,
viewModel: KeyguardRootViewModel,
- featureFlags: FeatureFlags,
+ configuration: ConfigurationState,
+ featureFlags: FeatureFlagsClassic,
occludingAppDeviceEntryMessageViewModel: OccludingAppDeviceEntryMessageViewModel,
chipbarCoordinator: ChipbarCoordinator,
- keyguardStateController: KeyguardStateController,
+ screenOffAnimationController: ScreenOffAnimationController,
shadeInteractor: ShadeInteractor,
clockControllerProvider: Provider<ClockController>?,
interactionJankMonitor: InteractionJankMonitor?,
@@ -149,6 +164,24 @@ object KeyguardRootViewBinder {
}
}
+ if (NotificationIconContainerRefactor.isEnabled) {
+ launch {
+ val iconsAppearTranslationPx =
+ configuration
+ .getDimensionPixelSize(R.dimen.shelf_appear_translation)
+ .stateIn(this)
+ viewModel.isNotifIconContainerVisible.collect { isVisible ->
+ childViews[aodNotificationIconContainerId]
+ ?.setAodNotifIconContainerIsVisible(
+ isVisible,
+ featureFlags,
+ iconsAppearTranslationPx.value,
+ screenOffAnimationController,
+ )
+ }
+ }
+ }
+
interactionJankMonitor?.let { jankMonitor ->
launch {
viewModel.goneToAodTransition.collect {
@@ -312,5 +345,124 @@ object KeyguardRootViewBinder {
}
}
+ @JvmStatic
+ fun bindAodIconVisibility(
+ view: View,
+ isVisible: Flow<AnimatedValue<Boolean>>,
+ configuration: ConfigurationState,
+ featureFlags: FeatureFlagsClassic,
+ screenOffAnimationController: ScreenOffAnimationController,
+ ): DisposableHandle? {
+ RefactorFlag(featureFlags, Flags.MIGRATE_KEYGUARD_STATUS_VIEW).assertInLegacyMode()
+ if (NotificationIconContainerRefactor.isUnexpectedlyInLegacyMode()) return null
+ return view.repeatWhenAttached {
+ lifecycleScope.launch {
+ val iconAppearTranslationPx =
+ configuration
+ .getDimensionPixelSize(R.dimen.shelf_appear_translation)
+ .stateIn(this)
+ isVisible.collect { isVisible ->
+ view.setAodNotifIconContainerIsVisible(
+ isVisible,
+ featureFlags,
+ iconAppearTranslationPx.value,
+ screenOffAnimationController,
+ )
+ }
+ }
+ }
+ }
+
+ private fun View.setAodNotifIconContainerIsVisible(
+ isVisible: AnimatedValue<Boolean>,
+ featureFlags: FeatureFlagsClassic,
+ iconsAppearTranslationPx: Int,
+ screenOffAnimationController: ScreenOffAnimationController,
+ ) {
+ val statusViewMigrated = featureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)
+ animate().cancel()
+ val animatorListener =
+ object : AnimatorListenerAdapter() {
+ override fun onAnimationEnd(animation: Animator) {
+ isVisible.stopAnimating()
+ }
+ }
+ when {
+ !isVisible.isAnimating -> {
+ alpha = 1f
+ if (!statusViewMigrated) {
+ translationY = 0f
+ }
+ visibility = if (isVisible.value) View.VISIBLE else View.INVISIBLE
+ }
+ featureFlags.isEnabled(Flags.NEW_AOD_TRANSITION) -> {
+ animateInIconTranslation(statusViewMigrated)
+ if (isVisible.value) {
+ CrossFadeHelper.fadeIn(this, animatorListener)
+ } else {
+ CrossFadeHelper.fadeOut(this, animatorListener)
+ }
+ }
+ !isVisible.value -> {
+ // Let's make sure the icon are translated to 0, since we cancelled it above
+ animateInIconTranslation(statusViewMigrated)
+ CrossFadeHelper.fadeOut(this, animatorListener)
+ }
+ visibility != View.VISIBLE -> {
+ // No fading here, let's just appear the icons instead!
+ visibility = View.VISIBLE
+ alpha = 1f
+ appearIcons(
+ animate = screenOffAnimationController.shouldAnimateAodIcons(),
+ iconsAppearTranslationPx,
+ statusViewMigrated,
+ animatorListener,
+ )
+ }
+ else -> {
+ // Let's make sure the icons are translated to 0, since we cancelled it above
+ animateInIconTranslation(statusViewMigrated)
+ // We were fading out, let's fade in instead
+ CrossFadeHelper.fadeIn(this, animatorListener)
+ }
+ }
+ }
+
+ private fun View.appearIcons(
+ animate: Boolean,
+ iconAppearTranslation: Int,
+ statusViewMigrated: Boolean,
+ animatorListener: Animator.AnimatorListener,
+ ) {
+ if (animate) {
+ if (!statusViewMigrated) {
+ translationY = -iconAppearTranslation.toFloat()
+ }
+ alpha = 0f
+ animate()
+ .alpha(1f)
+ .setInterpolator(Interpolators.LINEAR)
+ .setDuration(AOD_ICONS_APPEAR_DURATION)
+ .apply { if (statusViewMigrated) animateInIconTranslation() }
+ .setListener(animatorListener)
+ .start()
+ } else {
+ alpha = 1.0f
+ if (!statusViewMigrated) {
+ translationY = 0f
+ }
+ }
+ }
+
+ private fun View.animateInIconTranslation(statusViewMigrated: Boolean) {
+ if (!statusViewMigrated) {
+ animate().animateInIconTranslation().setDuration(AOD_ICONS_APPEAR_DURATION).start()
+ }
+ }
+
+ private fun ViewPropertyAnimator.animateInIconTranslation(): ViewPropertyAnimator =
+ setInterpolator(Interpolators.DECELERATE_QUINT).translationY(0f)
+
private const val ID = "occluding_app_device_entry_unlock_msg"
+ private const val AOD_ICONS_APPEAR_DURATION: Long = 200
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
index fe6a21ed89a0..b797c4b45445 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
@@ -44,10 +44,10 @@ import com.android.keyguard.KeyguardClockSwitch
import com.android.systemui.animation.view.LaunchableImageView
import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor
import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.common.ui.ConfigurationState
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.deviceentry.domain.interactor.DeviceEntryHapticsInteractor
-import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.FeatureFlagsClassic
import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.ui.binder.KeyguardPreviewClockViewBinder
import com.android.systemui.keyguard.ui.binder.KeyguardPreviewSmartspaceViewBinder
@@ -76,7 +76,7 @@ import com.android.systemui.statusbar.KeyguardIndicationController
import com.android.systemui.statusbar.VibratorHelper
import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController
import com.android.systemui.statusbar.phone.KeyguardBottomAreaView
-import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.statusbar.phone.ScreenOffAnimationController
import com.android.systemui.temporarydisplay.chipbar.ChipbarCoordinator
import dagger.assisted.Assisted
import dagger.assisted.AssistedInject
@@ -99,12 +99,13 @@ constructor(
private val quickAffordancesCombinedViewModel: KeyguardQuickAffordancesCombinedViewModel,
displayManager: DisplayManager,
private val windowManager: WindowManager,
+ private val configuration: ConfigurationState,
private val clockController: ClockEventController,
private val clockRegistry: ClockRegistry,
private val broadcastDispatcher: BroadcastDispatcher,
private val lockscreenSmartspaceController: LockscreenSmartspaceController,
private val udfpsOverlayInteractor: UdfpsOverlayInteractor,
- private val featureFlags: FeatureFlags,
+ private val featureFlags: FeatureFlagsClassic,
private val falsingManager: FalsingManager,
private val vibratorHelper: VibratorHelper,
private val indicationController: KeyguardIndicationController,
@@ -113,9 +114,8 @@ constructor(
private val keyguardBlueprintViewModel: KeyguardBlueprintViewModel,
private val occludingAppDeviceEntryMessageViewModel: OccludingAppDeviceEntryMessageViewModel,
private val chipbarCoordinator: ChipbarCoordinator,
- private val keyguardStateController: KeyguardStateController,
+ private val screenOffAnimationController: ScreenOffAnimationController,
private val shadeInteractor: ShadeInteractor,
- private val deviceEntryHapticsInteractor: DeviceEntryHapticsInteractor,
) {
val hostToken: IBinder? = bundle.getBinder(KEY_HOST_TOKEN)
@@ -341,10 +341,11 @@ constructor(
KeyguardRootViewBinder.bind(
keyguardRootView,
keyguardRootViewModel,
+ configuration,
featureFlags,
occludingAppDeviceEntryMessageViewModel,
chipbarCoordinator,
- keyguardStateController,
+ screenOffAnimationController,
shadeInteractor,
null, // clock provider only needed for burn in
null, // jank monitor not required for preview mode
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt
index b7fe9605a221..0390077a572f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt
@@ -31,7 +31,6 @@ import com.android.systemui.flags.FeatureFlagsClassic
import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.shared.model.KeyguardSection
import com.android.systemui.res.R
-import com.android.systemui.shade.NotificationPanelView
import com.android.systemui.statusbar.notification.icon.ui.viewbinder.AlwaysOnDisplayNotificationIconViewStore
import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerViewBinder
import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerAlwaysOnDisplayViewModel
@@ -39,7 +38,6 @@ import com.android.systemui.statusbar.notification.shared.NotificationIconContai
import com.android.systemui.statusbar.phone.DozeParameters
import com.android.systemui.statusbar.phone.NotificationIconAreaController
import com.android.systemui.statusbar.phone.NotificationIconContainer
-import com.android.systemui.statusbar.phone.ScreenOffAnimationController
import com.android.systemui.statusbar.policy.ConfigurationController
import javax.inject.Inject
import kotlinx.coroutines.DisposableHandle
@@ -54,9 +52,7 @@ constructor(
private val featureFlags: FeatureFlagsClassic,
private val nicAodViewModel: NotificationIconContainerAlwaysOnDisplayViewModel,
private val nicAodIconViewStore: AlwaysOnDisplayNotificationIconViewStore,
- private val notificationPanelView: NotificationPanelView,
private val notificationIconAreaController: NotificationIconAreaController,
- private val screenOffAnimationController: ScreenOffAnimationController,
) : KeyguardSection() {
private var nicBindingDisposable: DisposableHandle? = null
@@ -97,8 +93,6 @@ constructor(
configurationState,
configurationController,
dozeParameters,
- featureFlags,
- screenOffAnimationController,
nicAodIconViewStore,
)
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
index e12da53287ed..60f75f03609c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
@@ -22,14 +22,28 @@ import android.util.MathUtils
import android.view.View.VISIBLE
import com.android.app.animation.Interpolators
import com.android.systemui.common.shared.model.SharedNotificationContainerPosition
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
+import com.android.systemui.flags.FeatureFlagsClassic
+import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.domain.interactor.BurnInInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.BurnInModel
+import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
import com.android.systemui.plugins.ClockController
import com.android.systemui.res.R
+import com.android.systemui.statusbar.notification.domain.interactor.NotificationsKeyguardInteractor
+import com.android.systemui.statusbar.phone.DozeParameters
+import com.android.systemui.statusbar.phone.ScreenOffAnimationController
+import com.android.systemui.util.kotlin.pairwise
+import com.android.systemui.util.kotlin.sample
+import com.android.systemui.util.ui.AnimatableEvent
+import com.android.systemui.util.ui.AnimatedValue
+import com.android.systemui.util.ui.toAnimatedValueFlow
+import com.android.systemui.util.ui.zip
import javax.inject.Inject
import javax.inject.Provider
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -45,15 +59,21 @@ import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.flow.onStart
@OptIn(ExperimentalCoroutinesApi::class)
+@SysUISingleton
class KeyguardRootViewModel
@Inject
constructor(
private val context: Context,
+ private val deviceEntryInteractor: DeviceEntryInteractor,
+ private val dozeParameters: DozeParameters,
+ private val featureFlags: FeatureFlagsClassic,
private val keyguardInteractor: KeyguardInteractor,
+ private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
+ private val notificationsKeyguardInteractor: NotificationsKeyguardInteractor,
private val burnInInteractor: BurnInInteractor,
private val goneToAodTransitionViewModel: GoneToAodTransitionViewModel,
private val aodToLockscreenTransitionViewModel: AodToLockscreenTransitionViewModel,
- private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
+ screenOffAnimationController: ScreenOffAnimationController,
) {
data class PreviewMode(val isInPreviewMode: Boolean = false)
@@ -174,6 +194,47 @@ constructor(
}
}
+ /** Is the notification icon container visible? */
+ val isNotifIconContainerVisible: Flow<AnimatedValue<Boolean>> =
+ combine(
+ keyguardTransitionInteractor.finishedKeyguardState.map {
+ KeyguardState.lockscreenVisibleInState(it)
+ },
+ deviceEntryInteractor.isBypassEnabled,
+ areNotifsFullyHiddenAnimated(),
+ isPulseExpandingAnimated(),
+ ) {
+ onKeyguard: Boolean,
+ isBypassEnabled: Boolean,
+ notifsFullyHidden: AnimatedValue<Boolean>,
+ pulseExpanding: AnimatedValue<Boolean>,
+ ->
+ when {
+ // Hide the AOD icons if we're not in the KEYGUARD state unless the screen off
+ // animation is playing, in which case we want them to be visible if we're
+ // animating in the AOD UI and will be switching to KEYGUARD shortly.
+ !onKeyguard && !screenOffAnimationController.shouldShowAodIconsWhenShade() ->
+ AnimatedValue.NotAnimating(false)
+ else ->
+ zip(notifsFullyHidden, pulseExpanding) {
+ areNotifsFullyHidden,
+ isPulseExpanding,
+ ->
+ when {
+ // If we're bypassing, then we're visible
+ isBypassEnabled -> true
+ // If we are pulsing (and not bypassing), then we are hidden
+ isPulseExpanding -> false
+ // If notifs are fully gone, then we're visible
+ areNotifsFullyHidden -> true
+ // Otherwise, we're hidden
+ else -> false
+ }
+ }
+ }
+ }
+ .distinctUntilChanged()
+
/**
* Puts this view-model in "preview mode", which means it's being used for UI that is rendering
* the lock screen preview in wallpaper picker / settings and not the real experience on the
@@ -191,4 +252,39 @@ constructor(
keyguardInteractor.sharedNotificationContainerPosition.value =
SharedNotificationContainerPosition(top, bottom)
}
+
+ /** Is there an expanded pulse, are we animating in response? */
+ private fun isPulseExpandingAnimated(): Flow<AnimatedValue<Boolean>> {
+ return notificationsKeyguardInteractor.isPulseExpanding
+ .pairwise(initialValue = null)
+ // If pulsing changes, start animating, unless it's the first emission
+ .map { (prev, expanding) -> AnimatableEvent(expanding, startAnimating = prev != null) }
+ .toAnimatedValueFlow()
+ }
+
+ /** Are notifications completely hidden from view, are we animating in response? */
+ private fun areNotifsFullyHiddenAnimated(): Flow<AnimatedValue<Boolean>> {
+ return notificationsKeyguardInteractor.areNotificationsFullyHidden
+ .pairwise(initialValue = null)
+ .sample(deviceEntryInteractor.isBypassEnabled) { (prev, fullyHidden), bypassEnabled ->
+ val animate =
+ when {
+ // Don't animate for the first value
+ prev == null -> false
+ // Always animate if bypass is enabled.
+ bypassEnabled -> true
+ // If we're not bypassing and we're not going to AOD, then we're not
+ // animating.
+ !dozeParameters.alwaysOn -> false
+ // Don't animate when going to AOD if the display needs blanking.
+ dozeParameters.displayNeedsBlanking -> false
+ // We only want the appear animations to happen when the notifications
+ // get fully hidden, since otherwise the un-hide animation overlaps.
+ featureFlags.isEnabled(Flags.NEW_AOD_TRANSITION) -> true
+ else -> fullyHidden
+ }
+ AnimatableEvent(fullyHidden, animate)
+ }
+ .toAnimatedValueFlow()
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt
index c1f728a0b06e..db0fa99af440 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt
@@ -15,26 +15,19 @@
*/
package com.android.systemui.statusbar.notification.icon.ui.viewbinder
-import android.animation.Animator
-import android.animation.AnimatorListenerAdapter
import android.graphics.Color
import android.graphics.Rect
import android.view.View
import android.view.ViewGroup
-import android.view.ViewPropertyAnimator
import android.widget.FrameLayout
import androidx.annotation.ColorInt
import androidx.collection.ArrayMap
import androidx.lifecycle.lifecycleScope
-import com.android.app.animation.Interpolators
import com.android.internal.policy.SystemBarUtils
import com.android.internal.util.ContrastColorUtil
import com.android.systemui.common.ui.ConfigurationState
-import com.android.systemui.flags.FeatureFlagsClassic
-import com.android.systemui.flags.Flags
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.res.R
-import com.android.systemui.statusbar.CrossFadeHelper
import com.android.systemui.statusbar.StatusBarIconView
import com.android.systemui.statusbar.notification.NotificationUtils
import com.android.systemui.statusbar.notification.collection.NotifCollection
@@ -47,7 +40,6 @@ import com.android.systemui.statusbar.notification.icon.ui.viewmodel.Notificatio
import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconsViewData
import com.android.systemui.statusbar.phone.DozeParameters
import com.android.systemui.statusbar.phone.NotificationIconContainer
-import com.android.systemui.statusbar.phone.ScreenOffAnimationController
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.statusbar.policy.onConfigChanged
import com.android.systemui.util.children
@@ -65,7 +57,6 @@ import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.mapNotNull
-import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
/** Binds a view-model to a [NotificationIconContainer]. */
@@ -118,8 +109,6 @@ object NotificationIconContainerViewBinder {
configuration: ConfigurationState,
configurationController: ConfigurationController,
dozeParameters: DozeParameters,
- featureFlags: FeatureFlagsClassic,
- screenOffAnimationController: ScreenOffAnimationController,
viewStore: IconViewStore,
): DisposableHandle {
return view.repeatWhenAttached {
@@ -134,16 +123,6 @@ object NotificationIconContainerViewBinder {
}
launch { viewModel.animationsEnabled.bindAnimationsEnabled(view) }
launch { viewModel.isDozing.bindIsDozing(view, dozeParameters) }
- // TODO(b/278765923): this should live where AOD is bound, not inside of the NIC
- // view-binder
- launch {
- viewModel.isVisible.bindIsVisible(
- view,
- configuration,
- featureFlags,
- screenOffAnimationController,
- )
- }
launch {
configuration
.getColorAttr(R.attr.wallpaperTextColor, DEFAULT_AOD_ICON_COLOR)
@@ -333,106 +312,11 @@ object NotificationIconContainerViewBinder {
setDecorColor(iconColors.tint)
}
- private suspend fun Flow<AnimatedValue<Boolean>>.bindIsVisible(
- view: NotificationIconContainer,
- configuration: ConfigurationState,
- featureFlags: FeatureFlagsClassic,
- screenOffAnimationController: ScreenOffAnimationController,
- ): Unit = coroutineScope {
- val iconAppearTranslation =
- configuration.getDimensionPixelSize(R.dimen.shelf_appear_translation).stateIn(this)
- val statusViewMigrated = featureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)
- collect { isVisible ->
- view.animate().cancel()
- val animatorListener =
- object : AnimatorListenerAdapter() {
- override fun onAnimationEnd(animation: Animator) {
- isVisible.stopAnimating()
- }
- }
- when {
- !isVisible.isAnimating -> {
- view.alpha = 1f
- if (!statusViewMigrated) {
- view.translationY = 0f
- }
- view.visibility = if (isVisible.value) View.VISIBLE else View.INVISIBLE
- }
- featureFlags.isEnabled(Flags.NEW_AOD_TRANSITION) -> {
- view.animateInIconTranslation(statusViewMigrated)
- if (isVisible.value) {
- CrossFadeHelper.fadeIn(view, animatorListener)
- } else {
- CrossFadeHelper.fadeOut(view, animatorListener)
- }
- }
- !isVisible.value -> {
- // Let's make sure the icon are translated to 0, since we cancelled it above
- view.animateInIconTranslation(statusViewMigrated)
- CrossFadeHelper.fadeOut(view, animatorListener)
- }
- view.visibility != View.VISIBLE -> {
- // No fading here, let's just appear the icons instead!
- view.visibility = View.VISIBLE
- view.alpha = 1f
- view.appearIcons(
- animate = screenOffAnimationController.shouldAnimateAodIcons(),
- iconAppearTranslation.value,
- statusViewMigrated,
- animatorListener,
- )
- }
- else -> {
- // Let's make sure the icons are translated to 0, since we cancelled it above
- view.animateInIconTranslation(statusViewMigrated)
- // We were fading out, let's fade in instead
- CrossFadeHelper.fadeIn(view, animatorListener)
- }
- }
- }
- }
-
- private fun View.appearIcons(
- animate: Boolean,
- iconAppearTranslation: Int,
- statusViewMigrated: Boolean,
- animatorListener: Animator.AnimatorListener,
- ) {
- if (animate) {
- if (!statusViewMigrated) {
- translationY = -iconAppearTranslation.toFloat()
- }
- alpha = 0f
- animate()
- .alpha(1f)
- .setInterpolator(Interpolators.LINEAR)
- .setDuration(AOD_ICONS_APPEAR_DURATION)
- .apply { if (statusViewMigrated) animateInIconTranslation() }
- .setListener(animatorListener)
- .start()
- } else {
- alpha = 1.0f
- if (!statusViewMigrated) {
- translationY = 0f
- }
- }
- }
-
- private fun View.animateInIconTranslation(statusViewMigrated: Boolean) {
- if (!statusViewMigrated) {
- animate().animateInIconTranslation().setDuration(AOD_ICONS_APPEAR_DURATION).start()
- }
- }
-
- private fun ViewPropertyAnimator.animateInIconTranslation(): ViewPropertyAnimator =
- setInterpolator(Interpolators.DECELERATE_QUINT).translationY(0f)
-
/** External storage for [StatusBarIconView] instances. */
fun interface IconViewStore {
fun iconView(key: String): StatusBarIconView?
}
- private const val AOD_ICONS_APPEAR_DURATION: Long = 200
@ColorInt private val DEFAULT_AOD_ICON_COLOR = Color.WHITE
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModel.kt
index 611ed89c89af..835c0592c588 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModel.kt
@@ -15,26 +15,16 @@
*/
package com.android.systemui.statusbar.notification.icon.ui.viewmodel
-import android.graphics.Rect
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
-import com.android.systemui.flags.FeatureFlagsClassic
-import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionStep
import com.android.systemui.shade.domain.interactor.ShadeInteractor
-import com.android.systemui.statusbar.notification.domain.interactor.NotificationsKeyguardInteractor
import com.android.systemui.statusbar.notification.icon.domain.interactor.AlwaysOnDisplayNotificationIconsInteractor
-import com.android.systemui.statusbar.phone.DozeParameters
-import com.android.systemui.statusbar.phone.ScreenOffAnimationController
-import com.android.systemui.util.kotlin.pairwise
-import com.android.systemui.util.kotlin.sample
import com.android.systemui.util.ui.AnimatableEvent
import com.android.systemui.util.ui.AnimatedValue
import com.android.systemui.util.ui.toAnimatedValueFlow
-import com.android.systemui.util.ui.zip
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
@@ -46,14 +36,9 @@ import kotlinx.coroutines.flow.map
class NotificationIconContainerAlwaysOnDisplayViewModel
@Inject
constructor(
- private val deviceEntryInteractor: DeviceEntryInteractor,
- private val dozeParameters: DozeParameters,
- private val featureFlags: FeatureFlagsClassic,
iconsInteractor: AlwaysOnDisplayNotificationIconsInteractor,
keyguardInteractor: KeyguardInteractor,
keyguardTransitionInteractor: KeyguardTransitionInteractor,
- private val notificationsKeyguardInteractor: NotificationsKeyguardInteractor,
- screenOffAnimationController: ScreenOffAnimationController,
shadeInteractor: ShadeInteractor,
) {
@@ -84,45 +69,6 @@ constructor(
.distinctUntilChanged()
.toAnimatedValueFlow()
- /** Is the icon container visible? */
- val isVisible: Flow<AnimatedValue<Boolean>> =
- combine(
- keyguardTransitionInteractor.finishedKeyguardState.map { it != KeyguardState.GONE },
- deviceEntryInteractor.isBypassEnabled,
- areNotifsFullyHiddenAnimated(),
- isPulseExpandingAnimated(),
- ) {
- onKeyguard: Boolean,
- isBypassEnabled: Boolean,
- notifsFullyHidden: AnimatedValue<Boolean>,
- pulseExpanding: AnimatedValue<Boolean>,
- ->
- when {
- // Hide the AOD icons if we're not in the KEYGUARD state unless the screen off
- // animation is playing, in which case we want them to be visible if we're
- // animating in the AOD UI and will be switching to KEYGUARD shortly.
- !onKeyguard && !screenOffAnimationController.shouldShowAodIconsWhenShade() ->
- AnimatedValue.NotAnimating(false)
- else ->
- zip(notifsFullyHidden, pulseExpanding) {
- areNotifsFullyHidden,
- isPulseExpanding,
- ->
- when {
- // If we're bypassing, then we're visible
- isBypassEnabled -> true
- // If we are pulsing (and not bypassing), then we are hidden
- isPulseExpanding -> false
- // If notifs are fully gone, then we're visible
- areNotifsFullyHidden -> true
- // Otherwise, we're hidden
- else -> false
- }
- }
- }
- }
- .distinctUntilChanged()
-
/** [NotificationIconsViewData] indicating which icons to display in the view. */
val icons: Flow<NotificationIconsViewData> =
iconsInteractor.aodNotifs.map { entries ->
@@ -130,43 +76,4 @@ constructor(
visibleKeys = entries.mapNotNull { it.toIconInfo(it.aodIcon) },
)
}
-
- /** Is there an expanded pulse, are we animating in response? */
- private fun isPulseExpandingAnimated(): Flow<AnimatedValue<Boolean>> {
- return notificationsKeyguardInteractor.isPulseExpanding
- .pairwise(initialValue = null)
- // If pulsing changes, start animating, unless it's the first emission
- .map { (prev, expanding) -> AnimatableEvent(expanding, startAnimating = prev != null) }
- .toAnimatedValueFlow()
- }
-
- /** Are notifications completely hidden from view, are we animating in response? */
- private fun areNotifsFullyHiddenAnimated(): Flow<AnimatedValue<Boolean>> {
- return notificationsKeyguardInteractor.areNotificationsFullyHidden
- .pairwise(initialValue = null)
- .sample(deviceEntryInteractor.isBypassEnabled) { (prev, fullyHidden), bypassEnabled ->
- val animate =
- when {
- // Don't animate for the first value
- prev == null -> false
- // Always animate if bypass is enabled.
- bypassEnabled -> true
- // If we're not bypassing and we're not going to AOD, then we're not
- // animating.
- !dozeParameters.alwaysOn -> false
- // Don't animate when going to AOD if the display needs blanking.
- dozeParameters.displayNeedsBlanking -> false
- // We only want the appear animations to happen when the notifications
- // get fully hidden, since otherwise the un-hide animation overlaps.
- featureFlags.isEnabled(Flags.NEW_AOD_TRANSITION) -> true
- else -> fullyHidden
- }
- AnimatableEvent(fullyHidden, animate)
- }
- .toAnimatedValueFlow()
- }
-
- private class IconColorsImpl(override val tint: Int) : NotificationIconColors {
- override fun staticDrawableColor(viewBounds: Rect, isColorized: Boolean): Int = tint
- }
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerBaseTest.java
index 603d548694ec..4a799d8565b8 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerBaseTest.java
@@ -41,6 +41,7 @@ import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FakeFeatureFlags;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory;
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel;
import com.android.systemui.log.LogBuffer;
import com.android.systemui.plugins.ClockAnimations;
import com.android.systemui.plugins.ClockController;
@@ -191,6 +192,7 @@ public class KeyguardClockSwitchControllerBaseTest extends SysuiTestCase {
mClockEventController,
mLogBuffer,
mock(NotificationIconContainerAlwaysOnDisplayViewModel.class),
+ mock(KeyguardRootViewModel.class),
mock(ConfigurationState.class),
mock(DozeParameters.class),
mock(AlwaysOnDisplayNotificationIconViewStore.class),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
index b80771ff646c..4a1386e21e2a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
@@ -15,16 +15,24 @@
*
*/
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
package com.android.systemui.keyguard.ui.viewmodel
import android.view.View
import androidx.test.filters.SmallTest
+import com.android.SysUITestModule
+import com.android.TestMocksModule
import com.android.systemui.SysuiTestCase
import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.deviceentry.data.repository.FakeDeviceEntryRepository
+import com.android.systemui.flags.FakeFeatureFlagsClassic
+import com.android.systemui.flags.FakeFeatureFlagsClassicModule
import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
import com.android.systemui.keyguard.domain.interactor.BurnInInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory
@@ -33,14 +41,25 @@ import com.android.systemui.keyguard.shared.model.BurnInModel
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionStep
import com.android.systemui.plugins.ClockController
+import com.android.systemui.statusbar.notification.data.repository.FakeNotificationsKeyguardViewStateRepository
+import com.android.systemui.statusbar.phone.DozeParameters
+import com.android.systemui.statusbar.phone.ScreenOffAnimationController
+import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.ui.isAnimating
+import com.android.systemui.util.ui.stopAnimating
+import com.android.systemui.util.ui.value
import com.google.common.truth.Truth.assertThat
+import dagger.BindsInstance
+import dagger.Component
import javax.inject.Provider
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.emptyFlow
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
@@ -48,7 +67,10 @@ import org.junit.runner.RunWith
import org.junit.runners.JUnit4
import org.mockito.Answers
import org.mockito.Mock
+import org.mockito.Mockito.RETURNS_DEEP_STUBS
import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.reset
+import org.mockito.Mockito.withSettings
import org.mockito.MockitoAnnotations
@SmallTest
@@ -81,7 +103,7 @@ class KeyguardRootViewModelTest : SysuiTestCase() {
MockitoAnnotations.initMocks(this)
val featureFlags =
- FakeFeatureFlags().apply {
+ FakeFeatureFlagsClassic().apply {
set(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA, true)
set(Flags.FACE_AUTH_REFACTOR, true)
}
@@ -107,11 +129,21 @@ class KeyguardRootViewModelTest : SysuiTestCase() {
underTest =
KeyguardRootViewModel(
context,
+ deviceEntryInteractor =
+ mock { whenever(isBypassEnabled).thenReturn(MutableStateFlow(false)) },
+ dozeParameters = mock(),
+ featureFlags,
keyguardInteractor,
+ keyguardTransitionInteractor,
+ notificationsKeyguardInteractor =
+ mock {
+ whenever(areNotificationsFullyHidden).thenReturn(emptyFlow())
+ whenever(isPulseExpanding).thenReturn(emptyFlow())
+ },
burnInInteractor,
goneToAodTransitionViewModel,
aodToLockscreenTransitionViewModel,
- keyguardTransitionInteractor,
+ screenOffAnimationController = mock(),
)
underTest.clockControllerProvider = Provider { clockController }
}
@@ -281,3 +313,169 @@ class KeyguardRootViewModelTest : SysuiTestCase() {
assertThat(burnInLayerAlpha).isEqualTo(1f)
}
}
+
+@SmallTest
+class KeyguardRootViewModelTestWithFakes : SysuiTestCase() {
+
+ @Component(modules = [SysUITestModule::class])
+ @SysUISingleton
+ interface TestComponent {
+ val underTest: KeyguardRootViewModel
+ val deviceEntryRepository: FakeDeviceEntryRepository
+ val notifsKeyguardRepository: FakeNotificationsKeyguardViewStateRepository
+ val repository: FakeKeyguardRepository
+ val testScope: TestScope
+ val transitionRepository: FakeKeyguardTransitionRepository
+
+ @Component.Factory
+ interface Factory {
+ fun create(
+ @BindsInstance test: SysuiTestCase,
+ featureFlags: FakeFeatureFlagsClassicModule,
+ mocks: TestMocksModule,
+ ): TestComponent
+ }
+ }
+
+ private val clockController: ClockController =
+ mock(withSettings().defaultAnswer(RETURNS_DEEP_STUBS))
+ private val dozeParams: DozeParameters = mock()
+ private val screenOffAnimController: ScreenOffAnimationController = mock()
+
+ private fun runTest(block: suspend TestComponent.() -> Unit): Unit =
+ DaggerKeyguardRootViewModelTestWithFakes_TestComponent.factory()
+ .create(
+ test = this,
+ featureFlags =
+ FakeFeatureFlagsClassicModule {
+ setDefault(Flags.NEW_AOD_TRANSITION)
+ set(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA, true)
+ set(Flags.FACE_AUTH_REFACTOR, true)
+ },
+ mocks =
+ TestMocksModule(
+ dozeParameters = dozeParams,
+ screenOffAnimationController = screenOffAnimController,
+ )
+ )
+ .run {
+ reset(clockController)
+ underTest.clockControllerProvider = Provider { clockController }
+ testScope.runTest {
+ runCurrent()
+ block()
+ }
+ }
+
+ @Test
+ fun iconContainer_isNotVisible_notOnKeyguard_dontShowAodIconsWhenShade() = runTest {
+ val isVisible by testScope.collectLastValue(underTest.isNotifIconContainerVisible)
+ testScope.runCurrent()
+ transitionRepository.sendTransitionSteps(
+ from = KeyguardState.OFF,
+ to = KeyguardState.GONE,
+ testScope,
+ )
+ whenever(screenOffAnimController.shouldShowAodIconsWhenShade()).thenReturn(false)
+ testScope.runCurrent()
+
+ assertThat(isVisible?.value).isFalse()
+ assertThat(isVisible?.isAnimating).isFalse()
+ }
+
+ @Test
+ fun iconContainer_isVisible_bypassEnabled() = runTest {
+ val isVisible by testScope.collectLastValue(underTest.isNotifIconContainerVisible)
+ testScope.runCurrent()
+ deviceEntryRepository.setBypassEnabled(true)
+ testScope.runCurrent()
+
+ assertThat(isVisible?.value).isTrue()
+ }
+
+ @Test
+ fun iconContainer_isNotVisible_pulseExpanding_notBypassing() = runTest {
+ val isVisible by testScope.collectLastValue(underTest.isNotifIconContainerVisible)
+ testScope.runCurrent()
+ notifsKeyguardRepository.setPulseExpanding(true)
+ deviceEntryRepository.setBypassEnabled(false)
+ testScope.runCurrent()
+
+ assertThat(isVisible?.value).isEqualTo(false)
+ }
+
+ @Test
+ fun iconContainer_isVisible_notifsFullyHidden_bypassEnabled() = runTest {
+ val isVisible by testScope.collectLastValue(underTest.isNotifIconContainerVisible)
+ testScope.runCurrent()
+ notifsKeyguardRepository.setPulseExpanding(false)
+ deviceEntryRepository.setBypassEnabled(true)
+ notifsKeyguardRepository.setNotificationsFullyHidden(true)
+ testScope.runCurrent()
+
+ assertThat(isVisible?.value).isTrue()
+ assertThat(isVisible?.isAnimating).isTrue()
+ }
+
+ @Test
+ fun iconContainer_isVisible_notifsFullyHidden_bypassDisabled_aodDisabled() = runTest {
+ val isVisible by testScope.collectLastValue(underTest.isNotifIconContainerVisible)
+ testScope.runCurrent()
+ notifsKeyguardRepository.setPulseExpanding(false)
+ deviceEntryRepository.setBypassEnabled(false)
+ whenever(dozeParams.alwaysOn).thenReturn(false)
+ notifsKeyguardRepository.setNotificationsFullyHidden(true)
+ testScope.runCurrent()
+
+ assertThat(isVisible?.value).isTrue()
+ assertThat(isVisible?.isAnimating).isFalse()
+ }
+
+ @Test
+ fun iconContainer_isVisible_notifsFullyHidden_bypassDisabled_displayNeedsBlanking() = runTest {
+ val isVisible by testScope.collectLastValue(underTest.isNotifIconContainerVisible)
+ testScope.runCurrent()
+ notifsKeyguardRepository.setPulseExpanding(false)
+ deviceEntryRepository.setBypassEnabled(false)
+ whenever(dozeParams.alwaysOn).thenReturn(true)
+ whenever(dozeParams.displayNeedsBlanking).thenReturn(true)
+ notifsKeyguardRepository.setNotificationsFullyHidden(true)
+ testScope.runCurrent()
+
+ assertThat(isVisible?.value).isTrue()
+ assertThat(isVisible?.isAnimating).isFalse()
+ }
+
+ @Test
+ fun iconContainer_isVisible_notifsFullyHidden_bypassDisabled() = runTest {
+ val isVisible by testScope.collectLastValue(underTest.isNotifIconContainerVisible)
+ testScope.runCurrent()
+ notifsKeyguardRepository.setPulseExpanding(false)
+ deviceEntryRepository.setBypassEnabled(false)
+ whenever(dozeParams.alwaysOn).thenReturn(true)
+ whenever(dozeParams.displayNeedsBlanking).thenReturn(false)
+ notifsKeyguardRepository.setNotificationsFullyHidden(true)
+ testScope.runCurrent()
+
+ assertThat(isVisible?.value).isTrue()
+ assertThat(isVisible?.isAnimating).isTrue()
+ }
+
+ @Test
+ fun isIconContainerVisible_stopAnimation() = runTest {
+ val isVisible by testScope.collectLastValue(underTest.isNotifIconContainerVisible)
+ testScope.runCurrent()
+ notifsKeyguardRepository.setPulseExpanding(false)
+ deviceEntryRepository.setBypassEnabled(false)
+ whenever(dozeParams.alwaysOn).thenReturn(true)
+ whenever(dozeParams.displayNeedsBlanking).thenReturn(false)
+ notifsKeyguardRepository.setNotificationsFullyHidden(true)
+ testScope.runCurrent()
+
+ assertThat(isVisible?.isAnimating).isEqualTo(true)
+ isVisible?.stopAnimating()
+ testScope.runCurrent()
+
+ assertThat(isVisible?.isAnimating).isEqualTo(false)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModelTest.kt
index 14d188c69525..49e1493f642f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModelTest.kt
@@ -25,7 +25,6 @@ import com.android.systemui.SysuiTestCase
import com.android.systemui.biometrics.domain.BiometricsDomainLayerModule
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.deviceentry.data.repository.FakeDeviceEntryRepository
import com.android.systemui.flags.FakeFeatureFlagsClassicModule
import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
@@ -38,7 +37,6 @@ import com.android.systemui.keyguard.shared.model.TransitionStep
import com.android.systemui.power.data.repository.FakePowerRepository
import com.android.systemui.power.shared.model.WakeSleepReason
import com.android.systemui.power.shared.model.WakefulnessState
-import com.android.systemui.statusbar.notification.data.repository.FakeNotificationsKeyguardViewStateRepository
import com.android.systemui.statusbar.phone.DozeParameters
import com.android.systemui.statusbar.phone.ScreenOffAnimationController
import com.android.systemui.statusbar.policy.data.repository.FakeDeviceProvisioningRepository
@@ -70,16 +68,12 @@ class NotificationIconContainerAlwaysOnDisplayViewModelTest : SysuiTestCase() {
private lateinit var testComponent: TestComponent
private val underTest: NotificationIconContainerAlwaysOnDisplayViewModel
get() = testComponent.underTest
- private val deviceEntryRepository: FakeDeviceEntryRepository
- get() = testComponent.deviceEntryRepository
private val deviceProvisioningRepository: FakeDeviceProvisioningRepository
get() = testComponent.deviceProvisioningRepository
private val keyguardRepository: FakeKeyguardRepository
get() = testComponent.keyguardRepository
private val keyguardTransitionRepository: FakeKeyguardTransitionRepository
get() = testComponent.keyguardTransitionRepository
- private val notifsKeyguardRepository: FakeNotificationsKeyguardViewStateRepository
- get() = testComponent.notifsKeyguardRepository
private val powerRepository: FakePowerRepository
get() = testComponent.powerRepository
private val scope: TestScope
@@ -354,137 +348,6 @@ class NotificationIconContainerAlwaysOnDisplayViewModelTest : SysuiTestCase() {
assertThat(isDozing?.isAnimating).isEqualTo(false)
}
- @Test
- fun isNotVisible_pulseExpanding() =
- scope.runTest {
- val isVisible by collectLastValue(underTest.isVisible)
- runCurrent()
- notifsKeyguardRepository.setPulseExpanding(true)
- runCurrent()
-
- assertThat(isVisible?.value).isFalse()
- }
-
- @Test
- fun isNotVisible_notOnKeyguard_dontShowAodIconsWhenShade() =
- scope.runTest {
- val isVisible by collectLastValue(underTest.isVisible)
- runCurrent()
- keyguardTransitionRepository.sendTransitionSteps(
- from = KeyguardState.OFF,
- to = KeyguardState.GONE,
- scope,
- )
- whenever(screenOffAnimController.shouldShowAodIconsWhenShade()).thenReturn(false)
- runCurrent()
-
- assertThat(isVisible?.value).isFalse()
- assertThat(isVisible?.isAnimating).isFalse()
- }
-
- @Test
- fun isVisible_bypassEnabled() =
- scope.runTest {
- val isVisible by collectLastValue(underTest.isVisible)
- runCurrent()
- deviceEntryRepository.setBypassEnabled(true)
- runCurrent()
-
- assertThat(isVisible?.value).isTrue()
- }
-
- @Test
- fun isNotVisible_pulseExpanding_notBypassing() =
- scope.runTest {
- val isVisible by collectLastValue(underTest.isVisible)
- runCurrent()
- notifsKeyguardRepository.setPulseExpanding(true)
- deviceEntryRepository.setBypassEnabled(false)
- runCurrent()
-
- assertThat(isVisible?.value).isEqualTo(false)
- }
-
- @Test
- fun isVisible_notifsFullyHidden_bypassEnabled() =
- scope.runTest {
- val isVisible by collectLastValue(underTest.isVisible)
- runCurrent()
- notifsKeyguardRepository.setPulseExpanding(false)
- deviceEntryRepository.setBypassEnabled(true)
- notifsKeyguardRepository.setNotificationsFullyHidden(true)
- runCurrent()
-
- assertThat(isVisible?.value).isTrue()
- assertThat(isVisible?.isAnimating).isTrue()
- }
-
- @Test
- fun isVisible_notifsFullyHidden_bypassDisabled_aodDisabled() =
- scope.runTest {
- val isVisible by collectLastValue(underTest.isVisible)
- runCurrent()
- notifsKeyguardRepository.setPulseExpanding(false)
- deviceEntryRepository.setBypassEnabled(false)
- whenever(dozeParams.alwaysOn).thenReturn(false)
- notifsKeyguardRepository.setNotificationsFullyHidden(true)
- runCurrent()
-
- assertThat(isVisible?.value).isTrue()
- assertThat(isVisible?.isAnimating).isFalse()
- }
-
- @Test
- fun isVisible_notifsFullyHidden_bypassDisabled_displayNeedsBlanking() =
- scope.runTest {
- val isVisible by collectLastValue(underTest.isVisible)
- runCurrent()
- notifsKeyguardRepository.setPulseExpanding(false)
- deviceEntryRepository.setBypassEnabled(false)
- whenever(dozeParams.alwaysOn).thenReturn(true)
- whenever(dozeParams.displayNeedsBlanking).thenReturn(true)
- notifsKeyguardRepository.setNotificationsFullyHidden(true)
- runCurrent()
-
- assertThat(isVisible?.value).isTrue()
- assertThat(isVisible?.isAnimating).isFalse()
- }
-
- @Test
- fun isVisible_notifsFullyHidden_bypassDisabled() =
- scope.runTest {
- val isVisible by collectLastValue(underTest.isVisible)
- runCurrent()
- notifsKeyguardRepository.setPulseExpanding(false)
- deviceEntryRepository.setBypassEnabled(false)
- whenever(dozeParams.alwaysOn).thenReturn(true)
- whenever(dozeParams.displayNeedsBlanking).thenReturn(false)
- notifsKeyguardRepository.setNotificationsFullyHidden(true)
- runCurrent()
-
- assertThat(isVisible?.value).isTrue()
- assertThat(isVisible?.isAnimating).isTrue()
- }
-
- @Test
- fun isVisible_stopAnimation() =
- scope.runTest {
- val isVisible by collectLastValue(underTest.isVisible)
- runCurrent()
- notifsKeyguardRepository.setPulseExpanding(false)
- deviceEntryRepository.setBypassEnabled(false)
- whenever(dozeParams.alwaysOn).thenReturn(true)
- whenever(dozeParams.displayNeedsBlanking).thenReturn(false)
- notifsKeyguardRepository.setNotificationsFullyHidden(true)
- runCurrent()
-
- assertThat(isVisible?.isAnimating).isEqualTo(true)
- isVisible?.stopAnimating()
- runCurrent()
-
- assertThat(isVisible?.isAnimating).isEqualTo(false)
- }
-
@SysUISingleton
@Component(
modules =
@@ -498,11 +361,9 @@ class NotificationIconContainerAlwaysOnDisplayViewModelTest : SysuiTestCase() {
val underTest: NotificationIconContainerAlwaysOnDisplayViewModel
- val deviceEntryRepository: FakeDeviceEntryRepository
val deviceProvisioningRepository: FakeDeviceProvisioningRepository
val keyguardRepository: FakeKeyguardRepository
val keyguardTransitionRepository: FakeKeyguardTransitionRepository
- val notifsKeyguardRepository: FakeNotificationsKeyguardViewStateRepository
val powerRepository: FakePowerRepository
val scope: TestScope
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/mockito/KotlinMockitoHelpers.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/util/mockito/KotlinMockitoHelpers.kt
index 7e0632bddcf9..efccafc95a11 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/util/mockito/KotlinMockitoHelpers.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/mockito/KotlinMockitoHelpers.kt
@@ -22,47 +22,47 @@ package com.android.systemui.util.mockito
* be null"). To fix this, we can use methods that modify the return type to be nullable. This
* causes Kotlin to skip the null checks.
*/
-
import org.mockito.ArgumentCaptor
import org.mockito.ArgumentMatcher
+import org.mockito.MockSettings
import org.mockito.Mockito
import org.mockito.Mockito.`when`
+import org.mockito.Mockito.withSettings
import org.mockito.stubbing.OngoingStubbing
import org.mockito.stubbing.Stubber
/**
- * Returns Mockito.eq() as nullable type to avoid java.lang.IllegalStateException when
- * null is returned.
+ * Returns Mockito.eq() as nullable type to avoid java.lang.IllegalStateException when null is
+ * returned.
*
* Generic T is nullable because implicitly bounded by Any?.
*/
fun <T> eq(obj: T): T = Mockito.eq<T>(obj) ?: obj
/**
- * Returns Mockito.any() as nullable type to avoid java.lang.IllegalStateException when
- * null is returned.
+ * Returns Mockito.any() as nullable type to avoid java.lang.IllegalStateException when null is
+ * returned.
*
* Generic T is nullable because implicitly bounded by Any?.
*/
fun <T> any(type: Class<T>): T = Mockito.any<T>(type)
+
inline fun <reified T> any(): T = any(T::class.java)
/**
- * Returns Mockito.argThat() as nullable type to avoid java.lang.IllegalStateException when
- * null is returned.
+ * Returns Mockito.argThat() as nullable type to avoid java.lang.IllegalStateException when null is
+ * returned.
*
* Generic T is nullable because implicitly bounded by Any?.
*/
fun <T> argThat(matcher: ArgumentMatcher<T>): T = Mockito.argThat(matcher)
-/**
- * Kotlin type-inferred version of Mockito.nullable()
- */
+/** Kotlin type-inferred version of Mockito.nullable() */
inline fun <reified T> nullable(): T? = Mockito.nullable(T::class.java)
/**
- * Returns ArgumentCaptor.capture() as nullable type to avoid java.lang.IllegalStateException
- * when null is returned.
+ * Returns ArgumentCaptor.capture() as nullable type to avoid java.lang.IllegalStateException when
+ * null is returned.
*
* Generic T is nullable because implicitly bounded by Any?.
*/
@@ -74,7 +74,7 @@ fun <T> capture(argumentCaptor: ArgumentCaptor<T>): T = argumentCaptor.capture()
* Generic T is nullable because implicitly bounded by Any?.
*/
inline fun <reified T : Any> argumentCaptor(): ArgumentCaptor<T> =
- ArgumentCaptor.forClass(T::class.java)
+ ArgumentCaptor.forClass(T::class.java)
/**
* Helper function for creating new mocks, without the need to pass in a [Class] instance.
@@ -83,8 +83,8 @@ inline fun <reified T : Any> argumentCaptor(): ArgumentCaptor<T> =
*
* @param apply builder function to simplify stub configuration by improving type inference.
*/
-inline fun <reified T : Any> mock(apply: T.() -> Unit = {}): T = Mockito.mock(T::class.java)
- .apply(apply)
+inline fun <reified T : Any> mock(settings: MockSettings? = null, apply: T.() -> Unit = {}): T =
+ Mockito.mock(T::class.java, settings ?: withSettings()).apply(apply)
/**
* Helper function for stubbing methods without the need to use backticks.
@@ -92,6 +92,7 @@ inline fun <reified T : Any> mock(apply: T.() -> Unit = {}): T = Mockito.mock(T:
* @see Mockito.when
*/
fun <T> whenever(methodCall: T): OngoingStubbing<T> = `when`(methodCall)
+
fun <T> Stubber.whenever(mock: T): T = `when`(mock)
/**
@@ -115,34 +116,32 @@ class KotlinArgumentCaptor<T> constructor(clazz: Class<T>) {
* Generic T is nullable because implicitly bounded by Any?.
*/
inline fun <reified T : Any> kotlinArgumentCaptor(): KotlinArgumentCaptor<T> =
- KotlinArgumentCaptor(T::class.java)
+ KotlinArgumentCaptor(T::class.java)
/**
* Helper function for creating and using a single-use ArgumentCaptor in kotlin.
*
- * val captor = argumentCaptor<Foo>()
- * verify(...).someMethod(captor.capture())
- * val captured = captor.value
+ * val captor = argumentCaptor<Foo>() verify(...).someMethod(captor.capture()) val captured =
+ * captor.value
*
* becomes:
*
- * val captured = withArgCaptor<Foo> { verify(...).someMethod(capture()) }
+ * val captured = withArgCaptor<Foo> { verify(...).someMethod(capture()) }
*
* NOTE: this uses the KotlinArgumentCaptor to avoid the NullPointerException.
*/
inline fun <reified T : Any> withArgCaptor(block: KotlinArgumentCaptor<T>.() -> Unit): T =
- kotlinArgumentCaptor<T>().apply { block() }.value
+ kotlinArgumentCaptor<T>().apply { block() }.value
/**
* Variant of [withArgCaptor] for capturing multiple arguments.
*
- * val captor = argumentCaptor<Foo>()
- * verify(...).someMethod(captor.capture())
- * val captured: List<Foo> = captor.allValues
+ * val captor = argumentCaptor<Foo>() verify(...).someMethod(captor.capture()) val captured:
+ * List<Foo> = captor.allValues
*
* becomes:
*
- * val capturedList = captureMany<Foo> { verify(...).someMethod(capture()) }
+ * val capturedList = captureMany<Foo> { verify(...).someMethod(capture()) }
*/
inline fun <reified T : Any> captureMany(block: KotlinArgumentCaptor<T>.() -> Unit): List<T> =
- kotlinArgumentCaptor<T>().apply{ block() }.allValues
+ kotlinArgumentCaptor<T>().apply { block() }.allValues