Merge "Interop fix to not read PPCP for devices that report incompatible values." into main
diff --git a/OWNERS b/OWNERS
index f3eb92e..4df6c99 100644
--- a/OWNERS
+++ b/OWNERS
@@ -8,8 +8,8 @@
 
 # Per-file ownership
 
-# Build files / test_config / presubmit / preupload
-per-file PREUPLOAD.cfg,TEST_MAPPING,*.bp,*.xml=file:/OWNERS_build
+# Build files / test_config / presubmit / preupload / linter file
+per-file PREUPLOAD.cfg,TEST_MAPPING,*.bp,*.xml,pyrightconfig.json=file:/OWNERS_build
 
 # ChromeOS team owns Linux build files
 # - build.py is used for Linux build
diff --git a/android/pandora/mmi2grpc/mmi2grpc/l2cap.py b/android/pandora/mmi2grpc/mmi2grpc/l2cap.py
index 09ef10d..09136be 100644
--- a/android/pandora/mmi2grpc/mmi2grpc/l2cap.py
+++ b/android/pandora/mmi2grpc/mmi2grpc/l2cap.py
@@ -12,7 +12,6 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-import time
 import sys
 
 from mmi2grpc._helpers import assert_description
@@ -22,15 +21,16 @@
 
 from pandora.host_grpc import Host
 from pandora.host_pb2 import PUBLIC, RANDOM, Connection
+from pandora.l2cap_grpc import L2CAP
+from pandora.l2cap_pb2 import CreditBasedChannelRequest
 from pandora.security_pb2 import PairingEventAnswer
 from pandora.security_grpc import Security
-from pandora_experimental.l2cap_grpc import L2CAP
 
-from typing import Optional
+from typing import Optional, Dict
 
 
 class L2CAPProxy(ProfileProxy):
-    test_status_map = {}  # record test status and pass them between MMI
+    test_status_map: Dict[str, str] = {}  # record test status and pass them between MMI
     LE_DATA_PACKET_LARGE = "data: LE_DATA_PACKET_LARGE"
     LE_DATA_PACKET1 = "data: LE_PACKET1"
     connection: Optional[Connection] = None
@@ -44,6 +44,7 @@
 
         self.connection = None
         self.pairing_events = None
+        self.channel = None
 
     def test_started(self, test: str, **kwargs):
         self.rootcanal.select_pts_dongle(Dongle.CSR_RCK_PTS_DONGLE)
@@ -63,16 +64,12 @@
         tests_target_to_fail = [
             'L2CAP/LE/CFC/BV-01-C',
             'L2CAP/LE/CFC/BV-04-C',
-            'L2CAP/LE/CFC/BV-10-C',
-            'L2CAP/LE/CFC/BV-11-C',
-            'L2CAP/LE/CFC/BV-12-C',
             'L2CAP/LE/CFC/BV-14-C',
             'L2CAP/LE/CFC/BV-16-C',
             'L2CAP/LE/CFC/BV-18-C',
             'L2CAP/LE/CFC/BV-19-C',
             "L2CAP/LE/CFC/BV-21-C",
         ]
-        tests_require_secure_connection = []
 
         # This MMI is called twice in 'L2CAP/LE/CFC/BV-04-C'
         # We are not sure whether the lower tester’s BluetoothServerSocket
@@ -97,10 +94,13 @@
         if test == 'L2CAP/LE/CFC/BV-12-C':
             psm = 0xF3  # default TSPX_psm_authorization_required value
 
-        secure_connection = test in tests_require_secure_connection
-
         try:
-            self.l2cap.CreateLECreditBasedChannel(connection=self.connection, psm=psm, secure=secure_connection)
+            connect_response = self.l2cap.Connect(connection=self.connection,
+                                                  le_credit_based=CreditBasedChannelRequest(spsm=psm))
+            if connect_response.HasField('channel'):
+                self.channel = connect_response.channel
+            else:
+                raise Exception(connect_response.error)
         except Exception as e:
             if test in tests_target_to_fail:
                 self.test_status_map[test] = 'OK'
