altosdroid: Lots of bluetooth connection changes
[fw/altos] / altosdroid / src / org / altusmetrum / AltosDroid / AltosBluetooth.java
1 /*
2  * Copyright © 2011 Keith Packard <keithp@keithp.com>
3  * Copyright © 2012 Mike Beattie <mike@ethernal.org>
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; version 2 of the License.
8  *
9  * This program is distributed in the hope that it will be useful, but
10  * WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License along
15  * with this program; if not, write to the Free Software Foundation, Inc.,
16  * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
17  */
18
19 package org.altusmetrum.AltosDroid;
20
21 import java.io.IOException;
22 import java.io.InputStream;
23 import java.io.OutputStream;
24 import java.util.UUID;
25
26 import android.bluetooth.BluetoothAdapter;
27 import android.bluetooth.BluetoothDevice;
28 import android.bluetooth.BluetoothSocket;
29 //import android.os.Bundle;
30 import android.os.Handler;
31 //import android.os.Message;
32 import android.util.Log;
33
34 import org.altusmetrum.altoslib_6.*;
35
36 public class AltosBluetooth extends AltosLink {
37
38         // Debugging
39         private static final String TAG = "AltosBluetooth";
40         private static final boolean D = true;
41
42         private ConnectThread    connect_thread = null;
43         private Thread           input_thread   = null;
44
45         private Handler          handler;
46
47         private BluetoothAdapter adapter;
48         private BluetoothDevice  device;
49         private BluetoothSocket  socket;
50         private InputStream      input;
51         private OutputStream     output;
52
53         // Constructor
54         public AltosBluetooth(BluetoothDevice in_device, Handler in_handler) {
55 //              set_debug(D);
56                 adapter = BluetoothAdapter.getDefaultAdapter();
57                 device = in_device;
58                 handler = in_handler;
59
60                 connect_thread = new ConnectThread(device);
61                 connect_thread.start();
62
63         }
64
65         private void connected() {
66                 try {
67                         synchronized(this) {
68                                 if (socket != null) {
69                                         input = socket.getInputStream();
70                                         output = socket.getOutputStream();
71
72                                         input_thread = new Thread(this);
73                                         input_thread.start();
74
75                                         // Configure the newly connected device for telemetry
76                                         print("~\nE 0\n");
77                                         set_monitor(false);
78                                         if (D) Log.d(TAG, "ConnectThread: connected");
79
80                                         /* Let TelemetryService know we're connected
81                                          */
82                                         handler.obtainMessage(TelemetryService.MSG_CONNECTED).sendToTarget();
83
84                                         /* Notify other waiting threads that we're connected now
85                                          */
86                                         notifyAll();
87                                 }
88                         }
89                 } catch (IOException io) {
90                         connect_failed();
91                 }
92         }
93
94         private void connect_failed() {
95                 synchronized (this) {
96                         if (socket != null) {
97                                 try {
98                                         socket.close();
99                                 } catch (IOException e2) {
100                                         if (D) Log.e(TAG, "ConnectThread: Failed to close() socket after failed connection");
101                                 }
102                                 socket = null;
103                         }
104                         input = null;
105                         output = null;
106                         handler.obtainMessage(TelemetryService.MSG_CONNECT_FAILED).sendToTarget();
107                         if (D) Log.e(TAG, "ConnectThread: Failed to establish connection");
108                 }
109         }
110
111         private Object closing_lock = new Object();
112         private boolean closing = false;
113
114         private void disconnected() {
115                 synchronized(closing_lock) {
116                         if (D) Log.e(TAG, String.format("Connection lost during I/O. Closing  %b", closing));
117                         if (!closing) {
118                                 if (D) Log.d(TAG, "Sending disconnected message");
119                                 handler.obtainMessage(TelemetryService.MSG_DISCONNECTED).sendToTarget();
120                         }
121                 }
122         }
123
124         private class ConnectThread extends Thread {
125                 private final UUID SPP_UUID = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB");
126
127                 public ConnectThread(BluetoothDevice device) {
128                         BluetoothSocket tmp_socket = null;
129
130                         try {
131                                 tmp_socket = device.createInsecureRfcommSocketToServiceRecord(SPP_UUID);
132                         } catch (IOException e) {
133                                 e.printStackTrace();
134                         }
135                         socket = tmp_socket;
136                 }
137
138                 public void run() {
139                         if (D) Log.d(TAG, "ConnectThread: BEGIN");
140                         setName("ConnectThread");
141
142                         // Always cancel discovery because it will slow down a connection
143                         adapter.cancelDiscovery();
144
145                         BluetoothSocket local_socket;
146
147                         try {
148                                 synchronized (AltosBluetooth.this) {
149                                         local_socket = socket;
150                                 }
151
152                                 if (local_socket != null) {
153                                         // Make a connection to the BluetoothSocket
154                                         // This is a blocking call and will only return on a
155                                         // successful connection or an exception
156                                         local_socket.connect();
157                                 }
158
159                                 connected();
160
161                         } catch (IOException e) {
162                                 connect_failed();
163                         }
164
165                         synchronized (AltosBluetooth.this) {
166                                 /* Reset the ConnectThread because we're done
167                                  */
168                                 connect_thread = null;
169                         }
170                         if (D) Log.d(TAG, "ConnectThread: Connect completed");
171                 }
172
173                 public void cancel() {
174                         try {
175                                 BluetoothSocket local_socket;
176                                 synchronized(AltosBluetooth.this) {
177                                         local_socket = socket;
178                                         socket = null;
179                                 }
180                                 if (local_socket != null)
181                                         local_socket.close();
182
183                         } catch (IOException e) {
184                                 if (D) Log.e(TAG, "ConnectThread: close() of connect socket failed", e);
185                         }
186                 }
187         }
188
189         public double frequency() {
190                 return frequency;
191         }
192
193         public int telemetry_rate() {
194                 return telemetry_rate;
195         }
196
197         public void save_frequency() {
198                 AltosPreferences.set_frequency(0, frequency);
199         }
200
201         public void save_telemetry_rate() {
202                 AltosPreferences.set_telemetry_rate(0, telemetry_rate);
203         }
204
205         private synchronized void wait_connected() throws InterruptedException, IOException {
206                 if (input == null && socket != null) {
207                         if (D) Log.d(TAG, "wait_connected...");
208                         wait();
209                         if (D) Log.d(TAG, "wait_connected done");
210                 }
211                 if (socket == null)
212                         throw new IOException();
213         }
214
215         public void print(String data) {
216                 byte[] bytes = data.getBytes();
217                 if (D) Log.d(TAG, "print(): begin");
218                 try {
219                         wait_connected();
220                         output.write(bytes);
221                         if (D) Log.d(TAG, "print(): Wrote bytes: '" + data.replace('\n', '\\') + "'");
222                 } catch (IOException e) {
223                         disconnected();
224                 } catch (InterruptedException e) {
225                         disconnected();
226                 }
227         }
228
229         public void putchar(byte c) {
230                 byte[] bytes = { c };
231                 if (D) Log.d(TAG, "print(): begin");
232                 try {
233                         wait_connected();
234                         output.write(bytes);
235                         if (D) Log.d(TAG, "print(): Wrote byte: '" + c + "'");
236                 } catch (IOException e) {
237                         disconnected();
238                 } catch (InterruptedException e) {
239                         disconnected();
240                 }
241         }
242
243         private static final int buffer_size = 1024;
244
245         private byte[] buffer = new byte[buffer_size];
246         private int buffer_len = 0;
247         private int buffer_off = 0;
248
249         public int getchar() {
250                 while (buffer_off == buffer_len) {
251                         try {
252                                 wait_connected();
253                                 buffer_len = input.read(buffer);
254                                 buffer_off = 0;
255                         } catch (IOException e) {
256                                 if (D) Log.d(TAG, "getchar IOException");
257                                 disconnected();
258                                 return AltosLink.ERROR;
259                         } catch (java.lang.InterruptedException e) {
260                                 if (D) Log.d(TAG, "getchar Interrupted");
261                                 disconnected();
262                                 return AltosLink.ERROR;
263                         }
264                 }
265                 return buffer[buffer_off++];
266         }
267
268         public void closing() {
269                 synchronized(closing_lock) {
270                         if (D) Log.d(TAG, "Marked closing true");
271                         closing = true;
272                 }
273         }
274
275
276         public void close() {
277                 if (D) Log.d(TAG, "close(): begin");
278
279                 closing();
280
281                 synchronized(this) {
282                         if (D) Log.d(TAG, "close(): synched");
283
284                         if (socket != null) {
285                                 if (D) Log.d(TAG, "close(): Closing socket");
286                                 try {
287                                         socket.close();
288                                 } catch (IOException e) {
289                                         if (D) Log.e(TAG, "close(): unable to close() socket");
290                                 }
291                                 socket = null;
292                         }
293                         connect_thread = null;
294                         if (input_thread != null) {
295                                 if (D) Log.d(TAG, "close(): stopping input_thread");
296                                 try {
297                                         if (D) Log.d(TAG, "close(): input_thread.interrupt().....");
298                                         input_thread.interrupt();
299                                         if (D) Log.d(TAG, "close(): input_thread.join().....");
300                                         input_thread.join();
301                                 } catch (Exception e) {}
302                                 input_thread = null;
303                         }
304                         input = null;
305                         output = null;
306                         notifyAll();
307                 }
308         }
309
310
311         // We override this method so that we can add some debugging. Not 100% elegant, but more useful
312         // than debugging one char at a time above in getchar()!
313         public void add_reply(AltosLine line) throws InterruptedException {
314                 if (D) Log.d(TAG, String.format("Got REPLY: %s", line.line));
315                 super.add_reply(line);
316         }
317
318         //public void flush_output() { super.flush_output(); }
319
320         // Stubs of required methods when extending AltosLink
321         public boolean can_cancel_reply()   { return false; }
322         public boolean show_reply_timeout() { return true; }
323         public void hide_reply_timeout()    { }
324
325 }