| # Lint as: python3 |
| """Tests for AVRCP basic functionality.""" |
| |
| from __future__ import absolute_import |
| from __future__ import division |
| from __future__ import print_function |
| |
| import time |
| |
| from mobly import test_runner |
| from mobly import signals |
| from mobly.controllers.android_device_lib import adb |
| from blueberry.controllers import android_bt_target_device |
| from blueberry.utils import blueberry_base_test |
| from blueberry.utils import bt_constants |
| |
| # The audio source path of BluetoothMediaPlayback in the SL4A app. |
| ANDROID_MEDIA_PATH = '/sdcard/Music/test' |
| |
| # Timeout for track change and playback state update in second. |
| MEDIA_UPDATE_TIMEOUT_SEC = 3 |
| |
| |
| class BluetoothAvrcpTest(blueberry_base_test.BlueberryBaseTest): |
| """Test Class for Bluetooth AVRCP. |
| |
| This test requires two or more audio files exist in path "/sdcard/Music/test" |
| on the primary device, and device controllers need to have the following APIs: |
| 1. play() |
| 2. pause() |
| 3. track_previous() |
| 4. track_next() |
| """ |
| |
| def setup_class(self): |
| """Standard Mobly setup class.""" |
| super(BluetoothAvrcpTest, self).setup_class() |
| |
| for device in self.android_devices: |
| device.init_setup() |
| device.sl4a_setup() |
| |
| # The device which role is AVRCP Target (TG). |
| self.pri_device = self.android_devices[0] |
| |
| if len(self.android_devices) > 1 and not self.derived_bt_devices: |
| self.derived_bt_device = self.android_devices[1] |
| else: |
| self.derived_bt_device = self.derived_bt_devices[0] |
| |
| # Check if the derived bt device is android bt target device. |
| self.is_android_bt_target_device = isinstance( |
| self.derived_bt_device, android_bt_target_device.AndroidBtTargetDevice) |
| |
| # Check if the audio files exist on the primary device. |
| try: |
| self.audio_files = self.pri_device.adb.shell( |
| 'ls %s' % ANDROID_MEDIA_PATH).decode().split('\n')[:-1] |
| if len(self.audio_files) < 2: |
| raise signals.TestError( |
| 'Please push two or more audio files to %s on the primary device ' |
| '"%s".' % (ANDROID_MEDIA_PATH, self.pri_device.serial)) |
| except adb.AdbError as error: |
| if 'No such file or directory' in str(error): |
| raise signals.TestError( |
| 'No directory "%s" found on the primary device "%s".' % |
| (ANDROID_MEDIA_PATH, self.pri_device.serial)) |
| raise error |
| |
| self.mac_address = self.derived_bt_device.get_bluetooth_mac_address() |
| self.derived_bt_device.activate_pairing_mode() |
| self.pri_device.set_target(self.derived_bt_device) |
| self.pri_device.pair_and_connect_bluetooth(self.mac_address) |
| self.pri_device.allow_extra_permissions() |
| # Gives more time for the pairing between two devices. |
| time.sleep(3) |
| |
| if self.is_android_bt_target_device: |
| self.derived_bt_device.add_sec_ad_device(self.pri_device) |
| |
| # Starts BluetoothSL4AAudioSrcMBS on the phone. |
| if self.is_android_bt_target_device: |
| self.derived_bt_device.init_ambs_for_avrcp() |
| else: |
| self.pri_device.sl4a.bluetoothMediaPhoneSL4AMBSStart() |
| # Waits for BluetoothSL4AAudioSrcMBS to be active. |
| time.sleep(1) |
| # Changes the playback state to Playing in order to other Media passthrough |
| # commands can work. |
| self.pri_device.sl4a.bluetoothMediaHandleMediaCommandOnPhone( |
| bt_constants.CMD_MEDIA_PLAY) |
| |
| # Collects media metadata of all tracks. |
| self.tracks = [] |
| for _ in range(len(self.audio_files)): |
| self.tracks.append(self.pri_device.get_current_track_info()) |
| self.pri_device.sl4a.bluetoothMediaHandleMediaCommandOnPhone( |
| bt_constants.CMD_MEDIA_SKIP_NEXT) |
| self.pri_device.log.info('Tracks: %s' % self.tracks) |
| |
| # Sets Playback state to Paused as default. |
| self.pri_device.sl4a.bluetoothMediaHandleMediaCommandOnPhone( |
| bt_constants.CMD_MEDIA_PAUSE) |
| |
| def teardown_class(self): |
| """Teardown class for bluetooth avrcp media play test.""" |
| super(BluetoothAvrcpTest, self).teardown_class() |
| # Stops BluetoothSL4AAudioSrcMBS after all test methods finish. |
| if self.is_android_bt_target_device: |
| self.derived_bt_device.stop_ambs_for_avrcp() |
| else: |
| self.pri_device.sl4a.bluetoothMediaPhoneSL4AMBSStop() |
| |
| def teardown_test(self): |
| """Teardown test for bluetooth avrcp media play test.""" |
| super(BluetoothAvrcpTest, self).teardown_test() |
| # Sets Playback state to Paused after a test method finishes. |
| self.pri_device.sl4a.bluetoothMediaHandleMediaCommandOnPhone( |
| bt_constants.CMD_MEDIA_PAUSE) |
| |
| def wait_for_media_info_sync(self): |
| """Waits for sync Media information between two sides. |
| |
| Waits for sync the current playback state and Now playing track info from |
| the android bt target device to the phone. |
| """ |
| # Check if Playback state is sync. |
| expected_state = self.pri_device.get_current_playback_state() |
| self.derived_bt_device.verify_playback_state_changed( |
| expected_state=expected_state, |
| exception=signals.TestError( |
| 'Playback state is not equivalent between two sides. ' |
| '"%s" != "%s"' % |
| (self.derived_bt_device.get_current_playback_state(), |
| expected_state))) |
| |
| # Check if Now Playing track is sync. |
| expected_track = self.pri_device.get_current_track_info() |
| self.derived_bt_device.verify_current_track_changed( |
| expected_track=expected_track, |
| exception=signals.TestError( |
| 'Now Playing track is not equivalent between two sides. ' |
| '"%s" != "%s"' % |
| (self.derived_bt_device.get_current_track_info(), expected_track))) |
| |
| def execute_media_play_pause_test_logic(self, command_sender, test_command): |
| """Executes the test logic of the media command "play" or "pause". |
| |
| Steps: |
| 1. Correct the playback state if needed. |
| 2. Send a media passthrough command. |
| 3. Verify that the playback state is changed from AVRCP TG and CT. |
| |
| Args: |
| command_sender: a device controller sending the command. |
| test_command: string, the media passthrough command for testing, either |
| "play" or "pause". |
| |
| Raises: |
| signals.TestError: raised if the test command is invalid. |
| """ |
| # Checks if the test command is valid. |
| if test_command not in [bt_constants.CMD_MEDIA_PLAY, |
| bt_constants.CMD_MEDIA_PAUSE]: |
| raise signals.TestError( |
| 'Command "%s" is invalid. The test command should be "%s" or "%s".' % |
| (test_command, bt_constants.CMD_MEDIA_PLAY, |
| bt_constants.CMD_MEDIA_PAUSE)) |
| |
| # Make sure the playback state is playing if testing the command "pause". |
| if (self.pri_device.get_current_playback_state() != |
| bt_constants.STATE_PLAYING and |
| test_command == bt_constants.CMD_MEDIA_PAUSE): |
| self.pri_device.sl4a.bluetoothMediaHandleMediaCommandOnPhone( |
| bt_constants.CMD_MEDIA_PLAY) |
| |
| # Makes sure Media info is the same between two sides. |
| if self.is_android_bt_target_device: |
| self.wait_for_media_info_sync() |
| self.pri_device.log.info( |
| 'Current playback state: %s' % |
| self.pri_device.get_current_playback_state()) |
| |
| expected_state = None |
| if test_command == bt_constants.CMD_MEDIA_PLAY: |
| command_sender.play() |
| expected_state = bt_constants.STATE_PLAYING |
| elif test_command == bt_constants.CMD_MEDIA_PAUSE: |
| command_sender.pause() |
| expected_state = bt_constants.STATE_PAUSED |
| |
| # Verify that the playback state is changed. |
| self.pri_device.log.info('Expected playback state: %s' % expected_state) |
| device_check_list = [self.pri_device] |
| # Check the playback state from the android bt target device. |
| if self.is_android_bt_target_device: |
| device_check_list.append(self.derived_bt_device) |
| for device in device_check_list: |
| device.verify_playback_state_changed( |
| expected_state=expected_state, |
| exception=signals.TestFailure( |
| 'Playback state is not changed to "%s" from the device "%s". ' |
| 'Current state: %s' % |
| (expected_state, device.serial, |
| device.get_current_playback_state()))) |
| |
| def execute_skip_next_prev_test_logic(self, command_sender, test_command): |
| """Executes the test logic of the media command "skipNext" or "skipPrev". |
| |
| Steps: |
| 1. Correct the Now Playing track if needed. |
| 2. Send a media passthrough command. |
| 3. Verify that the Now Playing track is changed from AVRCP TG and CT. |
| |
| Args: |
| command_sender: a device controller sending the command. |
| test_command: string, the media passthrough command for testing, either |
| "skipNext" or "skipPrev". |
| |
| Raises: |
| signals.TestError: raised if the test command is invalid. |
| """ |
| # Checks if the test command is valid. |
| if test_command not in [bt_constants.CMD_MEDIA_SKIP_NEXT, |
| bt_constants.CMD_MEDIA_SKIP_PREV]: |
| raise signals.TestError( |
| 'Command "%s" is invalid. The test command should be "%s" or "%s".' % |
| (test_command, bt_constants.CMD_MEDIA_SKIP_NEXT, |
| bt_constants.CMD_MEDIA_SKIP_PREV)) |
| |
| # Make sure the track index is not 0 if testing the command "skipPrev". |
| if (self.tracks.index(self.pri_device.get_current_track_info()) == 0 |
| and test_command == bt_constants.CMD_MEDIA_SKIP_PREV): |
| self.pri_device.sl4a.bluetoothMediaHandleMediaCommandOnPhone( |
| bt_constants.CMD_MEDIA_SKIP_NEXT) |
| |
| # Makes sure Media info is the same between two sides. |
| if self.is_android_bt_target_device: |
| self.wait_for_media_info_sync() |
| current_track = self.pri_device.get_current_track_info() |
| current_index = self.tracks.index(current_track) |
| self.pri_device.log.info('Current track: %s' % current_track) |
| |
| expected_track = None |
| if test_command == bt_constants.CMD_MEDIA_SKIP_NEXT: |
| command_sender.track_next() |
| # It will return to the first track by skipNext if now playing is the last |
| # track. |
| if current_index + 1 == len(self.tracks): |
| expected_track = self.tracks[0] |
| else: |
| expected_track = self.tracks[current_index + 1] |
| elif test_command == bt_constants.CMD_MEDIA_SKIP_PREV: |
| command_sender.track_previous() |
| expected_track = self.tracks[current_index - 1] |
| |
| # Verify that the now playing track is changed. |
| self.pri_device.log.info('Expected track: %s' % expected_track) |
| device_check_list = [self.pri_device] |
| # Check the playback state from the android bt target device. |
| if self.is_android_bt_target_device: |
| device_check_list.append(self.derived_bt_device) |
| for device in device_check_list: |
| device.verify_current_track_changed( |
| expected_track=expected_track, |
| exception=signals.TestFailure( |
| 'Now Playing track is not changed to "%s" from the device "%s". ' |
| 'Current track: %s' % |
| (expected_track, device.serial, device.get_current_track_info()))) |
| |
| def test_media_pause(self): |
| """Tests the media pause from AVRCP Controller.""" |
| self.execute_media_play_pause_test_logic( |
| command_sender=self.derived_bt_device, |
| test_command=bt_constants.CMD_MEDIA_PAUSE) |
| |
| def test_media_play(self): |
| """Tests the media play from AVRCP Controller.""" |
| self.execute_media_play_pause_test_logic( |
| command_sender=self.derived_bt_device, |
| test_command=bt_constants.CMD_MEDIA_PLAY) |
| |
| def test_media_skip_prev(self): |
| """Tests the media skip prev from AVRCP Controller.""" |
| self.execute_skip_next_prev_test_logic( |
| command_sender=self.derived_bt_device, |
| test_command=bt_constants.CMD_MEDIA_SKIP_PREV) |
| |
| def test_media_skip_next(self): |
| """Tests the media skip next from AVRCP Controller.""" |
| self.execute_skip_next_prev_test_logic( |
| command_sender=self.derived_bt_device, |
| test_command=bt_constants.CMD_MEDIA_SKIP_NEXT) |
| |
| def test_media_pause_from_phone(self): |
| """Tests the media pause from AVRCP Target. |
| |
| Tests that Playback state of AVRCP Controller will be changed to paused when |
| AVRCP Target sends the command "pause". |
| """ |
| if not self.is_android_bt_target_device: |
| signals.TestError('The test requires an android bt target device.') |
| |
| self.execute_media_play_pause_test_logic( |
| command_sender=self.pri_device, |
| test_command=bt_constants.CMD_MEDIA_PAUSE) |
| |
| def test_media_play_from_phone(self): |
| """Tests the media play from AVRCP Target. |
| |
| Tests that Playback state of AVRCP Controller will be changed to playing |
| when AVRCP Target sends the command "play". |
| """ |
| if not self.is_android_bt_target_device: |
| signals.TestError('The test requires an android bt target device.') |
| |
| self.execute_media_play_pause_test_logic( |
| command_sender=self.pri_device, |
| test_command=bt_constants.CMD_MEDIA_PLAY) |
| |
| def test_media_skip_prev_from_phone(self): |
| """Tests the media skip prev from AVRCP Target. |
| |
| Tests that Now Playing track of AVRCP Controller will be changed to the |
| previous track when AVRCP Target sends the command "skipPrev". |
| """ |
| if not self.is_android_bt_target_device: |
| signals.TestError('The test requires an android bt target device.') |
| |
| self.execute_skip_next_prev_test_logic( |
| command_sender=self.pri_device, |
| test_command=bt_constants.CMD_MEDIA_SKIP_PREV) |
| |
| def test_media_skip_next_from_phone(self): |
| """Tests the media skip next from AVRCP Target. |
| |
| Tests that Now Playing track of AVRCP Controller will be changed to the next |
| track when AVRCP Target sends the command "skipNext". |
| """ |
| if not self.is_android_bt_target_device: |
| signals.TestError('The test requires an android bt target device.') |
| |
| self.execute_skip_next_prev_test_logic( |
| command_sender=self.pri_device, |
| test_command=bt_constants.CMD_MEDIA_SKIP_NEXT) |
| |
| |
| if __name__ == '__main__': |
| test_runner.main() |