1# Lint as: python3 2"""Tests for AVRCP basic functionality.""" 3 4from __future__ import absolute_import 5from __future__ import division 6from __future__ import print_function 7 8import time 9 10from mobly import test_runner 11from mobly import signals 12from mobly.controllers.android_device_lib import adb 13from blueberry.controllers import android_bt_target_device 14from blueberry.utils import blueberry_base_test 15from blueberry.utils import bt_constants 16 17# The audio source path of BluetoothMediaPlayback in the SL4A app. 18ANDROID_MEDIA_PATH = '/sdcard/Music/test' 19 20# Timeout for track change and playback state update in second. 21MEDIA_UPDATE_TIMEOUT_SEC = 3 22 23 24class BluetoothAvrcpTest(blueberry_base_test.BlueberryBaseTest): 25 """Test Class for Bluetooth AVRCP. 26 27 This test requires two or more audio files exist in path "/sdcard/Music/test" 28 on the primary device, and device controllers need to have the following APIs: 29 1. play() 30 2. pause() 31 3. track_previous() 32 4. track_next() 33 """ 34 35 def __init__(self, configs): 36 super().__init__(configs) 37 self.derived_bt_device = None 38 self.pri_device = None 39 self.is_android_bt_target_device = None 40 self.tracks = None 41 42 def setup_class(self): 43 """Standard Mobly setup class.""" 44 super(BluetoothAvrcpTest, self).setup_class() 45 46 for device in self.android_devices: 47 device.init_setup() 48 device.sl4a_setup() 49 50 # The device which role is AVRCP Target (TG). 51 self.pri_device = self.android_devices[0] 52 53 if len(self.android_devices) > 1 and not self.derived_bt_devices: 54 self.derived_bt_device = self.android_devices[1] 55 self.derived_bt_devices.append(self.derived_bt_device) 56 else: 57 self.derived_bt_device = self.derived_bt_devices[0] 58 59 # Check if the derived bt device is android bt target device. 60 self.is_android_bt_target_device = isinstance( 61 self.derived_bt_device, android_bt_target_device.AndroidBtTargetDevice) 62 63 # Check if the audio files exist on the primary device. 64 try: 65 self.audio_files = self.pri_device.adb.shell( 66 'ls %s' % ANDROID_MEDIA_PATH).decode().split('\n')[:-1] 67 if len(self.audio_files) < 2: 68 raise signals.TestError( 69 'Please push two or more audio files to %s on the primary device ' 70 '"%s".' % (ANDROID_MEDIA_PATH, self.pri_device.serial)) 71 except adb.AdbError as error: 72 if 'No such file or directory' in str(error): 73 raise signals.TestError( 74 'No directory "%s" found on the primary device "%s".' % 75 (ANDROID_MEDIA_PATH, self.pri_device.serial)) 76 raise error 77 78 self.mac_address = self.derived_bt_device.get_bluetooth_mac_address() 79 self.derived_bt_device.activate_pairing_mode() 80 self.pri_device.set_target(self.derived_bt_device) 81 self.pri_device.pair_and_connect_bluetooth(self.mac_address) 82 self.pri_device.allow_extra_permissions() 83 # Gives more time for the pairing between two devices. 84 time.sleep(3) 85 86 if self.is_android_bt_target_device: 87 self.derived_bt_device.add_sec_ad_device(self.pri_device) 88 89 # Starts BluetoothSL4AAudioSrcMBS on the phone. 90 if self.is_android_bt_target_device: 91 self.derived_bt_device.init_ambs_for_avrcp() 92 else: 93 self.pri_device.sl4a.bluetoothMediaPhoneSL4AMBSStart() 94 # Waits for BluetoothSL4AAudioSrcMBS to be active. 95 time.sleep(1) 96 # Changes the playback state to Playing in order to other Media passthrough 97 # commands can work. 98 self.pri_device.sl4a.bluetoothMediaHandleMediaCommandOnPhone( 99 bt_constants.CMD_MEDIA_PLAY) 100 101 # Collects media metadata of all tracks. 102 self.tracks = [] 103 for _ in range(len(self.audio_files)): 104 self.tracks.append(self.pri_device.get_current_track_info()) 105 self.pri_device.sl4a.bluetoothMediaHandleMediaCommandOnPhone( 106 bt_constants.CMD_MEDIA_SKIP_NEXT) 107 self.pri_device.log.info('Tracks: %s' % self.tracks) 108 109 # Sets Playback state to Paused as default. 110 self.pri_device.sl4a.bluetoothMediaHandleMediaCommandOnPhone( 111 bt_constants.CMD_MEDIA_PAUSE) 112 113 def teardown_class(self): 114 """Teardown class for bluetooth avrcp media play test.""" 115 super(BluetoothAvrcpTest, self).teardown_class() 116 # Stops BluetoothSL4AAudioSrcMBS after all test methods finish. 117 if self.is_android_bt_target_device: 118 self.derived_bt_device.stop_ambs_for_avrcp() 119 else: 120 self.pri_device.sl4a.bluetoothMediaPhoneSL4AMBSStop() 121 122 def teardown_test(self): 123 """Teardown test for bluetooth avrcp media play test.""" 124 super(BluetoothAvrcpTest, self).teardown_test() 125 # Adds 1 second waiting time to fix the NullPointerException when executing 126 # the following sl4a.bluetoothMediaHandleMediaCommandOnPhone method. 127 time.sleep(1) 128 # Sets Playback state to Paused after a test method finishes. 129 self.pri_device.sl4a.bluetoothMediaHandleMediaCommandOnPhone( 130 bt_constants.CMD_MEDIA_PAUSE) 131 # Buffer between tests. 132 time.sleep(1) 133 134 def wait_for_media_info_sync(self): 135 """Waits for sync Media information between two sides. 136 137 Waits for sync the current playback state and Now playing track info from 138 the android bt target device to the phone. 139 """ 140 # Check if Playback state is sync. 141 expected_state = self.pri_device.get_current_playback_state() 142 self.derived_bt_device.verify_playback_state_changed( 143 expected_state=expected_state, 144 exception=signals.TestError( 145 'Playback state is not equivalent between two sides. ' 146 '"%s" != "%s"' % 147 (self.derived_bt_device.get_current_playback_state(), 148 expected_state))) 149 150 # Check if Now Playing track is sync. 151 expected_track = self.pri_device.get_current_track_info() 152 self.derived_bt_device.verify_current_track_changed( 153 expected_track=expected_track, 154 exception=signals.TestError( 155 'Now Playing track is not equivalent between two sides. ' 156 '"%s" != "%s"' % 157 (self.derived_bt_device.get_current_track_info(), expected_track))) 158 159 def execute_media_play_pause_test_logic(self, command_sender, test_command): 160 """Executes the test logic of the media command "play" or "pause". 161 162 Steps: 163 1. Correct the playback state if needed. 164 2. Send a media passthrough command. 165 3. Verify that the playback state is changed from AVRCP TG and CT. 166 167 Args: 168 command_sender: a device controller sending the command. 169 test_command: string, the media passthrough command for testing, either 170 "play" or "pause". 171 172 Raises: 173 signals.TestError: raised if the test command is invalid. 174 """ 175 # Checks if the test command is valid. 176 if test_command not in [bt_constants.CMD_MEDIA_PLAY, 177 bt_constants.CMD_MEDIA_PAUSE]: 178 raise signals.TestError( 179 'Command "%s" is invalid. The test command should be "%s" or "%s".' % 180 (test_command, bt_constants.CMD_MEDIA_PLAY, 181 bt_constants.CMD_MEDIA_PAUSE)) 182 183 # Make sure the playback state is playing if testing the command "pause". 184 if (self.pri_device.get_current_playback_state() != 185 bt_constants.STATE_PLAYING and 186 test_command == bt_constants.CMD_MEDIA_PAUSE): 187 self.pri_device.sl4a.bluetoothMediaHandleMediaCommandOnPhone( 188 bt_constants.CMD_MEDIA_PLAY) 189 190 # Makes sure Media info is the same between two sides. 191 if self.is_android_bt_target_device: 192 self.wait_for_media_info_sync() 193 self.pri_device.log.info( 194 'Current playback state: %s' % 195 self.pri_device.get_current_playback_state()) 196 197 expected_state = None 198 if test_command == bt_constants.CMD_MEDIA_PLAY: 199 command_sender.play() 200 expected_state = bt_constants.STATE_PLAYING 201 elif test_command == bt_constants.CMD_MEDIA_PAUSE: 202 command_sender.pause() 203 expected_state = bt_constants.STATE_PAUSED 204 205 # Verify that the playback state is changed. 206 self.pri_device.log.info('Expected playback state: %s' % expected_state) 207 device_check_list = [self.pri_device] 208 # Check the playback state from the android bt target device. 209 if self.is_android_bt_target_device: 210 device_check_list.append(self.derived_bt_device) 211 for device in device_check_list: 212 device.verify_playback_state_changed( 213 expected_state=expected_state, 214 exception=signals.TestFailure( 215 'Playback state is not changed to "%s" from the device "%s". ' 216 'Current state: %s' % 217 (expected_state, device.serial, 218 device.get_current_playback_state()))) 219 220 def execute_skip_next_prev_test_logic(self, command_sender, test_command): 221 """Executes the test logic of the media command "skipNext" or "skipPrev". 222 223 Steps: 224 1. Correct the Now Playing track if needed. 225 2. Send a media passthrough command. 226 3. Verify that the Now Playing track is changed from AVRCP TG and CT. 227 228 Args: 229 command_sender: a device controller sending the command. 230 test_command: string, the media passthrough command for testing, either 231 "skipNext" or "skipPrev". 232 233 Raises: 234 signals.TestError: raised if the test command is invalid. 235 """ 236 # Checks if the test command is valid. 237 if test_command not in [bt_constants.CMD_MEDIA_SKIP_NEXT, 238 bt_constants.CMD_MEDIA_SKIP_PREV]: 239 raise signals.TestError( 240 'Command "%s" is invalid. The test command should be "%s" or "%s".' % 241 (test_command, bt_constants.CMD_MEDIA_SKIP_NEXT, 242 bt_constants.CMD_MEDIA_SKIP_PREV)) 243 244 # Make sure the track index is not 0 if testing the command "skipPrev". 245 if (self.tracks.index(self.pri_device.get_current_track_info()) == 0 246 and test_command == bt_constants.CMD_MEDIA_SKIP_PREV): 247 self.pri_device.sl4a.bluetoothMediaHandleMediaCommandOnPhone( 248 bt_constants.CMD_MEDIA_SKIP_NEXT) 249 250 # Makes sure Media info is the same between two sides. 251 if self.is_android_bt_target_device: 252 self.wait_for_media_info_sync() 253 current_track = self.pri_device.get_current_track_info() 254 current_index = self.tracks.index(current_track) 255 self.pri_device.log.info('Current track: %s' % current_track) 256 257 expected_track = None 258 if test_command == bt_constants.CMD_MEDIA_SKIP_NEXT: 259 command_sender.track_next() 260 # It will return to the first track by skipNext if now playing is the last 261 # track. 262 if current_index + 1 == len(self.tracks): 263 expected_track = self.tracks[0] 264 else: 265 expected_track = self.tracks[current_index + 1] 266 elif test_command == bt_constants.CMD_MEDIA_SKIP_PREV: 267 command_sender.track_previous() 268 expected_track = self.tracks[current_index - 1] 269 270 # Verify that the now playing track is changed. 271 self.pri_device.log.info('Expected track: %s' % expected_track) 272 device_check_list = [self.pri_device] 273 # Check the playback state from the android bt target device. 274 if self.is_android_bt_target_device: 275 device_check_list.append(self.derived_bt_device) 276 for device in device_check_list: 277 device.verify_current_track_changed( 278 expected_track=expected_track, 279 exception=signals.TestFailure( 280 'Now Playing track is not changed to "%s" from the device "%s". ' 281 'Current track: %s' % 282 (expected_track, device.serial, device.get_current_track_info()))) 283 284 def test_media_pause(self): 285 """Tests the media pause from AVRCP Controller.""" 286 self.execute_media_play_pause_test_logic( 287 command_sender=self.derived_bt_device, 288 test_command=bt_constants.CMD_MEDIA_PAUSE) 289 290 def test_media_play(self): 291 """Tests the media play from AVRCP Controller.""" 292 self.execute_media_play_pause_test_logic( 293 command_sender=self.derived_bt_device, 294 test_command=bt_constants.CMD_MEDIA_PLAY) 295 296 def test_media_skip_prev(self): 297 """Tests the media skip prev from AVRCP Controller.""" 298 self.execute_skip_next_prev_test_logic( 299 command_sender=self.derived_bt_device, 300 test_command=bt_constants.CMD_MEDIA_SKIP_PREV) 301 302 def test_media_skip_next(self): 303 """Tests the media skip next from AVRCP Controller.""" 304 self.execute_skip_next_prev_test_logic( 305 command_sender=self.derived_bt_device, 306 test_command=bt_constants.CMD_MEDIA_SKIP_NEXT) 307 308 def test_media_pause_from_phone(self): 309 """Tests the media pause from AVRCP Target. 310 311 Tests that Playback state of AVRCP Controller will be changed to paused when 312 AVRCP Target sends the command "pause". 313 """ 314 if not self.is_android_bt_target_device: 315 signals.TestError('The test requires an android bt target device.') 316 317 self.execute_media_play_pause_test_logic( 318 command_sender=self.pri_device, 319 test_command=bt_constants.CMD_MEDIA_PAUSE) 320 321 def test_media_play_from_phone(self): 322 """Tests the media play from AVRCP Target. 323 324 Tests that Playback state of AVRCP Controller will be changed to playing 325 when AVRCP Target sends the command "play". 326 """ 327 if not self.is_android_bt_target_device: 328 signals.TestError('The test requires an android bt target device.') 329 330 self.execute_media_play_pause_test_logic( 331 command_sender=self.pri_device, 332 test_command=bt_constants.CMD_MEDIA_PLAY) 333 334 def test_media_skip_prev_from_phone(self): 335 """Tests the media skip prev from AVRCP Target. 336 337 Tests that Now Playing track of AVRCP Controller will be changed to the 338 previous track when AVRCP Target sends the command "skipPrev". 339 """ 340 if not self.is_android_bt_target_device: 341 signals.TestError('The test requires an android bt target device.') 342 343 self.execute_skip_next_prev_test_logic( 344 command_sender=self.pri_device, 345 test_command=bt_constants.CMD_MEDIA_SKIP_PREV) 346 347 def test_media_skip_next_from_phone(self): 348 """Tests the media skip next from AVRCP Target. 349 350 Tests that Now Playing track of AVRCP Controller will be changed to the next 351 track when AVRCP Target sends the command "skipNext". 352 """ 353 if not self.is_android_bt_target_device: 354 signals.TestError('The test requires an android bt target device.') 355 356 self.execute_skip_next_prev_test_logic( 357 command_sender=self.pri_device, 358 test_command=bt_constants.CMD_MEDIA_SKIP_NEXT) 359 360 361if __name__ == '__main__': 362 test_runner.main() 363