altosuilib: Get rid of AltosUIVersion.java
[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.util.Log;
46 import android.view.Menu;
47 import android.view.MenuInflater;
48 import android.view.MenuItem;
49 import android.view.Window;
50 import android.view.View;
51 import android.view.LayoutInflater;
52 import android.widget.TabHost;
53 import android.widget.TextView;
54 import android.widget.RelativeLayout;
55 import android.widget.Toast;
56 import android.app.AlertDialog;
57 import android.location.Location;
58 import android.hardware.usb.*;
59
60 import org.altusmetrum.altoslib_7.*;
61
62 public class AltosDroid extends FragmentActivity implements AltosUnitsListener {
63         // Debugging
64         static final String TAG = "AltosDroid";
65         static final boolean D = true;
66
67         // Actions sent to the telemetry server at startup time
68
69         public static final String ACTION_BLUETOOTH = "org.altusmetrum.AltosDroid.BLUETOOTH";
70         public static final String ACTION_USB = "org.altusmetrum.AltosDroid.USB";
71
72         // Message types received by our Handler
73
74         public static final int MSG_STATE           = 1;
75         public static final int MSG_UPDATE_AGE      = 2;
76
77         // Intent request codes
78         public static final int REQUEST_CONNECT_DEVICE = 1;
79         public static final int REQUEST_ENABLE_BT      = 2;
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                                 if(D) Log.d(TAG, "MSG_STATE");
137                                 TelemetryState telemetry_state = (TelemetryState) msg.obj;
138                                 if (telemetry_state == null) {
139                                         Log.d(TAG, "telemetry_state null!");
140                                         return;
141                                 }
142
143                                 ad.update_state(telemetry_state);
144                                 break;
145                         case MSG_UPDATE_AGE:
146                                 if(D) Log.d(TAG, "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                 if(D) Log.e(TAG, "+++ ON CREATE +++");
425
426                 fm = getSupportFragmentManager();
427
428                 // Set up the window layout
429                 requestWindowFeature(Window.FEATURE_CUSTOM_TITLE);
430                 setContentView(R.layout.altosdroid);
431                 getWindow().setFeatureInt(Window.FEATURE_CUSTOM_TITLE, R.layout.custom_title);
432
433                 // Create the Tabs and ViewPager
434                 mTabHost = (TabHost)findViewById(android.R.id.tabhost);
435                 mTabHost.setup();
436
437                 mViewPager = (AltosViewPager)findViewById(R.id.pager);
438                 mViewPager.setOffscreenPageLimit(4);
439
440                 mTabsAdapter = new TabsAdapter(this, mTabHost, mViewPager);
441
442                 mTabsAdapter.addTab(mTabHost.newTabSpec("pad").setIndicator(create_tab_view("Pad")), TabPad.class, null);
443                 mTabsAdapter.addTab(mTabHost.newTabSpec("ascent").setIndicator(create_tab_view("Ascent")), TabAscent.class, null);
444                 mTabsAdapter.addTab(mTabHost.newTabSpec("descent").setIndicator(create_tab_view("Descent")), TabDescent.class, null);
445                 mTabsAdapter.addTab(mTabHost.newTabSpec("landed").setIndicator(create_tab_view("Landed")), TabLanded.class, null);
446                 mTabsAdapter.addTab(mTabHost.newTabSpec("map").setIndicator(create_tab_view("Map")), TabMap.class, null);
447
448                 // Set up the custom title
449                 mTitle = (TextView) findViewById(R.id.title_left_text);
450                 mTitle.setText(R.string.app_name);
451                 mTitle = (TextView) findViewById(R.id.title_right_text);
452
453                 // Display the Version
454                 mVersion = (TextView) findViewById(R.id.version);
455                 mVersion.setText("Version: " + BuildInfo.version +
456                                  "  Built: " + BuildInfo.builddate + " " + BuildInfo.buildtime + " " + BuildInfo.buildtz +
457                                  "  (" + BuildInfo.branch + "-" + BuildInfo.commitnum + "-" + BuildInfo.commithash + ")");
458
459                 mCallsignView  = (TextView) findViewById(R.id.callsign_value);
460                 mRSSIView      = (TextView) findViewById(R.id.rssi_value);
461                 mSerialView    = (TextView) findViewById(R.id.serial_value);
462                 mFlightView    = (TextView) findViewById(R.id.flight_value);
463                 mStateLayout   = (RelativeLayout) findViewById(R.id.state_container);
464                 mStateView     = (TextView) findViewById(R.id.state_value);
465                 mAgeView       = (TextView) findViewById(R.id.age_value);
466         }
467
468         private boolean ensureBluetooth() {
469                 // Get local Bluetooth adapter
470                 mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
471
472                 // If the adapter is null, then Bluetooth is not supported
473                 if (mBluetoothAdapter == null) {
474                         Toast.makeText(this, "Bluetooth is not available", Toast.LENGTH_LONG).show();
475                         return false;
476                 }
477
478                 if (!mBluetoothAdapter.isEnabled()) {
479                         Intent enableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
480                         startActivityForResult(enableIntent, AltosDroid.REQUEST_ENABLE_BT);
481                 }
482
483                 return true;
484         }
485
486         private boolean check_usb() {
487                 UsbDevice       device = AltosUsb.find_device(this, AltosLib.product_basestation);
488
489                 if (device != null) {
490                         Intent          i = new Intent(this, AltosDroid.class);
491                         PendingIntent   pi = PendingIntent.getActivity(this, 0, new Intent("hello world", null, this, AltosDroid.class), 0);
492
493                         if (AltosUsb.request_permission(this, device, pi)) {
494                                 connectUsb(device);
495                         }
496                         start_with_usb = true;
497                         return true;
498                 }
499
500                 start_with_usb = false;
501
502                 return false;
503         }
504
505         private void noticeIntent(Intent intent) {
506
507                 /* Ok, this is pretty convenient.
508                  *
509                  * When a USB device is plugged in, and our 'hotplug'
510                  * intent registration fires, we get an Intent with
511                  * EXTRA_DEVICE set.
512                  *
513                  * When we start up and see a usb device and request
514                  * permission to access it, that queues a
515                  * PendingIntent, which has the EXTRA_DEVICE added in,
516                  * along with the EXTRA_PERMISSION_GRANTED field as
517                  * well.
518                  *
519                  * So, in both cases, we get the device name using the
520                  * same call. We check to see if access was granted,
521                  * in which case we ignore the device field and do our
522                  * usual startup thing.
523                  */
524
525                 UsbDevice device = (UsbDevice) intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
526                 boolean granted = intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, true);
527
528                 if (D) Log.e(TAG, "intent " + intent + " device " + device + " granted " + granted);
529
530                 if (!granted)
531                         device = null;
532
533                 if (device != null) {
534                         if (D) Log.d(TAG, "intent has usb device " + device.toString());
535                         connectUsb(device);
536                 } else {
537
538                         /* 'granted' is only false if this intent came
539                          * from the request_permission call and
540                          * permission was denied. In which case, we
541                          * don't want to loop forever...
542                          */
543                         if (granted) {
544                                 if (D) Log.d(TAG, "check for a USB device at startup");
545                                 if (check_usb())
546                                         return;
547                         }
548                         if (D) Log.d(TAG, "Starting by looking for bluetooth devices");
549                         if (ensureBluetooth())
550                                 return;
551                         finish();
552                 }
553         }
554
555         @Override
556         public void onStart() {
557                 super.onStart();
558                 if(D) Log.e(TAG, "++ ON START ++");
559
560                 noticeIntent(getIntent());
561
562                 // Start Telemetry Service
563                 String  action = start_with_usb ? ACTION_USB : ACTION_BLUETOOTH;
564
565                 startService(new Intent(action, null, AltosDroid.this, TelemetryService.class));
566
567                 doBindService();
568
569                 if (mAltosVoice == null)
570                         mAltosVoice = new AltosVoice(this);
571
572         }
573
574         @Override
575         public void onNewIntent(Intent intent) {
576                 super.onNewIntent(intent);
577                 if(D) Log.d(TAG, "onNewIntent");
578                 noticeIntent(intent);
579         }
580
581         @Override
582         public void onResume() {
583                 super.onResume();
584                 if(D) Log.e(TAG, "+ ON RESUME +");
585         }
586
587         @Override
588         public void onPause() {
589                 super.onPause();
590                 if(D) Log.e(TAG, "- ON PAUSE -");
591         }
592
593         @Override
594         public void onStop() {
595                 super.onStop();
596                 if(D) Log.e(TAG, "-- ON STOP --");
597
598                 doUnbindService();
599                 if (mAltosVoice != null) {
600                         mAltosVoice.stop();
601                         mAltosVoice = null;
602                 }
603         }
604
605         @Override
606         public void onDestroy() {
607                 super.onDestroy();
608                 if(D) Log.e(TAG, "--- ON DESTROY ---");
609
610                 if (mAltosVoice != null) mAltosVoice.stop();
611                 stop_timer();
612         }
613
614         protected void onActivityResult(int requestCode, int resultCode, Intent data) {
615                 if(D) Log.d(TAG, "onActivityResult " + resultCode);
616                 switch (requestCode) {
617                 case REQUEST_CONNECT_DEVICE:
618                         // When DeviceListActivity returns with a device to connect to
619                         if (resultCode == Activity.RESULT_OK) {
620                                 connectDevice(data);
621                         }
622                         break;
623                 case REQUEST_ENABLE_BT:
624                         // When the request to enable Bluetooth returns
625                         if (resultCode == Activity.RESULT_OK) {
626                                 // Bluetooth is now enabled, so set up a chat session
627                                 //setupChat();
628                         } else {
629                                 // User did not enable Bluetooth or an error occured
630                                 Log.e(TAG, "BT not enabled");
631                                 stopService(new Intent(AltosDroid.this, TelemetryService.class));
632                                 Toast.makeText(this, R.string.bt_not_enabled, Toast.LENGTH_SHORT).show();
633                                 finish();
634                         }
635                         break;
636                 }
637         }
638
639         private void connectUsb(UsbDevice device) {
640                 if (mService == null)
641                         pending_usb_device = device;
642                 else {
643                         // Attempt to connect to the device
644                         try {
645                                 mService.send(Message.obtain(null, TelemetryService.MSG_OPEN_USB, device));
646                                 if (D) Log.d(TAG, "Sent OPEN_USB message");
647                         } catch (RemoteException e) {
648                                 if (D) Log.e(TAG, "connect device message failed");
649                         }
650                 }
651         }
652
653         private void connectDevice(Intent data) {
654                 // Attempt to connect to the device
655                 try {
656                         String address = data.getExtras().getString(DeviceListActivity.EXTRA_DEVICE_ADDRESS);
657                         String name = data.getExtras().getString(DeviceListActivity.EXTRA_DEVICE_NAME);
658
659                         if (D) Log.d(TAG, "Connecting to " + address + " " + name);
660                         DeviceAddress   a = new DeviceAddress(address, name);
661                         mService.send(Message.obtain(null, TelemetryService.MSG_CONNECT, a));
662                         if (D) Log.d(TAG, "Sent connecting message");
663                 } catch (RemoteException e) {
664                         if (D) Log.e(TAG, "connect device message failed");
665                 }
666         }
667
668         private void disconnectDevice() {
669                 try {
670                         mService.send(Message.obtain(null, TelemetryService.MSG_DISCONNECT, null));
671                 } catch (RemoteException e) {
672                 }
673         }
674
675         @Override
676         public boolean onCreateOptionsMenu(Menu menu) {
677                 MenuInflater inflater = getMenuInflater();
678                 inflater.inflate(R.menu.option_menu, menu);
679                 return true;
680         }
681
682         void setFrequency(double freq) {
683                 try {
684                         mService.send(Message.obtain(null, TelemetryService.MSG_SETFREQUENCY, freq));
685                 } catch (RemoteException e) {
686                 }
687         }
688
689         void setFrequency(String freq) {
690                 try {
691                         setFrequency (AltosParse.parse_double_net(freq.substring(11, 17)));
692                 } catch (ParseException e) {
693                 }
694         }
695
696         void setBaud(int baud) {
697                 try {
698                         mService.send(Message.obtain(null, TelemetryService.MSG_SETBAUD, baud));
699                 } catch (RemoteException e) {
700                 }
701         }
702
703         void setBaud(String baud) {
704                 try {
705                         int     value = Integer.parseInt(baud);
706                         int     rate = AltosLib.ao_telemetry_rate_38400;
707                         switch (value) {
708                         case 2400:
709                                 rate = AltosLib.ao_telemetry_rate_2400;
710                                 break;
711                         case 9600:
712                                 rate = AltosLib.ao_telemetry_rate_9600;
713                                 break;
714                         case 38400:
715                                 rate = AltosLib.ao_telemetry_rate_38400;
716                                 break;
717                         }
718                         setBaud(rate);
719                 } catch (NumberFormatException e) {
720                 }
721         }
722
723         @Override
724         public boolean onOptionsItemSelected(MenuItem item) {
725                 Intent serverIntent = null;
726                 switch (item.getItemId()) {
727                 case R.id.connect_scan:
728                         if (ensureBluetooth()) {
729                                 // Launch the DeviceListActivity to see devices and do scan
730                                 serverIntent = new Intent(this, DeviceListActivity.class);
731                                 startActivityForResult(serverIntent, REQUEST_CONNECT_DEVICE);
732                         }
733                         return true;
734                 case R.id.disconnect:
735                         /* Disconnect the device
736                          */
737                         disconnectDevice();
738                         return true;
739                 case R.id.quit:
740                         Log.d(TAG, "R.id.quit");
741                         disconnectDevice();
742                         finish();
743                         return true;
744                 case R.id.select_freq:
745                         // Set the TBT radio frequency
746
747                         final String[] frequencies = {
748                                 "Channel 0 (434.550MHz)",
749                                 "Channel 1 (434.650MHz)",
750                                 "Channel 2 (434.750MHz)",
751                                 "Channel 3 (434.850MHz)",
752                                 "Channel 4 (434.950MHz)",
753                                 "Channel 5 (435.050MHz)",
754                                 "Channel 6 (435.150MHz)",
755                                 "Channel 7 (435.250MHz)",
756                                 "Channel 8 (435.350MHz)",
757                                 "Channel 9 (435.450MHz)"
758                         };
759
760                         AlertDialog.Builder builder_freq = new AlertDialog.Builder(this);
761                         builder_freq.setTitle("Pick a frequency");
762                         builder_freq.setItems(frequencies,
763                                          new DialogInterface.OnClickListener() {
764                                                  public void onClick(DialogInterface dialog, int item) {
765                                                          setFrequency(frequencies[item]);
766                                                  }
767                                          });
768                         AlertDialog alert_freq = builder_freq.create();
769                         alert_freq.show();
770                         return true;
771                 case R.id.select_rate:
772                         // Set the TBT baud rate
773
774                         final String[] rates = {
775                                 "38400",
776                                 "9600",
777                                 "2400",
778                         };
779
780                         AlertDialog.Builder builder_rate = new AlertDialog.Builder(this);
781                         builder_rate.setTitle("Pick a baud rate");
782                         builder_rate.setItems(rates,
783                                          new DialogInterface.OnClickListener() {
784                                                  public void onClick(DialogInterface dialog, int item) {
785                                                          setBaud(rates[item]);
786                                                  }
787                                          });
788                         AlertDialog alert_rate = builder_rate.create();
789                         alert_rate.show();
790                         return true;
791                 case R.id.change_units:
792                         boolean imperial = AltosPreferences.imperial_units();
793                         AltosPreferences.set_imperial_units(!imperial);
794                         return true;
795                 }
796                 return false;
797         }
798
799 }