@@ -117,11 +117,13 @@
         """
         Place the IUT into LE connectable mode.
         """
+
         self.advertise = self.host.Advertise(
             legacy=True,
             connectable=True,
             own_address_type=PUBLIC,
         )
+
         # not strictly necessary, but can save time on waiting connection
         tests_to_open_bluetooth_server_socket = [
             "L2CAP/COS/CFC/BV-01-C",
@@ -129,31 +131,19 @@
             "L2CAP/COS/CFC/BV-03-C",
             "L2CAP/COS/CFC/BV-04-C",
             "L2CAP/LE/CFC/BV-03-C",
-            "L2CAP/LE/CFC/BV-05-C",
             "L2CAP/LE/CFC/BV-06-C",
             "L2CAP/LE/CFC/BV-09-C",
-            "L2CAP/LE/CFC/BV-13-C",
             "L2CAP/LE/CFC/BV-20-C",
             "L2CAP/LE/CFC/BI-01-C",
         ]
-        tests_require_secure_connection = [
-            "L2CAP/LE/CFC/BV-13-C",
-        ]
-        tests_connection_target_to_failed = [
-            "L2CAP/LE/CFC/BV-05-C",
-        ]
 
         if test in tests_to_open_bluetooth_server_socket:
-            secure_connection = test in tests_require_secure_connection
-            self.l2cap.ListenL2CAPChannel(connection=self.connection, secure=secure_connection)
-            try:
-                self.l2cap.AcceptL2CAPChannel(connection=self.connection)
-            except Exception as e:
-                if test in tests_connection_target_to_failed:
-                    self.test_status_map[test] = 'OK'
-                    print(test, 'connection targets to fail', file=sys.stderr)
-                else:
-                    raise e
+            wait_connection_response = self.l2cap.WaitConnection(le_credit_based=CreditBasedChannelRequest(spsm=0))
+            if wait_connection_response.HasField('channel'):
+                self.channel = wait_connection_response.channel
+            else:
+                raise Exception(wait_connection_response.error)
+
         return "OK"
 
     @assert_description
@@ -171,7 +161,8 @@
         # all data frames arrived
         # it seemed like when the time gap between the 1st frame and 2nd frame
         # larger than 100ms this problem will occur
-        self.l2cap.SendData(connection=self.connection, data=bytes(self.LE_DATA_PACKET_LARGE, "utf-8"))
+        assert self.channel
+        self.l2cap.Send(channel=self.channel, data=bytes(self.LE_DATA_PACKET_LARGE, "utf-8"))
         return "OK"
 
     @match_description
@@ -198,8 +189,9 @@
         Upper Tester command IUT to send at least 4 frames of LE data packets to
         the PTS.
         """
-        self.l2cap.SendData(
-            connection=self.connection,
+        assert self.channel
+        self.l2cap.Send(
+            channel=self.channel,
             data=b"this is a large data package with at least 4 frames: MMI_UPPER_TESTER_SEND_LE_DATA_PACKET_LARGE")
         return "OK"
 
@@ -208,8 +200,9 @@
         """
         IUT continue to send LE data packet(s) to the PTS.
         """
-        self.l2cap.SendData(
-            connection=self.connection,
+        assert self.channel
+        self.l2cap.Send(
+            channel=self.channel,
             data=b"this is a large data package with at least 4 frames: MMI_UPPER_TESTER_SEND_LE_DATA_PACKET_LARGE")
         return "OK"
 
@@ -232,7 +225,8 @@
         """
         Please confirm the Upper Tester receive data
         """
-        data = self.l2cap.ReceiveData(connection=self.connection)
+        assert self.channel
+        data = next(self.l2cap.Receive(channel=self.channel)).data
         assert data, "data received should not be empty"
         return "OK"
 
@@ -504,7 +498,8 @@
          Description : The Implementation Under Test(IUT)
         should send none segmantation LE frame of LE data to the PTS.
         """
-        self.l2cap.SendData(connection=self.connection, data=bytes(self.LE_DATA_PACKET1, "utf-8"))
+        assert self.channel
+        self.l2cap.Send(channel=self.channel, data=bytes(self.LE_DATA_PACKET1, "utf-8"))
         return "OK"
 
     @assert_description
