altosdroid: import code from mjb
[fw/altos] / altosdroid / src / org / altusmetrum / AltosDroid / BluetoothChatService.java
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 org.altusmetrum.AltosDroid;
18
19 import java.io.IOException;
20 import java.io.InputStream;
21 import java.io.OutputStream;
22 //import java.lang.reflect.InvocationTargetException;
23 import java.lang.reflect.Method;
24
25 import android.bluetooth.BluetoothAdapter;
26 import android.bluetooth.BluetoothDevice;
27 import android.bluetooth.BluetoothSocket;
28 import android.content.Context;
29 import android.os.Bundle;
30 import android.os.Handler;
31 import android.os.Message;
32 import android.util.Log;
33
34 /**
35  * This class does all the work for setting up and managing Bluetooth
36  * connections with other devices. It has a thread that listens for
37  * incoming connections, a thread for connecting with a device, and a
38  * thread for performing data transmissions when connected.
39  */
40 public class BluetoothChatService {
41     // Debugging
42     private static final String TAG = "BluetoothChatService";
43     private static final boolean D = true;
44
45     // Member fields
46     private final BluetoothAdapter mAdapter;
47     private final Handler mHandler;
48     private ConnectThread mConnectThread;
49     private ConnectedThread mConnectedThread;
50     private int mState;
51
52     // Constants that indicate the current connection state
53     public static final int STATE_NONE = 0;       // we're doing nothing
54     public static final int STATE_READY = 1;
55     public static final int STATE_CONNECTING = 2; // now initiating an outgoing connection
56     public static final int STATE_CONNECTED = 3;  // now connected to a remote device
57
58     /**
59      * Constructor. Prepares a new BluetoothChat session.
60      * @param context  The UI Activity Context
61      * @param handler  A Handler to send messages back to the UI Activity
62      */
63     public BluetoothChatService(Context context, Handler handler) {
64         mAdapter = BluetoothAdapter.getDefaultAdapter();
65         mState = STATE_NONE;
66         mHandler = handler;
67     }
68
69     /**
70      * Set the current state of the chat connection
71      * @param state  An integer defining the current connection state
72      */
73     private synchronized void setState(int state) {
74         if (D) Log.d(TAG, "setState() " + mState + " -> " + state);
75         mState = state;
76
77         // Give the new state to the Handler so the UI Activity can update
78         mHandler.obtainMessage(AltosDroid.MESSAGE_STATE_CHANGE, state, -1).sendToTarget();
79     }
80
81     /**
82      * Return the current connection state. */
83     public synchronized int getState() {
84         return mState;
85     }
86
87     /**
88      * Start the chat service. Specifically start AcceptThread to begin a
89      * session in listening (server) mode. Called by the Activity onResume() */
90     public synchronized void start() {
91         if (D) Log.d(TAG, "start");
92
93         // Cancel any thread attempting to make a connection
94         if (mConnectThread != null) {mConnectThread.cancel(); mConnectThread = null;}
95
96         // Cancel any thread currently running a connection
97         if (mConnectedThread != null) {mConnectedThread.cancel(); mConnectedThread = null;}
98
99         setState(STATE_READY);
100
101     }
102
103     /**
104      * Start the ConnectThread to initiate a connection to a remote device.
105      * @param device  The BluetoothDevice to connect
106      * @param secure Socket Security type - Secure (true) , Insecure (false)
107      */
108     public synchronized void connect(BluetoothDevice device, boolean secure) {
109         if (D) Log.d(TAG, "connect to: " + device);
110
111         // Cancel any thread attempting to make a connection
112         if (mState == STATE_CONNECTING) {
113             if (mConnectThread != null) {mConnectThread.cancel(); mConnectThread = null;}
114         }
115
116         // Cancel any thread currently running a connection
117         if (mConnectedThread != null) {mConnectedThread.cancel(); mConnectedThread = null;}
118
119         // Start the thread to connect with the given device
120         mConnectThread = new ConnectThread(device, secure);
121         mConnectThread.start();
122         setState(STATE_CONNECTING);
123     }
124
125     /**
126      * Start the ConnectedThread to begin managing a Bluetooth connection
127      * @param socket  The BluetoothSocket on which the connection was made
128      * @param device  The BluetoothDevice that has been connected
129      */
130     public synchronized void connected(BluetoothSocket socket, BluetoothDevice
131             device, final String socketType) {
132         if (D) Log.d(TAG, "connected, Socket Type:" + socketType);
133
134         // Cancel the thread that completed the connection
135         if (mConnectThread != null) {mConnectThread.cancel(); mConnectThread = null;}
136
137         // Cancel any thread currently running a connection
138         if (mConnectedThread != null) {mConnectedThread.cancel(); mConnectedThread = null;}
139
140         // Start the thread to manage the connection and perform transmissions
141         mConnectedThread = new ConnectedThread(socket, socketType);
142         mConnectedThread.start();
143
144         // Send the name of the connected device back to the UI Activity
145         Message msg = mHandler.obtainMessage(AltosDroid.MESSAGE_DEVICE_NAME);
146         Bundle bundle = new Bundle();
147         bundle.putString(AltosDroid.DEVICE_NAME, device.getName());
148         msg.setData(bundle);
149         mHandler.sendMessage(msg);
150
151         setState(STATE_CONNECTED);
152     }
153
154     /**
155      * Stop all threads
156      */
157     public synchronized void stop() {
158         if (D) Log.d(TAG, "stop");
159
160         if (mConnectThread != null) {
161             mConnectThread.cancel();
162             mConnectThread = null;
163         }
164
165         if (mConnectedThread != null) {
166             mConnectedThread.cancel();
167             mConnectedThread = null;
168         }
169
170         setState(STATE_NONE);
171     }
172
173     /**
174      * Write to the ConnectedThread in an unsynchronized manner
175      * @param out The bytes to write
176      * @see ConnectedThread#write(byte[])
177      */
178     public void write(byte[] out) {
179         // Create temporary object
180         ConnectedThread r;
181         // Synchronize a copy of the ConnectedThread
182         synchronized (this) {
183             if (mState != STATE_CONNECTED) return;
184             r = mConnectedThread;
185         }
186         // Perform the write unsynchronized
187         r.write(out);
188     }
189
190     /**
191      * Indicate that the connection attempt failed and notify the UI Activity.
192      */
193     private void connectionFailed() {
194         // Send a failure message back to the Activity
195         Message msg = mHandler.obtainMessage(AltosDroid.MESSAGE_TOAST);
196         Bundle bundle = new Bundle();
197         bundle.putString(AltosDroid.TOAST, "Unable to connect device");
198         msg.setData(bundle);
199         mHandler.sendMessage(msg);
200
201         // Start the service over to restart listening mode
202         BluetoothChatService.this.start();
203     }
204
205     /**
206      * Indicate that the connection was lost and notify the UI Activity.
207      */
208     private void connectionLost() {
209         // Send a failure message back to the Activity
210         Message msg = mHandler.obtainMessage(AltosDroid.MESSAGE_TOAST);
211         Bundle bundle = new Bundle();
212         bundle.putString(AltosDroid.TOAST, "Device connection was lost");
213         msg.setData(bundle);
214         mHandler.sendMessage(msg);
215
216         // Start the service over to restart listening mode
217         BluetoothChatService.this.start();
218     }
219
220
221     /**
222      * This thread runs while attempting to make an outgoing connection
223      * with a device. It runs straight through; the connection either
224      * succeeds or fails.
225      */
226     private class ConnectThread extends Thread {
227         private final BluetoothSocket mmSocket;
228         private final BluetoothDevice mmDevice;
229         private String mSocketType;
230
231         public ConnectThread(BluetoothDevice device, boolean secure) {
232             mmDevice = device;
233             BluetoothSocket tmp = null;
234             mSocketType = secure ? "Secure" : "Insecure";
235
236             // Get a BluetoothSocket for a connection with the
237             // given BluetoothDevice
238             try {
239                 if (secure) {
240                         Method m = device.getClass().getMethod("createRfcommSocket", new Class[] {int.class});
241                         tmp = (BluetoothSocket) m.invoke(device, 2);
242 //                    tmp = device.createRfcommSocket(1);
243                 } else {
244                         Method m = device.getClass().getMethod("createInsecureRfcommSocket", new Class[] {int.class});
245                         tmp = (BluetoothSocket) m.invoke(device, 2);
246 //                    tmp = device.createInsecureRfcommSocket(1);
247                 }
248             } catch (Exception e) {
249                 Log.e(TAG, "Socket Type: " + mSocketType + "create() failed", e);
250                                 e.printStackTrace();
251                         }
252             mmSocket = tmp;
253         }
254
255         public void run() {
256             Log.i(TAG, "BEGIN mConnectThread SocketType:" + mSocketType);
257             setName("ConnectThread" + mSocketType);
258
259             // Always cancel discovery because it will slow down a connection
260             mAdapter.cancelDiscovery();
261
262             // Make a connection to the BluetoothSocket
263             try {
264                 // This is a blocking call and will only return on a
265                 // successful connection or an exception
266                 mmSocket.connect();
267             } catch (IOException e) {
268                 // Close the socket
269                 try {
270                     mmSocket.close();
271                 } catch (IOException e2) {
272                     Log.e(TAG, "unable to close() " + mSocketType +
273                             " socket during connection failure", e2);
274                 }
275                 connectionFailed();
276                 return;
277             }
278
279             // Reset the ConnectThread because we're done
280             synchronized (BluetoothChatService.this) {
281                 mConnectThread = null;
282             }
283
284             // Start the connected thread
285             connected(mmSocket, mmDevice, mSocketType);
286         }
287
288         public void cancel() {
289             try {
290                 mmSocket.close();
291             } catch (IOException e) {
292                 Log.e(TAG, "close() of connect " + mSocketType + " socket failed", e);
293             }
294         }
295     }
296
297     /**
298      * This thread runs during a connection with a remote device.
299      * It handles all incoming and outgoing transmissions.
300      */
301     private class ConnectedThread extends Thread {
302         private final BluetoothSocket mmSocket;
303         private final InputStream mmInStream;
304         private final OutputStream mmOutStream;
305
306         public ConnectedThread(BluetoothSocket socket, String socketType) {
307             Log.d(TAG, "create ConnectedThread: " + socketType);
308             mmSocket = socket;
309             InputStream tmpIn = null;
310             OutputStream tmpOut = null;
311
312             // Get the BluetoothSocket input and output streams
313             try {
314                 tmpIn = socket.getInputStream();
315                 tmpOut = socket.getOutputStream();
316             } catch (IOException e) {
317                 Log.e(TAG, "temp sockets not created", e);
318             }
319
320             mmInStream = tmpIn;
321             mmOutStream = tmpOut;
322         }
323
324         public void run() {
325             Log.i(TAG, "BEGIN mConnectedThread");
326             byte[] buffer = new byte[1024];
327             int bytes;
328
329             // Keep listening to the InputStream while connected
330             while (true) {
331                 try {
332                     // Read from the InputStream
333                     bytes = mmInStream.read(buffer);
334
335                     // Send the obtained bytes to the UI Activity
336                     mHandler.obtainMessage(AltosDroid.MESSAGE_READ, bytes, -1, buffer)
337                             .sendToTarget();
338                 } catch (IOException e) {
339                     Log.e(TAG, "disconnected", e);
340                     connectionLost();
341                     break;
342                 }
343             }
344         }
345
346         /**
347          * Write to the connected OutStream.
348          * @param buffer  The bytes to write
349          */
350         public void write(byte[] buffer) {
351             try {
352                 mmOutStream.write(buffer);
353
354                 // Share the sent message back to the UI Activity
355                 mHandler.obtainMessage(AltosDroid.MESSAGE_WRITE, -1, -1, buffer)
356                         .sendToTarget();
357             } catch (IOException e) {
358                 Log.e(TAG, "Exception during write", e);
359             }
360         }
361
362         public void cancel() {
363             try {
364                 mmSocket.close();
365             } catch (IOException e) {
366                 Log.e(TAG, "close() of connect socket failed", e);
367             }
368         }
369     }
370 }