Home | History | Annotate | Download | only in com.example.android.bluetoothchat
      1 /*
      2  * Copyright (C) 2014 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 com.example.android.bluetoothchat;
     18 
     19 import android.app.ActionBar;
     20 import android.app.Activity;
     21 import android.bluetooth.BluetoothAdapter;
     22 import android.bluetooth.BluetoothDevice;
     23 import android.content.Intent;
     24 import android.os.Bundle;
     25 import android.os.Handler;
     26 import android.os.Message;
     27 import android.support.annotation.Nullable;
     28 import android.support.v4.app.Fragment;
     29 import android.support.v4.app.FragmentActivity;
     30 import android.view.KeyEvent;
     31 import android.view.LayoutInflater;
     32 import android.view.Menu;
     33 import android.view.MenuInflater;
     34 import android.view.MenuItem;
     35 import android.view.View;
     36 import android.view.ViewGroup;
     37 import android.view.inputmethod.EditorInfo;
     38 import android.widget.ArrayAdapter;
     39 import android.widget.Button;
     40 import android.widget.EditText;
     41 import android.widget.ListView;
     42 import android.widget.TextView;
     43 import android.widget.Toast;
     44 
     45 import com.example.android.common.logger.Log;
     46 
     47 /**
     48  * This fragment controls Bluetooth to communicate with other devices.
     49  */
     50 public class BluetoothChatFragment extends Fragment {
     51 
     52     private static final String TAG = "BluetoothChatFragment";
     53 
     54     // Intent request codes
     55     private static final int REQUEST_CONNECT_DEVICE_SECURE = 1;
     56     private static final int REQUEST_CONNECT_DEVICE_INSECURE = 2;
     57     private static final int REQUEST_ENABLE_BT = 3;
     58 
     59     // Layout Views
     60     private ListView mConversationView;
     61     private EditText mOutEditText;
     62     private Button mSendButton;
     63 
     64     /**
     65      * Name of the connected device
     66      */
     67     private String mConnectedDeviceName = null;
     68 
     69     /**
     70      * Array adapter for the conversation thread
     71      */
     72     private ArrayAdapter<String> mConversationArrayAdapter;
     73 
     74     /**
     75      * String buffer for outgoing messages
     76      */
     77     private StringBuffer mOutStringBuffer;
     78 
     79     /**
     80      * Local Bluetooth adapter
     81      */
     82     private BluetoothAdapter mBluetoothAdapter = null;
     83 
     84     /**
     85      * Member object for the chat services
     86      */
     87     private BluetoothChatService mChatService = null;
     88 
     89     @Override
     90     public void onCreate(Bundle savedInstanceState) {
     91         super.onCreate(savedInstanceState);
     92         setHasOptionsMenu(true);
     93         // Get local Bluetooth adapter
     94         mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
     95 
     96         // If the adapter is null, then Bluetooth is not supported
     97         if (mBluetoothAdapter == null) {
     98             FragmentActivity activity = getActivity();
     99             Toast.makeText(activity, "Bluetooth is not available", Toast.LENGTH_LONG).show();
    100             activity.finish();
    101         }
    102     }
    103 
    104 
    105     @Override
    106     public void onStart() {
    107         super.onStart();
    108         // If BT is not on, request that it be enabled.
    109         // setupChat() will then be called during onActivityResult
    110         if (!mBluetoothAdapter.isEnabled()) {
    111             Intent enableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
    112             startActivityForResult(enableIntent, REQUEST_ENABLE_BT);
    113             // Otherwise, setup the chat session
    114         } else if (mChatService == null) {
    115             setupChat();
    116         }
    117     }
    118 
    119     @Override
    120     public void onDestroy() {
    121         super.onDestroy();
    122         if (mChatService != null) {
    123             mChatService.stop();
    124         }
    125     }
    126 
    127     @Override
    128     public void onResume() {
    129         super.onResume();
    130 
    131         // Performing this check in onResume() covers the case in which BT was
    132         // not enabled during onStart(), so we were paused to enable it...
    133         // onResume() will be called when ACTION_REQUEST_ENABLE activity returns.
    134         if (mChatService != null) {
    135             // Only if the state is STATE_NONE, do we know that we haven't started already
    136             if (mChatService.getState() == BluetoothChatService.STATE_NONE) {
    137                 // Start the Bluetooth chat services
    138                 mChatService.start();
    139             }
    140         }
    141     }
    142 
    143     @Override
    144     public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
    145                              @Nullable Bundle savedInstanceState) {
    146         return inflater.inflate(R.layout.fragment_bluetooth_chat, container, false);
    147     }
    148 
    149     @Override
    150     public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
    151         mConversationView = (ListView) view.findViewById(R.id.in);
    152         mOutEditText = (EditText) view.findViewById(R.id.edit_text_out);
    153         mSendButton = (Button) view.findViewById(R.id.button_send);
    154     }
    155 
    156     /**
    157      * Set up the UI and background operations for chat.
    158      */
    159     private void setupChat() {
    160         Log.d(TAG, "setupChat()");
    161 
    162         // Initialize the array adapter for the conversation thread
    163         mConversationArrayAdapter = new ArrayAdapter<String>(getActivity(), R.layout.message);
    164 
    165         mConversationView.setAdapter(mConversationArrayAdapter);
    166 
    167         // Initialize the compose field with a listener for the return key
    168         mOutEditText.setOnEditorActionListener(mWriteListener);
    169 
    170         // Initialize the send button with a listener that for click events
    171         mSendButton.setOnClickListener(new View.OnClickListener() {
    172             public void onClick(View v) {
    173                 // Send a message using content of the edit text widget
    174                 View view = getView();
    175                 if (null != view) {
    176                     TextView textView = (TextView) view.findViewById(R.id.edit_text_out);
    177                     String message = textView.getText().toString();
    178                     sendMessage(message);
    179                 }
    180             }
    181         });
    182 
    183         // Initialize the BluetoothChatService to perform bluetooth connections
    184         mChatService = new BluetoothChatService(getActivity(), mHandler);
    185 
    186         // Initialize the buffer for outgoing messages
    187         mOutStringBuffer = new StringBuffer("");
    188     }
    189 
    190     /**
    191      * Makes this device discoverable for 300 seconds (5 minutes).
    192      */
    193     private void ensureDiscoverable() {
    194         if (mBluetoothAdapter.getScanMode() !=
    195                 BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE) {
    196             Intent discoverableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
    197             discoverableIntent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 300);
    198             startActivity(discoverableIntent);
    199         }
    200     }
    201 
    202     /**
    203      * Sends a message.
    204      *
    205      * @param message A string of text to send.
    206      */
    207     private void sendMessage(String message) {
    208         // Check that we're actually connected before trying anything
    209         if (mChatService.getState() != BluetoothChatService.STATE_CONNECTED) {
    210             Toast.makeText(getActivity(), R.string.not_connected, Toast.LENGTH_SHORT).show();
    211             return;
    212         }
    213 
    214         // Check that there's actually something to send
    215         if (message.length() > 0) {
    216             // Get the message bytes and tell the BluetoothChatService to write
    217             byte[] send = message.getBytes();
    218             mChatService.write(send);
    219 
    220             // Reset out string buffer to zero and clear the edit text field
    221             mOutStringBuffer.setLength(0);
    222             mOutEditText.setText(mOutStringBuffer);
    223         }
    224     }
    225 
    226     /**
    227      * The action listener for the EditText widget, to listen for the return key
    228      */
    229     private TextView.OnEditorActionListener mWriteListener
    230             = new TextView.OnEditorActionListener() {
    231         public boolean onEditorAction(TextView view, int actionId, KeyEvent event) {
    232             // If the action is a key-up event on the return key, send the message
    233             if (actionId == EditorInfo.IME_NULL && event.getAction() == KeyEvent.ACTION_UP) {
    234                 String message = view.getText().toString();
    235                 sendMessage(message);
    236             }
    237             return true;
    238         }
    239     };
    240 
    241     /**
    242      * Updates the status on the action bar.
    243      *
    244      * @param resId a string resource ID
    245      */
    246     private void setStatus(int resId) {
    247         FragmentActivity activity = getActivity();
    248         if (null == activity) {
    249             return;
    250         }
    251         final ActionBar actionBar = activity.getActionBar();
    252         if (null == actionBar) {
    253             return;
    254         }
    255         actionBar.setSubtitle(resId);
    256     }
    257 
    258     /**
    259      * Updates the status on the action bar.
    260      *
    261      * @param subTitle status
    262      */
    263     private void setStatus(CharSequence subTitle) {
    264         FragmentActivity activity = getActivity();
    265         if (null == activity) {
    266             return;
    267         }
    268         final ActionBar actionBar = activity.getActionBar();
    269         if (null == actionBar) {
    270             return;
    271         }
    272         actionBar.setSubtitle(subTitle);
    273     }
    274 
    275     /**
    276      * The Handler that gets information back from the BluetoothChatService
    277      */
    278     private final Handler mHandler = new Handler() {
    279         @Override
    280         public void handleMessage(Message msg) {
    281             FragmentActivity activity = getActivity();
    282             switch (msg.what) {
    283                 case Constants.MESSAGE_STATE_CHANGE:
    284                     switch (msg.arg1) {
    285                         case BluetoothChatService.STATE_CONNECTED:
    286                             setStatus(getString(R.string.title_connected_to, mConnectedDeviceName));
    287                             mConversationArrayAdapter.clear();
    288                             break;
    289                         case BluetoothChatService.STATE_CONNECTING:
    290                             setStatus(R.string.title_connecting);
    291                             break;
    292                         case BluetoothChatService.STATE_LISTEN:
    293                         case BluetoothChatService.STATE_NONE:
    294                             setStatus(R.string.title_not_connected);
    295                             break;
    296                     }
    297                     break;
    298                 case Constants.MESSAGE_WRITE:
    299                     byte[] writeBuf = (byte[]) msg.obj;
    300                     // construct a string from the buffer
    301                     String writeMessage = new String(writeBuf);
    302                     mConversationArrayAdapter.add("Me:  " + writeMessage);
    303                     break;
    304                 case Constants.MESSAGE_READ:
    305                     byte[] readBuf = (byte[]) msg.obj;
    306                     // construct a string from the valid bytes in the buffer
    307                     String readMessage = new String(readBuf, 0, msg.arg1);
    308                     mConversationArrayAdapter.add(mConnectedDeviceName + ":  " + readMessage);
    309                     break;
    310                 case Constants.MESSAGE_DEVICE_NAME:
    311                     // save the connected device's name
    312                     mConnectedDeviceName = msg.getData().getString(Constants.DEVICE_NAME);
    313                     if (null != activity) {
    314                         Toast.makeText(activity, "Connected to "
    315                                 + mConnectedDeviceName, Toast.LENGTH_SHORT).show();
    316                     }
    317                     break;
    318                 case Constants.MESSAGE_TOAST:
    319                     if (null != activity) {
    320                         Toast.makeText(activity, msg.getData().getString(Constants.TOAST),
    321                                 Toast.LENGTH_SHORT).show();
    322                     }
    323                     break;
    324             }
    325         }
    326     };
    327 
    328     public void onActivityResult(int requestCode, int resultCode, Intent data) {
    329         switch (requestCode) {
    330             case REQUEST_CONNECT_DEVICE_SECURE:
    331                 // When DeviceListActivity returns with a device to connect
    332                 if (resultCode == Activity.RESULT_OK) {
    333                     connectDevice(data, true);
    334                 }
    335                 break;
    336             case REQUEST_CONNECT_DEVICE_INSECURE:
    337                 // When DeviceListActivity returns with a device to connect
    338                 if (resultCode == Activity.RESULT_OK) {
    339                     connectDevice(data, false);
    340                 }
    341                 break;
    342             case REQUEST_ENABLE_BT:
    343                 // When the request to enable Bluetooth returns
    344                 if (resultCode == Activity.RESULT_OK) {
    345                     // Bluetooth is now enabled, so set up a chat session
    346                     setupChat();
    347                 } else {
    348                     // User did not enable Bluetooth or an error occurred
    349                     Log.d(TAG, "BT not enabled");
    350                     Toast.makeText(getActivity(), R.string.bt_not_enabled_leaving,
    351                             Toast.LENGTH_SHORT).show();
    352                     getActivity().finish();
    353                 }
    354         }
    355     }
    356 
    357     /**
    358      * Establish connection with other device
    359      *
    360      * @param data   An {@link Intent} with {@link DeviceListActivity#EXTRA_DEVICE_ADDRESS} extra.
    361      * @param secure Socket Security type - Secure (true) , Insecure (false)
    362      */
    363     private void connectDevice(Intent data, boolean secure) {
    364         // Get the device MAC address
    365         String address = data.getExtras()
    366                 .getString(DeviceListActivity.EXTRA_DEVICE_ADDRESS);
    367         // Get the BluetoothDevice object
    368         BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(address);
    369         // Attempt to connect to the device
    370         mChatService.connect(device, secure);
    371     }
    372 
    373     @Override
    374     public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
    375         inflater.inflate(R.menu.bluetooth_chat, menu);
    376     }
    377 
    378     @Override
    379     public boolean onOptionsItemSelected(MenuItem item) {
    380         switch (item.getItemId()) {
    381             case R.id.secure_connect_scan: {
    382                 // Launch the DeviceListActivity to see devices and do scan
    383                 Intent serverIntent = new Intent(getActivity(), DeviceListActivity.class);
    384                 startActivityForResult(serverIntent, REQUEST_CONNECT_DEVICE_SECURE);
    385                 return true;
    386             }
    387             case R.id.insecure_connect_scan: {
    388                 // Launch the DeviceListActivity to see devices and do scan
    389                 Intent serverIntent = new Intent(getActivity(), DeviceListActivity.class);
    390                 startActivityForResult(serverIntent, REQUEST_CONNECT_DEVICE_INSECURE);
    391                 return true;
    392             }
    393             case R.id.discoverable: {
    394                 // Ensure this device is discoverable by others
    395                 ensureDiscoverable();
    396                 return true;
    397             }
    398         }
    399         return false;
    400     }
    401 
    402 }
    403