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