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