03edeb93df0378dda8f2e05b652879b0007c42aa
[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                 Log.i(TAG, "Connect starting");
240                 if (secure) {
241                         Method m = device.getClass().getMethod("createRfcommSocket", new Class[] {int.class});
242                         tmp = (BluetoothSocket) m.invoke(device, 1);
243 //                    tmp = device.createRfcommSocket(1);
244                 } else {
245                         Method m = device.getClass().getMethod("createInsecureRfcommSocket", new Class[] {int.class});
246                         tmp = (BluetoothSocket) m.invoke(device, 1);
247 //                    tmp = device.createInsecureRfcommSocket(1);
248                 }
249                 Log.i(TAG, "Connect succeeded");
250             } catch (Exception e) {
251                 Log.e(TAG, "Socket Type: " + mSocketType + "create() failed", e);
252                                 e.printStackTrace();
253                         }
254             mmSocket = tmp;
255         }
256
257         public void run() {
258             Log.i(TAG, "BEGIN ya y a mConnectThread SocketType:" + mSocketType);
259             setName("ConnectThread" + mSocketType);
260
261             // Always cancel discovery because it will slow down a connection
262 //            mAdapter.cancelDiscovery();
263
264             // Make a connection to the BluetoothSocket
265             try {
266                 // This is a blocking call and will only return on a
267                 // successful connection or an exception
268                 Log.i(TAG, "CONNECT SocketType:" + mSocketType);
269                 mmSocket.connect();
270             } catch (IOException e) {
271                 // Close the socket
272                 Log.e(TAG, "Connect failed", e);
273                 try {
274                     mmSocket.close();
275                 } catch (IOException e2) {
276                     Log.e(TAG, "unable to close() " + mSocketType +
277                             " socket during connection failure", e2);
278                 }
279                 connectionFailed();
280                 return;
281             }
282
283             // Reset the ConnectThread because we're done
284             synchronized (BluetoothChatService.this) {
285                 mConnectThread = null;
286             }
287
288             // Start the connected thread
289             connected(mmSocket, mmDevice, mSocketType);
290         }
291
292         public void cancel() {
293             try {
294                 mmSocket.close();
295             } catch (IOException e) {
296                 Log.e(TAG, "close() of connect " + mSocketType + " socket failed", e);
297             }
298         }
299     }
300
301     /**
302      * This thread runs during a connection with a remote device.
303      * It handles all incoming and outgoing transmissions.
304      */
305     private class ConnectedThread extends Thread {
306         private final BluetoothSocket mmSocket;
307         private final InputStream mmInStream;
308         private final OutputStream mmOutStream;
309
310         public ConnectedThread(BluetoothSocket socket, String socketType) {
311             Log.d(TAG, "create ConnectedThread: " + socketType);
312             mmSocket = socket;
313             InputStream tmpIn = null;
314             OutputStream tmpOut = null;
315
316             // Get the BluetoothSocket input and output streams
317             try {
318                 tmpIn = socket.getInputStream();
319                 tmpOut = socket.getOutputStream();
320             } catch (IOException e) {
321                 Log.e(TAG, "temp sockets not created", e);
322             }
323
324             mmInStream = tmpIn;
325             mmOutStream = tmpOut;
326         }
327
328         public void run() {
329             Log.i(TAG, "BEGIN mConnectedThread");
330             byte[] buffer = new byte[1024];
331             int bytes;
332
333             // Keep listening to the InputStream while connected
334             while (true) {
335                 try {
336                     // Read from the InputStream
337                     bytes = mmInStream.read(buffer);
338
339                     if (bytes > 0) {
340                         Log.i(TAG, "Recv: " + new String(buffer, 0, bytes));
341                         // Send the obtained bytes to the UI Activity
342                         mHandler.obtainMessage(AltosDroid.MESSAGE_READ, bytes, -1, buffer)
343                             .sendToTarget();
344                     }
345                 } catch (IOException e) {
346                     Log.e(TAG, "disconnected", e);
347                     connectionLost();
348                     break;
349                 }
350             }
351         }
352
353         /**
354          * Write to the connected OutStream.
355          * @param buffer  The bytes to write
356          */
357         public void write(byte[] buffer) {
358             try {
359                 mmOutStream.write(buffer);
360
361                 // Share the sent message back to the UI Activity
362                 mHandler.obtainMessage(AltosDroid.MESSAGE_WRITE, -1, -1, buffer)
363                         .sendToTarget();
364             } catch (IOException e) {
365                 Log.e(TAG, "Exception during write", e);
366             }
367         }
368
369         public void cancel() {
370             try {
371                 mmSocket.close();
372             } catch (IOException e) {
373                 Log.e(TAG, "close() of connect socket failed", e);
374             }
375         }
376     }
377 }