diff options
| author | 2023-08-29 20:27:06 +0000 | |
|---|---|---|
| committer | 2023-08-29 20:27:06 +0000 | |
| commit | cf5fd08f38a8853a339081b34ca8aa70a51d6ed0 (patch) | |
| tree | 11c1c70b77e01191cd97a3045e49b1c211573fe8 | |
| parent | c56011bb93b01bbf33e14f1d3848dbcd6e7e2169 (diff) | |
| parent | f882c6671bff2553257ebfba6ef43e81d416c9e3 (diff) | |
Merge changes from topics "caitlinshk-main-backgesture", "caitlinshk-main-cs-notiflogger", "caitlinshk-main-statusbarservice" into main
* changes:
[CS] Have NotificationGutsManager listen for shade/LS vis directly.
[CS] 4/4: Update IStatusBarService from WindowRootViewVisIntr/Repo.
[CS] 3/4: Move back gesture callback registration to back interactor.
[CS] 2/4: Have NotificationLogger listen to shade/LS visibility directly
[CS] 1/4: Add WindowRootViewVisibilityInteractor visibility flows
24 files changed, 1267 insertions, 473 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/back/domain/interactor/BackActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/back/domain/interactor/BackActionInteractor.kt index 3c74bf484e98..066cba230b76 100644 --- a/packages/SystemUI/src/com/android/systemui/back/domain/interactor/BackActionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/back/domain/interactor/BackActionInteractor.kt @@ -16,24 +16,69 @@ package com.android.systemui.back.domain.interactor +import android.window.BackEvent +import android.window.OnBackAnimationCallback +import android.window.OnBackInvokedCallback +import android.window.OnBackInvokedDispatcher +import android.window.WindowOnBackInvokedDispatcher +import com.android.systemui.CoreStartable import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.flags.FeatureFlags +import com.android.systemui.flags.Flags import com.android.systemui.plugins.statusbar.StatusBarStateController +import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor import com.android.systemui.shade.QuickSettingsController import com.android.systemui.shade.ShadeController import com.android.systemui.shade.ShadeViewController +import com.android.systemui.statusbar.NotificationShadeWindowController import com.android.systemui.statusbar.StatusBarState import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch /** Handles requests to go back either from a button or gesture. */ @SysUISingleton class BackActionInteractor @Inject constructor( + @Application private val scope: CoroutineScope, private val statusBarStateController: StatusBarStateController, private val statusBarKeyguardViewManager: StatusBarKeyguardViewManager, - private val shadeController: ShadeController -) { + private val shadeController: ShadeController, + private val notificationShadeWindowController: NotificationShadeWindowController, + private val windowRootViewVisibilityInteractor: WindowRootViewVisibilityInteractor, + featureFlags: FeatureFlags, +) : CoreStartable { + + private var isCallbackRegistered = false + + private val callback = + if (featureFlags.isEnabled(Flags.WM_SHADE_ANIMATE_BACK_GESTURE)) { + /** + * New callback that handles back gesture invoked, cancel, progress and provides + * feedback via Shade animation. + */ + object : OnBackAnimationCallback { + override fun onBackInvoked() { + onBackRequested() + } + + override fun onBackProgressed(backEvent: BackEvent) { + if (shouldBackBeHandled() && shadeViewController.canBeCollapsed()) { + shadeViewController.onBackProgressed(backEvent.progress) + } + } + } + } else { + OnBackInvokedCallback { onBackRequested() } + } + + private val onBackInvokedDispatcher: WindowOnBackInvokedDispatcher? + get() = + notificationShadeWindowController.windowRootView?.viewRootImpl?.onBackInvokedDispatcher + private lateinit var shadeViewController: ShadeViewController private lateinit var qsController: QuickSettingsController @@ -42,6 +87,19 @@ constructor( this.shadeViewController = svController } + override fun start() { + scope.launch { + windowRootViewVisibilityInteractor.isLockscreenOrShadeVisibleAndInteractive.collect { + visible -> + if (visible) { + registerBackCallback() + } else { + unregisterBackCallback() + } + } + } + } + fun shouldBackBeHandled(): Boolean { return statusBarStateController.state != StatusBarState.KEYGUARD && statusBarStateController.state != StatusBarState.SHADE_LOCKED && @@ -74,4 +132,24 @@ constructor( } return false } + + private fun registerBackCallback() { + if (isCallbackRegistered) { + return + } + onBackInvokedDispatcher?.let { + it.registerOnBackInvokedCallback(OnBackInvokedDispatcher.PRIORITY_DEFAULT, callback) + isCallbackRegistered = true + } + } + + private fun unregisterBackCallback() { + if (!isCallbackRegistered) { + return + } + onBackInvokedDispatcher?.let { + it.unregisterOnBackInvokedCallback(callback) + isCallbackRegistered = false + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt index 1b2a9ebc9ca4..7ce7ce94e4c9 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt +++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt @@ -23,6 +23,7 @@ import com.android.systemui.ScreenDecorations import com.android.systemui.SliceBroadcastRelayHandler import com.android.systemui.accessibility.SystemActions import com.android.systemui.accessibility.WindowMagnification +import com.android.systemui.back.domain.interactor.BackActionInteractor import com.android.systemui.biometrics.AuthController import com.android.systemui.biometrics.BiometricNotificationService import com.android.systemui.clipboardoverlay.ClipboardListener @@ -346,4 +347,9 @@ abstract class SystemUICoreStartableModule { abstract fun bindStatusBarHeadsUpChangeListener( impl: StatusBarHeadsUpChangeListener ): CoreStartable + + @Binds + @IntoMap + @ClassKey(BackActionInteractor::class) + abstract fun bindBackActionInteractor(impl: BackActionInteractor): CoreStartable } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt index 42cd3a576926..d399e4c145fc 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt @@ -158,7 +158,7 @@ interface KeyguardRepository { val lastDozeTapToWakePosition: StateFlow<Point?> /** Observable for the [StatusBarState] */ - val statusBarState: Flow<StatusBarState> + val statusBarState: StateFlow<StatusBarState> /** Observable for device wake/sleep state */ val wakefulness: StateFlow<WakefulnessModel> @@ -520,23 +520,29 @@ constructor( return keyguardBypassController.bypassEnabled } - override val statusBarState: Flow<StatusBarState> = conflatedCallbackFlow { - val callback = - object : StatusBarStateController.StateListener { - override fun onStateChanged(state: Int) { - trySendWithFailureLogging(statusBarStateIntToObject(state), TAG, "state") - } - } - - statusBarStateController.addCallback(callback) - trySendWithFailureLogging( - statusBarStateIntToObject(statusBarStateController.getState()), - TAG, - "initial state" - ) + // TODO(b/297345631): Expose this at the interactor level instead so that it can be powered by + // [SceneInteractor] when scenes are ready. + override val statusBarState: StateFlow<StatusBarState> = + conflatedCallbackFlow { + val callback = + object : StatusBarStateController.StateListener { + override fun onStateChanged(state: Int) { + trySendWithFailureLogging( + statusBarStateIntToObject(state), + TAG, + "state" + ) + } + } - awaitClose { statusBarStateController.removeCallback(callback) } - } + statusBarStateController.addCallback(callback) + awaitClose { statusBarStateController.removeCallback(callback) } + } + .stateIn( + scope, + SharingStarted.Eagerly, + statusBarStateIntToObject(statusBarStateController.state) + ) override val biometricUnlockState: Flow<BiometricUnlockModel> = conflatedCallbackFlow { fun dispatchUpdate() { diff --git a/packages/SystemUI/src/com/android/systemui/scene/data/repository/WindowRootViewVisibilityRepository.kt b/packages/SystemUI/src/com/android/systemui/scene/data/repository/WindowRootViewVisibilityRepository.kt new file mode 100644 index 000000000000..d833e56a1795 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/scene/data/repository/WindowRootViewVisibilityRepository.kt @@ -0,0 +1,74 @@ +/* + * 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.scene.data.repository + +import android.os.RemoteException +import com.android.internal.statusbar.IStatusBarService +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.UiBackground +import java.util.concurrent.Executor +import javax.inject.Inject +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow + +/** Source of truth for the visibility of various parts of the window root view. */ +@SysUISingleton +class WindowRootViewVisibilityRepository +@Inject +constructor( + private val statusBarService: IStatusBarService, + @UiBackground private val uiBgExecutor: Executor, +) { + private val _isLockscreenOrShadeVisible = MutableStateFlow(false) + val isLockscreenOrShadeVisible: StateFlow<Boolean> = _isLockscreenOrShadeVisible.asStateFlow() + + fun setIsLockscreenOrShadeVisible(visible: Boolean) { + _isLockscreenOrShadeVisible.value = visible + } + + /** + * Called when the lockscreen or shade has been shown and can be interacted with so that SysUI + * can notify external services. + */ + fun onLockscreenOrShadeInteractive( + shouldClearNotificationEffects: Boolean, + notificationCount: Int, + ) { + executeServiceCallOnUiBg { + statusBarService.onPanelRevealed(shouldClearNotificationEffects, notificationCount) + } + } + + /** + * Called when the lockscreen or shade no longer can be interactecd with so that SysUI can + * notify external services. + */ + fun onLockscreenOrShadeNotInteractive() { + executeServiceCallOnUiBg { statusBarService.onPanelHidden() } + } + + private fun executeServiceCallOnUiBg(runnable: () -> Unit) { + uiBgExecutor.execute { + try { + runnable.invoke() + } catch (ex: RemoteException) { + // Won't fail unless the world has ended + } + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractor.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractor.kt new file mode 100644 index 000000000000..16ffcc27a70f --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractor.kt @@ -0,0 +1,121 @@ +/* + * 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.scene.domain.interactor + +import com.android.systemui.CoreStartable +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.keyguard.data.repository.KeyguardRepository +import com.android.systemui.keyguard.shared.model.StatusBarState +import com.android.systemui.scene.data.repository.WindowRootViewVisibilityRepository +import com.android.systemui.statusbar.NotificationPresenter +import com.android.systemui.statusbar.notification.init.NotificationsController +import com.android.systemui.statusbar.policy.HeadsUpManager +import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.launch + +/** Business logic about the visibility of various parts of the window root view. */ +@SysUISingleton +class WindowRootViewVisibilityInteractor +@Inject +constructor( + @Application private val scope: CoroutineScope, + private val windowRootViewVisibilityRepository: WindowRootViewVisibilityRepository, + private val keyguardRepository: KeyguardRepository, + private val headsUpManager: HeadsUpManager, +) : CoreStartable { + + private var notificationPresenter: NotificationPresenter? = null + private var notificationsController: NotificationsController? = null + + private val isNotifPresenterFullyCollapsed: Boolean + get() = notificationPresenter?.isPresenterFullyCollapsed ?: true + + /** + * True if lockscreen (including AOD) or the shade is visible and false otherwise. Notably, + * false if the bouncer is visible. + * + * TODO(b/297080059): Use [SceneInteractor] as the source of truth if the scene flag is on. + */ + val isLockscreenOrShadeVisible: StateFlow<Boolean> = + windowRootViewVisibilityRepository.isLockscreenOrShadeVisible + + /** + * True if lockscreen (including AOD) or the shade is visible **and** the user is currently + * interacting with the device, false otherwise. Notably, false if the bouncer is visible and + * false if the device is asleep. + */ + val isLockscreenOrShadeVisibleAndInteractive: StateFlow<Boolean> = + combine( + isLockscreenOrShadeVisible, + keyguardRepository.wakefulness, + ) { isKeyguardAodOrShadeVisible, wakefulness -> + isKeyguardAodOrShadeVisible && wakefulness.isDeviceInteractive() + } + .stateIn(scope, SharingStarted.Eagerly, initialValue = false) + + /** + * Sets classes that aren't easily injectable on this class. + * + * TODO(b/277762009): Inject these directly instead. + */ + fun setUp( + presenter: NotificationPresenter?, + notificationsController: NotificationsController?, + ) { + this.notificationPresenter = presenter + this.notificationsController = notificationsController + } + + override fun start() { + scope.launch { + isLockscreenOrShadeVisibleAndInteractive.collect { interactive -> + if (interactive) { + windowRootViewVisibilityRepository.onLockscreenOrShadeInteractive( + getShouldClearNotificationEffects(keyguardRepository.statusBarState.value), + getNotificationLoad(), + ) + } else { + windowRootViewVisibilityRepository.onLockscreenOrShadeNotInteractive() + } + } + } + } + + fun setIsLockscreenOrShadeVisible(visible: Boolean) { + windowRootViewVisibilityRepository.setIsLockscreenOrShadeVisible(visible) + } + + private fun getShouldClearNotificationEffects(statusBarState: StatusBarState): Boolean { + return !isNotifPresenterFullyCollapsed && + (statusBarState == StatusBarState.SHADE || + statusBarState == StatusBarState.SHADE_LOCKED) + } + + private fun getNotificationLoad(): Int { + return if (headsUpManager.hasPinnedHeadsUp() && isNotifPresenterFullyCollapsed) { + 1 + } else { + notificationsController?.getActiveNotificationsCount() ?: 0 + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartableModule.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartableModule.kt index 8da1803053f4..fcfdcebba2ea 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartableModule.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartableModule.kt @@ -17,6 +17,7 @@ package com.android.systemui.scene.domain.startable import com.android.systemui.CoreStartable +import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor import dagger.Binds import dagger.Module import dagger.multibindings.ClassKey @@ -29,4 +30,11 @@ interface SceneContainerStartableModule { @IntoMap @ClassKey(SceneContainerStartable::class) fun bind(impl: SceneContainerStartable): CoreStartable + + @Binds + @IntoMap + @ClassKey(WindowRootViewVisibilityInteractor::class) + fun bindWindowRootViewVisibilityInteractor( + impl: WindowRootViewVisibilityInteractor + ): CoreStartable } 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 c9a73e665293..ef688a8e4337 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,3 +1,19 @@ +/* + * 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.scene.ui.view import android.annotation.SuppressLint diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java index d7a339210412..9a3e4e577634 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java @@ -30,6 +30,7 @@ import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.log.LogBuffer; import com.android.systemui.log.dagger.ShadeTouchLog; import com.android.systemui.plugins.statusbar.StatusBarStateController; +import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.NotificationPresenter; import com.android.systemui.statusbar.NotificationShadeWindowController; @@ -59,6 +60,7 @@ public final class ShadeControllerImpl implements ShadeController { private final CommandQueue mCommandQueue; private final Executor mMainExecutor; private final LogBuffer mTouchLog; + private final WindowRootViewVisibilityInteractor mWindowRootViewVisibilityInteractor; private final KeyguardStateController mKeyguardStateController; private final NotificationShadeWindowController mNotificationShadeWindowController; private final StatusBarStateController mStatusBarStateController; @@ -83,6 +85,7 @@ public final class ShadeControllerImpl implements ShadeController { CommandQueue commandQueue, @Main Executor mainExecutor, @ShadeTouchLog LogBuffer touchLog, + WindowRootViewVisibilityInteractor windowRootViewVisibilityInteractor, KeyguardStateController keyguardStateController, StatusBarStateController statusBarStateController, StatusBarKeyguardViewManager statusBarKeyguardViewManager, @@ -97,6 +100,7 @@ public final class ShadeControllerImpl implements ShadeController { mCommandQueue = commandQueue; mMainExecutor = mainExecutor; mTouchLog = touchLog; + mWindowRootViewVisibilityInteractor = windowRootViewVisibilityInteractor; mShadeViewControllerLazy = shadeViewControllerLazy; mStatusBarStateController = statusBarStateController; mStatusBarWindowController = statusBarWindowController; @@ -391,6 +395,7 @@ public final class ShadeControllerImpl implements ShadeController { private void notifyVisibilityChanged(boolean visible) { mShadeVisibilityListener.visibilityChanged(visible); + mWindowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(visible); } private void notifyExpandedVisibleChanged(boolean expandedVisible) { diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeSurface.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeSurface.kt index 2532bad1d7a7..b553f0fccd91 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeSurface.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeSurface.kt @@ -99,9 +99,6 @@ interface ShadeSurface : ShadeViewController { /** Sets the view's alpha to max. */ fun resetAlpha() - /** Sets progress of the predictive back animation. */ - fun onBackProgressed(progressFraction: Float) - /** @see com.android.systemui.keyguard.ScreenLifecycle.Observer.onScreenTurningOn */ fun onScreenTurningOn() diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt index 182a676c9841..1121834f196d 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt @@ -155,6 +155,9 @@ interface ShadeViewController { /** Called when Back gesture has been committed (i.e. a back event has definitely occurred) */ fun onBackPressed() + /** Sets progress of the predictive back animation. */ + fun onBackProgressed(progressFraction: Float) + /** Sets whether the status bar launch animation is currently running. */ fun setIsLaunchAnimationRunning(running: Boolean) diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt index 09b74b213ebf..6a2bef296b6b 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt @@ -58,6 +58,7 @@ class ShadeViewControllerEmptyImpl @Inject constructor() : ShadeViewController { return false } override fun onBackPressed() {} + override fun onBackProgressed(progressFraction: Float) {} override fun setIsLaunchAnimationRunning(running: Boolean) {} override fun setAlpha(alpha: Int, animate: Boolean) {} override fun setAlphaChangeAnimationEndAction(r: Runnable) {} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java index 637637d39957..09be41b56a10 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java @@ -19,12 +19,13 @@ package com.android.systemui.statusbar.notification.dagger; import android.content.Context; import com.android.internal.jank.InteractionJankMonitor; +import com.android.systemui.CoreStartable; import com.android.systemui.R; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.UiBackground; import com.android.systemui.plugins.statusbar.StatusBarStateController; +import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor; import com.android.systemui.shade.ShadeEventsModule; -import com.android.systemui.shade.ShadeExpansionStateManager; import com.android.systemui.statusbar.NotificationListener; import com.android.systemui.statusbar.notification.NotificationActivityStarter; import com.android.systemui.statusbar.notification.NotificationLaunchAnimatorControllerProvider; @@ -76,10 +77,13 @@ import com.android.systemui.statusbar.notification.stack.ui.viewmodel.Notificati import com.android.systemui.statusbar.phone.HeadsUpManagerPhone; import com.android.systemui.statusbar.phone.KeyguardBypassController; import com.android.systemui.statusbar.phone.StatusBarNotificationActivityStarter; +import com.android.systemui.util.kotlin.JavaAdapter; import dagger.Binds; import dagger.Module; import dagger.Provides; +import dagger.multibindings.ClassKey; +import dagger.multibindings.IntoMap; import java.util.concurrent.Executor; @@ -110,6 +114,13 @@ public interface NotificationsModule { @Binds NotifGutsViewManager bindNotifGutsViewManager(NotificationGutsManager notificationGutsManager); + /** Binds {@link NotificationGutsManager} as a {@link CoreStartable}. */ + @Binds + @IntoMap + @ClassKey(NotificationGutsManager.class) + CoreStartable bindsNotificationGutsManager(NotificationGutsManager notificationGutsManager); + + /** Provides an instance of {@link VisibilityLocationProvider} */ @Binds VisibilityLocationProvider bindVisibilityLocationProvider( @@ -125,7 +136,8 @@ public interface NotificationsModule { NotificationVisibilityProvider visibilityProvider, NotifPipeline notifPipeline, StatusBarStateController statusBarStateController, - ShadeExpansionStateManager shadeExpansionStateManager, + WindowRootViewVisibilityInteractor windowRootViewVisibilityInteractor, + JavaAdapter javaAdapter, NotificationLogger.ExpansionStateLogger expansionStateLogger, NotificationPanelLogger notificationPanelLogger) { return new NotificationLogger( @@ -135,11 +147,18 @@ public interface NotificationsModule { visibilityProvider, notifPipeline, statusBarStateController, - shadeExpansionStateManager, + windowRootViewVisibilityInteractor, + javaAdapter, expansionStateLogger, notificationPanelLogger); } + /** Binds {@link NotificationLogger} as a {@link CoreStartable}. */ + @Binds + @IntoMap + @ClassKey(NotificationLogger.class) + CoreStartable bindsNotificationLogger(NotificationLogger notificationLogger); + /** Provides an instance of {@link NotificationPanelLogger} */ @SysUISingleton @Provides diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java index 26f97de95070..d2034d7a8564 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java @@ -33,10 +33,11 @@ import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.statusbar.IStatusBarService; import com.android.internal.statusbar.NotificationVisibility; +import com.android.systemui.CoreStartable; import com.android.systemui.dagger.qualifiers.UiBackground; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener; -import com.android.systemui.shade.ShadeExpansionStateManager; +import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor; import com.android.systemui.statusbar.NotificationListener; import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.notification.collection.NotifLiveDataStore; @@ -48,6 +49,7 @@ import com.android.systemui.statusbar.notification.dagger.NotificationsModule; import com.android.systemui.statusbar.notification.stack.ExpandableViewState; import com.android.systemui.statusbar.notification.stack.NotificationListContainer; import com.android.systemui.util.Compile; +import com.android.systemui.util.kotlin.JavaAdapter; import java.util.Collection; import java.util.Collections; @@ -62,7 +64,7 @@ import javax.inject.Inject; * Handles notification logging, in particular, logging which notifications are visible and which * are not. */ -public class NotificationLogger implements StateListener { +public class NotificationLogger implements StateListener, CoreStartable { static final String TAG = "NotificationLogger"; private static final boolean DEBUG = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.DEBUG); @@ -81,6 +83,8 @@ public class NotificationLogger implements StateListener { private final NotifPipeline mNotifPipeline; private final NotificationPanelLogger mNotificationPanelLogger; private final ExpansionStateLogger mExpansionStateLogger; + private final WindowRootViewVisibilityInteractor mWindowRootViewVisibilityInteractor; + private final JavaAdapter mJavaAdapter; protected Handler mHandler = new Handler(); protected IStatusBarService mBarService; @@ -88,10 +92,7 @@ public class NotificationLogger implements StateListener { private NotificationListContainer mListContainer; private final Object mDozingLock = new Object(); @GuardedBy("mDozingLock") - private Boolean mDozing = null; // Use null to indicate state is not yet known - @GuardedBy("mDozingLock") private Boolean mLockscreen = null; // Use null to indicate state is not yet known - private Boolean mPanelExpanded = null; // Use null to indicate state is not yet known private boolean mLogging = false; // Tracks notifications currently visible in mNotificationStackScroller and @@ -202,7 +203,8 @@ public class NotificationLogger implements StateListener { NotificationVisibilityProvider visibilityProvider, NotifPipeline notifPipeline, StatusBarStateController statusBarStateController, - ShadeExpansionStateManager shadeExpansionStateManager, + WindowRootViewVisibilityInteractor windowRootViewVisibilityInteractor, + JavaAdapter javaAdapter, ExpansionStateLogger expansionStateLogger, NotificationPanelLogger notificationPanelLogger) { mNotificationListener = notificationListener; @@ -214,9 +216,10 @@ public class NotificationLogger implements StateListener { ServiceManager.getService(Context.STATUS_BAR_SERVICE)); mExpansionStateLogger = expansionStateLogger; mNotificationPanelLogger = notificationPanelLogger; + mWindowRootViewVisibilityInteractor = windowRootViewVisibilityInteractor; + mJavaAdapter = javaAdapter; // Not expected to be destroyed, don't need to unsubscribe statusBarStateController.addCallback(this); - shadeExpansionStateManager.addFullExpansionListener(this::onShadeExpansionFullyChanged); registerNewPipelineListener(); } @@ -239,6 +242,22 @@ public class NotificationLogger implements StateListener { mListContainer = listContainer; } + @Override + public void start() { + mJavaAdapter.alwaysCollectFlow( + mWindowRootViewVisibilityInteractor.isLockscreenOrShadeVisibleAndInteractive(), + this::onLockscreenOrShadeVisibleAndInteractiveChanged); + } + + private void onLockscreenOrShadeVisibleAndInteractiveChanged(boolean visible) { + if (visible) { + startNotificationLogging(); + } else { + // Ensure we stop notification logging when the device isn't interactive. + stopNotificationLogging(); + } + } + public void stopNotificationLogging() { if (mLogging) { mLogging = false; @@ -257,16 +276,19 @@ public class NotificationLogger implements StateListener { } } + @GuardedBy("mDozingLock") public void startNotificationLogging() { if (!mLogging) { mLogging = true; if (DEBUG) { Log.i(TAG, "startNotificationLogging"); } + boolean lockscreen = mLockscreen != null && mLockscreen; + mNotificationPanelLogger.logPanelShown(lockscreen, getVisibleNotifications()); mListContainer.setChildLocationsChangedListener(this::onChildLocationsChanged); - // Some transitions like mVisibleToUser=false -> mVisibleToUser=true don't - // cause the scroller to emit child location events. Hence generate - // one ourselves to guarantee that we're reporting visible + // Sometimes, the transition from lockscreenOrShadeVisible=false -> + // lockscreenOrShadeVisible=true doesn't cause the scroller to emit child location + // events. Hence generate one ourselves to guarantee that we're reporting visible // notifications. // (Note that in cases where the scroller does emit events, this // additional event doesn't break anything.) @@ -274,13 +296,6 @@ public class NotificationLogger implements StateListener { } } - private void setDozing(boolean dozing) { - synchronized (mDozingLock) { - mDozing = dozing; - maybeUpdateLoggingStatus(); - } - } - private void logNotificationVisibilityChanges( Collection<NotificationVisibility> newlyVisible, Collection<NotificationVisibility> noLongerVisible) { @@ -362,39 +377,6 @@ public class NotificationLogger implements StateListener { } } - @Override - public void onDozingChanged(boolean isDozing) { - if (DEBUG) { - Log.i(TAG, "onDozingChanged: new=" + isDozing); - } - setDozing(isDozing); - } - - @GuardedBy("mDozingLock") - private void maybeUpdateLoggingStatus() { - if (mPanelExpanded == null || mDozing == null) { - if (DEBUG) { - Log.i(TAG, "Panel status unclear: panelExpandedKnown=" - + (mPanelExpanded == null) + " dozingKnown=" + (mDozing == null)); - } - return; - } - // Once we know panelExpanded and Dozing, turn logging on & off when appropriate - boolean lockscreen = mLockscreen == null ? false : mLockscreen; - if (mPanelExpanded && !mDozing) { - mNotificationPanelLogger.logPanelShown(lockscreen, getVisibleNotifications()); - if (DEBUG) { - Log.i(TAG, "Notification panel shown, lockscreen=" + lockscreen); - } - startNotificationLogging(); - } else { - if (DEBUG) { - Log.i(TAG, "Notification panel hidden, lockscreen=" + lockscreen); - } - stopNotificationLogging(); - } - } - /** * Called when the notification is expanded / collapsed. */ @@ -404,20 +386,6 @@ public class NotificationLogger implements StateListener { } @VisibleForTesting - void onShadeExpansionFullyChanged(Boolean isExpanded) { - // mPanelExpanded is initialized as null - if (mPanelExpanded == null || !mPanelExpanded.equals(isExpanded)) { - if (DEBUG) { - Log.i(TAG, "onPanelExpandedChanged: new=" + isExpanded); - } - mPanelExpanded = isExpanded; - synchronized (mDozingLock) { - maybeUpdateLoggingStatus(); - } - } - } - - @VisibleForTesting void onChildLocationsChanged() { if (mHandler.hasCallbacks(mVisibilityReporter)) { // Visibilities will be reported when the existing diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java index 6f79ea8c543b..44ead26de012 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java @@ -45,6 +45,7 @@ import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.UiEventLogger; import com.android.internal.logging.nano.MetricsProto; import com.android.settingslib.notification.ConversationIconFactory; +import com.android.systemui.CoreStartable; import com.android.systemui.R; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Background; @@ -53,6 +54,7 @@ import com.android.systemui.people.widget.PeopleSpaceWidgetManager; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin; import com.android.systemui.plugins.statusbar.StatusBarStateController; +import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor; import com.android.systemui.settings.UserContextProvider; import com.android.systemui.shade.ShadeController; import com.android.systemui.statusbar.NotificationLockscreenUserManager; @@ -69,6 +71,7 @@ import com.android.systemui.statusbar.notification.stack.NotificationListContain import com.android.systemui.statusbar.phone.CentralSurfaces; import com.android.systemui.statusbar.phone.HeadsUpManagerPhone; import com.android.systemui.statusbar.policy.DeviceProvisionedController; +import com.android.systemui.util.kotlin.JavaAdapter; import com.android.systemui.wmshell.BubblesManager; import java.util.Optional; @@ -80,7 +83,7 @@ import javax.inject.Inject; * closing guts, and keeping track of the currently exposed notification guts. */ @SysUISingleton -public class NotificationGutsManager implements NotifGutsViewManager { +public class NotificationGutsManager implements NotifGutsViewManager, CoreStartable { private static final String TAG = "NotificationGutsManager"; // Must match constant in Settings. Used to highlight preferences when linking to Settings. @@ -109,6 +112,7 @@ public class NotificationGutsManager implements NotifGutsViewManager { private final Handler mMainHandler; private final Handler mBgHandler; + private final JavaAdapter mJavaAdapter; private final Optional<BubblesManager> mBubblesManagerOptional; private Runnable mOpenRunnable; private final INotificationManager mNotificationManager; @@ -121,6 +125,7 @@ public class NotificationGutsManager implements NotifGutsViewManager { private final UserContextProvider mContextTracker; private final UiEventLogger mUiEventLogger; private final ShadeController mShadeController; + private final WindowRootViewVisibilityInteractor mWindowRootViewVisibilityInteractor; private NotifGutsViewListener mGutsListener; private final HeadsUpManagerPhone mHeadsUpManagerPhone; private final ActivityStarter mActivityStarter; @@ -129,6 +134,7 @@ public class NotificationGutsManager implements NotifGutsViewManager { public NotificationGutsManager(Context context, @Main Handler mainHandler, @Background Handler bgHandler, + JavaAdapter javaAdapter, AccessibilityManager accessibilityManager, HighPriorityProvider highPriorityProvider, INotificationManager notificationManager, @@ -143,6 +149,7 @@ public class NotificationGutsManager implements NotifGutsViewManager { UiEventLogger uiEventLogger, OnUserInteractionCallback onUserInteractionCallback, ShadeController shadeController, + WindowRootViewVisibilityInteractor windowRootViewVisibilityInteractor, NotificationLockscreenUserManager notificationLockscreenUserManager, StatusBarStateController statusBarStateController, DeviceProvisionedController deviceProvisionedController, @@ -152,6 +159,7 @@ public class NotificationGutsManager implements NotifGutsViewManager { mContext = context; mMainHandler = mainHandler; mBgHandler = bgHandler; + mJavaAdapter = javaAdapter; mAccessibilityManager = accessibilityManager; mHighPriorityProvider = highPriorityProvider; mNotificationManager = notificationManager; @@ -166,6 +174,7 @@ public class NotificationGutsManager implements NotifGutsViewManager { mUiEventLogger = uiEventLogger; mOnUserInteractionCallback = onUserInteractionCallback; mShadeController = shadeController; + mWindowRootViewVisibilityInteractor = windowRootViewVisibilityInteractor; mLockscreenUserManager = notificationLockscreenUserManager; mStatusBarStateController = statusBarStateController; mDeviceProvisionedController = deviceProvisionedController; @@ -187,6 +196,25 @@ public class NotificationGutsManager implements NotifGutsViewManager { mNotificationActivityStarter = notificationActivityStarter; } + @Override + public void start() { + mJavaAdapter.alwaysCollectFlow( + mWindowRootViewVisibilityInteractor.isLockscreenOrShadeVisible(), + this::onLockscreenShadeVisibilityChanged); + } + + private void onLockscreenShadeVisibilityChanged(boolean visible) { + if (!visible) { + closeAndSaveGuts( + /* removeLeavebehind= */ true , + /* force= */ true, + /* removeControls= */ true, + /* x= */ -1, + /* y= */ -1, + /* resetMenu= */ true); + } + } + public void onDensityOrFontScaleChanged(NotificationEntry entry) { setExposedGuts(entry.getGuts()); bindGuts(entry.getRow()); @@ -512,7 +540,7 @@ public class NotificationGutsManager implements NotifGutsViewManager { mNotificationGutsExposed.removeCallbacks(mOpenRunnable); mNotificationGutsExposed.closeControls(removeLeavebehinds, removeControls, x, y, force); } - if (resetMenu) { + if (resetMenu && mListContainer != null) { mListContainer.resetExposedMenuView(false /* animate */, true /* force */); } } 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 605b3d23be2d..42b29f8831e3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java @@ -92,17 +92,12 @@ import android.view.IWindowManager; import android.view.MotionEvent; import android.view.ThreadedRenderer; import android.view.View; -import android.view.ViewRootImpl; import android.view.WindowInsets; import android.view.WindowInsetsController.Appearance; import android.view.WindowManager; import android.view.WindowManagerGlobal; import android.view.accessibility.AccessibilityManager; import android.widget.DateTimeView; -import android.window.BackEvent; -import android.window.OnBackAnimationCallback; -import android.window.OnBackInvokedCallback; -import android.window.OnBackInvokedDispatcher; import androidx.annotation.NonNull; import androidx.lifecycle.Lifecycle; @@ -176,6 +171,7 @@ import com.android.systemui.power.domain.interactor.PowerInteractor; import com.android.systemui.qs.QSFragment; import com.android.systemui.qs.QSPanelController; import com.android.systemui.recents.ScreenPinningRequest; +import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor; import com.android.systemui.scrim.ScrimView; import com.android.systemui.settings.UserTracker; import com.android.systemui.settings.brightness.BrightnessSliderController; @@ -221,7 +217,6 @@ import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator import com.android.systemui.statusbar.notification.data.repository.NotificationExpansionRepository; import com.android.systemui.statusbar.notification.init.NotificationsController; import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider; -import com.android.systemui.statusbar.notification.logging.NotificationLogger; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.NotificationGutsManager; import com.android.systemui.statusbar.notification.stack.NotificationListContainer; @@ -462,6 +457,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { private final CentralSurfacesComponent.Factory mCentralSurfacesComponentFactory; private final PluginManager mPluginManager; private final ShadeController mShadeController; + private final WindowRootViewVisibilityInteractor mWindowRootViewVisibilityInteractor; private final InitController mInitController; private final Lazy<CameraLauncher> mCameraLauncherLazy; private final AlternateBouncerInteractor mAlternateBouncerInteractor; @@ -489,13 +485,11 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { private View mReportRejectedTouch; private final NotificationGutsManager mGutsManager; - private final NotificationLogger mNotificationLogger; private final ShadeExpansionStateManager mShadeExpansionStateManager; private final KeyguardViewMediator mKeyguardViewMediator; protected final NotificationInterruptStateProvider mNotificationInterruptStateProvider; private final BrightnessSliderController.Factory mBrightnessSliderFactory; private final FeatureFlags mFeatureFlags; - private final boolean mAnimateBack; private final FragmentService mFragmentService; private final ScreenOffAnimationController mScreenOffAnimationController; private final WallpaperController mWallpaperController; @@ -508,13 +502,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { private CentralSurfacesComponent mCentralSurfacesComponent; - /** - * This keeps track of whether we have (or haven't) registered the predictive back callback. - * Since we can have visible -> visible transitions, we need to avoid - * double-registering (or double-unregistering) our callback. - */ - private boolean mIsBackCallbackRegistered = false; - /** @see android.view.WindowInsetsController#setSystemBarsAppearance(int, int) */ private @Appearance int mAppearance; @@ -640,31 +627,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { private final InteractionJankMonitor mJankMonitor; - /** Existing callback that handles back gesture invoked for the Shade. */ - private final OnBackInvokedCallback mOnBackInvokedCallback; - - /** - * New callback that handles back gesture invoked, cancel, progress - * and provides feedback via Shade animation. - * (enabled via the WM_SHADE_ANIMATE_BACK_GESTURE flag) - */ - private final OnBackAnimationCallback mOnBackAnimationCallback = new OnBackAnimationCallback() { - @Override - public void onBackInvoked() { - mBackActionInteractor.onBackRequested(); - } - - @Override - public void onBackProgressed(BackEvent event) { - if (mBackActionInteractor.shouldBackBeHandled()) { - if (mShadeSurface.canBeCollapsed()) { - float fraction = event.getProgress(); - mShadeSurface.onBackProgressed(fraction); - } - } - } - }; - /** * Public constructor for CentralSurfaces. * @@ -694,7 +656,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { FalsingCollector falsingCollector, BroadcastDispatcher broadcastDispatcher, NotificationGutsManager notificationGutsManager, - NotificationLogger notificationLogger, NotificationInterruptStateProvider notificationInterruptStateProvider, ShadeExpansionStateManager shadeExpansionStateManager, KeyguardViewMediator keyguardViewMediator, @@ -745,6 +706,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { Lazy<CentralSurfacesCommandQueueCallbacks> commandQueueCallbacksLazy, PluginManager pluginManager, ShadeController shadeController, + WindowRootViewVisibilityInteractor windowRootViewVisibilityInteractor, StatusBarKeyguardViewManager statusBarKeyguardViewManager, ViewMediatorCallback viewMediatorCallback, InitController initController, @@ -803,7 +765,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { mFalsingManager = falsingManager; mBroadcastDispatcher = broadcastDispatcher; mGutsManager = notificationGutsManager; - mNotificationLogger = notificationLogger; mNotificationInterruptStateProvider = notificationInterruptStateProvider; mShadeExpansionStateManager = shadeExpansionStateManager; mKeyguardViewMediator = keyguardViewMediator; @@ -856,6 +817,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { mCommandQueueCallbacksLazy = commandQueueCallbacksLazy; mPluginManager = pluginManager; mShadeController = shadeController; + mWindowRootViewVisibilityInteractor = windowRootViewVisibilityInteractor; mStatusBarKeyguardViewManager = statusBarKeyguardViewManager; mKeyguardViewMediatorCallback = viewMediatorCallback; mInitController = initController; @@ -926,14 +888,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { if (mFeatureFlags.isEnabled(Flags.WM_ENABLE_PREDICTIVE_BACK_SYSUI)) { mContext.getApplicationInfo().setEnableOnBackInvokedCallback(true); } - // Based on teamfood flag, enable predictive back animation for the Shade. - mAnimateBack = mFeatureFlags.isEnabled(Flags.WM_SHADE_ANIMATE_BACK_GESTURE); - mOnBackInvokedCallback = () -> { - if (DEBUG) { - Log.d(TAG, "mOnBackInvokedCallback() called"); - } - mBackActionInteractor.onBackRequested(); - }; } private void initBubbles(Bubbles bubbles) { @@ -1171,7 +1125,10 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { new FoldStateListener(mContext, this::onFoldedStateChanged)); } - @VisibleForTesting + /** + * @deprecated use {@link + * WindowRootViewVisibilityInteractor.isLockscreenOrShadeVisible} instead. + */ @VisibleForTesting void initShadeVisibilityListener() { mShadeController.setVisibilityListener(new ShadeController.ShadeVisibilityListener() { @Override @@ -1563,6 +1520,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { mNotifListContainer, mStackScrollerController.getNotifStackController(), mNotificationActivityStarter); + mWindowRootViewVisibilityInteractor.setUp(mPresenter, mNotificationsController); } /** @@ -2149,82 +2107,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { com.android.systemui.R.dimen.physical_power_button_center_screen_location_y)); } - protected void handleVisibleToUserChanged(boolean visibleToUser) { - if (visibleToUser) { - onVisibleToUser(); - mNotificationLogger.startNotificationLogging(); - - if (!mIsBackCallbackRegistered) { - ViewRootImpl viewRootImpl = getViewRootImpl(); - if (viewRootImpl != null) { - viewRootImpl.getOnBackInvokedDispatcher() - .registerOnBackInvokedCallback(OnBackInvokedDispatcher.PRIORITY_DEFAULT, - mAnimateBack ? mOnBackAnimationCallback - : mOnBackInvokedCallback); - mIsBackCallbackRegistered = true; - if (DEBUG) Log.d(TAG, "is now VISIBLE to user AND callback registered"); - } - } else { - if (DEBUG) Log.d(TAG, "is now VISIBLE to user, BUT callback ALREADY unregistered"); - } - } else { - mNotificationLogger.stopNotificationLogging(); - onInvisibleToUser(); - - if (mIsBackCallbackRegistered) { - ViewRootImpl viewRootImpl = getViewRootImpl(); - if (viewRootImpl != null) { - viewRootImpl.getOnBackInvokedDispatcher() - .unregisterOnBackInvokedCallback( - mAnimateBack ? mOnBackAnimationCallback - : mOnBackInvokedCallback); - mIsBackCallbackRegistered = false; - if (DEBUG) Log.d(TAG, "is NOT VISIBLE to user, AND callback unregistered"); - } - } else { - if (DEBUG) { - Log.d(TAG, - "is NOT VISIBLE to user, BUT NO callback (or callback ALREADY " - + "unregistered)"); - } - } - } - } - - void onVisibleToUser() { - /* The LEDs are turned off when the notification panel is shown, even just a little bit. - * See also CentralSurfaces.setPanelExpanded for another place where we attempt to do - * this. - */ - boolean pinnedHeadsUp = mHeadsUpManager.hasPinnedHeadsUp(); - boolean clearNotificationEffects = - !mPresenter.isPresenterFullyCollapsed() && (mState == StatusBarState.SHADE - || mState == StatusBarState.SHADE_LOCKED); - int notificationLoad = mNotificationsController.getActiveNotificationsCount(); - if (pinnedHeadsUp && mPresenter.isPresenterFullyCollapsed()) { - notificationLoad = 1; - } - final int finalNotificationLoad = notificationLoad; - mUiBgExecutor.execute(() -> { - try { - mBarService.onPanelRevealed(clearNotificationEffects, - finalNotificationLoad); - } catch (RemoteException ex) { - // Won't fail unless the world has ended. - } - }); - } - - void onInvisibleToUser() { - mUiBgExecutor.execute(() -> { - try { - mBarService.onPanelHidden(); - } catch (RemoteException ex) { - // Won't fail unless the world has ended. - } - }); - } - private void logStateToEventlog() { boolean isShowing = mKeyguardStateController.isShowing(); boolean isOccluded = mKeyguardStateController.isOccluded(); @@ -2708,11 +2590,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { mNavigationBarController.showPinningEscapeToast(mDisplayId); } - protected ViewRootImpl getViewRootImpl() { - View root = mNotificationShadeWindowController.getWindowRootView(); - if (root != null) return root.getViewRootImpl(); - return null; - } /** * Propagation of the bouncer state, indicating that it's fully visible. */ @@ -2759,7 +2636,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { releaseGestureWakeLock(); mLaunchCameraWhenFinishedWaking = false; mDeviceInteractive = false; - updateVisibleToUser(); updateNotificationPanelTouchState(); getNotificationShadeWindowViewController().cancelCurrentTouch(); @@ -2858,7 +2734,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { /* wakingUp= */ true, mShouldDelayWakeUpAnimation); - updateVisibleToUser(); updateIsKeyguard(); mShouldDelayLockscreenTransitionFromAod = mDozeParameters.getAlwaysOn() && mFeatureFlags.isEnabled( @@ -3187,9 +3062,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { protected boolean mVisible; - // mScreenOnFromKeyguard && mVisible. - private boolean mVisibleToUser; - protected DevicePolicyManager mDevicePolicyManager; private final PowerManager mPowerManager; protected StatusBarKeyguardViewManager mStatusBarKeyguardViewManager; @@ -3313,21 +3185,8 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { if (visible) { DejankUtils.notifyRendererOfExpensiveFrame( getNotificationShadeWindowView(), "onShadeVisibilityChanged"); - } else { - mGutsManager.closeAndSaveGuts(true /* removeLeavebehind */, true /* force */, - true /* removeControls */, -1 /* x */, -1 /* y */, true /* resetMenu */); } } - updateVisibleToUser(); - } - - protected void updateVisibleToUser() { - boolean oldVisibleToUser = mVisibleToUser; - mVisibleToUser = mVisible && mDeviceInteractive; - - if (oldVisibleToUser != mVisibleToUser) { - handleVisibleToUserChanged(mVisibleToUser); - } } /** @@ -3503,7 +3362,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { // If we're visible and switched to SHADE_LOCKED (the user dragged // down on the lockscreen), clear notification LED, vibration, // ringing. - // Other transitions are covered in handleVisibleToUserChanged(). + // Other transitions are covered in WindowRootViewVisibilityInteractor. if (mVisible && (newState == StatusBarState.SHADE_LOCKED || mStatusBarStateController.goingToFullShade())) { clearNotificationEffects(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt index 88c710aedf93..ddb482f0623c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt @@ -16,18 +16,46 @@ package com.android.systemui.back.domain.interactor +import android.view.ViewRootImpl +import android.window.BackEvent +import android.window.BackEvent.EDGE_LEFT +import android.window.OnBackAnimationCallback +import android.window.OnBackInvokedCallback +import android.window.OnBackInvokedDispatcher +import android.window.WindowOnBackInvokedDispatcher import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest +import com.android.internal.statusbar.IStatusBarService import com.android.systemui.SysuiTestCase +import com.android.systemui.flags.FakeFeatureFlags +import com.android.systemui.flags.Flags +import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository +import com.android.systemui.keyguard.shared.model.WakeSleepReason +import com.android.systemui.keyguard.shared.model.WakefulnessModel +import com.android.systemui.keyguard.shared.model.WakefulnessState import com.android.systemui.plugins.statusbar.StatusBarStateController +import com.android.systemui.scene.data.repository.WindowRootViewVisibilityRepository +import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor +import com.android.systemui.scene.ui.view.WindowRootView import com.android.systemui.shade.QuickSettingsController import com.android.systemui.shade.ShadeController import com.android.systemui.shade.ShadeViewController +import com.android.systemui.statusbar.NotificationShadeWindowController import com.android.systemui.statusbar.StatusBarState import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager +import com.android.systemui.statusbar.policy.HeadsUpManager +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.eq import com.android.systemui.util.mockito.whenever +import com.android.systemui.util.time.FakeSystemClock +import com.google.common.truth.Truth.assertThat import junit.framework.Assert.assertFalse import junit.framework.Assert.assertTrue +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runCurrent import org.junit.Before import org.junit.Rule import org.junit.Test @@ -41,7 +69,12 @@ import org.mockito.junit.MockitoJUnit @SmallTest @RunWith(AndroidJUnit4::class) +@OptIn(ExperimentalCoroutinesApi::class) class BackActionInteractorTest : SysuiTestCase() { + private val testScope = TestScope() + private val featureFlags = FakeFeatureFlags() + private val executor = FakeExecutor(FakeSystemClock()) + @JvmField @Rule var mockitoRule = MockitoJUnit.rule() @Mock private lateinit var statusBarStateController: StatusBarStateController @@ -49,18 +82,42 @@ class BackActionInteractorTest : SysuiTestCase() { @Mock private lateinit var shadeController: ShadeController @Mock private lateinit var qsController: QuickSettingsController @Mock private lateinit var shadeViewController: ShadeViewController + @Mock private lateinit var notificationShadeWindowController: NotificationShadeWindowController + @Mock private lateinit var windowRootView: WindowRootView + @Mock private lateinit var viewRootImpl: ViewRootImpl + @Mock private lateinit var onBackInvokedDispatcher: WindowOnBackInvokedDispatcher + @Mock private lateinit var iStatusBarService: IStatusBarService + @Mock private lateinit var headsUpManager: HeadsUpManager - private lateinit var backActionInteractor: BackActionInteractor + private val keyguardRepository = FakeKeyguardRepository() + private val windowRootViewVisibilityInteractor: WindowRootViewVisibilityInteractor by lazy { + WindowRootViewVisibilityInteractor( + testScope.backgroundScope, + WindowRootViewVisibilityRepository(iStatusBarService, executor), + keyguardRepository, + headsUpManager, + ) + } - @Before - fun setup() { - backActionInteractor = - BackActionInteractor( + private val backActionInteractor: BackActionInteractor by lazy { + BackActionInteractor( + testScope.backgroundScope, statusBarStateController, statusBarKeyguardViewManager, shadeController, + notificationShadeWindowController, + windowRootViewVisibilityInteractor, + featureFlags, ) - backActionInteractor.setup(qsController, shadeViewController) + .apply { this.setup(qsController, shadeViewController) } + } + + @Before + fun setUp() { + featureFlags.set(Flags.WM_SHADE_ANIMATE_BACK_GESTURE, false) + whenever(notificationShadeWindowController.windowRootView).thenReturn(windowRootView) + whenever(windowRootView.viewRootImpl).thenReturn(viewRootImpl) + whenever(viewRootImpl.onBackInvokedDispatcher).thenReturn(onBackInvokedDispatcher) } @Test @@ -117,4 +174,139 @@ class BackActionInteractorTest : SysuiTestCase() { verify(statusBarKeyguardViewManager, never()).onBackPressed() verify(shadeViewController, never()).animateCollapseQs(anyBoolean()) } + + @Test + fun shadeVisibleAndDeviceAwake_callbackRegistered() { + backActionInteractor.start() + windowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(true) + setWakefulness(WakefulnessState.AWAKE) + + testScope.runCurrent() + + verify(onBackInvokedDispatcher) + .registerOnBackInvokedCallback(eq(OnBackInvokedDispatcher.PRIORITY_DEFAULT), any()) + } + + @Test + fun noWindowRootView_noCrashAttemptingCallbackRegistration() { + whenever(notificationShadeWindowController.windowRootView).thenReturn(null) + + backActionInteractor.start() + windowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(true) + setWakefulness(WakefulnessState.AWAKE) + + testScope.runCurrent() + // No assert necessary, just testing no crash + } + + @Test + fun shadeNotVisible_callbackUnregistered() { + backActionInteractor.start() + windowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(true) + setWakefulness(WakefulnessState.AWAKE) + val callback = getBackInvokedCallback() + + windowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(false) + testScope.runCurrent() + + verify(onBackInvokedDispatcher).unregisterOnBackInvokedCallback(callback) + } + + @Test + fun deviceAsleep_callbackUnregistered() { + backActionInteractor.start() + windowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(true) + setWakefulness(WakefulnessState.AWAKE) + val callback = getBackInvokedCallback() + + setWakefulness(WakefulnessState.ASLEEP) + testScope.runCurrent() + + verify(onBackInvokedDispatcher).unregisterOnBackInvokedCallback(callback) + } + + @Test + fun animationFlagOff_onBackInvoked_keyguardNotified() { + backActionInteractor.start() + featureFlags.set(Flags.WM_SHADE_ANIMATE_BACK_GESTURE, false) + windowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(true) + setWakefulness(WakefulnessState.AWAKE) + val callback = getBackInvokedCallback() + whenever(statusBarKeyguardViewManager.canHandleBackPressed()).thenReturn(true) + + callback.onBackInvoked() + + verify(statusBarKeyguardViewManager).onBackPressed() + } + + @Test + fun animationFlagOn_onBackInvoked_keyguardNotified() { + featureFlags.set(Flags.WM_SHADE_ANIMATE_BACK_GESTURE, true) + backActionInteractor.start() + windowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(true) + setWakefulness(WakefulnessState.AWAKE) + val callback = getBackInvokedCallback() + whenever(statusBarKeyguardViewManager.canHandleBackPressed()).thenReturn(true) + + callback.onBackInvoked() + + verify(statusBarKeyguardViewManager).onBackPressed() + } + + @Test + fun animationFlagOn_callbackIsAnimationCallback() { + featureFlags.set(Flags.WM_SHADE_ANIMATE_BACK_GESTURE, true) + backActionInteractor.start() + windowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(true) + setWakefulness(WakefulnessState.AWAKE) + + val callback = getBackInvokedCallback() + + assertThat(callback).isInstanceOf(OnBackAnimationCallback::class.java) + } + + @Test + fun onBackProgressed_shadeCannotBeCollapsed_shadeViewControllerNotNotified() { + featureFlags.set(Flags.WM_SHADE_ANIMATE_BACK_GESTURE, true) + backActionInteractor.start() + windowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(true) + setWakefulness(WakefulnessState.AWAKE) + val callback = getBackInvokedCallback() as OnBackAnimationCallback + + whenever(shadeViewController.canBeCollapsed()).thenReturn(false) + + callback.onBackProgressed(createBackEvent(0.3f)) + + verify(shadeViewController, never()).onBackProgressed(0.3f) + } + + @Test + fun onBackProgressed_shadeCanBeCollapsed_shadeViewControllerNotified() { + featureFlags.set(Flags.WM_SHADE_ANIMATE_BACK_GESTURE, true) + backActionInteractor.start() + windowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(true) + setWakefulness(WakefulnessState.AWAKE) + val callback = getBackInvokedCallback() as OnBackAnimationCallback + + whenever(shadeViewController.canBeCollapsed()).thenReturn(true) + + callback.onBackProgressed(createBackEvent(0.4f)) + + verify(shadeViewController).onBackProgressed(0.4f) + } + + private fun getBackInvokedCallback(): OnBackInvokedCallback { + testScope.runCurrent() + val captor = argumentCaptor<OnBackInvokedCallback>() + verify(onBackInvokedDispatcher).registerOnBackInvokedCallback(any(), captor.capture()) + return captor.value!! + } + + private fun createBackEvent(progress: Float): BackEvent = + BackEvent(/* touchX= */ 0f, /* touchY= */ 0f, progress, /* swipeEdge= */ EDGE_LEFT) + + private fun setWakefulness(state: WakefulnessState) { + val model = WakefulnessModel(state, WakeSleepReason.OTHER, WakeSleepReason.OTHER) + keyguardRepository.setWakefulnessModel(model) + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt index 5ead16bdc10f..2691860bc25b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt @@ -46,6 +46,7 @@ import com.android.systemui.statusbar.phone.BiometricUnlockController import com.android.systemui.statusbar.phone.DozeParameters import com.android.systemui.statusbar.phone.KeyguardBypassController import com.android.systemui.statusbar.policy.KeyguardStateController +import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.argumentCaptor import com.android.systemui.util.mockito.whenever import com.android.systemui.util.mockito.withArgCaptor @@ -322,20 +323,20 @@ class KeyguardRepositoryImplTest : SysuiTestCase() { val captor = argumentCaptor<StatusBarStateController.StateListener>() runCurrent() - verify(statusBarStateController).addCallback(captor.capture()) + verify(statusBarStateController, atLeastOnce()).addCallback(captor.capture()) - captor.value.onDozeAmountChanged(0.433f, 0.4f) + captor.allValues.forEach { it.onDozeAmountChanged(0.433f, 0.4f) } runCurrent() - captor.value.onDozeAmountChanged(0.498f, 0.5f) + captor.allValues.forEach { it.onDozeAmountChanged(0.498f, 0.5f) } runCurrent() - captor.value.onDozeAmountChanged(0.661f, 0.65f) + captor.allValues.forEach { it.onDozeAmountChanged(0.661f, 0.65f) } runCurrent() assertThat(values).isEqualTo(listOf(0f, 0.433f, 0.498f, 0.661f)) job.cancel() runCurrent() - verify(statusBarStateController).removeCallback(captor.value) + verify(statusBarStateController).removeCallback(any()) } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/data/repository/WindowRootViewVisibilityRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/data/repository/WindowRootViewVisibilityRepositoryTest.kt new file mode 100644 index 000000000000..2187f3657848 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/scene/data/repository/WindowRootViewVisibilityRepositoryTest.kt @@ -0,0 +1,67 @@ +/* + * 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.scene.data.repository + +import androidx.test.filters.SmallTest +import com.android.internal.statusbar.IStatusBarService +import com.android.systemui.SysuiTestCase +import com.android.systemui.util.concurrency.FakeExecutor +import com.android.systemui.util.mockito.mock +import com.android.systemui.util.time.FakeSystemClock +import com.google.common.truth.Truth.assertThat +import org.junit.Test +import org.mockito.Mockito.verify + +@SmallTest +class WindowRootViewVisibilityRepositoryTest : SysuiTestCase() { + private val iStatusBarService = mock<IStatusBarService>() + private val executor = FakeExecutor(FakeSystemClock()) + private val underTest = WindowRootViewVisibilityRepository(iStatusBarService, executor) + + @Test + fun isLockscreenOrShadeVisible_true() { + underTest.setIsLockscreenOrShadeVisible(true) + + assertThat(underTest.isLockscreenOrShadeVisible.value).isTrue() + } + + @Test + fun isLockscreenOrShadeVisible_false() { + underTest.setIsLockscreenOrShadeVisible(false) + + assertThat(underTest.isLockscreenOrShadeVisible.value).isFalse() + } + + @Test + fun onLockscreenOrShadeInteractive_statusBarServiceNotified() { + underTest.onLockscreenOrShadeInteractive( + shouldClearNotificationEffects = true, + notificationCount = 3, + ) + executor.runAllReady() + + verify(iStatusBarService).onPanelRevealed(true, 3) + } + + @Test + fun onLockscreenOrShadeNotInteractive_statusBarServiceNotified() { + underTest.onLockscreenOrShadeNotInteractive() + executor.runAllReady() + + verify(iStatusBarService).onPanelHidden() + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractorTest.kt new file mode 100644 index 000000000000..f304435b7a7a --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractorTest.kt @@ -0,0 +1,346 @@ +/* + * 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.scene.domain.interactor + +import androidx.test.filters.SmallTest +import com.android.internal.statusbar.IStatusBarService +import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository +import com.android.systemui.keyguard.shared.model.StatusBarState +import com.android.systemui.keyguard.shared.model.WakeSleepReason +import com.android.systemui.keyguard.shared.model.WakefulnessModel +import com.android.systemui.keyguard.shared.model.WakefulnessState +import com.android.systemui.scene.data.repository.WindowRootViewVisibilityRepository +import com.android.systemui.statusbar.NotificationPresenter +import com.android.systemui.statusbar.notification.init.NotificationsController +import com.android.systemui.statusbar.policy.HeadsUpManager +import com.android.systemui.util.concurrency.FakeExecutor +import com.android.systemui.util.mockito.any +import com.android.systemui.util.mockito.argumentCaptor +import com.android.systemui.util.mockito.mock +import com.android.systemui.util.mockito.whenever +import com.android.systemui.util.time.FakeSystemClock +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Test +import org.mockito.Mockito.reset +import org.mockito.Mockito.verify + +@SmallTest +@OptIn(ExperimentalCoroutinesApi::class) +class WindowRootViewVisibilityInteractorTest : SysuiTestCase() { + + private val testScope = TestScope() + private val iStatusBarService = mock<IStatusBarService>() + private val executor = FakeExecutor(FakeSystemClock()) + private val windowRootViewVisibilityRepository = + WindowRootViewVisibilityRepository(iStatusBarService, executor) + private val keyguardRepository = FakeKeyguardRepository() + private val headsUpManager = mock<HeadsUpManager>() + private val notificationPresenter = mock<NotificationPresenter>() + private val notificationsController = mock<NotificationsController>() + + private val underTest = + WindowRootViewVisibilityInteractor( + testScope.backgroundScope, + windowRootViewVisibilityRepository, + keyguardRepository, + headsUpManager, + ) + .apply { setUp(notificationPresenter, notificationsController) } + + @Test + fun isLockscreenOrShadeVisible_true() { + underTest.setIsLockscreenOrShadeVisible(true) + + assertThat(underTest.isLockscreenOrShadeVisible.value).isTrue() + } + + @Test + fun isLockscreenOrShadeVisible_false() { + underTest.setIsLockscreenOrShadeVisible(false) + + assertThat(underTest.isLockscreenOrShadeVisible.value).isFalse() + } + + @Test + fun isLockscreenOrShadeVisible_matchesRepo() { + windowRootViewVisibilityRepository.setIsLockscreenOrShadeVisible(true) + + assertThat(underTest.isLockscreenOrShadeVisible.value).isTrue() + + windowRootViewVisibilityRepository.setIsLockscreenOrShadeVisible(false) + + assertThat(underTest.isLockscreenOrShadeVisible.value).isFalse() + } + + @Test + fun isLockscreenOrShadeVisibleAndInteractive_notVisible_false() = + testScope.runTest { + val actual by collectLastValue(underTest.isLockscreenOrShadeVisibleAndInteractive) + setWakefulness(WakefulnessState.AWAKE) + + underTest.setIsLockscreenOrShadeVisible(false) + + assertThat(actual).isFalse() + } + + @Test + fun isLockscreenOrShadeVisibleAndInteractive_deviceAsleep_false() = + testScope.runTest { + val actual by collectLastValue(underTest.isLockscreenOrShadeVisibleAndInteractive) + underTest.setIsLockscreenOrShadeVisible(true) + + setWakefulness(WakefulnessState.ASLEEP) + + assertThat(actual).isFalse() + } + + @Test + fun isLockscreenOrShadeVisibleAndInteractive_visibleAndAwake_true() = + testScope.runTest { + val actual by collectLastValue(underTest.isLockscreenOrShadeVisibleAndInteractive) + + underTest.setIsLockscreenOrShadeVisible(true) + setWakefulness(WakefulnessState.AWAKE) + + assertThat(actual).isTrue() + } + + @Test + fun isLockscreenOrShadeVisibleAndInteractive_visibleAndStartingToWake_true() = + testScope.runTest { + val actual by collectLastValue(underTest.isLockscreenOrShadeVisibleAndInteractive) + + underTest.setIsLockscreenOrShadeVisible(true) + setWakefulness(WakefulnessState.STARTING_TO_WAKE) + + assertThat(actual).isTrue() + } + + @Test + fun isLockscreenOrShadeVisibleAndInteractive_visibleAndStartingToSleep_true() = + testScope.runTest { + val actual by collectLastValue(underTest.isLockscreenOrShadeVisibleAndInteractive) + + underTest.setIsLockscreenOrShadeVisible(true) + setWakefulness(WakefulnessState.STARTING_TO_SLEEP) + + assertThat(actual).isTrue() + } + + @Test + fun lockscreenShadeInteractive_statusBarServiceNotified() = + testScope.runTest { + underTest.start() + + makeLockscreenShadeVisible() + testScope.runCurrent() + executor.runAllReady() + + verify(iStatusBarService).onPanelRevealed(any(), any()) + } + + @Test + fun lockscreenShadeNotInteractive_statusBarServiceNotified() = + testScope.runTest { + underTest.start() + + // First, make the shade visible + makeLockscreenShadeVisible() + testScope.runCurrent() + reset(iStatusBarService) + + // WHEN lockscreen or shade is no longer visible + underTest.setIsLockscreenOrShadeVisible(false) + testScope.runCurrent() + executor.runAllReady() + + // THEN status bar service is notified + verify(iStatusBarService).onPanelHidden() + } + + @Test + fun lockscreenShadeInteractive_presenterCollapsed_notifEffectsNotCleared() = + testScope.runTest { + underTest.start() + keyguardRepository.setStatusBarState(StatusBarState.SHADE) + + whenever(notificationPresenter.isPresenterFullyCollapsed).thenReturn(true) + + makeLockscreenShadeVisible() + + val shouldClearNotifEffects = argumentCaptor<Boolean>() + verify(iStatusBarService).onPanelRevealed(shouldClearNotifEffects.capture(), any()) + assertThat(shouldClearNotifEffects.value).isFalse() + } + + @Test + fun lockscreenShadeInteractive_nullPresenter_notifEffectsNotCleared() = + testScope.runTest { + underTest.start() + keyguardRepository.setStatusBarState(StatusBarState.SHADE) + + underTest.setUp(presenter = null, notificationsController) + + makeLockscreenShadeVisible() + + val shouldClearNotifEffects = argumentCaptor<Boolean>() + verify(iStatusBarService).onPanelRevealed(shouldClearNotifEffects.capture(), any()) + assertThat(shouldClearNotifEffects.value).isFalse() + } + + @Test + fun lockscreenShadeInteractive_stateKeyguard_notifEffectsNotCleared() = + testScope.runTest { + underTest.start() + whenever(notificationPresenter.isPresenterFullyCollapsed).thenReturn(false) + + keyguardRepository.setStatusBarState(StatusBarState.KEYGUARD) + + makeLockscreenShadeVisible() + + val shouldClearNotifEffects = argumentCaptor<Boolean>() + verify(iStatusBarService).onPanelRevealed(shouldClearNotifEffects.capture(), any()) + assertThat(shouldClearNotifEffects.value).isFalse() + } + + @Test + fun lockscreenShadeInteractive_stateShade_presenterNotCollapsed_notifEffectsCleared() = + testScope.runTest { + underTest.start() + whenever(notificationPresenter.isPresenterFullyCollapsed).thenReturn(false) + + keyguardRepository.setStatusBarState(StatusBarState.SHADE) + + makeLockscreenShadeVisible() + + val shouldClearNotifEffects = argumentCaptor<Boolean>() + verify(iStatusBarService).onPanelRevealed(shouldClearNotifEffects.capture(), any()) + assertThat(shouldClearNotifEffects.value).isTrue() + } + + @Test + fun lockscreenShadeInteractive_stateShadeLocked_presenterNotCollapsed_notifEffectsCleared() = + testScope.runTest { + underTest.start() + whenever(notificationPresenter.isPresenterFullyCollapsed).thenReturn(false) + + keyguardRepository.setStatusBarState(StatusBarState.SHADE_LOCKED) + + makeLockscreenShadeVisible() + + val shouldClearNotifEffects = argumentCaptor<Boolean>() + verify(iStatusBarService).onPanelRevealed(shouldClearNotifEffects.capture(), any()) + assertThat(shouldClearNotifEffects.value).isTrue() + } + + @Test + fun lockscreenShadeInteractive_hasHeadsUpAndNotifPresenterCollapsed_notifCountOne() = + testScope.runTest { + underTest.start() + + whenever(headsUpManager.hasPinnedHeadsUp()).thenReturn(true) + whenever(notificationPresenter.isPresenterFullyCollapsed).thenReturn(true) + whenever(notificationsController.getActiveNotificationsCount()).thenReturn(4) + + makeLockscreenShadeVisible() + + val notifCount = argumentCaptor<Int>() + verify(iStatusBarService).onPanelRevealed(any(), notifCount.capture()) + assertThat(notifCount.value).isEqualTo(1) + } + + @Test + fun lockscreenShadeInteractive_hasHeadsUpAndNullPresenter_notifCountOne() = + testScope.runTest { + underTest.start() + + whenever(headsUpManager.hasPinnedHeadsUp()).thenReturn(true) + underTest.setUp(presenter = null, notificationsController) + + makeLockscreenShadeVisible() + + val notifCount = argumentCaptor<Int>() + verify(iStatusBarService).onPanelRevealed(any(), notifCount.capture()) + assertThat(notifCount.value).isEqualTo(1) + } + + @Test + fun lockscreenShadeInteractive_noHeadsUp_notifCountMatchesNotifController() = + testScope.runTest { + underTest.start() + whenever(notificationPresenter.isPresenterFullyCollapsed).thenReturn(true) + + whenever(headsUpManager.hasPinnedHeadsUp()).thenReturn(false) + whenever(notificationsController.getActiveNotificationsCount()).thenReturn(9) + + makeLockscreenShadeVisible() + + val notifCount = argumentCaptor<Int>() + verify(iStatusBarService).onPanelRevealed(any(), notifCount.capture()) + assertThat(notifCount.value).isEqualTo(9) + } + + @Test + fun lockscreenShadeInteractive_notifPresenterNotCollapsed_notifCountMatchesNotifController() = + testScope.runTest { + underTest.start() + whenever(headsUpManager.hasPinnedHeadsUp()).thenReturn(true) + + whenever(notificationPresenter.isPresenterFullyCollapsed).thenReturn(false) + whenever(notificationsController.getActiveNotificationsCount()).thenReturn(8) + + makeLockscreenShadeVisible() + + val notifCount = argumentCaptor<Int>() + verify(iStatusBarService).onPanelRevealed(any(), notifCount.capture()) + assertThat(notifCount.value).isEqualTo(8) + } + + @Test + fun lockscreenShadeInteractive_noHeadsUp_noNotifController_notifCountZero() = + testScope.runTest { + underTest.start() + whenever(notificationPresenter.isPresenterFullyCollapsed).thenReturn(true) + + whenever(headsUpManager.hasPinnedHeadsUp()).thenReturn(false) + underTest.setUp(notificationPresenter, notificationsController = null) + + makeLockscreenShadeVisible() + + val notifCount = argumentCaptor<Int>() + verify(iStatusBarService).onPanelRevealed(any(), notifCount.capture()) + assertThat(notifCount.value).isEqualTo(0) + } + + private fun makeLockscreenShadeVisible() { + underTest.setIsLockscreenOrShadeVisible(true) + setWakefulness(WakefulnessState.AWAKE) + testScope.runCurrent() + executor.runAllReady() + } + + private fun setWakefulness(state: WakefulnessState) { + val model = WakefulnessModel(state, WakeSleepReason.OTHER, WakeSleepReason.OTHER) + keyguardRepository.setWakefulnessModel(model) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeControllerImplTest.kt index 6a14a005b0f0..bf2d6a6f2f16 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeControllerImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeControllerImplTest.kt @@ -20,20 +20,28 @@ import android.testing.AndroidTestingRunner import android.view.Display import android.view.WindowManager import androidx.test.filters.SmallTest +import com.android.internal.statusbar.IStatusBarService +import com.android.keyguard.TestScopeProvider import com.android.systemui.SysuiTestCase import com.android.systemui.assist.AssistManager +import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository import com.android.systemui.log.LogBuffer import com.android.systemui.plugins.statusbar.StatusBarStateController +import com.android.systemui.scene.data.repository.WindowRootViewVisibilityRepository +import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor import com.android.systemui.statusbar.CommandQueue import com.android.systemui.statusbar.NotificationShadeWindowController import com.android.systemui.statusbar.notification.row.NotificationGutsManager import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager import com.android.systemui.statusbar.policy.DeviceProvisionedController +import com.android.systemui.statusbar.policy.HeadsUpManager import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.statusbar.window.StatusBarWindowController import com.android.systemui.util.concurrency.FakeExecutor +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 dagger.Lazy import org.junit.Before import org.junit.Test @@ -47,6 +55,8 @@ import org.mockito.MockitoAnnotations @RunWith(AndroidTestingRunner::class) @SmallTest class ShadeControllerImplTest : SysuiTestCase() { + private val executor = FakeExecutor(FakeSystemClock()) + @Mock private lateinit var commandQueue: CommandQueue @Mock private lateinit var keyguardStateController: KeyguardStateController @Mock private lateinit var statusBarStateController: StatusBarStateController @@ -61,6 +71,17 @@ class ShadeControllerImplTest : SysuiTestCase() { @Mock private lateinit var nswvc: NotificationShadeWindowViewController @Mock private lateinit var display: Display @Mock private lateinit var touchLog: LogBuffer + @Mock private lateinit var iStatusBarService: IStatusBarService + @Mock private lateinit var headsUpManager: HeadsUpManager + + private val windowRootViewVisibilityInteractor: WindowRootViewVisibilityInteractor by lazy { + WindowRootViewVisibilityInteractor( + TestScopeProvider.getTestScope(), + WindowRootViewVisibilityRepository(iStatusBarService, executor), + FakeKeyguardRepository(), + headsUpManager, + ) + } private lateinit var shadeController: ShadeControllerImpl @@ -74,6 +95,7 @@ class ShadeControllerImplTest : SysuiTestCase() { commandQueue, FakeExecutor(FakeSystemClock()), touchLog, + windowRootViewVisibilityInteractor, keyguardStateController, statusBarStateController, statusBarKeyguardViewManager, @@ -86,6 +108,7 @@ class ShadeControllerImplTest : SysuiTestCase() { Lazy { gutsManager }, ) shadeController.setNotificationShadeWindowViewController(nswvc) + shadeController.setVisibilityListener(mock()) } @Test @@ -133,4 +156,24 @@ class ShadeControllerImplTest : SysuiTestCase() { // VERIFY that cancelCurrentTouch is NOT called verify(nswvc, never()).cancelCurrentTouch() } + + @Test + fun visible_changesToTrue_windowInteractorUpdated() { + shadeController.makeExpandedVisible(true) + + assertThat(windowRootViewVisibilityInteractor.isLockscreenOrShadeVisible.value).isTrue() + } + + @Test + fun visible_changesToFalse_windowInteractorUpdated() { + // GIVEN the shade is currently expanded + shadeController.makeExpandedVisible(true) + assertThat(windowRootViewVisibilityInteractor.isLockscreenOrShadeVisible.value).isTrue() + + // WHEN the shade is collapsed + shadeController.collapseShade() + + // THEN the interactor is notified + assertThat(windowRootViewVisibilityInteractor.isLockscreenOrShadeVisible.value).isFalse() + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java index 7117c233c8c5..bbf0151a90ce 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java @@ -40,8 +40,14 @@ import androidx.test.filters.SmallTest; import com.android.internal.logging.InstanceId; import com.android.internal.statusbar.IStatusBarService; import com.android.internal.statusbar.NotificationVisibility; +import com.android.keyguard.TestScopeProvider; import com.android.systemui.SysuiTestCase; -import com.android.systemui.shade.ShadeExpansionStateManager; +import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository; +import com.android.systemui.keyguard.shared.model.WakeSleepReason; +import com.android.systemui.keyguard.shared.model.WakefulnessModel; +import com.android.systemui.keyguard.shared.model.WakefulnessState; +import com.android.systemui.scene.data.repository.WindowRootViewVisibilityRepository; +import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor; import com.android.systemui.statusbar.NotificationListener; import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.StatusBarStateControllerImpl; @@ -54,7 +60,9 @@ import com.android.systemui.statusbar.notification.collection.render.Notificatio import com.android.systemui.statusbar.notification.logging.nano.Notifications; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.stack.NotificationListContainer; +import com.android.systemui.statusbar.policy.HeadsUpManager; import com.android.systemui.util.concurrency.FakeExecutor; +import com.android.systemui.util.kotlin.JavaAdapter; import com.android.systemui.util.time.FakeSystemClock; import com.google.android.collect.Lists; @@ -71,6 +79,8 @@ import java.util.List; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.Executor; +import kotlinx.coroutines.test.TestScope; + @SmallTest @RunWith(AndroidTestingRunner.class) @TestableLooper.RunWithLooper @@ -89,7 +99,7 @@ public class NotificationLoggerTest extends SysuiTestCase { @Mock private NotificationVisibilityProvider mVisibilityProvider; @Mock private NotifPipeline mNotifPipeline; @Mock private NotificationListener mListener; - @Mock private ShadeExpansionStateManager mShadeExpansionStateManager; + @Mock private HeadsUpManager mHeadsUpManager; private NotificationEntry mEntry; private TestableNotificationLogger mLogger; @@ -97,12 +107,23 @@ public class NotificationLoggerTest extends SysuiTestCase { private FakeExecutor mUiBgExecutor = new FakeExecutor(new FakeSystemClock()); private NotificationPanelLoggerFake mNotificationPanelLoggerFake = new NotificationPanelLoggerFake(); + private final TestScope mTestScope = TestScopeProvider.getTestScope(); + private final FakeKeyguardRepository mKeyguardRepository = new FakeKeyguardRepository(); + private WindowRootViewVisibilityInteractor mWindowRootViewVisibilityInteractor; + private final JavaAdapter mJavaAdapter = new JavaAdapter(mTestScope.getBackgroundScope()); @Before public void setUp() { MockitoAnnotations.initMocks(this); when(mNotifLiveDataStore.getActiveNotifList()).thenReturn(mActiveNotifEntries); + mWindowRootViewVisibilityInteractor = new WindowRootViewVisibilityInteractor( + mTestScope.getBackgroundScope(), + new WindowRootViewVisibilityRepository(mBarService, mUiBgExecutor), + mKeyguardRepository, + mHeadsUpManager); + mWindowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(true); + mEntry = new NotificationEntryBuilder() .setPkg(TEST_PACKAGE_NAME) .setOpPkg(TEST_PACKAGE_NAME) @@ -120,10 +141,12 @@ public class NotificationLoggerTest extends SysuiTestCase { mVisibilityProvider, mNotifPipeline, mock(StatusBarStateControllerImpl.class), - mShadeExpansionStateManager, + mWindowRootViewVisibilityInteractor, + mJavaAdapter, mBarService, mExpansionStateLogger ); + mLogger.start(); mLogger.setUpWithContainer(mListContainer); verify(mNotifPipeline).addCollectionListener(any()); } @@ -183,31 +206,26 @@ public class NotificationLoggerTest extends SysuiTestCase { Mockito.reset(mBarService); setStateAsleep(); - mLogger.onDozingChanged(false); // Wake to lockscreen - mLogger.onDozingChanged(true); // And go back to sleep, turning off logging + + setStateAwake(); // Wake to lockscreen + + setStateAsleep(); // And go back to sleep, turning off logging mUiBgExecutor.runAllReady(); + // The visibility objects are recycled by NotificationLogger, so we can't use specific // matchers here. verify(mBarService, times(1)).onNotificationVisibilityChanged(any(), any()); } - private void setStateAsleep() { - mLogger.onShadeExpansionFullyChanged(true); - mLogger.onDozingChanged(true); - mLogger.onStateChanged(StatusBarState.KEYGUARD); - } - - private void setStateAwake() { - mLogger.onShadeExpansionFullyChanged(false); - mLogger.onDozingChanged(false); - mLogger.onStateChanged(StatusBarState.SHADE); - } - @Test - public void testLogPanelShownOnWake() { + public void testLogPanelShownOnWakeToLockscreen() { when(mActiveNotifEntries.getValue()).thenReturn(Lists.newArrayList(mEntry)); setStateAsleep(); - mLogger.onDozingChanged(false); // Wake to lockscreen + + // Wake to lockscreen + mLogger.onStateChanged(StatusBarState.KEYGUARD); + setStateAwake(); + assertEquals(1, mNotificationPanelLoggerFake.getCalls().size()); assertTrue(mNotificationPanelLoggerFake.get(0).isLockscreen); assertEquals(1, mNotificationPanelLoggerFake.get(0).list.notifications.length); @@ -222,9 +240,14 @@ public class NotificationLoggerTest extends SysuiTestCase { @Test public void testLogPanelShownOnShadePull() { when(mActiveNotifEntries.getValue()).thenReturn(Lists.newArrayList(mEntry)); + // Start as awake, but with the panel not visible setStateAwake(); + mWindowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(false); + // Now expand panel - mLogger.onShadeExpansionFullyChanged(true); + mLogger.onStateChanged(StatusBarState.SHADE); + mWindowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(true); + assertEquals(1, mNotificationPanelLoggerFake.getCalls().size()); assertFalse(mNotificationPanelLoggerFake.get(0).isLockscreen); assertEquals(1, mNotificationPanelLoggerFake.get(0).list.notifications.length); @@ -251,13 +274,34 @@ public class NotificationLoggerTest extends SysuiTestCase { when(mActiveNotifEntries.getValue()).thenReturn(Lists.newArrayList(entry)); setStateAsleep(); - mLogger.onDozingChanged(false); // Wake to lockscreen + + // Wake to lockscreen + setStateAwake(); + assertEquals(1, mNotificationPanelLoggerFake.getCalls().size()); assertEquals(1, mNotificationPanelLoggerFake.get(0).list.notifications.length); Notifications.Notification n = mNotificationPanelLoggerFake.get(0).list.notifications[0]; assertEquals(0, n.instanceId); } + private void setStateAsleep() { + mKeyguardRepository.setWakefulnessModel( + new WakefulnessModel( + WakefulnessState.ASLEEP, + WakeSleepReason.OTHER, + WakeSleepReason.OTHER)); + mTestScope.getTestScheduler().runCurrent(); + } + + private void setStateAwake() { + mKeyguardRepository.setWakefulnessModel( + new WakefulnessModel( + WakefulnessState.AWAKE, + WakeSleepReason.OTHER, + WakeSleepReason.OTHER)); + mTestScope.getTestScheduler().runCurrent(); + } + private class TestableNotificationLogger extends NotificationLogger { TestableNotificationLogger(NotificationListener notificationListener, @@ -266,7 +310,8 @@ public class NotificationLoggerTest extends SysuiTestCase { NotificationVisibilityProvider visibilityProvider, NotifPipeline notifPipeline, StatusBarStateControllerImpl statusBarStateController, - ShadeExpansionStateManager shadeExpansionStateManager, + WindowRootViewVisibilityInteractor windowRootViewVisibilityInteractor, + JavaAdapter javaAdapter, IStatusBarService barService, ExpansionStateLogger expansionStateLogger) { super( @@ -276,7 +321,8 @@ public class NotificationLoggerTest extends SysuiTestCase { visibilityProvider, notifPipeline, statusBarStateController, - shadeExpansionStateManager, + windowRootViewVisibilityInteractor, + javaAdapter, expansionStateLogger, mNotificationPanelLoggerFake ); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java index 705d52bcf13f..9e0f83c9fc53 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java @@ -37,6 +37,7 @@ import static org.mockito.ArgumentMatchers.anySet; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -66,11 +67,16 @@ import androidx.test.filters.SmallTest; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.UiEventLogger; import com.android.internal.logging.testing.UiEventLoggerFake; +import com.android.internal.statusbar.IStatusBarService; +import com.android.keyguard.TestScopeProvider; import com.android.systemui.SysuiTestCase; +import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository; import com.android.systemui.people.widget.PeopleSpaceWidgetManager; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin; import com.android.systemui.plugins.statusbar.StatusBarStateController; +import com.android.systemui.scene.data.repository.WindowRootViewVisibilityRepository; +import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor; import com.android.systemui.settings.UserContextProvider; import com.android.systemui.shade.ShadeController; import com.android.systemui.statusbar.NotificationLockscreenUserManager; @@ -84,6 +90,9 @@ import com.android.systemui.statusbar.notification.row.NotificationGutsManager.O import com.android.systemui.statusbar.notification.stack.NotificationListContainer; import com.android.systemui.statusbar.phone.HeadsUpManagerPhone; import com.android.systemui.statusbar.policy.DeviceProvisionedController; +import com.android.systemui.util.concurrency.FakeExecutor; +import com.android.systemui.util.kotlin.JavaAdapter; +import com.android.systemui.util.time.FakeSystemClock; import com.android.systemui.wmshell.BubblesManager; import org.junit.Before; @@ -97,6 +106,8 @@ import org.mockito.junit.MockitoRule; import java.util.Optional; +import kotlinx.coroutines.test.TestScope; + /** * Tests for {@link NotificationGutsManager}. */ @@ -108,6 +119,10 @@ public class NotificationGutsManagerTest extends SysuiTestCase { private NotificationChannel mTestNotificationChannel = new NotificationChannel( TEST_CHANNEL_ID, TEST_CHANNEL_ID, IMPORTANCE_DEFAULT); + + private TestScope mTestScope = TestScopeProvider.getTestScope(); + private JavaAdapter mJavaAdapter = new JavaAdapter(mTestScope.getBackgroundScope()); + private FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock()); private TestableLooper mTestableLooper; private Handler mHandler; private NotificationTestHelper mHelper; @@ -124,6 +139,7 @@ public class NotificationGutsManagerTest extends SysuiTestCase { @Mock private AccessibilityManager mAccessibilityManager; @Mock private HighPriorityProvider mHighPriorityProvider; @Mock private INotificationManager mINotificationManager; + @Mock private IStatusBarService mBarService; @Mock private LauncherApps mLauncherApps; @Mock private ShortcutManager mShortcutManager; @Mock private ChannelEditorDialogController mChannelEditorDialogController; @@ -140,6 +156,8 @@ public class NotificationGutsManagerTest extends SysuiTestCase { @Mock private UserManager mUserManager; + private WindowRootViewVisibilityInteractor mWindowRootViewVisibilityInteractor; + @Before public void setUp() { mTestableLooper = TestableLooper.get(this); @@ -148,21 +166,42 @@ public class NotificationGutsManagerTest extends SysuiTestCase { mHelper = new NotificationTestHelper(mContext, mDependency, TestableLooper.get(this)); when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(false); - mGutsManager = new NotificationGutsManager(mContext, mHandler, mHandler, + mWindowRootViewVisibilityInteractor = new WindowRootViewVisibilityInteractor( + mTestScope.getBackgroundScope(), + new WindowRootViewVisibilityRepository(mBarService, mExecutor), + new FakeKeyguardRepository(), + mHeadsUpManagerPhone); + + mGutsManager = new NotificationGutsManager( + mContext, + mHandler, + mHandler, + mJavaAdapter, mAccessibilityManager, - mHighPriorityProvider, mINotificationManager, mUserManager, - mPeopleSpaceWidgetManager, mLauncherApps, mShortcutManager, - mChannelEditorDialogController, mContextTracker, mAssistantFeedbackController, - Optional.of(mBubblesManager), new UiEventLoggerFake(), mOnUserInteractionCallback, + mHighPriorityProvider, + mINotificationManager, + mUserManager, + mPeopleSpaceWidgetManager, + mLauncherApps, + mShortcutManager, + mChannelEditorDialogController, + mContextTracker, + mAssistantFeedbackController, + Optional.of(mBubblesManager), + new UiEventLoggerFake(), + mOnUserInteractionCallback, mShadeController, + mWindowRootViewVisibilityInteractor, mNotificationLockscreenUserManager, mStatusBarStateController, mDeviceProvisionedController, mMetricsLogger, - mHeadsUpManagerPhone, mActivityStarter); + mHeadsUpManagerPhone, + mActivityStarter); mGutsManager.setUpWithPresenter(mPresenter, mNotificationListContainer, mOnSettingsClickListener); mGutsManager.setNotificationActivityStarter(mNotificationActivityStarter); + mGutsManager.start(); } //////////////////////////////////////////////////////////////////////////////////////////////// @@ -210,6 +249,62 @@ public class NotificationGutsManagerTest extends SysuiTestCase { } @Test + public void testLockscreenShadeVisible_visible_gutsNotClosed() { + // First, start out lockscreen or shade as not visible + mWindowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(false); + mTestScope.getTestScheduler().runCurrent(); + + NotificationGuts guts = mock(NotificationGuts.class); + mGutsManager.setExposedGuts(guts); + + // WHEN the lockscreen or shade becomes visible + mWindowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(true); + mTestScope.getTestScheduler().runCurrent(); + + // THEN the guts are not closed + verify(guts, never()).removeCallbacks(any()); + verify(guts, never()).closeControls( + anyBoolean(), anyBoolean(), anyInt(), anyInt(), anyBoolean()); + } + + @Test + public void testLockscreenShadeVisible_notVisible_gutsClosed() { + // First, start out lockscreen or shade as visible + mWindowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(true); + mTestScope.getTestScheduler().runCurrent(); + + NotificationGuts guts = mock(NotificationGuts.class); + mGutsManager.setExposedGuts(guts); + + // WHEN the lockscreen or shade is no longer visible + mWindowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(false); + mTestScope.getTestScheduler().runCurrent(); + + // THEN the guts are closed + verify(guts).removeCallbacks(any()); + verify(guts).closeControls( + /* leavebehinds= */ eq(true), + /* controls= */ eq(true), + /* x= */ anyInt(), + /* y= */ anyInt(), + /* force= */ eq(true)); + } + + @Test + public void testLockscreenShadeVisible_notVisible_listContainerReset() { + // First, start out lockscreen or shade as visible + mWindowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(true); + mTestScope.getTestScheduler().runCurrent(); + + // WHEN the lockscreen or shade is no longer visible + mWindowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(false); + mTestScope.getTestScheduler().runCurrent(); + + // THEN the list container is reset + verify(mNotificationListContainer).resetExposedMenuView(anyBoolean(), anyBoolean()); + } + + @Test public void testChangeDensityOrFontScale() { NotificationGuts guts = spy(new NotificationGuts(mContext)); when(guts.post(any())).thenAnswer(invocation -> { 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 f47efe3f79c4..5a1450f1c57b 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 @@ -24,7 +24,6 @@ import static com.android.systemui.statusbar.StatusBarState.SHADE; import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertTrue; -import static junit.framework.TestCase.fail; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; @@ -62,7 +61,6 @@ import android.os.IPowerManager; import android.os.IThermalService; import android.os.Looper; import android.os.PowerManager; -import android.os.RemoteException; import android.os.UserHandle; import android.service.dreams.IDreamManager; import android.support.test.metricshelper.MetricsAsserts; @@ -73,13 +71,7 @@ import android.util.DisplayMetrics; import android.util.SparseArray; import android.view.ViewGroup; import android.view.ViewGroup.LayoutParams; -import android.view.ViewRootImpl; import android.view.WindowManager; -import android.window.BackEvent; -import android.window.OnBackAnimationCallback; -import android.window.OnBackInvokedCallback; -import android.window.OnBackInvokedDispatcher; -import android.window.WindowOnBackInvokedDispatcher; import androidx.test.filters.SmallTest; @@ -125,6 +117,7 @@ import com.android.systemui.plugins.PluginManager; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.power.domain.interactor.PowerInteractor; import com.android.systemui.recents.ScreenPinningRequest; +import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor; import com.android.systemui.settings.UserTracker; import com.android.systemui.settings.brightness.BrightnessSliderController; import com.android.systemui.shade.CameraLauncher; @@ -158,7 +151,6 @@ import com.android.systemui.statusbar.notification.NotificationActivityStarter; import com.android.systemui.statusbar.notification.NotificationLaunchAnimatorControllerProvider; import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator; import com.android.systemui.statusbar.notification.collection.NotifLiveDataStore; -import com.android.systemui.statusbar.notification.collection.NotifPipeline; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder; import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider; @@ -167,10 +159,7 @@ import com.android.systemui.statusbar.notification.init.NotificationsController; import com.android.systemui.statusbar.notification.interruption.KeyguardNotificationVisibilityProvider; import com.android.systemui.statusbar.notification.interruption.NotificationInterruptLogger; import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderImpl; -import com.android.systemui.statusbar.notification.logging.NotificationLogger; -import com.android.systemui.statusbar.notification.logging.NotificationPanelLoggerFake; import com.android.systemui.statusbar.notification.row.NotificationGutsManager; -import com.android.systemui.statusbar.notification.stack.NotificationListContainer; import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout; import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController; import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent; @@ -199,7 +188,6 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; -import org.mockito.Captor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; @@ -227,7 +215,6 @@ public class CentralSurfacesImplTest extends SysuiTestCase { @Mock private KeyguardIndicationController mKeyguardIndicationController; @Mock private NotificationStackScrollLayout mStackScroller; @Mock private NotificationStackScrollLayoutController mStackScrollerController; - @Mock private NotificationListContainer mNotificationListContainer; @Mock private HeadsUpManagerPhone mHeadsUpManager; @Mock private NotificationPanelViewController mNotificationPanelViewController; @Mock private ShadeLogger mShadeLogger; @@ -254,7 +241,6 @@ public class CentralSurfacesImplTest extends SysuiTestCase { @Mock private StatusBarNotificationPresenter mNotificationPresenter; @Mock private NotificationActivityStarter mNotificationActivityStarter; @Mock private AmbientDisplayConfiguration mAmbientDisplayConfiguration; - @Mock private NotificationLogger.ExpansionStateLogger mExpansionStateLogger; @Mock private KeyguardUpdateMonitor mKeyguardUpdateMonitor; @Mock private StatusBarSignalPolicy mStatusBarSignalPolicy; @Mock private BroadcastDispatcher mBroadcastDispatcher; @@ -325,18 +311,11 @@ public class CentralSurfacesImplTest extends SysuiTestCase { @Mock private Lazy<CameraLauncher> mCameraLauncherLazy; @Mock private CameraLauncher mCameraLauncher; @Mock private AlternateBouncerInteractor mAlternateBouncerInteractor; - /** - * The process of registering/unregistering a predictive back callback requires a - * ViewRootImpl, which is present IRL, but may be missing during a Mockito unit test. - * To prevent an NPE during test execution, we explicitly craft and provide a fake ViewRootImpl. - */ - @Mock private ViewRootImpl mViewRootImpl; - @Mock private WindowOnBackInvokedDispatcher mOnBackInvokedDispatcher; @Mock private UserTracker mUserTracker; @Mock private FingerprintManager mFingerprintManager; - @Captor private ArgumentCaptor<OnBackInvokedCallback> mOnBackInvokedCallback; @Mock IPowerManager mPowerManagerService; @Mock ActivityStarter mActivityStarter; + @Mock private WindowRootViewVisibilityInteractor mWindowRootViewVisibilityInteractor; private ShadeController mShadeController; private final FakeSystemClock mFakeSystemClock = new FakeSystemClock(); @@ -388,18 +367,6 @@ public class CentralSurfacesImplTest extends SysuiTestCase { mContext.addMockSystemService(FingerprintManager.class, mock(FingerprintManager.class)); mMetricsLogger = new FakeMetricsLogger(); - NotificationLogger notificationLogger = new NotificationLogger( - mNotificationListener, - mUiBgExecutor, - mNotifLiveDataStore, - mVisibilityProvider, - mock(NotifPipeline.class), - mStatusBarStateController, - mShadeExpansionStateManager, - mExpansionStateLogger, - new NotificationPanelLoggerFake() - ); - notificationLogger.setVisibilityReporter(mock(Runnable.class)); when(mCommandQueue.asBinder()).thenReturn(new Binder()); @@ -448,6 +415,7 @@ public class CentralSurfacesImplTest extends SysuiTestCase { mCommandQueue, mMainExecutor, mock(LogBuffer.class), + mock(WindowRootViewVisibilityInteractor.class), mKeyguardStateController, mStatusBarStateController, mStatusBarKeyguardViewManager, @@ -490,7 +458,6 @@ public class CentralSurfacesImplTest extends SysuiTestCase { new FalsingCollectorFake(), mBroadcastDispatcher, mNotificationGutsManager, - notificationLogger, mNotificationInterruptStateProvider, new ShadeExpansionStateManager(), mKeyguardViewMediator, @@ -541,6 +508,7 @@ public class CentralSurfacesImplTest extends SysuiTestCase { () -> mCentralSurfacesCommandQueueCallbacks, mPluginManager, mShadeController, + mWindowRootViewVisibilityInteractor, mStatusBarKeyguardViewManager, mViewMediatorCallback, mInitController, @@ -578,16 +546,9 @@ public class CentralSurfacesImplTest extends SysuiTestCase { mUserTracker, () -> mFingerprintManager, mActivityStarter - ) { - @Override - protected ViewRootImpl getViewRootImpl() { - return mViewRootImpl; - } - }; + ); mScreenLifecycle.addObserver(mCentralSurfaces.mScreenObserver); mCentralSurfaces.initShadeVisibilityListener(); - when(mViewRootImpl.getOnBackInvokedDispatcher()) - .thenReturn(mOnBackInvokedDispatcher); when(mKeyguardViewMediator.registerCentralSurfaces( any(CentralSurfacesImpl.class), any(NotificationPanelViewController.class), @@ -609,7 +570,6 @@ public class CentralSurfacesImplTest extends SysuiTestCase { PowerManager.SCREEN_BRIGHT_WAKE_LOCK, "sysui:GestureWakeLock"); mCentralSurfaces.startKeyguard(); mInitController.executePostInitTasks(); - notificationLogger.setUpWithContainer(mNotificationListContainer); mCentralSurfaces.registerCallbacks(); } @@ -799,151 +759,6 @@ public class CentralSurfacesImplTest extends SysuiTestCase { } @Test - public void testLogHidden() { - try { - mCentralSurfaces.handleVisibleToUserChanged(false); - mUiBgExecutor.runAllReady(); - verify(mBarService, times(1)).onPanelHidden(); - verify(mBarService, never()).onPanelRevealed(anyBoolean(), anyInt()); - } catch (RemoteException e) { - fail(); - } - } - - /** - * Do the following: - * 1. verify that a predictive back callback is registered when CSurf becomes visible - * 2. verify that the same callback is unregistered when CSurf becomes invisible - */ - @Test - public void testPredictiveBackCallback_registration() { - mCentralSurfaces.handleVisibleToUserChanged(true); - verify(mOnBackInvokedDispatcher).registerOnBackInvokedCallback( - eq(OnBackInvokedDispatcher.PRIORITY_DEFAULT), - mOnBackInvokedCallback.capture()); - - mCentralSurfaces.handleVisibleToUserChanged(false); - verify(mOnBackInvokedDispatcher).unregisterOnBackInvokedCallback( - eq(mOnBackInvokedCallback.getValue())); - } - - /** - * Do the following: - * 1. capture the predictive back callback during registration - * 2. call the callback directly - * 3. verify that the ShadeController's panel collapse animation is invoked - */ - @Test - public void testPredictiveBackCallback_invocationCollapsesPanel() { - mCentralSurfaces.handleVisibleToUserChanged(true); - verify(mOnBackInvokedDispatcher).registerOnBackInvokedCallback( - eq(OnBackInvokedDispatcher.PRIORITY_DEFAULT), - mOnBackInvokedCallback.capture()); - - when(mBackActionInteractor.shouldBackBeHandled()).thenReturn(true); - mOnBackInvokedCallback.getValue().onBackInvoked(); - verify(mBackActionInteractor).onBackRequested(); - } - - /** - * When back progress is at 100%, the onBackProgressed animation driver inside - * NotificationPanelViewController should be invoked appropriately (with 1.0f passed in). - */ - @Test - public void testPredictiveBackAnimation_progressMaxScalesPanel() { - mCentralSurfaces.handleVisibleToUserChanged(true); - verify(mOnBackInvokedDispatcher).registerOnBackInvokedCallback( - eq(OnBackInvokedDispatcher.PRIORITY_DEFAULT), - mOnBackInvokedCallback.capture()); - - OnBackAnimationCallback onBackAnimationCallback = - (OnBackAnimationCallback) (mOnBackInvokedCallback.getValue()); - when(mBackActionInteractor.shouldBackBeHandled()).thenReturn(true); - when(mNotificationPanelViewController.canBeCollapsed()).thenReturn(true); - - BackEvent fakeSwipeInFromLeftEdge = new BackEvent(20.0f, 100.0f, 1.0f, BackEvent.EDGE_LEFT); - onBackAnimationCallback.onBackProgressed(fakeSwipeInFromLeftEdge); - verify(mNotificationPanelViewController).onBackProgressed(eq(1.0f)); - } - - /** - * When back progress is at 0%, the onBackProgressed animation driver inside - * NotificationPanelViewController should be invoked appropriately (with 0.0f passed in). - */ - @Test - public void testPredictiveBackAnimation_progressMinScalesPanel() { - mCentralSurfaces.handleVisibleToUserChanged(true); - verify(mOnBackInvokedDispatcher).registerOnBackInvokedCallback( - eq(OnBackInvokedDispatcher.PRIORITY_DEFAULT), - mOnBackInvokedCallback.capture()); - - OnBackAnimationCallback onBackAnimationCallback = - (OnBackAnimationCallback) (mOnBackInvokedCallback.getValue()); - when(mBackActionInteractor.shouldBackBeHandled()).thenReturn(true); - when(mNotificationPanelViewController.canBeCollapsed()).thenReturn(true); - - BackEvent fakeSwipeInFromLeftEdge = new BackEvent(20.0f, 10.0f, 0.0f, BackEvent.EDGE_LEFT); - onBackAnimationCallback.onBackProgressed(fakeSwipeInFromLeftEdge); - verify(mNotificationPanelViewController).onBackProgressed(eq(0.0f)); - } - - @Test - public void testPanelOpenForHeadsUp() { - when(mDeviceProvisionedController.isDeviceProvisioned()).thenReturn(true); - when(mHeadsUpManager.hasPinnedHeadsUp()).thenReturn(true); - when(mNotificationsController.getActiveNotificationsCount()).thenReturn(5); - when(mNotificationPresenter.isPresenterFullyCollapsed()).thenReturn(true); - mCentralSurfaces.setBarStateForTest(SHADE); - - try { - mCentralSurfaces.handleVisibleToUserChanged(true); - mUiBgExecutor.runAllReady(); - verify(mBarService, never()).onPanelHidden(); - verify(mBarService, times(1)).onPanelRevealed(false, 1); - } catch (RemoteException e) { - fail(); - } - mMainExecutor.runAllReady(); - } - - @Test - public void testPanelOpenAndClear() { - when(mHeadsUpManager.hasPinnedHeadsUp()).thenReturn(false); - when(mNotificationsController.getActiveNotificationsCount()).thenReturn(5); - - when(mNotificationPresenter.isPresenterFullyCollapsed()).thenReturn(false); - mCentralSurfaces.setBarStateForTest(SHADE); - - try { - mCentralSurfaces.handleVisibleToUserChanged(true); - mUiBgExecutor.runAllReady(); - verify(mBarService, never()).onPanelHidden(); - verify(mBarService, times(1)).onPanelRevealed(true, 5); - } catch (RemoteException e) { - fail(); - } - mMainExecutor.runAllReady(); - } - - @Test - public void testPanelOpenAndNoClear() { - when(mHeadsUpManager.hasPinnedHeadsUp()).thenReturn(false); - when(mNotificationsController.getActiveNotificationsCount()).thenReturn(5); - when(mNotificationPresenter.isPresenterFullyCollapsed()).thenReturn(false); - mCentralSurfaces.setBarStateForTest(StatusBarState.KEYGUARD); - - try { - mCentralSurfaces.handleVisibleToUserChanged(true); - mUiBgExecutor.runAllReady(); - verify(mBarService, never()).onPanelHidden(); - verify(mBarService, times(1)).onPanelRevealed(false, 5); - } catch (RemoteException e) { - fail(); - } - mMainExecutor.runAllReady(); - } - - @Test public void testDump_DoesNotCrash() { mCentralSurfaces.dump(new PrintWriter(new ByteArrayOutputStream()), null); } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt index faebcaac1be3..cc0c943bdbbe 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt @@ -81,7 +81,7 @@ class FakeKeyguardRepository : KeyguardRepository { override val linearDozeAmount: Flow<Float> = _dozeAmount private val _statusBarState = MutableStateFlow(StatusBarState.SHADE) - override val statusBarState: Flow<StatusBarState> = _statusBarState + override val statusBarState: StateFlow<StatusBarState> = _statusBarState private val _dozeTransitionModel = MutableStateFlow(DozeTransitionModel()) override val dozeTransitionModel: Flow<DozeTransitionModel> = _dozeTransitionModel |