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