SDL_Android/SmartDeviceLinkAndroidProxy - added the correct version of the proxy
[profile/ivi/smartdevicelink.git] / SDL_Android / SmartDeviceLinkProxyAndroid / src / com / smartdevicelink / transport / BTTransport.java
1 package com.smartdevicelink.transport;\r
2 \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
7 \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
12 \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
18 \r
19 /**\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
21  *\r
22  */\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
26 \r
27         private static final String SMARTDEVICELINK_LIB_TRACE_KEY = "42baba60-eb57-11df-98cf-0800200c9a66";\r
28         \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
37         \r
38         // Boolean to monitor if the transport is in a disconnecting state\r
39     private boolean _disconnecting = false;\r
40         \r
41         public BTTransport(ITransportListener transportListener) {\r
42                 super(transportListener);\r
43         } // end-ctor\r
44         \r
45         public void openConnection () throws SmartDeviceLinkException {         \r
46                 \r
47                 // Get the device's default Bluetooth Adapter\r
48                 _adapter = BluetoothAdapter.getDefaultAdapter();\r
49                 \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
53                 }\r
54                 \r
55                 // Test if Bluetooth is enabled\r
56                 try {\r
57                         if (!_adapter.isEnabled()) {\r
58                                 throw new SmartDeviceLinkException("Bluetooth adapter must be enabled to instantiate a SMARTDEVICELINKProxy object.", SmartDeviceLinkExceptionCause.BLUETOOTH_DISABLED);\r
59                         }\r
60                 } catch (SecurityException e) {\r
61                         throw new SmartDeviceLinkException("Insufficient permissions to interact with the Bluetooth Adapter.", SmartDeviceLinkExceptionCause.PERMISSION_DENIED);\r
62                 }\r
63                 \r
64                 // Start BluetoothAdapterMonitor to ensure the Bluetooth Adapter continues to be enabled\r
65                 _bluetoothAdapterMonitor = new BluetoothAdapterMonitor(_adapter);\r
66                 \r
67                 try {\r
68                         _serverSocket = _adapter.listenUsingRfcommWithServiceRecord("SMARTDEVICELINKProxy", _listeningServiceUUID);\r
69                 } catch (IOException ex) {\r
70                         \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
74                         }\r
75                         \r
76                         throw new SmartDeviceLinkException("Could not open connection to SMARTDEVICELINK.", ex, SmartDeviceLinkExceptionCause.SMARTDEVICELINK_CONNECTION_FAILED);\r
77                 } \r
78                 \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
82                 }\r
83                 \r
84                 SmartDeviceLinkTrace.logTransportEvent("BTTransport: listening for incoming connect to service ID " + _listeningServiceUUID, null, InterfaceActivityDirection.Receive, null, 0, SMARTDEVICELINK_LIB_TRACE_KEY);\r
85                 \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
91                 \r
92                 // Initialize the SiphonServer\r
93                 SiphonServer.init();\r
94         } // end-method\r
95 \r
96         public void disconnect() {\r
97                 disconnect(null, null);\r
98         }\r
99 \r
100         /**\r
101          * Destroys the transport between SMARTDEVICELINK and the mobile app\r
102          * \r
103          * @param msg\r
104          * @param ex\r
105          */\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
110                         return;\r
111                 }               \r
112                 _disconnecting = true;\r
113                 \r
114                 String disconnectMsg = (msg == null ? "" : msg);\r
115                 if (ex != null) {\r
116                         disconnectMsg += ", " + ex.toString();\r
117                 } // end-if\r
118 \r
119                 SmartDeviceLinkTrace.logTransportEvent("BTTransport.disconnect: " + disconnectMsg, null, InterfaceActivityDirection.Transmit, null, 0, SMARTDEVICELINK_LIB_TRACE_KEY);\r
120 \r
121                 try {                   \r
122                         if (_transportReader != null) {\r
123                                 _transportReader.halt();\r
124                                 _transportReader = null;\r
125                         }\r
126                 } catch (Exception e) {\r
127                         DebugTool.logError("Failed to stop transport reader thread.", e);\r
128                 } // end-catch  \r
129                 \r
130                 try {\r
131                         if (_bluetoothAdapterMonitor != null) {\r
132                                 _bluetoothAdapterMonitor.halt();\r
133                                 _bluetoothAdapterMonitor = null;\r
134                         }\r
135                 } catch (Exception e) {\r
136                         DebugTool.logError("Failed to stop adapter monitor thread.", e);\r
137                 }\r
138                 \r
139                 try {\r
140                         if (_serverSocket != null) {\r
141                                 _serverSocket.close();\r
142                                 _serverSocket = null;\r
143                         } \r
144                 } catch (Exception e) {\r
145                         DebugTool.logError("Failed to close serverSocket", e);\r
146                 } // end-catch\r
147                 \r
148                 try {\r
149                         if (_activeSocket != null) {\r
150                                 _activeSocket.close();\r
151                                 _activeSocket = null;\r
152                         }\r
153                 } catch (Exception e) {\r
154                         DebugTool.logError("Failed to close activeSocket", e);\r
155                 } // end-catch\r
156                 \r
157                 try {\r
158                         if (_input != null) {\r
159                                 _input.close();\r
160                                 _input = null;\r
161                         }\r
162                 } catch (Exception e) {\r
163                         DebugTool.logError("Failed to close input stream", e);\r
164                 } // end-catch\r
165                 \r
166                 try {\r
167                         if (_output != null) {\r
168                                 _output.close();\r
169                                 _output = null;\r
170                         }\r
171                 } catch (Exception e) {\r
172                         DebugTool.logError("Failed to close output stream", e);\r
173                 } // end-catch\r
174                 \r
175                 if (ex == null) {\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
179                 } else {\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
183                 }\r
184         } // end-method\r
185 \r
186         \r
187         /**\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
190          */\r
191         public boolean sendBytesOverTransport(byte[] msgBytes, int offset, int length) {\r
192                 boolean sendResult = false;\r
193                 try {\r
194                         _output.write(msgBytes, offset, length);\r
195                         sendResult = true;\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
200                 } // end-catch\r
201                 return sendResult;\r
202         } // end-method\r
203         \r
204         \r
205         \r
206         private class TransportReaderThread extends Thread {\r
207                 \r
208                 byte[] buf = new byte[4096];\r
209                 private Boolean isHalted = false;\r
210                 \r
211                 public void halt() {\r
212                         isHalted = true;\r
213                 }\r
214                 \r
215                 private void acceptConnection() {\r
216                         SmartDeviceLinkTrace.logTransportEvent("BTTransport: Waiting for incoming RFCOMM connect", "", InterfaceActivityDirection.Receive, null, 0, SMARTDEVICELINK_LIB_TRACE_KEY);\r
217                         \r
218                         try {\r
219                                 // Blocks thread until connection established.\r
220                                 _activeSocket = _serverSocket.accept();\r
221                                 \r
222                                 // If halted after serverSocket.accept(), then return immediately\r
223                                 if (isHalted) {\r
224                                         return;\r
225                                 }\r
226                                 \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
231                                 \r
232                                 _output = _activeSocket.getOutputStream();\r
233                                 _input = _activeSocket.getInputStream();\r
234 \r
235                                 handleTransportConnected();\r
236                                 \r
237                         } catch (Exception e) {\r
238                                 \r
239                                 if (!isHalted) {                                        \r
240                                         // Only call disconnect if the thread has not been halted\r
241                                         \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
245                                         } else {\r
246                                                 disconnect("Failed to accept connection", e);\r
247                                         }\r
248                                 } \r
249                         } finally {\r
250                                 if (_serverSocket != null && !isHalted) {\r
251                                         try {\r
252                                                 _serverSocket.close();\r
253                                         } catch (IOException e) {\r
254                                                 //do nothing\r
255                                         }\r
256                                         _serverSocket = null;\r
257                                 }\r
258                         }\r
259                 }\r
260                 \r
261                 private void readFromTransport() {\r
262                         try {\r
263                                 int bytesRead = -1;\r
264                                 try {\r
265                                         bytesRead = _input.read(buf);\r
266                                 } catch (Exception e) {\r
267                                         if (!isHalted) {\r
268                                                 // Only call disconnect if the thread has not been halted\r
269                                                 \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
273                                                 } else {\r
274                                                         disconnect("Failed to read from Bluetooth transport.", e);\r
275                                                 }\r
276                                         }\r
277                                         return;\r
278                                 } // end-catch\r
279                                 \r
280                                 if (bytesRead != -1) {\r
281                                         handleReceivedBytes(buf, bytesRead);\r
282                                 } else {\r
283                                         // When bytesRead == -1, it indicates end of stream\r
284                                         if (!isHalted) {\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
288                                         }\r
289                                 }\r
290                         } catch (Exception excp) {\r
291                                 if (!isHalted) {\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
296                                 }\r
297                                 return;\r
298                         } // end-catch\r
299                 } // end-method\r
300                 \r
301                 public void run() {\r
302                         // acceptConnection blocks until the connection has been accepted\r
303                         acceptConnection();\r
304                         \r
305                         while (!isHalted) {\r
306                                 readFromTransport();\r
307                         }\r
308                 }\r
309         }\r
310         \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
316                 \r
317                 public BluetoothAdapterMonitor(BluetoothAdapter bluetoothAdapter) {\r
318                         if (bluetoothAdapter == null) {\r
319                                 throw new IllegalArgumentException("BluetoothAdapter cannot be null.");\r
320                         }\r
321                         \r
322                         // Set the bluetooth adapter\r
323                         _bluetoothAdapter = bluetoothAdapter;\r
324                         \r
325                         _bluetoothAdapterMonitorThread = new Thread(new Runnable() {\r
326                                 @Override\r
327                                 public void run() {\r
328                                         while (!_isHalted) {\r
329                                                 checkIfBluetoothAdapterDisabled();\r
330                                                 try {\r
331                                                         Thread.sleep(15000);\r
332                                                 } catch (InterruptedException e) {\r
333                                                         // Break if interrupted\r
334                                                         break;\r
335                                                 }\r
336                                         }\r
337                                 }\r
338                         });\r
339                         _bluetoothAdapterMonitorThread.setName(THREAD_NAME);\r
340                         _bluetoothAdapterMonitorThread.setDaemon(true);\r
341                         _bluetoothAdapterMonitorThread.start();\r
342                 }\r
343                 \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
349                         }\r
350                 }\r
351                 \r
352                 public void halt() {\r
353                         _isHalted = true;\r
354                         _bluetoothAdapterMonitorThread.interrupt();\r
355                 }\r
356         }\r
357 \r
358         /**\r
359          * Overridden abstract method which returns specific type of this transport.\r
360          * \r
361          * @return Constant value - TransportType.BLUETOOTH.\r
362          * \r
363          * @see TransportType\r
364          */\r
365         public TransportType getTransportType() {\r
366                 return TransportType.BLUETOOTH;\r
367         }\r
368 } // end-class\r