1 #/usr/bin/env python3.4 2 # 3 # Copyright (C) 2016 The Android Open Source Project 4 # 5 # Licensed under the Apache License, Version 2.0 (the "License"); you may not 6 # use this file except in compliance with the License. You may obtain a copy of 7 # the License at 8 # 9 # http://www.apache.org/licenses/LICENSE-2.0 10 # 11 # Unless required by applicable law or agreed to in writing, software 12 # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14 # License for the specific language governing permissions and limitations under 15 # the License. 16 """ 17 Automated tests for the testing passthrough commands in Avrcp/A2dp profile. 18 """ 19 20 import os 21 import time 22 23 from acts.test_utils.bt.BluetoothBaseTest import BluetoothBaseTest 24 from acts.test_utils.bt import bt_test_utils 25 from acts.test_utils.bt import BtEnum 26 from acts.test_utils.car import car_media_utils 27 from acts.utils import exe_cmd 28 from acts.controllers import adb 29 30 DEFAULT_WAIT_TIME = 1.0 31 DEFAULT_EVENT_TIMEOUT = 1.0 32 PHONE_MEDIA_BROWSER_SERVICE_NAME = 'BluetoothSL4AAudioSrcMBS' 33 CAR_MEDIA_BROWSER_SERVICE_NAME = 'A2dpMediaBrowserService' 34 # This test requires some media files to play, skip and compare metadata. 35 # The setup part of BtCarMediaPassthroughTest pushes media files from 36 # the local_media_path in user params defined below to ANDROID_MEDIA_PATH 37 # via adb. Before running these tests, place some media files in your host 38 # location. 39 ANDROID_MEDIA_PATH = '/sdcard/Music/test' 40 41 42 class BtCarMediaPassthroughTest(BluetoothBaseTest): 43 local_media_path = "" 44 45 def setup_class(self): 46 if not super(BtCarMediaPassthroughTest, self).setup_class(): 47 return False 48 # AVRCP roles 49 self.CT = self.android_devices[0] 50 self.TG = self.android_devices[1] 51 # A2DP roles for the same devices 52 self.SNK = self.CT 53 self.SRC = self.TG 54 # To keep track of the state of the MediaBrowserService 55 self.mediaBrowserServiceRunning = False 56 self.btAddrCT = self.CT.droid.bluetoothGetLocalAddress() 57 self.btAddrTG = self.TG.droid.bluetoothGetLocalAddress() 58 self.android_music_path = ANDROID_MEDIA_PATH 59 60 if not "local_media_path" in self.user_params.keys(): 61 self.log.error( 62 "Missing mandatory user config \"local_media_path\"!") 63 return False 64 self.local_media_path = self.user_params["local_media_path"] 65 if not os.path.isdir(self.local_media_path): 66 self.local_media_path = os.path.join( 67 self.user_params[Config.key_config_path], 68 self.local_media_path) 69 if not os.path.isdir(self.local_media_path): 70 self.log.error("Unable to load user config " + self. 71 local_media_path + " from test config file.") 72 return False 73 74 # Additional time from the stack reset in setup. 75 time.sleep(4) 76 # Pair and connect the devices. 77 if not bt_test_utils.pair_pri_to_sec( 78 self.CT, self.TG, attempts=4, auto_confirm=False): 79 self.log.error("Failed to pair") 80 return False 81 82 # TODO - check for Avrcp Connection state as well. 83 # For now, the passthrough tests will catch Avrcp Connection failures 84 # But add an explicit test for it. 85 bt_test_utils.connect_pri_to_sec( 86 self.SNK, self.SRC, set([BtEnum.BluetoothProfile.A2DP_SINK.value])) 87 88 # Push media files from self.local_media_path to ANDROID_MEDIA_PATH 89 # Refer to note in the beginning of file 90 self.TG.adb.push("{} {}".format(self.local_media_path, 91 self.android_music_path)) 92 93 return True 94 95 def _init_mbs(self): 96 """ 97 This is required to be done before running any of the passthrough 98 commands. 99 1. Starts up the AvrcpMediaBrowserService on the TG. 100 This MediaBrowserService is part of the SL4A app 101 2. Connects a MediaBrowser to the Carkitt's A2dpMediaBrowserService 102 """ 103 if (not self.mediaBrowserServiceRunning): 104 self.TG.log.info("Starting AvrcpMediaBrowserService") 105 self.TG.droid.bluetoothMediaPhoneSL4AMBSStart() 106 time.sleep(DEFAULT_WAIT_TIME) 107 self.mediaBrowserServiceRunning = True 108 109 self.CT.droid.bluetoothMediaConnectToCarMBS() 110 #TODO - Wait for an event back instead of sleep 111 time.sleep(DEFAULT_WAIT_TIME) 112 113 def teardown_test(self): 114 # Stop the browser service if it is running to clean up the slate. 115 if self.mediaBrowserServiceRunning: 116 self.TG.log.info("Stopping AvrcpMediaBrowserService") 117 self.TG.droid.bluetoothMediaPhoneSL4AMBSStop() 118 self.mediaBrowserServiceRunning = False 119 if not super(BtCarMediaPassthroughTest, self).teardown_test(): 120 return False 121 # If A2dp connection was disconnected as part of the test, connect it back 122 if not (car_media_utils.is_a2dp_connected(self.log, self.SNK, 123 self.SRC)): 124 result = bt_test_utils.connect_pri_to_sec( 125 self.SRC, self.SNK, set([BtEnum.BluetoothProfile.A2DP.value])) 126 if not result: 127 if not bt_test_utils.is_a2dp_src_device_connected( 128 self.SRC, self.SNK.droid.bluetoothGetLocalAddress()): 129 self.SRC.log.error("Failed to connect on A2dp") 130 return False 131 return True 132 133 #@BluetoothTest(UUID=cf4fae08-f4f6-4e0d-b00a-4f6c41d69ff9) 134 @BluetoothBaseTest.bt_test_wrap 135 def test_play_pause(self): 136 """ 137 Test the Play and Pause passthrough commands 138 139 Pre-Condition: 140 1. Devices previously bonded & Connected 141 142 Steps: 143 1. Invoke Play, Pause from CT 144 2. Wait to receive the corresponding received event from TG 145 146 Returns: 147 True if the event was received 148 False if the event was not received 149 150 Priority: 0 151 """ 152 # Set up the MediaBrowserService 153 self._init_mbs() 154 if not car_media_utils.send_media_passthrough_cmd( 155 self.log, self.CT, self.TG, car_media_utils.CMD_MEDIA_PLAY, 156 car_media_utils.EVENT_PLAY_RECEIVED, DEFAULT_EVENT_TIMEOUT): 157 return False 158 if not car_media_utils.send_media_passthrough_cmd( 159 self.log, self.CT, self.TG, car_media_utils.CMD_MEDIA_PAUSE, 160 car_media_utils.EVENT_PAUSE_RECEIVED, DEFAULT_EVENT_TIMEOUT): 161 return False 162 return True 163 164 #@BluetoothTest(UUID=15615b26-3a49-4fa0-b369-41962e8de192) 165 @BluetoothBaseTest.bt_test_wrap 166 def test_passthrough(self): 167 """ 168 Test the Skip Next & Skip Previous passthrough commands 169 170 Pre-Condition: 171 1. Devices previously bonded & Connected 172 173 Steps: 174 1. Invoke other passthrough commands (skip >> & <<) from CT 175 2. Wait to receive the corresponding received event from TG 176 177 Returns: 178 True if the event was received 179 False if the event was not received 180 181 Priority: 0 182 """ 183 # Set up the MediaBrowserService 184 self._init_mbs() 185 if not car_media_utils.send_media_passthrough_cmd( 186 self.log, self.CT, self.TG, 187 car_media_utils.CMD_MEDIA_SKIP_NEXT, 188 car_media_utils.EVENT_SKIP_NEXT_RECEIVED, 189 DEFAULT_EVENT_TIMEOUT): 190 return False 191 if not car_media_utils.send_media_passthrough_cmd( 192 self.log, self.CT, self.TG, 193 car_media_utils.CMD_MEDIA_SKIP_PREV, 194 car_media_utils.EVENT_SKIP_PREV_RECEIVED, 195 DEFAULT_EVENT_TIMEOUT): 196 return False 197 198 # Just pause media before test ends 199 if not car_media_utils.send_media_passthrough_cmd( 200 self.log, self.CT, self.TG, car_media_utils.CMD_MEDIA_PAUSE, 201 car_media_utils.EVENT_PAUSE_RECEIVED): 202 return False 203 204 return True 205 206 @BluetoothBaseTest.bt_test_wrap 207 def test_media_metadata(self): 208 """ 209 Test if the metadata matches between the two ends. 210 Send some random sequence of passthrough commands and compare metadata. 211 TODO: truely randomize of the seq of passthrough commands. 212 Pre-Condition: 213 1. Devices previously bonded & Connected 214 215 Steps: 216 1. Invoke Play from CT 217 2. Compare the metadata between CT and TG. Fail if they don't match 218 3. Send Skip Next from CT 219 4. Compare the metadata between CT and TG. Fail if they don't match 220 5. Repeat steps 3 & 4 221 6. Send Skip Prev from CT 222 7. Compare the metadata between CT and TG. Fail if they don't match 223 224 Returns: 225 True if the metadata matched all the way 226 False if there was a metadata mismatch at any point 227 228 Priority: 0 229 """ 230 if not (car_media_utils.is_a2dp_connected(self.log, self.SNK, 231 self.SRC)): 232 self.SNK.log.error('No A2dp Connection') 233 return False 234 235 self._init_mbs() 236 if not car_media_utils.send_media_passthrough_cmd( 237 self.log, self.CT, self.TG, car_media_utils.CMD_MEDIA_PLAY, 238 car_media_utils.EVENT_PLAY_RECEIVED, DEFAULT_EVENT_TIMEOUT): 239 return False 240 time.sleep(DEFAULT_WAIT_TIME) 241 if not car_media_utils.check_metadata(self.log, self.TG, self.CT): 242 return False 243 244 if not car_media_utils.send_media_passthrough_cmd( 245 self.log, self.CT, self.TG, 246 car_media_utils.CMD_MEDIA_SKIP_NEXT, 247 car_media_utils.EVENT_SKIP_NEXT_RECEIVED, 248 DEFAULT_EVENT_TIMEOUT): 249 return False 250 time.sleep(DEFAULT_WAIT_TIME) 251 if not car_media_utils.check_metadata(self.log, self.TG, self.CT): 252 return False 253 254 if not car_media_utils.send_media_passthrough_cmd( 255 self.log, self.CT, self.TG, 256 car_media_utils.CMD_MEDIA_SKIP_NEXT, 257 car_media_utils.EVENT_SKIP_NEXT_RECEIVED, 258 DEFAULT_EVENT_TIMEOUT): 259 return False 260 time.sleep(DEFAULT_WAIT_TIME) 261 if not car_media_utils.check_metadata(self.log, self.TG, self.CT): 262 return False 263 264 if not car_media_utils.send_media_passthrough_cmd( 265 self.log, self.CT, self.TG, 266 car_media_utils.CMD_MEDIA_SKIP_PREV, 267 car_media_utils.EVENT_SKIP_PREV_RECEIVED, 268 DEFAULT_EVENT_TIMEOUT): 269 return False 270 time.sleep(DEFAULT_WAIT_TIME) 271 if not car_media_utils.check_metadata(self.log, self.TG, self.CT): 272 return False 273 274 @BluetoothBaseTest.bt_test_wrap 275 def test_disconnect_while_media_playing(self): 276 """ 277 Disconnect BT between CT and TG in the middle of a audio streaming session and check 278 1) If TG continues to still play music 279 2) If CT stops playing 280 281 Pre-Condition: 282 1. Devices previously bonded & Connected 283 284 Steps: 285 1. Invoke Play from CT 286 2. Check if both CT and TG are playing music by checking if the respective 287 MediaSessions are active 288 3. Fail if either the CT or TG is not playing 289 4. Disconnect Bluetooth connection between CT and TG 290 5. Check if the CT MediaSession stopped being active. 291 Fail if its mediasession is still active. 292 6. Check if the TG MediaSession is still active. 293 7. Fail if TG stopped playing music. 294 295 Returns: 296 True if the CT stopped playing audio and the TG continued after BT disconnect 297 False if the CT still was playing audio or TG stopped after BT disconnect. 298 299 Priority: 0 300 """ 301 self._init_mbs() 302 self.log.info("Sending Play command from Car") 303 if not car_media_utils.send_media_passthrough_cmd( 304 self.log, self.CT, self.TG, car_media_utils.CMD_MEDIA_PLAY, 305 car_media_utils.EVENT_PLAY_RECEIVED, DEFAULT_EVENT_TIMEOUT): 306 return False 307 308 time.sleep(DEFAULT_WAIT_TIME) 309 310 self.TG.log.info("Phone Media Sessions:") 311 if not car_media_utils.isMediaSessionActive( 312 self.log, self.TG, PHONE_MEDIA_BROWSER_SERVICE_NAME): 313 self.TG.log.error("Media not playing in connected Phone") 314 return False 315 316 self.CT.log.info("Car Media Sessions:") 317 if not car_media_utils.isMediaSessionActive( 318 self.log, self.CT, CAR_MEDIA_BROWSER_SERVICE_NAME): 319 self.CT.log.error("Media not playing in connected Car") 320 return False 321 322 self.log.info("Bluetooth Disconnect the car and phone") 323 result = bt_test_utils.disconnect_pri_from_sec( 324 self.SRC, self.SNK, [BtEnum.BluetoothProfile.A2DP.value]) 325 if not result: 326 if bt_test_utils.is_a2dp_src_device_connected( 327 self.SRC, self.SNK.droid.bluetoothGetLocalAddress()): 328 self.SRC.log.error("Failed to disconnect on A2dp") 329 return False 330 331 self.TG.log.info("Phone Media Sessions:") 332 if not car_media_utils.isMediaSessionActive( 333 self.log, self.TG, PHONE_MEDIA_BROWSER_SERVICE_NAME): 334 self.TG.log.error( 335 "Media stopped playing in phone after BT disconnect") 336 return False 337 338 self.CT.log.info("Car Media Sessions:") 339 if car_media_utils.isMediaSessionActive( 340 self.log, self.CT, CAR_MEDIA_BROWSER_SERVICE_NAME): 341 self.CT.log.error( 342 "Media still playing in a Car after BT disconnect") 343 return False 344 345 return True 346 347 @BluetoothBaseTest.bt_test_wrap 348 def test_connect_while_media_playing(self): 349 """ 350 BT connect SRC and SNK when the SRC is already playing music and verify SNK strarts streaming 351 after connection. 352 Connect to another device (Audio Sink) via BT while it is playing audio. 353 Check if the audio starts streaming on the Sink. 354 355 Pre-Condition: 356 1. Devices previously bonded & Connected 357 358 Steps: 359 1. Disconnect TG from CT (since they are connected as a precondition) 360 2. Play Music on TG (Audio SRC) 361 3. Get the metadata of the playing music 362 4. Connect TG and CT 363 5. Check if the music is streaming on CT (Audio SNK) by checking if its MediaSession became active. 364 6. Fail if CT is not streaming. 365 7. Get the metdata from the CT (Audio SNK) and compare it with the metadata from Step 3 366 8. Fail if the metadata did not match. 367 368 Returns: 369 True if the event was received 370 False if the event was not received 371 372 Priority: 0 373 """ 374 self.log.info("Bluetooth Disconnect the car and phone") 375 result = bt_test_utils.disconnect_pri_from_sec( 376 self.SRC, self.SNK, [BtEnum.BluetoothProfile.A2DP.value]) 377 if not result: 378 # Temporary timeout 379 time.sleep(3) 380 if bt_test_utils.is_a2dp_src_device_connected( 381 self.SRC, self.SNK.droid.bluetoothGetLocalAddress()): 382 self.SRC.log.error("Failed to disconnect on A2dp") 383 return False 384 385 self._init_mbs() 386 387 # Play Media on Phone 388 self.TG.droid.bluetoothMediaHandleMediaCommandOnPhone( 389 car_media_utils.CMD_MEDIA_PLAY) 390 # At this point, media should be playing only on phone, not on Car, since they are disconnected 391 if not car_media_utils.isMediaSessionActive( 392 self.log, self.TG, 393 PHONE_MEDIA_BROWSER_SERVICE_NAME) or car_media_utils.isMediaSessionActive( 394 self.log, self.CT, CAR_MEDIA_BROWSER_SERVICE_NAME): 395 self.log.error("Media playing in wrong end") 396 return False 397 398 # Get the metadata of the song that the phone is playing 399 metadata_TG = self.TG.droid.bluetoothMediaGetCurrentMediaMetaData() 400 if metadata_TG is None: 401 self.TG.log.error("No Media Metadata available from Phone") 402 return False 403 404 # Now connect to Car on Bluetooth 405 if (not bt_test_utils.connect_pri_to_sec(self.SRC, self.SNK, set( 406 [BtEnum.BluetoothProfile.A2DP.value]))): 407 return False 408 409 # Wait for a bit for the information to show up in the car side 410 time.sleep(2) 411 412 # At this point, since we have connected while the Phone was playing media, the car 413 # should automatically play. Both devices should have their respective MediaSessions active 414 if not car_media_utils.isMediaSessionActive( 415 self.log, self.TG, PHONE_MEDIA_BROWSER_SERVICE_NAME): 416 self.TG.log.error("Media not playing in Phone") 417 return False 418 if not car_media_utils.isMediaSessionActive( 419 self.log, self.CT, CAR_MEDIA_BROWSER_SERVICE_NAME): 420 self.CT.log.error("Media not playing in Car") 421 return False 422 423 # Get the metadata from Car and compare it with the Phone's media metadata before the connection happened. 424 metadata_CT = self.CT.droid.bluetoothMediaGetCurrentMediaMetaData() 425 if metadata_CT is None: 426 self.CT.log.info("No Media Metadata available from car") 427 return car_media_utils.compare_metadata(self.log, metadata_TG, 428 metadata_CT) 429