summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt70
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/MediaHierarchyManagerTest.kt145
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