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