1 package com.smartdevicelink.transport;
\r
3 import java.io.IOException;
\r
4 import java.io.InputStream;
\r
5 import java.io.OutputStream;
\r
6 import java.util.UUID;
\r
8 import android.bluetooth.BluetoothAdapter;
\r
9 import android.bluetooth.BluetoothDevice;
\r
10 import android.bluetooth.BluetoothServerSocket;
\r
11 import android.bluetooth.BluetoothSocket;
\r
13 import com.smartdevicelink.exception.SmartDeviceLinkException;
\r
14 import com.smartdevicelink.exception.SmartDeviceLinkExceptionCause;
\r
15 import com.smartdevicelink.trace.SmartDeviceLinkTrace;
\r
16 import com.smartdevicelink.trace.enums.InterfaceActivityDirection;
\r
17 import com.smartdevicelink.util.DebugTool;
\r
20 * Bluetooth Transport Implementation. This transport advertises its existence to SMARTDEVICELINK by publishing an SDP record and waiting for an incoming connection from SMARTDEVICELINK. Connection is verified by checking for the SMARTDEVICELINK UUID. For more detailed information please refer to the <a href="#bluetoothTransport">Bluetooth Transport Guide</a>.
\r
23 public class BTTransport extends SmartDeviceLinkTransport {
\r
24 //936DA01F9ABD4D9D80C702AF85C822A8
\r
25 private final static UUID SMARTDEVICELINK_V4_MOBILE_APPLICATION_SVC_CLASS = new UUID(0x936DA01F9ABD4D9DL, 0x80C702AF85C822A8L);
\r
27 private static final String SMARTDEVICELINK_LIB_TRACE_KEY = "42baba60-eb57-11df-98cf-0800200c9a66";
\r
29 private BluetoothAdapter _adapter = null;
\r
30 private BluetoothSocket _activeSocket = null;
\r
31 private InputStream _input = null;
\r
32 private UUID _listeningServiceUUID = SMARTDEVICELINK_V4_MOBILE_APPLICATION_SVC_CLASS;
\r
33 private BluetoothAdapterMonitor _bluetoothAdapterMonitor = null;
\r
34 private TransportReaderThread _transportReader = null;
\r
35 private OutputStream _output = null;
\r
36 private BluetoothServerSocket _serverSocket = null;
\r
38 // Boolean to monitor if the transport is in a disconnecting state
\r
39 private boolean _disconnecting = false;
\r
41 public BTTransport(ITransportListener transportListener) {
\r
42 super(transportListener);
\r
45 public void openConnection () throws SmartDeviceLinkException {
\r
47 // Get the device's default Bluetooth Adapter
\r
48 _adapter = BluetoothAdapter.getDefaultAdapter();
\r
50 // Test if Adapter exists
\r
51 if (_adapter == null) {
\r
52 throw new SmartDeviceLinkException("No Bluetooth adapter found. Bluetooth adapter must exist to communicate with SMARTDEVICELINK.", SmartDeviceLinkExceptionCause.BLUETOOTH_ADAPTER_NULL);
\r
55 // Test if Bluetooth is enabled
\r
57 if (!_adapter.isEnabled()) {
\r
58 throw new SmartDeviceLinkException("Bluetooth adapter must be enabled to instantiate a SMARTDEVICELINKProxy object.", SmartDeviceLinkExceptionCause.BLUETOOTH_DISABLED);
\r
60 } catch (SecurityException e) {
\r
61 throw new SmartDeviceLinkException("Insufficient permissions to interact with the Bluetooth Adapter.", SmartDeviceLinkExceptionCause.PERMISSION_DENIED);
\r
64 // Start BluetoothAdapterMonitor to ensure the Bluetooth Adapter continues to be enabled
\r
65 _bluetoothAdapterMonitor = new BluetoothAdapterMonitor(_adapter);
\r
68 _serverSocket = _adapter.listenUsingRfcommWithServiceRecord("SMARTDEVICELINKProxy", _listeningServiceUUID);
\r
69 } catch (IOException ex) {
\r
71 // Test to determine if the bluetooth has been disabled since last check
\r
72 if (!_adapter.isEnabled()) {
\r
73 throw new SmartDeviceLinkException("Bluetooth adapter must be on to instantiate a SMARTDEVICELINKProxy object.", SmartDeviceLinkExceptionCause.BLUETOOTH_DISABLED);
\r
76 throw new SmartDeviceLinkException("Could not open connection to SMARTDEVICELINK.", ex, SmartDeviceLinkExceptionCause.SMARTDEVICELINK_CONNECTION_FAILED);
\r
79 // Test to ensure serverSocket is not null
\r
80 if (_serverSocket == null) {
\r
81 throw new SmartDeviceLinkException("Could not open connection to SMARTDEVICELINK.", SmartDeviceLinkExceptionCause.SMARTDEVICELINK_CONNECTION_FAILED);
\r
84 SmartDeviceLinkTrace.logTransportEvent("BTTransport: listening for incoming connect to service ID " + _listeningServiceUUID, null, InterfaceActivityDirection.Receive, null, 0, SMARTDEVICELINK_LIB_TRACE_KEY);
\r
86 // Setup transportReader thread
\r
87 _transportReader = new TransportReaderThread();
\r
88 _transportReader.setName("TransportReader");
\r
89 _transportReader.setDaemon(true);
\r
90 _transportReader.start();
\r
92 // Initialize the SiphonServer
\r
93 SiphonServer.init();
\r
96 public void disconnect() {
\r
97 disconnect(null, null);
\r
101 * Destroys the transport between SMARTDEVICELINK and the mobile app
\r
106 private synchronized void disconnect(String msg, Exception ex) {
\r
107 // If already disconnecting, return
\r
108 if (_disconnecting) {
\r
109 // No need to recursively call
\r
112 _disconnecting = true;
\r
114 String disconnectMsg = (msg == null ? "" : msg);
\r
116 disconnectMsg += ", " + ex.toString();
\r
119 SmartDeviceLinkTrace.logTransportEvent("BTTransport.disconnect: " + disconnectMsg, null, InterfaceActivityDirection.Transmit, null, 0, SMARTDEVICELINK_LIB_TRACE_KEY);
\r
122 if (_transportReader != null) {
\r
123 _transportReader.halt();
\r
124 _transportReader = null;
\r
126 } catch (Exception e) {
\r
127 DebugTool.logError("Failed to stop transport reader thread.", e);
\r
131 if (_bluetoothAdapterMonitor != null) {
\r
132 _bluetoothAdapterMonitor.halt();
\r
133 _bluetoothAdapterMonitor = null;
\r
135 } catch (Exception e) {
\r
136 DebugTool.logError("Failed to stop adapter monitor thread.", e);
\r
140 if (_serverSocket != null) {
\r
141 _serverSocket.close();
\r
142 _serverSocket = null;
\r
144 } catch (Exception e) {
\r
145 DebugTool.logError("Failed to close serverSocket", e);
\r
149 if (_activeSocket != null) {
\r
150 _activeSocket.close();
\r
151 _activeSocket = null;
\r
153 } catch (Exception e) {
\r
154 DebugTool.logError("Failed to close activeSocket", e);
\r
158 if (_input != null) {
\r
162 } catch (Exception e) {
\r
163 DebugTool.logError("Failed to close input stream", e);
\r
167 if (_output != null) {
\r
171 } catch (Exception e) {
\r
172 DebugTool.logError("Failed to close output stream", e);
\r
176 // This disconnect was not caused by an error, notify the proxy that
\r
177 // the trasport has been disconnected.
\r
178 handleTransportDisconnected(msg);
\r
180 // This disconnect was caused by an error, notify the proxy
\r
181 // that there was a transport error.
\r
182 handleTransportError(msg, ex);
\r
188 * Sends data over the transport. Takes a byte array and transmits data provided starting at the
\r
189 * offset and of the provided length to fragment transmission.
\r
191 public boolean sendBytesOverTransport(byte[] msgBytes, int offset, int length) {
\r
192 boolean sendResult = false;
\r
194 _output.write(msgBytes, offset, length);
\r
196 } catch (Exception ex) {
\r
197 DebugTool.logError("Error writing to Bluetooth socket: " + ex.toString(), ex);
\r
198 handleTransportError("Error writing to Bluetooth socket:", ex);
\r
199 sendResult = false;
\r
206 private class TransportReaderThread extends Thread {
\r
208 byte[] buf = new byte[4096];
\r
209 private Boolean isHalted = false;
\r
211 public void halt() {
\r
215 private void acceptConnection() {
\r
216 SmartDeviceLinkTrace.logTransportEvent("BTTransport: Waiting for incoming RFCOMM connect", "", InterfaceActivityDirection.Receive, null, 0, SMARTDEVICELINK_LIB_TRACE_KEY);
\r
219 // Blocks thread until connection established.
\r
220 _activeSocket = _serverSocket.accept();
\r
222 // If halted after serverSocket.accept(), then return immediately
\r
227 // Log info of the connected device
\r
228 BluetoothDevice btDevice = _activeSocket.getRemoteDevice();
\r
229 String btDeviceInfoXml = SmartDeviceLinkTrace.getBTDeviceInfo(btDevice);
\r
230 SmartDeviceLinkTrace.logTransportEvent("BTTransport: RFCOMM Connection Accepted", btDeviceInfoXml, InterfaceActivityDirection.Receive, null, 0, SMARTDEVICELINK_LIB_TRACE_KEY);
\r
232 _output = _activeSocket.getOutputStream();
\r
233 _input = _activeSocket.getInputStream();
\r
235 handleTransportConnected();
\r
237 } catch (Exception e) {
\r
240 // Only call disconnect if the thread has not been halted
\r
242 // Check to see if Bluetooth was disabled
\r
243 if (_adapter != null && !_adapter.isEnabled()) {
\r
244 disconnect("Bluetooth Adapater has been disabled.", new SmartDeviceLinkException("Bluetooth adapter must be enabled to instantiate a SMARTDEVICELINKProxy object.", e, SmartDeviceLinkExceptionCause.BLUETOOTH_DISABLED));
\r
246 disconnect("Failed to accept connection", e);
\r
250 if (_serverSocket != null && !isHalted) {
\r
252 _serverSocket.close();
\r
253 } catch (IOException e) {
\r
256 _serverSocket = null;
\r
261 private void readFromTransport() {
\r
263 int bytesRead = -1;
\r
265 bytesRead = _input.read(buf);
\r
266 } catch (Exception e) {
\r
268 // Only call disconnect if the thread has not been halted
\r
270 // Check to see if Bluetooth was disabled
\r
271 if (_adapter != null && !_adapter.isEnabled()) {
\r
272 disconnect("Bluetooth Adapater has been disabled.", new SmartDeviceLinkException("Bluetooth adapter must be enabled to instantiate a SMARTDEVICELINKProxy object.", e, SmartDeviceLinkExceptionCause.BLUETOOTH_DISABLED));
\r
274 disconnect("Failed to read from Bluetooth transport.", e);
\r
280 if (bytesRead != -1) {
\r
281 handleReceivedBytes(buf, bytesRead);
\r
283 // When bytesRead == -1, it indicates end of stream
\r
285 // Only call disconnect if the thread has not been halted
\r
286 DebugTool.logError("End of stream reached!");
\r
287 disconnect("End of stream reached.", null);
\r
290 } catch (Exception excp) {
\r
292 // Only call disconnect if the thread has not been halted
\r
293 String errString = "Failure in BTTransport reader thread: " + excp.toString();
\r
294 DebugTool.logError(errString, excp);
\r
295 disconnect(errString, excp);
\r
301 public void run() {
\r
302 // acceptConnection blocks until the connection has been accepted
\r
303 acceptConnection();
\r
305 while (!isHalted) {
\r
306 readFromTransport();
\r
311 private class BluetoothAdapterMonitor {
\r
312 private boolean _isHalted = false;
\r
313 private BluetoothAdapter _bluetoothAdapter = null;
\r
314 private final String THREAD_NAME = "BluetoothAdapterMonitor";
\r
315 private Thread _bluetoothAdapterMonitorThread = null;
\r
317 public BluetoothAdapterMonitor(BluetoothAdapter bluetoothAdapter) {
\r
318 if (bluetoothAdapter == null) {
\r
319 throw new IllegalArgumentException("BluetoothAdapter cannot be null.");
\r
322 // Set the bluetooth adapter
\r
323 _bluetoothAdapter = bluetoothAdapter;
\r
325 _bluetoothAdapterMonitorThread = new Thread(new Runnable() {
\r
327 public void run() {
\r
328 while (!_isHalted) {
\r
329 checkIfBluetoothAdapterDisabled();
\r
331 Thread.sleep(15000);
\r
332 } catch (InterruptedException e) {
\r
333 // Break if interrupted
\r
339 _bluetoothAdapterMonitorThread.setName(THREAD_NAME);
\r
340 _bluetoothAdapterMonitorThread.setDaemon(true);
\r
341 _bluetoothAdapterMonitorThread.start();
\r
344 private void checkIfBluetoothAdapterDisabled() {
\r
345 if (_bluetoothAdapter != null && !_bluetoothAdapter.isEnabled()) {
\r
346 // Bluetooth adapter has been disabled, disconnect the transport
\r
347 disconnect("Bluetooth adapter has been disabled.",
\r
348 new SmartDeviceLinkException("Bluetooth adapter must be enabled to instantiate a SMARTDEVICELINKProxy object.", SmartDeviceLinkExceptionCause.BLUETOOTH_DISABLED));
\r
352 public void halt() {
\r
354 _bluetoothAdapterMonitorThread.interrupt();
\r
359 * Overridden abstract method which returns specific type of this transport.
\r
361 * @return Constant value - TransportType.BLUETOOTH.
\r
363 * @see TransportType
\r
365 public TransportType getTransportType() {
\r
366 return TransportType.BLUETOOTH;
\r