content: package com.alexkang.bluechat;
  
  import android.app.Activity;
  import android.app.ProgressDialog;
  import android.bluetooth.BluetoothAdapter;
  import android.bluetooth.BluetoothSocket;
  import android.content.Intent;
  import android.content.SharedPreferences;
  import android.graphics.Bitmap;
  import android.graphics.BitmapFactory;
  import android.os.Handler;
  import android.os.Message;
  import android.preference.PreferenceManager;
  import android.view.View;
  import android.view.ViewGroup;
  import android.widget.AbsListView;
  import android.widget.ListView;
  import android.widget.Toast;
  
  import java.io.ByteArrayOutputStream;
  import java.io.IOException;
  import java.io.InputStream;
  import java.io.OutputStream;
  import java.util.ArrayList;
  import java.util.Arrays;
  import java.util.Date;
  
  public class ChatManager {
  
      public static final int BODY_LENGTH_END = 255;
      public static final int BODY_LENGTH_END_SIGNED = -1;
  
      public static final int MESSAGE_ID = 1;
      public static final int MESSAGE_NAME = 2;
      public static final int MESSAGE_SEND = 3;
      public static final int MESSAGE_RECEIVE = 4;
      public static final int MESSAGE_SEND_IMAGE = 5;
      public static final int MESSAGE_RECEIVE_IMAGE = 6;
  
      private boolean isHost;
      private boolean isInitialized = false;
      private ArrayList<ConnectedThread> connections;
      private int id;
  
      private ArrayList<MessageBox> mMessageList;
      private MessageFeedAdapter mFeedAdapter;
      private ListView mMessageFeed;
  
      private Activity mActivity;
      private ProgressDialog mProgressDialog;
  
      private ConnectedThread mConnectedThread;
  
      private final Handler mHandler = new Handler() {
  
          @Override
          public void handleMessage(Message msg) {
              byte[] packet = (byte[]) msg.obj;
              int senderLength = msg.arg1;
              int senderId = msg.arg2;
  
              String sender = new String(Arrays.copyOfRange(packet, 0, senderLength));
              byte[] body = Arrays.copyOfRange(packet, senderLength, packet.length);
  
              boolean isSelf = senderId == id;
  
              switch (msg.what) {
                  case MESSAGE_ID:
                      id = body[0];
                  case MESSAGE_NAME:
                      if (!isHost && !isInitialized) {
                          String chatRoomName = new String(body);
  
                          if (mActivity.getActionBar() != null) {
                              mActivity.getActionBar().setTitle(chatRoomName);
                          }
  
                          BluetoothAdapter.getDefaultAdapter().cancelDiscovery();
                          mProgressDialog.dismiss();
  
                          Toast.makeText(mActivity, mActivity.getString(R.string.connected), Toast.LENGTH_SHORT).show();
  
                          isInitialized = true;
                      }
                      break;
                  case MESSAGE_SEND:
                      if (isHost) {
                          byte[] sendPacket = buildPacket(MESSAGE_SEND, senderId, sender, body);
                          writeMessage(sendPacket, senderId);
                      }
                      break;
                  case MESSAGE_RECEIVE:
                      MessageBox messageBox = new MessageBox(sender, new String(body), new Date(), isSelf);
                      addMessage(messageBox);
                      break;
                  case MESSAGE_SEND_IMAGE:
                      if (isHost) {
                          byte[] sendImagePacket = buildPacket(MESSAGE_SEND_IMAGE, senderId, sender, body);
                          writeMessage(sendImagePacket, senderId);
                      }
                      break;
                  case MESSAGE_RECEIVE_IMAGE:
                      Bitmap bitmap = BitmapFactory.decodeByteArray(body, 0, body.length);
                      MessageBox imageBox = new MessageBox(sender, bitmap, new Date(), isSelf);
                      addMessage(imageBox);
              }
          }
  
      };
  
      public ChatManager(Activity activity, boolean isHost) {
          mActivity = activity;
          mMessageFeed = (ListView) mActivity.findViewById(R.id.message_feed);
          this.isHost = isHost;
  
          if (isHost) {
              id = 0;
              connections = new ArrayList<>();
          }
  
          mMessageList = new ArrayList<>();
          mFeedAdapter = new MessageFeedAdapter(mActivity, mMessageList);
          mMessageFeed.setAdapter(mFeedAdapter);
  
          View footer = new View(mActivity);
          footer.setLayoutParams(new AbsListView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 250));
          footer.setBackgroundColor(mActivity.getResources().getColor(android.R.color.transparent));
  
          mMessageFeed.addFooterView(footer, null, false);
      }
  
      public void startConnection(BluetoothSocket socket) {
          SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(mActivity);
          String username = sharedPref.getString("username", BluetoothAdapter.getDefaultAdapter().getName());
          mConnectedThread = new ConnectedThread(socket);
          mConnectedThread.start();
  
          if (isHost) {
              connections.add(mConnectedThread);
              byte[] idAssignmentPacket = buildPacket(
                      MESSAGE_ID,
                      username,
                      new byte[] { (byte) connections.size() }
              );
              mConnectedThread.write(idAssignmentPacket);
          }
      }
  
      public void startConnection(BluetoothSocket socket, ProgressDialog progressDialog) {
          startConnection(socket);
          mProgressDialog = progressDialog;
      }
  
      public byte[] buildPacket(int type, int senderId, String sender, byte[] body) {
          ByteArrayOutputStream output = new ByteArrayOutputStream();
          output.write(type);
          output.write(sender.length());
  
          int bodyLength = body.length;
          do {
             output.write(bodyLength % 10);
             bodyLength = bodyLength / 10;
          } while (bodyLength > 0);
  
          try {
              output.write(BODY_LENGTH_END);
              output.write(senderId);
              output.write(sender.getBytes());
              output.write(body);
          } catch (IOException e) {
              System.err.println("Error in building packet.");
              return null;
          }
  
          return output.toByteArray();
      }
  
      public byte[] buildPacket(int type, String sender, byte[] body) {
          return buildPacket(type, id, sender, body);
      }
  
      public void writeChatRoomName(byte[] byteArray) {
          connections.get(connections.size() - 1).write(byteArray);
      }
  
      public void writeMessage(byte[] byteArray, int senderId) {
          int type = byteArray[0];
          int receiveType = 0;
          if (type == MESSAGE_SEND) {
              receiveType = MESSAGE_RECEIVE;
          } else if (type == MESSAGE_SEND_IMAGE) {
              receiveType = MESSAGE_RECEIVE_IMAGE;
          }
  
          int senderLength = byteArray[1];
  
          int currIndex = 2;
          do {
              currIndex++;
          } while (byteArray[currIndex] != BODY_LENGTH_END_SIGNED);
  
          mHandler.obtainMessage(receiveType, senderLength, senderId, Arrays.copyOfRange(byteArray, currIndex + 2, byteArray.length))
                  .sendToTarget();
  
          if (isHost) {
              new DistributeThread(receiveType, senderId, byteArray).start();
          } else {
              mConnectedThread.write(byteArray);
          }
      }
  
      public void writeMessage(byte[] byteArray) {
          writeMessage(byteArray, id);
      }
  
      private void addMessage(MessageBox message) {
          mMessageList.add(message);
          mMessageFeed.invalidateViews();
          mFeedAdapter.notifyDataSetChanged();
          mMessageFeed.setSelection(mFeedAdapter.getCount()-1);
      }
  
      private class DistributeThread extends Thread {
  
          int mReceiveType;
          int mSenderId;
          private byte[] mByteArray;
  
          public DistributeThread(int receiveType, int senderId, byte[] byteArray) {
              mReceiveType = receiveType;
              mSenderId = senderId;
              mByteArray = byteArray;
          }
  
          public void run() {
              mByteArray[0] = (byte) mReceiveType;
              for (int i = 0; i < connections.size(); i++) {
                  if (i + 1 != mSenderId) {
                      connections.get(i).write(mByteArray);
                  }
              }
          }
  
      }
  
      private class ConnectedThread extends Thread {
  
          private final InputStream mmInStream;
          private final OutputStream mmOutStream;
  
          public ConnectedThread(BluetoothSocket socket) {
              InputStream tmpIn = null;
              OutputStream tmpOut = null;
  
              try {
                  tmpIn = socket.getInputStream();
                  tmpOut = socket.getOutputStream();
              } catch (IOException e) {
                  Toast.makeText(mActivity, mActivity.getString(R.string.could_not_connect_to_chatroom), Toast.LENGTH_SHORT).show();
                  Intent i = new Intent(mActivity, MainActivity.class);
                  mActivity.startActivity(i);
                  mActivity.finish();
              }
  
              mmInStream = tmpIn;
              mmOutStream = tmpOut;
          }
  
          public void run() {
              while (true) {
                  try {
                      int type = mmInStream.read();
                      int senderLength = mmInStream.read();
  
                      /*
                       * Calculate the length of the body in bytes. Each byte read is a digit
                       * in the body length in order of least to most significant digit.
                       *
                       * i.e. Body length of 247 would be read in the form {7, 4, 2}.
                       */
                      int bodyLength = 0;
                      int currPlace = 1;
                      int currDigit = mmInStream.read();
                      do {
                          bodyLength += (currDigit * currPlace);
                          currPlace *= 10;
                          currDigit = mmInStream.read();
                      } while (currDigit != BODY_LENGTH_END);
  
                      int senderId = mmInStream.read();
  
                      ByteArrayOutputStream packetStream = new ByteArrayOutputStream();
                      for (int i = 0; i < senderLength + bodyLength; i++) {
                          packetStream.write(mmInStream.read());
                      }
                      byte[] packet = packetStream.toByteArray();
  
                      mHandler.obtainMessage(type, senderLength, senderId, packet)
                              .sendToTarget();
                  } catch (IOException e) {
                      System.err.println("Error in receiving packets");
                      e.printStackTrace();
                      endActivity();
                      break;
                  }
              }
          }
  
          public void write(byte[] byteArray) {
              try {
                  mmOutStream.write(byteArray);
                  mmOutStream.flush();
              } catch (IOException e) {
                  String byteArrayString = "";
                  for (byte b : byteArray) {
                      byteArrayString += b + ", ";
                  }
                  System.err.println("Failed to write bytes: " + byteArrayString);
                  System.err.println(e.toString());
                  endActivity();
              }
          }
  
          private void endActivity() {
              if (!isHost) {
                  mActivity.runOnUiThread(new Runnable() {
  
                      @Override
                      public void run() {
                          Toast.makeText(mActivity, mActivity.getString(R.string.chatroom_closed), Toast.LENGTH_SHORT).show();
                          mActivity.finish();
                      }
  
                  });
              }
          }
  
      }
  
  }
  