1 /* 2 * Copyright (C) 2022 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.BluetoothManager 20 import android.bluetooth.BluetoothServerSocket 21 import android.bluetooth.BluetoothSocket 22 import android.content.Context 23 import android.util.Log 24 import com.google.protobuf.ByteString 25 import io.grpc.Status 26 import io.grpc.stub.StreamObserver 27 import java.io.Closeable 28 import java.io.IOException 29 import java.io.InputStream 30 import java.io.OutputStream 31 import java.util.UUID 32 import kotlinx.coroutines.CoroutineScope 33 import kotlinx.coroutines.Dispatchers 34 import kotlinx.coroutines.cancel 35 import kotlinx.coroutines.withContext 36 import pandora.RFCOMMGrpc.RFCOMMImplBase 37 import pandora.RfcommProto.* 38 39 @kotlinx.coroutines.ExperimentalCoroutinesApi 40 class Rfcomm(val context: Context) : RFCOMMImplBase(), Closeable { 41 42 private val _bufferSize = 512 43 44 private val TAG = "PandoraRfcomm" 45 46 private val scope: CoroutineScope = CoroutineScope(Dispatchers.Default.limitedParallelism(1)) 47 48 private val bluetoothManager = context.getSystemService(BluetoothManager::class.java)!! 49 private val bluetoothAdapter = bluetoothManager.adapter 50 51 private var currentCookie = 0x12FC0 // Non-zero cookie RFCo(mm) 52 53 data class Connection( 54 val connection: BluetoothSocket, 55 val inputStream: InputStream, 56 val outputStream: OutputStream 57 ) 58 59 private var serverMap: HashMap<Int, BluetoothServerSocket> = hashMapOf() 60 private var connectionMap: HashMap<Int, Connection> = hashMapOf() 61 closenull62 override fun close() { 63 scope.cancel() 64 } 65 connectToServernull66 override fun connectToServer( 67 request: ConnectionRequest, 68 responseObserver: StreamObserver<ConnectionResponse>, 69 ) { 70 grpcUnary(scope, responseObserver) { 71 Log.i(TAG, "RFCOMM: connect: request=${request.address}") 72 val device = request.address.toBluetoothDevice(bluetoothAdapter) 73 val clientSocket = 74 device.createInsecureRfcommSocketToServiceRecord(UUID.fromString(request.uuid)) 75 try { 76 clientSocket.connect() 77 } catch (e: IOException) { 78 Log.e(TAG, "connect threw ${e}.") 79 throw Status.UNKNOWN.asException() 80 } 81 Log.i(TAG, "connected.") 82 val connectedClientSocket = currentCookie++ 83 // Get the BluetoothSocket input and output streams 84 try { 85 val tmpIn = clientSocket.inputStream!! 86 val tmpOut = clientSocket.outputStream!! 87 connectionMap[connectedClientSocket] = Connection(clientSocket, tmpIn, tmpOut) 88 } catch (e: IOException) { 89 Log.e(TAG, "temp sockets not created", e) 90 } 91 92 ConnectionResponse.newBuilder() 93 .setConnection(RfcommConnection.newBuilder().setId(connectedClientSocket).build()) 94 .build() 95 } 96 } 97 disconnectnull98 override fun disconnect( 99 request: DisconnectionRequest, 100 responseObserver: StreamObserver<DisconnectionResponse>, 101 ) { 102 grpcUnary(scope, responseObserver) { 103 val id = request.connection.id 104 Log.i(TAG, "RFCOMM: disconnect: request=${id}") 105 if (connectionMap.containsKey(id)) { 106 connectionMap[id]!!.connection.close() 107 connectionMap.remove(id) 108 } else { 109 throw Status.UNKNOWN.asException() 110 } 111 DisconnectionResponse.newBuilder().build() 112 } 113 } 114 startServernull115 override fun startServer( 116 request: StartServerRequest, 117 responseObserver: StreamObserver<StartServerResponse>, 118 ) { 119 grpcUnary(scope, responseObserver) { 120 Log.i(TAG, "startServer") 121 val serverSocket = 122 bluetoothAdapter.listenUsingInsecureRfcommWithServiceRecord( 123 request.name, 124 UUID.fromString(request.uuid) 125 ) 126 val serverSocketCookie = currentCookie++ 127 serverMap[serverSocketCookie] = serverSocket 128 129 StartServerResponse.newBuilder() 130 .setServer(ServerId.newBuilder().setId(serverSocketCookie).build()) 131 .build() 132 } 133 } 134 acceptConnectionnull135 override fun acceptConnection( 136 request: AcceptConnectionRequest, 137 responseObserver: StreamObserver<AcceptConnectionResponse>, 138 ) { 139 grpcUnary(scope, responseObserver) { 140 Log.i(TAG, "accepting: serverSocket= $(request.id)") 141 val acceptedSocketCookie = currentCookie++ 142 try { 143 val acceptedSocket: BluetoothSocket = serverMap[request.server.id]!!.accept(2000) 144 Log.i(TAG, "accepted: acceptedSocket= $acceptedSocket") 145 val tmpIn = acceptedSocket.inputStream!! 146 val tmpOut = acceptedSocket.outputStream!! 147 connectionMap[acceptedSocketCookie] = Connection(acceptedSocket, tmpIn, tmpOut) 148 } catch (e: IOException) { 149 Log.e(TAG, "Caught an IOException while trying to accept and create streams.") 150 } 151 152 Log.i(TAG, "after accept") 153 AcceptConnectionResponse.newBuilder() 154 .setConnection(RfcommConnection.newBuilder().setId(acceptedSocketCookie).build()) 155 .build() 156 } 157 } 158 sendnull159 override fun send( 160 request: TxRequest, 161 responseObserver: StreamObserver<TxResponse>, 162 ) { 163 grpcUnary(scope, responseObserver) { 164 if (request.data.isEmpty) { 165 throw Status.UNKNOWN.asException() 166 } 167 val data = request.data!!.toByteArray() 168 169 val socketOut = connectionMap[request.connection.id]!!.outputStream 170 withContext(Dispatchers.IO) { 171 try { 172 socketOut.write(data) 173 socketOut.flush() 174 } catch (e: IOException) { 175 Log.e(TAG, "Exception while writing output stream", e) 176 } 177 } 178 Log.i(TAG, "Sent data") 179 TxResponse.newBuilder().build() 180 } 181 } 182 receivenull183 override fun receive( 184 request: RxRequest, 185 responseObserver: StreamObserver<RxResponse>, 186 ) { 187 grpcUnary(scope, responseObserver) { 188 val data = ByteArray(_bufferSize) 189 190 val socketIn = connectionMap[request.connection.id]!!.inputStream 191 withContext(Dispatchers.IO) { 192 try { 193 socketIn.read(data) 194 } catch (e: IOException) { 195 Log.e(TAG, "Exception while reading from input stream", e) 196 } 197 } 198 Log.i(TAG, "Read data") 199 RxResponse.newBuilder().setData(ByteString.copyFrom(data)).build() 200 } 201 } 202 } 203