Merge "Hide resume players on entering the lockdown mode" into tm-qpr-dev am: 561bd764ba

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/20930404

Change-Id: I26f63bfadb58c2bec96546afae4ffe1c248eff2f
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
Michael Mikhail
2023-02-07 18:39:41 +00:00
committed by Automerger Merge Worker
5 changed files with 132 additions and 1 deletions

View File

@@ -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 }

View File

@@ -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)

View File

@@ -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

View File

@@ -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

View File

@@ -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()
}
}