• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
18 
19 import android.content.ComponentName
20 import android.content.Context
21 import android.media.MediaDescription
22 import android.media.browse.MediaBrowser
23 import android.media.session.MediaController
24 import android.media.session.MediaSession
25 import android.service.media.MediaBrowserService
26 import android.testing.AndroidTestingRunner
27 import android.testing.TestableLooper
28 import androidx.test.filters.SmallTest
29 import com.android.systemui.SysuiTestCase
30 import org.junit.Before
31 import org.junit.Test
32 import org.junit.runner.RunWith
33 import org.mockito.ArgumentCaptor
34 import org.mockito.Captor
35 import org.mockito.Mock
36 import org.mockito.Mockito
37 import org.mockito.Mockito.verify
38 import org.mockito.MockitoAnnotations
39 import org.mockito.Mockito.`when` as whenever
40 
41 private const val PACKAGE_NAME = "package"
42 private const val CLASS_NAME = "class"
43 private const val TITLE = "song title"
44 private const val MEDIA_ID = "media ID"
45 private const val ROOT = "media browser root"
46 
capturenull47 private fun <T> capture(argumentCaptor: ArgumentCaptor<T>): T = argumentCaptor.capture()
48 private fun <T> eq(value: T): T = Mockito.eq(value) ?: value
49 private fun <T> any(): T = Mockito.any<T>()
50 
51 @SmallTest
52 @RunWith(AndroidTestingRunner::class)
53 @TestableLooper.RunWithLooper
54 public class ResumeMediaBrowserTest : SysuiTestCase() {
55 
56     private lateinit var resumeBrowser: TestableResumeMediaBrowser
57     private val component = ComponentName(PACKAGE_NAME, CLASS_NAME)
58     private val description = MediaDescription.Builder()
59             .setTitle(TITLE)
60             .setMediaId(MEDIA_ID)
61             .build()
62 
63     @Mock lateinit var callback: ResumeMediaBrowser.Callback
64     @Mock lateinit var listener: MediaResumeListener
65     @Mock lateinit var service: MediaBrowserService
66     @Mock lateinit var browserFactory: MediaBrowserFactory
67     @Mock lateinit var browser: MediaBrowser
68     @Mock lateinit var token: MediaSession.Token
69     @Mock lateinit var mediaController: MediaController
70     @Mock lateinit var transportControls: MediaController.TransportControls
71 
72     @Captor lateinit var connectionCallback: ArgumentCaptor<MediaBrowser.ConnectionCallback>
73     @Captor lateinit var subscriptionCallback: ArgumentCaptor<MediaBrowser.SubscriptionCallback>
74 
75     @Before
76     fun setUp() {
77         MockitoAnnotations.initMocks(this)
78 
79         whenever(browserFactory.create(any(), capture(connectionCallback), any()))
80                 .thenReturn(browser)
81 
82         whenever(mediaController.transportControls).thenReturn(transportControls)
83 
84         resumeBrowser = TestableResumeMediaBrowser(context, callback, component, browserFactory,
85                 mediaController)
86     }
87 
88     @Test
89     fun testConnection_connectionFails_callsOnError() {
90         // When testConnection cannot connect to the service
91         setupBrowserFailed()
92         resumeBrowser.testConnection()
93 
94         // Then it calls onError and disconnects
95         verify(callback).onError()
96         verify(browser).disconnect()
97     }
98 
99     @Test
100     fun testConnection_connects_onConnected() {
101         // When testConnection can connect to the service
102         setupBrowserConnection()
103         resumeBrowser.testConnection()
104 
105         // Then it calls onConnected
106         verify(callback).onConnected()
107     }
108 
109     @Test
110     fun testConnection_noValidMedia_error() {
111         // When testConnection can connect to the service, and does not find valid media
112         setupBrowserConnectionNoResults()
113         resumeBrowser.testConnection()
114 
115         // Then it calls onError and disconnects
116         verify(callback).onError()
117         verify(browser).disconnect()
118     }
119 
120     @Test
121     fun testConnection_hasValidMedia_addTrack() {
122         // When testConnection can connect to the service, and finds valid media
123         setupBrowserConnectionValidMedia()
124         resumeBrowser.testConnection()
125 
126         // Then it calls addTrack
127         verify(callback).onConnected()
128         verify(callback).addTrack(eq(description), eq(component), eq(resumeBrowser))
129     }
130 
131     @Test
132     fun testFindRecentMedia_connectionFails_error() {
133         // When findRecentMedia is called and we cannot connect
134         setupBrowserFailed()
135         resumeBrowser.findRecentMedia()
136 
137         // Then it calls onError and disconnects
138         verify(callback).onError()
139         verify(browser).disconnect()
140     }
141 
142     @Test
143     fun testFindRecentMedia_noRoot_error() {
144         // When findRecentMedia is called and does not get a valid root
145         setupBrowserConnection()
146         whenever(browser.getRoot()).thenReturn(null)
147         resumeBrowser.findRecentMedia()
148 
149         // Then it calls onError and disconnects
150         verify(callback).onError()
151         verify(browser).disconnect()
152     }
153 
154     @Test
155     fun testFindRecentMedia_connects_onConnected() {
156         // When findRecentMedia is called and we connect
157         setupBrowserConnection()
158         resumeBrowser.findRecentMedia()
159 
160         // Then it calls onConnected
161         verify(callback).onConnected()
162     }
163 
164     @Test
165     fun testFindRecentMedia_noChildren_error() {
166         // When findRecentMedia is called and we connect, but do not get any results
167         setupBrowserConnectionNoResults()
168         resumeBrowser.findRecentMedia()
169 
170         // Then it calls onError and disconnects
171         verify(callback).onError()
172         verify(browser).disconnect()
173     }
174 
175     @Test
176     fun testFindRecentMedia_notPlayable_error() {
177         // When findRecentMedia is called and we connect, but do not get a playable child
178         setupBrowserConnectionNotPlayable()
179         resumeBrowser.findRecentMedia()
180 
181         // Then it calls onError and disconnects
182         verify(callback).onError()
183         verify(browser).disconnect()
184     }
185 
186     @Test
187     fun testFindRecentMedia_hasValidMedia_addTrack() {
188         // When findRecentMedia is called and we can connect and get playable media
189         setupBrowserConnectionValidMedia()
190         resumeBrowser.findRecentMedia()
191 
192         // Then it calls addTrack
193         verify(callback).addTrack(eq(description), eq(component), eq(resumeBrowser))
194     }
195 
196     @Test
197     fun testRestart_connectionFails_error() {
198         // When restart is called and we cannot connect
199         setupBrowserFailed()
200         resumeBrowser.restart()
201 
202         // Then it calls onError and disconnects
203         verify(callback).onError()
204         verify(browser).disconnect()
205     }
206 
207     @Test
208     fun testRestart_connects() {
209         // When restart is called and we connect successfully
210         setupBrowserConnection()
211         resumeBrowser.restart()
212         verify(callback).onConnected()
213 
214         // Then it creates a new controller and sends play command
215         verify(transportControls).prepare()
216         verify(transportControls).play()
217     }
218 
219     /**
220      * Helper function to mock a failed connection
221      */
222     private fun setupBrowserFailed() {
223         whenever(browser.connect()).thenAnswer {
224             connectionCallback.value.onConnectionFailed()
225         }
226     }
227 
228     /**
229      * Helper function to mock a successful connection only
230      */
231     private fun setupBrowserConnection() {
232         whenever(browser.connect()).thenAnswer {
233             connectionCallback.value.onConnected()
234         }
235         whenever(browser.isConnected()).thenReturn(true)
236         whenever(browser.getRoot()).thenReturn(ROOT)
237         whenever(browser.sessionToken).thenReturn(token)
238     }
239 
240     /**
241      * Helper function to mock a successful connection, but no media results
242      */
243     private fun setupBrowserConnectionNoResults() {
244         setupBrowserConnection()
245         whenever(browser.subscribe(any(), capture(subscriptionCallback))).thenAnswer {
246             subscriptionCallback.value.onChildrenLoaded(ROOT, emptyList())
247         }
248     }
249 
250     /**
251      * Helper function to mock a successful connection, but no playable results
252      */
253     private fun setupBrowserConnectionNotPlayable() {
254         setupBrowserConnection()
255 
256         val child = MediaBrowser.MediaItem(description, 0)
257 
258         whenever(browser.subscribe(any(), capture(subscriptionCallback))).thenAnswer {
259             subscriptionCallback.value.onChildrenLoaded(ROOT, listOf(child))
260         }
261     }
262 
263     /**
264      * Helper function to mock a successful connection with playable media
265      */
266     private fun setupBrowserConnectionValidMedia() {
267         setupBrowserConnection()
268 
269         val child = MediaBrowser.MediaItem(description, MediaBrowser.MediaItem.FLAG_PLAYABLE)
270 
271         whenever(browser.serviceComponent).thenReturn(component)
272         whenever(browser.subscribe(any(), capture(subscriptionCallback))).thenAnswer {
273             subscriptionCallback.value.onChildrenLoaded(ROOT, listOf(child))
274         }
275     }
276 
277     /**
278      * Override so media controller use is testable
279      */
280     private class TestableResumeMediaBrowser(
281         context: Context,
282         callback: Callback,
283         componentName: ComponentName,
284         browserFactory: MediaBrowserFactory,
285         private val fakeController: MediaController
286     ) : ResumeMediaBrowser(context, callback, componentName, browserFactory) {
287 
288         override fun createMediaController(token: MediaSession.Token): MediaController {
289             return fakeController
290         }
291     }
292 }