Fixes an issue where the media players would animate on screen off
Because qs collapses when we start dozing, we triggered an animation that looked janky. We now instead don't reattach them at all and block the animation. Test: atest SystemUItests Bug: 154137987 Change-Id: I75a386d7f1dfb65c040d42b8d7aeaf34314b5d10
This commit is contained in:
@@ -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))
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user