406 lines
		
	
	
	
		
			13 KiB
		
	
	
	
		
			Java
		
	
	
	
	
	
			
		
		
	
	
			406 lines
		
	
	
	
		
			13 KiB
		
	
	
	
		
			Java
		
	
	
	
	
	
| /*
 | |
|  * Copyright (C) 2014 The Android Open Source Project
 | |
|  *
 | |
|  * Licensed under the Apache License, Version 2.0 (the "License");
 | |
|  * you may not use this file except in compliance with the License.
 | |
|  * You may obtain a copy of the License at
 | |
|  *
 | |
|  *      http://www.apache.org/licenses/LICENSE-2.0
 | |
|  *
 | |
|  * Unless required by applicable law or agreed to in writing, software
 | |
|  * distributed under the License is distributed on an "AS IS" BASIS,
 | |
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | |
|  * See the License for the specific language governing permissions and
 | |
|  * limitations under the License.
 | |
|  */
 | |
| 
 | |
| package org.surfsite.iconsole;
 | |
| 
 | |
| import android.bluetooth.BluetoothAdapter;
 | |
| import android.bluetooth.BluetoothDevice;
 | |
| import android.bluetooth.BluetoothSocket;
 | |
| import android.content.Context;
 | |
| import android.os.Bundle;
 | |
| import android.os.Handler;
 | |
| import android.os.Message;
 | |
| 
 | |
| import org.surfsite.iconsole.common.logger.Log;
 | |
| 
 | |
| import java.io.IOException;
 | |
| import java.io.InputStream;
 | |
| import java.io.OutputStream;
 | |
| import java.util.Arrays;
 | |
| import java.util.UUID;
 | |
| 
 | |
| /**
 | |
|  * This class does all the work for setting up and managing Bluetooth
 | |
|  * connections with other devices. It has a thread that listens for
 | |
|  * incoming connections, a thread for connecting with a device, and a
 | |
|  * thread for performing data transmissions when connected.
 | |
|  */
 | |
