Hide resume players on entering the lockdown mode
Don't add the resume player when onNotificationRemoved due to entering the lockdown mode. The media keeps playing and the resume player should not be visible. Hides media carousel on entering lockdown mode. Tracks when the keyguard is gone to make sure media carousel is visible. Bug: 253335072 Test: atest MediaDataManagerTest. Test: atest MediaCarouselControllerTest. Change-Id: I5d4ed30a24f43db984d73c58bbc551a0ec89509c
This commit is contained in:
@@ -42,6 +42,10 @@ class KeyguardTransitionInteractor
|
||||
constructor(
|
||||
repository: KeyguardTransitionRepository,
|
||||
) {
|
||||
/** (any)->GONE transition information */
|
||||
val anyStateToGoneTransition: Flow<TransitionStep> =
|
||||
repository.transitions.filter { step -> step.to == KeyguardState.GONE }
|
||||
|
||||
/** (any)->AOD transition information */
|
||||
val anyStateToAodTransition: Flow<TransitionStep> =
|
||||
repository.transitions.filter { step -> step.to == KeyguardState.AOD }
|
||||
|
||||
@@ -50,6 +50,7 @@ import android.text.TextUtils
|
||||
import android.util.Log
|
||||
import androidx.media.utils.MediaConstants
|
||||
import com.android.internal.logging.InstanceId
|
||||
import com.android.keyguard.KeyguardUpdateMonitor
|
||||
import com.android.systemui.Dumpable
|
||||
import com.android.systemui.R
|
||||
import com.android.systemui.broadcast.BroadcastDispatcher
|
||||
@@ -177,6 +178,7 @@ class MediaDataManager(
|
||||
private val mediaFlags: MediaFlags,
|
||||
private val logger: MediaUiEventLogger,
|
||||
private val smartspaceManager: SmartspaceManager,
|
||||
private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
|
||||
) : Dumpable, BcSmartspaceDataPlugin.SmartspaceTargetListener {
|
||||
|
||||
companion object {
|
||||
@@ -241,6 +243,7 @@ class MediaDataManager(
|
||||
mediaFlags: MediaFlags,
|
||||
logger: MediaUiEventLogger,
|
||||
smartspaceManager: SmartspaceManager,
|
||||
keyguardUpdateMonitor: KeyguardUpdateMonitor,
|
||||
) : this(
|
||||
context,
|
||||
backgroundExecutor,
|
||||
@@ -264,6 +267,7 @@ class MediaDataManager(
|
||||
mediaFlags,
|
||||
logger,
|
||||
smartspaceManager,
|
||||
keyguardUpdateMonitor,
|
||||
)
|
||||
|
||||
private val appChangeReceiver =
|
||||
@@ -1336,7 +1340,9 @@ class MediaDataManager(
|
||||
Assert.isMainThread()
|
||||
val removed = mediaEntries.remove(key) ?: return
|
||||
|
||||
if (useMediaResumption && removed.resumeAction != null && removed.isLocalSession()) {
|
||||
if (keyguardUpdateMonitor.isUserInLockdown(removed.userId)) {
|
||||
logger.logMediaRemoved(removed.appUid, removed.packageName, removed.instanceId)
|
||||
} else if (useMediaResumption && removed.resumeAction != null && removed.isLocalSession()) {
|
||||
convertToResumePlayer(removed)
|
||||
} else if (mediaFlags.isRetainingPlayersEnabled()) {
|
||||
handlePossibleRemoval(removed, notificationRemoved = true)
|
||||
|
||||
@@ -30,13 +30,20 @@ import android.view.ViewGroup
|
||||
import android.view.animation.PathInterpolator
|
||||
import android.widget.LinearLayout
|
||||
import androidx.annotation.VisibleForTesting
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import com.android.internal.logging.InstanceId
|
||||
import com.android.keyguard.KeyguardUpdateMonitor
|
||||
import com.android.keyguard.KeyguardUpdateMonitorCallback
|
||||
import com.android.systemui.Dumpable
|
||||
import com.android.systemui.R
|
||||
import com.android.systemui.classifier.FalsingCollector
|
||||
import com.android.systemui.dagger.SysUISingleton
|
||||
import com.android.systemui.dagger.qualifiers.Main
|
||||
import com.android.systemui.dump.DumpManager
|
||||
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
|
||||
import com.android.systemui.keyguard.shared.model.TransitionState
|
||||
import com.android.systemui.lifecycle.repeatWhenAttached
|
||||
import com.android.systemui.media.controls.models.player.MediaData
|
||||
import com.android.systemui.media.controls.models.player.MediaViewHolder
|
||||
import com.android.systemui.media.controls.models.recommendation.RecommendationViewHolder
|
||||
@@ -63,6 +70,10 @@ import java.io.PrintWriter
|
||||
import java.util.TreeMap
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Provider
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.flow.filter
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
private const val TAG = "MediaCarouselController"
|
||||
private val settingsIntent = Intent().setAction(ACTION_MEDIA_CONTROLS_SETTINGS)
|
||||
@@ -91,6 +102,8 @@ constructor(
|
||||
private val logger: MediaUiEventLogger,
|
||||
private val debugLogger: MediaCarouselControllerLogger,
|
||||
private val mediaFlags: MediaFlags,
|
||||
private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
|
||||
private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
|
||||
) : Dumpable {
|
||||
/** The current width of the carousel */
|
||||
private var currentCarouselWidth: Int = 0
|
||||
@@ -213,6 +226,17 @@ constructor(
|
||||
}
|
||||
}
|
||||
|
||||
private val keyguardUpdateMonitorCallback =
|
||||
object : KeyguardUpdateMonitorCallback() {
|
||||
override fun onStrongAuthStateChanged(userId: Int) {
|
||||
if (keyguardUpdateMonitor.isUserInLockdown(userId)) {
|
||||
hideMediaCarousel()
|
||||
} else if (keyguardUpdateMonitor.isUserUnlocked(userId)) {
|
||||
showMediaCarousel()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update MediaCarouselScrollHandler.visibleToUser to reflect media card container visibility.
|
||||
* It will be called when the container is out of view.
|
||||
@@ -487,6 +511,13 @@ constructor(
|
||||
}
|
||||
}
|
||||
)
|
||||
keyguardUpdateMonitor.registerCallback(keyguardUpdateMonitorCallback)
|
||||
mediaCarousel.repeatWhenAttached {
|
||||
repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||
// A backup to show media carousel (if available) once the keyguard is gone.
|
||||
listenForAnyStateToGoneKeyguardTransition(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun inflateSettingsButton() {
|
||||
@@ -516,6 +547,23 @@ constructor(
|
||||
return mediaCarousel
|
||||
}
|
||||
|
||||
private fun hideMediaCarousel() {
|
||||
mediaCarousel.visibility = View.GONE
|
||||
}
|
||||
|
||||
private fun showMediaCarousel() {
|
||||
mediaCarousel.visibility = View.VISIBLE
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
internal fun listenForAnyStateToGoneKeyguardTransition(scope: CoroutineScope): Job {
|
||||
return scope.launch {
|
||||
keyguardTransitionInteractor.anyStateToGoneTransition
|
||||
.filter { it.transitionState == TransitionState.FINISHED }
|
||||
.collect { showMediaCarousel() }
|
||||
}
|
||||
}
|
||||
|
||||
private fun reorderAllPlayers(
|
||||
previousVisiblePlayerKey: MediaPlayerData.MediaSortKey?,
|
||||
key: String? = null
|
||||
|
||||
@@ -40,6 +40,7 @@ import android.testing.TestableLooper.RunWithLooper
|
||||
import androidx.media.utils.MediaConstants
|
||||
import androidx.test.filters.SmallTest
|
||||
import com.android.internal.logging.InstanceId
|
||||
import com.android.keyguard.KeyguardUpdateMonitor
|
||||
import com.android.systemui.InstanceIdSequenceFake
|
||||
import com.android.systemui.R
|
||||
import com.android.systemui.SysuiTestCase
|
||||
@@ -126,6 +127,7 @@ class MediaDataManagerTest : SysuiTestCase() {
|
||||
@Mock lateinit var pendingIntent: PendingIntent
|
||||
@Mock lateinit var activityStarter: ActivityStarter
|
||||
@Mock lateinit var smartspaceManager: SmartspaceManager
|
||||
@Mock lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
|
||||
lateinit var smartspaceMediaDataProvider: SmartspaceMediaDataProvider
|
||||
@Mock lateinit var mediaSmartspaceTarget: SmartspaceTarget
|
||||
@Mock private lateinit var mediaRecommendationItem: SmartspaceAction
|
||||
@@ -187,6 +189,7 @@ class MediaDataManagerTest : SysuiTestCase() {
|
||||
mediaFlags = mediaFlags,
|
||||
logger = logger,
|
||||
smartspaceManager = smartspaceManager,
|
||||
keyguardUpdateMonitor = keyguardUpdateMonitor
|
||||
)
|
||||
verify(tunerService)
|
||||
.addTunable(capture(tunableCaptor), eq(Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION))
|
||||
@@ -242,6 +245,7 @@ class MediaDataManagerTest : SysuiTestCase() {
|
||||
whenever(mediaFlags.isRetainingPlayersEnabled()).thenReturn(false)
|
||||
whenever(mediaFlags.isPersistentSsCardEnabled()).thenReturn(false)
|
||||
whenever(logger.getNewInstanceId()).thenReturn(instanceIdSequence.newInstanceId())
|
||||
whenever(keyguardUpdateMonitor.isUserInLockdown(any())).thenReturn(false)
|
||||
}
|
||||
|
||||
@After
|
||||
@@ -682,6 +686,19 @@ class MediaDataManagerTest : SysuiTestCase() {
|
||||
verify(listener).onMediaDataRemoved(eq("0:$PACKAGE_NAME"))
|
||||
}
|
||||
|
||||
fun testOnNotificationRemoved_lockDownMode() {
|
||||
whenever(keyguardUpdateMonitor.isUserInLockdown(any())).thenReturn(true)
|
||||
|
||||
addNotificationAndLoad()
|
||||
val data = mediaDataCaptor.value
|
||||
mediaDataManager.onNotificationRemoved(KEY)
|
||||
|
||||
verify(listener, never()).onMediaDataRemoved(eq(KEY))
|
||||
verify(logger, never())
|
||||
.logActiveConvertedToResume(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId))
|
||||
verify(logger).logMediaRemoved(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testAddResumptionControls() {
|
||||
// WHEN resumption controls are added
|
||||
|
||||
@@ -21,12 +21,20 @@ import android.content.res.Configuration
|
||||
import android.testing.AndroidTestingRunner
|
||||
import android.testing.TestableLooper
|
||||
import android.util.MathUtils.abs
|
||||
import android.view.View
|
||||
import androidx.test.filters.SmallTest
|
||||
import com.android.internal.logging.InstanceId
|
||||
import com.android.keyguard.KeyguardUpdateMonitor
|
||||
import com.android.keyguard.KeyguardUpdateMonitorCallback
|
||||
import com.android.systemui.SysuiTestCase
|
||||
import com.android.systemui.classifier.FalsingCollector
|
||||
import com.android.systemui.dagger.qualifiers.Main
|
||||
import com.android.systemui.dump.DumpManager
|
||||
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
|
||||
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
|
||||
import com.android.systemui.keyguard.shared.model.KeyguardState
|
||||
import com.android.systemui.keyguard.shared.model.TransitionState
|
||||
import com.android.systemui.keyguard.shared.model.TransitionStep
|
||||
import com.android.systemui.media.controls.MediaTestUtils
|
||||
import com.android.systemui.media.controls.models.player.MediaData
|
||||
import com.android.systemui.media.controls.models.recommendation.SmartspaceMediaData
|
||||
@@ -49,6 +57,9 @@ import javax.inject.Provider
|
||||
import junit.framework.Assert.assertEquals
|
||||
import junit.framework.Assert.assertFalse
|
||||
import junit.framework.Assert.assertTrue
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.test.UnconfinedTestDispatcher
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Before
|
||||
import org.junit.Ignore
|
||||
import org.junit.Test
|
||||
@@ -90,11 +101,15 @@ class MediaCarouselControllerTest : SysuiTestCase() {
|
||||
@Mock lateinit var mediaCarousel: MediaScrollView
|
||||
@Mock lateinit var pageIndicator: PageIndicator
|
||||
@Mock lateinit var mediaFlags: MediaFlags
|
||||
@Mock lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
|
||||
@Mock lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor
|
||||
private lateinit var transitionRepository: FakeKeyguardTransitionRepository
|
||||
@Captor lateinit var listener: ArgumentCaptor<MediaDataManager.Listener>
|
||||
@Captor
|
||||
lateinit var configListener: ArgumentCaptor<ConfigurationController.ConfigurationListener>
|
||||
@Captor lateinit var newConfig: ArgumentCaptor<Configuration>
|
||||
@Captor lateinit var visualStabilityCallback: ArgumentCaptor<OnReorderingAllowedListener>
|
||||
@Captor lateinit var keyguardCallback: ArgumentCaptor<KeyguardUpdateMonitorCallback>
|
||||
|
||||
private val clock = FakeSystemClock()
|
||||
private lateinit var mediaCarouselController: MediaCarouselController
|
||||
@@ -102,6 +117,7 @@ class MediaCarouselControllerTest : SysuiTestCase() {
|
||||
@Before
|
||||
fun setup() {
|
||||
MockitoAnnotations.initMocks(this)
|
||||
transitionRepository = FakeKeyguardTransitionRepository()
|
||||
mediaCarouselController =
|
||||
MediaCarouselController(
|
||||
context,
|
||||
@@ -119,11 +135,14 @@ class MediaCarouselControllerTest : SysuiTestCase() {
|
||||
logger,
|
||||
debugLogger,
|
||||
mediaFlags,
|
||||
keyguardUpdateMonitor,
|
||||
KeyguardTransitionInteractor(repository = transitionRepository),
|
||||
)
|
||||
verify(configurationController).addCallback(capture(configListener))
|
||||
verify(mediaDataManager).addListener(capture(listener))
|
||||
verify(visualStabilityProvider)
|
||||
.addPersistentReorderingAllowedListener(capture(visualStabilityCallback))
|
||||
verify(keyguardUpdateMonitor).registerCallback(capture(keyguardCallback))
|
||||
whenever(mediaControlPanelFactory.get()).thenReturn(panel)
|
||||
whenever(panel.mediaViewController).thenReturn(mediaViewController)
|
||||
whenever(mediaDataManager.smartspaceMediaData).thenReturn(smartspaceMediaData)
|
||||
@@ -740,4 +759,41 @@ class MediaCarouselControllerTest : SysuiTestCase() {
|
||||
assertTrue(MediaPlayerData.visiblePlayerKeys().elementAt(0).isSsMediaRec)
|
||||
assertFalse(MediaPlayerData.visiblePlayerKeys().elementAt(0).data.active)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testOnLockDownMode_hideMediaCarousel() {
|
||||
whenever(keyguardUpdateMonitor.isUserInLockdown(context.userId)).thenReturn(true)
|
||||
mediaCarouselController.mediaCarousel = mediaCarousel
|
||||
|
||||
keyguardCallback.value.onStrongAuthStateChanged(context.userId)
|
||||
|
||||
verify(mediaCarousel).visibility = View.GONE
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testLockDownModeOff_showMediaCarousel() {
|
||||
whenever(keyguardUpdateMonitor.isUserInLockdown(context.userId)).thenReturn(false)
|
||||
whenever(keyguardUpdateMonitor.isUserUnlocked(context.userId)).thenReturn(true)
|
||||
mediaCarouselController.mediaCarousel = mediaCarousel
|
||||
|
||||
keyguardCallback.value.onStrongAuthStateChanged(context.userId)
|
||||
|
||||
verify(mediaCarousel).visibility = View.VISIBLE
|
||||
}
|
||||
|
||||
@ExperimentalCoroutinesApi
|
||||
@Test
|
||||
fun testKeyguardGone_showMediaCarousel() =
|
||||
runTest(UnconfinedTestDispatcher()) {
|
||||
mediaCarouselController.mediaCarousel = mediaCarousel
|
||||
|
||||
val job = mediaCarouselController.listenForAnyStateToGoneKeyguardTransition(this)
|
||||
transitionRepository.sendTransitionStep(
|
||||
TransitionStep(to = KeyguardState.GONE, transitionState = TransitionState.FINISHED)
|
||||
)
|
||||
|
||||
verify(mediaCarousel).visibility = View.VISIBLE
|
||||
|
||||
job.cancel()
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user