Merge "Do not dismiss notification, flip activity flag" into rvc-dev am: fe16654f21
Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/11902767 Change-Id: I2ba16cb8e34ba0e4ed6012884fcca426bb50fbab
This commit is contained in:
@@ -38,18 +38,20 @@ import android.service.notification.StatusBarNotification
|
||||
import android.text.TextUtils
|
||||
import android.util.Log
|
||||
import com.android.internal.graphics.ColorUtils
|
||||
import com.android.systemui.Dumpable
|
||||
import com.android.systemui.R
|
||||
import com.android.systemui.broadcast.BroadcastDispatcher
|
||||
import com.android.systemui.dagger.qualifiers.Background
|
||||
import com.android.systemui.dagger.qualifiers.Main
|
||||
import com.android.systemui.dump.DumpManager
|
||||
import com.android.systemui.statusbar.NotificationMediaManager
|
||||
import com.android.systemui.statusbar.notification.MediaNotificationProcessor
|
||||
import com.android.systemui.statusbar.notification.NotificationEntryManager
|
||||
import com.android.systemui.statusbar.notification.NotificationEntryManager.UNDEFINED_DISMISS_REASON
|
||||
import com.android.systemui.statusbar.notification.row.HybridGroupManager
|
||||
import com.android.systemui.util.Assert
|
||||
import com.android.systemui.util.Utils
|
||||
import java.io.FileDescriptor
|
||||
import java.io.IOException
|
||||
import java.io.PrintWriter
|
||||
import java.util.concurrent.Executor
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
@@ -85,20 +87,35 @@ fun isMediaNotification(sbn: StatusBarNotification): Boolean {
|
||||
* A class that facilitates management and loading of Media Data, ready for binding.
|
||||
*/
|
||||
@Singleton
|
||||
class MediaDataManager @Inject constructor(
|
||||
class MediaDataManager(
|
||||
private val context: Context,
|
||||
private val mediaControllerFactory: MediaControllerFactory,
|
||||
private val notificationEntryManager: NotificationEntryManager,
|
||||
@Background private val backgroundExecutor: Executor,
|
||||
@Main private val foregroundExecutor: Executor,
|
||||
broadcastDispatcher: BroadcastDispatcher,
|
||||
private val mediaControllerFactory: MediaControllerFactory,
|
||||
private val broadcastDispatcher: BroadcastDispatcher,
|
||||
dumpManager: DumpManager,
|
||||
mediaTimeoutListener: MediaTimeoutListener,
|
||||
mediaResumeListener: MediaResumeListener
|
||||
) {
|
||||
mediaResumeListener: MediaResumeListener,
|
||||
private val useMediaResumption: Boolean,
|
||||
private val useQsMediaPlayer: Boolean
|
||||
) : Dumpable {
|
||||
|
||||
private val listeners: MutableSet<Listener> = mutableSetOf()
|
||||
private val mediaEntries: LinkedHashMap<String, MediaData> = LinkedHashMap()
|
||||
private val useMediaResumption: Boolean = Utils.useMediaResumption(context)
|
||||
|
||||
@Inject
|
||||
constructor(
|
||||
context: Context,
|
||||
@Background backgroundExecutor: Executor,
|
||||
@Main foregroundExecutor: Executor,
|
||||
mediaControllerFactory: MediaControllerFactory,
|
||||
dumpManager: DumpManager,
|
||||
broadcastDispatcher: BroadcastDispatcher,
|
||||
mediaTimeoutListener: MediaTimeoutListener,
|
||||
mediaResumeListener: MediaResumeListener
|
||||
) : this(context, backgroundExecutor, foregroundExecutor, mediaControllerFactory,
|
||||
broadcastDispatcher, dumpManager, mediaTimeoutListener, mediaResumeListener,
|
||||
Utils.useMediaResumption(context), Utils.useQsMediaPlayer(context))
|
||||
|
||||
private val userChangeReceiver = object : BroadcastReceiver() {
|
||||
override fun onReceive(context: Context, intent: Intent) {
|
||||
@@ -128,6 +145,7 @@ class MediaDataManager @Inject constructor(
|
||||
}
|
||||
|
||||
init {
|
||||
dumpManager.registerDumpable(TAG, this)
|
||||
mediaTimeoutListener.timeoutCallback = { token: String, timedOut: Boolean ->
|
||||
setTimedOut(token, timedOut) }
|
||||
addListener(mediaTimeoutListener)
|
||||
@@ -159,8 +177,13 @@ class MediaDataManager @Inject constructor(
|
||||
context.registerReceiver(appChangeReceiver, uninstallFilter)
|
||||
}
|
||||
|
||||
fun destroy() {
|
||||
context.unregisterReceiver(appChangeReceiver)
|
||||
broadcastDispatcher.unregisterReceiver(userChangeReceiver)
|
||||
}
|
||||
|
||||
fun onNotificationAdded(key: String, sbn: StatusBarNotification) {
|
||||
if (Utils.useQsMediaPlayer(context) && isMediaNotification(sbn)) {
|
||||
if (useQsMediaPlayer && isMediaNotification(sbn)) {
|
||||
Assert.isMainThread()
|
||||
val oldKey = findExistingEntry(key, sbn.packageName)
|
||||
if (oldKey == null) {
|
||||
@@ -253,18 +276,18 @@ class MediaDataManager @Inject constructor(
|
||||
*/
|
||||
fun removeListener(listener: Listener) = listeners.remove(listener)
|
||||
|
||||
/**
|
||||
* Called whenever the player has been paused or stopped for a while.
|
||||
* This will make the player not active anymore, hiding it from QQS and Keyguard.
|
||||
* @see MediaData.active
|
||||
*/
|
||||
private fun setTimedOut(token: String, timedOut: Boolean) {
|
||||
mediaEntries[token]?.let {
|
||||
if (Utils.useMediaResumption(context)) {
|
||||
if (it.active == !timedOut) {
|
||||
return
|
||||
}
|
||||
it.active = !timedOut
|
||||
onMediaDataLoaded(token, token, it)
|
||||
} else if (timedOut) {
|
||||
notificationEntryManager.removeNotification(it.notificationKey, null /* ranking */,
|
||||
UNDEFINED_DISMISS_REASON)
|
||||
if (it.active == !timedOut) {
|
||||
return
|
||||
}
|
||||
it.active = !timedOut
|
||||
onMediaDataLoaded(token, token, it)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -570,4 +593,12 @@ class MediaDataManager @Inject constructor(
|
||||
*/
|
||||
fun onMediaDataRemoved(key: String) {}
|
||||
}
|
||||
|
||||
override fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<out String>) {
|
||||
pw.apply {
|
||||
println("listeners: $listeners")
|
||||
println("mediaEntries: $mediaEntries")
|
||||
println("useMediaResumption: $useMediaResumption")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,126 @@
|
||||
package com.android.systemui.media
|
||||
|
||||
import android.app.Notification
|
||||
import android.service.notification.StatusBarNotification
|
||||
import android.testing.AndroidTestingRunner
|
||||
import android.testing.TestableLooper.RunWithLooper
|
||||
import androidx.test.filters.SmallTest
|
||||
import com.android.systemui.SysuiTestCase
|
||||
import com.android.systemui.broadcast.BroadcastDispatcher
|
||||
import com.android.systemui.dump.DumpManager
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import org.junit.After
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.mockito.Mock
|
||||
import org.mockito.Mockito
|
||||
import org.mockito.Mockito.mock
|
||||
import org.mockito.Mockito.verify
|
||||
import org.mockito.junit.MockitoJUnit
|
||||
import java.util.concurrent.Executor
|
||||
import org.mockito.Mockito.`when` as whenever
|
||||
|
||||
private const val KEY = "KEY"
|
||||
private const val PACKAGE_NAME = "com.android.systemui"
|
||||
|
||||
private fun <T> eq(value: T): T = Mockito.eq(value) ?: value
|
||||
private fun <T> anyObject(): T {
|
||||
return Mockito.anyObject<T>()
|
||||
}
|
||||
|
||||
@SmallTest
|
||||
@RunWithLooper(setAsMainLooper = true)
|
||||
@RunWith(AndroidTestingRunner::class)
|
||||
class MediaDataManagerTest : SysuiTestCase() {
|
||||
|
||||
@Mock lateinit var mediaControllerFactory: MediaControllerFactory
|
||||
@Mock lateinit var backgroundExecutor: Executor
|
||||
@Mock lateinit var foregroundExecutor: Executor
|
||||
@Mock lateinit var dumpManager: DumpManager
|
||||
@Mock lateinit var broadcastDispatcher: BroadcastDispatcher
|
||||
@Mock lateinit var mediaTimeoutListener: MediaTimeoutListener
|
||||
@Mock lateinit var mediaResumeListener: MediaResumeListener
|
||||
@JvmField @Rule val mockito = MockitoJUnit.rule()
|
||||
lateinit var mediaDataManager: MediaDataManager
|
||||
lateinit var mediaNotification: StatusBarNotification
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
mediaDataManager = MediaDataManager(context, backgroundExecutor, foregroundExecutor,
|
||||
mediaControllerFactory, broadcastDispatcher, dumpManager,
|
||||
mediaTimeoutListener, mediaResumeListener, useMediaResumption = true,
|
||||
useQsMediaPlayer = true)
|
||||
val sbn = mock(StatusBarNotification::class.java)
|
||||
val notification = mock(Notification::class.java)
|
||||
whenever(notification.hasMediaSession()).thenReturn(true)
|
||||
whenever(notification.notificationStyle).thenReturn(Notification.MediaStyle::class.java)
|
||||
whenever(sbn.notification).thenReturn(notification)
|
||||
whenever(sbn.packageName).thenReturn(PACKAGE_NAME)
|
||||
mediaNotification = sbn
|
||||
}
|
||||
|
||||
@After
|
||||
fun tearDown() {
|
||||
mediaDataManager.destroy()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testHasActiveMedia() {
|
||||
assertThat(mediaDataManager.hasActiveMedia()).isFalse()
|
||||
val data = mock(MediaData::class.java)
|
||||
|
||||
mediaDataManager.onNotificationAdded(KEY, mediaNotification)
|
||||
mediaDataManager.onMediaDataLoaded(KEY, oldKey = null, data = data)
|
||||
assertThat(mediaDataManager.hasActiveMedia()).isFalse()
|
||||
|
||||
whenever(data.active).thenReturn(true)
|
||||
assertThat(mediaDataManager.hasActiveMedia()).isTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testLoadsMetadataOnBackground() {
|
||||
mediaDataManager.onNotificationAdded(KEY, mediaNotification)
|
||||
verify(backgroundExecutor).execute(anyObject())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testOnMetaDataLoaded_callsListener() {
|
||||
val listener = mock(MediaDataManager.Listener::class.java)
|
||||
mediaDataManager.addListener(listener)
|
||||
mediaDataManager.onNotificationAdded(KEY, mediaNotification)
|
||||
mediaDataManager.onMediaDataLoaded(KEY, oldKey = null, data = mock(MediaData::class.java))
|
||||
verify(listener).onMediaDataLoaded(eq(KEY), eq(null), anyObject())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testHasAnyMedia_whenAddingMedia() {
|
||||
assertThat(mediaDataManager.hasAnyMedia()).isFalse()
|
||||
val data = mock(MediaData::class.java)
|
||||
|
||||
mediaDataManager.onNotificationAdded(KEY, mediaNotification)
|
||||
mediaDataManager.onMediaDataLoaded(KEY, oldKey = null, data = data)
|
||||
assertThat(mediaDataManager.hasAnyMedia()).isTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testOnNotificationRemoved_doesntHaveMedia() {
|
||||
val data = mock(MediaData::class.java)
|
||||
mediaDataManager.onNotificationAdded(KEY, mediaNotification)
|
||||
mediaDataManager.onMediaDataLoaded(KEY, oldKey = null, data = data)
|
||||
mediaDataManager.onNotificationRemoved(KEY)
|
||||
assertThat(mediaDataManager.hasAnyMedia()).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testOnNotificationRemoved_callsListener() {
|
||||
val listener = mock(MediaDataManager.Listener::class.java)
|
||||
mediaDataManager.addListener(listener)
|
||||
mediaDataManager.onNotificationAdded(KEY, mediaNotification)
|
||||
mediaDataManager.onMediaDataLoaded(KEY, oldKey = null, data = mock(MediaData::class.java))
|
||||
mediaDataManager.onNotificationRemoved(KEY)
|
||||
|
||||
verify(listener).onMediaDataRemoved(eq(KEY))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user