altosdroid: Track device location in app, not telemetry service
[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 boolean ensureBluetooth() {
568                 // Get local Bluetooth adapter
569                 mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
570
571                 // If the adapter is null, then Bluetooth is not supported
572                 if (mBluetoothAdapter == null) {
573                         Toast.makeText(this, "Bluetooth is not available", Toast.LENGTH_LONG).show();
574                         return false;
575                 }
576
577                 if (!mBluetoothAdapter.isEnabled()) {
578                         Intent enableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
579                         startActivityForResult(enableIntent, AltosDroid.REQUEST_ENABLE_BT);
580                 }
581
582                 return true;
583         }
584
585         private boolean check_usb() {
586                 UsbDevice       device = AltosUsb.find_device(this, AltosLib.product_basestation);
587
588                 if (device != null) {
589                         Intent          i = new Intent(this, AltosDroid.class);
590                         PendingIntent   pi = PendingIntent.getActivity(this, 0, new Intent("hello world", null, this, AltosDroid.class), 0);
591
592                         if (AltosUsb.request_permission(this, device, pi)) {
593                                 connectUsb(device);
594                         }
595                         start_with_usb = true;
596                         return true;
597                 }
598
599                 start_with_usb = false;
600
601                 return false;
602         }
603
604         private void noticeIntent(Intent intent) {
605
606                 /* Ok, this is pretty convenient.
607                  *
608                  * When a USB device is plugged in, and our 'hotplug'
609                  * intent registration fires, we get an Intent with
610                  * EXTRA_DEVICE set.
611                  *
612                  * When we start up and see a usb device and request
613                  * permission to access it, that queues a
614                  * PendingIntent, which has the EXTRA_DEVICE added in,
615                  * along with the EXTRA_PERMISSION_GRANTED field as
616                  * well.
617                  *
618                  * So, in both cases, we get the device name using the
619                  * same call. We check to see if access was granted,
620                  * in which case we ignore the device field and do our
621                  * usual startup thing.
622                  */
623
624                 UsbDevice device = (UsbDevice) intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
625                 boolean granted = intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, true);
626
627                 AltosDebug.debug("intent %s device %s granted %s", intent, device, granted);
628
629                 if (!granted)
630                         device = null;
631
632                 if (device != null) {
633                         AltosDebug.debug("intent has usb device " + device.toString());
634                         connectUsb(device);
635                 } else {
636
637                         /* 'granted' is only false if this intent came
638                          * from the request_permission call and
639                          * permission was denied. In which case, we
640                          * don't want to loop forever...
641                          */
642                         if (granted) {
643                                 AltosDebug.debug("check for a USB device at startup");
644                                 if (check_usb())
645                                         return;
646                         }
647                         AltosDebug.debug("Starting by looking for bluetooth devices");
648                         if (ensureBluetooth())
649                                 return;
650                         finish();
651                 }
652         }
653
654         @Override
655         public void onStart() {
656                 super.onStart();
657                 AltosDebug.debug("++ ON START ++");
658
659                 set_switch_time();
660
661                 noticeIntent(getIntent());
662
663                 // Start Telemetry Service
664                 String  action = start_with_usb ? ACTION_USB : ACTION_BLUETOOTH;
665
666                 startService(new Intent(action, null, AltosDroid.this, TelemetryService.class));
667
668                 doBindService();
669
670                 if (mAltosVoice == null)
671                         mAltosVoice = new AltosVoice(this);
672
673         }
674
675         @Override
676         public void onNewIntent(Intent intent) {
677                 super.onNewIntent(intent);
678                 AltosDebug.debug("onNewIntent");
679                 noticeIntent(intent);
680         }
681
682         @Override
683         public void onResume() {
684                 super.onResume();
685                 AltosDebug.debug("+ ON RESUME +");
686
687                 // Listen for GPS and Network position updates
688                 LocationManager locationManager = (LocationManager) this.getSystemService(Context.LOCATION_SERVICE);
689                 locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 1000, 1, this);
690
691                 location = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER);
692
693                 AltosDebug.debug("Resume, location is %f,%f\n",
694                                  location.getLatitude(),
695                                  location.getLongitude());
696
697                 update_ui(telemetry_state, saved_state);
698         }
699
700         @Override
701         public void onPause() {
702                 super.onPause();
703                 AltosDebug.debug("- ON PAUSE -");
704                 // Stop listening for location updates
705                 ((LocationManager) getSystemService(Context.LOCATION_SERVICE)).removeUpdates(this);
706         }
707
708         @Override
709         public void onStop() {
710                 super.onStop();
711                 AltosDebug.debug("-- ON STOP --");
712         }
713
714         @Override
715         public void onDestroy() {
716                 super.onDestroy();
717                 AltosDebug.debug("--- ON DESTROY ---");
718
719                 doUnbindService();
720                 if (mAltosVoice != null) {
721                         mAltosVoice.stop();
722                         mAltosVoice = null;
723                 }
724                 stop_timer();
725         }
726
727         protected void onActivityResult(int requestCode, int resultCode, Intent data) {
728                 AltosDebug.debug("onActivityResult " + resultCode);
729                 switch (requestCode) {
730                 case REQUEST_CONNECT_DEVICE:
731                         // When DeviceListActivity returns with a device to connect to
732                         if (resultCode == Activity.RESULT_OK) {
733                                 connectDevice(data);
734                         }
735                         break;
736                 case REQUEST_ENABLE_BT:
737                         // When the request to enable Bluetooth returns
738                         if (resultCode == Activity.RESULT_OK) {
739                                 // Bluetooth is now enabled, so set up a chat session
740                                 //setupChat();
741                         } else {
742                                 // User did not enable Bluetooth or an error occured
743                                 AltosDebug.error("BT not enabled");
744                                 stopService(new Intent(AltosDroid.this, TelemetryService.class));
745                                 Toast.makeText(this, R.string.bt_not_enabled, Toast.LENGTH_SHORT).show();
746                                 finish();
747                         }
748                         break;
749                 case REQUEST_MAP_TYPE:
750                         if (resultCode == Activity.RESULT_OK)
751                                 set_map_type(data);
752                         break;
753                 }
754         }
755
756         private void connectUsb(UsbDevice device) {
757                 if (mService == null)
758                         pending_usb_device = device;
759                 else {
760                         // Attempt to connect to the device
761                         try {
762                                 mService.send(Message.obtain(null, TelemetryService.MSG_OPEN_USB, device));
763                                 AltosDebug.debug("Sent OPEN_USB message");
764                         } catch (RemoteException e) {
765                                 AltosDebug.debug("connect device message failed");
766                         }
767                 }
768         }
769
770         private void connectDevice(Intent data) {
771                 // Attempt to connect to the device
772                 try {
773                         String address = data.getExtras().getString(DeviceListActivity.EXTRA_DEVICE_ADDRESS);
774                         String name = data.getExtras().getString(DeviceListActivity.EXTRA_DEVICE_NAME);
775
776                         AltosDebug.debug("Connecting to " + address + " " + name);
777                         DeviceAddress   a = new DeviceAddress(address, name);
778                         mService.send(Message.obtain(null, TelemetryService.MSG_CONNECT, a));
779                         AltosDebug.debug("Sent connecting message");
780                 } catch (RemoteException e) {
781                         AltosDebug.debug("connect device message failed");
782                 }
783         }
784
785         private void disconnectDevice() {
786                 try {
787                         mService.send(Message.obtain(null, TelemetryService.MSG_DISCONNECT, null));
788                 } catch (RemoteException e) {
789                 }
790         }
791
792         private void set_map_type(Intent data) {
793                 int type = data.getIntExtra(MapTypeActivity.EXTRA_MAP_TYPE, -1);
794
795                 AltosDebug.debug("intent set_map_type %d\n", type);
796                 if (type != -1) {
797                         map_type = type;
798                         for (AltosDroidTab mTab : mTabs)
799                                 mTab.set_map_type(map_type);
800                 }
801         }
802
803         @Override
804         public boolean onCreateOptionsMenu(Menu menu) {
805                 MenuInflater inflater = getMenuInflater();
806                 inflater.inflate(R.menu.option_menu, menu);
807                 return true;
808         }
809
810         void setFrequency(double freq) {
811                 try {
812                         mService.send(Message.obtain(null, TelemetryService.MSG_SETFREQUENCY, freq));
813                         set_switch_time();
814                 } catch (RemoteException e) {
815                 }
816         }
817
818         void setFrequency(String freq) {
819                 try {
820                         setFrequency (AltosParse.parse_double_net(freq.substring(11, 17)));
821                 } catch (ParseException e) {
822                 }
823         }
824
825         void setBaud(int baud) {
826                 try {
827                         mService.send(Message.obtain(null, TelemetryService.MSG_SETBAUD, baud));
828                         set_switch_time();
829                 } catch (RemoteException e) {
830                 }
831         }
832
833         void setBaud(String baud) {
834                 try {
835                         int     value = Integer.parseInt(baud);
836                         int     rate = AltosLib.ao_telemetry_rate_38400;
837                         switch (value) {
838                         case 2400:
839                                 rate = AltosLib.ao_telemetry_rate_2400;
840                                 break;
841                         case 9600:
842                                 rate = AltosLib.ao_telemetry_rate_9600;
843                                 break;
844                         case 38400:
845                                 rate = AltosLib.ao_telemetry_rate_38400;
846                                 break;
847                         }
848                         setBaud(rate);
849                 } catch (NumberFormatException e) {
850                 }
851         }
852
853         void select_tracker(int serial) {
854                 int i;
855
856                 AltosDebug.debug("select tracker %d\n", serial);
857
858                 if (serial == selected_serial) {
859                         AltosDebug.debug("%d already selected\n", serial);
860                         return;
861                 }
862
863                 if (serial != 0) {
864                         for (i = 0; i < serials.length; i++)
865                                 if (serials[i] == serial)
866                                         break;
867
868                         if (i == serials.length) {
869                                 AltosDebug.debug("attempt to select unknown tracker %d\n", serial);
870                                 return;
871                         }
872                 }
873
874                 current_serial = selected_serial = serial;
875                 update_state(null);
876         }
877
878         void touch_trackers(Integer[] serials) {
879                 AlertDialog.Builder builder_tracker = new AlertDialog.Builder(this);
880                 builder_tracker.setTitle("Select Tracker");
881                 final String[] trackers = new String[serials.length + 1];
882                 trackers[0] = "Auto";
883                 for (int i = 0; i < serials.length; i++)
884                         trackers[i+1] = String.format("%d", serials[i]);
885                 builder_tracker.setItems(trackers,
886                                          new DialogInterface.OnClickListener() {
887                                                  public void onClick(DialogInterface dialog, int item) {
888                                                          if (item == 0)
889                                                                  select_tracker(0);
890                                                          else
891                                                                  select_tracker(Integer.parseInt(trackers[item]));
892                                                  }
893                                          });
894                 AlertDialog alert_tracker = builder_tracker.create();
895                 alert_tracker.show();
896         }
897
898         void delete_track(int serial) {
899                 try {
900                         mService.send(Message.obtain(null, TelemetryService.MSG_DELETE_SERIAL, (Integer) serial));
901                 } catch (Exception ex) {
902                 }
903         }
904
905         @Override
906         public boolean onOptionsItemSelected(MenuItem item) {
907                 Intent serverIntent = null;
908                 switch (item.getItemId()) {
909                 case R.id.connect_scan:
910                         if (ensureBluetooth()) {
911                                 // Launch the DeviceListActivity to see devices and do scan
912                                 serverIntent = new Intent(this, DeviceListActivity.class);
913                                 startActivityForResult(serverIntent, REQUEST_CONNECT_DEVICE);
914                         }
915                         return true;
916                 case R.id.disconnect:
917                         /* Disconnect the device
918                          */
919                         disconnectDevice();
920                         return true;
921                 case R.id.quit:
922                         AltosDebug.debug("R.id.quit");
923                         disconnectDevice();
924                         finish();
925                         return true;
926                 case R.id.select_freq:
927                         // Set the TBT radio frequency
928
929                         final String[] frequencies = {
930                                 "Channel 0 (434.550MHz)",
931                                 "Channel 1 (434.650MHz)",
932                                 "Channel 2 (434.750MHz)",
933                                 "Channel 3 (434.850MHz)",
934                                 "Channel 4 (434.950MHz)",
935                                 "Channel 5 (435.050MHz)",
936                                 "Channel 6 (435.150MHz)",
937                                 "Channel 7 (435.250MHz)",
938                                 "Channel 8 (435.350MHz)",
939                                 "Channel 9 (435.450MHz)"
940                         };
941
942                         AlertDialog.Builder builder_freq = new AlertDialog.Builder(this);
943                         builder_freq.setTitle("Pick a frequency");
944                         builder_freq.setItems(frequencies,
945                                          new DialogInterface.OnClickListener() {
946                                                  public void onClick(DialogInterface dialog, int item) {
947                                                          setFrequency(frequencies[item]);
948                                                  }
949                                          });
950                         AlertDialog alert_freq = builder_freq.create();
951                         alert_freq.show();
952                         return true;
953                 case R.id.select_rate:
954                         // Set the TBT baud rate
955
956                         final String[] rates = {
957                                 "38400",
958                                 "9600",
959                                 "2400",
960                         };
961
962                         AlertDialog.Builder builder_rate = new AlertDialog.Builder(this);
963                         builder_rate.setTitle("Pick a baud rate");
964                         builder_rate.setItems(rates,
965                                          new DialogInterface.OnClickListener() {
966                                                  public void onClick(DialogInterface dialog, int item) {
967                                                          setBaud(rates[item]);
968                                                  }
969                                          });
970                         AlertDialog alert_rate = builder_rate.create();
971                         alert_rate.show();
972                         return true;
973                 case R.id.change_units:
974                         boolean imperial = AltosPreferences.imperial_units();
975                         AltosPreferences.set_imperial_units(!imperial);
976                         return true;
977                 case R.id.preload_maps:
978                         serverIntent = new Intent(this, PreloadMapActivity.class);
979                         startActivityForResult(serverIntent, REQUEST_PRELOAD_MAPS);
980                         return true;
981                 case R.id.map_type:
982                         serverIntent = new Intent(this, MapTypeActivity.class);
983                         startActivityForResult(serverIntent, REQUEST_MAP_TYPE);
984                         return true;
985                 case R.id.map_source:
986                         int source = AltosDroidPreferences.map_source();
987                         int new_source = source == AltosDroidPreferences.MAP_SOURCE_ONLINE ? AltosDroidPreferences.MAP_SOURCE_OFFLINE : AltosDroidPreferences.MAP_SOURCE_ONLINE;
988                         AltosDroidPreferences.set_map_source(new_source);
989                         set_map_source(new_source);
990                         return true;
991                 case R.id.select_tracker:
992                         if (serials != null) {
993                                 String[] trackers = new String[serials.length+1];
994                                 trackers[0] = "Auto";
995                                 for (int i = 0; i < serials.length; i++)
996                                         trackers[i+1] = String.format("%d", serials[i]);
997                                 AlertDialog.Builder builder_serial = new AlertDialog.Builder(this);
998                                 builder_serial.setTitle("Select a tracker");
999                                 builder_serial.setItems(trackers,
1000                                                         new DialogInterface.OnClickListener() {
1001                                                                 public void onClick(DialogInterface dialog, int item) {
1002                                                                         if (item == 0)
1003                                                                                 select_tracker(0);
1004                                                                         else
1005                                                                                 select_tracker(serials[item-1]);
1006                                                                 }
1007                                                         });
1008                                 AlertDialog alert_serial = builder_serial.create();
1009                                 alert_serial.show();
1010
1011                         }
1012                         return true;
1013                 case R.id.delete_track:
1014                         if (serials != null) {
1015                                 String[] trackers = new String[serials.length];
1016                                 for (int i = 0; i < serials.length; i++)
1017                                         trackers[i] = String.format("%d", serials[i]);
1018                                 AlertDialog.Builder builder_serial = new AlertDialog.Builder(this);
1019                                 builder_serial.setTitle("Delete a track");
1020                                 builder_serial.setItems(trackers,
1021                                                         new DialogInterface.OnClickListener() {
1022                                                                 public void onClick(DialogInterface dialog, int item) {
1023                                                                         delete_track(serials[item]);
1024                                                                 }
1025                                                         });
1026                                 AlertDialog alert_serial = builder_serial.create();
1027                                 alert_serial.show();
1028
1029                         }
1030                         return true;
1031                 }
1032                 return false;
1033         }
1034
1035         static String direction(AltosGreatCircle from_receiver,
1036                                 Location receiver) {
1037                 if (from_receiver == null)
1038                         return null;
1039
1040                 if (receiver == null)
1041                         return null;
1042
1043                 if (!receiver.hasBearing())
1044                         return null;
1045
1046                 float   bearing = receiver.getBearing();
1047                 float   heading = (float) from_receiver.bearing - bearing;
1048
1049                 while (heading <= -180.0f)
1050                         heading += 360.0f;
1051                 while (heading > 180.0f)
1052                         heading -= 360.0f;
1053
1054                 int iheading = (int) (heading + 0.5f);
1055
1056                 if (-1 < iheading && iheading < 1)
1057                         return "ahead";
1058                 else if (iheading < -179 || 179 < iheading)
1059                         return "backwards";
1060                 else if (iheading < 0)
1061                         return String.format("left %d°", -iheading);
1062                 else
1063                         return String.format("right %d°", iheading);
1064         }
1065
1066         public void onLocationChanged(Location location) {
1067                 this.location = location;
1068                 AltosDebug.debug("Location changed to %f,%f",
1069                                  location.getLatitude(),
1070                                  location.getLongitude());
1071                 update_ui(telemetry_state, saved_state);
1072         }
1073
1074         public void onStatusChanged(String provider, int status, Bundle extras) {
1075                 AltosDebug.debug("Location status now %d\n", status);
1076         }
1077
1078         public void onProviderEnabled(String provider) {
1079                 AltosDebug.debug("Location provider enabled %s\n", provider);
1080         }
1081
1082         public void onProviderDisabled(String provider) {
1083                 AltosDebug.debug("Location provider disabled %s\n", provider);
1084         }
1085 }