1 /*
2  * Copyright 2017 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.bluetooth.avrcp;
18 
19 import static org.mockito.Mockito.*;
20 
21 import android.media.MediaDescription;
22 import android.media.browse.MediaBrowser.MediaItem;
23 import android.media.session.PlaybackState;
24 import android.os.Handler;
25 import android.os.HandlerThread;
26 
27 import androidx.test.filters.SmallTest;
28 import androidx.test.runner.AndroidJUnit4;
29 
30 import org.junit.Assert;
31 import org.junit.Before;
32 import org.junit.Test;
33 import org.junit.runner.RunWith;
34 import org.mockito.ArgumentCaptor;
35 import org.mockito.Captor;
36 import org.mockito.Mock;
37 import org.mockito.MockitoAnnotations;
38 
39 import java.util.ArrayList;
40 import java.util.List;
41 
42 @SmallTest
43 @RunWith(AndroidJUnit4.class)
44 public class BrowserPlayerWrapperTest {
45 
46     @Captor ArgumentCaptor<MediaBrowser.ConnectionCallback> mBrowserConnCb;
47     @Captor ArgumentCaptor<MediaBrowser.SubscriptionCallback> mSubscriptionCb;
48     @Captor ArgumentCaptor<MediaController.Callback> mControllerCb;
49     @Captor ArgumentCaptor<Handler> mTimeoutHandler;
50     @Captor ArgumentCaptor<List<ListItem>> mWrapperBrowseCb;
51     @Mock MediaBrowser mMockBrowser;
52     @Mock BrowsedPlayerWrapper.ConnectionCallback mConnCb;
53     @Mock BrowsedPlayerWrapper.BrowseCallback mBrowseCb;
54     private HandlerThread mThread;
55 
56     @Before
setUp()57     public void setUp() {
58         MockitoAnnotations.initMocks(this);
59 
60         // Set up Looper thread for the timeout handler
61         mThread = new HandlerThread("MediaPlayerWrapperTestThread");
62         mThread.start();
63 
64         when(mMockBrowser.getRoot()).thenReturn("root_folder");
65 
66         MediaBrowserFactory.inject(mMockBrowser);
67     }
68 
69     @Test
testWrap()70     public void testWrap() {
71         BrowsedPlayerWrapper wrapper =
72                 BrowsedPlayerWrapper.wrap(null, mThread.getLooper(), "test", "test");
73         wrapper.connect(mConnCb);
74         verify(mMockBrowser).testInit(any(), any(), mBrowserConnCb.capture(), any());
75         verify(mMockBrowser).connect();
76 
77         MediaBrowser.ConnectionCallback browserConnCb = mBrowserConnCb.getValue();
78         browserConnCb.onConnected();
79 
80         verify(mConnCb).run(eq(BrowsedPlayerWrapper.STATUS_SUCCESS), eq(wrapper));
81         verify(mMockBrowser).disconnect();
82     }
83 
84     @Test
testConnect_Successful()85     public void testConnect_Successful() {
86         BrowsedPlayerWrapper wrapper =
87                 BrowsedPlayerWrapper.wrap(null, mThread.getLooper(), "test", "test");
88         wrapper.connect(mConnCb);
89         verify(mMockBrowser).testInit(any(), any(), mBrowserConnCb.capture(), any());
90         MediaBrowser.ConnectionCallback browserConnCb = mBrowserConnCb.getValue();
91 
92         verify(mMockBrowser, times(1)).connect();
93         browserConnCb.onConnected();
94         verify(mConnCb).run(eq(BrowsedPlayerWrapper.STATUS_SUCCESS), eq(wrapper));
95         verify(mMockBrowser, times(1)).disconnect();
96     }
97 
98     @Test
testConnect_Suspended()99     public void testConnect_Suspended() {
100         BrowsedPlayerWrapper wrapper =
101                 BrowsedPlayerWrapper.wrap(null, mThread.getLooper(), "test", "test");
102         wrapper.connect(mConnCb);
103         verify(mMockBrowser).testInit(any(), any(), mBrowserConnCb.capture(), any());
104         MediaBrowser.ConnectionCallback browserConnCb = mBrowserConnCb.getValue();
105 
106         verify(mMockBrowser, times(1)).connect();
107         browserConnCb.onConnectionSuspended();
108         verify(mConnCb).run(eq(BrowsedPlayerWrapper.STATUS_CONN_ERROR), eq(wrapper));
109         // Twice because our mConnCb is wrapped when using the plain connect() call and disconnect
110         // is called for us when the callback is invoked in addition to error handling calling
111         // disconnect.
112         verify(mMockBrowser, times(2)).disconnect();
113     }
114 
115     @Test
testConnect_Failed()116     public void testConnect_Failed() {
117         BrowsedPlayerWrapper wrapper =
118                 BrowsedPlayerWrapper.wrap(null, mThread.getLooper(), "test", "test");
119         wrapper.connect(mConnCb);
120         verify(mMockBrowser).testInit(any(), any(), mBrowserConnCb.capture(), any());
121         MediaBrowser.ConnectionCallback browserConnCb = mBrowserConnCb.getValue();
122 
123         verify(mMockBrowser, times(1)).connect();
124         browserConnCb.onConnectionFailed();
125         verify(mConnCb).run(eq(BrowsedPlayerWrapper.STATUS_CONN_ERROR), eq(wrapper));
126         verify(mMockBrowser, times(1)).disconnect();
127     }
128 
129     @Test
testEmptyRoot()130     public void testEmptyRoot() {
131         BrowsedPlayerWrapper wrapper =
132                 BrowsedPlayerWrapper.wrap(null, mThread.getLooper(), "test", "test");
133 
134         doReturn("").when(mMockBrowser).getRoot();
135 
136         wrapper.connect(mConnCb);
137         verify(mMockBrowser).testInit(any(), any(), mBrowserConnCb.capture(), any());
138         MediaBrowser.ConnectionCallback browserConnCb = mBrowserConnCb.getValue();
139 
140         verify(mMockBrowser, times(1)).connect();
141 
142         browserConnCb.onConnected();
143         verify(mConnCb).run(eq(BrowsedPlayerWrapper.STATUS_CONN_ERROR), eq(wrapper));
144         verify(mMockBrowser, times(1)).disconnect();
145     }
146 
147     @Test
testDisconnect()148     public void testDisconnect() {
149         BrowsedPlayerWrapper wrapper =
150                 BrowsedPlayerWrapper.wrap(null, mThread.getLooper(), "test", "test");
151         wrapper.connect(mConnCb);
152         verify(mMockBrowser).testInit(any(), any(), mBrowserConnCb.capture(), any());
153         MediaBrowser.ConnectionCallback browserConnCb = mBrowserConnCb.getValue();
154         browserConnCb.onConnected();
155         verify(mMockBrowser).disconnect();
156     }
157 
158     @Test
testGetRootId()159     public void testGetRootId() {
160         BrowsedPlayerWrapper wrapper =
161                 BrowsedPlayerWrapper.wrap(null, mThread.getLooper(), "test", "test");
162         wrapper.connect(mConnCb);
163         verify(mMockBrowser).testInit(any(), any(), mBrowserConnCb.capture(), any());
164         MediaBrowser.ConnectionCallback browserConnCb = mBrowserConnCb.getValue();
165         browserConnCb.onConnected();
166 
167         Assert.assertEquals("root_folder", wrapper.getRootId());
168         verify(mMockBrowser).disconnect();
169     }
170 
171     @Test
testPlayItem()172     public void testPlayItem() {
173         BrowsedPlayerWrapper wrapper =
174                 BrowsedPlayerWrapper.wrap(null, mThread.getLooper(), "test", "test");
175         verify(mMockBrowser).testInit(any(), any(), mBrowserConnCb.capture(), any());
176         MediaBrowser.ConnectionCallback browserConnCb = mBrowserConnCb.getValue();
177 
178         wrapper.playItem("test_item");
179         verify(mMockBrowser, times(1)).connect();
180 
181         MediaController mockController = mock(MediaController.class);
182         MediaController.TransportControls mockTransport =
183                 mock(MediaController.TransportControls.class);
184         when(mockController.getTransportControls()).thenReturn(mockTransport);
185         MediaControllerFactory.inject(mockController);
186 
187         browserConnCb.onConnected();
188         verify(mockTransport).playFromMediaId(eq("test_item"), eq(null));
189 
190         // Do not immediately disconnect. Non-foreground playback serves will likely fail
191         verify(mMockBrowser, times(0)).disconnect();
192 
193         verify(mockController).registerCallback(mControllerCb.capture(), any());
194         MediaController.Callback controllerCb = mControllerCb.getValue();
195         PlaybackState.Builder builder = new PlaybackState.Builder();
196 
197         // Do not disconnect on an event that isn't "playing"
198         builder.setState(PlaybackState.STATE_PAUSED, 0, 1);
199         controllerCb.onPlaybackStateChanged(builder.build());
200         verify(mMockBrowser, times(0)).disconnect();
201 
202         // Once we're told we're playing, make sure we disconnect
203         builder.setState(PlaybackState.STATE_PLAYING, 0, 1);
204         controllerCb.onPlaybackStateChanged(builder.build());
205         verify(mMockBrowser, times(1)).disconnect();
206     }
207 
208     @Test
testPlayItem_Timeout()209     public void testPlayItem_Timeout() {
210         BrowsedPlayerWrapper wrapper =
211                 BrowsedPlayerWrapper.wrap(null, mThread.getLooper(), "test", "test");
212         verify(mMockBrowser).testInit(any(), any(), mBrowserConnCb.capture(), any());
213         MediaBrowser.ConnectionCallback browserConnCb = mBrowserConnCb.getValue();
214 
215         wrapper.playItem("test_item");
216         verify(mMockBrowser, times(1)).connect();
217 
218         MediaController mockController = mock(MediaController.class);
219         MediaController.TransportControls mockTransport =
220                 mock(MediaController.TransportControls.class);
221         when(mockController.getTransportControls()).thenReturn(mockTransport);
222         MediaControllerFactory.inject(mockController);
223 
224         browserConnCb.onConnected();
225         verify(mockTransport).playFromMediaId(eq("test_item"), eq(null));
226 
227         verify(mockController).registerCallback(any(), mTimeoutHandler.capture());
228         Handler timeoutHandler = mTimeoutHandler.getValue();
229 
230         timeoutHandler.sendEmptyMessage(BrowsedPlayerWrapper.TimeoutHandler.MSG_TIMEOUT);
231 
232         verify(mMockBrowser, timeout(2000).times(1)).disconnect();
233     }
234 
235     @Test
testGetFolderItems()236     public void testGetFolderItems() {
237         BrowsedPlayerWrapper wrapper =
238                 BrowsedPlayerWrapper.wrap(null, mThread.getLooper(), "test", "test");
239         verify(mMockBrowser).testInit(any(), any(), mBrowserConnCb.capture(), any());
240         MediaBrowser.ConnectionCallback browserConnCb = mBrowserConnCb.getValue();
241 
242         wrapper.getFolderItems("test_folder", mBrowseCb);
243 
244 
245         browserConnCb.onConnected();
246         verify(mMockBrowser).subscribe(any(), mSubscriptionCb.capture());
247         MediaBrowser.SubscriptionCallback subscriptionCb = mSubscriptionCb.getValue();
248 
249         ArrayList<MediaItem> items = new ArrayList<MediaItem>();
250         MediaDescription.Builder bob = new MediaDescription.Builder();
251         bob.setTitle("test_song1");
252         bob.setMediaId("ts1");
253         items.add(new MediaItem(bob.build(), 0));
254         bob.setTitle("test_song2");
255         bob.setMediaId("ts2");
256         items.add(new MediaItem(bob.build(), 0));
257 
258         subscriptionCb.onChildrenLoaded("test_folder", items);
259         verify(mMockBrowser).unsubscribe(eq("test_folder"));
260         verify(mBrowseCb).run(eq(BrowsedPlayerWrapper.STATUS_SUCCESS), eq("test_folder"),
261                 mWrapperBrowseCb.capture());
262 
263         List<ListItem> item_list = mWrapperBrowseCb.getValue();
264         for (int i = 0; i < item_list.size(); i++) {
265             Assert.assertFalse(item_list.get(i).isFolder);
266             Assert.assertEquals(item_list.get(i).song, Util.toMetadata(items.get(i)));
267         }
268 
269         verify(mMockBrowser).disconnect();
270     }
271 }
272