1 /*
2  * Copyright 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  *      https://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.devicediagnostics.bluetooth
18 
19 import android.bluetooth.BluetoothAdapter
20 import android.bluetooth.BluetoothServerSocket
21 import android.bluetooth.BluetoothSocket
22 import android.bluetooth.le.AdvertiseCallback
23 import android.bluetooth.le.AdvertiseData
24 import android.bluetooth.le.AdvertiseSettings
25 import android.os.Handler
26 import android.os.Looper
27 import android.os.ParcelUuid
28 import android.util.Log
29 import com.android.devicediagnostics.Protos.BluetoothPacket
30 import com.android.devicediagnostics.Protos.PacketCommand
31 import com.android.devicediagnostics.Protos.TrustedDeviceInfo
32 import com.android.devicediagnostics.runInBackground
33 import java.util.UUID
34 
35 val SERVICE_UUID: UUID = UUID.fromString("0000b81d-0000-1000-8000-00805f9b34fb")
36 private const val TAG = "BluetoothServer"
37 const val CHUNK = 4096
38 
39 class BluetoothServer {
40     private val adapter
41         get() = BluetoothAdapter.getDefaultAdapter()
42 
43     private var socket: BluetoothSocket? = null
44     private var starter: ServerStarter? = null
45 
46     interface StartListener {
onServerAvailablenull47         fun onServerAvailable(connectionData: BluetoothConnectionData)
48 
49         fun onServerStarted()
50 
51         fun onServerError()
52     }
53 
54     interface ReadListener {
55         fun onBluetoothReadPacket(packet: BluetoothPacket)
56 
57         fun onBluetoothReadError(e: Exception)
58     }
59 
startnull60     fun start(trustedDeviceInfo: TrustedDeviceInfo, listener: StartListener) {
61         if (starter != null) {
62             starter!!.stop()
63         }
64 
65         val server = this
66         val callbacks =
67             object : ServerStarter.Listener {
68                 override fun onServerAdvertiseStarted(connectionData: BluetoothConnectionData) {
69                     listener.onServerAvailable(connectionData)
70                 }
71 
72                 override fun onServerConnected(socket: BluetoothSocket) {
73                     server.socket = socket
74                     listener.onServerStarted()
75                     starter = null
76                 }
77 
78                 override fun onServerAdvertiseError() {
79                     listener.onServerError()
80                 }
81             }
82         starter = ServerStarter(adapter, trustedDeviceInfo, callbacks)
83         starter!!.start()
84     }
85 
startReadnull86     fun startRead(listener: ReadListener) {
87         val handler = Handler(Looper.myLooper()!!)
88         val localSocket = socket
89         runInBackground {
90             try {
91                 if (localSocket == null) {
92                     throw Exception("Socket is closed")
93                 }
94                 val packet = readBluetoothPacket(localSocket)
95                 handler.post() { listener.onBluetoothReadPacket(packet) }
96             } catch (e: Exception) {
97                 handler.post() { listener.onBluetoothReadError(e) }
98                 return@runInBackground
99             }
100 
101             try {
102                 val ack = BluetoothPacket.newBuilder().setCommand(PacketCommand.COMMAND_ACK)
103                 writeBluetoothPacket(socket!!, ack)
104             } catch (e: Exception) {
105                 Log.e(TAG, "Error sending report acknowledgement: $e")
106                 return@runInBackground
107             }
108 
109             // Wait for the client to hang up before resetting.
110             try {
111                 readBluetoothPacketOrEof(localSocket)
112             } catch (_: Exception) {} finally {
113                 reset()
114             }
115         }
116     }
117 
resetnull118     fun reset() {
119         starter?.stop()
120         starter = null
121         socket?.close()
122         socket = null
123     }
124 
125     val bluetoothEnabled
126         get() = adapter.isEnabled
127 }
128 
129 class ServerStarter(
130     private val adapter: BluetoothAdapter,
131     private val trustedDeviceInfo: TrustedDeviceInfo,
132     private val listener: ServerStarter.Listener
133 ) {
134     private val handler = Handler(Looper.myLooper()!!)
135     private val advertiseSettings =
136         AdvertiseSettings.Builder()
137             .setAdvertiseMode(AdvertiseSettings.ADVERTISE_MODE_LOW_POWER)
138             .setTimeout(0)
139             .build()
140     private val advertiseData =
141         AdvertiseData.Builder()
142             .addServiceUuid(ParcelUuid(SERVICE_UUID))
143             .setIncludeDeviceName(true)
144             .build()
145     private val advertiser = adapter.bluetoothLeAdvertiser
146     private var advertiseCallback: AdvertiseCallback? = null
147     private var active: Boolean = false
148 
149     // Access to this should be protected by synchronized(this).
150     private var serverSocket: BluetoothServerSocket? = null
151 
152     interface Listener {
onServerAdvertiseStartednull153         fun onServerAdvertiseStarted(connectionData: BluetoothConnectionData)
154 
155         fun onServerConnected(socket: BluetoothSocket)
156 
157         fun onServerAdvertiseError()
158     }
159 
160     fun start() {
161         active = true
162 
163         advertiseCallback =
164             object : AdvertiseCallback() {
165                 override fun onStartFailure(errorCode: Int) {
166                     postServerError(Exception("Advertisement failed, error code $errorCode"))
167                 }
168 
169                 override fun onStartSuccess(settings: AdvertiseSettings?) {
170                     Log.d(TAG, "Advertisement started")
171                 }
172             }
173         advertiser.startAdvertising(advertiseSettings, advertiseData, advertiseCallback!!)
174         Log.d(TAG, "startAdvertisement: with advertiser $advertiser")
175 
176         serverSocket = adapter.listenUsingInsecureL2capChannel()
177         Log.d(TAG, "Server started with psm ${serverSocket!!.psm}")
178 
179         val connectionData =
180             BluetoothConnectionData(serverSocket!!.psm, trustedDeviceInfo.challenge.toByteArray())
181         listener.onServerAdvertiseStarted(connectionData)
182 
183         val packet = BluetoothPacket.newBuilder().setTrustedDeviceInfo(trustedDeviceInfo)
184 
185         // Thread since this waits for connections and otherwise will block Ux
186         runInBackground() {
187             synchronized(this) {
188                 if (serverSocket == null) {
189                     return@runInBackground
190                 }
191 
192                 try {
193                     val socket = serverSocket!!.accept()
194                     stopAdvertising()
195 
196                     writeBluetoothPacket(socket, packet)
197                     Log.d(TAG, "wrote start message")
198 
199                     postServerStarted(socket)
200                 } catch (e: Exception) {
201                     postServerError(e)
202                 }
203             }
204         }
205     }
206 
postServerStartednull207     private fun postServerStarted(socket: BluetoothSocket) {
208         handler.post() {
209             if (active) {
210                 listener.onServerConnected(socket)
211                 active = false
212             } else {
213                 socket.close()
214             }
215         }
216     }
217 
postServerErrornull218     private fun postServerError(e: Exception) {
219         handler.post() {
220             if (active) {
221                 listener.onServerAdvertiseError()
222                 stopAdvertising()
223                 active = false
224             }
225         }
226     }
227 
stopAdvertisingnull228     private fun stopAdvertising() {
229         serverSocket?.close()
230         synchronized(this) {
231             if (advertiseCallback != null) {
232                 advertiser.stopAdvertising(advertiseCallback!!)
233                 advertiseCallback = null
234             }
235             if (serverSocket != null) {
236                 serverSocket?.close()
237                 serverSocket = null
238             }
239         }
240     }
241 
stopnull242     fun stop() {
243         stopAdvertising()
244         active = false
245     }
246 }
247