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