| public class BluetoothChatService {
 | |
|     // Debugging
 | |
|     private static final String TAG = "BluetoothChatService";
 | |
|     // Name for the SDP record when creating server socket
 | |
|     private static final String NAME_SECURE = "BluetoothChatSecure";
 | |
|     private static final String NAME_INSECURE = "BluetoothChatInsecure";
 | |
| 
 | |
|     // Unique UUID for this application
 | |
|     private static final UUID SERIAL_PORT_CLASS = UUID.fromString("00001101-0000-1000-8000-00805f9b34fb");
 | |
|     // Member fields
 | |
|     private final BluetoothAdapter mAdapter;
 | |
|     private final Handler mHandler;
 | |
| 
 | |
|     private ConnectThread mConnectThread;
 | |
|     private ConnectedThread mConnectedThread;
 | |
|     private int mState;
 | |
|     private int mNewState;
 | |
| 
 | |
|     // Constants that indicate the current connection state
 | |
|     public static final int STATE_NONE = 0;       // we're doing nothing
 | |
|     public static final int STATE_LISTEN = 1;     // now listening for incoming connections
 | |
|     public static final int STATE_CONNECTING = 2; // now initiating an outgoing connection
 | |
|     public static final int STATE_CONNECTED = 3;  // now connected to a remote device
 | |
| 
 | |
|     /**
 | |
|      * Constructor. Prepares a new BluetoothChat session.
 | |
|      *
 | |
|      * @param context The UI Activity Context
 | |
|      * @param handler A Handler to send messages back to the UI Activity
 | |
|      */
 | |
|     public BluetoothChatService(Context context, Handler handler) {
 | |
|         mAdapter = BluetoothAdapter.getDefaultAdapter();
 | |
|         mState = STATE_NONE;
 | |
|         mNewState = mState;
 | |
|         mHandler = handler;
 | |
|     }
 | |
| 
 | |
| 
 | |
|     /**
 | |
|      * Update UI title according to the current state of the chat connection
 | |
|      */
 | |
|     private synchronized void updateUserInterfaceTitle() {
 | |
|         mState = getState();
 | |
|         Log.d(TAG, "updateUserInterfaceTitle() " + mNewState + " -> " + mState);
 | |
|         mNewState = mState;
 | |
| 
 | |
|         // Give the new state to the Handler so the UI Activity can update
 | |
|         mHandler.obtainMessage(Constants.MESSAGE_STATE_CHANGE, mNewState, -1).sendToTarget();
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Return the current connection state.
 | |
|      */
 | |
|     public synchronized int getState() {
 | |
|         return mState;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Start the chat service. Specifically start AcceptThread to begin a
 | |
|      * session in listening (server) mode. Called by the Activity onResume()
 | |
|      */
 | |
|     public synchronized void start() {
 | |
|         Log.d(TAG, "start");
 | |
| 
 | |
|         // Cancel any thread attempting to make a connection
 | |
|         if (mConnectThread != null) {
 | |
|             mConnectThread.cancel();
 | |
|             mConnectThread = null;
 | |
|         }
 | |
| 
 | |
|         // Cancel any thread currently running a connection
 | |
|         if (mConnectedThread != null) {
 | |
|             mConnectedThread.cancel();
 | |
|             mConnectedThread = null;
 | |
|         }
 | |
|         // Update UI title
 | |
|         updateUserInterfaceTitle();
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Start the ConnectThread to initiate a connection to a remote device.
 | |
|      *
 | |
|      * @param device The BluetoothDevice to connect
 | |
|      */
 | |
|     public synchronized void connect(BluetoothDevice device) {
 | |
|         Log.d(TAG, "connect to: " + device);
 | |
| 
 | |
|         // Cancel any thread attempting to make a connection
 | |
|         if (mState == STATE_CONNECTING) {
 | |
|             if (mConnectThread != null) {
 | |
|                 mConnectThread.cancel();
 | |
|                 mConnectThread = null;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         // Cancel any thread currently running a connection
 | |
|         if (mConnectedThread != null) {
 | |
|             mConnectedThread.cancel();
 | |
|             mConnectedThread = null;
 | |
|         }
 | |
| 
 | |
|         // Start the thread to connect with the given device
 | |
|         mConnectThread = new ConnectThread(device);
 | |
|         mConnectThread.start();
 | |
|         // Update UI title
 | |
|         updateUserInterfaceTitle();
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Start the ConnectedThread to begin managing a Bluetooth connection
 | |
|      *
 | |
|      * @param socket The BluetoothSocket on which the connection was made
 | |
|      * @param device The BluetoothDevice that has been connected
 | |
|      */
 | |
|     public synchronized void connected(BluetoothSocket socket, BluetoothDevice
 | |
|             device) {
 | |
|         Log.d(TAG, "connected");
 | |
| 
 | |
|         // Cancel the thread that completed the connection
 | |
|         if (mConnectThread != null) {
 | |
|             mConnectThread.cancel();
 | |
|             mConnectThread = null;
 | |
|         }
 | |
| 
 | |
|         // Cancel any thread currently running a connection
 | |
|         if (mConnectedThread != null) {
 | |
|             mConnectedThread.cancel();
 | |
|             mConnectedThread = null;
 | |
|         }
 | |
| 
 | |
|         // Start the thread to manage the connection and perform transmissions
 | |
|         mConnectedThread = new ConnectedThread(socket);
 | |
|         mConnectedThread.start();
 | |
| 
 | |
|         // Send the name of the connected device back to the UI Activity
 | |
|         Message msg = mHandler.obtainMessage(Constants.MESSAGE_DEVICE_NAME);
 | |
|         Bundle bundle = new Bundle();
 | |
|         bundle.putString(Constants.DEVICE_NAME, device.getName());
 | |
|         msg.setData(bundle);
 | |
|         mHandler.sendMessage(msg);
 | |
|         // Update UI title
 | |
|         updateUserInterfaceTitle();
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Stop all threads
 | |
|      */
 | |
|     public synchronized void stop() {
 | |
|         Log.d(TAG, "stop");
 | |
| 
 | |
|         if (mConnectThread != null) {
 | |
|             mConnectThread.cancel();
 | |
|             mConnectThread = null;
 | |
|         }
 | |
| 
 | |
|         if (mConnectedThread != null) {
 | |
|             mConnectedThread.cancel();
 | |
|             mConnectedThread = null;
 | |
|         }
 | |
| 
 | |
|         mState = STATE_NONE;
 | |
|         // Update UI title
 | |
|         updateUserInterfaceTitle();
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Write to the ConnectedThread in an unsynchronized manner
 | |
|      *
 | |
|      * @param out The bytes to write
 | |
|      * @see ConnectedThread#write(byte[])
 | |
|      */
 | |
|     public void write(byte[] out) {
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Indicate that the connection attempt failed and notify the UI Activity.
 | |
|      */
 | |
|     private void connectionFailed() {
 | |
|         // Send a failure message back to the Activity
 | |
|         Message msg = mHandler.obtainMessage(Constants.MESSAGE_TOAST);
 | |
|         Bundle bundle = new Bundle();
 | |
|         bundle.putString(Constants.TOAST, "Unable to connect device");
 | |
|         msg.setData(bundle);
 | |
|         mHandler.sendMessage(msg);
 | |
| 
 | |
|         mState = STATE_NONE;
 | |
|         // Update UI title
 | |
|         updateUserInterfaceTitle();
 | |
| 
 | |
|         // Start the service over to restart listening mode
 | |
|         BluetoothChatService.this.start();
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Indicate that the connection was lost and notify the UI Activity.
 | |
|      */
 | |
|     private void connectionLost() {
 | |
|         // Send a failure message back to the Activity
 | |
|         Message msg = mHandler.obtainMessage(Constants.MESSAGE_TOAST);
 | |
|         Bundle bundle = new Bundle();
 | |
|         bundle.putString(Constants.TOAST, "Device connection was lost");
 | |
|         msg.setData(bundle);
 | |
|         mHandler.sendMessage(msg);
 | |
| 
 | |
|         mState = STATE_NONE;
 | |
|         // Update UI title
 | |
|         updateUserInterfaceTitle();
 | |
| 
 | |
|         // Start the service over to restart listening mode
 | |
|         BluetoothChatService.this.start();
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * This thread runs while attempting to make an outgoing connection
 | |
|      * with a device. It runs straight through; the connection either
 | |
|      * succeeds or fails.
 | |
|      */
 | |
|     private class ConnectThread extends Thread {
 | |
|         private final BluetoothSocket mmSocket;
 | |
|         private final BluetoothDevice mmDevice;
 | |
| 
 | |
|         public ConnectThread(BluetoothDevice device) {
 | |
|             mmDevice = device;
 | |
|             BluetoothSocket tmp = null;
 | |
| 
 | |
|             // Get a BluetoothSocket for a connection with the
 | |
|             // given BluetoothDevice
 | |
|             try {
 | |
|                 tmp = device.createRfcommSocketToServiceRecord(SERIAL_PORT_CLASS);
 | |
|             } catch (IOException e) {
 | |
|                 Log.e(TAG, "Socket Type: create() failed", e);
 | |
|             }
 | |
|             mmSocket = tmp;
 | |
|             mState = STATE_CONNECTING;
 | |
|         }
 | |
| 
 | |
|         public void run() {
 | |
|             Log.i(TAG, "BEGIN mConnectThread");
 | |
|             setName("ConnectThread");
 | |
| 
 | |
|             // Always cancel discovery because it will slow down a connection
 | |
|             mAdapter.cancelDiscovery();
 | |
| 
 | |
|             // Make a connection to the BluetoothSocket
 | |
|             try {
 | |
|                 // This is a blocking call and will only return on a
 | |
|                 // successful connection or an exception
 | |
|                 mmSocket.connect();
 | |
|             } catch (IOException e) {
 | |
|                 // Close the socket
 | |
|                 try {
 | |
|                     mmSocket.close();
 | |
|                 } catch (IOException e2) {
 | |
|                     Log.e(TAG, "unable to close() socket during connection failure", e2);
 | |
|                 }
 | |
|                 connectionFailed();
 | |
|                 return;
 | |
|             }
 | |
| 
 | |
|             // Reset the ConnectThread because we're done
 | |
|             synchronized (BluetoothChatService.this) {
 | |
|                 mConnectThread = null;
 | |
|             }
 | |
| 
 | |
|             // Start the connected thread
 | |
|             connected(mmSocket, mmDevice);
 | |
|         }
 | |
| 
 | |
|         public void cancel() {
 | |
|             try {
 | |
|                 mmSocket.close();
 | |
|             } catch (IOException e) {
 | |
|                 Log.e(TAG, "close() of socket failed", e);
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * This thread runs during a connection with a remote device.
 | |
|      * It handles all incoming and outgoing transmissions.
 | |
|      */
 | |
|     private class ConnectedThread extends Thread {
 | |
|         private final BluetoothSocket mmSocket;
 | |
|         private final InputStream mmInStream;
 | |
|         private final OutputStream mmOutStream;
 | |
|         private final IConsole mmIConsole;
 | |
| 
 | |
|         public ConnectedThread(BluetoothSocket socket) {
 | |
|             Log.d(TAG, "create ConnectedThread");
 | |
|             mmSocket = socket;
 | |
|             InputStream tmpIn = null;
 | |
|             OutputStream tmpOut = null;
 | |
| 
 | |
|             // Get the BluetoothSocket input and output streams
 | |
|             try {
 | |
|                 tmpIn = socket.getInputStream();
 | |
|                 tmpOut = socket.getOutputStream();
 | |
|             } catch (IOException e) {
 | |
|                 Log.e(TAG, "temp sockets not created", e);
 | |
|             }
 | |
| 
 | |
|             mmInStream = tmpIn;
 | |
|             mmOutStream = tmpOut;
 | |
|             mState = STATE_CONNECTED;
 | |
| 
 | |
|             mmIConsole = new IConsole(mmInStream, mmOutStream, new IConsole.DataListener() {
 | |
|                 @Override
 | |
|                 public void onData(IConsole.Data data) {
 | |
|                     Log.i(TAG, "mConnectedThread: " + data.toString());
 | |
|                     /* print */
 | |
|                 }
 | |
| 
 | |
|                 @Override
 | |
|                 public void onError(Exception e) {
 | |
|                     Log.e(TAG, "mConnectedThread Error: ", e);
 | |
| 
 | |
|                     if (e instanceof IOException)
 | |
|                         connectionLost();
 | |
|                 }
 | |
|             }, new IConsole.DebugListener() {
 | |
|                 @Override
 | |
|                 public void onRead(byte[] buffer) {
 | |
|                     if (buffer.length > 0) {
 | |
|                         String hexbuf = IConsole.byteArrayToHex(Arrays.copyOfRange(buffer, 0, buffer.length)) + '\n';
 | |
| 
 | |
|                         // Send the obtained bytes to the UI Activity
 | |
|                         mHandler.obtainMessage(Constants.MESSAGE_READ, hexbuf.length(), -1, hexbuf.getBytes())
 | |
|                                 .sendToTarget();
 | |
|                     }
 | |
|                 }
 | |
| 
 | |
|                 @Override
 | |
|                 public void onWrite(byte[] buffer) {
 | |
|                     String hexbuf = IConsole.byteArrayToHex(buffer) + '\n';
 | |
| 
 | |
|                     // Share the sent message back to the UI Activity
 | |
|                     mHandler.obtainMessage(Constants.MESSAGE_WRITE, -1, -1, hexbuf.getBytes())
 | |
|                             .sendToTarget();
 | |
|                 }
 | |
|             });
 | |
|         }
 | |
| 
 | |
|         public void run() {
 | |
|             Log.i(TAG, "BEGIN mConnectedThread");
 | |
| 
 | |
|             while (mState == STATE_CONNECTED) {
 | |
|                 if (!mmIConsole.processIO())
 | |
|                     break;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         public boolean setLevel(int level) {
 | |
|             return mmIConsole.setLevel(level);
 | |
|         }
 | |
| 
 | |
|         public void cancel() {
 | |
|             mmIConsole.stop();
 | |
| 
 | |
|             try {
 | |
|                 mmSocket.close();
 | |
|             } catch (IOException e) {
 | |
|                 Log.e(TAG, "close() of connect socket failed", e);
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| }
 |