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 android.companion.cts.multidevice 18 19 import android.bluetooth.BluetoothAdapter 20 import android.bluetooth.BluetoothDevice 21 import android.bluetooth.BluetoothServerSocket 22 import android.bluetooth.BluetoothSocket 23 import android.companion.CompanionDeviceManager 24 import android.util.Log 25 import java.io.IOException 26 import java.util.UUID 27 28 class BluetoothConnector( 29 private val adapter: BluetoothAdapter, 30 private val cdm: CompanionDeviceManager 31 ) { 32 companion object { 33 private const val TAG = "CDM_BluetoothServer" 34 35 private val SERVICE_NAME = "CDM_BluetoothChannel" 36 private val SERVICE_UUID = UUID.fromString("435fe1d9-56c5-455d-a516-d5e6b22c52f9") 37 38 // Registry of bluetooth server threads 39 private val serverThreads = mutableMapOf<Int, BluetoothServerThread>() 40 41 // Set of connected client sockets 42 private val clientSockets = mutableMapOf<Int, BluetoothSocket>() 43 } 44 attachClientSocketnull45 fun attachClientSocket(associationId: Int) { 46 try { 47 val device = getRemoteDevice(associationId) 48 val socket = device.createRfcommSocketToServiceRecord(SERVICE_UUID) 49 if (clientSockets.containsKey(associationId)) { 50 detachClientSocket(associationId) 51 clientSockets[associationId] = socket 52 } else { 53 clientSockets += associationId to socket 54 } 55 56 socket.connect() 57 Log.d(TAG, "Attaching client socket $socket.") 58 cdm.attachSystemDataTransport( 59 associationId, 60 socket.inputStream, 61 socket.outputStream 62 ) 63 } catch (e: IOException) { 64 Log.e(TAG, "Failed to attach client socket.", e) 65 throw RuntimeException(e) 66 } 67 } 68 attachServerSocketnull69 fun attachServerSocket(associationId: Int) { 70 val serverThread: BluetoothServerThread 71 if (serverThreads.containsKey(associationId)) { 72 serverThread = serverThreads[associationId]!! 73 } else { 74 serverThread = BluetoothServerThread(associationId) 75 serverThreads += associationId to serverThread 76 } 77 78 // Start thread 79 if (!serverThread.isOpen) { 80 serverThread.start() 81 } 82 } 83 closeAllSocketsnull84 fun closeAllSockets() { 85 val iter = clientSockets.keys.iterator() 86 while (iter.hasNext()) { 87 detachClientSocket(iter.next()) 88 } 89 for (thread in serverThreads.values) { 90 thread.shutdown() 91 } 92 serverThreads.clear() 93 } 94 getRemoteDevicenull95 private fun getRemoteDevice(associationId: Int): BluetoothDevice { 96 val association = cdm.myAssociations.stream() 97 .filter { it.id == associationId } 98 .findAny() 99 .orElseThrow() 100 val address = association.deviceMacAddress 101 return adapter.getRemoteDevice(address.toString().uppercase()) 102 } 103 detachClientSocketnull104 private fun detachClientSocket(associationId: Int) { 105 try { 106 Log.d(TAG, "Detaching client socket.") 107 cdm.detachSystemDataTransport(associationId) 108 clientSockets[associationId]?.close() 109 } catch (e: IOException) { 110 Log.e(TAG, "Failed to detach client socket.", e) 111 throw RuntimeException(e) 112 } 113 } 114 115 inner class BluetoothServerThread( 116 private val associationId: Int 117 ) : Thread() { 118 private lateinit var mServerSocket: BluetoothServerSocket 119 120 var isOpen = false 121 runnull122 override fun run() { 123 try { 124 Log.d(TAG, "Listening for remote connections...") 125 mServerSocket = adapter.listenUsingRfcommWithServiceRecord( 126 SERVICE_NAME, 127 SERVICE_UUID 128 ) 129 isOpen = true 130 do { 131 val socket = mServerSocket.accept() 132 Log.d(TAG, "Attaching server socket $socket.") 133 cdm.attachSystemDataTransport( 134 associationId, 135 socket.inputStream, 136 socket.outputStream 137 ) 138 } while (isOpen) 139 } catch (e: IOException) { 140 throw RuntimeException(e) 141 } 142 } 143 shutdownnull144 fun shutdown() { 145 if (!isOpen || !this::mServerSocket.isInitialized) return 146 147 try { 148 Log.d(TAG, "Closing server socket.") 149 cdm.detachSystemDataTransport(associationId) 150 mServerSocket.close() 151 isOpen = false 152 } catch (e: IOException) { 153 throw RuntimeException(e) 154 } 155 } 156 } 157 } 158