1 /* 2 * Copyright (C) 2009 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.bluetooth; 18 19 import android.annotation.SuppressLint; 20 import android.compat.annotation.UnsupportedAppUsage; 21 import android.os.Handler; 22 import android.os.ParcelUuid; 23 import android.util.Log; 24 25 import java.io.Closeable; 26 import java.io.IOException; 27 28 /** 29 * A listening Bluetooth socket. 30 * 31 * <p>The interface for Bluetooth Sockets is similar to that of TCP sockets: {@link java.net.Socket} 32 * and {@link java.net.ServerSocket}. On the server side, use a {@link BluetoothServerSocket} to 33 * create a listening server socket. When a connection is accepted by the {@link 34 * BluetoothServerSocket}, it will return a new {@link BluetoothSocket} to manage the connection. On 35 * the client side, use a single {@link BluetoothSocket} to both initiate an outgoing connection and 36 * to manage the connection. 37 * 38 * <p>For Bluetooth BR/EDR, the most common type of socket is RFCOMM, which is the type supported by 39 * the Android APIs. RFCOMM is a connection-oriented, streaming transport over Bluetooth BR/EDR. It 40 * is also known as the Serial Port Profile (SPP). To create a listening {@link 41 * BluetoothServerSocket} that's ready for incoming Bluetooth BR/EDR connections, use {@link 42 * BluetoothAdapter#listenUsingRfcommWithServiceRecord 43 * BluetoothAdapter.listenUsingRfcommWithServiceRecord()}. 44 * 45 * <p>For Bluetooth LE, the socket uses LE Connection-oriented Channel (CoC). LE CoC is a 46 * connection-oriented, streaming transport over Bluetooth LE and has a credit-based flow control. 47 * Correspondingly, use {@link BluetoothAdapter#listenUsingL2capChannel 48 * BluetoothAdapter.listenUsingL2capChannel()} to create a listening {@link BluetoothServerSocket} 49 * that's ready for incoming Bluetooth LE CoC connections. For LE CoC, you can use {@link #getPsm()} 50 * to get the protocol/service multiplexer (PSM) value that the peer needs to use to connect to your 51 * socket. 52 * 53 * <p>After the listening {@link BluetoothServerSocket} is created, call {@link #accept()} to listen 54 * for incoming connection requests. This call will block until a connection is established, at 55 * which point, it will return a {@link BluetoothSocket} to manage the connection. Once the {@link 56 * BluetoothSocket} is acquired, it's a good idea to call {@link #close()} on the {@link 57 * BluetoothServerSocket} when it's no longer needed for accepting connections. Closing the {@link 58 * BluetoothServerSocket} will <em>not</em> close the returned {@link BluetoothSocket}. 59 * 60 * <p>{@link BluetoothServerSocket} is thread safe. In particular, {@link #close} will always 61 * immediately abort ongoing operations and close the server socket. 62 * 63 * <p><div class="special reference"> 64 * 65 * <h3>Developer Guides</h3> 66 * 67 * <p>For more information about using Bluetooth, read the <a 68 * href="{@docRoot}guide/topics/connectivity/bluetooth.html">Bluetooth</a> developer guide. </div> 69 * 70 * @see BluetoothSocket 71 */ 72 @SuppressLint("AndroidFrameworkBluetoothPermission") 73 public final class BluetoothServerSocket implements Closeable { 74 75 private static final String TAG = "BluetoothServerSocket"; 76 private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG); 77 78 @UnsupportedAppUsage( 79 publicAlternatives = "Use public {@link BluetoothServerSocket} API " + "instead.") 80 /*package*/ final BluetoothSocket mSocket; 81 82 private Handler mHandler; 83 private int mMessage; 84 private int mChannel; 85 private long mSocketCreationTimeMillis = 0; 86 private long mSocketCreationLatencyMillis = 0; 87 88 // BluetoothSocket.getConnectionType() will hide L2CAP_LE. 89 // Therefore a new variable need to be maintained here. 90 private int mType; 91 92 /** 93 * Construct a socket for incoming connections. 94 * 95 * @param type type of socket 96 * @param auth require the remote device to be authenticated 97 * @param encrypt require the connection to be encrypted 98 * @param port remote port 99 * @throws IOException On error, for example Bluetooth not available, or insufficient privileges 100 */ BluetoothServerSocket(int type, boolean auth, boolean encrypt, int port)101 /*package*/ BluetoothServerSocket(int type, boolean auth, boolean encrypt, int port) 102 throws IOException { 103 mSocketCreationTimeMillis = System.currentTimeMillis(); 104 mType = type; 105 mChannel = port; 106 mSocket = new BluetoothSocket(type, auth, encrypt, null, port, null); 107 if (port == BluetoothAdapter.SOCKET_CHANNEL_AUTO_STATIC_NO_SDP) { 108 mSocket.setExcludeSdp(true); 109 } 110 mSocketCreationLatencyMillis = System.currentTimeMillis() - mSocketCreationTimeMillis; 111 } 112 113 /** 114 * Construct a socket for incoming connections. 115 * 116 * @param type type of socket 117 * @param auth require the remote device to be authenticated 118 * @param encrypt require the connection to be encrypted 119 * @param port remote port 120 * @param mitm enforce person-in-the-middle protection for authentication. 121 * @param min16DigitPin enforce a minimum length of 16 digits for a sec mode 2 connection 122 * @throws IOException On error, for example Bluetooth not available, or insufficient privileges 123 */ BluetoothServerSocket( int type, boolean auth, boolean encrypt, int port, boolean mitm, boolean min16DigitPin)124 /*package*/ BluetoothServerSocket( 125 int type, boolean auth, boolean encrypt, int port, boolean mitm, boolean min16DigitPin) 126 throws IOException { 127 mSocketCreationTimeMillis = System.currentTimeMillis(); 128 mType = type; 129 mChannel = port; 130 mSocket = new BluetoothSocket(type, auth, encrypt, null, port, null, mitm, min16DigitPin); 131 if (port == BluetoothAdapter.SOCKET_CHANNEL_AUTO_STATIC_NO_SDP) { 132 mSocket.setExcludeSdp(true); 133 } 134 mSocketCreationLatencyMillis = System.currentTimeMillis() - mSocketCreationTimeMillis; 135 } 136 137 /** 138 * Construct a socket for incoming connections. 139 * 140 * @param type type of socket 141 * @param auth require the remote device to be authenticated 142 * @param encrypt require the connection to be encrypted 143 * @param uuid uuid 144 * @throws IOException On error, for example Bluetooth not available, or insufficient privileges 145 */ BluetoothServerSocket(int type, boolean auth, boolean encrypt, ParcelUuid uuid)146 /*package*/ BluetoothServerSocket(int type, boolean auth, boolean encrypt, ParcelUuid uuid) 147 throws IOException { 148 mSocketCreationTimeMillis = System.currentTimeMillis(); 149 mType = type; 150 mSocket = new BluetoothSocket(type, auth, encrypt, null, -1, uuid); 151 // TODO: This is the same as mChannel = -1 - is this intentional? 152 mChannel = mSocket.getPort(); 153 mSocketCreationLatencyMillis = System.currentTimeMillis() - mSocketCreationTimeMillis; 154 } 155 156 /** 157 * Block until a connection is established. 158 * 159 * <p>Returns a connected {@link BluetoothSocket} on successful connection. 160 * 161 * <p>Once this call returns, it can be called again to accept subsequent incoming connections. 162 * 163 * <p>{@link #close} can be used to abort this call from another thread. 164 * 165 * @return a connected {@link BluetoothSocket} 166 * @throws IOException on error, for example this call was aborted, or timeout 167 */ accept()168 public BluetoothSocket accept() throws IOException { 169 return accept(-1); 170 } 171 172 /** 173 * Block until a connection is established, with timeout. 174 * 175 * <p>Returns a connected {@link BluetoothSocket} on successful connection. 176 * 177 * <p>Once this call returns, it can be called again to accept subsequent incoming connections. 178 * 179 * <p>{@link #close} can be used to abort this call from another thread. 180 * 181 * @return a connected {@link BluetoothSocket} 182 * @throws IOException on error, for example this call was aborted, or timeout 183 */ accept(int timeout)184 public BluetoothSocket accept(int timeout) throws IOException { 185 long socketConnectionTime = System.currentTimeMillis(); 186 BluetoothSocket acceptedSocket = null; 187 try { 188 acceptedSocket = mSocket.accept(timeout); 189 SocketMetrics.logSocketAccept( 190 acceptedSocket, 191 mSocket, 192 mType, 193 mChannel, 194 timeout, 195 SocketMetrics.RESULT_L2CAP_CONN_SUCCESS, 196 mSocketCreationTimeMillis, 197 mSocketCreationLatencyMillis, 198 socketConnectionTime); 199 return acceptedSocket; 200 } catch (IOException e) { 201 SocketMetrics.logSocketAccept( 202 acceptedSocket, 203 mSocket, 204 mType, 205 mChannel, 206 timeout, 207 SocketMetrics.RESULT_L2CAP_CONN_SERVER_FAILURE, 208 mSocketCreationTimeMillis, 209 mSocketCreationLatencyMillis, 210 socketConnectionTime); 211 throw e; 212 } 213 } 214 215 /** 216 * Immediately close this socket, and release all associated resources. 217 * 218 * <p>Causes blocked calls on this socket in other threads to immediately throw an IOException. 219 * 220 * <p>Closing the {@link BluetoothServerSocket} will <em>not</em> close any {@link 221 * BluetoothSocket} received from {@link #accept()}. 222 */ close()223 public void close() throws IOException { 224 if (DBG) Log.d(TAG, "BluetoothServerSocket:close() called. mChannel=" + mChannel); 225 synchronized (this) { 226 if (mHandler != null) { 227 mHandler.obtainMessage(mMessage).sendToTarget(); 228 } 229 } 230 mSocket.close(); 231 } 232 233 /*package*/ setCloseHandler(Handler handler, int message)234 synchronized void setCloseHandler(Handler handler, int message) { 235 mHandler = handler; 236 mMessage = message; 237 } 238 setServiceName(String serviceName)239 /*package*/ void setServiceName(String serviceName) { 240 mSocket.setServiceName(serviceName); 241 } 242 243 /** 244 * Returns the channel on which this socket is bound. 245 * 246 * @hide 247 */ getChannel()248 public int getChannel() { 249 return mChannel; 250 } 251 252 /** 253 * Returns the assigned dynamic protocol/service multiplexer (PSM) value for the listening L2CAP 254 * Connection-oriented Channel (CoC) server socket. This server socket must be returned by the 255 * {@link BluetoothAdapter#listenUsingL2capChannel()} or {@link 256 * BluetoothAdapter#listenUsingInsecureL2capChannel()}. The returned value is undefined if this 257 * method is called on non-L2CAP server sockets. 258 * 259 * @return the assigned PSM or LE_PSM value depending on transport 260 */ getPsm()261 public int getPsm() { 262 return mChannel; 263 } 264 265 /** 266 * Sets the channel on which future sockets are bound. Currently used only when a channel is 267 * auto generated. 268 */ setChannel(int newChannel)269 /*package*/ void setChannel(int newChannel) { 270 /* TODO: From a design/architecture perspective this is wrong. 271 * The bind operation should be conducted through this class 272 * and the resulting port should be kept in mChannel, and 273 * not set from BluetoothAdapter. */ 274 if (mSocket != null) { 275 if (mSocket.getPort() != newChannel) { 276 Log.w( 277 TAG, 278 "The port set is different that the underlying port. mSocket.getPort(): " 279 + mSocket.getPort() 280 + " requested newChannel: " 281 + newChannel); 282 } 283 } 284 mChannel = newChannel; 285 } 286 287 @Override toString()288 public String toString() { 289 StringBuilder sb = new StringBuilder(); 290 sb.append("ServerSocket: Type: "); 291 switch (mSocket.getConnectionType()) { 292 case BluetoothSocket.TYPE_RFCOMM: 293 { 294 sb.append("TYPE_RFCOMM"); 295 break; 296 } 297 case BluetoothSocket.TYPE_L2CAP: 298 { 299 sb.append("TYPE_L2CAP"); 300 break; 301 } 302 case BluetoothSocket.TYPE_L2CAP_LE: 303 { 304 sb.append("TYPE_L2CAP_LE"); 305 break; 306 } 307 case BluetoothSocket.TYPE_SCO: 308 { 309 sb.append("TYPE_SCO"); 310 break; 311 } 312 } 313 sb.append(" Channel: ").append(mChannel); 314 return sb.toString(); 315 } 316 } 317