| # Copyright 2022 Google LLC |
| # |
| # Licensed under the Apache License, Version 2.0 (the "License"); |
| # you may not use this file except in compliance with the License. |
| # You may obtain a copy of the License at |
| # |
| # https://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| # See the License for the specific language governing permissions and |
| # limitations under the License. |
| |
| """A2DP proxy module.""" |
| |
| import time |
| from typing import Optional |
| |
| from grpc import RpcError |
| |
| from mmi2grpc._audio import AudioSignal |
| from mmi2grpc._helpers import assert_description |
| from mmi2grpc._proxy import ProfileProxy |
| from pandora.a2dp_grpc import A2DP |
| from pandora.a2dp_pb2 import Sink, Source, PlaybackAudioRequest |
| from pandora.host_grpc import Host |
| from pandora.host_pb2 import Connection |
| |
| AUDIO_SIGNAL_AMPLITUDE = 0.8 |
| AUDIO_SIGNAL_SAMPLING_RATE = 44100 |
| |
| |
| class A2DPProxy(ProfileProxy): |
| """A2DP proxy. |
| |
| Implements A2DP and AVDTP PTS MMIs. |
| """ |
| |
| connection: Optional[Connection] = None |
| sink: Optional[Sink] = None |
| source: Optional[Source] = None |
| |
| def __init__(self, channel): |
| super().__init__() |
| |
| self.host = Host(channel) |
| self.a2dp = A2DP(channel) |
| |
| def convert_frame(data): |
| return PlaybackAudioRequest(data=data, source=self.source) |
| self.audio = AudioSignal( |
| lambda frames: self.a2dp.PlaybackAudio(map(convert_frame, frames)), |
| AUDIO_SIGNAL_AMPLITUDE, |
| AUDIO_SIGNAL_SAMPLING_RATE |
| ) |
| |
| @assert_description |
| def TSC_AVDTP_mmi_iut_accept_connect( |
| self, test: str, pts_addr: bytes, **kwargs): |
| """ |
| If necessary, take action to accept the AVDTP Signaling Channel |
| Connection initiated by the tester. |
| |
| Description: Make sure the IUT |
| (Implementation Under Test) is in a state to accept incoming Bluetooth |
| connections. Some devices may need to be on a specific screen, like a |
| Bluetooth settings screen, in order to pair with PTS. If the IUT is |
| still having problems pairing with PTS, try running a test case where |
| the IUT connects to PTS to establish pairing. |
| """ |
| |
| if "SRC" in test: |
| self.connection = self.host.WaitConnection( |
| address=pts_addr).connection |
| try: |
| if "INT" in test: |
| self.source = self.a2dp.OpenSource( |
| connection=self.connection).source |
| else: |
| self.source = self.a2dp.WaitSource( |
| connection=self.connection).source |
| except RpcError: |
| pass |
| else: |
| self.connection = self.host.WaitConnection( |
| address=pts_addr).connection |
| try: |
| self.sink = self.a2dp.WaitSink( |
| connection=self.connection).sink |
| except RpcError: |
| pass |
| return "OK" |
| |
| @assert_description |
| def TSC_AVDTP_mmi_iut_initiate_discover(self, **kwargs): |
| """ |
| Send a discover command to PTS. |
| |
| Action: If the IUT (Implementation |
| Under Test) is already connected to PTS, attempting to send or receive |
| streaming media should trigger this action. If the IUT is not connected |
| to PTS, attempting to connect may trigger this action. |
| """ |
| |
| return "OK" |
| |
| @assert_description |
| def TSC_AVDTP_mmi_iut_initiate_start(self, test: str, **kwargs): |
| """ |
| Send a start command to PTS. |
| |
| Action: If the IUT (Implementation Under |
| Test) is already connected to PTS, attempting to send or receive |
| streaming media should trigger this action. If the IUT is not connected |
| to PTS, attempting to connect may trigger this action. |
| """ |
| |
| if "SRC" in test: |
| self.a2dp.Start(source=self.source) |
| else: |
| self.a2dp.Start(sink=self.sink) |
| return "OK" |
| |
| @assert_description |
| def TSC_AVDTP_mmi_iut_initiate_suspend(self, test: str, **kwargs): |
| """ |
| Suspend the streaming channel. |
| """ |
| |
| if "SRC" in test: |
| self.a2dp.Suspend(source=self.source) |
| else: |
| assert False |
| return "OK" |
| |
| @assert_description |
| def TSC_AVDTP_mmi_iut_initiate_close_stream(self, test: str, **kwargs): |
| """ |
| Close the streaming channel. |
| |
| Action: Disconnect the streaming channel, |
| or close the Bluetooth connection to the PTS. |
| """ |
| |
| if "SRC" in test: |
| self.a2dp.Close(source=self.source) |
| self.source = None |
| else: |
| self.a2dp.Close(sink=self.sink) |
| self.sink = None |
| return "OK" |
| |
| @assert_description |
| def TSC_AVDTP_mmi_iut_initiate_out_of_range( |
| self, pts_addr: bytes, **kwargs): |
| """ |
| Move the IUT out of range to create a link loss scenario. |
| |
| Action: This |
| can be also be done by placing the IUT or PTS in an RF shielded box. |
| """ |
| |
| if self.connection is None: |
| self.connection = self.host.GetConnection( |
| address=pts_addr).connection |
| self.host.Disconnect(connection=self.connection) |
| self.connection = None |
| self.sink = None |
| self.source = None |
| return "OK" |
| |
| @assert_description |
| def TSC_AVDTP_mmi_iut_begin_streaming(self, test: str, **kwargs): |
| """ |
| Begin streaming media ... |
| |
| Note: If the IUT has suspended the stream |
| please restart the stream to begin streaming media. |
| """ |
| |
| if test == "AVDTP/SRC/ACP/SIG/SMG/BI-29-C": |
| time.sleep(2) # TODO: Remove, AVRCP SegFault |
| if test in ("A2DP/SRC/CC/BV-09-I", |
| "A2DP/SRC/SET/BV-04-I", |
| "AVDTP/SRC/ACP/SIG/SMG/BV-18-C", |
| "AVDTP/SRC/ACP/SIG/SMG/BV-20-C", |
| "AVDTP/SRC/ACP/SIG/SMG/BV-22-C"): |
| time.sleep(1) # TODO: Remove, AVRCP SegFault |
| if test == "A2DP/SRC/SUS/BV-01-I": |
| # Stream is not suspended when we receive the interaction |
| time.sleep(1) |
| |
| self.a2dp.Start(source=self.source) |
| self.audio.start() |
| return "OK" |
| |
| @assert_description |
| def TSC_AVDTP_mmi_iut_initiate_media(self, **kwargs): |
| """ |
| Take action if necessary to start streaming media to the tester. |
| """ |
| |
| self.audio.start() |
| return "OK" |
| |
| @assert_description |
| def TSC_AVDTP_mmi_iut_initiate_stream_media(self, **kwargs): |
| """ |
| Stream media to PTS. If the IUT is a SNK, wait for PTS to start |
| streaming media. |
| |
| Action: If the IUT (Implementation Under Test) is |
| already connected to PTS, attempting to send or receive streaming media |
| should trigger this action. If the IUT is not connected to PTS, |
| attempting to connect may trigger this action. |
| """ |
| |
| self.audio.start() |
| return "OK" |
| |
| @assert_description |
| def TSC_AVDTP_mmi_user_verify_media_playback(self, **kwargs): |
| """ |
| Is the test system properly playing back the media being sent by the |
| IUT? |
| """ |
| |
| result = self.audio.verify() |
| assert result |
| |
| return "Yes" if result else "No" |
| |
| @assert_description |
| def TSC_AVDTP_mmi_iut_initiate_get_capabilities(self, **kwargs): |
| """ |
| Send a get capabilities command to PTS. |
| |
| Action: If the IUT |
| (Implementation Under Test) is already connected to PTS, attempting to |
| send or receive streaming media should trigger this action. If the IUT |
| is not connected to PTS, attempting to connect may trigger this action. |
| """ |
| |
| # This will be done as part as the a2dp.OpenSource or a2dp.WaitSource |
| return "OK" |
| |
| @assert_description |
| def TSC_AVDTP_mmi_iut_accept_discover(self, **kwargs): |
| """ |
| If necessary, take action to accept the AVDTP Discover operation |
| initiated by the tester. |
| """ |
| |
| return "OK" |
| |
| @assert_description |
| def TSC_AVDTP_mmi_iut_initiate_set_configuration(self, **kwargs): |
| """ |
| Send a set configuration command to PTS. |
| |
| Action: If the IUT |
| (Implementation Under Test) is already connected to PTS, attempting to |
| send or receive streaming media should trigger this action. If the IUT |
| is not connected to PTS, attempting to connect may trigger this action. |
| """ |
| |
| return "OK" |
| |
| @assert_description |
| def TSC_AVDTP_mmi_iut_accept_close_stream(self, **kwargs): |
| """ |
| If necessary, take action to accept the AVDTP Close operation initiated |
| by the tester. |
| """ |
| |
| return "OK" |
| |
| @assert_description |
| def TSC_AVDTP_mmi_iut_accept_abort(self, **kwargs): |
| """ |
| If necessary, take action to accept the AVDTP Abort operation initiated |
| by the tester.. |
| """ |
| |
| return "OK" |
| |
| @assert_description |
| def TSC_AVDTP_mmi_iut_accept_get_all_capabilities(self, **kwargs): |
| """ |
| If necessary, take action to accept the AVDTP Get All Capabilities |
| operation initiated by the tester. |
| """ |
| |
| return "OK" |
| |
| @assert_description |
| def TSC_AVDTP_mmi_iut_accept_get_capabilities(self, **kwargs): |
| """ |
| If necessary, take action to accept the AVDTP Get Capabilities operation |
| initiated by the tester. |
| """ |
| |
| return "OK" |
| |
| @assert_description |
| def TSC_AVDTP_mmi_iut_accept_set_configuration(self, **kwargs): |
| """ |
| If necessary, take action to accept the AVDTP Set Configuration |
| operation initiated by the tester. |
| """ |
| |
| return "OK" |
| |
| @assert_description |
| def TSC_AVDTP_mmi_iut_accept_get_configuration(self, **kwargs): |
| """ |
| Take action to accept the AVDTP Get Configuration command from the |
| tester. |
| """ |
| |
| return "OK" |
| |
| @assert_description |
| def TSC_AVDTP_mmi_iut_accept_open_stream(self, **kwargs): |
| """ |
| If necessary, take action to accept the AVDTP Open operation initiated |
| by the tester. |
| """ |
| |
| return "OK" |
| |
| @assert_description |
| def TSC_AVDTP_mmi_iut_accept_start(self, **kwargs): |
| """ |
| If necessary, take action to accept the AVDTP Start operation initiated |
| by the tester. |
| """ |
| |
| return "OK" |
| |
| @assert_description |
| def TSC_AVDTP_mmi_iut_accept_suspend(self, **kwargs): |
| """ |
| If necessary, take action to accept the AVDTP Suspend operation |
| initiated by the tester. |
| """ |
| |
| return "OK" |
| |
| @assert_description |
| def TSC_AVDTP_mmi_iut_accept_reconfigure(self, **kwargs): |
| """ |
| If necessary, take action to accept the AVDTP Reconfigure operation |
| initiated by the tester. |
| """ |
| |
| return "OK" |
| |
| @assert_description |
| def TSC_AVDTP_mmi_iut_accept_media_transports(self, **kwargs): |
| """ |
| Take action to accept transport channels for the recently configured |
| media stream. |
| """ |
| |
| return "OK" |
| |
| @assert_description |
| def TSC_AVDTP_mmi_iut_confirm_streaming(self, **kwargs): |
| """ |
| Is the IUT (Implementation Under Test) receiving streaming media from |
| PTS? |
| |
| Action: Press 'Yes' if the IUT is receiving streaming data from |
| the PTS (in some cases the sound may not be clear, this is normal). |
| """ |
| |
| # TODO: verify |
| return "OK" |
| |
| @assert_description |
| def TSC_AVDTP_mmi_iut_initiate_open_stream(self, **kwargs): |
| """ |
| Open a streaming media channel. |
| |
| Action: If the IUT (Implementation |
| Under Test) is already connected to PTS, attempting to send or receive |
| streaming media should trigger this action. If the IUT is not connected |
| to PTS, attempting to connect may trigger this action. |
| """ |
| |
| return "OK" |
| |
| @assert_description |
| def TSC_AVDTP_mmi_iut_accept_reconnect(self, pts_addr: bytes, **kwargs): |
| """ |
| Press OK when the IUT (Implementation Under Test) is ready to allow the |
| PTS to reconnect the AVDTP signaling channel. |
| |
| Action: Press OK when the |
| IUT is ready to accept Bluetooth connections again. |
| """ |
| |
| return "OK" |
| |
| @assert_description |
| def TSC_AVDTP_mmi_iut_initiate_get_all_capabilities(self, **kwargs): |
| """ |
| Send a GET ALL CAPABILITIES command to PTS. |
| |
| Action: If the IUT |
| (Implementation Under Test) is already connected to PTS, attempting to |
| send or receive streaming media should trigger this action. If the IUT |
| is not connected to PTS, attempting to connect may trigger this action. |
| """ |
| |
| return "OK" |
| |
| @assert_description |
| def TSC_AVDTP_mmi_tester_verifying_suspend(self, **kwargs): |
| """ |
| Please wait while the tester verifies the IUT does not send media during |
| suspend ... |
| """ |
| |
| return "Yes" |
| |
| @assert_description |
| def TSC_A2DP_mmi_user_confirm_optional_data_attribute(self, **kwargs): |
| """ |
| Tester found the optional SDP attribute named 'Supported Features'. |
| Press 'Yes' if the data displayed below is correct. |
| |
| Value: 0x0001 |
| """ |
| |
| # TODO: Extract and verify attribute name and value from description |
| return "OK" |
| |
| @assert_description |
| def TSC_A2DP_mmi_user_confirm_optional_string_attribute(self, **kwargs): |
| """ |
| Tester found the optional SDP attribute named 'Service Name'. Press |
| 'Yes' if the string displayed below is correct. |
| |
| Value: Advanced Audio |
| Source |
| """ |
| |
| # TODO: Extract and verify attribute name and value from description |
| return "OK" |
| |
| @assert_description |
| def TSC_A2DP_mmi_user_confirm_no_optional_attribute_support(self, **kwargs): |
| """ |
| Tester could not find the optional SDP attribute named 'Provider Name'. |
| Is this correct? |
| """ |
| |
| # TODO: Extract and verify attribute name from description |
| return "OK" |
| |
| @assert_description |
| def TSC_AVDTPEX_mmi_iut_accept_delayreport(self, **kwargs): |
| """ |
| Take action if necessary to accept the Delay Reportl command from the |
| tester. |
| """ |
| |
| return "OK" |
| |
| @assert_description |
| def TSC_AVDTPEX_mmi_iut_initiate_media_transport_connect(self, **kwargs): |
| """ |
| Take action to initiate an AVDTP media transport. |
| """ |
| |
| return "OK" |
| |
| @assert_description |
| def TSC_AVDTPEX_mmi_user_confirm_SIG_SMG_BV_28_C(self, **kwargs): |
| """ |
| Were all the service capabilities reported to the upper tester valid? |
| """ |
| |
| # TODO: verify |
| return "Yes" |
| |
| @assert_description |
| def TSC_AVDTPEX_mmi_iut_reject_invalid_command(self, **kwargs): |
| """ |
| Take action to reject the invalid command sent by the tester. |
| """ |
| |
| return "OK" |
| |
| @assert_description |
| def TSC_AVDTPEX_mmi_iut_reject_open(self, **kwargs): |
| """ |
| Take action to reject the invalid OPEN command sent by the tester. |
| """ |
| |
| return "OK" |
| |
| @assert_description |
| def TSC_AVDTPEX_mmi_iut_reject_start(self, **kwargs): |
| """ |
| Take action to reject the invalid START command sent by the tester. |
| """ |
| |
| return "OK" |
| |
| @assert_description |
| def TSC_AVDTPEX_mmi_iut_reject_suspend(self, **kwargs): |
| """ |
| Take action to reject the invalid SUSPEND command sent by the tester. |
| """ |
| |
| return "OK" |
| |
| @assert_description |
| def TSC_AVDTPEX_mmi_iut_reject_reconfigure(self, **kwargs): |
| """ |
| Take action to reject the invalid or incompatible RECONFIGURE command |
| sent by the tester. |
| """ |
| |
| return "OK" |
| |
| @assert_description |
| def TSC_AVDTPEX_mmi_iut_reject_get_all_capabilities(self, **kwargs): |
| """ |
| Take action to reject the invalid GET ALL CAPABILITIES command with the |
| error code BAD_LENGTH. |
| """ |
| |
| return "OK" |
| |
| @assert_description |
| def TSC_AVDTPEX_mmi_iut_reject_get_capabilities(self, **kwargs): |
| """ |
| Take action to reject the invalid GET CAPABILITIES command with the |
| error code BAD_LENGTH. |
| """ |
| |
| return "OK" |
| |
| @assert_description |
| def TSC_AVDTPEX_mmi_iut_reject_set_configuration(self, **kwargs): |
| """ |
| Take action to reject the SET CONFIGURATION sent by the tester. The IUT |
| is expected to respond with SEP_IN_USE because the SEP requested was |
| previously configured. |
| """ |
| |
| return "OK" |
| |
| def TSC_AVDTPEX_mmi_iut_reject_get_configuration(self, **kwargs): |
| """ |
| Take action to reject the GET CONFIGURATION sent by the tester. The IUT |
| is expected to respond with BAD_ACP_SEID because the SEID requested was |
| not previously configured. |
| """ |
| |
| return "OK" |
| |
| @assert_description |
| def TSC_AVDTPEX_mmi_iut_reject_close(self, **kwargs): |
| """ |
| Take action to reject the invalid CLOSE command sent by the tester. |
| """ |
| |
| return "OK" |
| |
| @assert_description |
| def TSC_AVDTPEX_mmi_user_confirm_SIG_SMG_BV_18_C(self, **kwargs): |
| """ |
| Did the IUT receive media with the following information? |
| |
| - V = RTP_Ver |
| - P = 0 (no padding bits) |
| - X = 0 (no extension) |
| - CC = 0 (no |
| contributing source) |
| - M = 0 |
| """ |
| |
| # TODO: verify |
| return "OK" |