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