altosdroid: Run even without Bluetooth
[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.text.*;
22 import java.util.*;
23 import java.io.*;
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.content.res.Resources;
41 import android.support.v4.app.FragmentActivity;
42 import android.support.v4.app.FragmentManager;
43 import android.util.DisplayMetrics;
44 import android.view.*;
45 import android.widget.*;
46 import android.app.AlertDialog;
47 import android.location.Location;
48 import android.location.LocationManager;
49 import android.location.LocationListener;
50 import android.hardware.usb.*;
51 import android.graphics.*;
52 import android.graphics.drawable.*;
53
54 import org.altusmetrum.altoslib_10.*;
55
56 public class AltosDroid extends FragmentActivity implements AltosUnitsListener, LocationListener {
57
58         // Actions sent to the telemetry server at startup time
59
60         public static final String ACTION_BLUETOOTH = "org.altusmetrum.AltosDroid.BLUETOOTH";
61         public static final String ACTION_USB = "org.altusmetrum.AltosDroid.USB";
62
63         // Message types received by our Handler
64
65         public static final int MSG_STATE           = 1;
66         public static final int MSG_UPDATE_AGE      = 2;
67
68         // Intent request codes
69         public static final int REQUEST_CONNECT_DEVICE = 1;
70         public static final int REQUEST_ENABLE_BT      = 2;
71         public static final int REQUEST_PRELOAD_MAPS   = 3;
72         public static final int REQUEST_MAP_TYPE       = 4;
73
74         public int map_type = AltosMap.maptype_hybrid;
75
76         public static FragmentManager   fm;
77
78         private BluetoothAdapter mBluetoothAdapter = null;
79
80         // Flight state values
81         private TextView mCallsignView;
82         private TextView mRSSIView;
83         private TextView mSerialView;
84         private TextView mFlightView;
85         private RelativeLayout mStateLayout;
86         private TextView mStateView;
87         private TextView mAgeView;
88         private boolean  mAgeViewOld;
89         private int mAgeNewColor;
90         private int mAgeOldColor;
91
92         public static final String      tab_pad_name = "pad";
93         public static final String      tab_flight_name = "flight";
94         public static final String      tab_recover_name = "recover";
95         public static final String      tab_map_name = "map";
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         public Location location = null;
104
105         // Tabs
106         TabHost         mTabHost;
107         AltosViewPager  mViewPager;
108         TabsAdapter     mTabsAdapter;
109         ArrayList<AltosDroidTab> mTabs = new ArrayList<AltosDroidTab>();
110         int             tabHeight;
111
112         // Timer and Saved flight state for Age calculation
113         private Timer timer;
114         AltosState saved_state;
115         TelemetryState  telemetry_state;
116         Integer[]       serials;
117
118         UsbDevice       pending_usb_device;
119         boolean         start_with_usb;
120
121         // Service
122         private boolean mIsBound   = false;
123         private Messenger mService = null;
124         final Messenger mMessenger = new Messenger(new IncomingHandler(this));
125
126         // Text to Speech
127         private AltosVoice mAltosVoice = null;
128
129         // The Handler that gets information back from the Telemetry Service
130         static class IncomingHandler extends Handler {
131                 private final WeakReference<AltosDroid> mAltosDroid;
132                 IncomingHandler(AltosDroid ad) { mAltosDroid = new WeakReference<AltosDroid>(ad); }
133
134                 @Override
135                 public void handleMessage(Message msg) {
136                         AltosDroid ad = mAltosDroid.get();
137
138                         switch (msg.what) {
139                         case MSG_STATE:
140                                 if (msg.obj == null) {
141                                         AltosDebug.debug("telemetry_state null!");
142                                         return;
143                                 }
144                                 ad.update_state((TelemetryState) msg.obj);
145                                 break;
146                         case 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                                 setTitle(str);
225                         } else {
226                                 setTitle(R.string.title_connected_to);
227                         }
228                         break;
229                 case TelemetryState.CONNECT_CONNECTING:
230                         if (telemetry_state.address != null)
231                                 setTitle(String.format("Connecting to %s...", telemetry_state.address.name));
232                         else
233                                 setTitle("Connecting to something...");
234                         break;
235                 case TelemetryState.CONNECT_DISCONNECTED:
236                 case TelemetryState.CONNECT_NONE:
237                         setTitle(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         int     selected_serial = 0;
258         int     current_serial;
259         long    switch_time;
260
261         void set_switch_time() {
262                 switch_time = System.currentTimeMillis();
263                 selected_serial = 0;
264         }
265
266         boolean registered_units_listener;
267
268         void update_state(TelemetryState new_telemetry_state) {
269
270                 if (new_telemetry_state != null)
271                         telemetry_state = new_telemetry_state;
272
273                 if (selected_serial != 0)
274                         current_serial = selected_serial;
275
276                 if (current_serial == 0)
277                         current_serial = telemetry_state.latest_serial;
278
279                 if (!registered_units_listener) {
280                         registered_units_listener = true;
281                         AltosPreferences.register_units_listener(this);
282                 }
283
284                 serials = telemetry_state.states.keySet().toArray(new Integer[0]);
285                 Arrays.sort(serials);
286
287                 update_title(telemetry_state);
288
289                 AltosState      state = null;
290                 boolean         aged = true;
291
292                 if (telemetry_state.states.containsKey(current_serial)) {
293                         state = telemetry_state.states.get(current_serial);
294                         int age = state_age(state);
295                         if (age < 20)
296                                 aged = false;
297                         if (current_serial == selected_serial)
298                                 aged = false;
299                         else if (switch_time != 0 && (switch_time - state.received_time) > 0)
300                                 aged = true;
301                 }
302
303                 if (aged) {
304                         AltosState      newest_state = null;
305                         int             newest_age = 0;
306
307                         for (int serial : telemetry_state.states.keySet()) {
308                                 AltosState      existing = telemetry_state.states.get(serial);
309                                 int             existing_age = state_age(existing);
310
311                                 if (newest_state == null || existing_age < newest_age) {
312                                         newest_state = existing;
313                                         newest_age = existing_age;
314                                 }
315                         }
316
317                         if (newest_state != null)
318                                 state = newest_state;
319                 }
320
321                 update_ui(telemetry_state, state);
322
323                 start_timer();
324         }
325
326         boolean same_string(String a, String b) {
327                 if (a == null) {
328                         if (b == null)
329                                 return true;
330                         return false;
331                 } else {
332                         if (b == null)
333                                 return false;
334                         return a.equals(b);
335                 }
336         }
337
338
339         private int blend_component(int a, int b, double r, int shift, int mask) {
340                 return ((int) (((a >> shift) & mask) * r + ((b >> shift) & mask) * (1 - r)) & mask) << shift;
341         }
342         private int blend_color(int a, int b, double r) {
343                 return (blend_component(a, b, r, 0, 0xff) |
344                         blend_component(a, b, r, 8, 0xff) |
345                         blend_component(a, b, r, 16, 0xff) |
346                         blend_component(a, b, r, 24, 0xff));
347         }
348
349         int state_age(AltosState state) {
350                 return (int) ((System.currentTimeMillis() - state.received_time + 500) / 1000);
351         }
352
353         void set_screen_on(int age) {
354                 if (age < 60)
355                         getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
356                 else
357                         getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
358         }
359
360         void update_age() {
361                 if (saved_state != null) {
362                         int age = state_age(saved_state);
363
364                         double age_scale = age / 100.0;
365
366                         if (age_scale > 1.0)
367                                 age_scale = 1.0;
368
369                         mAgeView.setTextColor(blend_color(mAgeOldColor, mAgeNewColor, age_scale));
370
371                         set_screen_on(age);
372
373                         String  text;
374                         if (age < 60)
375                                 text = String.format("%ds", age);
376                         else if (age < 60 * 60)
377                                 text = String.format("%dm", age / 60);
378                         else if (age < 60 * 60 * 24)
379                                 text = String.format("%dh", age / (60 * 60));
380                         else
381                                 text = String.format("%dd", age / (24 * 60 * 60));
382                         mAgeView.setText(text);
383                 }
384         }
385
386         void update_ui(TelemetryState telem_state, AltosState state) {
387
388                 int prev_state = AltosLib.ao_flight_invalid;
389
390                 AltosGreatCircle from_receiver = null;
391
392                 if (saved_state != null)
393                         prev_state = saved_state.state();
394
395                 if (state != null) {
396                         set_screen_on(state_age(state));
397
398                         if (state.state() == AltosLib.ao_flight_stateless) {
399                                 boolean prev_locked = false;
400                                 boolean locked = false;
401
402                                 if(state.gps != null)
403                                         locked = state.gps.locked;
404                                 if (saved_state != null && saved_state.gps != null)
405                                         prev_locked = saved_state.gps.locked;
406                                 if (prev_locked != locked) {
407                                         String currentTab = mTabHost.getCurrentTabTag();
408                                         if (locked) {
409                                                 if (currentTab.equals(tab_pad_name)) mTabHost.setCurrentTabByTag(tab_flight_name);
410                                         } else {
411                                                 if (currentTab.equals(tab_flight_name)) mTabHost.setCurrentTabByTag(tab_pad_name);
412                                         }
413                                 }
414                         } else {
415                                 if (prev_state != state.state()) {
416                                         String currentTab = mTabHost.getCurrentTabTag();
417                                         switch (state.state()) {
418                                         case AltosLib.ao_flight_boost:
419                                                 if (currentTab.equals(tab_pad_name)) mTabHost.setCurrentTabByTag(tab_flight_name);
420                                                 break;
421                                         case AltosLib.ao_flight_landed:
422                                                 if (currentTab.equals(tab_flight_name)) mTabHost.setCurrentTabByTag(tab_recover_name);
423                                                 break;
424                                         case AltosLib.ao_flight_stateless:
425                                                 if (currentTab.equals(tab_pad_name)) mTabHost.setCurrentTabByTag(tab_flight_name);
426                                                 break;
427                                         }
428                                 }
429                         }
430
431                         if (location != null && state.gps != null && state.gps.locked) {
432                                 double altitude = 0;
433                                 if (location.hasAltitude())
434                                         altitude = location.getAltitude();
435                                 from_receiver = new AltosGreatCircle(location.getLatitude(),
436                                                                      location.getLongitude(),
437                                                                      altitude,
438                                                                      state.gps.lat,
439                                                                      state.gps.lon,
440                                                                      state.gps.alt);
441                         }
442
443                         if (saved_state == null || !same_string(saved_state.callsign, state.callsign)) {
444                                 mCallsignView.setText(state.callsign);
445                         }
446                         if (saved_state == null || state.serial != saved_state.serial) {
447                                 mSerialView.setText(String.format("%d", state.serial));
448                         }
449                         if (saved_state == null || state.flight != saved_state.flight) {
450                                 if (state.flight == AltosLib.MISSING)
451                                         mFlightView.setText("");
452                                 else
453                                         mFlightView.setText(String.format("%d", state.flight));
454                         }
455                         if (saved_state == null || state.state() != saved_state.state()) {
456                                 if (state.state() == AltosLib.ao_flight_stateless) {
457                                         mStateLayout.setVisibility(View.GONE);
458                                 } else {
459                                         mStateView.setText(state.state_name());
460                                         mStateLayout.setVisibility(View.VISIBLE);
461                                 }
462                         }
463                         if (saved_state == null || state.rssi != saved_state.rssi) {
464                                 mRSSIView.setText(String.format("%d", state.rssi));
465                         }
466                 }
467
468                 for (AltosDroidTab mTab : mTabs)
469                         mTab.update_ui(telem_state, state, from_receiver, location, mTab == mTabsAdapter.currentItem());
470
471                 if (mAltosVoice != null)
472                         mAltosVoice.tell(telem_state, state, from_receiver, location, (AltosDroidTab) mTabsAdapter.currentItem());
473
474                 saved_state = state;
475         }
476
477         private void onTimerTick() {
478                 try {
479                         mMessenger.send(Message.obtain(null, MSG_UPDATE_AGE));
480                 } catch (RemoteException e) {
481                 }
482         }
483
484         static String pos(double p, String pos, String neg) {
485                 String  h = pos;
486                 if (p == AltosLib.MISSING)
487                         return "";
488                 if (p < 0) {
489                         h = neg;
490                         p = -p;
491                 }
492                 int deg = (int) Math.floor(p);
493                 double min = (p - Math.floor(p)) * 60.0;
494                 return String.format("%d°%9.4f\" %s", deg, min, h);
495         }
496
497         static String number(String format, double value) {
498                 if (value == AltosLib.MISSING)
499                         return "";
500                 return String.format(format, value);
501         }
502
503         static String integer(String format, int value) {
504                 if (value == AltosLib.MISSING)
505                         return "";
506                 return String.format(format, value);
507         }
508
509         private View create_tab_view(String label) {
510                 LayoutInflater inflater = (LayoutInflater) this.getLayoutInflater();
511                 View tab_view = inflater.inflate(R.layout.tab_layout, null);
512                 TextView text_view = (TextView) tab_view.findViewById (R.id.tabLabel);
513                 text_view.setText(label);
514                 return tab_view;
515         }
516
517         public void set_map_source(int source) {
518                 for (AltosDroidTab mTab : mTabs)
519                         mTab.set_map_source(source);
520         }
521
522         @Override
523         public void onCreate(Bundle savedInstanceState) {
524                 super.onCreate(savedInstanceState);
525                 AltosDebug.init(this);
526                 AltosDebug.debug("+++ ON CREATE +++");
527
528                 // Initialise preferences
529                 AltosDroidPreferences.init(this);
530
531                 fm = getSupportFragmentManager();
532
533                 // Set up the window layout
534                 setContentView(R.layout.altosdroid);
535
536                 // Create the Tabs and ViewPager
537                 mTabHost = (TabHost)findViewById(android.R.id.tabhost);
538                 mTabHost.setup();
539
540                 mViewPager = (AltosViewPager)findViewById(R.id.pager);
541                 mViewPager.setOffscreenPageLimit(4);
542
543                 mTabsAdapter = new TabsAdapter(this, mTabHost, mViewPager);
544
545                 mTabsAdapter.addTab(mTabHost.newTabSpec(tab_pad_name).setIndicator(create_tab_view("Pad")), TabPad.class, null);
546                 mTabsAdapter.addTab(mTabHost.newTabSpec(tab_flight_name).setIndicator(create_tab_view("Flight")), TabFlight.class, null);
547                 mTabsAdapter.addTab(mTabHost.newTabSpec(tab_recover_name).setIndicator(create_tab_view("Recover")), TabRecover.class, null);
548                 mTabsAdapter.addTab(mTabHost.newTabSpec(tab_map_name).setIndicator(create_tab_view("Map")), TabMap.class, null);
549
550                 // Display the Version
551                 mVersion = (TextView) findViewById(R.id.version);
552                 mVersion.setText("Version: " + BuildInfo.version +
553                                  "  Built: " + BuildInfo.builddate + " " + BuildInfo.buildtime + " " + BuildInfo.buildtz +
554                                  "  (" + BuildInfo.branch + "-" + BuildInfo.commitnum + "-" + BuildInfo.commithash + ")");
555
556                 mCallsignView  = (TextView) findViewById(R.id.callsign_value);
557                 mRSSIView      = (TextView) findViewById(R.id.rssi_value);
558                 mSerialView    = (TextView) findViewById(R.id.serial_value);
559                 mFlightView    = (TextView) findViewById(R.id.flight_value);
560                 mStateLayout   = (RelativeLayout) findViewById(R.id.state_container);
561                 mStateView     = (TextView) findViewById(R.id.state_value);
562                 mAgeView       = (TextView) findViewById(R.id.age_value);
563                 mAgeNewColor   = mAgeView.getTextColors().getDefaultColor();
564                 mAgeOldColor   = getResources().getColor(R.color.old_color);
565         }
566
567         private void ensureBluetooth() {
568                 // Get local Bluetooth adapter
569                 mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
570
571                 /* if there is a BT adapter and it isn't turned on, then turn it on */
572                 if (mBluetoothAdapter != null && !mBluetoothAdapter.isEnabled()) {
573                         Intent enableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
574                         startActivityForResult(enableIntent, AltosDroid.REQUEST_ENABLE_BT);
575                 }
576         }
577
578         private boolean check_usb() {
579                 UsbDevice       device = AltosUsb.find_device(this, AltosLib.product_basestation);
580
581                 if (device != null) {
582                         Intent          i = new Intent(this, AltosDroid.class);
583                         PendingIntent   pi = PendingIntent.getActivity(this, 0, new Intent("hello world", null, this, AltosDroid.class), 0);
584
585                         if (AltosUsb.request_permission(this, device, pi)) {
586                                 connectUsb(device);
587                         }
588                         start_with_usb = true;
589                         return true;
590                 }
591
592                 start_with_usb = false;
593
594                 return false;
595         }
596
597         private void noticeIntent(Intent intent) {
598
599                 /* Ok, this is pretty convenient.
600                  *
601                  * When a USB device is plugged in, and our 'hotplug'
602                  * intent registration fires, we get an Intent with
603                  * EXTRA_DEVICE set.
604                  *
605                  * When we start up and see a usb device and request
606                  * permission to access it, that queues a
607                  * PendingIntent, which has the EXTRA_DEVICE added in,
608                  * along with the EXTRA_PERMISSION_GRANTED field as
609                  * well.
610                  *
611                  * So, in both cases, we get the device name using the
612                  * same call. We check to see if access was granted,
613                  * in which case we ignore the device field and do our
614                  * usual startup thing.
615                  */
616
617                 UsbDevice device = (UsbDevice) intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
618                 boolean granted = intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, true);
619
620                 AltosDebug.debug("intent %s device %s granted %s", intent, device, granted);
621
622                 if (!granted)
623                         device = null;
624
625                 if (device != null) {
626                         AltosDebug.debug("intent has usb device " + device.toString());
627                         connectUsb(device);
628                 } else {
629
630                         /* 'granted' is only false if this intent came
631                          * from the request_permission call and
632                          * permission was denied. In which case, we
633                          * don't want to loop forever...
634                          */
635                         if (granted) {
636                                 AltosDebug.debug("check for a USB device at startup");
637                                 if (check_usb())
638                                         return;
639                         }
640                         AltosDebug.debug("Starting by looking for bluetooth devices");
641                         ensureBluetooth();
642                 }
643         }
644
645         @Override
646         public void onStart() {
647                 super.onStart();
648                 AltosDebug.debug("++ ON START ++");
649
650                 set_switch_time();
651
652                 noticeIntent(getIntent());
653
654                 // Start Telemetry Service
655                 String  action = start_with_usb ? ACTION_USB : ACTION_BLUETOOTH;
656
657                 startService(new Intent(action, null, AltosDroid.this, TelemetryService.class));
658
659                 doBindService();
660
661                 if (mAltosVoice == null)
662                         mAltosVoice = new AltosVoice(this);
663
664         }
665
666         @Override
667         public void onNewIntent(Intent intent) {
668                 super.onNewIntent(intent);
669                 AltosDebug.debug("onNewIntent");
670                 noticeIntent(intent);
671         }
672
673         @Override
674         public void onResume() {
675                 super.onResume();
676                 AltosDebug.debug("+ ON RESUME +");
677
678                 // Listen for GPS and Network position updates
679                 LocationManager locationManager = (LocationManager) this.getSystemService(Context.LOCATION_SERVICE);
680                 locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 1000, 1, this);
681
682                 location = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER);
683
684                 AltosDebug.debug("Resume, location is %f,%f\n",
685                                  location.getLatitude(),
686                                  location.getLongitude());
687
688                 update_ui(telemetry_state, saved_state);
689         }
690
691         @Override
692         public void onPause() {
693                 super.onPause();
694                 AltosDebug.debug("- ON PAUSE -");
695                 // Stop listening for location updates
696                 ((LocationManager) getSystemService(Context.LOCATION_SERVICE)).removeUpdates(this);
697         }
698
699         @Override
700         public void onStop() {
701                 super.onStop();
702                 AltosDebug.debug("-- ON STOP --");
703         }
704
705         @Override
706         public void onDestroy() {
707                 super.onDestroy();
708                 AltosDebug.debug("--- ON DESTROY ---");
709
710                 doUnbindService();
711                 if (mAltosVoice != null) {
712                         mAltosVoice.stop();
713                         mAltosVoice = null;
714                 }
715                 stop_timer();
716         }
717
718         protected void onActivityResult(int requestCode, int resultCode, Intent data) {
719                 AltosDebug.debug("onActivityResult " + resultCode);
720                 switch (requestCode) {
721                 case REQUEST_CONNECT_DEVICE:
722                         // When DeviceListActivity returns with a device to connect to
723                         if (resultCode == Activity.RESULT_OK) {
724                                 connectDevice(data);
725                         }
726                         break;
727                 case REQUEST_ENABLE_BT:
728                         // When the request to enable Bluetooth returns
729                         if (resultCode == Activity.RESULT_OK) {
730                                 // Bluetooth is now enabled, so set up a chat session
731                                 //setupChat();
732                                 AltosDebug.debug("BT enabled");
733                                 bluetoothEnabled(data);
734                         } else {
735                                 // User did not enable Bluetooth or an error occured
736                                 AltosDebug.debug("BT not enabled");
737                         }
738                         break;
739                 case REQUEST_MAP_TYPE:
740                         if (resultCode == Activity.RESULT_OK)
741                                 set_map_type(data);
742                         break;
743                 }
744         }
745
746         private void connectUsb(UsbDevice device) {
747                 if (mService == null)
748                         pending_usb_device = device;
749                 else {
750                         // Attempt to connect to the device
751                         try {
752                                 mService.send(Message.obtain(null, TelemetryService.MSG_OPEN_USB, device));
753                                 AltosDebug.debug("Sent OPEN_USB message");
754                         } catch (RemoteException e) {
755                                 AltosDebug.debug("connect device message failed");
756                         }
757                 }
758         }
759
760         private void bluetoothEnabled(Intent data) {
761                 try {
762                         mService.send(Message.obtain(null, TelemetryService.MSG_BLUETOOTH_ENABLED, null));
763                 } catch (RemoteException e) {
764                         AltosDebug.debug("send BT enabled message failed");
765                 }
766         }
767
768         private void connectDevice(Intent data) {
769                 // Attempt to connect to the device
770                 try {
771                         String address = data.getExtras().getString(DeviceListActivity.EXTRA_DEVICE_ADDRESS);
772                         String name = data.getExtras().getString(DeviceListActivity.EXTRA_DEVICE_NAME);
773
774                         AltosDebug.debug("Connecting to " + address + " " + name);
775                         DeviceAddress   a = new DeviceAddress(address, name);
776                         mService.send(Message.obtain(null, TelemetryService.MSG_CONNECT, a));
777                         AltosDebug.debug("Sent connecting message");
778                 } catch (RemoteException e) {
779                         AltosDebug.debug("connect device message failed");
780                 }
781         }
782
783         private void disconnectDevice() {
784                 try {
785                         mService.send(Message.obtain(null, TelemetryService.MSG_DISCONNECT, null));
786                 } catch (RemoteException e) {
787                 }
788         }
789
790         private void set_map_type(Intent data) {
791                 int type = data.getIntExtra(MapTypeActivity.EXTRA_MAP_TYPE, -1);
792
793                 AltosDebug.debug("intent set_map_type %d\n", type);
794                 if (type != -1) {
795                         map_type = type;
796                         for (AltosDroidTab mTab : mTabs)
797                                 mTab.set_map_type(map_type);
798                 }
799         }
800
801         @Override
802         public boolean onCreateOptionsMenu(Menu menu) {
803                 MenuInflater inflater = getMenuInflater();
804                 inflater.inflate(R.menu.option_menu, menu);
805                 return true;
806         }
807
808         void setFrequency(double freq) {
809                 try {
810                         mService.send(Message.obtain(null, TelemetryService.MSG_SETFREQUENCY, freq));
811                         set_switch_time();
812                 } catch (RemoteException e) {
813                 }
814         }
815
816         void setFrequency(String freq) {
817                 try {
818                         setFrequency (AltosParse.parse_double_net(freq.substring(11, 17)));
819                 } catch (ParseException e) {
820                 }
821         }
822
823         void setBaud(int baud) {
824                 try {
825                         mService.send(Message.obtain(null, TelemetryService.MSG_SETBAUD, baud));
826                         set_switch_time();
827                 } catch (RemoteException e) {
828                 }
829         }
830
831         void setBaud(String baud) {
832                 try {
833                         int     value = Integer.parseInt(baud);
834                         int     rate = AltosLib.ao_telemetry_rate_38400;
835                         switch (value) {
836                         case 2400:
837                                 rate = AltosLib.ao_telemetry_rate_2400;
838                                 break;
839                         case 9600:
840                                 rate = AltosLib.ao_telemetry_rate_9600;
841                                 break;
842                         case 38400:
843                                 rate = AltosLib.ao_telemetry_rate_38400;
844                                 break;
845                         }
846                         setBaud(rate);
847                 } catch (NumberFormatException e) {
848                 }
849         }
850
851         void select_tracker(int serial) {
852                 int i;
853
854                 AltosDebug.debug("select tracker %d\n", serial);
855
856                 if (serial == selected_serial) {
857                         AltosDebug.debug("%d already selected\n", serial);
858                         return;
859                 }
860
861                 if (serial != 0) {
862                         for (i = 0; i < serials.length; i++)
863                                 if (serials[i] == serial)
864                                         break;
865
866                         if (i == serials.length) {
867                                 AltosDebug.debug("attempt to select unknown tracker %d\n", serial);
868                                 return;
869                         }
870                 }
871
872                 current_serial = selected_serial = serial;
873                 update_state(null);
874         }
875
876         void touch_trackers(Integer[] serials) {
877                 AlertDialog.Builder builder_tracker = new AlertDialog.Builder(this);
878                 builder_tracker.setTitle("Select Tracker");
879                 final String[] trackers = new String[serials.length + 1];
880                 trackers[0] = "Auto";
881                 for (int i = 0; i < serials.length; i++)
882                         trackers[i+1] = String.format("%d", serials[i]);
883                 builder_tracker.setItems(trackers,
884                                          new DialogInterface.OnClickListener() {
885                                                  public void onClick(DialogInterface dialog, int item) {
886                                                          if (item == 0)
887                                                                  select_tracker(0);
888                                                          else
889                                                                  select_tracker(Integer.parseInt(trackers[item]));
890                                                  }
891                                          });
892                 AlertDialog alert_tracker = builder_tracker.create();
893                 alert_tracker.show();
894         }
895
896         void delete_track(int serial) {
897                 try {
898                         mService.send(Message.obtain(null, TelemetryService.MSG_DELETE_SERIAL, (Integer) serial));
899                 } catch (Exception ex) {
900                 }
901         }
902
903         @Override
904         public boolean onOptionsItemSelected(MenuItem item) {
905                 Intent serverIntent = null;
906                 switch (item.getItemId()) {
907                 case R.id.connect_scan:
908                         ensureBluetooth();
909                         // Launch the DeviceListActivity to see devices and do scan
910                         serverIntent = new Intent(this, DeviceListActivity.class);
911                         startActivityForResult(serverIntent, REQUEST_CONNECT_DEVICE);
912                         return true;
913                 case R.id.disconnect:
914                         /* Disconnect the device
915                          */
916                         disconnectDevice();
917                         return true;
918                 case R.id.quit:
919                         AltosDebug.debug("R.id.quit");
920                         disconnectDevice();
921                         finish();
922                         return true;
923                 case R.id.select_freq:
924                         // Set the TBT radio frequency
925
926                         final String[] frequencies = {
927                                 "Channel 0 (434.550MHz)",
928                                 "Channel 1 (434.650MHz)",
929                                 "Channel 2 (434.750MHz)",
930                                 "Channel 3 (434.850MHz)",
931                                 "Channel 4 (434.950MHz)",
932                                 "Channel 5 (435.050MHz)",
933                                 "Channel 6 (435.150MHz)",
934                                 "Channel 7 (435.250MHz)",
935                                 "Channel 8 (435.350MHz)",
936                                 "Channel 9 (435.450MHz)"
937                         };
938
939                         AlertDialog.Builder builder_freq = new AlertDialog.Builder(this);
940                         builder_freq.setTitle("Pick a frequency");
941                         builder_freq.setItems(frequencies,
942                                          new DialogInterface.OnClickListener() {
943                                                  public void onClick(DialogInterface dialog, int item) {
944                                                          setFrequency(frequencies[item]);
945                                                  }
946                                          });
947                         AlertDialog alert_freq = builder_freq.create();
948                         alert_freq.show();
949                         return true;
950                 case R.id.select_rate:
951                         // Set the TBT baud rate
952
953                         final String[] rates = {
954                                 "38400",
955                                 "9600",
956                                 "2400",
957                         };
958
959                         AlertDialog.Builder builder_rate = new AlertDialog.Builder(this);
960                         builder_rate.setTitle("Pick a baud rate");
961                         builder_rate.setItems(rates,
962                                          new DialogInterface.OnClickListener() {
963                                                  public void onClick(DialogInterface dialog, int item) {
964                                                          setBaud(rates[item]);
965                                                  }
966                                          });
967                         AlertDialog alert_rate = builder_rate.create();
968                         alert_rate.show();
969                         return true;
970                 case R.id.change_units:
971                         boolean imperial = AltosPreferences.imperial_units();
972                         AltosPreferences.set_imperial_units(!imperial);
973                         return true;
974                 case R.id.preload_maps:
975                         serverIntent = new Intent(this, PreloadMapActivity.class);
976                         startActivityForResult(serverIntent, REQUEST_PRELOAD_MAPS);
977                         return true;
978                 case R.id.map_type:
979                         serverIntent = new Intent(this, MapTypeActivity.class);
980                         startActivityForResult(serverIntent, REQUEST_MAP_TYPE);
981                         return true;
982                 case R.id.map_source:
983                         int source = AltosDroidPreferences.map_source();
984                         int new_source = source == AltosDroidPreferences.MAP_SOURCE_ONLINE ? AltosDroidPreferences.MAP_SOURCE_OFFLINE : AltosDroidPreferences.MAP_SOURCE_ONLINE;
985                         AltosDroidPreferences.set_map_source(new_source);
986                         set_map_source(new_source);
987                         return true;
988                 case R.id.select_tracker:
989                         if (serials != null) {
990                                 String[] trackers = new String[serials.length+1];
991                                 trackers[0] = "Auto";
992                                 for (int i = 0; i < serials.length; i++)
993                                         trackers[i+1] = String.format("%d", serials[i]);
994                                 AlertDialog.Builder builder_serial = new AlertDialog.Builder(this);
995                                 builder_serial.setTitle("Select a tracker");
996                                 builder_serial.setItems(trackers,
997                                                         new DialogInterface.OnClickListener() {
998                                                                 public void onClick(DialogInterface dialog, int item) {
999                                                                         if (item == 0)
1000                                                                                 select_tracker(0);
1001                                                                         else
1002                                                                                 select_tracker(serials[item-1]);
1003                                                                 }
1004                                                         });
1005                                 AlertDialog alert_serial = builder_serial.create();
1006                                 alert_serial.show();
1007
1008                         }
1009                         return true;
1010                 case R.id.delete_track:
1011                         if (serials != null) {
1012                                 String[] trackers = new String[serials.length];
1013                                 for (int i = 0; i < serials.length; i++)
1014                                         trackers[i] = String.format("%d", serials[i]);
1015                                 AlertDialog.Builder builder_serial = new AlertDialog.Builder(this);
1016                                 builder_serial.setTitle("Delete a track");
1017                                 builder_serial.setItems(trackers,
1018                                                         new DialogInterface.OnClickListener() {
1019                                                                 public void onClick(DialogInterface dialog, int item) {
1020                                                                         delete_track(serials[item]);
1021                                                                 }
1022                                                         });
1023                                 AlertDialog alert_serial = builder_serial.create();
1024                                 alert_serial.show();
1025
1026                         }
1027                         return true;
1028                 }
1029                 return false;
1030         }
1031
1032         static String direction(AltosGreatCircle from_receiver,
1033                                 Location receiver) {
1034                 if (from_receiver == null)
1035                         return null;
1036
1037                 if (receiver == null)
1038                         return null;
1039
1040                 if (!receiver.hasBearing())
1041                         return null;
1042
1043                 float   bearing = receiver.getBearing();
1044                 float   heading = (float) from_receiver.bearing - bearing;
1045
1046                 while (heading <= -180.0f)
1047                         heading += 360.0f;
1048                 while (heading > 180.0f)
1049                         heading -= 360.0f;
1050
1051                 int iheading = (int) (heading + 0.5f);
1052
1053                 if (-1 < iheading && iheading < 1)
1054                         return "ahead";
1055                 else if (iheading < -179 || 179 < iheading)
1056                         return "backwards";
1057                 else if (iheading < 0)
1058                         return String.format("left %d°", -iheading);
1059                 else
1060                         return String.format("right %d°", iheading);
1061         }
1062
1063         public void onLocationChanged(Location location) {
1064                 this.location = location;
1065                 AltosDebug.debug("Location changed to %f,%f",
1066                                  location.getLatitude(),
1067                                  location.getLongitude());
1068                 update_ui(telemetry_state, saved_state);
1069         }
1070
1071         public void onStatusChanged(String provider, int status, Bundle extras) {
1072                 AltosDebug.debug("Location status now %d\n", status);
1073         }
1074
1075         public void onProviderEnabled(String provider) {
1076                 AltosDebug.debug("Location provider enabled %s\n", provider);
1077         }
1078
1079         public void onProviderDisabled(String provider) {
1080                 AltosDebug.debug("Location provider disabled %s\n", provider);
1081         }
1082 }