• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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