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