Loading android/pandora/mmi2grpc/mmi2grpc/a2dp.py +25 −27 Original line number Diff line number Diff line Loading @@ -11,7 +11,6 @@ # 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 Loading Loading @@ -49,15 +48,12 @@ class A2DPProxy(ProfileProxy): 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 ) 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): 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. Loading @@ -71,27 +67,34 @@ class A2DPProxy(ProfileProxy): """ if "SRC" in test: self.connection = self.host.WaitConnection( address=pts_addr).connection self.connection = self.host.WaitConnection(address=pts_addr).connection try: if "INT" in test: self.source = self.a2dp.OpenSource( connection=self.connection).source self.source = self.a2dp.OpenSource(connection=self.connection).source else: self.source = self.a2dp.WaitSource( connection=self.connection).source self.source = self.a2dp.WaitSource(connection=self.connection).source except RpcError: pass else: self.connection = self.host.WaitConnection( address=pts_addr).connection self.connection = self.host.WaitConnection(address=pts_addr).connection try: self.sink = self.a2dp.WaitSink( connection=self.connection).sink self.sink = self.a2dp.WaitSink(connection=self.connection).sink except RpcError: pass return "OK" @assert_description def TSC_AVDTP_mmi_iut_accept_disconnect(self, **kwargs): """ If necessary, take action to accept the AVDTP Signaling Channnel Disconnection initiated by the tester. Note: If an AVCTP signaling channel was established it will also be disconnected. """ return "OK" @assert_description def TSC_AVDTP_mmi_iut_initiate_discover(self, **kwargs): """ Loading Loading @@ -152,8 +155,7 @@ class A2DPProxy(ProfileProxy): return "OK" @assert_description def TSC_AVDTP_mmi_iut_initiate_out_of_range( self, pts_addr: bytes, **kwargs): 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. Loading @@ -162,8 +164,7 @@ class A2DPProxy(ProfileProxy): """ if self.connection is None: self.connection = self.host.GetConnection( address=pts_addr).connection self.connection = self.host.GetConnection(address=pts_addr).connection self.host.Disconnect(connection=self.connection) self.connection = None self.sink = None Loading @@ -181,11 +182,8 @@ class A2DPProxy(ProfileProxy): 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"): 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 Loading android/pandora/server/configs/pts_bot_tests_config.json +2 −2 Original line number Diff line number Diff line Loading @@ -8,6 +8,7 @@ "A2DP/SRC/SET/BV-02-I", "A2DP/SRC/SET/BV-04-I", "A2DP/SNK/AS/BV-01-I", "A2DP/SNK/AS/BV-02-I", "A2DP/SNK/CC/BV-01-I", "A2DP/SNK/CC/BV-02-I", "A2DP/SNK/CC/BV-05-I", Loading @@ -15,6 +16,7 @@ "A2DP/SNK/CC/BV-07-I", "A2DP/SNK/CC/BV-08-I", "A2DP/SNK/REL/BV-01-I", "A2DP/SNK/REL/BV-02-I", "A2DP/SNK/SET/BV-01-I", "A2DP/SNK/SET/BV-02-I", "A2DP/SNK/SET/BV-03-I", Loading Loading @@ -277,8 +279,6 @@ "A2DP/SRC/SUS/BV-01-I", "A2DP/SRC/SUS/BV-02-I", "A2DP/SRC/SYN/BV-02-I", "A2DP/SNK/AS/BV-02-I", "A2DP/SNK/REL/BV-02-I", "A2DP/SNK/SDP/BV-02-I", "A2DP/SNK/SET/BV-04-I", "A2DP/SNK/SET/BV-05-I", Loading android/pandora/server/src/com/android/pandora/A2dpSink.kt 0 → 100644 +128 −0 Original line number Diff line number Diff line /* * Copyright (C) 2022 The Android Open Source Project * * 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 * * http://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. */ package com.android.pandora import android.bluetooth.BluetoothA2dpSink import android.bluetooth.BluetoothAdapter import android.bluetooth.BluetoothDevice import android.bluetooth.BluetoothManager import android.bluetooth.BluetoothProfile import android.content.Context import android.content.Intent import android.content.IntentFilter import android.media.* import android.util.Log import io.grpc.Status import io.grpc.stub.StreamObserver import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.cancel import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.shareIn import pandora.A2DPGrpc.A2DPImplBase import pandora.A2dpProto.* @kotlinx.coroutines.ExperimentalCoroutinesApi class A2dpSink(val context: Context) : A2DPImplBase() { private val TAG = "PandoraA2dpSink" private val scope: CoroutineScope private val flow: Flow<Intent> private val bluetoothManager = context.getSystemService(BluetoothManager::class.java)!! private val bluetoothAdapter = bluetoothManager.adapter private val bluetoothA2dpSink = getProfileProxy<BluetoothA2dpSink>(context, BluetoothProfile.A2DP_SINK) init { scope = CoroutineScope(Dispatchers.Default) val intentFilter = IntentFilter() intentFilter.addAction(BluetoothA2dpSink.ACTION_CONNECTION_STATE_CHANGED) flow = intentFlow(context, intentFilter).shareIn(scope, SharingStarted.Eagerly) } fun deinit() { bluetoothAdapter.closeProfileProxy(BluetoothProfile.A2DP_SINK, bluetoothA2dpSink) scope.cancel() } override fun waitSink( request: WaitSinkRequest, responseObserver: StreamObserver<WaitSinkResponse> ) { grpcUnary<WaitSinkResponse>(scope, responseObserver) { val device = request.connection.toBluetoothDevice(bluetoothAdapter) Log.i(TAG, "waitSink: device=$device") if (device.getBondState() != BluetoothDevice.BOND_BONDED) { Log.e(TAG, "Device is not bonded, cannot wait for stream") throw Status.UNKNOWN.asException() } if (bluetoothA2dpSink.getConnectionState(device) != BluetoothProfile.STATE_CONNECTED) { val state = flow .filter { it.getAction() == BluetoothA2dpSink.ACTION_CONNECTION_STATE_CHANGED } .filter { it.getBluetoothDeviceExtra() == device } .map { it.getIntExtra(BluetoothProfile.EXTRA_STATE, BluetoothAdapter.ERROR) } .filter { it == BluetoothProfile.STATE_CONNECTED || it == BluetoothProfile.STATE_DISCONNECTED } .first() if (state == BluetoothProfile.STATE_DISCONNECTED) { Log.e(TAG, "waitStream failed, A2DP has been disconnected") throw Status.UNKNOWN.asException() } } val sink = Sink.newBuilder().setConnection(request.connection).build() WaitSinkResponse.newBuilder().setSink(sink).build() } } override fun close(request: CloseRequest, responseObserver: StreamObserver<CloseResponse>) { grpcUnary<CloseResponse>(scope, responseObserver) { val device = if (request.hasSink()) { request.sink.connection.toBluetoothDevice(bluetoothAdapter) } else { Log.e(TAG, "Sink device required") throw Status.UNKNOWN.asException() } Log.i(TAG, "close: device=$device") if (bluetoothA2dpSink.getConnectionState(device) != BluetoothProfile.STATE_CONNECTED) { Log.e(TAG, "Device is not connected, cannot close") throw Status.UNKNOWN.asException() } val a2dpConnectionStateChangedFlow = flow .filter { it.getAction() == BluetoothA2dpSink.ACTION_CONNECTION_STATE_CHANGED } .filter { it.getBluetoothDeviceExtra() == device } .map { it.getIntExtra(BluetoothProfile.EXTRA_STATE, BluetoothAdapter.ERROR) } bluetoothA2dpSink.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) a2dpConnectionStateChangedFlow.filter { it == BluetoothProfile.STATE_DISCONNECTED }.first() CloseResponse.getDefaultInstance() } } } android/pandora/server/src/com/android/pandora/Server.kt +20 −6 Original line number Diff line number Diff line Loading @@ -16,6 +16,8 @@ package com.android.pandora import android.bluetooth.BluetoothManager import android.bluetooth.BluetoothProfile import android.content.Context import android.util.Log import io.grpc.Server as GrpcServer Loading @@ -28,7 +30,8 @@ class Server(context: Context) { private val GRPC_PORT = 8999 private var host: Host private var a2dp: A2dp private var a2dp: A2dp? = null private var a2dpSink: A2dpSink? = null private var avrcp: Avrcp private var gatt: Gatt private var hfp: Hfp Loading @@ -39,24 +42,34 @@ class Server(context: Context) { init { host = Host(context, this) a2dp = A2dp(context) avrcp = Avrcp(context) gatt = Gatt(context) hfp = Hfp(context) hid = Hid(context) l2cap = L2cap(context) security = Security(context) grpcServer = val grpcServerBuilder = NettyServerBuilder.forPort(GRPC_PORT) .addService(host) .addService(a2dp) .addService(avrcp) .addService(gatt) .addService(hfp) .addService(hid) .addService(l2cap) .addService(security) .build() val bluetoothAdapter = context.getSystemService(BluetoothManager::class.java)!!.adapter val is_a2dp_source = bluetoothAdapter.getSupportedProfiles().contains(BluetoothProfile.A2DP) if (is_a2dp_source) { a2dp = A2dp(context) grpcServerBuilder.addService(a2dp!!) } else { a2dpSink = A2dpSink(context) grpcServerBuilder.addService(a2dpSink!!) } grpcServer = grpcServerBuilder.build() Log.d(TAG, "Starting Pandora Server") grpcServer.start() Loading @@ -69,7 +82,8 @@ class Server(context: Context) { fun deinit() { host.deinit() a2dp.deinit() a2dp?.deinit() a2dpSink?.deinit() avrcp.deinit() gatt.deinit() hfp.deinit() Loading android/pandora/server/src/com/android/pandora/Utils.kt +1 −1 Original line number Diff line number Diff line Loading @@ -230,7 +230,7 @@ fun <T> getProfileProxy(context: Context, profile: Int): T { if (proxy == null) { Log.w(TAG, "profile proxy $profile is null") } return proxy as T return proxy!! as T } fun Intent.getBluetoothDeviceExtra(): BluetoothDevice = Loading Loading
android/pandora/mmi2grpc/mmi2grpc/a2dp.py +25 −27 Original line number Diff line number Diff line Loading @@ -11,7 +11,6 @@ # 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 Loading Loading @@ -49,15 +48,12 @@ class A2DPProxy(ProfileProxy): 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 ) 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): 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. Loading @@ -71,27 +67,34 @@ class A2DPProxy(ProfileProxy): """ if "SRC" in test: self.connection = self.host.WaitConnection( address=pts_addr).connection self.connection = self.host.WaitConnection(address=pts_addr).connection try: if "INT" in test: self.source = self.a2dp.OpenSource( connection=self.connection).source self.source = self.a2dp.OpenSource(connection=self.connection).source else: self.source = self.a2dp.WaitSource( connection=self.connection).source self.source = self.a2dp.WaitSource(connection=self.connection).source except RpcError: pass else: self.connection = self.host.WaitConnection( address=pts_addr).connection self.connection = self.host.WaitConnection(address=pts_addr).connection try: self.sink = self.a2dp.WaitSink( connection=self.connection).sink self.sink = self.a2dp.WaitSink(connection=self.connection).sink except RpcError: pass return "OK" @assert_description def TSC_AVDTP_mmi_iut_accept_disconnect(self, **kwargs): """ If necessary, take action to accept the AVDTP Signaling Channnel Disconnection initiated by the tester. Note: If an AVCTP signaling channel was established it will also be disconnected. """ return "OK" @assert_description def TSC_AVDTP_mmi_iut_initiate_discover(self, **kwargs): """ Loading Loading @@ -152,8 +155,7 @@ class A2DPProxy(ProfileProxy): return "OK" @assert_description def TSC_AVDTP_mmi_iut_initiate_out_of_range( self, pts_addr: bytes, **kwargs): 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. Loading @@ -162,8 +164,7 @@ class A2DPProxy(ProfileProxy): """ if self.connection is None: self.connection = self.host.GetConnection( address=pts_addr).connection self.connection = self.host.GetConnection(address=pts_addr).connection self.host.Disconnect(connection=self.connection) self.connection = None self.sink = None Loading @@ -181,11 +182,8 @@ class A2DPProxy(ProfileProxy): 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"): 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 Loading
android/pandora/server/configs/pts_bot_tests_config.json +2 −2 Original line number Diff line number Diff line Loading @@ -8,6 +8,7 @@ "A2DP/SRC/SET/BV-02-I", "A2DP/SRC/SET/BV-04-I", "A2DP/SNK/AS/BV-01-I", "A2DP/SNK/AS/BV-02-I", "A2DP/SNK/CC/BV-01-I", "A2DP/SNK/CC/BV-02-I", "A2DP/SNK/CC/BV-05-I", Loading @@ -15,6 +16,7 @@ "A2DP/SNK/CC/BV-07-I", "A2DP/SNK/CC/BV-08-I", "A2DP/SNK/REL/BV-01-I", "A2DP/SNK/REL/BV-02-I", "A2DP/SNK/SET/BV-01-I", "A2DP/SNK/SET/BV-02-I", "A2DP/SNK/SET/BV-03-I", Loading Loading @@ -277,8 +279,6 @@ "A2DP/SRC/SUS/BV-01-I", "A2DP/SRC/SUS/BV-02-I", "A2DP/SRC/SYN/BV-02-I", "A2DP/SNK/AS/BV-02-I", "A2DP/SNK/REL/BV-02-I", "A2DP/SNK/SDP/BV-02-I", "A2DP/SNK/SET/BV-04-I", "A2DP/SNK/SET/BV-05-I", Loading
android/pandora/server/src/com/android/pandora/A2dpSink.kt 0 → 100644 +128 −0 Original line number Diff line number Diff line /* * Copyright (C) 2022 The Android Open Source Project * * 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 * * http://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. */ package com.android.pandora import android.bluetooth.BluetoothA2dpSink import android.bluetooth.BluetoothAdapter import android.bluetooth.BluetoothDevice import android.bluetooth.BluetoothManager import android.bluetooth.BluetoothProfile import android.content.Context import android.content.Intent import android.content.IntentFilter import android.media.* import android.util.Log import io.grpc.Status import io.grpc.stub.StreamObserver import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.cancel import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.shareIn import pandora.A2DPGrpc.A2DPImplBase import pandora.A2dpProto.* @kotlinx.coroutines.ExperimentalCoroutinesApi class A2dpSink(val context: Context) : A2DPImplBase() { private val TAG = "PandoraA2dpSink" private val scope: CoroutineScope private val flow: Flow<Intent> private val bluetoothManager = context.getSystemService(BluetoothManager::class.java)!! private val bluetoothAdapter = bluetoothManager.adapter private val bluetoothA2dpSink = getProfileProxy<BluetoothA2dpSink>(context, BluetoothProfile.A2DP_SINK) init { scope = CoroutineScope(Dispatchers.Default) val intentFilter = IntentFilter() intentFilter.addAction(BluetoothA2dpSink.ACTION_CONNECTION_STATE_CHANGED) flow = intentFlow(context, intentFilter).shareIn(scope, SharingStarted.Eagerly) } fun deinit() { bluetoothAdapter.closeProfileProxy(BluetoothProfile.A2DP_SINK, bluetoothA2dpSink) scope.cancel() } override fun waitSink( request: WaitSinkRequest, responseObserver: StreamObserver<WaitSinkResponse> ) { grpcUnary<WaitSinkResponse>(scope, responseObserver) { val device = request.connection.toBluetoothDevice(bluetoothAdapter) Log.i(TAG, "waitSink: device=$device") if (device.getBondState() != BluetoothDevice.BOND_BONDED) { Log.e(TAG, "Device is not bonded, cannot wait for stream") throw Status.UNKNOWN.asException() } if (bluetoothA2dpSink.getConnectionState(device) != BluetoothProfile.STATE_CONNECTED) { val state = flow .filter { it.getAction() == BluetoothA2dpSink.ACTION_CONNECTION_STATE_CHANGED } .filter { it.getBluetoothDeviceExtra() == device } .map { it.getIntExtra(BluetoothProfile.EXTRA_STATE, BluetoothAdapter.ERROR) } .filter { it == BluetoothProfile.STATE_CONNECTED || it == BluetoothProfile.STATE_DISCONNECTED } .first() if (state == BluetoothProfile.STATE_DISCONNECTED) { Log.e(TAG, "waitStream failed, A2DP has been disconnected") throw Status.UNKNOWN.asException() } } val sink = Sink.newBuilder().setConnection(request.connection).build() WaitSinkResponse.newBuilder().setSink(sink).build() } } override fun close(request: CloseRequest, responseObserver: StreamObserver<CloseResponse>) { grpcUnary<CloseResponse>(scope, responseObserver) { val device = if (request.hasSink()) { request.sink.connection.toBluetoothDevice(bluetoothAdapter) } else { Log.e(TAG, "Sink device required") throw Status.UNKNOWN.asException() } Log.i(TAG, "close: device=$device") if (bluetoothA2dpSink.getConnectionState(device) != BluetoothProfile.STATE_CONNECTED) { Log.e(TAG, "Device is not connected, cannot close") throw Status.UNKNOWN.asException() } val a2dpConnectionStateChangedFlow = flow .filter { it.getAction() == BluetoothA2dpSink.ACTION_CONNECTION_STATE_CHANGED } .filter { it.getBluetoothDeviceExtra() == device } .map { it.getIntExtra(BluetoothProfile.EXTRA_STATE, BluetoothAdapter.ERROR) } bluetoothA2dpSink.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) a2dpConnectionStateChangedFlow.filter { it == BluetoothProfile.STATE_DISCONNECTED }.first() CloseResponse.getDefaultInstance() } } }
android/pandora/server/src/com/android/pandora/Server.kt +20 −6 Original line number Diff line number Diff line Loading @@ -16,6 +16,8 @@ package com.android.pandora import android.bluetooth.BluetoothManager import android.bluetooth.BluetoothProfile import android.content.Context import android.util.Log import io.grpc.Server as GrpcServer Loading @@ -28,7 +30,8 @@ class Server(context: Context) { private val GRPC_PORT = 8999 private var host: Host private var a2dp: A2dp private var a2dp: A2dp? = null private var a2dpSink: A2dpSink? = null private var avrcp: Avrcp private var gatt: Gatt private var hfp: Hfp Loading @@ -39,24 +42,34 @@ class Server(context: Context) { init { host = Host(context, this) a2dp = A2dp(context) avrcp = Avrcp(context) gatt = Gatt(context) hfp = Hfp(context) hid = Hid(context) l2cap = L2cap(context) security = Security(context) grpcServer = val grpcServerBuilder = NettyServerBuilder.forPort(GRPC_PORT) .addService(host) .addService(a2dp) .addService(avrcp) .addService(gatt) .addService(hfp) .addService(hid) .addService(l2cap) .addService(security) .build() val bluetoothAdapter = context.getSystemService(BluetoothManager::class.java)!!.adapter val is_a2dp_source = bluetoothAdapter.getSupportedProfiles().contains(BluetoothProfile.A2DP) if (is_a2dp_source) { a2dp = A2dp(context) grpcServerBuilder.addService(a2dp!!) } else { a2dpSink = A2dpSink(context) grpcServerBuilder.addService(a2dpSink!!) } grpcServer = grpcServerBuilder.build() Log.d(TAG, "Starting Pandora Server") grpcServer.start() Loading @@ -69,7 +82,8 @@ class Server(context: Context) { fun deinit() { host.deinit() a2dp.deinit() a2dp?.deinit() a2dpSink?.deinit() avrcp.deinit() gatt.deinit() hfp.deinit() Loading
android/pandora/server/src/com/android/pandora/Utils.kt +1 −1 Original line number Diff line number Diff line Loading @@ -230,7 +230,7 @@ fun <T> getProfileProxy(context: Context, profile: Int): T { if (proxy == null) { Log.w(TAG, "profile proxy $profile is null") } return proxy as T return proxy!! as T } fun Intent.getBluetoothDeviceExtra(): BluetoothDevice = Loading