Merge "Do not dismiss notification, flip activity flag" into rvc-dev

This commit is contained in:
Lucas Dupin
2020-06-18 20:48:32 +00:00
committed by Android (Google) Code Review
2 changed files with 176 additions and 19 deletions

View File

@@ -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
onMediaDataLoaded(token, token, it)
} else if (timedOut) {
notificationEntryManager.removeNotification(it.notificationKey, null /* ranking */,
UNDEFINED_DISMISS_REASON)
} }
it.active = !timedOut
onMediaDataLoaded(token, token, it)
} }
} }
@@ -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")
}
}
} }

View File

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