1 /*
2  * Copyright (C) 2023 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.server.media;
18 
19 import static com.google.common.truth.Truth.assertThat;
20 
21 import static org.mockito.Mockito.any;
22 import static org.mockito.Mockito.eq;
23 import static org.mockito.Mockito.when;
24 
25 import android.app.Application;
26 import android.bluetooth.BluetoothA2dp;
27 import android.bluetooth.BluetoothAdapter;
28 import android.bluetooth.BluetoothDevice;
29 import android.bluetooth.BluetoothManager;
30 import android.bluetooth.BluetoothProfile;
31 import android.content.Context;
32 import android.content.Intent;
33 import android.media.AudioManager;
34 import android.media.MediaRoute2Info;
35 import android.os.UserHandle;
36 
37 import androidx.test.core.app.ApplicationProvider;
38 
39 import org.junit.Before;
40 import org.junit.Test;
41 import org.junit.runner.RunWith;
42 import org.mockito.Mock;
43 import org.mockito.MockitoAnnotations;
44 import org.robolectric.RobolectricTestRunner;
45 import org.robolectric.Shadows;
46 import org.robolectric.shadows.ShadowBluetoothAdapter;
47 import org.robolectric.shadows.ShadowBluetoothDevice;
48 
49 import java.util.Collection;
50 import java.util.HashSet;
51 import java.util.Set;
52 
53 @RunWith(RobolectricTestRunner.class)
54 public class AudioPoliciesBluetoothRouteControllerTest {
55 
56     private static final String DEVICE_ADDRESS_UNKNOWN = ":unknown:ip:address:";
57     private static final String DEVICE_ADDRESS_SAMPLE_1 = "30:59:8B:E4:C6:35";
58     private static final String DEVICE_ADDRESS_SAMPLE_2 = "0D:0D:A6:FF:8D:B6";
59     private static final String DEVICE_ADDRESS_SAMPLE_3 = "2D:9B:0C:C2:6F:78";
60     private static final String DEVICE_ADDRESS_SAMPLE_4 = "66:88:F9:2D:A8:1E";
61 
62     private Context mContext;
63 
64     private ShadowBluetoothAdapter mShadowBluetoothAdapter;
65 
66     @Mock
67     private BluetoothRouteController.BluetoothRoutesUpdatedListener mListener;
68 
69     @Mock
70     private BluetoothProfileMonitor mBluetoothProfileMonitor;
71 
72     private AudioPoliciesBluetoothRouteController mAudioPoliciesBluetoothRouteController;
73 
74     @Before
setUp()75     public void setUp() {
76         MockitoAnnotations.initMocks(this);
77 
78         Application application = ApplicationProvider.getApplicationContext();
79         mContext = application;
80 
81         BluetoothManager bluetoothManager = (BluetoothManager)
82                 mContext.getSystemService(Context.BLUETOOTH_SERVICE);
83 
84         BluetoothAdapter bluetoothAdapter = bluetoothManager.getAdapter();
85         mShadowBluetoothAdapter = Shadows.shadowOf(bluetoothAdapter);
86 
87         mAudioPoliciesBluetoothRouteController =
88                 new AudioPoliciesBluetoothRouteController(mContext, bluetoothAdapter,
89                         mBluetoothProfileMonitor, mListener) {
90                     @Override
91                     boolean isDeviceConnected(BluetoothDevice device) {
92                         return true;
93                     }
94                 };
95 
96         // Enable A2DP profile.
97         when(mBluetoothProfileMonitor.isProfileSupported(eq(BluetoothProfile.A2DP), any()))
98                 .thenReturn(true);
99         mShadowBluetoothAdapter.setProfileConnectionState(BluetoothProfile.A2DP,
100                 BluetoothProfile.STATE_CONNECTED);
101 
102         mAudioPoliciesBluetoothRouteController.start(UserHandle.of(0));
103     }
104 
105     @Test
getSelectedRoute_noBluetoothRoutesAvailable_returnsNull()106     public void getSelectedRoute_noBluetoothRoutesAvailable_returnsNull() {
107         assertThat(mAudioPoliciesBluetoothRouteController.getSelectedRoute()).isNull();
108     }
109 
110     @Test
selectRoute_noBluetoothRoutesAvailable_returnsFalse()111     public void selectRoute_noBluetoothRoutesAvailable_returnsFalse() {
112         assertThat(mAudioPoliciesBluetoothRouteController
113                 .selectRoute(DEVICE_ADDRESS_UNKNOWN)).isFalse();
114     }
115 
116     @Test
selectRoute_noDeviceWithGivenAddress_returnsFalse()117     public void selectRoute_noDeviceWithGivenAddress_returnsFalse() {
118         Set<BluetoothDevice> devices = generateFakeBluetoothDevicesSet(
119                 DEVICE_ADDRESS_SAMPLE_1, DEVICE_ADDRESS_SAMPLE_3);
120 
121         mShadowBluetoothAdapter.setBondedDevices(devices);
122 
123         assertThat(mAudioPoliciesBluetoothRouteController
124                 .selectRoute(DEVICE_ADDRESS_SAMPLE_2)).isFalse();
125     }
126 
127     @Test
selectRoute_deviceIsInDevicesSet_returnsTrue()128     public void selectRoute_deviceIsInDevicesSet_returnsTrue() {
129         Set<BluetoothDevice> devices = generateFakeBluetoothDevicesSet(
130                 DEVICE_ADDRESS_SAMPLE_1, DEVICE_ADDRESS_SAMPLE_2);
131 
132         mShadowBluetoothAdapter.setBondedDevices(devices);
133 
134         assertThat(mAudioPoliciesBluetoothRouteController
135                 .selectRoute(DEVICE_ADDRESS_SAMPLE_1)).isTrue();
136     }
137 
138     @Test
selectRoute_resetSelectedDevice_returnsTrue()139     public void selectRoute_resetSelectedDevice_returnsTrue() {
140         Set<BluetoothDevice> devices = generateFakeBluetoothDevicesSet(
141                 DEVICE_ADDRESS_SAMPLE_1, DEVICE_ADDRESS_SAMPLE_2);
142 
143         mShadowBluetoothAdapter.setBondedDevices(devices);
144 
145         mAudioPoliciesBluetoothRouteController.selectRoute(DEVICE_ADDRESS_SAMPLE_1);
146         assertThat(mAudioPoliciesBluetoothRouteController.selectRoute(null)).isTrue();
147     }
148 
149     @Test
selectRoute_noSelectedDevice_returnsTrue()150     public void selectRoute_noSelectedDevice_returnsTrue() {
151         Set<BluetoothDevice> devices = generateFakeBluetoothDevicesSet(
152                 DEVICE_ADDRESS_SAMPLE_1, DEVICE_ADDRESS_SAMPLE_2);
153 
154         mShadowBluetoothAdapter.setBondedDevices(devices);
155 
156         assertThat(mAudioPoliciesBluetoothRouteController.selectRoute(null)).isTrue();
157     }
158 
159     @Test
getSelectedRoute_updateRouteFailed_returnsNull()160     public void getSelectedRoute_updateRouteFailed_returnsNull() {
161         Set<BluetoothDevice> devices = generateFakeBluetoothDevicesSet(
162                 DEVICE_ADDRESS_SAMPLE_1, DEVICE_ADDRESS_SAMPLE_2);
163 
164         mShadowBluetoothAdapter.setBondedDevices(devices);
165         mAudioPoliciesBluetoothRouteController
166                 .selectRoute(DEVICE_ADDRESS_SAMPLE_3);
167 
168         assertThat(mAudioPoliciesBluetoothRouteController.getSelectedRoute()).isNull();
169     }
170 
171     @Test
getSelectedRoute_updateRouteSuccessful_returnsUpdateDevice()172     public void getSelectedRoute_updateRouteSuccessful_returnsUpdateDevice() {
173         Set<BluetoothDevice> devices = generateFakeBluetoothDevicesSet(
174                 DEVICE_ADDRESS_SAMPLE_1, DEVICE_ADDRESS_SAMPLE_2, DEVICE_ADDRESS_SAMPLE_4);
175 
176         assertThat(mAudioPoliciesBluetoothRouteController.getSelectedRoute()).isNull();
177 
178         mShadowBluetoothAdapter.setBondedDevices(devices);
179 
180         assertThat(mAudioPoliciesBluetoothRouteController
181                 .selectRoute(DEVICE_ADDRESS_SAMPLE_4)).isTrue();
182 
183         MediaRoute2Info selectedRoute = mAudioPoliciesBluetoothRouteController.getSelectedRoute();
184         assertThat(selectedRoute.getAddress()).isEqualTo(DEVICE_ADDRESS_SAMPLE_4);
185     }
186 
187     @Test
getSelectedRoute_resetSelectedRoute_returnsNull()188     public void getSelectedRoute_resetSelectedRoute_returnsNull() {
189         Set<BluetoothDevice> devices = generateFakeBluetoothDevicesSet(
190                 DEVICE_ADDRESS_SAMPLE_1, DEVICE_ADDRESS_SAMPLE_2, DEVICE_ADDRESS_SAMPLE_4);
191 
192         mShadowBluetoothAdapter.setBondedDevices(devices);
193 
194         // Device is not null now.
195         mAudioPoliciesBluetoothRouteController.selectRoute(DEVICE_ADDRESS_SAMPLE_4);
196         // Rest the device.
197         mAudioPoliciesBluetoothRouteController.selectRoute(null);
198 
199         assertThat(mAudioPoliciesBluetoothRouteController.getSelectedRoute())
200                 .isNull();
201     }
202 
203     @Test
getTransferableRoutes_noSelectedRoute_returnsAllBluetoothDevices()204     public void getTransferableRoutes_noSelectedRoute_returnsAllBluetoothDevices() {
205         String[] addresses = new String[] { DEVICE_ADDRESS_SAMPLE_1,
206                 DEVICE_ADDRESS_SAMPLE_2, DEVICE_ADDRESS_SAMPLE_4 };
207         Set<BluetoothDevice> devices = generateFakeBluetoothDevicesSet(addresses);
208         mShadowBluetoothAdapter.setBondedDevices(devices);
209 
210         // Force route controller to update bluetooth devices list.
211         sendBluetoothDevicesChangedBroadcast();
212 
213         Set<String> transferableDevices = extractAddressesListFrom(
214                 mAudioPoliciesBluetoothRouteController.getTransferableRoutes());
215         assertThat(transferableDevices).containsExactlyElementsIn(addresses);
216     }
217 
218     @Test
getTransferableRoutes_hasSelectedRoute_returnsRoutesWithoutSelectedDevice()219     public void getTransferableRoutes_hasSelectedRoute_returnsRoutesWithoutSelectedDevice() {
220         String[] addresses = new String[] { DEVICE_ADDRESS_SAMPLE_1,
221                 DEVICE_ADDRESS_SAMPLE_2, DEVICE_ADDRESS_SAMPLE_4 };
222         Set<BluetoothDevice> devices = generateFakeBluetoothDevicesSet(addresses);
223         mShadowBluetoothAdapter.setBondedDevices(devices);
224 
225         // Force route controller to update bluetooth devices list.
226         sendBluetoothDevicesChangedBroadcast();
227         mAudioPoliciesBluetoothRouteController.selectRoute(DEVICE_ADDRESS_SAMPLE_4);
228 
229         Set<String> transferableDevices = extractAddressesListFrom(
230                 mAudioPoliciesBluetoothRouteController.getTransferableRoutes());
231         assertThat(transferableDevices).containsExactly(DEVICE_ADDRESS_SAMPLE_1,
232                 DEVICE_ADDRESS_SAMPLE_2);
233     }
234 
235     @Test
getAllBluetoothRoutes_hasSelectedRoute_returnsAllRoutes()236     public void getAllBluetoothRoutes_hasSelectedRoute_returnsAllRoutes() {
237         String[] addresses = new String[] { DEVICE_ADDRESS_SAMPLE_1,
238                 DEVICE_ADDRESS_SAMPLE_2, DEVICE_ADDRESS_SAMPLE_4 };
239         Set<BluetoothDevice> devices = generateFakeBluetoothDevicesSet(addresses);
240         mShadowBluetoothAdapter.setBondedDevices(devices);
241 
242         // Force route controller to update bluetooth devices list.
243         sendBluetoothDevicesChangedBroadcast();
244         mAudioPoliciesBluetoothRouteController.selectRoute(DEVICE_ADDRESS_SAMPLE_4);
245 
246         Set<String> bluetoothDevices = extractAddressesListFrom(
247                 mAudioPoliciesBluetoothRouteController.getAllBluetoothRoutes());
248         assertThat(bluetoothDevices).containsExactlyElementsIn(addresses);
249     }
250 
251     @Test
updateVolumeForDevice_setVolumeForA2DPTo25_selectedRouteVolumeIsUpdated()252     public void updateVolumeForDevice_setVolumeForA2DPTo25_selectedRouteVolumeIsUpdated() {
253         String[] addresses = new String[] { DEVICE_ADDRESS_SAMPLE_1,
254                 DEVICE_ADDRESS_SAMPLE_2, DEVICE_ADDRESS_SAMPLE_4 };
255         Set<BluetoothDevice> devices = generateFakeBluetoothDevicesSet(addresses);
256         mShadowBluetoothAdapter.setBondedDevices(devices);
257 
258         // Force route controller to update bluetooth devices list.
259         sendBluetoothDevicesChangedBroadcast();
260         mAudioPoliciesBluetoothRouteController.selectRoute(DEVICE_ADDRESS_SAMPLE_4);
261 
262         mAudioPoliciesBluetoothRouteController.updateVolumeForDevices(
263                 AudioManager.DEVICE_OUT_BLUETOOTH_A2DP, 25);
264 
265         MediaRoute2Info selectedRoute = mAudioPoliciesBluetoothRouteController.getSelectedRoute();
266         assertThat(selectedRoute.getVolume()).isEqualTo(25);
267     }
268 
sendBluetoothDevicesChangedBroadcast()269     private void sendBluetoothDevicesChangedBroadcast() {
270         Intent intent = new Intent(BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED);
271         mContext.sendBroadcast(intent);
272     }
273 
extractAddressesListFrom(Collection<MediaRoute2Info> routes)274     private static Set<String> extractAddressesListFrom(Collection<MediaRoute2Info> routes) {
275         Set<String> addresses = new HashSet<>();
276 
277         for (MediaRoute2Info route: routes) {
278             addresses.add(route.getAddress());
279         }
280 
281         return addresses;
282     }
283 
generateFakeBluetoothDevicesSet(String... addresses)284     private static Set<BluetoothDevice> generateFakeBluetoothDevicesSet(String... addresses) {
285         Set<BluetoothDevice> devices = new HashSet<>();
286 
287         for (String address: addresses) {
288             devices.add(ShadowBluetoothDevice.newInstance(address));
289         }
290 
291         return devices;
292     }
293 }
294