Android 蓝牙聊天

jopen 8年前

BluetoothChat.java

/*   * Copyright (C) 2009 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 com.example.android.BluetoothChat;    import android.app.Activity;  import android.bluetooth.BluetoothAdapter;  import android.bluetooth.BluetoothDevice;  import android.content.Intent;  import android.os.Bundle;  import android.os.Handler;  import android.os.Message;  import android.util.Log;  import android.view.KeyEvent;  import android.view.Menu;  import android.view.MenuInflater;  import android.view.MenuItem;  import android.view.View;  import android.view.Window;  import android.view.View.OnClickListener;  import android.view.inputmethod.EditorInfo;  import android.widget.ArrayAdapter;  import android.widget.Button;  import android.widget.EditText;  import android.widget.ListView;  import android.widget.TextView;  import android.widget.Toast;    /**   * This is the main Activity that displays the current chat session.   */  public class BluetoothChat extends Activity {      // Debugging      private static final String TAG = "BluetoothChat";      private static final boolean D = true;        // Message types sent from the BluetoothChatService Handler      public static final int MESSAGE_STATE_CHANGE = 1;      public static final int MESSAGE_READ = 2;      public static final int MESSAGE_WRITE = 3;      public static final int MESSAGE_DEVICE_NAME = 4;      public static final int MESSAGE_TOAST = 5;        // Key names received from the BluetoothChatService Handler      public static final String DEVICE_NAME = "device_name";      public static final String TOAST = "toast";        // Intent request codes      private static final int REQUEST_CONNECT_DEVICE = 1;      private static final int REQUEST_ENABLE_BT = 2;        // Layout Views      private TextView mTitle;      private ListView mConversationView;      private EditText mOutEditText;      private Button mSendButton;        // Name of the connected device      private String mConnectedDeviceName = null;      // Array adapter for the conversation thread      private ArrayAdapter<String> mConversationArrayAdapter;      // String buffer for outgoing messages      private StringBuffer mOutStringBuffer;      // Local Bluetooth adapter      private BluetoothAdapter mBluetoothAdapter = null;      // Member object for the chat services      private BluetoothChatService mChatService = null;        @Override      public void onCreate(Bundle savedInstanceState) {          super.onCreate(savedInstanceState);          if (D)              Log.e(TAG, "+++ ON CREATE +++");            // Set up the window layout          requestWindowFeature(Window.FEATURE_CUSTOM_TITLE);          setContentView(R.layout.main);          getWindow().setFeatureInt(Window.FEATURE_CUSTOM_TITLE,                  R.layout.custom_title);            // Set up the custom title          mTitle = (TextView) findViewById(R.id.title_left_text);          mTitle.setText(R.string.app_name);          mTitle = (TextView) findViewById(R.id.title_right_text);            // Get local Bluetooth adapter          mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();            // If the adapter is null, then Bluetooth is not supported          if (mBluetoothAdapter == null) {              Toast.makeText(this, "Bluetooth is not available",                      Toast.LENGTH_LONG).show();              finish();              return;          }      }        @Override      public void onStart() {          super.onStart();          if (D)              Log.e(TAG, "++ ON START ++");            // If BT is not on, request that it be enabled.          // setupChat() will then be called during onActivityResult          if (!mBluetoothAdapter.isEnabled()) {              Intent enableIntent = new Intent(                      BluetoothAdapter.ACTION_REQUEST_ENABLE);              startActivityForResult(enableIntent, REQUEST_ENABLE_BT);              // Otherwise, setup the chat session          } else {              if (mChatService == null)                  setupChat();          }      }        @Override      public synchronized void onResume() {          super.onResume();          if (D)              Log.e(TAG, "+ ON RESUME +");            // Performing this check in onResume() covers the case in which BT was          // not enabled during onStart(), so we were paused to enable it...          // onResume() will be called when ACTION_REQUEST_ENABLE activity          // returns.          if (mChatService != null) {              // Only if the state is STATE_NONE, do we know that we haven't              // started already              if (mChatService.getState() == BluetoothChatService.STATE_NONE) {                  // Start the Bluetooth chat services                  mChatService.start();              }          }      }        private void setupChat() {          Log.d(TAG, "setupChat()");            // Initialize the array adapter for the conversation thread          mConversationArrayAdapter = new ArrayAdapter<String>(this,                  R.layout.message);          mConversationView = (ListView) findViewById(R.id.in);          mConversationView.setAdapter(mConversationArrayAdapter);            // Initialize the compose field with a listener for the return key          mOutEditText = (EditText) findViewById(R.id.edit_text_out);          mOutEditText.setOnEditorActionListener(mWriteListener);            // Initialize the send button with a listener that for click events          mSendButton = (Button) findViewById(R.id.button_send);          mSendButton.setOnClickListener(new OnClickListener() {              public void onClick(View v) {                  // Send a message using content of the edit text widget                  TextView view = (TextView) findViewById(R.id.edit_text_out);                  String message = view.getText().toString();                  sendMessage(message);              }          });            // Initialize the BluetoothChatService to perform bluetooth connections          mChatService = new BluetoothChatService(this, mHandler);            // Initialize the buffer for outgoing messages          mOutStringBuffer = new StringBuffer("");      }        @Override      public synchronized void onPause() {          super.onPause();          if (D)              Log.e(TAG, "- ON PAUSE -");      }        @Override      public void onStop() {          super.onStop();          if (D)              Log.e(TAG, "-- ON STOP --");      }        @Override      public void onDestroy() {          super.onDestroy();          // Stop the Bluetooth chat services          if (mChatService != null)              mChatService.stop();          if (D)              Log.e(TAG, "--- ON DESTROY ---");      }        private void ensureDiscoverable() {          if (D)              Log.d(TAG, "ensure discoverable");          if (mBluetoothAdapter.getScanMode() != BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE) {              Intent discoverableIntent = new Intent(                      BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);              discoverableIntent.putExtra(                      BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 300);              startActivity(discoverableIntent);          }      }        /**       * Sends a message.       *        * @param message       *            A string of text to send.       */      private void sendMessage(String message) {          // Check that we're actually connected before trying anything          if (mChatService.getState() != BluetoothChatService.STATE_CONNECTED) {              Toast.makeText(this, R.string.not_connected, Toast.LENGTH_SHORT)                      .show();              return;          }            // Check that there's actually something to send          if (message.length() > 0) {              // Get the message bytes and tell the BluetoothChatService to write              byte[] send = message.getBytes();              mChatService.write(send);                // Reset out string buffer to zero and clear the edit text field              mOutStringBuffer.setLength(0);              mOutEditText.setText(mOutStringBuffer);          }      }        // The action listener for the EditText widget, to listen for the return key      private TextView.OnEditorActionListener mWriteListener = new TextView.OnEditorActionListener() {          public boolean onEditorAction(TextView view, int actionId,                  KeyEvent event) {              // If the action is a key-up event on the return key, send the              // message              if (actionId == EditorInfo.IME_NULL                      && event.getAction() == KeyEvent.ACTION_UP) {                  String message = view.getText().toString();                  sendMessage(message);              }              if (D)                  Log.i(TAG, "END onEditorAction");              return true;          }      };        // The Handler that gets information back from the BluetoothChatService      private final Handler mHandler = new Handler() {          @Override          public void handleMessage(Message msg) {              switch (msg.what) {              case MESSAGE_STATE_CHANGE:                  if (D)                      Log.i(TAG, "MESSAGE_STATE_CHANGE: " + msg.arg1);                  switch (msg.arg1) {                  case BluetoothChatService.STATE_CONNECTED:                      mTitle.setText(R.string.title_connected_to);                      mTitle.append(mConnectedDeviceName);                      mConversationArrayAdapter.clear();                      break;                  case BluetoothChatService.STATE_CONNECTING:                      mTitle.setText(R.string.title_connecting);                      break;                  case BluetoothChatService.STATE_LISTEN:                  case BluetoothChatService.STATE_NONE:                      mTitle.setText(R.string.title_not_connected);                      break;                  }                  break;              case MESSAGE_WRITE:                  byte[] writeBuf = (byte[]) msg.obj;                  // construct a string from the buffer                  String writeMessage = new String(writeBuf);                  mConversationArrayAdapter.add("Me:  " + writeMessage);                  break;              case MESSAGE_READ:                  byte[] readBuf = (byte[]) msg.obj;                  // construct a string from the valid bytes in the buffer                  String readMessage = new String(readBuf, 0, msg.arg1);                  mConversationArrayAdapter.add(mConnectedDeviceName + ":  "                          + readMessage);                  break;              case MESSAGE_DEVICE_NAME:                  // save the connected device's name                  mConnectedDeviceName = msg.getData().getString(DEVICE_NAME);                  Toast.makeText(getApplicationContext(),                          "Connected to " + mConnectedDeviceName,                          Toast.LENGTH_SHORT).show();                  break;              case MESSAGE_TOAST:                  Toast.makeText(getApplicationContext(),                          msg.getData().getString(TOAST), Toast.LENGTH_SHORT)                          .show();                  break;              }          }      };        public void onActivityResult(int requestCode, int resultCode, Intent data) {          if (D)              Log.d(TAG, "onActivityResult " + resultCode);          switch (requestCode) {          case REQUEST_CONNECT_DEVICE:              // When DeviceListActivity returns with a device to connect              if (resultCode == Activity.RESULT_OK) {                  // Get the device MAC address                  String address = data.getExtras().getString(                          DeviceListActivity.EXTRA_DEVICE_ADDRESS);                  // Get the BLuetoothDevice object                  BluetoothDevice device = mBluetoothAdapter                          .getRemoteDevice(address);                  // Attempt to connect to the device                  mChatService.connect(device);              }              break;          case REQUEST_ENABLE_BT:              // When the request to enable Bluetooth returns              if (resultCode == Activity.RESULT_OK) {                  // Bluetooth is now enabled, so set up a chat session                  setupChat();              } else {                  // User did not enable Bluetooth or an error occured                  Log.d(TAG, "BT not enabled");                  Toast.makeText(this, R.string.bt_not_enabled_leaving,                          Toast.LENGTH_SHORT).show();                  finish();              }          }      }        @Override      public boolean onCreateOptionsMenu(Menu menu) {          MenuInflater inflater = getMenuInflater();          inflater.inflate(R.menu.option_menu, menu);          return true;      }        @Override      public boolean onOptionsItemSelected(MenuItem item) {          switch (item.getItemId()) {          case R.id.scan:              // Launch the DeviceListActivity to see devices and do scan              Intent serverIntent = new Intent(this, DeviceListActivity.class);              startActivityForResult(serverIntent, REQUEST_CONNECT_DEVICE);              return true;          case R.id.discoverable:              // Ensure this device is discoverable by others              ensureDiscoverable();              return true;          }          return false;      }    }
BluetoothChatService.java
/*   * Copyright (C) 2009 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 com.example.android.BluetoothChat;    import java.io.IOException;  import java.io.InputStream;  import java.io.OutputStream;  import java.util.UUID;    import android.bluetooth.BluetoothAdapter;  import android.bluetooth.BluetoothDevice;  import android.bluetooth.BluetoothServerSocket;  import android.bluetooth.BluetoothSocket;  import android.content.Context;  import android.os.Bundle;  import android.os.Handler;  import android.os.Message;  import android.util.Log;    /**   * 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";      private static final boolean D = true;        // Name for the SDP record when creating server socket      private static final String NAME = "BluetoothChat";        // Unique UUID for this application      private static final UUID MY_UUID = UUID              .fromString("fa87c0d0-afac-11de-8a39-0800200c9a66");        // Member fields      private final BluetoothAdapter mAdapter;      private final Handler mHandler;      private AcceptThread mAcceptThread;      private ConnectThread mConnectThread;      private ConnectedThread mConnectedThread;      private int mState;        // 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;          mHandler = handler;      }        /**       * Set the current state of the chat connection       *        * @param state       *            An integer defining the current connection state       */      private synchronized void setState(int state) {          if (D)              Log.d(TAG, "setState() " + mState + " -> " + state);          mState = state;            // Give the new state to the Handler so the UI Activity can update          mHandler.obtainMessage(BluetoothChat.MESSAGE_STATE_CHANGE, state, -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() {          if (D)              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;          }            // Start the thread to listen on a BluetoothServerSocket          if (mAcceptThread == null) {              mAcceptThread = new AcceptThread();              mAcceptThread.start();          }          setState(STATE_LISTEN);      }        /**       * Start the ConnectThread to initiate a connection to a remote device.       *        * @param device       *            The BluetoothDevice to connect       */      public synchronized void connect(BluetoothDevice device) {          if (D)              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();          setState(STATE_CONNECTING);      }        /**       * 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) {          if (D)              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;          }            // Cancel the accept thread because we only want to connect to one          // device          if (mAcceptThread != null) {              mAcceptThread.cancel();              mAcceptThread = 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(BluetoothChat.MESSAGE_DEVICE_NAME);          Bundle bundle = new Bundle();          bundle.putString(BluetoothChat.DEVICE_NAME, device.getName());          msg.setData(bundle);          mHandler.sendMessage(msg);            setState(STATE_CONNECTED);      }        /**       * Stop all threads       */      public synchronized void stop() {          if (D)              Log.d(TAG, "stop");          if (mConnectThread != null) {              mConnectThread.cancel();              mConnectThread = null;          }          if (mConnectedThread != null) {              mConnectedThread.cancel();              mConnectedThread = null;          }          if (mAcceptThread != null) {              mAcceptThread.cancel();              mAcceptThread = null;          }          setState(STATE_NONE);      }        /**       * Write to the ConnectedThread in an unsynchronized manner       *        * @param out       *            The bytes to write       * @see ConnectedThread#write(byte[])       */      public void write(byte[] out) {          // Create temporary object          ConnectedThread r;          // Synchronize a copy of the ConnectedThread          synchronized (this) {              if (mState != STATE_CONNECTED)                  return;              r = mConnectedThread;          }          // Perform the write unsynchronized          r.write(out);      }        /**       * Indicate that the connection attempt failed and notify the UI Activity.       */      private void connectionFailed() {          setState(STATE_LISTEN);            // Send a failure message back to the Activity          Message msg = mHandler.obtainMessage(BluetoothChat.MESSAGE_TOAST);          Bundle bundle = new Bundle();          bundle.putString(BluetoothChat.TOAST, "Unable to connect device");          msg.setData(bundle);          mHandler.sendMessage(msg);      }        /**       * Indicate that the connection was lost and notify the UI Activity.       */      private void connectionLost() {          setState(STATE_LISTEN);            // Send a failure message back to the Activity          Message msg = mHandler.obtainMessage(BluetoothChat.MESSAGE_TOAST);          Bundle bundle = new Bundle();          bundle.putString(BluetoothChat.TOAST, "Device connection was lost");          msg.setData(bundle);          mHandler.sendMessage(msg);      }        /**       * This thread runs while listening for incoming connections. It behaves       * like a server-side client. It runs until a connection is accepted (or       * until cancelled).       */      private class AcceptThread extends Thread {          // The local server socket          private final BluetoothServerSocket mmServerSocket;            public AcceptThread() {              BluetoothServerSocket tmp = null;                // Create a new listening server socket              try {                  tmp = mAdapter                          .listenUsingRfcommWithServiceRecord(NAME, MY_UUID);              } catch (IOException e) {                  Log.e(TAG, "listen() failed", e);              }              mmServerSocket = tmp;          }            public void run() {              if (D)                  Log.d(TAG, "BEGIN mAcceptThread" + this);              setName("AcceptThread");              BluetoothSocket socket = null;                // Listen to the server socket if we're not connected              while (mState != STATE_CONNECTED) {                  try {                      // This is a blocking call and will only return on a                      // successful connection or an exception                      socket = mmServerSocket.accept();                  } catch (IOException e) {                      Log.e(TAG, "accept() failed", e);                      break;                  }                    // If a connection was accepted                  if (socket != null) {                      synchronized (BluetoothChatService.this) {                          switch (mState) {                          case STATE_LISTEN:                          case STATE_CONNECTING:                              // Situation normal. Start the connected thread.                              connected(socket, socket.getRemoteDevice());                              break;                          case STATE_NONE:                          case STATE_CONNECTED:                              // Either not ready or already connected. Terminate                              // new socket.                              try {                                  socket.close();                              } catch (IOException e) {                                  Log.e(TAG, "Could not close unwanted socket", e);                              }                              break;                          }                      }                  }              }              if (D)                  Log.i(TAG, "END mAcceptThread");          }            public void cancel() {              if (D)                  Log.d(TAG, "cancel " + this);              try {                  mmServerSocket.close();              } catch (IOException e) {                  Log.e(TAG, "close() of server failed", e);              }          }      }        /**       * 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(MY_UUID);              } catch (IOException e) {                  Log.e(TAG, "create() failed", e);              }              mmSocket = tmp;          }            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) {                  connectionFailed();                  // Close the socket                  try {                      mmSocket.close();                  } catch (IOException e2) {                      Log.e(TAG,                              "unable to close() socket during connection failure",                              e2);                  }                  // Start the service over to restart listening mode                  BluetoothChatService.this.start();                  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 connect 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;            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;          }            public void run() {              Log.i(TAG, "BEGIN mConnectedThread");              byte[] buffer = new byte[1024];              int bytes;                // Keep listening to the InputStream while connected              while (true) {                  try {                      // Read from the InputStream                      bytes = mmInStream.read(buffer);                        // Send the obtained bytes to the UI Activity                      mHandler.obtainMessage(BluetoothChat.MESSAGE_READ, bytes,                              -1, buffer).sendToTarget();                  } catch (IOException e) {                      Log.e(TAG, "disconnected", e);                      connectionLost();                      break;                  }              }          }            /**           * Write to the connected OutStream.           *            * @param buffer           *            The bytes to write           */          public void write(byte[] buffer) {              try {                  mmOutStream.write(buffer);                    // Share the sent message back to the UI Activity                  mHandler.obtainMessage(BluetoothChat.MESSAGE_WRITE, -1, -1,                          buffer).sendToTarget();              } catch (IOException e) {                  Log.e(TAG, "Exception during write", e);              }          }            public void cancel() {              try {                  mmSocket.close();              } catch (IOException e) {                  Log.e(TAG, "close() of connect socket failed", e);              }          }      }  }