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