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: I67939a3741b21b48eecc1402d848cf3acef42467
This commit is contained in:
@@ -38,18 +38,20 @@ import android.service.notification.StatusBarNotification
|
|||||||
import android.text.TextUtils
|
import android.text.TextUtils
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import com.android.internal.graphics.ColorUtils
|
import com.android.internal.graphics.ColorUtils
|
||||||
|
import com.android.systemui.Dumpable
|
||||||
import com.android.systemui.R
|
import com.android.systemui.R
|
||||||
import com.android.systemui.broadcast.BroadcastDispatcher
|
import com.android.systemui.broadcast.BroadcastDispatcher
|
||||||
import com.android.systemui.dagger.qualifiers.Background
|
import com.android.systemui.dagger.qualifiers.Background
|
||||||
import com.android.systemui.dagger.qualifiers.Main
|
import com.android.systemui.dagger.qualifiers.Main
|
||||||
|
import com.android.systemui.dump.DumpManager
|
||||||
import com.android.systemui.statusbar.NotificationMediaManager
|
import com.android.systemui.statusbar.NotificationMediaManager
|
||||||
import com.android.systemui.statusbar.notification.MediaNotificationProcessor
|
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.statusbar.notification.row.HybridGroupManager
|
||||||
import com.android.systemui.util.Assert
|
import com.android.systemui.util.Assert
|
||||||
import com.android.systemui.util.Utils
|
import com.android.systemui.util.Utils
|
||||||
|
import java.io.FileDescriptor
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
|
import java.io.PrintWriter
|
||||||
import java.util.concurrent.Executor
|
import java.util.concurrent.Executor
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.inject.Singleton
|
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.
|
* A class that facilitates management and loading of Media Data, ready for binding.
|
||||||
*/
|
*/
|
||||||
@Singleton
|
@Singleton
|
||||||
class MediaDataManager @Inject constructor(
|
class MediaDataManager(
|
||||||
private val context: Context,
|
private val context: Context,
|
||||||
private val mediaControllerFactory: MediaControllerFactory,
|
|
||||||
private val notificationEntryManager: NotificationEntryManager,
|
|
||||||
@Background private val backgroundExecutor: Executor,
|
@Background private val backgroundExecutor: Executor,
|
||||||
@Main private val foregroundExecutor: Executor,
|
@Main private val foregroundExecutor: Executor,
|
||||||
broadcastDispatcher: BroadcastDispatcher,
|
private val mediaControllerFactory: MediaControllerFactory,
|
||||||
|
private val broadcastDispatcher: BroadcastDispatcher,
|
||||||
|
dumpManager: DumpManager,
|
||||||
mediaTimeoutListener: MediaTimeoutListener,
|
mediaTimeoutListener: MediaTimeoutListener,
|
||||||
mediaResumeListener: MediaResumeListener
|
mediaResumeListener: MediaResumeListener,
|
||||||
) {
|
private val useMediaResumption: Boolean,
|
||||||
|
private val useQsMediaPlayer: Boolean
|
||||||
|
) : Dumpable {
|
||||||
|
|
||||||
private val listeners: MutableSet<Listener> = mutableSetOf()
|
private val listeners: MutableSet<Listener> = mutableSetOf()
|
||||||
private val mediaEntries: LinkedHashMap<String, MediaData> = LinkedHashMap()
|
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() {
|
private val userChangeReceiver = object : BroadcastReceiver() {
|
||||||
override fun onReceive(context: Context, intent: Intent) {
|
override fun onReceive(context: Context, intent: Intent) {
|
||||||
@@ -128,6 +145,7 @@ class MediaDataManager @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
init {
|
init {
|
||||||
|
dumpManager.registerDumpable(TAG, this)
|
||||||
mediaTimeoutListener.timeoutCallback = { token: String, timedOut: Boolean ->
|
mediaTimeoutListener.timeoutCallback = { token: String, timedOut: Boolean ->
|
||||||
setTimedOut(token, timedOut) }
|
setTimedOut(token, timedOut) }
|
||||||
addListener(mediaTimeoutListener)
|
addListener(mediaTimeoutListener)
|
||||||
@@ -159,8 +177,13 @@ class MediaDataManager @Inject constructor(
|
|||||||
context.registerReceiver(appChangeReceiver, uninstallFilter)
|
context.registerReceiver(appChangeReceiver, uninstallFilter)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun destroy() {
|
||||||
|
context.unregisterReceiver(appChangeReceiver)
|
||||||
|
broadcastDispatcher.unregisterReceiver(userChangeReceiver)
|
||||||
|
}
|
||||||
|
|
||||||
fun onNotificationAdded(key: String, sbn: StatusBarNotification) {
|
fun onNotificationAdded(key: String, sbn: StatusBarNotification) {
|
||||||
if (Utils.useQsMediaPlayer(context) && isMediaNotification(sbn)) {
|
if (useQsMediaPlayer && isMediaNotification(sbn)) {
|
||||||
Assert.isMainThread()
|
Assert.isMainThread()
|
||||||
val oldKey = findExistingEntry(key, sbn.packageName)
|
val oldKey = findExistingEntry(key, sbn.packageName)
|
||||||
if (oldKey == null) {
|
if (oldKey == null) {
|
||||||
@@ -253,18 +276,18 @@ class MediaDataManager @Inject constructor(
|
|||||||
*/
|
*/
|
||||||
fun removeListener(listener: Listener) = listeners.remove(listener)
|
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) {
|
private fun setTimedOut(token: String, timedOut: Boolean) {
|
||||||
mediaEntries[token]?.let {
|
mediaEntries[token]?.let {
|
||||||
if (Utils.useMediaResumption(context)) {
|
|
||||||
if (it.active == !timedOut) {
|
if (it.active == !timedOut) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
it.active = !timedOut
|
it.active = !timedOut
|
||||||
onMediaDataLoaded(token, token, it)
|
onMediaDataLoaded(token, token, it)
|
||||||
} else if (timedOut) {
|
|
||||||
notificationEntryManager.removeNotification(it.notificationKey, null /* ranking */,
|
|
||||||
UNDEFINED_DISMISS_REASON)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -570,4 +593,12 @@ class MediaDataManager @Inject constructor(
|
|||||||
*/
|
*/
|
||||||
fun onMediaDataRemoved(key: String) {}
|
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