1 /*
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 
17 package com.android.systemui.media.controls.ui.binder
18 
19 import android.animation.Animator
20 import android.animation.ObjectAnimator
21 import android.testing.TestableLooper
22 import android.view.View
23 import android.widget.SeekBar
24 import android.widget.TextView
25 import androidx.test.ext.junit.runners.AndroidJUnit4
26 import androidx.test.filters.SmallTest
27 import com.android.systemui.SysuiTestCase
28 import com.android.systemui.media.controls.ui.drawable.SquigglyProgress
29 import com.android.systemui.media.controls.ui.view.MediaViewHolder
30 import com.android.systemui.media.controls.ui.viewmodel.SeekBarViewModel
31 import com.android.systemui.res.R
32 import com.google.common.truth.Truth.assertThat
33 import org.junit.Before
34 import org.junit.Rule
35 import org.junit.Test
36 import org.junit.runner.RunWith
37 import org.mockito.Mock
38 import org.mockito.Mockito.verify
39 import org.mockito.Mockito.`when` as whenever
40 import org.mockito.junit.MockitoJUnit
41 
42 @SmallTest
43 @RunWith(AndroidJUnit4::class)
44 @TestableLooper.RunWithLooper
45 class SeekBarObserverTest : SysuiTestCase() {
46 
47     private val disabledHeight = 1
48     private val enabledHeight = 2
49 
50     private lateinit var observer: SeekBarObserver
51     @Mock private lateinit var mockSeekbarAnimator: ObjectAnimator
52     @Mock private lateinit var mockHolder: MediaViewHolder
53     @Mock private lateinit var mockSquigglyProgress: SquigglyProgress
54     private lateinit var seekBarView: SeekBar
55     private lateinit var scrubbingElapsedTimeView: TextView
56     private lateinit var scrubbingTotalTimeView: TextView
57 
58     @JvmField @Rule val mockitoRule = MockitoJUnit.rule()
59 
60     @Before
setUpnull61     fun setUp() {
62         context.orCreateTestableResources.addOverride(
63             R.dimen.qs_media_enabled_seekbar_height,
64             enabledHeight
65         )
66         context.orCreateTestableResources.addOverride(
67             R.dimen.qs_media_disabled_seekbar_height,
68             disabledHeight
69         )
70 
71         seekBarView = SeekBar(context)
72         seekBarView.progressDrawable = mockSquigglyProgress
73         scrubbingElapsedTimeView = TextView(context)
74         scrubbingTotalTimeView = TextView(context)
75         whenever(mockHolder.seekBar).thenReturn(seekBarView)
76         whenever(mockHolder.scrubbingElapsedTimeView).thenReturn(scrubbingElapsedTimeView)
77         whenever(mockHolder.scrubbingTotalTimeView).thenReturn(scrubbingTotalTimeView)
78 
79         observer =
80             object : SeekBarObserver(mockHolder) {
81                 override fun buildResetAnimator(targetTime: Int): Animator {
82                     return mockSeekbarAnimator
83                 }
84             }
85     }
86 
87     @Test
seekBarGonenull88     fun seekBarGone() {
89         // WHEN seek bar is disabled
90         val isEnabled = false
91         val data = SeekBarViewModel.Progress(isEnabled, false, false, false, null, 0, false)
92         observer.onChanged(data)
93         // THEN seek bar shows just a thin line with no text
94         assertThat(seekBarView.isEnabled()).isFalse()
95         assertThat(seekBarView.getThumb().getAlpha()).isEqualTo(0)
96         assertThat(seekBarView.contentDescription).isEqualTo("")
97         assertThat(seekBarView.maxHeight).isEqualTo(disabledHeight)
98     }
99 
100     @Test
seekBarVisiblenull101     fun seekBarVisible() {
102         // WHEN seek bar is enabled
103         val isEnabled = true
104         val data = SeekBarViewModel.Progress(isEnabled, true, false, false, 3000, 12000, true)
105         observer.onChanged(data)
106         // THEN seek bar is visible and thick
107         assertThat(seekBarView.getVisibility()).isEqualTo(View.VISIBLE)
108         assertThat(seekBarView.maxHeight).isEqualTo(enabledHeight)
109     }
110 
111     @Test
seekBarProgressnull112     fun seekBarProgress() {
113         // WHEN part of the track has been played
114         val data = SeekBarViewModel.Progress(true, true, true, false, 3000, 120000, true)
115         observer.onChanged(data)
116         // THEN seek bar shows the progress
117         assertThat(seekBarView.progress).isEqualTo(3000)
118         assertThat(seekBarView.max).isEqualTo(120000)
119 
120         val desc = context.getString(R.string.controls_media_seekbar_description, "00:03", "02:00")
121         assertThat(seekBarView.contentDescription).isEqualTo(desc)
122     }
123 
124     @Test
seekBarDisabledWhenSeekNotAvailablenull125     fun seekBarDisabledWhenSeekNotAvailable() {
126         // WHEN seek is not available
127         val isSeekAvailable = false
128         val data =
129             SeekBarViewModel.Progress(true, isSeekAvailable, false, false, 3000, 120000, false)
130         observer.onChanged(data)
131         // THEN seek bar is not enabled
132         assertThat(seekBarView.isEnabled()).isFalse()
133     }
134 
135     @Test
seekBarEnabledWhenSeekNotAvailablenull136     fun seekBarEnabledWhenSeekNotAvailable() {
137         // WHEN seek is available
138         val isSeekAvailable = true
139         val data =
140             SeekBarViewModel.Progress(true, isSeekAvailable, false, false, 3000, 120000, false)
141         observer.onChanged(data)
142         // THEN seek bar is not enabled
143         assertThat(seekBarView.isEnabled()).isTrue()
144     }
145 
146     @Test
seekBarPlayingNotScrubbingnull147     fun seekBarPlayingNotScrubbing() {
148         // WHEN playing
149         val isPlaying = true
150         val isScrubbing = false
151         val data = SeekBarViewModel.Progress(true, true, isPlaying, isScrubbing, 3000, 120000, true)
152         observer.onChanged(data)
153         // THEN progress drawable is animating
154         verify(mockSquigglyProgress).animate = true
155     }
156 
157     @Test
seekBarNotPlayingNotScrubbingnull158     fun seekBarNotPlayingNotScrubbing() {
159         // WHEN not playing & not scrubbing
160         val isPlaying = false
161         val isScrubbing = false
162         val data = SeekBarViewModel.Progress(true, true, isPlaying, isScrubbing, 3000, 120000, true)
163         observer.onChanged(data)
164         // THEN progress drawable is not animating
165         verify(mockSquigglyProgress).animate = false
166     }
167 
168     @Test
seekbarNotListeningNotScrubbingPlayingnull169     fun seekbarNotListeningNotScrubbingPlaying() {
170         // WHEN playing
171         val isPlaying = true
172         val isScrubbing = false
173         val data =
174             SeekBarViewModel.Progress(true, true, isPlaying, isScrubbing, 3000, 120000, false)
175         observer.onChanged(data)
176         // THEN progress drawable is not animating
177         verify(mockSquigglyProgress).animate = false
178     }
179 
180     @Test
seekBarPlayingScrubbingnull181     fun seekBarPlayingScrubbing() {
182         // WHEN playing & scrubbing
183         val isPlaying = true
184         val isScrubbing = true
185         val data = SeekBarViewModel.Progress(true, true, isPlaying, isScrubbing, 3000, 120000, true)
186         observer.onChanged(data)
187         // THEN progress drawable is not animating
188         verify(mockSquigglyProgress).animate = false
189     }
190 
191     @Test
seekBarNotPlayingScrubbingnull192     fun seekBarNotPlayingScrubbing() {
193         // WHEN playing & scrubbing
194         val isPlaying = false
195         val isScrubbing = true
196         val data = SeekBarViewModel.Progress(true, true, isPlaying, isScrubbing, 3000, 120000, true)
197         observer.onChanged(data)
198         // THEN progress drawable is not animating
199         verify(mockSquigglyProgress).animate = false
200     }
201 
202     @Test
seekBarProgress_enabledAndScrubbing_timeViewsHaveTimenull203     fun seekBarProgress_enabledAndScrubbing_timeViewsHaveTime() {
204         val isEnabled = true
205         val isScrubbing = true
206         val data = SeekBarViewModel.Progress(isEnabled, true, true, isScrubbing, 3000, 120000, true)
207 
208         observer.onChanged(data)
209 
210         assertThat(scrubbingElapsedTimeView.text).isEqualTo("00:03")
211         assertThat(scrubbingTotalTimeView.text).isEqualTo("02:00")
212     }
213 
214     @Test
seekBarProgress_disabledAndScrubbing_timeViewsEmptynull215     fun seekBarProgress_disabledAndScrubbing_timeViewsEmpty() {
216         val isEnabled = false
217         val isScrubbing = true
218         val data = SeekBarViewModel.Progress(isEnabled, true, true, isScrubbing, 3000, 120000, true)
219 
220         observer.onChanged(data)
221 
222         assertThat(scrubbingElapsedTimeView.text).isEqualTo("")
223         assertThat(scrubbingTotalTimeView.text).isEqualTo("")
224     }
225 
226     @Test
seekBarProgress_enabledAndNotScrubbing_timeViewsEmptynull227     fun seekBarProgress_enabledAndNotScrubbing_timeViewsEmpty() {
228         val isEnabled = true
229         val isScrubbing = false
230         val data = SeekBarViewModel.Progress(isEnabled, true, true, isScrubbing, 3000, 120000, true)
231 
232         observer.onChanged(data)
233 
234         assertThat(scrubbingElapsedTimeView.text).isEqualTo("")
235         assertThat(scrubbingTotalTimeView.text).isEqualTo("")
236     }
237 
238     @Test
seekBarJumpAnimationnull239     fun seekBarJumpAnimation() {
240         val data0 = SeekBarViewModel.Progress(true, true, true, false, 4000, 120000, true)
241         val data1 = SeekBarViewModel.Progress(true, true, true, false, 10, 120000, true)
242 
243         // Set initial position of progress bar
244         observer.onChanged(data0)
245         assertThat(seekBarView.progress).isEqualTo(4000)
246         assertThat(seekBarView.max).isEqualTo(120000)
247 
248         // Change to second data & confirm no change to position (due to animation delay)
249         observer.onChanged(data1)
250         assertThat(seekBarView.progress).isEqualTo(4000)
251         verify(mockSeekbarAnimator).start()
252     }
253 
254     @Test
seekbarActive_animationsDisablednull255     fun seekbarActive_animationsDisabled() {
256         // WHEN playing, but animations have been disabled
257         observer.animationEnabled = false
258         val isPlaying = true
259         val isScrubbing = false
260         val data = SeekBarViewModel.Progress(true, true, isPlaying, isScrubbing, 3000, 120000, true)
261         observer.onChanged(data)
262 
263         // THEN progress drawable does not animate
264         verify(mockSquigglyProgress).animate = false
265     }
266 }
267