Loading android/pandora/mmi2grpc/mmi2grpc/_modem.py +8 −0 Original line number Diff line number Diff line Loading @@ -7,10 +7,18 @@ class Modem: def __init__(self, port): s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect(("127.0.0.1", port)) self.active_calls = [] self.socket = s def close(self): for phone_number in self.active_calls: self.socket.sendall(b'REM0\r\nAT+REMOTECALL=6,0,0,"' + str(phone_number).encode("utf-8") + b'",0\r\n') self.socket.close() def call(self, phone_number): self.active_calls.append(phone_number) self.socket.sendall(b'REM0\r\nAT+REMOTECALL=4,0,0,"' + str(phone_number).encode("utf-8") + b'",129\r\n') def answer_outgoing_call(self, phone_number): self.active_calls.append(phone_number) self.socket.sendall(b'REM0\r\nAT+REMOTECALL=0,0,0,"' + str(phone_number).encode("utf-8") + b'",129\r\n') android/pandora/mmi2grpc/mmi2grpc/hfp.py +393 −10 Original line number Diff line number Diff line Loading @@ -13,11 +13,12 @@ # limitations under the License. """HFP proxy module.""" from mmi2grpc._helpers import assert_description from mmi2grpc._helpers import assert_description, match_description from mmi2grpc._proxy import ProfileProxy from pandora_experimental.hfp_grpc import HFP from pandora_experimental.host_grpc import Host from pandora_experimental.host_pb2 import ConnectabilityMode from pandora_experimental.security_grpc import Security from pandora_experimental.hfp_pb2 import AudioPath Loading @@ -29,9 +30,10 @@ import time WAIT_DELAY_BEFORE_CONNECTION = 2 # The tests needs the MMI to accept pairing confirmation request. NEEDS_WAIT_CONNECTION_BEFORE_TEST = {'HFP/AG/WBS/BV-01-I', 'HFP/AG/SLC/BV-05-I'} NEEDS_WAIT_CONNECTION_BEFORE_TEST = {"HFP/AG/WBS/BV-01-I", "HFP/AG/SLC/BV-05-I"} IXIT_PHONE_NUMBER = 42 IXIT_SECOND_PHONE_NUMBER = 43 class HFPProxy(ProfileProxy): Loading @@ -56,7 +58,7 @@ class HFPProxy(ProfileProxy): def waitConnectionCallback(self, pts_addr): self.connection = self.host.WaitConnection(address=pts_addr).connection print(f'HFP placeholder mmi: asyncWaitConnection', file=sys.stderr) print(f"HFP placeholder mmi: asyncWaitConnection", file=sys.stderr) th = threading.Timer(interval=delay, function=waitConnectionCallback, args=(self, pts_addr)) th.start() Loading @@ -77,15 +79,26 @@ class HFPProxy(ProfileProxy): return "OK" @assert_description def TSC_iut_enable_slc(self, pts_addr: bytes, **kwargs): def TSC_iut_enable_slc(self, test: str, pts_addr: bytes, **kwargs): """ Click Ok, then initiate a service level connection from the Implementation Under Test (IUT) to the PTS. """ def enable_slc(): time.sleep(2) if test == "HFP/AG/SLC/BV-02-C": self.host.SetConnectabilityMode(connectability=ConnectabilityMode.CONNECTABILITY_CONNECTABLE) self.connection = self.host.Connect(address=pts_addr).connection else: if not self.connection: self.connection = self.host.Connect(address=pts_addr).connection self.hfp.EnableSlc(connection=self.connection) threading.Thread(target=enable_slc).start() return "OK" @assert_description Loading Loading @@ -118,8 +131,7 @@ class HFPProxy(ProfileProxy): Make the Implementation Under Test (IUT) connectable, then click Ok. """ if "HFP/AG/SLC/BV-03-C" in test: self.connection = self.host.WaitConnection(pts_addr).connection self.host.SetConnectabilityMode(connectability=ConnectabilityMode.CONNECTABILITY_CONNECTABLE) return "OK" Loading Loading @@ -184,6 +196,7 @@ class HFPProxy(ProfileProxy): """ # TODO time.sleep(2) # give it time for SCO to come up return "OK" Loading Loading @@ -234,13 +247,383 @@ class HFPProxy(ProfileProxy): return "OK" @assert_description def TSC_iut_enable_audio(self, **kwargs): """ Click Ok, then initiate an audio connection (SCO) from the Implementation Under Test (IUT) to the PTS. """ def enable_audio(): time.sleep(2) self.hfp.SetAudioPath(audio_path=AudioPath.AUDIO_PATH_HANDSFREE) threading.Thread(target=enable_audio).start() return "OK" @assert_description def TSC_iut_disable_audio_slc_down_ok(self, pts_addr: bytes, **kwargs): """ Click OK, then close the audio connection (SCO) between the Implementation Under Test (IUT) and the PTS. If necessary, it is OK to close the service level connection. Do not power-off the IUT. """ self.connection = self.host.GetConnection(address=pts_addr).connection def disable_slc(): time.sleep(2) self.hfp.DisableSlc(connection=self.connection) threading.Thread(target=disable_slc).start() return "OK" @assert_description def TSC_ag_iut_call_no_slc(self, **kwargs): """ Place a call from an external line to the Implementation Under Test (IUT). When the call is active, click Ok. """ self.modem.call(IXIT_PHONE_NUMBER) time.sleep(5) # there's a delay before Android registers the call self.hfp.AnswerCall() time.sleep(2) return "OK" @assert_description def TSC_ag_iut_enable_second_call(self, **kwargs): """ Click Ok, then place a second call from an external line to the Implementation Under Test (IUT). Do not answer the call unless prompted to do so. """ def enable_second_call(): time.sleep(2) self.modem.call(IXIT_SECOND_PHONE_NUMBER) threading.Thread(target=enable_second_call).start() return "OK" @assert_description def TSC_ag_iut_call_swap(self, **kwargs): """ Click Ok, then place the current call on hold and make the incoming/held call active using the Implementation Under Test (IUT). """ self.hfp.SwapActiveCall() return "OK" @assert_description def TSC_verify_audio_second_call(self, **kwargs): """ Verify the audio is returned to the 2nd call and then click Ok. Resume action may be needed. If the audio is not returned to the 2nd call, click Cancel. """ return "OK" @assert_description def TSC_ag_iut_disable_call_after_verdict(self, **kwargs): """ After the test verdict is given, end all active calls using the external line or the Implementation Under Test (IUT). Click OK to continue. """ self.hfp.DeclineCall() return "OK" @assert_description def TSC_verify_no_ecnr(self, **kwargs): """ Verify that EC and NR functionality is disabled, then click Ok. """ return "OK" @assert_description def TSC_disable_inband_ring(self, **kwargs): """ Click Ok, then disable the in-band ringtone using the Implemenation Under Test (IUT). """ self.hfp.SetInBandRingtone(enabled=False) self.host.SoftReset() return "OK" @assert_description def TSC_wait_until_ringing(self, **kwargs): """ When the Implementation Under Test (IUT) alerts the incoming call, click Ok. """ # we are triggering a call from modem_simulator, so the alert is immediate return "OK" @assert_description def TSC_verify_incoming_call_ag(self, **kwargs): """ Verify that there is an incoming call on the Implementation Under Test (IUT). """ # we are triggering a call from modem_simulator, so this is guaranteed return "OK" @assert_description def TSC_disable_ag_cellular_network_expect_notification(self, pts_addr: bytes, **kwargs): """ Click OK. Then, disable the control channel, such that the AG is de- registered. """ self.connection = self.host.GetConnection(address=pts_addr).connection def disable_slc(): time.sleep(2) self.hfp.DisableSlc(connection=self.connection) threading.Thread(target=disable_slc).start() return "OK" @assert_description def TSC_adjust_ag_battery_level_expect_no_notification(self, **kwargs): """ Adjust the battery level on the AG to a level that should cause a battery level indication to be sent to HF. Then, click OK. """ self.hfp.SetBatteryLevel(connection=self.connection, battery_percentage=42) return "OK" @assert_description def TSC_verify_subscriber_number(self, **kwargs): """ Using the Implementation Under Test (IUT), verify that the following is a valid Audio Gateway (AG) subscriber number, then click Ok."+15551234567"nnNOTE: Subscriber service type is 145 """ return "OK" def TSC_ag_prepare_at_bldn(self, **kwargs): r""" Place the Implemenation Under Test (IUT) in a state which will accept an outgoing call set-up request from the PTS, then click OK. Note: The PTS will send a request to establish an outgoing call from the IUT to the last dialed number. Answer the incoming call when alerted. """ self.hfp.MakeCall(number=str(IXIT_PHONE_NUMBER)) self.log("Calling") time.sleep(2) self.hfp.DeclineCall() self.log("Declining") time.sleep(2) return "OK" @assert_description def TSC_ag_iut_prepare_for_atd(self, **kwargs): """ Place the Implementation Under Test (IUT) in a mode that will allow an outgoing call initiated by the PTS, and click Ok. """ return "OK" @assert_description def TSC_terminal_answer_call(self, **kwargs): """ Click Ok, then answer the incoming call on the external terminal. """ def answer_call(): time.sleep(2) self.log("Answering") self.modem.answer_outgoing_call(IXIT_PHONE_NUMBER) threading.Thread(target=answer_call).start() return "OK" @match_description def TSC_signal_strength_verify(self, **kwargs): """ Verify that the signal reported on the Implementaion Under Test \(IUT\) is proportional to the value \(out of 5\), then click Ok.[0-9] """ return "OK" @assert_description def TSC_signal_strength_impair(self, **kwargs): """ Impair the cellular signal by placing the Implementation Under Test (IUT) under partial RF shielding, then click Ok. """ return "OK" @assert_description def TSC_verify_network_operator(self, **kwargs): """ Verify the following information matches the network operator reported on the Implementation Under Test (IUT), then click Ok:"Android Virtual " """ return "OK" @assert_description def TSC_INFO_slc_with_30_seconds_wait(self, **kwargs): """ After clicking the OK button, PTS will connect to the IUT and then be idle for 30 seconds as part of the test procedure. Click OK to proceed. """ return "OK" @assert_description def TSC_ag_iut_disable_call(self, **kwargs): """ Click Ok, then end the call using the Implemention Under Test IUT). """ def disable_call(): time.sleep(2) self.hfp.DeclineCall() threading.Thread(target=disable_call).start() return "OK" @match_description def TSC_dtmf_verify(self, **kwargs): """ Verify the DTMF code, then click Ok. . """ return "OK" @assert_description def TSC_TWC_instructions(self, **kwargs): """ NOTE: The following rules apply for this test case: 1. TSPX_phone_number - the 1st call 2. TSPX_second_phone_number - the 2nd call Edits can be made within the IXIT settings for the above phone numbers. """ return "OK" def TSC_call_swap_and_disable_held_tester(self, **kwargs): """ Set the Implementation Under Test (IUT) in a state that will allow the PTS to initiate a AT+CHLD=1 operation, then click Ok. Note: Upon receiving the said command, the IUT will simultaneously drop the active call and make the held call active. """ return "OK" @assert_description def TSC_verify_audio_first_call(self, **kwargs): """ Verify the audio is returned to the 1st call and click Ok. Resume action my be needed. If the audio is not present in the 1st call, click Cancel. """ # TODO return "OK" @assert_description def TSC_ag_iut_dial_out_second(self, **kwargs): """ Verify that the last number dialed on the Implementation Under Test (IUT) matches the TSPX_Second_phone_number entered in the IXIT settings. """ # TODO return "OK" @assert_description def TSC_prepare_iut_for_vra(self, pts_addr: bytes, **kwargs): """ Place the Implementation Under Test (IUT) in a state which will allow a request from the PTS to activate voice recognition, then click Ok. """ self.hfp.SetVoiceRecognition( enabled=True, connection=self.host.GetConnection(address=pts_addr).connection, ) return "OK" @assert_description def TSC_ag_iut_clear_call_history(self, **kwargs): """ Clear the call history on the Implementation Under Test (IUT) such that there are zero records of any numbers dialed, then click Ok. """ self.hfp.ClearCallHistory() return "OK" @assert_description def TSC_reject_call(self, **kwargs): """ Click Ok, then reject the incoming call using the Implemention Under Test (IUT). """ def reject_call(): time.sleep(2) self.hfp.DeclineCall() threading.Thread(target=reject_call).start() return "OK" def _auto_confirm_requests(self, times=None): def task(): cnt = 0 pairing_events = self.security.OnPairing() for event in pairing_events: if event.WhichOneof('method') in {"just_works", "numeric_comparison"}: if event.WhichOneof("method") in {"just_works", "numeric_comparison"}: if times is None or cnt < times: cnt += 1 pairing_events.send(event=event, confirm=True) Loading android/pandora/server/configs/PtsBotTest.xml +3 −3 Original line number Diff line number Diff line Loading @@ -22,12 +22,12 @@ <test class="com.android.tradefed.testtype.pandora.PtsBotTest" > <!-- Creates a randomized temp dir for pts-bot binaries and avoid conflicts when running multiple pts-bot on the same machine --> <option name="create-bin-temp-dir" value="true"/> <!-- <option name="create-bin-temp-dir" value="true"/> --> <!-- mmi2grpc is contained inside pts-bot folder --> <option name="mmi2grpc" value="pts-bot" /> <option name="tests-config-file" value="pts_bot_tests_config.json" /> <option name="max-flaky-tests" value="2" /> <option name="max-retries-per-test" value="3" /> <option name="max-flaky-tests" value="0" /> <option name="max-retries-per-test" value="0" /> <option name="physical" value="false" /> <option name="profile" value="A2DP/SNK" /> <option name="profile" value="A2DP/SRC" /> Loading android/pandora/server/configs/pts_bot_tests_config.json +35 −35 Original line number Diff line number Diff line Loading @@ -236,14 +236,40 @@ "HFP/AG/DIS/BV-01-I", "HFP/AG/ACC/BV-08-I", "HFP/AG/ACC/BV-09-I", "HFP/AG/ACC/BV-15-I", "HFP/AG/ACR/BV-01-I", "HFP/AG/ACR/BV-02-I", "HFP/AG/ACS/BI-14-I", "HFP/AG/ACS/BV-04-I", "HFP/AG/ACS/BV-08-I", "HFP/AG/ACS/BV-11-I", "HFP/AG/ATA/BV-02-I", "HFP/AG/ATH/BV-03-I", "HFP/AG/ATH/BV-04-I", "HFP/AG/ATH/BV-06-I", "HFP/AG/CLI/BV-01-I", "HFP/AG/ECS/BV-03-I", "HFP/AG/ENO/BV-01-I", "HFP/AG/HFI/BV-02-I", "HFP/AG/ICA/BV-07-I", "HFP/AG/ICA/BV-08-I", "HFP/AG/ICA/BV-09-I", "HFP/AG/NUM/BV-01-I", "HFP/AG/OCL/BV-02-I", "HFP/AG/PSI/BV-03-C", "HFP/AG/PSI/BV-04-I", "HFP/AG/SDP/BV-01-I", "HFP/AG/SLC/BV-01-C", "HFP/AG/SLC/BV-03-C", "HFP/AG/SLC/BV-09-I", "HFP/AG/SLC/BV-10-I", "HFP/AG/TCA/BV-02-I", "HFP/AG/TCA/BV-03-I", "HFP/AG/TCA/BV-04-I", "HFP/AG/TCA/BV-05-I", "HFP/AG/TDC/BV-01-I", "HFP/AG/TWC/BV-02-I", "HFP/AG/TWC/BV-03-I", "HFP/AG/WBS/BV-01-I", "HID/HOS/DAT/BV-01-C", "HID/HOS/HCE/BV-01-I", Loading Loading @@ -582,60 +608,32 @@ "GATT/SR/GAW/BV-11-C", "HFP/AG/TRS/BV-01-C", "HFP/AG/PSI/BV-01-C", "HFP/AG/PSI/BV-04-I", "HFP/AG/ACS/BV-04-I", "HFP/AG/ACS/BV-08-I", "HFP/AG/ACS/BV-11-I", "HFP/AG/ACC/BI-13-I", "HFP/AG/ACS/BV-16-I", "HFP/AG/CLI/BV-01-I", "HFP/AG/ICA/BV-04-I", "HFP/AG/ICA/BV-07-I", "HFP/AG/ICA/BV-08-I", "HFP/AG/ICA/BV-09-I", "HFP/AG/TCA/BV-01-I", "HFP/AG/TCA/BV-02-I", "HFP/AG/TCA/BV-03-I", "HFP/AG/TCA/BV-05-I", "HFP/AG/ATH/BV-03-I", "HFP/AG/ATH/BV-04-I", "HFP/AG/ATH/BV-06-I", "HFP/AG/ATA/BV-01-I", "HFP/AG/ATA/BV-02-I", "HFP/AG/OCN/BV-01-I", "HFP/AG/OCL/BV-01-I", "HFP/AG/OCM/BV-01-I", "HFP/AG/OCM/BV-02-I", "HFP/AG/OCL/BV-01-I", "HFP/AG/OCL/BV-02-I", "HFP/AG/TWC/BV-02-I", "HFP/AG/TWC/BV-03-I", "HFP/AG/TWC/BV-05-I", "HFP/AG/ENO/BV-01-I", "HFP/AG/VRA/BV-01-I", "HFP/AG/TDC/BV-01-I", "HFP/AG/ECS/BV-03-I", "HFP/AG/NUM/BV-01-I", "HFP/AG/SLC/BV-01-C", "HFP/AG/SLC/BV-02-C", "HFP/AG/SLC/BV-03-C", "HFP/AG/SLC/BV-04-C", "HFP/AG/SLC/BV-05-I", "HFP/AG/SLC/BV-06-I", "HFP/AG/SLC/BV-07-I", "HFP/AG/ACC/BV-10-I", "HFP/AG/ACC/BV-11-I", "HFP/AG/ACC/BI-12-I", "HFP/AG/ACC/BI-13-I", "HFP/AG/ACC/BI-14-I", "HFP/AG/ACC/BV-15-I", "HFP/AG/SDP/BV-01-I", "HFP/AG/ICA/BV-06-I", "HFP/AG/IIA/BV-01-I", "HFP/AG/IIA/BV-02-I", "HFP/AG/IIC/BV-02-I", "HFP/AG/IID/BV-01-I", "HFP/AG/IID/BV-03-I", "HFP/AG/IIC/BV-01-I", "HFP/AG/IIC/BV-02-I", "HFP/AG/IIC/BV-03-I", "HFP/AG/HFI/BI-03-I", "HFP/AG/OCN/BV-01-I", "HFP/AG/SLC/BV-04-C", "HID/HOS/HCR/BV-01-I", "L2CAP/LE/CFC/BV-07-C", "L2CAP/LE/CFC/BV-11-C", Loading Loading @@ -1216,7 +1214,9 @@ "TSPC_HFP_2_3": true, "TSPC_HFP_2_3b": true, "TSPC_HFP_2_3c": true, "TSPC_HFP_2_4a": true, "TSPC_HFP_2_4b": true, "TSPC_HFP_2_5": true, "TSPC_HFP_2_6": true, "TSPC_HFP_2_7": true, "TSPC_HFP_2_7a": true, Loading Loading @@ -1674,7 +1674,7 @@ "HCI": {}, "HFP": { "TSPX_phone_number": "42", "TSPX_second_phone_number": "42" "TSPX_second_phone_number": "43" }, "HID": {}, "HOGP": {}, Loading android/pandora/server/src/com/android/pandora/Hfp.kt +87 −3 Original line number Diff line number Diff line Loading @@ -24,10 +24,15 @@ import android.bluetooth.BluetoothProfile import android.content.Context import android.content.Intent import android.content.IntentFilter import android.net.Uri import android.os.Bundle import android.os.IBinder import android.provider.CallLog import android.telecom.Call import android.telecom.CallAudioState import android.telecom.InCallService import android.telecom.TelecomManager import android.telecom.VideoProfile import com.google.protobuf.Empty import io.grpc.stub.StreamObserver import kotlinx.coroutines.CoroutineScope Loading @@ -53,8 +58,7 @@ class Hfp(val context: Context) : HFPImplBase() { private val bluetoothHfp = getProfileProxy<BluetoothHeadset>(context, BluetoothProfile.HEADSET) companion object { @SuppressLint("StaticFieldLeak") private lateinit var inCallService: InCallService @SuppressLint("StaticFieldLeak") private lateinit var inCallService: InCallService } init { Loading @@ -64,6 +68,8 @@ class Hfp(val context: Context) : HFPImplBase() { // kill any existing call telecomManager.endCall() shell("su root setprop persist.bluetooth.disableinbandringing false") } fun deinit() { Loading Loading @@ -129,7 +135,7 @@ class Hfp(val context: Context) : HFPImplBase() { grpcUnary(scope, responseObserver) { when (request.audioPath!!) { AudioPath.AUDIO_PATH_UNKNOWN, AudioPath.UNRECOGNIZED -> {} AudioPath.UNRECOGNIZED, -> {} AudioPath.AUDIO_PATH_HANDSFREE -> { check(bluetoothHfp.getActiveDevice() != null) inCallService.setAudioRoute(CallAudioState.ROUTE_BLUETOOTH) Loading @@ -139,4 +145,82 @@ class Hfp(val context: Context) : HFPImplBase() { SetAudioPathResponse.getDefaultInstance() } } override fun answerCall( request: AnswerCallRequest, responseObserver: StreamObserver<AnswerCallResponse>, ) { grpcUnary(scope, responseObserver) { telecomManager.acceptRingingCall() AnswerCallResponse.getDefaultInstance() } } override fun swapActiveCall( request: SwapActiveCallRequest, responseObserver: StreamObserver<SwapActiveCallResponse>, ) { grpcUnary(scope, responseObserver) { val callsToActivate = mutableListOf<Call>() for (call in inCallService.calls) { if (call.details.state == Call.STATE_ACTIVE) { call.hold() } else { callsToActivate.add(call) } } for (call in callsToActivate) { call.answer(VideoProfile.STATE_AUDIO_ONLY) } inCallService.calls[0].hold() inCallService.calls[1].unhold() SwapActiveCallResponse.getDefaultInstance() } } override fun setInBandRingtone( request: SetInBandRingtoneRequest, responseObserver: StreamObserver<SetInBandRingtoneResponse>, ) { grpcUnary(scope, responseObserver) { shell( "su root setprop persist.bluetooth.disableinbandringing " + (!request.enabled).toString() ) SetInBandRingtoneResponse.getDefaultInstance() } } override fun makeCall( request: MakeCallRequest, responseObserver: StreamObserver<MakeCallResponse> ) { grpcUnary(scope, responseObserver) { telecomManager.placeCall(Uri.fromParts("tel", request.number, null), Bundle()) MakeCallResponse.getDefaultInstance() } } override fun setVoiceRecognition( request: SetVoiceRecognitionRequest, responseObserver: StreamObserver<SetVoiceRecognitionResponse> ) { grpcUnary(scope, responseObserver) { if (request.enabled) { bluetoothHfp.startVoiceRecognition(request.connection.toBluetoothDevice(bluetoothAdapter)) } else { bluetoothHfp.stopVoiceRecognition(request.connection.toBluetoothDevice(bluetoothAdapter)) } SetVoiceRecognitionResponse.getDefaultInstance() } } override fun clearCallHistory( request: ClearCallHistoryRequest, responseObserver: StreamObserver<ClearCallHistoryResponse> ) { grpcUnary(scope, responseObserver) { context.contentResolver.delete(CallLog.Calls.CONTENT_URI, null, null) ClearCallHistoryResponse.getDefaultInstance() } } } Loading
android/pandora/mmi2grpc/mmi2grpc/_modem.py +8 −0 Original line number Diff line number Diff line Loading @@ -7,10 +7,18 @@ class Modem: def __init__(self, port): s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect(("127.0.0.1", port)) self.active_calls = [] self.socket = s def close(self): for phone_number in self.active_calls: self.socket.sendall(b'REM0\r\nAT+REMOTECALL=6,0,0,"' + str(phone_number).encode("utf-8") + b'",0\r\n') self.socket.close() def call(self, phone_number): self.active_calls.append(phone_number) self.socket.sendall(b'REM0\r\nAT+REMOTECALL=4,0,0,"' + str(phone_number).encode("utf-8") + b'",129\r\n') def answer_outgoing_call(self, phone_number): self.active_calls.append(phone_number) self.socket.sendall(b'REM0\r\nAT+REMOTECALL=0,0,0,"' + str(phone_number).encode("utf-8") + b'",129\r\n')
android/pandora/mmi2grpc/mmi2grpc/hfp.py +393 −10 Original line number Diff line number Diff line Loading @@ -13,11 +13,12 @@ # limitations under the License. """HFP proxy module.""" from mmi2grpc._helpers import assert_description from mmi2grpc._helpers import assert_description, match_description from mmi2grpc._proxy import ProfileProxy from pandora_experimental.hfp_grpc import HFP from pandora_experimental.host_grpc import Host from pandora_experimental.host_pb2 import ConnectabilityMode from pandora_experimental.security_grpc import Security from pandora_experimental.hfp_pb2 import AudioPath Loading @@ -29,9 +30,10 @@ import time WAIT_DELAY_BEFORE_CONNECTION = 2 # The tests needs the MMI to accept pairing confirmation request. NEEDS_WAIT_CONNECTION_BEFORE_TEST = {'HFP/AG/WBS/BV-01-I', 'HFP/AG/SLC/BV-05-I'} NEEDS_WAIT_CONNECTION_BEFORE_TEST = {"HFP/AG/WBS/BV-01-I", "HFP/AG/SLC/BV-05-I"} IXIT_PHONE_NUMBER = 42 IXIT_SECOND_PHONE_NUMBER = 43 class HFPProxy(ProfileProxy): Loading @@ -56,7 +58,7 @@ class HFPProxy(ProfileProxy): def waitConnectionCallback(self, pts_addr): self.connection = self.host.WaitConnection(address=pts_addr).connection print(f'HFP placeholder mmi: asyncWaitConnection', file=sys.stderr) print(f"HFP placeholder mmi: asyncWaitConnection", file=sys.stderr) th = threading.Timer(interval=delay, function=waitConnectionCallback, args=(self, pts_addr)) th.start() Loading @@ -77,15 +79,26 @@ class HFPProxy(ProfileProxy): return "OK" @assert_description def TSC_iut_enable_slc(self, pts_addr: bytes, **kwargs): def TSC_iut_enable_slc(self, test: str, pts_addr: bytes, **kwargs): """ Click Ok, then initiate a service level connection from the Implementation Under Test (IUT) to the PTS. """ def enable_slc(): time.sleep(2) if test == "HFP/AG/SLC/BV-02-C": self.host.SetConnectabilityMode(connectability=ConnectabilityMode.CONNECTABILITY_CONNECTABLE) self.connection = self.host.Connect(address=pts_addr).connection else: if not self.connection: self.connection = self.host.Connect(address=pts_addr).connection self.hfp.EnableSlc(connection=self.connection) threading.Thread(target=enable_slc).start() return "OK" @assert_description Loading Loading @@ -118,8 +131,7 @@ class HFPProxy(ProfileProxy): Make the Implementation Under Test (IUT) connectable, then click Ok. """ if "HFP/AG/SLC/BV-03-C" in test: self.connection = self.host.WaitConnection(pts_addr).connection self.host.SetConnectabilityMode(connectability=ConnectabilityMode.CONNECTABILITY_CONNECTABLE) return "OK" Loading Loading @@ -184,6 +196,7 @@ class HFPProxy(ProfileProxy): """ # TODO time.sleep(2) # give it time for SCO to come up return "OK" Loading Loading @@ -234,13 +247,383 @@ class HFPProxy(ProfileProxy): return "OK" @assert_description def TSC_iut_enable_audio(self, **kwargs): """ Click Ok, then initiate an audio connection (SCO) from the Implementation Under Test (IUT) to the PTS. """ def enable_audio(): time.sleep(2) self.hfp.SetAudioPath(audio_path=AudioPath.AUDIO_PATH_HANDSFREE) threading.Thread(target=enable_audio).start() return "OK" @assert_description def TSC_iut_disable_audio_slc_down_ok(self, pts_addr: bytes, **kwargs): """ Click OK, then close the audio connection (SCO) between the Implementation Under Test (IUT) and the PTS. If necessary, it is OK to close the service level connection. Do not power-off the IUT. """ self.connection = self.host.GetConnection(address=pts_addr).connection def disable_slc(): time.sleep(2) self.hfp.DisableSlc(connection=self.connection) threading.Thread(target=disable_slc).start() return "OK" @assert_description def TSC_ag_iut_call_no_slc(self, **kwargs): """ Place a call from an external line to the Implementation Under Test (IUT). When the call is active, click Ok. """ self.modem.call(IXIT_PHONE_NUMBER) time.sleep(5) # there's a delay before Android registers the call self.hfp.AnswerCall() time.sleep(2) return "OK" @assert_description def TSC_ag_iut_enable_second_call(self, **kwargs): """ Click Ok, then place a second call from an external line to the Implementation Under Test (IUT). Do not answer the call unless prompted to do so. """ def enable_second_call(): time.sleep(2) self.modem.call(IXIT_SECOND_PHONE_NUMBER) threading.Thread(target=enable_second_call).start() return "OK" @assert_description def TSC_ag_iut_call_swap(self, **kwargs): """ Click Ok, then place the current call on hold and make the incoming/held call active using the Implementation Under Test (IUT). """ self.hfp.SwapActiveCall() return "OK" @assert_description def TSC_verify_audio_second_call(self, **kwargs): """ Verify the audio is returned to the 2nd call and then click Ok. Resume action may be needed. If the audio is not returned to the 2nd call, click Cancel. """ return "OK" @assert_description def TSC_ag_iut_disable_call_after_verdict(self, **kwargs): """ After the test verdict is given, end all active calls using the external line or the Implementation Under Test (IUT). Click OK to continue. """ self.hfp.DeclineCall() return "OK" @assert_description def TSC_verify_no_ecnr(self, **kwargs): """ Verify that EC and NR functionality is disabled, then click Ok. """ return "OK" @assert_description def TSC_disable_inband_ring(self, **kwargs): """ Click Ok, then disable the in-band ringtone using the Implemenation Under Test (IUT). """ self.hfp.SetInBandRingtone(enabled=False) self.host.SoftReset() return "OK" @assert_description def TSC_wait_until_ringing(self, **kwargs): """ When the Implementation Under Test (IUT) alerts the incoming call, click Ok. """ # we are triggering a call from modem_simulator, so the alert is immediate return "OK" @assert_description def TSC_verify_incoming_call_ag(self, **kwargs): """ Verify that there is an incoming call on the Implementation Under Test (IUT). """ # we are triggering a call from modem_simulator, so this is guaranteed return "OK" @assert_description def TSC_disable_ag_cellular_network_expect_notification(self, pts_addr: bytes, **kwargs): """ Click OK. Then, disable the control channel, such that the AG is de- registered. """ self.connection = self.host.GetConnection(address=pts_addr).connection def disable_slc(): time.sleep(2) self.hfp.DisableSlc(connection=self.connection) threading.Thread(target=disable_slc).start() return "OK" @assert_description def TSC_adjust_ag_battery_level_expect_no_notification(self, **kwargs): """ Adjust the battery level on the AG to a level that should cause a battery level indication to be sent to HF. Then, click OK. """ self.hfp.SetBatteryLevel(connection=self.connection, battery_percentage=42) return "OK" @assert_description def TSC_verify_subscriber_number(self, **kwargs): """ Using the Implementation Under Test (IUT), verify that the following is a valid Audio Gateway (AG) subscriber number, then click Ok."+15551234567"nnNOTE: Subscriber service type is 145 """ return "OK" def TSC_ag_prepare_at_bldn(self, **kwargs): r""" Place the Implemenation Under Test (IUT) in a state which will accept an outgoing call set-up request from the PTS, then click OK. Note: The PTS will send a request to establish an outgoing call from the IUT to the last dialed number. Answer the incoming call when alerted. """ self.hfp.MakeCall(number=str(IXIT_PHONE_NUMBER)) self.log("Calling") time.sleep(2) self.hfp.DeclineCall() self.log("Declining") time.sleep(2) return "OK" @assert_description def TSC_ag_iut_prepare_for_atd(self, **kwargs): """ Place the Implementation Under Test (IUT) in a mode that will allow an outgoing call initiated by the PTS, and click Ok. """ return "OK" @assert_description def TSC_terminal_answer_call(self, **kwargs): """ Click Ok, then answer the incoming call on the external terminal. """ def answer_call(): time.sleep(2) self.log("Answering") self.modem.answer_outgoing_call(IXIT_PHONE_NUMBER) threading.Thread(target=answer_call).start() return "OK" @match_description def TSC_signal_strength_verify(self, **kwargs): """ Verify that the signal reported on the Implementaion Under Test \(IUT\) is proportional to the value \(out of 5\), then click Ok.[0-9] """ return "OK" @assert_description def TSC_signal_strength_impair(self, **kwargs): """ Impair the cellular signal by placing the Implementation Under Test (IUT) under partial RF shielding, then click Ok. """ return "OK" @assert_description def TSC_verify_network_operator(self, **kwargs): """ Verify the following information matches the network operator reported on the Implementation Under Test (IUT), then click Ok:"Android Virtual " """ return "OK" @assert_description def TSC_INFO_slc_with_30_seconds_wait(self, **kwargs): """ After clicking the OK button, PTS will connect to the IUT and then be idle for 30 seconds as part of the test procedure. Click OK to proceed. """ return "OK" @assert_description def TSC_ag_iut_disable_call(self, **kwargs): """ Click Ok, then end the call using the Implemention Under Test IUT). """ def disable_call(): time.sleep(2) self.hfp.DeclineCall() threading.Thread(target=disable_call).start() return "OK" @match_description def TSC_dtmf_verify(self, **kwargs): """ Verify the DTMF code, then click Ok. . """ return "OK" @assert_description def TSC_TWC_instructions(self, **kwargs): """ NOTE: The following rules apply for this test case: 1. TSPX_phone_number - the 1st call 2. TSPX_second_phone_number - the 2nd call Edits can be made within the IXIT settings for the above phone numbers. """ return "OK" def TSC_call_swap_and_disable_held_tester(self, **kwargs): """ Set the Implementation Under Test (IUT) in a state that will allow the PTS to initiate a AT+CHLD=1 operation, then click Ok. Note: Upon receiving the said command, the IUT will simultaneously drop the active call and make the held call active. """ return "OK" @assert_description def TSC_verify_audio_first_call(self, **kwargs): """ Verify the audio is returned to the 1st call and click Ok. Resume action my be needed. If the audio is not present in the 1st call, click Cancel. """ # TODO return "OK" @assert_description def TSC_ag_iut_dial_out_second(self, **kwargs): """ Verify that the last number dialed on the Implementation Under Test (IUT) matches the TSPX_Second_phone_number entered in the IXIT settings. """ # TODO return "OK" @assert_description def TSC_prepare_iut_for_vra(self, pts_addr: bytes, **kwargs): """ Place the Implementation Under Test (IUT) in a state which will allow a request from the PTS to activate voice recognition, then click Ok. """ self.hfp.SetVoiceRecognition( enabled=True, connection=self.host.GetConnection(address=pts_addr).connection, ) return "OK" @assert_description def TSC_ag_iut_clear_call_history(self, **kwargs): """ Clear the call history on the Implementation Under Test (IUT) such that there are zero records of any numbers dialed, then click Ok. """ self.hfp.ClearCallHistory() return "OK" @assert_description def TSC_reject_call(self, **kwargs): """ Click Ok, then reject the incoming call using the Implemention Under Test (IUT). """ def reject_call(): time.sleep(2) self.hfp.DeclineCall() threading.Thread(target=reject_call).start() return "OK" def _auto_confirm_requests(self, times=None): def task(): cnt = 0 pairing_events = self.security.OnPairing() for event in pairing_events: if event.WhichOneof('method') in {"just_works", "numeric_comparison"}: if event.WhichOneof("method") in {"just_works", "numeric_comparison"}: if times is None or cnt < times: cnt += 1 pairing_events.send(event=event, confirm=True) Loading
android/pandora/server/configs/PtsBotTest.xml +3 −3 Original line number Diff line number Diff line Loading @@ -22,12 +22,12 @@ <test class="com.android.tradefed.testtype.pandora.PtsBotTest" > <!-- Creates a randomized temp dir for pts-bot binaries and avoid conflicts when running multiple pts-bot on the same machine --> <option name="create-bin-temp-dir" value="true"/> <!-- <option name="create-bin-temp-dir" value="true"/> --> <!-- mmi2grpc is contained inside pts-bot folder --> <option name="mmi2grpc" value="pts-bot" /> <option name="tests-config-file" value="pts_bot_tests_config.json" /> <option name="max-flaky-tests" value="2" /> <option name="max-retries-per-test" value="3" /> <option name="max-flaky-tests" value="0" /> <option name="max-retries-per-test" value="0" /> <option name="physical" value="false" /> <option name="profile" value="A2DP/SNK" /> <option name="profile" value="A2DP/SRC" /> Loading
android/pandora/server/configs/pts_bot_tests_config.json +35 −35 Original line number Diff line number Diff line Loading @@ -236,14 +236,40 @@ "HFP/AG/DIS/BV-01-I", "HFP/AG/ACC/BV-08-I", "HFP/AG/ACC/BV-09-I", "HFP/AG/ACC/BV-15-I", "HFP/AG/ACR/BV-01-I", "HFP/AG/ACR/BV-02-I", "HFP/AG/ACS/BI-14-I", "HFP/AG/ACS/BV-04-I", "HFP/AG/ACS/BV-08-I", "HFP/AG/ACS/BV-11-I", "HFP/AG/ATA/BV-02-I", "HFP/AG/ATH/BV-03-I", "HFP/AG/ATH/BV-04-I", "HFP/AG/ATH/BV-06-I", "HFP/AG/CLI/BV-01-I", "HFP/AG/ECS/BV-03-I", "HFP/AG/ENO/BV-01-I", "HFP/AG/HFI/BV-02-I", "HFP/AG/ICA/BV-07-I", "HFP/AG/ICA/BV-08-I", "HFP/AG/ICA/BV-09-I", "HFP/AG/NUM/BV-01-I", "HFP/AG/OCL/BV-02-I", "HFP/AG/PSI/BV-03-C", "HFP/AG/PSI/BV-04-I", "HFP/AG/SDP/BV-01-I", "HFP/AG/SLC/BV-01-C", "HFP/AG/SLC/BV-03-C", "HFP/AG/SLC/BV-09-I", "HFP/AG/SLC/BV-10-I", "HFP/AG/TCA/BV-02-I", "HFP/AG/TCA/BV-03-I", "HFP/AG/TCA/BV-04-I", "HFP/AG/TCA/BV-05-I", "HFP/AG/TDC/BV-01-I", "HFP/AG/TWC/BV-02-I", "HFP/AG/TWC/BV-03-I", "HFP/AG/WBS/BV-01-I", "HID/HOS/DAT/BV-01-C", "HID/HOS/HCE/BV-01-I", Loading Loading @@ -582,60 +608,32 @@ "GATT/SR/GAW/BV-11-C", "HFP/AG/TRS/BV-01-C", "HFP/AG/PSI/BV-01-C", "HFP/AG/PSI/BV-04-I", "HFP/AG/ACS/BV-04-I", "HFP/AG/ACS/BV-08-I", "HFP/AG/ACS/BV-11-I", "HFP/AG/ACC/BI-13-I", "HFP/AG/ACS/BV-16-I", "HFP/AG/CLI/BV-01-I", "HFP/AG/ICA/BV-04-I", "HFP/AG/ICA/BV-07-I", "HFP/AG/ICA/BV-08-I", "HFP/AG/ICA/BV-09-I", "HFP/AG/TCA/BV-01-I", "HFP/AG/TCA/BV-02-I", "HFP/AG/TCA/BV-03-I", "HFP/AG/TCA/BV-05-I", "HFP/AG/ATH/BV-03-I", "HFP/AG/ATH/BV-04-I", "HFP/AG/ATH/BV-06-I", "HFP/AG/ATA/BV-01-I", "HFP/AG/ATA/BV-02-I", "HFP/AG/OCN/BV-01-I", "HFP/AG/OCL/BV-01-I", "HFP/AG/OCM/BV-01-I", "HFP/AG/OCM/BV-02-I", "HFP/AG/OCL/BV-01-I", "HFP/AG/OCL/BV-02-I", "HFP/AG/TWC/BV-02-I", "HFP/AG/TWC/BV-03-I", "HFP/AG/TWC/BV-05-I", "HFP/AG/ENO/BV-01-I", "HFP/AG/VRA/BV-01-I", "HFP/AG/TDC/BV-01-I", "HFP/AG/ECS/BV-03-I", "HFP/AG/NUM/BV-01-I", "HFP/AG/SLC/BV-01-C", "HFP/AG/SLC/BV-02-C", "HFP/AG/SLC/BV-03-C", "HFP/AG/SLC/BV-04-C", "HFP/AG/SLC/BV-05-I", "HFP/AG/SLC/BV-06-I", "HFP/AG/SLC/BV-07-I", "HFP/AG/ACC/BV-10-I", "HFP/AG/ACC/BV-11-I", "HFP/AG/ACC/BI-12-I", "HFP/AG/ACC/BI-13-I", "HFP/AG/ACC/BI-14-I", "HFP/AG/ACC/BV-15-I", "HFP/AG/SDP/BV-01-I", "HFP/AG/ICA/BV-06-I", "HFP/AG/IIA/BV-01-I", "HFP/AG/IIA/BV-02-I", "HFP/AG/IIC/BV-02-I", "HFP/AG/IID/BV-01-I", "HFP/AG/IID/BV-03-I", "HFP/AG/IIC/BV-01-I", "HFP/AG/IIC/BV-02-I", "HFP/AG/IIC/BV-03-I", "HFP/AG/HFI/BI-03-I", "HFP/AG/OCN/BV-01-I", "HFP/AG/SLC/BV-04-C", "HID/HOS/HCR/BV-01-I", "L2CAP/LE/CFC/BV-07-C", "L2CAP/LE/CFC/BV-11-C", Loading Loading @@ -1216,7 +1214,9 @@ "TSPC_HFP_2_3": true, "TSPC_HFP_2_3b": true, "TSPC_HFP_2_3c": true, "TSPC_HFP_2_4a": true, "TSPC_HFP_2_4b": true, "TSPC_HFP_2_5": true, "TSPC_HFP_2_6": true, "TSPC_HFP_2_7": true, "TSPC_HFP_2_7a": true, Loading Loading @@ -1674,7 +1674,7 @@ "HCI": {}, "HFP": { "TSPX_phone_number": "42", "TSPX_second_phone_number": "42" "TSPX_second_phone_number": "43" }, "HID": {}, "HOGP": {}, Loading
android/pandora/server/src/com/android/pandora/Hfp.kt +87 −3 Original line number Diff line number Diff line Loading @@ -24,10 +24,15 @@ import android.bluetooth.BluetoothProfile import android.content.Context import android.content.Intent import android.content.IntentFilter import android.net.Uri import android.os.Bundle import android.os.IBinder import android.provider.CallLog import android.telecom.Call import android.telecom.CallAudioState import android.telecom.InCallService import android.telecom.TelecomManager import android.telecom.VideoProfile import com.google.protobuf.Empty import io.grpc.stub.StreamObserver import kotlinx.coroutines.CoroutineScope Loading @@ -53,8 +58,7 @@ class Hfp(val context: Context) : HFPImplBase() { private val bluetoothHfp = getProfileProxy<BluetoothHeadset>(context, BluetoothProfile.HEADSET) companion object { @SuppressLint("StaticFieldLeak") private lateinit var inCallService: InCallService @SuppressLint("StaticFieldLeak") private lateinit var inCallService: InCallService } init { Loading @@ -64,6 +68,8 @@ class Hfp(val context: Context) : HFPImplBase() { // kill any existing call telecomManager.endCall() shell("su root setprop persist.bluetooth.disableinbandringing false") } fun deinit() { Loading Loading @@ -129,7 +135,7 @@ class Hfp(val context: Context) : HFPImplBase() { grpcUnary(scope, responseObserver) { when (request.audioPath!!) { AudioPath.AUDIO_PATH_UNKNOWN, AudioPath.UNRECOGNIZED -> {} AudioPath.UNRECOGNIZED, -> {} AudioPath.AUDIO_PATH_HANDSFREE -> { check(bluetoothHfp.getActiveDevice() != null) inCallService.setAudioRoute(CallAudioState.ROUTE_BLUETOOTH) Loading @@ -139,4 +145,82 @@ class Hfp(val context: Context) : HFPImplBase() { SetAudioPathResponse.getDefaultInstance() } } override fun answerCall( request: AnswerCallRequest, responseObserver: StreamObserver<AnswerCallResponse>, ) { grpcUnary(scope, responseObserver) { telecomManager.acceptRingingCall() AnswerCallResponse.getDefaultInstance() } } override fun swapActiveCall( request: SwapActiveCallRequest, responseObserver: StreamObserver<SwapActiveCallResponse>, ) { grpcUnary(scope, responseObserver) { val callsToActivate = mutableListOf<Call>() for (call in inCallService.calls) { if (call.details.state == Call.STATE_ACTIVE) { call.hold() } else { callsToActivate.add(call) } } for (call in callsToActivate) { call.answer(VideoProfile.STATE_AUDIO_ONLY) } inCallService.calls[0].hold() inCallService.calls[1].unhold() SwapActiveCallResponse.getDefaultInstance() } } override fun setInBandRingtone( request: SetInBandRingtoneRequest, responseObserver: StreamObserver<SetInBandRingtoneResponse>, ) { grpcUnary(scope, responseObserver) { shell( "su root setprop persist.bluetooth.disableinbandringing " + (!request.enabled).toString() ) SetInBandRingtoneResponse.getDefaultInstance() } } override fun makeCall( request: MakeCallRequest, responseObserver: StreamObserver<MakeCallResponse> ) { grpcUnary(scope, responseObserver) { telecomManager.placeCall(Uri.fromParts("tel", request.number, null), Bundle()) MakeCallResponse.getDefaultInstance() } } override fun setVoiceRecognition( request: SetVoiceRecognitionRequest, responseObserver: StreamObserver<SetVoiceRecognitionResponse> ) { grpcUnary(scope, responseObserver) { if (request.enabled) { bluetoothHfp.startVoiceRecognition(request.connection.toBluetoothDevice(bluetoothAdapter)) } else { bluetoothHfp.stopVoiceRecognition(request.connection.toBluetoothDevice(bluetoothAdapter)) } SetVoiceRecognitionResponse.getDefaultInstance() } } override fun clearCallHistory( request: ClearCallHistoryRequest, responseObserver: StreamObserver<ClearCallHistoryResponse> ) { grpcUnary(scope, responseObserver) { context.contentResolver.delete(CallLog.Calls.CONTENT_URI, null, null) ClearCallHistoryResponse.getDefaultInstance() } } }