Lucas Dupin | ae437a1 | 2020-06-17 17:20:33 -0700 | [diff] [blame] | 1 | package com.android.systemui.media |
| 2 | |
Robert Snoeberger | f05aa68f | 2020-06-18 16:14:29 -0400 | [diff] [blame] | 3 | import android.app.Notification.MediaStyle |
| 4 | import android.app.PendingIntent |
| 5 | import android.media.MediaDescription |
| 6 | import android.media.MediaMetadata |
| 7 | import android.media.session.MediaController |
| 8 | import android.media.session.MediaSession |
Lucas Dupin | ae437a1 | 2020-06-17 17:20:33 -0700 | [diff] [blame] | 9 | import android.service.notification.StatusBarNotification |
| 10 | import android.testing.AndroidTestingRunner |
| 11 | import android.testing.TestableLooper.RunWithLooper |
| 12 | import androidx.test.filters.SmallTest |
| 13 | import com.android.systemui.SysuiTestCase |
| 14 | import com.android.systemui.broadcast.BroadcastDispatcher |
| 15 | import com.android.systemui.dump.DumpManager |
Robert Snoeberger | f05aa68f | 2020-06-18 16:14:29 -0400 | [diff] [blame] | 16 | import com.android.systemui.statusbar.SbnBuilder |
| 17 | import com.android.systemui.util.concurrency.FakeExecutor |
Lucas Dupin | e267f4d | 2020-06-22 22:55:23 -0700 | [diff] [blame] | 18 | import com.android.systemui.util.mockito.eq |
Robert Snoeberger | f05aa68f | 2020-06-18 16:14:29 -0400 | [diff] [blame] | 19 | import com.android.systemui.util.time.FakeSystemClock |
Lucas Dupin | ae437a1 | 2020-06-17 17:20:33 -0700 | [diff] [blame] | 20 | import com.google.common.truth.Truth.assertThat |
| 21 | import org.junit.After |
| 22 | import org.junit.Before |
| 23 | import org.junit.Rule |
| 24 | import org.junit.Test |
| 25 | import org.junit.runner.RunWith |
| 26 | import org.mockito.Mock |
| 27 | import org.mockito.Mockito |
| 28 | import org.mockito.Mockito.mock |
| 29 | import org.mockito.Mockito.verify |
Robert Snoeberger | f05aa68f | 2020-06-18 16:14:29 -0400 | [diff] [blame] | 30 | import org.mockito.junit.MockitoJUnit |
Lucas Dupin | e267f4d | 2020-06-22 22:55:23 -0700 | [diff] [blame] | 31 | import org.mockito.Mockito.`when` as whenever |
Lucas Dupin | ae437a1 | 2020-06-17 17:20:33 -0700 | [diff] [blame] | 32 | |
| 33 | private const val KEY = "KEY" |
| 34 | private const val PACKAGE_NAME = "com.android.systemui" |
Robert Snoeberger | f05aa68f | 2020-06-18 16:14:29 -0400 | [diff] [blame] | 35 | private const val APP_NAME = "SystemUI" |
| 36 | private const val SESSION_ARTIST = "artist" |
| 37 | private const val SESSION_TITLE = "title" |
Lucas Dupin | ae437a1 | 2020-06-17 17:20:33 -0700 | [diff] [blame] | 38 | |
Lucas Dupin | ae437a1 | 2020-06-17 17:20:33 -0700 | [diff] [blame] | 39 | private fun <T> anyObject(): T { |
| 40 | return Mockito.anyObject<T>() |
| 41 | } |
| 42 | |
| 43 | @SmallTest |
| 44 | @RunWithLooper(setAsMainLooper = true) |
| 45 | @RunWith(AndroidTestingRunner::class) |
| 46 | class MediaDataManagerTest : SysuiTestCase() { |
| 47 | |
| 48 | @Mock lateinit var mediaControllerFactory: MediaControllerFactory |
Robert Snoeberger | f05aa68f | 2020-06-18 16:14:29 -0400 | [diff] [blame] | 49 | @Mock lateinit var controller: MediaController |
| 50 | lateinit var session: MediaSession |
| 51 | lateinit var metadataBuilder: MediaMetadata.Builder |
| 52 | lateinit var backgroundExecutor: FakeExecutor |
| 53 | lateinit var foregroundExecutor: FakeExecutor |
Lucas Dupin | ae437a1 | 2020-06-17 17:20:33 -0700 | [diff] [blame] | 54 | @Mock lateinit var dumpManager: DumpManager |
| 55 | @Mock lateinit var broadcastDispatcher: BroadcastDispatcher |
| 56 | @Mock lateinit var mediaTimeoutListener: MediaTimeoutListener |
| 57 | @Mock lateinit var mediaResumeListener: MediaResumeListener |
Robert Snoeberger | f05aa68f | 2020-06-18 16:14:29 -0400 | [diff] [blame] | 58 | @Mock lateinit var pendingIntent: PendingIntent |
Lucas Dupin | ae437a1 | 2020-06-17 17:20:33 -0700 | [diff] [blame] | 59 | @JvmField @Rule val mockito = MockitoJUnit.rule() |
| 60 | lateinit var mediaDataManager: MediaDataManager |
| 61 | lateinit var mediaNotification: StatusBarNotification |
| 62 | |
| 63 | @Before |
| 64 | fun setup() { |
Robert Snoeberger | f05aa68f | 2020-06-18 16:14:29 -0400 | [diff] [blame] | 65 | foregroundExecutor = FakeExecutor(FakeSystemClock()) |
| 66 | backgroundExecutor = FakeExecutor(FakeSystemClock()) |
Lucas Dupin | ae437a1 | 2020-06-17 17:20:33 -0700 | [diff] [blame] | 67 | mediaDataManager = MediaDataManager(context, backgroundExecutor, foregroundExecutor, |
| 68 | mediaControllerFactory, broadcastDispatcher, dumpManager, |
| 69 | mediaTimeoutListener, mediaResumeListener, useMediaResumption = true, |
| 70 | useQsMediaPlayer = true) |
Robert Snoeberger | f05aa68f | 2020-06-18 16:14:29 -0400 | [diff] [blame] | 71 | session = MediaSession(context, "MediaDataManagerTestSession") |
| 72 | mediaNotification = SbnBuilder().run { |
| 73 | setPkg(PACKAGE_NAME) |
| 74 | modifyNotification(context).also { |
| 75 | it.setSmallIcon(android.R.drawable.ic_media_pause) |
| 76 | it.setStyle(MediaStyle().apply { setMediaSession(session.sessionToken) }) |
| 77 | } |
| 78 | build() |
| 79 | } |
| 80 | metadataBuilder = MediaMetadata.Builder().apply { |
| 81 | putString(MediaMetadata.METADATA_KEY_ARTIST, SESSION_ARTIST) |
| 82 | putString(MediaMetadata.METADATA_KEY_TITLE, SESSION_TITLE) |
| 83 | } |
| 84 | whenever(mediaControllerFactory.create(eq(session.sessionToken))).thenReturn(controller) |
Lucas Dupin | ae437a1 | 2020-06-17 17:20:33 -0700 | [diff] [blame] | 85 | } |
| 86 | |
| 87 | @After |
| 88 | fun tearDown() { |
Robert Snoeberger | f05aa68f | 2020-06-18 16:14:29 -0400 | [diff] [blame] | 89 | session.release() |
Lucas Dupin | ae437a1 | 2020-06-17 17:20:33 -0700 | [diff] [blame] | 90 | mediaDataManager.destroy() |
| 91 | } |
| 92 | |
| 93 | @Test |
| 94 | fun testHasActiveMedia() { |
| 95 | assertThat(mediaDataManager.hasActiveMedia()).isFalse() |
| 96 | val data = mock(MediaData::class.java) |
| 97 | |
| 98 | mediaDataManager.onNotificationAdded(KEY, mediaNotification) |
| 99 | mediaDataManager.onMediaDataLoaded(KEY, oldKey = null, data = data) |
| 100 | assertThat(mediaDataManager.hasActiveMedia()).isFalse() |
| 101 | |
| 102 | whenever(data.active).thenReturn(true) |
| 103 | assertThat(mediaDataManager.hasActiveMedia()).isTrue() |
| 104 | } |
| 105 | |
| 106 | @Test |
Lucas Dupin | e267f4d | 2020-06-22 22:55:23 -0700 | [diff] [blame] | 107 | fun testOnSwipeToDismiss_deactivatesMedia() { |
| 108 | val data = MediaData(initialized = true, backgroundColor = 0, app = null, appIcon = null, |
| 109 | artist = null, song = null, artwork = null, actions = emptyList(), |
| 110 | actionsToShowInCompact = emptyList(), packageName = "INVALID", token = null, |
| 111 | clickIntent = null, device = null, active = true, resumeAction = null) |
| 112 | mediaDataManager.onNotificationAdded(KEY, mediaNotification) |
| 113 | mediaDataManager.onMediaDataLoaded(KEY, oldKey = null, data = data) |
| 114 | |
| 115 | mediaDataManager.onSwipeToDismiss() |
| 116 | assertThat(data.active).isFalse() |
| 117 | } |
| 118 | |
| 119 | @Test |
Lucas Dupin | ae437a1 | 2020-06-17 17:20:33 -0700 | [diff] [blame] | 120 | fun testLoadsMetadataOnBackground() { |
| 121 | mediaDataManager.onNotificationAdded(KEY, mediaNotification) |
Robert Snoeberger | f05aa68f | 2020-06-18 16:14:29 -0400 | [diff] [blame] | 122 | assertThat(backgroundExecutor.numPending()).isEqualTo(1) |
Lucas Dupin | ae437a1 | 2020-06-17 17:20:33 -0700 | [diff] [blame] | 123 | } |
| 124 | |
| 125 | @Test |
| 126 | fun testOnMetaDataLoaded_callsListener() { |
| 127 | val listener = mock(MediaDataManager.Listener::class.java) |
| 128 | mediaDataManager.addListener(listener) |
| 129 | mediaDataManager.onNotificationAdded(KEY, mediaNotification) |
| 130 | mediaDataManager.onMediaDataLoaded(KEY, oldKey = null, data = mock(MediaData::class.java)) |
| 131 | verify(listener).onMediaDataLoaded(eq(KEY), eq(null), anyObject()) |
| 132 | } |
| 133 | |
| 134 | @Test |
Lucas Dupin | e267f4d | 2020-06-22 22:55:23 -0700 | [diff] [blame] | 135 | fun testOnMetaDataLoaded_conservesActiveFlag() { |
| 136 | val listener = TestListener() |
| 137 | whenever(mediaControllerFactory.create(anyObject())).thenReturn(controller) |
| 138 | whenever(controller.metadata).thenReturn(metadataBuilder.build()) |
| 139 | mediaDataManager.addListener(listener) |
| 140 | mediaDataManager.onNotificationAdded(KEY, mediaNotification) |
| 141 | assertThat(backgroundExecutor.runAllReady()).isEqualTo(1) |
| 142 | assertThat(foregroundExecutor.runAllReady()).isEqualTo(1) |
| 143 | assertThat(listener.data!!.active).isTrue() |
| 144 | |
| 145 | // Swiping away makes the notification not active |
| 146 | mediaDataManager.onSwipeToDismiss() |
| 147 | assertThat(mediaDataManager.hasActiveMedia()).isFalse() |
| 148 | |
| 149 | // And when a notification is updated |
| 150 | mediaDataManager.onNotificationAdded(KEY, mediaNotification) |
| 151 | assertThat(backgroundExecutor.runAllReady()).isEqualTo(1) |
| 152 | assertThat(foregroundExecutor.runAllReady()).isEqualTo(1) |
| 153 | |
| 154 | // MediaData should still be inactive |
| 155 | assertThat(mediaDataManager.hasActiveMedia()).isFalse() |
| 156 | } |
| 157 | |
| 158 | @Test |
Lucas Dupin | ae437a1 | 2020-06-17 17:20:33 -0700 | [diff] [blame] | 159 | fun testHasAnyMedia_whenAddingMedia() { |
| 160 | assertThat(mediaDataManager.hasAnyMedia()).isFalse() |
| 161 | val data = mock(MediaData::class.java) |
| 162 | |
| 163 | mediaDataManager.onNotificationAdded(KEY, mediaNotification) |
| 164 | mediaDataManager.onMediaDataLoaded(KEY, oldKey = null, data = data) |
| 165 | assertThat(mediaDataManager.hasAnyMedia()).isTrue() |
| 166 | } |
| 167 | |
| 168 | @Test |
| 169 | fun testOnNotificationRemoved_doesntHaveMedia() { |
| 170 | val data = mock(MediaData::class.java) |
| 171 | mediaDataManager.onNotificationAdded(KEY, mediaNotification) |
| 172 | mediaDataManager.onMediaDataLoaded(KEY, oldKey = null, data = data) |
| 173 | mediaDataManager.onNotificationRemoved(KEY) |
| 174 | assertThat(mediaDataManager.hasAnyMedia()).isFalse() |
| 175 | } |
| 176 | |
| 177 | @Test |
| 178 | fun testOnNotificationRemoved_callsListener() { |
| 179 | val listener = mock(MediaDataManager.Listener::class.java) |
| 180 | mediaDataManager.addListener(listener) |
| 181 | mediaDataManager.onNotificationAdded(KEY, mediaNotification) |
| 182 | mediaDataManager.onMediaDataLoaded(KEY, oldKey = null, data = mock(MediaData::class.java)) |
| 183 | mediaDataManager.onNotificationRemoved(KEY) |
| 184 | |
| 185 | verify(listener).onMediaDataRemoved(eq(KEY)) |
| 186 | } |
Robert Snoeberger | f05aa68f | 2020-06-18 16:14:29 -0400 | [diff] [blame] | 187 | |
| 188 | @Test |
| 189 | fun testOnNotificationRemoved_withResumption() { |
| 190 | // GIVEN that the manager has a notification with a resume action |
| 191 | val listener = TestListener() |
| 192 | mediaDataManager.addListener(listener) |
| 193 | whenever(controller.metadata).thenReturn(metadataBuilder.build()) |
| 194 | mediaDataManager.onNotificationAdded(KEY, mediaNotification) |
| 195 | assertThat(backgroundExecutor.runAllReady()).isEqualTo(1) |
| 196 | assertThat(foregroundExecutor.runAllReady()).isEqualTo(1) |
| 197 | val data = listener.data!! |
| 198 | assertThat(data.resumption).isFalse() |
| 199 | mediaDataManager.onMediaDataLoaded(KEY, null, data.copy(resumeAction = Runnable {})) |
| 200 | // WHEN the notification is removed |
| 201 | mediaDataManager.onNotificationRemoved(KEY) |
| 202 | // THEN the media data indicates that it is |
| 203 | assertThat(listener.data!!.resumption).isTrue() |
| 204 | } |
| 205 | |
| 206 | @Test |
| 207 | fun testAddResumptionControls() { |
| 208 | val listener = TestListener() |
| 209 | mediaDataManager.addListener(listener) |
| 210 | // WHEN resumption controls are added` |
| 211 | val desc = MediaDescription.Builder().run { |
| 212 | setTitle(SESSION_TITLE) |
| 213 | build() |
| 214 | } |
| 215 | mediaDataManager.addResumptionControls(desc, Runnable {}, session.sessionToken, APP_NAME, |
| 216 | pendingIntent, PACKAGE_NAME) |
| 217 | assertThat(backgroundExecutor.runAllReady()).isEqualTo(1) |
| 218 | assertThat(foregroundExecutor.runAllReady()).isEqualTo(1) |
| 219 | // THEN the media data indicates that it is for resumption |
| 220 | val data = listener.data!! |
| 221 | assertThat(data.resumption).isTrue() |
| 222 | assertThat(data.song).isEqualTo(SESSION_TITLE) |
| 223 | assertThat(data.app).isEqualTo(APP_NAME) |
| 224 | assertThat(data.actions).hasSize(1) |
| 225 | } |
| 226 | |
| 227 | /** |
| 228 | * Simple implementation of [MediaDataManager.Listener] for the test. |
| 229 | * |
| 230 | * Giving up on trying to get a mock Listener and ArgumentCaptor to work. |
| 231 | */ |
| 232 | private class TestListener : MediaDataManager.Listener { |
| 233 | var data: MediaData? = null |
| 234 | var key: String? = null |
| 235 | var oldKey: String? = null |
| 236 | |
| 237 | override fun onMediaDataLoaded(key: String, oldKey: String?, data: MediaData) { |
| 238 | this.key = key |
| 239 | this.oldKey = oldKey |
| 240 | this.data = data |
| 241 | } |
| 242 | |
| 243 | override fun onMediaDataRemoved(key: String) { |
| 244 | this.key = key |
| 245 | oldKey = null |
| 246 | data = null |
| 247 | } |
| 248 | } |
| 249 | } |