blob: f316d0480fac2fa29ef5fb96c8c55fa865223010 [file] [log] [blame]
Robert Snoeberger7dffd372020-04-01 17:32:44 -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.graphics.Color
20import android.media.MediaMetadata
21import android.media.session.MediaController
22import android.media.session.PlaybackState
23import android.testing.AndroidTestingRunner
24import android.testing.TestableLooper
25import android.widget.SeekBar
26import androidx.arch.core.executor.ArchTaskExecutor
27import androidx.arch.core.executor.TaskExecutor
28import androidx.test.filters.SmallTest
29
30import com.android.systemui.SysuiTestCase
31import com.android.systemui.util.concurrency.FakeExecutor
32import com.android.systemui.util.time.FakeSystemClock
33import com.google.common.truth.Truth.assertThat
34
35import org.junit.After
36import org.junit.Before
37import org.junit.Test
38import org.junit.runner.RunWith
39import org.mockito.Mock
40import org.mockito.Mockito.mock
41import org.mockito.Mockito.never
42import org.mockito.Mockito.verify
43import org.mockito.Mockito.`when` as whenever
44
45@SmallTest
46@RunWith(AndroidTestingRunner::class)
47@TestableLooper.RunWithLooper
48public class SeekBarViewModelTest : SysuiTestCase() {
49
50 private lateinit var viewModel: SeekBarViewModel
51 private lateinit var fakeExecutor: FakeExecutor
52 private val taskExecutor: TaskExecutor = object : TaskExecutor() {
53 override fun executeOnDiskIO(runnable: Runnable) {
54 runnable.run()
55 }
56 override fun postToMainThread(runnable: Runnable) {
57 runnable.run()
58 }
59 override fun isMainThread(): Boolean {
60 return true
61 }
62 }
63 @Mock private lateinit var mockController: MediaController
64 @Mock private lateinit var mockTransport: MediaController.TransportControls
65
66 @Before
67 fun setUp() {
68 fakeExecutor = FakeExecutor(FakeSystemClock())
69 viewModel = SeekBarViewModel(fakeExecutor)
70 mockController = mock(MediaController::class.java)
71 mockTransport = mock(MediaController.TransportControls::class.java)
72
73 // LiveData to run synchronously
74 ArchTaskExecutor.getInstance().setDelegate(taskExecutor)
75 }
76
77 @After
78 fun tearDown() {
79 ArchTaskExecutor.getInstance().setDelegate(null)
80 }
81
82 @Test
83 fun updateColor() {
84 viewModel.updateController(mockController, Color.RED)
85 assertThat(viewModel.progress.value!!.color).isEqualTo(Color.RED)
86 }
87
88 @Test
89 fun updateDuration() {
90 // GIVEN that the duration is contained within the metadata
91 val duration = 12000L
92 val metadata = MediaMetadata.Builder().run {
93 putLong(MediaMetadata.METADATA_KEY_DURATION, duration)
94 build()
95 }
96 whenever(mockController.getMetadata()).thenReturn(metadata)
97 // WHEN the controller is updated
98 viewModel.updateController(mockController, Color.RED)
99 // THEN the duration is extracted
100 assertThat(viewModel.progress.value!!.duration).isEqualTo(duration)
101 assertThat(viewModel.progress.value!!.enabled).isTrue()
102 }
103
104 @Test
105 fun updateDurationNegative() {
106 // GIVEN that the duration is negative
107 val duration = -1L
108 val metadata = MediaMetadata.Builder().run {
109 putLong(MediaMetadata.METADATA_KEY_DURATION, duration)
110 build()
111 }
112 whenever(mockController.getMetadata()).thenReturn(metadata)
113 // WHEN the controller is updated
114 viewModel.updateController(mockController, Color.RED)
115 // THEN the seek bar is disabled
116 assertThat(viewModel.progress.value!!.enabled).isFalse()
117 }
118
119 @Test
120 fun updateDurationZero() {
121 // GIVEN that the duration is zero
122 val duration = 0L
123 val metadata = MediaMetadata.Builder().run {
124 putLong(MediaMetadata.METADATA_KEY_DURATION, duration)
125 build()
126 }
127 whenever(mockController.getMetadata()).thenReturn(metadata)
128 // WHEN the controller is updated
129 viewModel.updateController(mockController, Color.RED)
130 // THEN the seek bar is disabled
131 assertThat(viewModel.progress.value!!.enabled).isFalse()
132 }
133
134 @Test
135 fun updateElapsedTime() {
136 // GIVEN that the PlaybackState contins the current position
137 val position = 200L
138 val state = PlaybackState.Builder().run {
139 setState(PlaybackState.STATE_PLAYING, position, 1f)
140 build()
141 }
142 whenever(mockController.getPlaybackState()).thenReturn(state)
143 // WHEN the controller is updated
144 viewModel.updateController(mockController, Color.RED)
145 // THEN elapsed time is captured
146 assertThat(viewModel.progress.value!!.elapsedTime).isEqualTo(200.toInt())
147 }
148
149 @Test
150 fun updateSeekAvailable() {
151 // GIVEN that seek is included in actions
152 val state = PlaybackState.Builder().run {
153 setActions(PlaybackState.ACTION_SEEK_TO)
154 build()
155 }
156 whenever(mockController.getPlaybackState()).thenReturn(state)
157 // WHEN the controller is updated
158 viewModel.updateController(mockController, Color.RED)
159 // THEN seek is available
160 assertThat(viewModel.progress.value!!.seekAvailable).isTrue()
161 }
162
163 @Test
164 fun updateSeekNotAvailable() {
165 // GIVEN that seek is not included in actions
166 val state = PlaybackState.Builder().run {
167 setActions(PlaybackState.ACTION_PLAY)
168 build()
169 }
170 whenever(mockController.getPlaybackState()).thenReturn(state)
171 // WHEN the controller is updated
172 viewModel.updateController(mockController, Color.RED)
173 // THEN seek is not available
174 assertThat(viewModel.progress.value!!.seekAvailable).isFalse()
175 }
176
177 @Test
178 fun handleSeek() {
179 whenever(mockController.getTransportControls()).thenReturn(mockTransport)
180 viewModel.updateController(mockController, Color.RED)
181 // WHEN user input is dispatched
182 val pos = 42L
183 viewModel.onSeek(pos)
184 fakeExecutor.runAllReady()
185 // THEN transport controls should be used
186 verify(mockTransport).seekTo(pos)
187 }
188
189 @Test
190 fun handleProgressChangedUser() {
191 whenever(mockController.getTransportControls()).thenReturn(mockTransport)
192 viewModel.updateController(mockController, Color.RED)
193 // WHEN user starts dragging the seek bar
194 val pos = 42
195 viewModel.seekBarListener.onProgressChanged(SeekBar(context), pos, true)
196 fakeExecutor.runAllReady()
197 // THEN transport controls should be used
198 verify(mockTransport).seekTo(pos.toLong())
199 }
200
201 @Test
202 fun handleProgressChangedOther() {
203 whenever(mockController.getTransportControls()).thenReturn(mockTransport)
204 viewModel.updateController(mockController, Color.RED)
205 // WHEN user starts dragging the seek bar
206 val pos = 42
207 viewModel.seekBarListener.onProgressChanged(SeekBar(context), pos, false)
208 fakeExecutor.runAllReady()
209 // THEN transport controls should be used
210 verify(mockTransport, never()).seekTo(pos.toLong())
211 }
212
213 @Test
214 fun handleStartTrackingTouch() {
215 whenever(mockController.getTransportControls()).thenReturn(mockTransport)
216 viewModel.updateController(mockController, Color.RED)
217 // WHEN user starts dragging the seek bar
218 val pos = 42
219 val bar = SeekBar(context).apply {
220 progress = pos
221 }
222 viewModel.seekBarListener.onStartTrackingTouch(bar)
223 fakeExecutor.runAllReady()
224 // THEN transport controls should be used
225 verify(mockTransport, never()).seekTo(pos.toLong())
226 }
227
228 @Test
229 fun handleStopTrackingTouch() {
230 whenever(mockController.getTransportControls()).thenReturn(mockTransport)
231 viewModel.updateController(mockController, Color.RED)
232 // WHEN user ends drag
233 val pos = 42
234 val bar = SeekBar(context).apply {
235 progress = pos
236 }
237 viewModel.seekBarListener.onStopTrackingTouch(bar)
238 fakeExecutor.runAllReady()
239 // THEN transport controls should be used
240 verify(mockTransport).seekTo(pos.toLong())
241 }
242
243 @Test
244 fun queuePollTaskWhenPlaying() {
245 // GIVEN that the track is playing
246 val state = PlaybackState.Builder().run {
247 setState(PlaybackState.STATE_PLAYING, 100L, 1f)
248 build()
249 }
250 whenever(mockController.getPlaybackState()).thenReturn(state)
251 // WHEN the controller is updated
252 viewModel.updateController(mockController, Color.RED)
253 // THEN a task is queued
254 assertThat(fakeExecutor.numPending()).isEqualTo(1)
255 }
256
257 @Test
258 fun noQueuePollTaskWhenStopped() {
259 // GIVEN that the playback state is stopped
260 val state = PlaybackState.Builder().run {
261 setState(PlaybackState.STATE_STOPPED, 200L, 1f)
262 build()
263 }
264 whenever(mockController.getPlaybackState()).thenReturn(state)
265 // WHEN updated
266 viewModel.updateController(mockController, Color.RED)
267 // THEN an update task is not queued
268 assertThat(fakeExecutor.numPending()).isEqualTo(0)
269 }
270
271 @Test
272 fun queuePollTaskWhenListening() {
273 // GIVEN listening
274 viewModel.listening = true
275 with(fakeExecutor) {
276 advanceClockToNext()
277 runAllReady()
278 }
279 // AND the playback state is playing
280 val state = PlaybackState.Builder().run {
281 setState(PlaybackState.STATE_PLAYING, 200L, 1f)
282 build()
283 }
284 whenever(mockController.getPlaybackState()).thenReturn(state)
285 // WHEN updated
286 viewModel.updateController(mockController, Color.RED)
287 // THEN an update task is queued
288 assertThat(fakeExecutor.numPending()).isEqualTo(1)
289 }
290
291 @Test
292 fun noQueuePollTaskWhenNotListening() {
293 // GIVEN not listening
294 viewModel.listening = false
295 with(fakeExecutor) {
296 advanceClockToNext()
297 runAllReady()
298 }
299 // AND the playback state is playing
300 val state = PlaybackState.Builder().run {
301 setState(PlaybackState.STATE_STOPPED, 200L, 1f)
302 build()
303 }
304 whenever(mockController.getPlaybackState()).thenReturn(state)
305 // WHEN updated
306 viewModel.updateController(mockController, Color.RED)
307 // THEN an update task is not queued
308 assertThat(fakeExecutor.numPending()).isEqualTo(0)
309 }
310
311 @Test
312 fun pollTaskQueuesAnotherPollTaskWhenPlaying() {
313 // GIVEN that the track is playing
314 val state = PlaybackState.Builder().run {
315 setState(PlaybackState.STATE_PLAYING, 100L, 1f)
316 build()
317 }
318 whenever(mockController.getPlaybackState()).thenReturn(state)
319 viewModel.updateController(mockController, Color.RED)
320 // WHEN the next task runs
321 with(fakeExecutor) {
322 advanceClockToNext()
323 runAllReady()
324 }
325 // THEN another task is queued
326 assertThat(fakeExecutor.numPending()).isEqualTo(1)
327 }
328
329 @Test
330 fun taskUpdatesProgress() {
331 // GIVEN that the PlaybackState contins the current position
332 val position = 200L
333 val state = PlaybackState.Builder().run {
334 setState(PlaybackState.STATE_PLAYING, position, 1f)
335 build()
336 }
337 whenever(mockController.getPlaybackState()).thenReturn(state)
338 viewModel.updateController(mockController, Color.RED)
339 // AND the playback state advances
340 val nextPosition = 300L
341 val nextState = PlaybackState.Builder().run {
342 setState(PlaybackState.STATE_PLAYING, nextPosition, 1f)
343 build()
344 }
345 whenever(mockController.getPlaybackState()).thenReturn(nextState)
346 // WHEN the task runs
347 with(fakeExecutor) {
348 advanceClockToNext()
349 runAllReady()
350 }
351 // THEN elapsed time is captured
352 assertThat(viewModel.progress.value!!.elapsedTime).isEqualTo(nextPosition.toInt())
353 }
354
355 @Test
356 fun startListeningQueuesPollTask() {
357 // GIVEN not listening
358 viewModel.listening = false
359 with(fakeExecutor) {
360 advanceClockToNext()
361 runAllReady()
362 }
363 // AND the playback state is playing
364 val state = PlaybackState.Builder().run {
365 setState(PlaybackState.STATE_STOPPED, 200L, 1f)
366 build()
367 }
368 whenever(mockController.getPlaybackState()).thenReturn(state)
369 viewModel.updateController(mockController, Color.RED)
370 // WHEN start listening
371 viewModel.listening = true
372 // THEN an update task is queued
373 assertThat(fakeExecutor.numPending()).isEqualTo(1)
374 }
375}