blob: b7f317b38743397b25e5ada2c972ce1c1e16cbe0 [file] [log] [blame]
Robert Snoeberger45be3fb2020-05-19 02:00:43 -04001/*
2 * Copyright (C) 2020 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.systemui.media
18
19import android.content.res.ColorStateList
20import android.graphics.Color
Robert Snoeberger70d0d6b2020-05-14 16:47:02 -040021import android.graphics.drawable.GradientDrawable
22import android.graphics.drawable.RippleDrawable
Robert Snoeberger45be3fb2020-05-19 02:00:43 -040023import android.media.MediaMetadata
24import android.media.session.MediaSession
25import android.media.session.PlaybackState
26import android.testing.AndroidTestingRunner
27import android.testing.TestableLooper
Robert Snoeberger07340432020-06-19 17:33:48 -040028import android.view.View
Robert Snoeberger45be3fb2020-05-19 02:00:43 -040029import android.view.ViewGroup
30import android.widget.FrameLayout
31import android.widget.ImageButton
32import android.widget.ImageView
33import android.widget.SeekBar
34import android.widget.TextView
Lucas Dupin84f5a0e2020-06-08 19:55:33 -070035import androidx.constraintlayout.widget.ConstraintSet
Robert Snoeberger7cec5422020-05-29 17:09:14 -040036import androidx.lifecycle.LiveData
Robert Snoeberger45be3fb2020-05-19 02:00:43 -040037import androidx.test.filters.SmallTest
Robert Snoeberger45be3fb2020-05-19 02:00:43 -040038import com.android.systemui.R
39import com.android.systemui.SysuiTestCase
40import com.android.systemui.plugins.ActivityStarter
Selim Cinek2de5ebb2020-05-20 15:39:03 -070041import com.android.systemui.util.animation.TransitionLayout
Robert Snoeberger45be3fb2020-05-19 02:00:43 -040042import com.android.systemui.util.concurrency.FakeExecutor
43import com.android.systemui.util.time.FakeSystemClock
Robert Snoeberger45be3fb2020-05-19 02:00:43 -040044import com.google.common.truth.Truth.assertThat
Robert Snoeberger45be3fb2020-05-19 02:00:43 -040045import org.junit.After
46import org.junit.Before
Robert Snoeberger7cec5422020-05-29 17:09:14 -040047import org.junit.Rule
Robert Snoeberger45be3fb2020-05-19 02:00:43 -040048import org.junit.Test
49import org.junit.runner.RunWith
Selim Cinek2de5ebb2020-05-20 15:39:03 -070050import org.mockito.ArgumentCaptor
Robert Snoeberger45be3fb2020-05-19 02:00:43 -040051import org.mockito.Mock
52import org.mockito.Mockito.mock
Selim Cinek2de5ebb2020-05-20 15:39:03 -070053import org.mockito.Mockito.verify
Robert Snoeberger45be3fb2020-05-19 02:00:43 -040054import org.mockito.Mockito.`when` as whenever
Robert Snoeberger7cec5422020-05-29 17:09:14 -040055import org.mockito.junit.MockitoJUnit
Robert Snoeberger45be3fb2020-05-19 02:00:43 -040056
Robert Snoeberger45be3fb2020-05-19 02:00:43 -040057private const val KEY = "TEST_KEY"
58private const val APP = "APP"
59private const val BG_COLOR = Color.RED
60private const val PACKAGE = "PKG"
61private const val ARTIST = "ARTIST"
62private const val TITLE = "TITLE"
63private const val DEVICE_NAME = "DEVICE_NAME"
64private const val SESSION_KEY = "SESSION_KEY"
65private const val SESSION_ARTIST = "SESSION_ARTIST"
66private const val SESSION_TITLE = "SESSION_TITLE"
67
68@SmallTest
69@RunWith(AndroidTestingRunner::class)
70@TestableLooper.RunWithLooper(setAsMainLooper = true)
71public class MediaControlPanelTest : SysuiTestCase() {
72
73 private lateinit var player: MediaControlPanel
74
Robert Snoeberger45be3fb2020-05-19 02:00:43 -040075 private lateinit var bgExecutor: FakeExecutor
76 @Mock private lateinit var activityStarter: ActivityStarter
77
78 @Mock private lateinit var holder: PlayerViewHolder
Selim Cinek2de5ebb2020-05-20 15:39:03 -070079 @Mock private lateinit var view: TransitionLayout
Robert Snoeberger7cec5422020-05-29 17:09:14 -040080 @Mock private lateinit var seekBarViewModel: SeekBarViewModel
81 @Mock private lateinit var seekBarData: LiveData<SeekBarViewModel.Progress>
Lucas Dupin84f5a0e2020-06-08 19:55:33 -070082 @Mock private lateinit var mediaViewController: MediaViewController
Robert Snoeberger07340432020-06-19 17:33:48 -040083 @Mock private lateinit var expandedSet: ConstraintSet
84 @Mock private lateinit var collapsedSet: ConstraintSet
Robert Snoeberger45be3fb2020-05-19 02:00:43 -040085 private lateinit var appIcon: ImageView
86 private lateinit var appName: TextView
87 private lateinit var albumView: ImageView
88 private lateinit var titleText: TextView
89 private lateinit var artistText: TextView
90 private lateinit var seamless: ViewGroup
91 private lateinit var seamlessIcon: ImageView
92 private lateinit var seamlessText: TextView
Robert Snoeberger07340432020-06-19 17:33:48 -040093 private lateinit var seamlessFallback: ImageView
Robert Snoeberger45be3fb2020-05-19 02:00:43 -040094 private lateinit var seekBar: SeekBar
95 private lateinit var elapsedTimeView: TextView
96 private lateinit var totalTimeView: TextView
97 private lateinit var action0: ImageButton
98 private lateinit var action1: ImageButton
99 private lateinit var action2: ImageButton
100 private lateinit var action3: ImageButton
101 private lateinit var action4: ImageButton
102
103 private lateinit var session: MediaSession
Robert Snoeberger23e36892020-05-21 00:33:24 -0400104 private val device = MediaDeviceData(true, null, DEVICE_NAME)
105 private val disabledDevice = MediaDeviceData(false, null, null)
Robert Snoeberger45be3fb2020-05-19 02:00:43 -0400106
Robert Snoeberger7cec5422020-05-29 17:09:14 -0400107 @JvmField @Rule val mockito = MockitoJUnit.rule()
108
Robert Snoeberger45be3fb2020-05-19 02:00:43 -0400109 @Before
110 fun setUp() {
Robert Snoeberger45be3fb2020-05-19 02:00:43 -0400111 bgExecutor = FakeExecutor(FakeSystemClock())
Robert Snoeberger07340432020-06-19 17:33:48 -0400112 whenever(mediaViewController.expandedLayout).thenReturn(expandedSet)
113 whenever(mediaViewController.collapsedLayout).thenReturn(collapsedSet)
Robert Snoeberger45be3fb2020-05-19 02:00:43 -0400114
Lucas Dupin84f5a0e2020-06-08 19:55:33 -0700115 player = MediaControlPanel(context, bgExecutor, activityStarter, mediaViewController,
Robert Snoeberger7cec5422020-05-29 17:09:14 -0400116 seekBarViewModel)
117 whenever(seekBarViewModel.progress).thenReturn(seekBarData)
Robert Snoeberger45be3fb2020-05-19 02:00:43 -0400118
119 // Mock out a view holder for the player to attach to.
Selim Cinek2de5ebb2020-05-20 15:39:03 -0700120 whenever(holder.player).thenReturn(view)
Robert Snoeberger45be3fb2020-05-19 02:00:43 -0400121 appIcon = ImageView(context)
122 whenever(holder.appIcon).thenReturn(appIcon)
123 appName = TextView(context)
124 whenever(holder.appName).thenReturn(appName)
125 albumView = ImageView(context)
126 whenever(holder.albumView).thenReturn(albumView)
127 titleText = TextView(context)
128 whenever(holder.titleText).thenReturn(titleText)
129 artistText = TextView(context)
130 whenever(holder.artistText).thenReturn(artistText)
131 seamless = FrameLayout(context)
Robert Snoeberger70d0d6b2020-05-14 16:47:02 -0400132 val seamlessBackground = mock(RippleDrawable::class.java)
Lucas Dupin5feeaca2020-05-28 11:01:54 -0700133 seamless.foreground = seamlessBackground
Robert Snoeberger70d0d6b2020-05-14 16:47:02 -0400134 whenever(seamlessBackground.getDrawable(0)).thenReturn(mock(GradientDrawable::class.java))
Robert Snoeberger45be3fb2020-05-19 02:00:43 -0400135 whenever(holder.seamless).thenReturn(seamless)
136 seamlessIcon = ImageView(context)
137 whenever(holder.seamlessIcon).thenReturn(seamlessIcon)
138 seamlessText = TextView(context)
139 whenever(holder.seamlessText).thenReturn(seamlessText)
Robert Snoeberger07340432020-06-19 17:33:48 -0400140 seamlessFallback = ImageView(context)
141 whenever(holder.seamlessFallback).thenReturn(seamlessFallback)
Robert Snoeberger45be3fb2020-05-19 02:00:43 -0400142 seekBar = SeekBar(context)
143 whenever(holder.seekBar).thenReturn(seekBar)
144 elapsedTimeView = TextView(context)
145 whenever(holder.elapsedTimeView).thenReturn(elapsedTimeView)
146 totalTimeView = TextView(context)
147 whenever(holder.totalTimeView).thenReturn(totalTimeView)
148 action0 = ImageButton(context)
149 whenever(holder.action0).thenReturn(action0)
150 action1 = ImageButton(context)
151 whenever(holder.action1).thenReturn(action1)
152 action2 = ImageButton(context)
153 whenever(holder.action2).thenReturn(action2)
154 action3 = ImageButton(context)
155 whenever(holder.action3).thenReturn(action3)
156 action4 = ImageButton(context)
157 whenever(holder.action4).thenReturn(action4)
158
159 // Create media session
160 val metadataBuilder = MediaMetadata.Builder().apply {
161 putString(MediaMetadata.METADATA_KEY_ARTIST, SESSION_ARTIST)
162 putString(MediaMetadata.METADATA_KEY_TITLE, SESSION_TITLE)
163 }
164 val playbackBuilder = PlaybackState.Builder().apply {
165 setState(PlaybackState.STATE_PAUSED, 6000L, 1f)
166 setActions(PlaybackState.ACTION_PLAY)
167 }
168 session = MediaSession(context, SESSION_KEY).apply {
169 setMetadata(metadataBuilder.build())
170 setPlaybackState(playbackBuilder.build())
171 }
172 session.setActive(true)
173 }
174
175 @After
176 fun tearDown() {
177 session.release()
178 player.onDestroy()
179 }
180
181 @Test
182 fun bindWhenUnattached() {
183 val state = MediaData(true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(),
Lucas Dupin84f5a0e2020-06-08 19:55:33 -0700184 emptyList(), PACKAGE, null, null, device, true, null)
Robert Snoeberger45be3fb2020-05-19 02:00:43 -0400185 player.bind(state)
186 assertThat(player.isPlaying()).isFalse()
187 }
188
189 @Test
190 fun bindText() {
191 player.attach(holder)
192 val state = MediaData(true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(),
Lucas Dupin84f5a0e2020-06-08 19:55:33 -0700193 emptyList(), PACKAGE, session.getSessionToken(), null, device, true, null)
Robert Snoeberger45be3fb2020-05-19 02:00:43 -0400194 player.bind(state)
195 assertThat(appName.getText()).isEqualTo(APP)
196 assertThat(titleText.getText()).isEqualTo(TITLE)
197 assertThat(artistText.getText()).isEqualTo(ARTIST)
198 }
199
200 @Test
201 fun bindBackgroundColor() {
202 player.attach(holder)
203 val state = MediaData(true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(),
Lucas Dupin84f5a0e2020-06-08 19:55:33 -0700204 emptyList(), PACKAGE, session.getSessionToken(), null, device, true, null)
Robert Snoeberger45be3fb2020-05-19 02:00:43 -0400205 player.bind(state)
Selim Cinek2de5ebb2020-05-20 15:39:03 -0700206 val list = ArgumentCaptor.forClass(ColorStateList::class.java)
207 verify(view).setBackgroundTintList(list.capture())
208 assertThat(list.value).isEqualTo(ColorStateList.valueOf(BG_COLOR))
Robert Snoeberger45be3fb2020-05-19 02:00:43 -0400209 }
Robert Snoeberger23e36892020-05-21 00:33:24 -0400210
211 @Test
212 fun bindDevice() {
213 player.attach(holder)
214 val state = MediaData(true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(),
Lucas Dupin84f5a0e2020-06-08 19:55:33 -0700215 emptyList(), PACKAGE, session.getSessionToken(), null, device, true, null)
Robert Snoeberger23e36892020-05-21 00:33:24 -0400216 player.bind(state)
217 assertThat(seamlessText.getText()).isEqualTo(DEVICE_NAME)
218 assertThat(seamless.isEnabled()).isTrue()
219 }
220
221 @Test
222 fun bindDisabledDevice() {
Robert Snoeberger07340432020-06-19 17:33:48 -0400223 seamless.id = 1
224 seamlessFallback.id = 2
Robert Snoeberger23e36892020-05-21 00:33:24 -0400225 player.attach(holder)
226 val state = MediaData(true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(),
Lucas Dupin84f5a0e2020-06-08 19:55:33 -0700227 emptyList(), PACKAGE, session.getSessionToken(), null, disabledDevice, true, null)
Robert Snoeberger23e36892020-05-21 00:33:24 -0400228 player.bind(state)
Robert Snoeberger07340432020-06-19 17:33:48 -0400229 verify(expandedSet).setVisibility(seamless.id, View.GONE)
230 verify(expandedSet).setVisibility(seamlessFallback.id, View.VISIBLE)
231 verify(collapsedSet).setVisibility(seamless.id, View.GONE)
232 verify(collapsedSet).setVisibility(seamlessFallback.id, View.VISIBLE)
Robert Snoeberger23e36892020-05-21 00:33:24 -0400233 }
234
235 @Test
236 fun bindNullDevice() {
237 player.attach(holder)
238 val state = MediaData(true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(),
Lucas Dupin84f5a0e2020-06-08 19:55:33 -0700239 emptyList(), PACKAGE, session.getSessionToken(), null, null, true, null)
Robert Snoeberger23e36892020-05-21 00:33:24 -0400240 player.bind(state)
241 assertThat(seamless.isEnabled()).isTrue()
242 assertThat(seamlessText.getText()).isEqualTo(context.getResources().getString(
243 com.android.internal.R.string.ext_media_seamless_action))
244 }
Robert Snoebergerf05aa68f2020-06-18 16:14:29 -0400245
246 @Test
247 fun bindDeviceResumptionPlayer() {
248 player.attach(holder)
249 val state = MediaData(true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(),
250 emptyList(), PACKAGE, session.getSessionToken(), null, device, true, null,
251 resumption = true)
252 player.bind(state)
253 assertThat(seamlessText.getText()).isEqualTo(DEVICE_NAME)
254 assertThat(seamless.isEnabled()).isFalse()
255 }
Robert Snoeberger45be3fb2020-05-19 02:00:43 -0400256}