blob: e6a41fbac3b55d1e6ce80b276b4ecca0b6da606d [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
28import android.view.ViewGroup
29import android.widget.FrameLayout
30import android.widget.ImageButton
31import android.widget.ImageView
32import android.widget.SeekBar
33import android.widget.TextView
Lucas Dupin84f5a0e2020-06-08 19:55:33 -070034import androidx.constraintlayout.widget.ConstraintSet
Robert Snoeberger7cec5422020-05-29 17:09:14 -040035import androidx.lifecycle.LiveData
Robert Snoeberger45be3fb2020-05-19 02:00:43 -040036import androidx.test.filters.SmallTest
Robert Snoeberger45be3fb2020-05-19 02:00:43 -040037import com.android.systemui.R
38import com.android.systemui.SysuiTestCase
39import com.android.systemui.plugins.ActivityStarter
Selim Cinek2de5ebb2020-05-20 15:39:03 -070040import com.android.systemui.util.animation.TransitionLayout
Robert Snoeberger45be3fb2020-05-19 02:00:43 -040041import com.android.systemui.util.concurrency.FakeExecutor
42import com.android.systemui.util.time.FakeSystemClock
Robert Snoeberger45be3fb2020-05-19 02:00:43 -040043import com.google.common.truth.Truth.assertThat
Robert Snoeberger45be3fb2020-05-19 02:00:43 -040044import org.junit.After
45import org.junit.Before
Robert Snoeberger7cec5422020-05-29 17:09:14 -040046import org.junit.Rule
Robert Snoeberger45be3fb2020-05-19 02:00:43 -040047import org.junit.Test
48import org.junit.runner.RunWith
Selim Cinek2de5ebb2020-05-20 15:39:03 -070049import org.mockito.ArgumentCaptor
Robert Snoeberger45be3fb2020-05-19 02:00:43 -040050import org.mockito.Mock
51import org.mockito.Mockito.mock
Selim Cinek2de5ebb2020-05-20 15:39:03 -070052import org.mockito.Mockito.verify
Robert Snoeberger45be3fb2020-05-19 02:00:43 -040053import org.mockito.Mockito.`when` as whenever
Robert Snoeberger7cec5422020-05-29 17:09:14 -040054import org.mockito.junit.MockitoJUnit
Robert Snoeberger45be3fb2020-05-19 02:00:43 -040055
Robert Snoeberger45be3fb2020-05-19 02:00:43 -040056private const val KEY = "TEST_KEY"
57private const val APP = "APP"
58private const val BG_COLOR = Color.RED
59private const val PACKAGE = "PKG"
60private const val ARTIST = "ARTIST"
61private const val TITLE = "TITLE"
62private const val DEVICE_NAME = "DEVICE_NAME"
63private const val SESSION_KEY = "SESSION_KEY"
64private const val SESSION_ARTIST = "SESSION_ARTIST"
65private const val SESSION_TITLE = "SESSION_TITLE"
66
67@SmallTest
68@RunWith(AndroidTestingRunner::class)
69@TestableLooper.RunWithLooper(setAsMainLooper = true)
70public class MediaControlPanelTest : SysuiTestCase() {
71
72 private lateinit var player: MediaControlPanel
73
Robert Snoeberger45be3fb2020-05-19 02:00:43 -040074 private lateinit var bgExecutor: FakeExecutor
75 @Mock private lateinit var activityStarter: ActivityStarter
76
77 @Mock private lateinit var holder: PlayerViewHolder
Selim Cinek2de5ebb2020-05-20 15:39:03 -070078 @Mock private lateinit var view: TransitionLayout
Robert Snoeberger7cec5422020-05-29 17:09:14 -040079 @Mock private lateinit var seekBarViewModel: SeekBarViewModel
80 @Mock private lateinit var seekBarData: LiveData<SeekBarViewModel.Progress>
Lucas Dupin84f5a0e2020-06-08 19:55:33 -070081 @Mock private lateinit var mediaViewController: MediaViewController
Robert Snoeberger45be3fb2020-05-19 02:00:43 -040082 private lateinit var appIcon: ImageView
83 private lateinit var appName: TextView
84 private lateinit var albumView: ImageView
85 private lateinit var titleText: TextView
86 private lateinit var artistText: TextView
87 private lateinit var seamless: ViewGroup
88 private lateinit var seamlessIcon: ImageView
89 private lateinit var seamlessText: TextView
90 private lateinit var seekBar: SeekBar
91 private lateinit var elapsedTimeView: TextView
92 private lateinit var totalTimeView: TextView
93 private lateinit var action0: ImageButton
94 private lateinit var action1: ImageButton
95 private lateinit var action2: ImageButton
96 private lateinit var action3: ImageButton
97 private lateinit var action4: ImageButton
98
99 private lateinit var session: MediaSession
Robert Snoeberger23e36892020-05-21 00:33:24 -0400100 private val device = MediaDeviceData(true, null, DEVICE_NAME)
101 private val disabledDevice = MediaDeviceData(false, null, null)
Robert Snoeberger45be3fb2020-05-19 02:00:43 -0400102
Robert Snoeberger7cec5422020-05-29 17:09:14 -0400103 @JvmField @Rule val mockito = MockitoJUnit.rule()
104
Robert Snoeberger45be3fb2020-05-19 02:00:43 -0400105 @Before
106 fun setUp() {
Robert Snoeberger45be3fb2020-05-19 02:00:43 -0400107 bgExecutor = FakeExecutor(FakeSystemClock())
Lucas Dupin84f5a0e2020-06-08 19:55:33 -0700108 whenever(mediaViewController.expandedLayout).thenReturn(mock(ConstraintSet::class.java))
109 whenever(mediaViewController.collapsedLayout).thenReturn(mock(ConstraintSet::class.java))
Robert Snoeberger45be3fb2020-05-19 02:00:43 -0400110
Lucas Dupin84f5a0e2020-06-08 19:55:33 -0700111 player = MediaControlPanel(context, bgExecutor, activityStarter, mediaViewController,
Robert Snoeberger7cec5422020-05-29 17:09:14 -0400112 seekBarViewModel)
113 whenever(seekBarViewModel.progress).thenReturn(seekBarData)
Robert Snoeberger45be3fb2020-05-19 02:00:43 -0400114
115 // Mock out a view holder for the player to attach to.
Selim Cinek2de5ebb2020-05-20 15:39:03 -0700116 whenever(holder.player).thenReturn(view)
Robert Snoeberger45be3fb2020-05-19 02:00:43 -0400117 appIcon = ImageView(context)
118 whenever(holder.appIcon).thenReturn(appIcon)
119 appName = TextView(context)
120 whenever(holder.appName).thenReturn(appName)
121 albumView = ImageView(context)
122 whenever(holder.albumView).thenReturn(albumView)
123 titleText = TextView(context)
124 whenever(holder.titleText).thenReturn(titleText)
125 artistText = TextView(context)
126 whenever(holder.artistText).thenReturn(artistText)
127 seamless = FrameLayout(context)
Robert Snoeberger70d0d6b2020-05-14 16:47:02 -0400128 val seamlessBackground = mock(RippleDrawable::class.java)
Lucas Dupin5feeaca2020-05-28 11:01:54 -0700129 seamless.foreground = seamlessBackground
Robert Snoeberger70d0d6b2020-05-14 16:47:02 -0400130 whenever(seamlessBackground.getDrawable(0)).thenReturn(mock(GradientDrawable::class.java))
Robert Snoeberger45be3fb2020-05-19 02:00:43 -0400131 whenever(holder.seamless).thenReturn(seamless)
132 seamlessIcon = ImageView(context)
133 whenever(holder.seamlessIcon).thenReturn(seamlessIcon)
134 seamlessText = TextView(context)
135 whenever(holder.seamlessText).thenReturn(seamlessText)
136 seekBar = SeekBar(context)
137 whenever(holder.seekBar).thenReturn(seekBar)
138 elapsedTimeView = TextView(context)
139 whenever(holder.elapsedTimeView).thenReturn(elapsedTimeView)
140 totalTimeView = TextView(context)
141 whenever(holder.totalTimeView).thenReturn(totalTimeView)
142 action0 = ImageButton(context)
143 whenever(holder.action0).thenReturn(action0)
144 action1 = ImageButton(context)
145 whenever(holder.action1).thenReturn(action1)
146 action2 = ImageButton(context)
147 whenever(holder.action2).thenReturn(action2)
148 action3 = ImageButton(context)
149 whenever(holder.action3).thenReturn(action3)
150 action4 = ImageButton(context)
151 whenever(holder.action4).thenReturn(action4)
152
153 // Create media session
154 val metadataBuilder = MediaMetadata.Builder().apply {
155 putString(MediaMetadata.METADATA_KEY_ARTIST, SESSION_ARTIST)
156 putString(MediaMetadata.METADATA_KEY_TITLE, SESSION_TITLE)
157 }
158 val playbackBuilder = PlaybackState.Builder().apply {
159 setState(PlaybackState.STATE_PAUSED, 6000L, 1f)
160 setActions(PlaybackState.ACTION_PLAY)
161 }
162 session = MediaSession(context, SESSION_KEY).apply {
163 setMetadata(metadataBuilder.build())
164 setPlaybackState(playbackBuilder.build())
165 }
166 session.setActive(true)
167 }
168
169 @After
170 fun tearDown() {
171 session.release()
172 player.onDestroy()
173 }
174
175 @Test
176 fun bindWhenUnattached() {
177 val state = MediaData(true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(),
Lucas Dupin84f5a0e2020-06-08 19:55:33 -0700178 emptyList(), PACKAGE, null, null, device, true, null)
Robert Snoeberger45be3fb2020-05-19 02:00:43 -0400179 player.bind(state)
180 assertThat(player.isPlaying()).isFalse()
181 }
182
183 @Test
184 fun bindText() {
185 player.attach(holder)
186 val state = MediaData(true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(),
Lucas Dupin84f5a0e2020-06-08 19:55:33 -0700187 emptyList(), PACKAGE, session.getSessionToken(), null, device, true, null)
Robert Snoeberger45be3fb2020-05-19 02:00:43 -0400188 player.bind(state)
189 assertThat(appName.getText()).isEqualTo(APP)
190 assertThat(titleText.getText()).isEqualTo(TITLE)
191 assertThat(artistText.getText()).isEqualTo(ARTIST)
192 }
193
194 @Test
195 fun bindBackgroundColor() {
196 player.attach(holder)
197 val state = MediaData(true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(),
Lucas Dupin84f5a0e2020-06-08 19:55:33 -0700198 emptyList(), PACKAGE, session.getSessionToken(), null, device, true, null)
Robert Snoeberger45be3fb2020-05-19 02:00:43 -0400199 player.bind(state)
Selim Cinek2de5ebb2020-05-20 15:39:03 -0700200 val list = ArgumentCaptor.forClass(ColorStateList::class.java)
201 verify(view).setBackgroundTintList(list.capture())
202 assertThat(list.value).isEqualTo(ColorStateList.valueOf(BG_COLOR))
Robert Snoeberger45be3fb2020-05-19 02:00:43 -0400203 }
Robert Snoeberger23e36892020-05-21 00:33:24 -0400204
205 @Test
206 fun bindDevice() {
207 player.attach(holder)
208 val state = MediaData(true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(),
Lucas Dupin84f5a0e2020-06-08 19:55:33 -0700209 emptyList(), PACKAGE, session.getSessionToken(), null, device, true, null)
Robert Snoeberger23e36892020-05-21 00:33:24 -0400210 player.bind(state)
211 assertThat(seamlessText.getText()).isEqualTo(DEVICE_NAME)
212 assertThat(seamless.isEnabled()).isTrue()
213 }
214
215 @Test
216 fun bindDisabledDevice() {
217 player.attach(holder)
218 val state = MediaData(true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(),
Lucas Dupin84f5a0e2020-06-08 19:55:33 -0700219 emptyList(), PACKAGE, session.getSessionToken(), null, disabledDevice, true, null)
Robert Snoeberger23e36892020-05-21 00:33:24 -0400220 player.bind(state)
221 assertThat(seamless.isEnabled()).isFalse()
222 assertThat(seamlessText.getText()).isEqualTo(context.getResources().getString(
223 R.string.media_seamless_remote_device))
224 }
225
226 @Test
227 fun bindNullDevice() {
228 player.attach(holder)
229 val state = MediaData(true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(),
Lucas Dupin84f5a0e2020-06-08 19:55:33 -0700230 emptyList(), PACKAGE, session.getSessionToken(), null, null, true, null)
Robert Snoeberger23e36892020-05-21 00:33:24 -0400231 player.bind(state)
232 assertThat(seamless.isEnabled()).isTrue()
233 assertThat(seamlessText.getText()).isEqualTo(context.getResources().getString(
234 com.android.internal.R.string.ext_media_seamless_action))
235 }
Robert Snoeberger45be3fb2020-05-19 02:00:43 -0400236}