• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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