1 /* 2 * Copyright (C) 2024 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.pandora 18 19 import android.bluetooth.BluetoothDevice 20 import android.bluetooth.BluetoothManager 21 import android.bluetooth.BluetoothSocket 22 import android.content.Context 23 import android.util.Log 24 import com.google.protobuf.Any 25 import com.google.protobuf.ByteString 26 import io.grpc.stub.StreamObserver 27 import java.io.Closeable 28 import java.io.IOException 29 import java.util.concurrent.atomic.AtomicLong 30 import kotlinx.coroutines.CoroutineScope 31 import kotlinx.coroutines.Dispatchers 32 import kotlinx.coroutines.cancel 33 import kotlinx.coroutines.flow.flow 34 import pandora.l2cap.L2CAPGrpc.L2CAPImplBase 35 import pandora.l2cap.L2CAPProto.* 36 37 @kotlinx.coroutines.ExperimentalCoroutinesApi 38 class L2cap(val context: Context) : L2CAPImplBase(), Closeable { 39 private val TAG = "PandoraL2cap" 40 private val scope: CoroutineScope 41 private val BLUETOOTH_SERVER_SOCKET_TIMEOUT: Int = 10000 42 private val channelIdCounter = AtomicLong(1) 43 private val BUFFER_SIZE = 512 44 45 private val bluetoothManager = 46 context.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager 47 private val bluetoothAdapter = bluetoothManager.adapter 48 private val channels: HashMap<Long, BluetoothSocket> = hashMapOf() 49 50 init { 51 // Init the CoroutineScope 52 scope = CoroutineScope(Dispatchers.Default.limitedParallelism(1)) 53 } 54 closenull55 override fun close() { 56 // Deinit the CoroutineScope 57 scope.cancel() 58 } 59 connectnull60 override fun connect( 61 request: ConnectRequest, 62 responseObserver: StreamObserver<ConnectResponse>, 63 ) { 64 grpcUnary(scope, responseObserver) { 65 val device = request.connection.toBluetoothDevice(bluetoothAdapter) 66 67 val psm = 68 when { 69 request.hasBasic() -> request.basic.psm 70 request.hasLeCreditBased() -> request.leCreditBased.spsm 71 request.hasEnhancedCreditBased() -> request.enhancedCreditBased.spsm 72 else -> throw RuntimeException("unreachable") 73 } 74 Log.i(TAG, "connect: $device psm: $psm") 75 76 val bluetoothSocket = device.createInsecureL2capChannel(psm) 77 bluetoothSocket.connect() 78 val channelId = getNewChannelId() 79 channels.put(channelId, bluetoothSocket) 80 81 Log.d(TAG, "connect: channelId=$channelId") 82 ConnectResponse.newBuilder().setChannel(craftChannel(channelId)).build() 83 } 84 } 85 waitConnectionnull86 override fun waitConnection( 87 request: WaitConnectionRequest, 88 responseObserver: StreamObserver<WaitConnectionResponse>, 89 ) { 90 grpcUnary(scope, responseObserver) { 91 val device: BluetoothDevice? = 92 try { 93 request.connection.toBluetoothDevice(bluetoothAdapter) 94 } catch (e: Exception) { 95 Log.w(TAG, e) 96 null 97 } 98 99 Log.i(TAG, "waitConnection: $device") 100 101 val psm = 102 when { 103 request.hasBasic() -> request.basic.psm 104 request.hasLeCreditBased() -> request.leCreditBased.spsm 105 request.hasEnhancedCreditBased() -> request.enhancedCreditBased.spsm 106 else -> throw RuntimeException("unreachable") 107 } 108 109 var bluetoothSocket: BluetoothSocket? 110 111 while (true) { 112 val bluetoothServerSocket = 113 if (psm == 0) { 114 bluetoothAdapter.listenUsingInsecureL2capChannel() 115 } else { 116 bluetoothAdapter.listenUsingInsecureL2capOn(psm) 117 } 118 bluetoothSocket = bluetoothServerSocket.accept() 119 bluetoothServerSocket.close() 120 if (device != null && !bluetoothSocket.getRemoteDevice().equals(device)) continue 121 break 122 } 123 124 val channelId = getNewChannelId() 125 channels.put(channelId, bluetoothSocket!!) 126 127 Log.d(TAG, "waitConnection: channelId=$channelId") 128 WaitConnectionResponse.newBuilder().setChannel(craftChannel(channelId)).build() 129 } 130 } 131 disconnectnull132 override fun disconnect( 133 request: DisconnectRequest, 134 responseObserver: StreamObserver<DisconnectResponse>, 135 ) { 136 grpcUnary(scope, responseObserver) { 137 val channel = request.channel 138 val bluetoothSocket = channel.toBluetoothSocket(channels) 139 Log.i(TAG, "disconnect: ${channel.id()} ") 140 141 try { 142 bluetoothSocket.close() 143 DisconnectResponse.getDefaultInstance() 144 } catch (e: IOException) { 145 Log.e(TAG, "disconnect: exception while closing the socket: $e") 146 147 DisconnectResponse.newBuilder() 148 .setErrorValue(CommandRejectReason.COMMAND_NOT_UNDERSTOOD_VALUE) 149 .build() 150 } finally { 151 channels.remove(channel.id()) 152 } 153 } 154 } 155 waitDisconnectionnull156 override fun waitDisconnection( 157 request: WaitDisconnectionRequest, 158 responseObserver: StreamObserver<WaitDisconnectionResponse>, 159 ) { 160 grpcUnary(scope, responseObserver) { 161 Log.i(TAG, "waitDisconnection: ${request.channel.id()}") 162 val bluetoothSocket = request.channel.toBluetoothSocket(channels) 163 164 while (bluetoothSocket.isConnected()) Thread.sleep(100) 165 166 WaitDisconnectionResponse.getDefaultInstance() 167 } 168 } 169 sendnull170 override fun send(request: SendRequest, responseObserver: StreamObserver<SendResponse>) { 171 grpcUnary(scope, responseObserver) { 172 Log.i(TAG, "send") 173 val bluetoothSocket = request.channel.toBluetoothSocket(channels) 174 val outputStream = bluetoothSocket.outputStream 175 176 outputStream.write(request.data.toByteArray()) 177 outputStream.flush() 178 179 SendResponse.newBuilder().build() 180 } 181 } 182 receivenull183 override fun receive( 184 request: ReceiveRequest, 185 responseObserver: StreamObserver<ReceiveResponse>, 186 ) { 187 Log.i(TAG, "receive") 188 val bluetoothSocket = request.channel.toBluetoothSocket(channels) 189 val inputStream = bluetoothSocket.inputStream 190 grpcServerStream(scope, responseObserver) { 191 flow { 192 val buffer = ByteArray(BUFFER_SIZE) 193 inputStream.read(buffer, 0, BUFFER_SIZE) 194 val data = ByteString.copyFrom(buffer) 195 val response = ReceiveResponse.newBuilder().setData(data).build() 196 emit(response) 197 } 198 } 199 } 200 getNewChannelIdnull201 fun getNewChannelId(): Long = channelIdCounter.getAndIncrement() 202 203 fun craftChannel(id: Long): Channel { 204 val cookie = Any.newBuilder().setValue(ByteString.copyFromUtf8(id.toString())).build() 205 val channel = Channel.newBuilder().setCookie(cookie).build() 206 return channel 207 } 208 Channelnull209 fun Channel.id(): Long = this.cookie.value.toStringUtf8().toLong() 210 211 fun Channel.toBluetoothSocket(channels: HashMap<Long, BluetoothSocket>): BluetoothSocket = 212 channels.get(this.id())!! 213 } 214