From 989a11107022133baf988757956d10410268fbcd Mon Sep 17 00:00:00 2001 From: Lucas Dupin Date: Tue, 19 May 2020 18:56:28 -0700 Subject: [PATCH] Hide player on keyguard when notif are hidden Respect notification settings and also hide Keyguard media player and notifications are not visible on the lock screen. Test: manual Test: atest KeyguardMediaControllerTest Fixes: 153645800 Change-Id: I7cac760bd6d430f53abfd0c727698261a0785ad7 --- .../systemui/media/KeyguardMediaController.kt | 14 ++- .../systemui/media/MediaHierarchyManager.kt | 50 ++++----- .../media/KeyguardMediaControllerTest.kt | 100 ++++++++++++++++++ 3 files changed, 135 insertions(+), 29 deletions(-) create mode 100644 packages/SystemUI/tests/src/com/android/systemui/media/KeyguardMediaControllerTest.kt diff --git a/packages/SystemUI/src/com/android/systemui/media/KeyguardMediaController.kt b/packages/SystemUI/src/com/android/systemui/media/KeyguardMediaController.kt index 4ee4ad46d4c7e..85e1c6b77be48 100644 --- a/packages/SystemUI/src/com/android/systemui/media/KeyguardMediaController.kt +++ b/packages/SystemUI/src/com/android/systemui/media/KeyguardMediaController.kt @@ -18,6 +18,7 @@ package com.android.systemui.media import android.view.View import com.android.systemui.plugins.statusbar.StatusBarStateController +import com.android.systemui.statusbar.NotificationLockscreenUserManager import com.android.systemui.statusbar.StatusBarState import com.android.systemui.statusbar.SysuiStatusBarStateController import com.android.systemui.statusbar.notification.stack.MediaHeaderView @@ -33,7 +34,8 @@ import javax.inject.Singleton class KeyguardMediaController @Inject constructor( private val mediaHost: MediaHost, private val bypassController: KeyguardBypassController, - private val statusBarStateController: SysuiStatusBarStateController + private val statusBarStateController: SysuiStatusBarStateController, + private val notifLockscreenUserManager: NotificationLockscreenUserManager ) { init { @@ -61,10 +63,12 @@ class KeyguardMediaController @Inject constructor( } private fun updateVisibility() { - val shouldBeVisible = mediaHost.visible - && !bypassController.bypassEnabled - && (statusBarStateController.state == StatusBarState.KEYGUARD || - statusBarStateController.state == StatusBarState.FULLSCREEN_USER_SWITCHER) + val keyguardOrUserSwitcher = (statusBarStateController.state == StatusBarState.KEYGUARD || + statusBarStateController.state == StatusBarState.FULLSCREEN_USER_SWITCHER) + val shouldBeVisible = mediaHost.visible && + !bypassController.bypassEnabled && + keyguardOrUserSwitcher && + notifLockscreenUserManager.shouldShowLockscreenNotifications() view?.visibility = if (shouldBeVisible) View.VISIBLE else View.GONE } } \ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt index 6b1c520db7b10..3e7661ac0dd91 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt @@ -26,6 +26,7 @@ import android.view.ViewGroup import android.view.ViewGroupOverlay import com.android.systemui.Interpolators import com.android.systemui.plugins.statusbar.StatusBarStateController +import com.android.systemui.statusbar.NotificationLockscreenUserManager import com.android.systemui.statusbar.StatusBarState import com.android.systemui.statusbar.SysuiStatusBarStateController import com.android.systemui.statusbar.notification.stack.StackStateAnimator @@ -46,7 +47,8 @@ class MediaHierarchyManager @Inject constructor( private val keyguardStateController: KeyguardStateController, private val bypassController: KeyguardBypassController, private val mediaViewManager: MediaViewManager, - private val mediaMeasurementProvider: MediaMeasurementManager + private val mediaMeasurementProvider: MediaMeasurementManager, + private val notifLockscreenUserManager: NotificationLockscreenUserManager ) { /** * The root overlay of the hierarchy. This is where the media notification is attached to @@ -56,7 +58,7 @@ class MediaHierarchyManager @Inject constructor( private var rootOverlay: ViewGroupOverlay? = null private lateinit var currentState: MediaState private val mediaCarousel - get() = mediaViewManager.mediaCarousel + get() = mediaViewManager.mediaCarousel private var animationStartState: MediaState? = null private var statusbarState: Int = statusBarStateController.state private var animator = ValueAnimator.ofFloat(0.0f, 1.0f).apply { @@ -136,9 +138,9 @@ class MediaHierarchyManager @Inject constructor( * * @return the hostView associated with this location */ - fun register(mediaObject: MediaHost) : ViewGroup { + fun register(mediaObject: MediaHost): ViewGroup { val viewHost = createUniqueObjectHost(mediaObject) - mediaObject.hostView = viewHost; + mediaObject.hostView = viewHost mediaHosts[mediaObject.location] = mediaObject if (mediaObject.location == desiredLocation) { // In case we are overriding a view that is already visible, make sure we attach it @@ -155,7 +157,7 @@ class MediaHierarchyManager @Inject constructor( private fun createUniqueObjectHost(host: MediaHost): UniqueObjectHostView { val viewHost = UniqueObjectHostView(context) viewHost.measurementCache = mediaMeasurementProvider.obtainCache(host) - viewHost.onMeasureListener = { input -> + viewHost.onMeasureListener = { input -> if (host.location == desiredLocation) { // Measurement of the currently active player is happening, Let's make // sure the player width is up to date @@ -215,8 +217,8 @@ class MediaHierarchyManager @Inject constructor( applyTargetStateIfNotAnimating() } else if (animate) { animator.cancel() - if (currentAttachmentLocation == IN_OVERLAY - || !previousHost.hostView.isAttachedToWindow) { + if (currentAttachmentLocation == IN_OVERLAY || + !previousHost.hostView.isAttachedToWindow) { // Let's animate to the new position, starting from the current position // We also go in here in case the view was detached, since the bounds wouldn't // be correct anymore @@ -237,10 +239,10 @@ class MediaHierarchyManager @Inject constructor( @MediaLocation currentLocation: Int, @MediaLocation previousLocation: Int ): Boolean { - if (currentLocation == LOCATION_QQS - && previousLocation == LOCATION_LOCKSCREEN - && (statusBarStateController.leaveOpenOnKeyguardHide() - || statusbarState == StatusBarState.SHADE_LOCKED)) { + if (currentLocation == LOCATION_QQS && + previousLocation == LOCATION_LOCKSCREEN && + (statusBarStateController.leaveOpenOnKeyguardHide() || + statusbarState == StatusBarState.SHADE_LOCKED)) { // Usually listening to the isShown is enough to determine this, but there is some // non-trivial reattaching logic happening that will make the view not-shown earlier return true @@ -251,10 +253,9 @@ class MediaHierarchyManager @Inject constructor( private fun adjustAnimatorForTransition(desiredLocation: Int, previousLocation: Int) { val (animDuration, delay) = getAnimationParams(previousLocation, desiredLocation) animator.apply { - duration = animDuration + duration = animDuration startDelay = delay } - } private fun getAnimationParams(previousLocation: Int, desiredLocation: Int): Pair { @@ -262,8 +263,8 @@ class MediaHierarchyManager @Inject constructor( var delay = 0L if (previousLocation == LOCATION_LOCKSCREEN && desiredLocation == LOCATION_QQS) { // Going to the full shade, let's adjust the animation duration - if (statusbarState == StatusBarState.SHADE - && keyguardStateController.isKeyguardFadingAway) { + if (statusbarState == StatusBarState.SHADE && + keyguardStateController.isKeyguardFadingAway) { delay = keyguardStateController.keyguardFadingAwayDelay } animDuration = StackStateAnimator.ANIMATION_DURATION_GO_TO_FULL_SHADE.toLong() @@ -301,12 +302,12 @@ class MediaHierarchyManager @Inject constructor( /** * @return true if this transformation is guided by an external progress like a finger */ - private fun isCurrentlyInGuidedTransformation() : Boolean { + private fun isCurrentlyInGuidedTransformation(): Boolean { return getTransformationProgress() >= 0 } /** - * @return the current transformation progress if we're in a guided transformation and -1 + * @return the current transformation progress if we're in a guided transformation and -1 * otherwise */ private fun getTransformationProgress(): Float { @@ -377,19 +378,20 @@ class MediaHierarchyManager @Inject constructor( } private fun isTransitionRunning(): Boolean { - return isCurrentlyInGuidedTransformation() && getTransformationProgress() != 1.0f - || animator.isRunning + return isCurrentlyInGuidedTransformation() && getTransformationProgress() != 1.0f || + animator.isRunning } @MediaLocation - private fun calculateLocation() : Int { - val onLockscreen = (!bypassController.bypassEnabled - && (statusbarState == StatusBarState.KEYGUARD - || statusbarState == StatusBarState.FULLSCREEN_USER_SWITCHER)) + private fun calculateLocation(): Int { + val onLockscreen = (!bypassController.bypassEnabled && + (statusbarState == StatusBarState.KEYGUARD || + statusbarState == StatusBarState.FULLSCREEN_USER_SWITCHER)) + val allowedOnLockscreen = notifLockscreenUserManager.shouldShowLockscreenNotifications() return when { qsExpansion > 0.0f && !onLockscreen -> LOCATION_QS qsExpansion > 0.4f && onLockscreen -> LOCATION_QS - onLockscreen -> LOCATION_LOCKSCREEN + onLockscreen && allowedOnLockscreen -> LOCATION_LOCKSCREEN else -> LOCATION_QQS } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/KeyguardMediaControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/KeyguardMediaControllerTest.kt new file mode 100644 index 0000000000000..9aee11e4924ff --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/media/KeyguardMediaControllerTest.kt @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2020 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.media + +import android.testing.AndroidTestingRunner +import android.view.View.GONE +import android.view.View.VISIBLE +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.controls.controller.ControlsControllerImplTest.Companion.eq +import com.android.systemui.statusbar.NotificationLockscreenUserManager +import com.android.systemui.statusbar.StatusBarState +import com.android.systemui.statusbar.SysuiStatusBarStateController +import com.android.systemui.statusbar.notification.stack.MediaHeaderView +import com.android.systemui.statusbar.phone.KeyguardBypassController +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentCaptor +import org.mockito.Captor +import org.mockito.Mock +import org.mockito.Mockito.`when` +import org.mockito.Mockito.verify +import org.mockito.junit.MockitoJUnit + +@SmallTest +@RunWith(AndroidTestingRunner::class) +class KeyguardMediaControllerTest : SysuiTestCase() { + + @Mock + private lateinit var mediaHost: MediaHost + @Mock + private lateinit var bypassController: KeyguardBypassController + @Mock + private lateinit var statusBarStateController: SysuiStatusBarStateController + @Mock + private lateinit var notificationLockscreenUserManager: NotificationLockscreenUserManager + @Mock + private lateinit var mediaHeaderView: MediaHeaderView + @Captor + private lateinit var visibilityListener: ArgumentCaptor<((Boolean) -> Unit)> + @JvmField @Rule + val mockito = MockitoJUnit.rule() + private lateinit var keyguardMediaController: KeyguardMediaController + + @Before + fun setup() { + keyguardMediaController = KeyguardMediaController(mediaHost, bypassController, + statusBarStateController, notificationLockscreenUserManager) + } + + @Test + fun testAttach_hiddenWhenHostIsHidden() { + `when`(mediaHost.visible).thenReturn(false) + triggerVisibilityListener() + + verify(mediaHeaderView).visibility = eq(GONE) + } + @Test + fun testAttach_visibleOnKeyguard() { + `when`(mediaHost.visible).thenReturn(true) + `when`(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD) + `when`(notificationLockscreenUserManager.shouldShowLockscreenNotifications()) + .thenReturn(true) + triggerVisibilityListener() + + verify(mediaHeaderView).visibility = eq(VISIBLE) + } + @Test + fun testAttach_hiddenOnKeyguard_whenNotificationsAreHidden() { + `when`(mediaHost.visible).thenReturn(true) + `when`(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD) + `when`(notificationLockscreenUserManager.shouldShowLockscreenNotifications()) + .thenReturn(false) + triggerVisibilityListener() + + verify(mediaHeaderView).visibility = eq(GONE) + } + + private fun triggerVisibilityListener() { + keyguardMediaController.attach(mediaHeaderView) + verify(mediaHost).visibleChangedListener = visibilityListener.capture() + visibilityListener.value.invoke(true) + } +} \ No newline at end of file