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 onCreate(Bundle savedInstanceState)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 onStart()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 onResume()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 setupChat()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 onPause()167 public synchronized void onPause() { 168 super.onPause(); 169 if(D) Log.e(TAG, "- ON PAUSE -"); 170 } 171 172 @Override onStop()173 public void onStop() { 174 super.onStop(); 175 if(D) Log.e(TAG, "-- ON STOP --"); 176 } 177 178 @Override onDestroy()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 ensureDiscoverable()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 */ sendMessage(String message)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 setStatus(int resId)233 private final void setStatus(int resId) { 234 final ActionBar actionBar = getActionBar(); 235 actionBar.setSubtitle(resId); 236 } 237 setStatus(CharSequence subTitle)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 onActivityResult(int requestCode, int resultCode, Intent data)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 connectDevice(Intent data, boolean secure)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 onCreateOptionsMenu(Menu menu)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 onOptionsItemSelected(MenuItem item)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