5be9ba84429f38190e9d5038648fa85eab453600
[fw/altos] / altosdroid / src / org / altusmetrum / AltosDroid / AltosDroid.java
1 /*
2  * Copyright © 2012-2013 Mike Beattie <mike@ethernal.org>
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; version 2 of the License.
7  *
8  * This program is distributed in the hope that it will be useful, but
9  * WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
11  * General Public License for more details.
12  *
13  * You should have received a copy of the GNU General Public License along
14  * with this program; if not, write to the Free Software Foundation, Inc.,
15  * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
16  */
17
18 package org.altusmetrum.AltosDroid;
19
20 import java.lang.ref.WeakReference;
21 import java.util.ArrayList;
22 import java.util.Timer;
23 import java.util.TimerTask;
24 import java.text.*;
25
26 import android.app.Activity;
27 import android.app.PendingIntent;
28 import android.bluetooth.BluetoothAdapter;
29 import android.bluetooth.BluetoothDevice;
30 import android.content.Intent;
31 import android.content.Context;
32 import android.content.ComponentName;
33 import android.content.ServiceConnection;
34 import android.content.DialogInterface;
35 import android.os.IBinder;
36 import android.os.Bundle;
37 import android.os.Handler;
38 import android.os.Message;
39 import android.os.Messenger;
40 import android.os.RemoteException;
41 import android.content.res.Resources;
42 import android.support.v4.app.FragmentActivity;
43 import android.support.v4.app.FragmentManager;
44 import android.util.DisplayMetrics;
45 import android.view.Menu;
46 import android.view.MenuInflater;
47 import android.view.MenuItem;
48 import android.view.Window;
49 import android.view.View;
50 import android.view.LayoutInflater;
51 import android.widget.TabHost;
52 import android.widget.TextView;
53 import android.widget.RelativeLayout;
54 import android.widget.Toast;
55 import android.app.AlertDialog;
56 import android.location.Location;
57 import android.hardware.usb.*;
58
59 import org.altusmetrum.altoslib_7.*;
60
61 public class AltosDroid extends FragmentActivity implements AltosUnitsListener {
62
63         // Actions sent to the telemetry server at startup time
64
65         public static final String ACTION_BLUETOOTH = "org.altusmetrum.AltosDroid.BLUETOOTH";
66         public static final String ACTION_USB = "org.altusmetrum.AltosDroid.USB";
67
68         // Message types received by our Handler
69
70         public static final int MSG_STATE           = 1;
71         public static final int MSG_UPDATE_AGE      = 2;
72
73         // Intent request codes
74         public static final int REQUEST_CONNECT_DEVICE = 1;
75         public static final int REQUEST_ENABLE_BT      = 2;
76         public static final int REQUEST_PRELOAD_MAPS   = 3;
77         public static final int REQUEST_MAP_TYPE       = 4;
78
79         public int map_type = AltosMap.maptype_hybrid;
80
81         public static FragmentManager   fm;
82
83         private BluetoothAdapter mBluetoothAdapter = null;
84
85         // Layout Views
86         private TextView mTitle;
87
88         // Flight state values
89         private TextView mCallsignView;
90         private TextView mRSSIView;
91         private TextView mSerialView;
92         private TextView mFlightView;
93         private RelativeLayout mStateLayout;
94         private TextView mStateView;
95         private TextView mAgeView;
96
97         // field to display the version at the bottom of the screen
98         private TextView mVersion;
99
100         private double frequency;
101         private int telemetry_rate;
102
103         // Tabs
104         TabHost         mTabHost;
105         AltosViewPager  mViewPager;
106         TabsAdapter     mTabsAdapter;
107         ArrayList<AltosDroidTab> mTabs = new ArrayList<AltosDroidTab>();
108         int             tabHeight;
109
110         // Timer and Saved flight state for Age calculation
111         private Timer timer;
112         AltosState saved_state;
113
114         UsbDevice       pending_usb_device;
115         boolean         start_with_usb;
116
117         // Service
118         private boolean mIsBound   = false;
119         private Messenger mService = null;
120         final Messenger mMessenger = new Messenger(new IncomingHandler(this));
121
122         // Text to Speech
123         private AltosVoice mAltosVoice = null;
124
125         // The Handler that gets information back from the Telemetry Service
126         static class IncomingHandler extends Handler {
127                 private final WeakReference<AltosDroid> mAltosDroid;
128                 IncomingHandler(AltosDroid ad) { mAltosDroid = new WeakReference<AltosDroid>(ad); }
129
130                 @Override
131                 public void handleMessage(Message msg) {
132                         AltosDroid ad = mAltosDroid.get();
133
134                         switch (msg.what) {
135                         case MSG_STATE:
136                                 AltosDebug.debug("MSG_STATE");
137                                 TelemetryState telemetry_state = (TelemetryState) msg.obj;
138                                 if (telemetry_state == null) {
139                                         AltosDebug.debug("telemetry_state null!");
140                                         return;
141                                 }
142
143                                 ad.update_state(telemetry_state);
144                                 break;
145                         case MSG_UPDATE_AGE:
146                                 AltosDebug.debug("MSG_UPDATE_AGE");
147                                 ad.update_age();
148                                 break;
149                         }
150                 }
151         };
152
153
154         private ServiceConnection mConnection = new ServiceConnection() {
155                 public void onServiceConnected(ComponentName className, IBinder service) {
156                         mService = new Messenger(service);
157                         try {
158                                 Message msg = Message.obtain(null, TelemetryService.MSG_REGISTER_CLIENT);
159                                 msg.replyTo = mMessenger;
160                                 mService.send(msg);
161                         } catch (RemoteException e) {
162                                 // In this case the service has crashed before we could even do anything with it
163                         }
164                         if (pending_usb_device != null) {
165                                 try {
166                                         mService.send(Message.obtain(null, TelemetryService.MSG_OPEN_USB, pending_usb_device));
167                                         pending_usb_device = null;
168                                 } catch (RemoteException e) {
169                                 }
170                         }
171                 }
172
173                 public void onServiceDisconnected(ComponentName className) {
174                         // This is called when the connection with the service has been unexpectedly disconnected - process crashed.
175                         mService = null;
176                 }
177         };
178
179         void doBindService() {
180                 bindService(new Intent(this, TelemetryService.class), mConnection, Context.BIND_AUTO_CREATE);
181                 mIsBound = true;
182         }
183
184         void doUnbindService() {
185                 if (mIsBound) {
186                         // If we have received the service, and hence registered with it, then now is the time to unregister.
187                         if (mService != null) {
188                                 try {
189                                         Message msg = Message.obtain(null, TelemetryService.MSG_UNREGISTER_CLIENT);
190                                         msg.replyTo = mMessenger;
191                                         mService.send(msg);
192                                 } catch (RemoteException e) {
193                                         // There is nothing special we need to do if the service has crashed.
194                                 }
195                         }
196                         // Detach our existing connection.
197                         unbindService(mConnection);
198                         mIsBound = false;
199                 }
200         }
201
202         public void registerTab(AltosDroidTab mTab) {
203                 mTabs.add(mTab);
204         }
205
206         public void unregisterTab(AltosDroidTab mTab) {
207                 mTabs.remove(mTab);
208         }
209
210         public void units_changed(boolean imperial_units) {
211                 for (AltosDroidTab mTab : mTabs)
212                         mTab.units_changed(imperial_units);
213         }
214
215         void update_title(TelemetryState telemetry_state) {
216                 switch (telemetry_state.connect) {
217                 case TelemetryState.CONNECT_CONNECTED:
218                         if (telemetry_state.config != null) {
219                                 String str = String.format("S/N %d %6.3f MHz", telemetry_state.config.serial,
220                                                            telemetry_state.frequency);
221                                 if (telemetry_state.telemetry_rate != AltosLib.ao_telemetry_rate_38400)
222                                         str = str.concat(String.format(" %d bps",
223                                                                        AltosLib.ao_telemetry_rate_values[telemetry_state.telemetry_rate]));
224                                 mTitle.setText(str);
225                         } else {
226                                 mTitle.setText(R.string.title_connected_to);
227                         }
228                         break;
229                 case TelemetryState.CONNECT_CONNECTING:
230                         if (telemetry_state.address != null)
231                                 mTitle.setText(String.format("Connecting to %s...", telemetry_state.address.name));
232                         else
233                                 mTitle.setText("Connecting to something...");
234                         break;
235                 case TelemetryState.CONNECT_DISCONNECTED:
236                 case TelemetryState.CONNECT_NONE:
237                         mTitle.setText(R.string.title_not_connected);
238                         break;
239                 }
240         }
241
242         void start_timer() {
243                 if (timer == null) {
244                         timer = new Timer();
245                         timer.scheduleAtFixedRate(new TimerTask(){ public void run() {onTimerTick();}}, 1000L, 1000L);
246                 }
247         }
248
249         void stop_timer() {
250                 if (timer != null) {
251                         timer.cancel();
252                         timer.purge();
253                         timer = null;
254                 }
255         }
256
257         boolean registered_units_listener;
258
259         void update_state(TelemetryState telemetry_state) {
260
261                 if (!registered_units_listener) {
262                         registered_units_listener = true;
263                         AltosPreferences.register_units_listener(this);
264                 }
265
266                 update_title(telemetry_state);
267                 update_ui(telemetry_state.state, telemetry_state.location);
268                 if (telemetry_state.connect == TelemetryState.CONNECT_CONNECTED)
269                         start_timer();
270         }
271
272         boolean same_string(String a, String b) {
273                 if (a == null) {
274                         if (b == null)
275                                 return true;
276                         return false;
277                 } else {
278                         if (b == null)
279                                 return false;
280                         return a.equals(b);
281                 }
282         }
283
284         void update_age() {
285                 if (saved_state != null)
286                         mAgeView.setText(String.format("%d", (System.currentTimeMillis() - saved_state.received_time + 500) / 1000));
287         }
288
289         void update_ui(AltosState state, Location location) {
290
291                 int prev_state = AltosLib.ao_flight_invalid;
292
293                 AltosGreatCircle from_receiver = null;
294
295                 if (saved_state != null)
296                         prev_state = saved_state.state;
297
298                 if (state != null) {
299                         if (state.state == AltosLib.ao_flight_stateless) {
300                                 boolean prev_locked = false;
301                                 boolean locked = false;
302
303                                 if(state.gps != null)
304                                         locked = state.gps.locked;
305                                 if (saved_state != null && saved_state.gps != null)
306                                         prev_locked = saved_state.gps.locked;
307                                 if (prev_locked != locked) {
308                                         String currentTab = mTabHost.getCurrentTabTag();
309                                         if (locked) {
310                                                 if (currentTab.equals("pad")) mTabHost.setCurrentTabByTag("descent");
311                                         } else {
312                                                 if (currentTab.equals("descent")) mTabHost.setCurrentTabByTag("pad");
313                                         }
314                                 }
315                         } else {
316                                 if (prev_state != state.state) {
317                                         String currentTab = mTabHost.getCurrentTabTag();
318                                         switch (state.state) {
319                                         case AltosLib.ao_flight_boost:
320                                                 if (currentTab.equals("pad")) mTabHost.setCurrentTabByTag("ascent");
321                                                 break;
322                                         case AltosLib.ao_flight_drogue:
323                                                 if (currentTab.equals("ascent")) mTabHost.setCurrentTabByTag("descent");
324                                                 break;
325                                         case AltosLib.ao_flight_landed:
326                                                 if (currentTab.equals("descent")) mTabHost.setCurrentTabByTag("landed");
327                                                 break;
328                                         case AltosLib.ao_flight_stateless:
329                                                 if (currentTab.equals("pad")) mTabHost.setCurrentTabByTag("descent");
330                                                 break;
331                                         }
332                                 }
333                         }
334
335                         if (location != null && state.gps != null && state.gps.locked) {
336                                 double altitude = 0;
337                                 if (location.hasAltitude())
338                                         altitude = location.getAltitude();
339                                 from_receiver = new AltosGreatCircle(location.getLatitude(),
340                                                                      location.getLongitude(),
341                                                                      altitude,
342                                                                      state.gps.lat,
343                                                                      state.gps.lon,
344                                                                      state.gps.alt);
345                         }
346
347                         if (saved_state == null || !same_string(saved_state.callsign, state.callsign)) {
348                                 mCallsignView.setText(state.callsign);
349                         }
350                         if (saved_state == null || state.serial != saved_state.serial) {
351                                 mSerialView.setText(String.format("%d", state.serial));
352                         }
353                         if (saved_state == null || state.flight != saved_state.flight) {
354                                 if (state.flight == AltosLib.MISSING)
355                                         mFlightView.setText("");
356                                 else
357                                         mFlightView.setText(String.format("%d", state.flight));
358                         }
359                         if (saved_state == null || state.state != saved_state.state) {
360                                 if (state.state == AltosLib.ao_flight_stateless) {
361                                         mStateLayout.setVisibility(View.GONE);
362                                 } else {
363                                         mStateView.setText(state.state_name());
364                                         mStateLayout.setVisibility(View.VISIBLE);
365                                 }
366                         }
367                         if (saved_state == null || state.rssi != saved_state.rssi) {
368                                 mRSSIView.setText(String.format("%d", state.rssi));
369                         }
370                 }
371
372                 for (AltosDroidTab mTab : mTabs)
373                         mTab.update_ui(state, from_receiver, location, mTab == mTabsAdapter.currentItem());
374
375                 if (state != null && mAltosVoice != null)
376                         mAltosVoice.tell(state, from_receiver);
377
378                 saved_state = state;
379         }
380
381         private void onTimerTick() {
382                 try {
383                         mMessenger.send(Message.obtain(null, MSG_UPDATE_AGE));
384                 } catch (RemoteException e) {
385                 }
386         }
387
388         static String pos(double p, String pos, String neg) {
389                 String  h = pos;
390                 if (p == AltosLib.MISSING)
391                         return "";
392                 if (p < 0) {
393                         h = neg;
394                         p = -p;
395                 }
396                 int deg = (int) Math.floor(p);
397                 double min = (p - Math.floor(p)) * 60.0;
398                 return String.format("%d°%9.4f\" %s", deg, min, h);
399         }
400
401         static String number(String format, double value) {
402                 if (value == AltosLib.MISSING)
403                         return "";
404                 return String.format(format, value);
405         }
406
407         static String integer(String format, int value) {
408                 if (value == AltosLib.MISSING)
409                         return "";
410                 return String.format(format, value);
411         }
412
413         private View create_tab_view(String label) {
414                 LayoutInflater inflater = (LayoutInflater) this.getLayoutInflater();
415                 View tab_view = inflater.inflate(R.layout.tab_layout, null);
416                 TextView text_view = (TextView) tab_view.findViewById (R.id.tabLabel);
417                 text_view.setText(label);
418                 return tab_view;
419         }
420
421         @Override
422         public void onCreate(Bundle savedInstanceState) {
423                 super.onCreate(savedInstanceState);
424                 AltosDebug.init(this);
425                 AltosDebug.debug("+++ ON CREATE +++");
426
427                 fm = getSupportFragmentManager();
428
429                 // Set up the window layout
430                 requestWindowFeature(Window.FEATURE_CUSTOM_TITLE);
431                 setContentView(R.layout.altosdroid);
432                 getWindow().setFeatureInt(Window.FEATURE_CUSTOM_TITLE, R.layout.custom_title);
433
434                 // Create the Tabs and ViewPager
435                 mTabHost = (TabHost)findViewById(android.R.id.tabhost);
436                 mTabHost.setup();
437
438                 mViewPager = (AltosViewPager)findViewById(R.id.pager);
439                 mViewPager.setOffscreenPageLimit(4);
440
441                 mTabsAdapter = new TabsAdapter(this, mTabHost, mViewPager);
442
443                 mTabsAdapter.addTab(mTabHost.newTabSpec("pad").setIndicator(create_tab_view("Pad")), TabPad.class, null);
444                 mTabsAdapter.addTab(mTabHost.newTabSpec("ascent").setIndicator(create_tab_view("Ascent")), TabAscent.class, null);
445                 mTabsAdapter.addTab(mTabHost.newTabSpec("descent").setIndicator(create_tab_view("Descent")), TabDescent.class, null);
446                 mTabsAdapter.addTab(mTabHost.newTabSpec("landed").setIndicator(create_tab_view("Landed")), TabLanded.class, null);
447                 mTabsAdapter.addTab(mTabHost.newTabSpec("map").setIndicator(create_tab_view("Map")), TabMap.class, null);
448                 mTabsAdapter.addTab(mTabHost.newTabSpec("offmap").setIndicator(create_tab_view("OffMap")), TabMapOffline.class, null);
449
450                 // Set up the custom title
451                 mTitle = (TextView) findViewById(R.id.title_left_text);
452                 mTitle.setText(R.string.app_name);
453                 mTitle = (TextView) findViewById(R.id.title_right_text);
454
455                 // Display the Version
456                 mVersion = (TextView) findViewById(R.id.version);
457                 mVersion.setText("Version: " + BuildInfo.version +
458                                  "  Built: " + BuildInfo.builddate + " " + BuildInfo.buildtime + " " + BuildInfo.buildtz +
459                                  "  (" + BuildInfo.branch + "-" + BuildInfo.commitnum + "-" + BuildInfo.commithash + ")");
460
461                 mCallsignView  = (TextView) findViewById(R.id.callsign_value);
462                 mRSSIView      = (TextView) findViewById(R.id.rssi_value);
463                 mSerialView    = (TextView) findViewById(R.id.serial_value);
464                 mFlightView    = (TextView) findViewById(R.id.flight_value);
465                 mStateLayout   = (RelativeLayout) findViewById(R.id.state_container);
466                 mStateView     = (TextView) findViewById(R.id.state_value);
467                 mAgeView       = (TextView) findViewById(R.id.age_value);
468         }
469
470         private boolean ensureBluetooth() {
471                 // Get local Bluetooth adapter
472                 mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
473
474                 // If the adapter is null, then Bluetooth is not supported
475                 if (mBluetoothAdapter == null) {
476                         Toast.makeText(this, "Bluetooth is not available", Toast.LENGTH_LONG).show();
477                         return false;
478                 }
479
480                 if (!mBluetoothAdapter.isEnabled()) {
481                         Intent enableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
482                         startActivityForResult(enableIntent, AltosDroid.REQUEST_ENABLE_BT);
483                 }
484
485                 return true;
486         }
487
488         private boolean check_usb() {
489                 UsbDevice       device = AltosUsb.find_device(this, AltosLib.product_basestation);
490
491                 if (device != null) {
492                         Intent          i = new Intent(this, AltosDroid.class);
493                         PendingIntent   pi = PendingIntent.getActivity(this, 0, new Intent("hello world", null, this, AltosDroid.class), 0);
494
495                         if (AltosUsb.request_permission(this, device, pi)) {
496                                 connectUsb(device);
497                         }
498                         start_with_usb = true;
499                         return true;
500                 }
501
502                 start_with_usb = false;
503
504                 return false;
505         }
506
507         private void noticeIntent(Intent intent) {
508
509                 /* Ok, this is pretty convenient.
510                  *
511                  * When a USB device is plugged in, and our 'hotplug'
512                  * intent registration fires, we get an Intent with
513                  * EXTRA_DEVICE set.
514                  *
515                  * When we start up and see a usb device and request
516                  * permission to access it, that queues a
517                  * PendingIntent, which has the EXTRA_DEVICE added in,
518                  * along with the EXTRA_PERMISSION_GRANTED field as
519                  * well.
520                  *
521                  * So, in both cases, we get the device name using the
522                  * same call. We check to see if access was granted,
523                  * in which case we ignore the device field and do our
524                  * usual startup thing.
525                  */
526
527                 UsbDevice device = (UsbDevice) intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
528                 boolean granted = intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, true);
529
530                 AltosDebug.debug("intent %s device %s granted %s", intent, device, granted);
531
532                 if (!granted)
533                         device = null;
534
535                 if (device != null) {
536                         AltosDebug.debug("intent has usb device " + device.toString());
537                         connectUsb(device);
538                 } else {
539
540                         /* 'granted' is only false if this intent came
541                          * from the request_permission call and
542                          * permission was denied. In which case, we
543                          * don't want to loop forever...
544                          */
545                         if (granted) {
546                                 AltosDebug.debug("check for a USB device at startup");
547                                 if (check_usb())
548                                         return;
549                         }
550                         AltosDebug.debug("Starting by looking for bluetooth devices");
551                         if (ensureBluetooth())
552                                 return;
553                         finish();
554                 }
555         }
556
557         @Override
558         public void onStart() {
559                 super.onStart();
560                 AltosDebug.debug("++ ON START ++");
561
562                 noticeIntent(getIntent());
563
564                 // Start Telemetry Service
565                 String  action = start_with_usb ? ACTION_USB : ACTION_BLUETOOTH;
566
567                 startService(new Intent(action, null, AltosDroid.this, TelemetryService.class));
568
569                 doBindService();
570
571                 if (mAltosVoice == null)
572                         mAltosVoice = new AltosVoice(this);
573
574         }
575
576         @Override
577         public void onNewIntent(Intent intent) {
578                 super.onNewIntent(intent);
579                 AltosDebug.debug("onNewIntent");
580                 noticeIntent(intent);
581         }
582
583         @Override
584         public void onResume() {
585                 super.onResume();
586                 AltosDebug.debug("+ ON RESUME +");
587         }
588
589         @Override
590         public void onPause() {
591                 super.onPause();
592                 AltosDebug.debug("- ON PAUSE -");
593         }
594
595         @Override
596         public void onStop() {
597                 super.onStop();
598                 AltosDebug.debug("-- ON STOP --");
599
600                 doUnbindService();
601                 if (mAltosVoice != null) {
602                         mAltosVoice.stop();
603                         mAltosVoice = null;
604                 }
605         }
606
607         @Override
608         public void onDestroy() {
609                 super.onDestroy();
610                 AltosDebug.debug("--- ON DESTROY ---");
611
612                 if (mAltosVoice != null) mAltosVoice.stop();
613                 stop_timer();
614         }
615
616         protected void onActivityResult(int requestCode, int resultCode, Intent data) {
617                 AltosDebug.debug("onActivityResult " + resultCode);
618                 switch (requestCode) {
619                 case REQUEST_CONNECT_DEVICE:
620                         // When DeviceListActivity returns with a device to connect to
621                         if (resultCode == Activity.RESULT_OK) {
622                                 connectDevice(data);
623                         }
624                         break;
625                 case REQUEST_ENABLE_BT:
626                         // When the request to enable Bluetooth returns
627                         if (resultCode == Activity.RESULT_OK) {
628                                 // Bluetooth is now enabled, so set up a chat session
629                                 //setupChat();
630                         } else {
631                                 // User did not enable Bluetooth or an error occured
632                                 AltosDebug.error("BT not enabled");
633                                 stopService(new Intent(AltosDroid.this, TelemetryService.class));
634                                 Toast.makeText(this, R.string.bt_not_enabled, Toast.LENGTH_SHORT).show();
635                                 finish();
636                         }
637                         break;
638                 case REQUEST_MAP_TYPE:
639                         if (resultCode == Activity.RESULT_OK)
640                                 set_map_type(data);
641                         break;
642                 }
643         }
644
645         private void connectUsb(UsbDevice device) {
646                 if (mService == null)
647                         pending_usb_device = device;
648                 else {
649                         // Attempt to connect to the device
650                         try {
651                                 mService.send(Message.obtain(null, TelemetryService.MSG_OPEN_USB, device));
652                                 AltosDebug.debug("Sent OPEN_USB message");
653                         } catch (RemoteException e) {
654                                 AltosDebug.debug("connect device message failed");
655                         }
656                 }
657         }
658
659         private void connectDevice(Intent data) {
660                 // Attempt to connect to the device
661                 try {
662                         String address = data.getExtras().getString(DeviceListActivity.EXTRA_DEVICE_ADDRESS);
663                         String name = data.getExtras().getString(DeviceListActivity.EXTRA_DEVICE_NAME);
664
665                         AltosDebug.debug("Connecting to " + address + " " + name);
666                         DeviceAddress   a = new DeviceAddress(address, name);
667                         mService.send(Message.obtain(null, TelemetryService.MSG_CONNECT, a));
668                         AltosDebug.debug("Sent connecting message");
669                 } catch (RemoteException e) {
670                         AltosDebug.debug("connect device message failed");
671                 }
672         }
673
674         private void disconnectDevice() {
675                 try {
676                         mService.send(Message.obtain(null, TelemetryService.MSG_DISCONNECT, null));
677                 } catch (RemoteException e) {
678                 }
679         }
680
681         private void set_map_type(Intent data) {
682                 int type = data.getIntExtra(MapTypeActivity.EXTRA_MAP_TYPE, -1);
683
684                 AltosDebug.debug("intent set_map_type %d\n", type);
685                 if (type != -1) {
686                         map_type = type;
687                         for (AltosDroidTab mTab : mTabs)
688                                 mTab.set_map_type(map_type);
689                 }
690         }
691
692         @Override
693         public boolean onCreateOptionsMenu(Menu menu) {
694                 MenuInflater inflater = getMenuInflater();
695                 inflater.inflate(R.menu.option_menu, menu);
696                 return true;
697         }
698
699         void setFrequency(double freq) {
700                 try {
701                         mService.send(Message.obtain(null, TelemetryService.MSG_SETFREQUENCY, freq));
702                 } catch (RemoteException e) {
703                 }
704         }
705
706         void setFrequency(String freq) {
707                 try {
708                         setFrequency (AltosParse.parse_double_net(freq.substring(11, 17)));
709                 } catch (ParseException e) {
710                 }
711         }
712
713         void setBaud(int baud) {
714                 try {
715                         mService.send(Message.obtain(null, TelemetryService.MSG_SETBAUD, baud));
716                 } catch (RemoteException e) {
717                 }
718         }
719
720         void setBaud(String baud) {
721                 try {
722                         int     value = Integer.parseInt(baud);
723                         int     rate = AltosLib.ao_telemetry_rate_38400;
724                         switch (value) {
725                         case 2400:
726                                 rate = AltosLib.ao_telemetry_rate_2400;
727                                 break;
728                         case 9600:
729                                 rate = AltosLib.ao_telemetry_rate_9600;
730                                 break;
731                         case 38400:
732                                 rate = AltosLib.ao_telemetry_rate_38400;
733                                 break;
734                         }
735                         setBaud(rate);
736                 } catch (NumberFormatException e) {
737                 }
738         }
739
740         @Override
741         public boolean onOptionsItemSelected(MenuItem item) {
742                 Intent serverIntent = null;
743                 switch (item.getItemId()) {
744                 case R.id.connect_scan:
745                         if (ensureBluetooth()) {
746                                 // Launch the DeviceListActivity to see devices and do scan
747                                 serverIntent = new Intent(this, DeviceListActivity.class);
748                                 startActivityForResult(serverIntent, REQUEST_CONNECT_DEVICE);
749                         }
750                         return true;
751                 case R.id.disconnect:
752                         /* Disconnect the device
753                          */
754                         disconnectDevice();
755                         return true;
756                 case R.id.quit:
757                         AltosDebug.debug("R.id.quit");
758                         disconnectDevice();
759                         finish();
760                         return true;
761                 case R.id.select_freq:
762                         // Set the TBT radio frequency
763
764                         final String[] frequencies = {
765                                 "Channel 0 (434.550MHz)",
766                                 "Channel 1 (434.650MHz)",
767                                 "Channel 2 (434.750MHz)",
768                                 "Channel 3 (434.850MHz)",
769                                 "Channel 4 (434.950MHz)",
770                                 "Channel 5 (435.050MHz)",
771                                 "Channel 6 (435.150MHz)",
772                                 "Channel 7 (435.250MHz)",
773                                 "Channel 8 (435.350MHz)",
774                                 "Channel 9 (435.450MHz)"
775                         };
776
777                         AlertDialog.Builder builder_freq = new AlertDialog.Builder(this);
778                         builder_freq.setTitle("Pick a frequency");
779                         builder_freq.setItems(frequencies,
780                                          new DialogInterface.OnClickListener() {
781                                                  public void onClick(DialogInterface dialog, int item) {
782                                                          setFrequency(frequencies[item]);
783                                                  }
784                                          });
785                         AlertDialog alert_freq = builder_freq.create();
786                         alert_freq.show();
787                         return true;
788                 case R.id.select_rate:
789                         // Set the TBT baud rate
790
791                         final String[] rates = {
792                                 "38400",
793                                 "9600",
794                                 "2400",
795                         };
796
797                         AlertDialog.Builder builder_rate = new AlertDialog.Builder(this);
798                         builder_rate.setTitle("Pick a baud rate");
799                         builder_rate.setItems(rates,
800                                          new DialogInterface.OnClickListener() {
801                                                  public void onClick(DialogInterface dialog, int item) {
802                                                          setBaud(rates[item]);
803                                                  }
804                                          });
805                         AlertDialog alert_rate = builder_rate.create();
806                         alert_rate.show();
807                         return true;
808                 case R.id.change_units:
809                         boolean imperial = AltosPreferences.imperial_units();
810                         AltosPreferences.set_imperial_units(!imperial);
811                         return true;
812                 case R.id.preload_maps:
813                         serverIntent = new Intent(this, PreloadMapActivity.class);
814                         startActivityForResult(serverIntent, REQUEST_PRELOAD_MAPS);
815                         return true;
816                 case R.id.map_type:
817                         serverIntent = new Intent(this, MapTypeActivity.class);
818                         startActivityForResult(serverIntent, REQUEST_MAP_TYPE);
819                         return true;
820                 }
821                 return false;
822         }
823
824 }