diff --git a/android/pandora/mmi2grpc/pyrightconfig.json b/android/pandora/mmi2grpc/pyrightconfig.json
deleted file mode 100644
index fab9f61..0000000
--- a/android/pandora/mmi2grpc/pyrightconfig.json
+++ /dev/null
@@ -1,14 +0,0 @@
-{
-    "typeCheckingMode": "strict",
-    "useLibraryCodeForTypes": true,
-    "verboseOutput": false,
-    "reportMissingTypeStubs": false,
-    "reportUnknownLambdaType": false,
-    "reportImportCycles": false,
-    "reportPrivateUsage": false,
-    "extraPaths": [
-        "../../../pandora/server",
-        "../../../../../../out/soong/.intermediates/external/pandora/bt-test-interfaces/python/pandora-python-gen-src/gen/",
-        "../../../../../../out/soong/.intermediates/packages/modules/Bluetooth/pandora/interfaces/python/pandora_experimental-python-gen-src/gen/"
-    ]
-}
diff --git a/android/pandora/server/configs/pts_bot_tests_config.json b/android/pandora/server/configs/pts_bot_tests_config.json
index 9310292..3cfbe0d2 100644
--- a/android/pandora/server/configs/pts_bot_tests_config.json
+++ b/android/pandora/server/configs/pts_bot_tests_config.json
@@ -1048,6 +1048,7 @@
     "L2CAP/CMC/BV-13-C",
     "L2CAP/CMC/BV-14-C",
     "L2CAP/CMC/BV-15-C",
+    "L2CAP/COS/CED/BI-02-C",
     "L2CAP/COS/CED/BV-10-C",
     "L2CAP/COS/CED/BV-12-C",
     "L2CAP/COS/CED/BV-13-C",
diff --git a/android/pandora/server/src/L2cap.kt b/android/pandora/server/src/L2cap.kt
index 77b9603..1d1e9c8 100644
--- a/android/pandora/server/src/L2cap.kt
+++ b/android/pandora/server/src/L2cap.kt
@@ -16,46 +16,35 @@
 
 package com.android.pandora
 
+import android.bluetooth.BluetoothDevice
 import android.bluetooth.BluetoothManager
-import android.bluetooth.BluetoothServerSocket
+import android.bluetooth.BluetoothSocket
 import android.content.Context
 import android.util.Log
+import com.google.protobuf.Any
 import com.google.protobuf.ByteString
 import io.grpc.stub.StreamObserver
 import java.io.Closeable
