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