1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
|
/*
* Copyright (C) 2015 Samsung System LSI
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.bluetooth;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothServerSocket;
import android.bluetooth.BluetoothSocket;
import android.util.Log;
import java.io.IOException;
import javax.obex.ResponseCodes;
import javax.obex.ServerSession;
/**
* Wraps multiple BluetoothServerSocket objects to make it possible to accept connections on
* both a RFCOMM and L2CAP channel in parallel.<br>
* Create an instance using {@link #create()}, which will block until the sockets have been created
* and channel numbers have been assigned.<br>
* Use {@link #getRfcommChannel()} and {@link #getL2capPsm()} to get the channel numbers to
* put into the SDP record.<br>
* Call {@link #shutdown(boolean)} to terminate the accept threads created by the call to
* {@link #create(IObexConnectionHandler)}.<br>
* A reference to an object of this type cannot be reused, and the {@link BluetoothServerSocket}
* object references passed to this object will be closed by this object, hence cannot be reused
* either (This is needed, as the only way to interrupt an accept call is to close the socket...)
* <br>
* When a connection is accepted,
* {@link IObexConnectionHandler#onConnect(BluetoothDevice, BluetoothSocket)} will be called.<br>
* If the an error occur while waiting for an incoming connection
* {@link IObexConnectionHandler#onConnect(BluetoothDevice, BluetoothSocket)} will be called.<br>
* In both cases the {@link ObexServerSockets} object have terminated, and a new must be created.
*/
public class ObexServerSockets {
private final String mTag;
private static final String STAG = "ObexServerSockets";
private static final boolean D = true; // TODO: set to false!
private static final int NUMBER_OF_SOCKET_TYPES = 2; // increment if LE will be supported
private final IObexConnectionHandler mConHandler;
/* The wrapped sockets */
private final BluetoothServerSocket mRfcommSocket;
private final BluetoothServerSocket mL2capSocket;
/* Handles to the accept threads. Needed for shutdown. */
private SocketAcceptThread mRfcommThread;
private SocketAcceptThread mL2capThread;
private static volatile int sInstanceCounter;
private ObexServerSockets(IObexConnectionHandler conHandler, BluetoothServerSocket rfcommSocket,
BluetoothServerSocket l2capSocket) {
mConHandler = conHandler;
mRfcommSocket = rfcommSocket;
mL2capSocket = l2capSocket;
mTag = "ObexServerSockets" + sInstanceCounter++;
}
/**
* Creates an RFCOMM {@link BluetoothServerSocket} and a L2CAP {@link BluetoothServerSocket}
* @param validator a reference to the {@link IObexConnectionHandler} object to call
* to validate an incoming connection.
* @return a reference to a {@link ObexServerSockets} object instance.
* @throws IOException if it occurs while creating the {@link BluetoothServerSocket}s.
*/
public static ObexServerSockets create(IObexConnectionHandler validator) {
return create(validator, BluetoothAdapter.SOCKET_CHANNEL_AUTO_STATIC_NO_SDP,
BluetoothAdapter.SOCKET_CHANNEL_AUTO_STATIC_NO_SDP, true);
}
/**
* Creates an Insecure RFCOMM {@link BluetoothServerSocket} and a L2CAP
* {@link BluetoothServerSocket}
* @param validator a reference to the {@link IObexConnectionHandler} object to call
* to validate an incoming connection.
* @return a reference to a {@link ObexServerSockets} object instance.
* @throws IOException if it occurs while creating the {@link BluetoothServerSocket}s.
*/
public static ObexServerSockets createInsecure(IObexConnectionHandler validator) {
return create(validator, BluetoothAdapter.SOCKET_CHANNEL_AUTO_STATIC_NO_SDP,
BluetoothAdapter.SOCKET_CHANNEL_AUTO_STATIC_NO_SDP, false);
}
private static final int CREATE_RETRY_TIME = 10;
/**
* Creates an RFCOMM {@link BluetoothServerSocket} and a L2CAP {@link BluetoothServerSocket}
* with specific l2cap and RFCOMM channel numbers. It is the responsibility of the caller to
* ensure the numbers are free and can be used, e.g. by calling {@link #getL2capPsm()} and
* {@link #getRfcommChannel()} in {@link ObexServerSockets}.
* @param validator a reference to the {@link IObexConnectionHandler} object to call
* to validate an incoming connection.
* @param isSecure boolean flag to determine whther socket would be secured or inseucure.
* @return a reference to a {@link ObexServerSockets} object instance.
* @throws IOException if it occurs while creating the {@link BluetoothServerSocket}s.
*
* TODO: Make public when it becomes possible to determine that the listen-call
* failed due to channel-in-use.
*/
private static ObexServerSockets create(IObexConnectionHandler validator, int rfcommChannel,
int l2capPsm, boolean isSecure) {
if (D) {
Log.d(STAG, "create(rfcomm = " + rfcommChannel + ", l2capPsm = " + l2capPsm + ")");
}
BluetoothAdapter bt = BluetoothAdapter.getDefaultAdapter();
if (bt == null) {
throw new RuntimeException("No bluetooth adapter...");
}
BluetoothServerSocket rfcommSocket = null;
BluetoothServerSocket l2capSocket = null;
boolean initSocketOK = false;
// It's possible that create will fail in some cases. retry for 10 times
for (int i = 0; i < CREATE_RETRY_TIME; i++) {
initSocketOK = true;
try {
if (rfcommSocket == null) {
if (isSecure) {
rfcommSocket = bt.listenUsingRfcommOn(rfcommChannel);
} else {
rfcommSocket = bt.listenUsingInsecureRfcommOn(rfcommChannel);
}
}
if (l2capSocket == null) {
if (isSecure) {
l2capSocket = bt.listenUsingL2capOn(l2capPsm);
} else {
l2capSocket = bt.listenUsingInsecureL2capOn(l2capPsm);
}
}
} catch (IOException e) {
Log.e(STAG, "Error create ServerSockets ", e);
initSocketOK = false;
}
if (!initSocketOK) {
// Need to break out of this loop if BT is being turned off.
int state = bt.getState();
if ((state != BluetoothAdapter.STATE_TURNING_ON) && (state
!= BluetoothAdapter.STATE_ON)) {
Log.w(STAG, "initServerSockets failed as BT is (being) turned off");
break;
}
try {
if (D) {
Log.v(STAG, "waiting 300 ms...");
}
Thread.sleep(300);
} catch (InterruptedException e) {
Log.e(STAG, "create() was interrupted");
}
} else {
break;
}
}
if (initSocketOK) {
if (D) {
Log.d(STAG, "Succeed to create listening sockets ");
}
ObexServerSockets sockets = new ObexServerSockets(validator, rfcommSocket, l2capSocket);
sockets.startAccept();
return sockets;
} else {
Log.e(STAG, "Error to create listening socket after " + CREATE_RETRY_TIME + " try");
return null;
}
}
/**
* Returns the channel number assigned to the RFCOMM socket. This will be a static value, that
* should be reused for multiple connections.
* @return the RFCOMM channel number
*/
public int getRfcommChannel() {
return mRfcommSocket.getChannel();
}
/**
* Returns the channel number assigned to the L2CAP socket. This will be a static value, that
* should be reused for multiple connections.
* @return the L2CAP channel number
*/
public int getL2capPsm() {
return mL2capSocket.getChannel();
}
/**
* Initiate the accept threads.
* Will create a thread for each socket type. an incoming connection will be signaled to
* the {@link IObexConnectionValidator#onConnect()}, at which point both threads will exit.
*/
private void startAccept() {
if (D) {
Log.d(mTag, "startAccept()");
}
mRfcommThread = new SocketAcceptThread(mRfcommSocket);
mRfcommThread.start();
mL2capThread = new SocketAcceptThread(mL2capSocket);
mL2capThread.start();
}
/**
* Called from the AcceptThreads to signal an incoming connection.
* @param device the connecting device.
* @param conSocket the socket associated with the connection.
* @return true if the connection is accepted, false otherwise.
*/
private synchronized boolean onConnect(BluetoothDevice device, BluetoothSocket conSocket) {
if (D) {
Log.d(mTag, "onConnect() socket: " + conSocket);
}
return mConHandler.onConnect(device, conSocket);
}
/**
* Signal to the {@link IObexConnectionHandler} that an error have occurred.
*/
private synchronized void onAcceptFailed() {
shutdown(false);
BluetoothAdapter mAdapter = BluetoothAdapter.getDefaultAdapter();
if ((mAdapter != null) && (mAdapter.getState() == BluetoothAdapter.STATE_ON)) {
Log.d(mTag, "onAcceptFailed() calling shutdown...");
mConHandler.onAcceptFailed();
}
}
/**
* Terminate any running accept threads
* @param block Set true to block the calling thread until the AcceptThreads
* has ended execution
*/
public synchronized void shutdown(boolean block) {
if (D) {
Log.d(mTag, "shutdown(block = " + block + ")");
}
if (mRfcommThread != null) {
mRfcommThread.shutdown();
}
if (mL2capThread != null) {
mL2capThread.shutdown();
}
if (block) {
while (mRfcommThread != null || mL2capThread != null) {
try {
if (mRfcommThread != null) {
mRfcommThread.join();
mRfcommThread = null;
}
if (mL2capThread != null) {
mL2capThread.join();
mL2capThread = null;
}
} catch (InterruptedException e) {
Log.i(mTag, "shutdown() interrupted, continue waiting...", e);
}
}
} else {
mRfcommThread = null;
mL2capThread = null;
}
}
/**
* A thread that runs in the background waiting for remote an incoming
* connect. Once a remote socket connects, this thread will be
* shutdown. When the remote disconnect, this thread shall be restarted to
* accept a new connection.
*/
private class SocketAcceptThread extends Thread {
private boolean mStopped = false;
private final BluetoothServerSocket mServerSocket;
/**
* Create a SocketAcceptThread
* @param serverSocket shall never be null.
* @param latch shall never be null.
* @throws IllegalArgumentException
*/
SocketAcceptThread(BluetoothServerSocket serverSocket) {
if (serverSocket == null) {
throw new IllegalArgumentException("serverSocket cannot be null");
}
mServerSocket = serverSocket;
}
/**
* Run until shutdown of BT.
* Accept incoming connections and reject if needed. Keep accepting incoming connections.
*/
@Override
public void run() {
try {
while (!mStopped) {
BluetoothSocket connSocket;
BluetoothDevice device;
try {
if (D) {
Log.d(mTag, "Accepting socket connection...");
}
connSocket = mServerSocket.accept();
if (D) {
Log.d(mTag, "Accepted socket connection from: " + mServerSocket);
}
if (connSocket == null) {
// TODO: Do we need a max error count, to avoid spinning?
Log.w(mTag, "connSocket is null - reattempt accept");
continue;
}
device = connSocket.getRemoteDevice();
if (device == null) {
Log.i(mTag, "getRemoteDevice() = null - reattempt accept");
try {
connSocket.close();
} catch (IOException e) {
Log.w(mTag, "Error closing the socket. ignoring...", e);
}
continue;
}
/* Signal to the service that we have received an incoming connection.
*/
boolean isValid = ObexServerSockets.this.onConnect(device, connSocket);
if (!isValid) {
/* Close connection if we already have a connection with another device
* by responding to the OBEX connect request.
*/
Log.i(mTag, "RemoteDevice is invalid - creating ObexRejectServer.");
BluetoothObexTransport obexTrans =
new BluetoothObexTransport(connSocket);
// Create and detach a selfdestructing ServerSession to respond to any
// incoming OBEX signals.
new ServerSession(obexTrans,
new ObexRejectServer(ResponseCodes.OBEX_HTTP_UNAVAILABLE,
connSocket), null);
// now wait for a new connect
} else {
// now wait for a new connect
}
} catch (IOException ex) {
if (mStopped) {
// Expected exception because of shutdown.
} else {
Log.w(mTag, "Accept exception for " + mServerSocket, ex);
ObexServerSockets.this.onAcceptFailed();
}
mStopped = true;
}
} // End while()
} finally {
if (D) {
Log.d(mTag, "AcceptThread ended for: " + mServerSocket);
}
}
}
/**
* Shuts down the accept threads, and closes the ServerSockets, causing all related
* BluetoothSockets to disconnect, hence do not call until all all accepted connections
* are ready to be disconnected.
*/
public void shutdown() {
if (!mStopped) {
mStopped = true;
// TODO: According to the documentation, this should not close the accepted
// sockets - and that is true, but it closes the l2cap connections, and
// therefore it implicitly also closes the accepted sockets...
try {
mServerSocket.close();
} catch (IOException e) {
if (D) {
Log.d(mTag, "Exception while thread shutdown:", e);
}
}
}
// If called from another thread, interrupt the thread
if (!Thread.currentThread().equals(this)) {
// TODO: Will this interrupt the thread if it is blocked in synchronized?
// Else: change to use InterruptableLock
if (D) {
Log.d(mTag, "shutdown called from another thread - interrupt().");
}
interrupt();
}
}
}
}
|