diff options
| -rw-r--r-- | packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt | 70 | ||||
| -rw-r--r-- | packages/SystemUI/tests/src/com/android/systemui/media/MediaHierarchyManagerTest.kt | 145 |
2 files changed, 214 insertions, 1 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt index 24bcc2f586ca..775a1649702a 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt @@ -27,6 +27,7 @@ import android.view.View import android.view.ViewGroup import android.view.ViewGroupOverlay import com.android.systemui.Interpolators +import com.android.systemui.keyguard.WakefulnessLifecycle import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.statusbar.NotificationLockscreenUserManager import com.android.systemui.statusbar.StatusBarState @@ -49,7 +50,8 @@ class MediaHierarchyManager @Inject constructor( private val keyguardStateController: KeyguardStateController, private val bypassController: KeyguardBypassController, private val mediaViewManager: MediaViewManager, - private val notifLockscreenUserManager: NotificationLockscreenUserManager + private val notifLockscreenUserManager: NotificationLockscreenUserManager, + wakefulnessLifecycle: WakefulnessLifecycle ) { /** * The root overlay of the hierarchy. This is where the media notification is attached to @@ -137,6 +139,40 @@ class MediaHierarchyManager @Inject constructor( } } + /** + * Are location changes currently blocked? + */ + private val blockLocationChanges: Boolean + get() { + return goingToSleep || dozeAnimationRunning + } + + /** + * Are we currently going to sleep + */ + private var goingToSleep: Boolean = false + set(value) { + if (field != value) { + field = value + if (!value) { + updateDesiredLocation() + } + } + } + + /** + * Is the doze animation currently Running + */ + private var dozeAnimationRunning: Boolean = false + private set(value) { + if (field != value) { + field = value + if (!value) { + updateDesiredLocation() + } + } + } + init { statusBarStateController.addCallback(object : StatusBarStateController.StateListener { override fun onStatePreChange(oldState: Int, newState: Int) { @@ -149,6 +185,34 @@ class MediaHierarchyManager @Inject constructor( override fun onStateChanged(newState: Int) { updateTargetState() } + + override fun onDozeAmountChanged(linear: Float, eased: Float) { + dozeAnimationRunning = linear != 0.0f && linear != 1.0f + } + + override fun onDozingChanged(isDozing: Boolean) { + if (!isDozing) { + dozeAnimationRunning = false + } + } + }) + + wakefulnessLifecycle.addObserver(object : WakefulnessLifecycle.Observer { + override fun onFinishedGoingToSleep() { + goingToSleep = false + } + + override fun onStartedGoingToSleep() { + goingToSleep = true + } + + override fun onFinishedWakingUp() { + goingToSleep = false + } + + override fun onStartedWakingUp() { + goingToSleep = false + } }) } @@ -428,6 +492,10 @@ class MediaHierarchyManager @Inject constructor( @MediaLocation private fun calculateLocation(): Int { + if (blockLocationChanges) { + // Keep the current location until we're allowed to again + return desiredLocation + } val onLockscreen = (!bypassController.bypassEnabled && (statusbarState == StatusBarState.KEYGUARD || statusbarState == StatusBarState.FULLSCREEN_USER_SWITCHER)) diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaHierarchyManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaHierarchyManagerTest.kt new file mode 100644 index 000000000000..c9e6f55ff59a --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaHierarchyManagerTest.kt @@ -0,0 +1,145 @@ +/* + * 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.graphics.Rect +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper +import android.view.ViewGroup +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.controls.controller.ControlsControllerImplTest.Companion.eq +import com.android.systemui.keyguard.WakefulnessLifecycle +import com.android.systemui.statusbar.NotificationLockscreenUserManager +import com.android.systemui.statusbar.StatusBarState +import com.android.systemui.statusbar.SysuiStatusBarStateController +import com.android.systemui.statusbar.phone.KeyguardBypassController +import com.android.systemui.statusbar.policy.KeyguardStateController +import com.android.systemui.util.animation.UniqueObjectHostView +import org.junit.Assert.assertNotNull +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentCaptor +import org.mockito.ArgumentMatchers +import org.mockito.Captor +import org.mockito.Mock +import org.mockito.Mockito.`when` +import org.mockito.Mockito.any +import org.mockito.Mockito.anyBoolean +import org.mockito.Mockito.anyLong +import org.mockito.Mockito.clearInvocations +import org.mockito.Mockito.times +import org.mockito.Mockito.verify +import org.mockito.junit.MockitoJUnit + +@SmallTest +@RunWith(AndroidTestingRunner::class) +@TestableLooper.RunWithLooper +class MediaHierarchyManagerTest : SysuiTestCase() { + + @Mock + private lateinit var lockHost: MediaHost + @Mock + private lateinit var qsHost: MediaHost + @Mock + private lateinit var qqsHost: MediaHost + @Mock + private lateinit var bypassController: KeyguardBypassController + @Mock + private lateinit var mediaFrame: ViewGroup + @Mock + private lateinit var keyguardStateController: KeyguardStateController + @Mock + private lateinit var statusBarStateController: SysuiStatusBarStateController + @Mock + private lateinit var notificationLockscreenUserManager: NotificationLockscreenUserManager + @Mock + private lateinit var mediaViewManager: MediaViewManager + @Mock + private lateinit var wakefulnessLifecycle: WakefulnessLifecycle + @Captor + private lateinit var wakefullnessObserver: ArgumentCaptor<(WakefulnessLifecycle.Observer)> + @JvmField + @Rule + val mockito = MockitoJUnit.rule() + private lateinit var mediaHiearchyManager: MediaHierarchyManager + + @Before + fun setup() { + `when`(mediaViewManager.mediaFrame).thenReturn(mediaFrame) + mediaHiearchyManager = MediaHierarchyManager( + context, + statusBarStateController, + keyguardStateController, + bypassController, + mediaViewManager, + notificationLockscreenUserManager, + wakefulnessLifecycle) + verify(wakefulnessLifecycle).addObserver(wakefullnessObserver.capture()) + setupHost(lockHost, MediaHierarchyManager.LOCATION_LOCKSCREEN) + setupHost(qsHost, MediaHierarchyManager.LOCATION_QS) + setupHost(qqsHost, MediaHierarchyManager.LOCATION_QQS) + `when`(statusBarStateController.state).thenReturn(StatusBarState.SHADE) + // We'll use the viewmanager to verify a few calls below, let's reset this. + clearInvocations(mediaViewManager) + + } + + private fun setupHost(host: MediaHost, location: Int) { + `when`(host.location).thenReturn(location) + `when`(host.currentBounds).thenReturn(Rect()) + `when`(host.hostView).thenReturn(UniqueObjectHostView(context)) + mediaHiearchyManager.register(host) + } + + @Test + fun testHostViewSetOnRegister() { + val host = mediaHiearchyManager.register(lockHost) + verify(lockHost).hostView = eq(host) + } + + @Test + fun testBlockedWhenScreenTurningOff() { + // Let's set it onto QS: + mediaHiearchyManager.qsExpansion = 1.0f + verify(mediaViewManager).onDesiredLocationChanged(ArgumentMatchers.anyInt(), + any(MediaHostState::class.java), anyBoolean(), anyLong(), anyLong()) + val observer = wakefullnessObserver.value + assertNotNull("lifecycle observer wasn't registered", observer) + observer.onStartedGoingToSleep() + clearInvocations(mediaViewManager) + mediaHiearchyManager.qsExpansion = 0.0f + verify(mediaViewManager, times(0)).onDesiredLocationChanged(ArgumentMatchers.anyInt(), + any(MediaHostState::class.java), anyBoolean(), anyLong(), anyLong()) + } + + @Test + fun testAllowedWhenNotTurningOff() { + // Let's set it onto QS: + mediaHiearchyManager.qsExpansion = 1.0f + verify(mediaViewManager).onDesiredLocationChanged(ArgumentMatchers.anyInt(), + any(MediaHostState::class.java), anyBoolean(), anyLong(), anyLong()) + val observer = wakefullnessObserver.value + assertNotNull("lifecycle observer wasn't registered", observer) + clearInvocations(mediaViewManager) + mediaHiearchyManager.qsExpansion = 0.0f + verify(mediaViewManager).onDesiredLocationChanged(ArgumentMatchers.anyInt(), + any(MediaHostState::class.java), anyBoolean(), anyLong(), anyLong()) + } +}
\ No newline at end of file |