-import java.io.IOException
-import java.io.InputStream
-import java.io.OutputStream
+import java.util.concurrent.atomic.AtomicLong
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.cancel
-import kotlinx.coroutines.withContext
-import pandora.HostProto.Connection
-import pandora.L2CAPGrpc.L2CAPImplBase
-import pandora.L2capProto.AcceptL2CAPChannelRequest
-import pandora.L2capProto.AcceptL2CAPChannelResponse
-import pandora.L2capProto.CreateLECreditBasedChannelRequest
-import pandora.L2capProto.CreateLECreditBasedChannelResponse
-import pandora.L2capProto.ListenL2CAPChannelRequest
-import pandora.L2capProto.ListenL2CAPChannelResponse
-import pandora.L2capProto.ReceiveDataRequest
-import pandora.L2capProto.ReceiveDataResponse
-import pandora.L2capProto.SendDataRequest
-import pandora.L2capProto.SendDataResponse
+import kotlinx.coroutines.flow.flow
+import pandora.l2cap.L2CAPGrpc.L2CAPImplBase
+import pandora.l2cap.L2CAPProto.*
 
 @kotlinx.coroutines.ExperimentalCoroutinesApi
 class L2cap(val context: Context) : L2CAPImplBase(), Closeable {
     private val TAG = "PandoraL2cap"
     private val scope: CoroutineScope
     private val BLUETOOTH_SERVER_SOCKET_TIMEOUT: Int = 10000
+    private val channelIdCounter = AtomicLong(1)
     private val BUFFER_SIZE = 512
 
     private val bluetoothManager =
         context.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager
     private val bluetoothAdapter = bluetoothManager.adapter
-    private var connectionInStreamMap: HashMap<Connection, InputStream> = hashMapOf()
-    private var connectionOutStreamMap: HashMap<Connection, OutputStream> = hashMapOf()
-    private var connectionServerSocketMap: HashMap<Connection, BluetoothServerSocket> = hashMapOf()
+    private val channels: HashMap<Long, BluetoothSocket> = hashMapOf()
 
     init {
         // Init the CoroutineScope
@@ -67,127 +56,117 @@
         scope.cancel()
     }
 
-    suspend fun receive(inStream: InputStream): ByteArray {
-        return withContext(Dispatchers.IO) {
-            val buf = ByteArray(BUFFER_SIZE)
-            inStream.read(buf, 0, BUFFER_SIZE) // blocking
-            Log.i(TAG, "receive: $buf")
-            buf
-        }
-    }
-
-    /** Open a BluetoothServerSocket to accept connections */
-    override fun listenL2CAPChannel(
-        request: ListenL2CAPChannelRequest,
-        responseObserver: StreamObserver<ListenL2CAPChannelResponse>,
+    override fun connect(
+        request: ConnectRequest,
+        responseObserver: StreamObserver<ConnectResponse>,
     ) {
         grpcUnary(scope, responseObserver) {
-            Log.i(TAG, "listenL2CAPChannel: secure=${request.secure}")
-            val connection = request.connection
-            val bluetoothServerSocket =
-                if (request.secure) {
-                    bluetoothAdapter.listenUsingL2capChannel()
-                } else {
-                    bluetoothAdapter.listenUsingInsecureL2capChannel()
-                }
-            connectionServerSocketMap[connection] = bluetoothServerSocket
-            ListenL2CAPChannelResponse.newBuilder().build()
-        }
-    }
-
-    override fun acceptL2CAPChannel(
-        request: AcceptL2CAPChannelRequest,
-        responseObserver: StreamObserver<AcceptL2CAPChannelResponse>,
-    ) {
-        grpcUnary(scope, responseObserver) {
-            Log.i(TAG, "acceptL2CAPChannel")
-
-            val connection = request.connection
-            val bluetoothServerSocket = connectionServerSocketMap[connection]
-            try {
-                val bluetoothSocket =
-                    bluetoothServerSocket!!.accept(BLUETOOTH_SERVER_SOCKET_TIMEOUT)
-                connectionInStreamMap[connection] = bluetoothSocket.getInputStream()!!
-                connectionOutStreamMap[connection] = bluetoothSocket.getOutputStream()!!
-            } catch (e: IOException) {
-                Log.e(TAG, "bluetoothServerSocket not accepted", e)
-                throw e
-            }
-
-            AcceptL2CAPChannelResponse.newBuilder().build()
-        }
-    }
-
-    /** Set device to send LE based connection request */
-    override fun createLECreditBasedChannel(
-        request: CreateLECreditBasedChannelRequest,
-        responseObserver: StreamObserver<CreateLECreditBasedChannelResponse>,
-    ) {
-        // Creates a gRPC coroutine in a given coroutine scope which executes a given suspended
-        // function
-        // returning a gRPC response and sends it on a given gRPC stream observer.
-        grpcUnary(scope, responseObserver) {
-            Log.i(TAG, "createLECreditBasedChannel: secure=${request.secure}, psm=${request.psm}")
-            val connection = request.connection
             val device = request.connection.toBluetoothDevice(bluetoothAdapter)
-            val psm = request.psm
 
-            try {
-                val bluetoothSocket =
-                    if (request.secure) {
-                        device.createL2capChannel(psm)
-                    } else {
-                        device.createInsecureL2capChannel(psm)
-                    }
-                bluetoothSocket.connect()
-                connectionInStreamMap[connection] = bluetoothSocket.getInputStream()!!
-                connectionOutStreamMap[connection] = bluetoothSocket.getOutputStream()!!
-            } catch (e: IOException) {
-                Log.d(TAG, "bluetoothSocket not connected: $e")
-                throw e
-            }
-
-            // Response sent to client
-            CreateLECreditBasedChannelResponse.newBuilder().build()
-        }
-    }
-
-    /** send data packet */
-    override fun sendData(
-        request: SendDataRequest,
-        responseObserver: StreamObserver<SendDataResponse>,
-    ) {
-        grpcUnary(scope, responseObserver) {
-            Log.i(TAG, "sendDataPacket: data=${request.data}")
-            val buffer = request.data!!.toByteArray()
-            val connection = request.connection
-            val outputStream = connectionOutStreamMap[connection]!!
-
-            withContext(Dispatchers.IO) {
-                try {
-                    outputStream.write(buffer)
-                    outputStream.flush()
-                } catch (e: IOException) {
-                    Log.e(TAG, "Exception during writing to sendDataPacket output stream", e)
+            val psm =
+                when {
+                    request.hasBasic() -> request.basic.psm
+                    request.hasLeCreditBased() -> request.leCreditBased.spsm
+                    request.hasEnhancedCreditBased() -> request.enhancedCreditBased.spsm
+                    else -> throw RuntimeException("unreachable")
                 }
-            }
+            Log.i(TAG, "connect: $device psm: $psm")
 
-            // Response sent to client
-            SendDataResponse.newBuilder().build()
+            val bluetoothSocket = device.createInsecureL2capChannel(psm)
+            bluetoothSocket.connect()
+            val channelId = getNewChannelId()
+            channels.put(channelId, bluetoothSocket)
+
+            Log.d(TAG, "connect: channelId=$channelId")
+            ConnectResponse.newBuilder().setChannel(craftChannel(channelId)).build()
         }
     }
 
-    override fun receiveData(
-        request: ReceiveDataRequest,
-        responseObserver: StreamObserver<ReceiveDataResponse>,
+    override fun waitConnection(
+        request: WaitConnectionRequest,
+        responseObserver: StreamObserver<WaitConnectionResponse>,
     ) {
         grpcUnary(scope, responseObserver) {
-            Log.i(TAG, "receiveData")
-            val connection = request.connection
-            val inputStream = connectionInStreamMap[connection]!!
-            val buf = receive(inputStream)
+            val device: BluetoothDevice? =
+                try {
+                    request.connection.toBluetoothDevice(bluetoothAdapter)
+                } catch (e: Exception) {
+                    Log.w(TAG, e)
+                    null
+                }
 
-            ReceiveDataResponse.newBuilder().setData(ByteString.copyFrom(buf)).build()
+            Log.i(TAG, "waitConnection: $device")
+
+            val psm =
+                when {
+                    request.hasBasic() -> request.basic.psm
+                    request.hasLeCreditBased() -> request.leCreditBased.spsm
+                    request.hasEnhancedCreditBased() -> request.enhancedCreditBased.spsm
+                    else -> throw RuntimeException("unreachable")
+                }
+
+            var bluetoothSocket: BluetoothSocket?
+
+            while (true) {
+                val bluetoothServerSocket =
+                    if (psm == 0) {
+                        bluetoothAdapter.listenUsingInsecureL2capChannel()
+                    } else {
+                        bluetoothAdapter.listenUsingInsecureL2capOn(psm)
+                    }
+                bluetoothSocket = bluetoothServerSocket.accept()
+                bluetoothServerSocket.close()
+                if (device != null && !bluetoothSocket.getRemoteDevice().equals(device)) continue
+                break
+            }
+
+            val channelId = getNewChannelId()
+            channels.put(channelId, bluetoothSocket!!)
+
+            Log.d(TAG, "waitConnection: channelId=$channelId")
+            WaitConnectionResponse.newBuilder().setChannel(craftChannel(channelId)).build()
         }
     }
+
+    override fun send(request: SendRequest, responseObserver: StreamObserver<SendResponse>) {
+        grpcUnary(scope, responseObserver) {
+            Log.i(TAG, "send")
+            val bluetoothSocket = request.channel.toBluetoothSocket(channels)
+            val outputStream = bluetoothSocket.outputStream
+
+            outputStream.write(request.data.toByteArray())
+            outputStream.flush()
+
+            SendResponse.newBuilder().build()
+        }
+    }
+
+    override fun receive(
+        request: ReceiveRequest,
+        responseObserver: StreamObserver<ReceiveResponse>,
+    ) {
+        Log.i(TAG, "receive")
+        val bluetoothSocket = request.channel.toBluetoothSocket(channels)
+        val inputStream = bluetoothSocket.inputStream
+        grpcServerStream(scope, responseObserver) {
+            flow {
+                val buffer = ByteArray(BUFFER_SIZE)
+                inputStream.read(buffer, 0, BUFFER_SIZE)
+                val data = ByteString.copyFrom(buffer)
+                val response = ReceiveResponse.newBuilder().setData(data).build()
+                emit(response)
+            }
+        }
+    }
+
+    fun getNewChannelId(): Long = channelIdCounter.getAndIncrement()
+
+    fun craftChannel(id: Long): Channel {
+        val cookie = Any.newBuilder().setValue(ByteString.copyFromUtf8(id.toString())).build()
+        val channel = Channel.newBuilder().setCookie(cookie).build()
+        return channel
+    }
+
+    fun Channel.toBluetoothSocket(channels: HashMap<Long, BluetoothSocket>): BluetoothSocket =
+        channels.get(this.cookie.value.toStringUtf8().toLong())!!
 }
diff --git a/android/pandora/test/pyrightconfig.json b/android/pandora/test/pyrightconfig.json
deleted file mode 100644
index ea458b5c6..0000000
--- a/android/pandora/test/pyrightconfig.json
+++ /dev/null
@@ -1,19 +0,0 @@
-{
-    "typeCheckingMode": "strict",
-    "useLibraryCodeForTypes": true,
-    "verboseOutput": false,
-    "reportMissingTypeStubs": false,
-    "reportUnknownLambdaType": false,
-    "reportImportCycles": false,
-    "reportPrivateUsage": false,
-    "extraPaths": [
-        "../../../pandora/server",
-        "../../../../../../external/pandora/avatar",
-        "../../../../../../external/python/bumble",
-        "../../../../../../external/python/mobly",
-        "../../../../../../external/python/pyee",
-        "../../../../../../external/python/portpicker/src",
-        "../../../../../../out/soong/.intermediates/external/pandora/bt-test-interfaces/python/pandora-python-gen-src/gen/",
-        "../../../../../../out/soong/.intermediates/packages/modules/Bluetooth/pandora/interfaces/python/pandora_experimental-python-gen-src/gen/"
-    ]
-}
\ No newline at end of file
diff --git a/pandora/interfaces/pandora_experimental/l2cap.proto b/pandora/interfaces/pandora_experimental/l2cap.proto
deleted file mode 100644
index 443010a..0000000
--- a/pandora/interfaces/pandora_experimental/l2cap.proto
+++ /dev/null
@@ -1,62 +0,0 @@
-syntax = "proto3";
-
-package pandora;
-
-option java_outer_classname = "L2capProto";
-
-import "google/protobuf/empty.proto";
-import "pandora/host.proto";
-
-service L2CAP {
-  // Create a L2CAP connection to a peer.
-  rpc CreateLECreditBasedChannel(CreateLECreditBasedChannelRequest) returns (CreateLECreditBasedChannelResponse);
-  // Send some data
-  rpc SendData(SendDataRequest) returns (SendDataResponse);
-  // Receive data
-  rpc ReceiveData(ReceiveDataRequest) returns (ReceiveDataResponse);
-  // Listen L2CAP channel for connection
-  rpc ListenL2CAPChannel(ListenL2CAPChannelRequest) returns (ListenL2CAPChannelResponse);
-  // Accept L2CAP connection
-  rpc AcceptL2CAPChannel(AcceptL2CAPChannelRequest) returns (AcceptL2CAPChannelResponse);
-}
-
-// Request for the `OpenSource` method.
-message CreateLECreditBasedChannelRequest {
-  // The connection that will open the stream.
-  Connection connection = 1;
-  int32 psm = 2;
-  bool secure = 3;
-}
-
-// Request for the `OpenSource` method.
-message CreateLECreditBasedChannelResponse {}
-
-message SendDataRequest {
-  // The connection that will open the stream.
-  Connection connection = 1;
-  bytes data = 2;
-}
-
-message SendDataResponse {}
-
-message ReceiveDataRequest {
-  // The connection that will open the stream.
-  Connection connection = 1;
-}
-
-message ReceiveDataResponse {
-  bytes data = 1;
-}
-
-message ListenL2CAPChannelRequest{
-  Connection connection = 1;
-  bool secure = 2;
-}
-
-message ListenL2CAPChannelResponse {}
-
-message AcceptL2CAPChannelRequest{
-  Connection connection = 1;
-}
-
-message AcceptL2CAPChannelResponse {}
\ No newline at end of file
diff --git a/pandora/interfaces/python/Android.bp b/pandora/interfaces/python/Android.bp
index 4532716..2abac30 100644
--- a/pandora/interfaces/python/Android.bp
+++ b/pandora/interfaces/python/Android.bp
@@ -54,10 +54,6 @@
         "pandora_experimental/hid_grpc_aio.py",
         "pandora_experimental/hid_pb2.py",
         "pandora_experimental/hid_pb2.pyi",
-        "pandora_experimental/l2cap_grpc.py",
-        "pandora_experimental/l2cap_grpc_aio.py",
-        "pandora_experimental/l2cap_pb2.py",
-        "pandora_experimental/l2cap_pb2.pyi",
         "pandora_experimental/le_audio_grpc.py",
         "pandora_experimental/le_audio_grpc_aio.py",
         "pandora_experimental/le_audio_pb2.py",
@@ -118,7 +114,6 @@
         ":pandora_experimental-python-gen-src{pandora_experimental/hap_pb2.pyi}",
         ":pandora_experimental-python-gen-src{pandora_experimental/hfp_pb2.pyi}",
         ":pandora_experimental-python-gen-src{pandora_experimental/hid_pb2.pyi}",
-        ":pandora_experimental-python-gen-src{pandora_experimental/l2cap_pb2.pyi}",
         ":pandora_experimental-python-gen-src{pandora_experimental/le_audio_pb2.pyi}",
         ":pandora_experimental-python-gen-src{pandora_experimental/map_pb2.pyi}",
         ":pandora_experimental-python-gen-src{pandora_experimental/mediaplayer_pb2.pyi}",
diff --git a/pyrightconfig.json b/pyrightconfig.json
new file mode 100644
index 0000000..e2b62af
--- /dev/null
+++ b/pyrightconfig.json
@@ -0,0 +1,34 @@
+{
+    "typeCheckingMode": "strict",
+    "useLibraryCodeForTypes": true,
+    "verboseOutput": false,
+    "reportMissingTypeStubs": false,
+    "reportUnknownLambdaType": false,
+    "reportImportCycles": false,
+    "reportPrivateUsage": false,
+    "executionEnvironments": [
+        {
+            "root": "android/pandora/test",
+            "extraPaths": ["pandora/server"]
+        },
+        {
+            "root": "android/pandora/mmi2grpc"
+        },
+        {
+            "root": "pandora/server/bumble_experimental"
+        },
+        {
+            "root": "framework/tests/bumble/src/bumble_server.py",
+            "extraPaths": ["pandora/server"]
+        }
+    ],
+    "extraPaths": [
+        "../../../external/pandora/avatar",
+        "../../../external/python/bumble",
+        "../../../external/python/mobly",
+        "../../../external/python/pyee",
+        "../../../external/python/portpicker/src",
+        "../../../out/soong/.intermediates/external/pandora/bt-test-interfaces/python/pandora-python-gen-src/gen/",
+        "../../../out/soong/.intermediates/packages/modules/Bluetooth/pandora/interfaces/python/pandora_experimental-python-gen-src/gen/"
+    ]
+}
diff --git a/system/bta/gatt/bta_gattc_act.cc b/system/bta/gatt/bta_gattc_act.cc
index 636951c..95eb0a7 100644
--- a/system/bta/gatt/bta_gattc_act.cc
+++ b/system/bta/gatt/bta_gattc_act.cc
@@ -297,7 +297,7 @@
   /* close all CLCB related to this app */
   if (com::android::bluetooth::flags::gatt_client_dynamic_allocation()) {
     for (auto& p_clcb : bta_gattc_cb.clcb_set) {
-      if (p_clcb->p_rcb != p_clreg) {
+      if (!p_clcb->in_use || p_clcb->p_rcb != p_clreg) {
         continue;
       }
       p_clreg->dereg_pending = true;
@@ -1504,7 +1504,8 @@
       tBTA_GATTC_CLCB* p_clcb = &bta_gattc_cb.clcb[0];
       if (com::android::bluetooth::flags::gatt_client_dynamic_allocation()) {
         for (auto& p_clcb_i : bta_gattc_cb.clcb_set) {
-          if (p_clcb_i->p_srcb == p_srvc_cb) {
+          if (p_clcb_i->in_use && p_clcb_i->p_srcb == p_srvc_cb) {
+            p_clcb = p_clcb_i.get();
             found = true;
             break;
           }
@@ -1576,7 +1577,7 @@
     if (p_clcb == NULL || (p_clcb && p_clcb->p_q_cmd != NULL)) {
       if (com::android::bluetooth::flags::gatt_client_dynamic_allocation()) {
         for (auto& p_clcb_i : bta_gattc_cb.clcb_set) {
-          if (p_clcb_i->p_srcb == p_srcb && p_clcb_i->p_q_cmd == NULL) {
+          if (p_clcb_i->in_use && p_clcb_i->p_srcb == p_srcb && p_clcb_i->p_q_cmd == NULL) {
             p_clcb = p_clcb_i.get();
             break;
           }
diff --git a/system/bta/gatt/bta_gattc_utils.cc b/system/bta/gatt/bta_gattc_utils.cc
index 36e05ba..ca911de 100644
--- a/system/bta/gatt/bta_gattc_utils.cc
+++ b/system/bta/gatt/bta_gattc_utils.cc
@@ -145,7 +145,7 @@
 tBTA_GATTC_CLCB* bta_gattc_find_clcb_by_conn_id(tCONN_ID conn_id) {
   if (com::android::bluetooth::flags::gatt_client_dynamic_allocation()) {
     for (auto& p_clcb : bta_gattc_cb.clcb_set) {
-      if (p_clcb->bta_conn_id == conn_id) {
+      if (p_clcb->in_use && p_clcb->bta_conn_id == conn_id) {
         return p_clcb.get();
       }
     }
@@ -960,6 +960,9 @@
   if (com::android::bluetooth::flags::gatt_client_dynamic_allocation()) {
     stream << " ->clcb (dynamic)\n";
     for (auto& p_clcb : bta_gattc_cb.clcb_set) {
+      if (!p_clcb->in_use) {
+        continue;
+      }
       entry_count++;
       stream << "  conn_id: " << loghex(p_clcb->bta_conn_id)
              << "  address: " << ADDRESS_TO_LOGGABLE_STR(p_clcb->bda)
diff --git a/system/btif/include/btif_sock_l2cap.h b/system/btif/include/btif_sock_l2cap.h
index d527ed8..c08ef53 100644
--- a/system/btif/include/btif_sock_l2cap.h
+++ b/system/btif/include/btif_sock_l2cap.h
@@ -1,3 +1,19 @@
+/*
+ * Copyright 2024 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.
+ */
+
 /*******************************************************************************
  *  L2CAP Socket Interface
  ******************************************************************************/
diff --git a/system/btif/src/btif_sock.cc b/system/btif/src/btif_sock.cc
index 95f2789..04b43dd 100644
--- a/system/btif/src/btif_sock.cc
+++ b/system/btif/src/btif_sock.cc
@@ -114,7 +114,7 @@
 
   return BT_STATUS_SUCCESS;
 
-error:;
+error:
   thread_free(thread);
   thread = NULL;
   if (thread_handle != -1) {