altosdroid: Add BLUETOOTH_CONNECT permission, handle its lack
[fw/altos] / altosdroid / app / src / main / java / 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; either version 2 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful, but
10  * WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License along
15  * with this program; if not, write to the Free Software Foundation, Inc.,
16  * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
17  */
18
19 package org.altusmetrum.AltosDroid;
20
21 import java.lang.ref.WeakReference;
22 import java.util.*;
23 import java.io.*;
24
25 import android.Manifest;
26 import android.app.Activity;
27 import android.app.PendingIntent;
28 import android.bluetooth.BluetoothAdapter;
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.os.Parcelable;
41 import androidx.fragment.app.FragmentActivity;
42 import androidx.fragment.app.FragmentManager;
43 import android.view.*;
44 import android.widget.*;
45 import android.app.AlertDialog;
46 import android.location.Location;
47 import android.location.LocationManager;
48 import android.location.LocationListener;
49 import android.hardware.usb.*;
50 import android.content.pm.PackageManager;
51 import androidx.core.app.ActivityCompat;
52 import org.altusmetrum.altoslib_14.*;
53
54 class SavedState {
55         long    received_time;
56         int     state;
57         boolean locked;
58         String  callsign;
59         int     serial;
60         int     flight;
61         int     rssi;
62
63         SavedState(AltosState state) {
64                 received_time = state.received_time;
65                 this.state = state.state();
66                 if (state.gps != null)
67                         locked = state.gps.locked;
68                 else
69                         locked = false;
70                 callsign = state.cal_data().callsign;
71                 serial = state.cal_data().serial;
72                 flight = state.cal_data().flight;
73                 rssi = state.rssi;
74         }
75 }
76
77 public class AltosDroid extends FragmentActivity implements AltosUnitsListener, LocationListener, ActivityCompat.OnRequestPermissionsResultCallback {
78
79         // Actions sent to the telemetry server at startup time
80
81         public static final String ACTION_BLUETOOTH = "org.altusmetrum.AltosDroid.BLUETOOTH";
82         public static final String ACTION_USB = "org.altusmetrum.AltosDroid.USB";
83
84         // Message types received by our Handler
85
86         public static final int MSG_STATE           = 1;
87         public static final int MSG_UPDATE_AGE      = 2;
88         public static final int MSG_IDLE_MODE       = 3;
89         public static final int MSG_IGNITER_STATUS  = 4;
90         public static final int MSG_FILE_FAILED     = 5;
91
92         // Intent request codes
93         public static final int REQUEST_CONNECT_DEVICE = 1;
94         public static final int REQUEST_ENABLE_BT      = 2;
95         public static final int REQUEST_PRELOAD_MAPS   = 3;
96         public static final int REQUEST_IDLE_MODE      = 5;
97         public static final int REQUEST_IGNITERS       = 6;
98         public static final int REQUEST_SETUP          = 7;
99         public static final int REQUEST_SELECT_TRACKER = 8;
100         public static final int REQUEST_DELETE_TRACKER = 9;
101
102         public static final String EXTRA_IDLE_MODE = "idle_mode";
103         public static final String EXTRA_IDLE_RESULT = "idle_result";
104         public static final String EXTRA_FREQUENCY = "frequency";
105         public static final String EXTRA_TELEMETRY_SERVICE = "telemetry_service";
106         public static final String EXTRA_TRACKERS = "trackers";
107         public static final String EXTRA_TRACKERS_TITLE = "trackers_title";
108
109         // Setup result bits
110         public static final int SETUP_BAUD = 1;
111         public static final int SETUP_UNITS = 2;
112         public static final int SETUP_MAP_SOURCE = 4;
113         public static final int SETUP_MAP_TYPE = 8;
114         public static final int SETUP_FONT_SIZE = 16;
115
116         public static FragmentManager   fm;
117
118         private BluetoothAdapter mBluetoothAdapter = null;
119
120         // Flight state values
121         private TextView mCallsignView;
122         private TextView mRSSIView;
123         private TextView mSerialView;
124         private TextView mFlightView;
125         private RelativeLayout mStateLayout;
126         private TextView mStateView;
127         private TextView mAgeView;
128         private boolean  mAgeViewOld;
129         private int mAgeNewColor;
130         private int mAgeOldColor;
131
132         public static final String      tab_pad_name = "pad";
133         public static final String      tab_flight_name = "flight";
134         public static final String      tab_recover_name = "recover";
135         public static final String      tab_map_name = "map";
136
137         // field to display the version at the bottom of the screen
138         private TextView mVersion;
139
140         private boolean idle_mode = false;
141
142         public Location location = null;
143
144         private AltosState      state;
145         private SavedState      saved_state;
146
147         // Tabs
148         TabHost         mTabHost;
149         AltosViewPager  mViewPager;
150         TabsAdapter     mTabsAdapter;
151         ArrayList<AltosDroidTab> mTabs = new ArrayList<AltosDroidTab>();
152         int             tabHeight;
153
154         // Timer and Saved flight state for Age calculation
155         private Timer timer;
156
157         TelemetryState  telemetry_state;
158         Tracker[]       trackers;
159
160         UsbDevice       pending_usb_device;
161         boolean         start_with_usb;
162
163         // Service
164         private boolean mIsBound   = false;
165         private Messenger mService = null;
166         final Messenger mMessenger = new Messenger(new IncomingHandler(this));
167
168         // Text to Speech
169         private AltosVoice mAltosVoice = null;
170
171         // The Handler that gets information back from the Telemetry Service
172         static class IncomingHandler extends Handler {
173                 private final WeakReference<AltosDroid> mAltosDroid;
174                 IncomingHandler(AltosDroid ad) { mAltosDroid = new WeakReference<AltosDroid>(ad); }
175
176                 @Override
177                 public void handleMessage(Message msg) {
178                         AltosDroid ad = mAltosDroid.get();
179
180                         switch (msg.what) {
181                         case MSG_STATE:
182                                 if (msg.obj == null) {
183                                         AltosDebug.debug("telemetry_state null!");
184                                         return;
185                                 }
186                                 ad.update_state((TelemetryState) msg.obj);
187                                 break;
188                         case MSG_UPDATE_AGE:
189                                 ad.update_age();
190                                 break;
191                         case MSG_IDLE_MODE:
192                                 ad.idle_mode = (Boolean) msg.obj;
193                                 ad.update_state(null);
194                                 break;
195                         case MSG_FILE_FAILED:
196                                 ad.file_failed((File) msg.obj);
197                                 break;
198                         }
199                 }
200         };
201
202         private ServiceConnection mConnection = new ServiceConnection() {
203                 public void onServiceConnected(ComponentName className, IBinder service) {
204                         AltosDebug.debug("onServiceConnected\n");
205                         mService = new Messenger(service);
206                         try {
207                                 Message msg = Message.obtain(null, TelemetryService.MSG_REGISTER_CLIENT);
208                                 msg.replyTo = mMessenger;
209                                 mService.send(msg);
210                         } catch (RemoteException e) {
211                                 AltosDebug.debug("attempt to register telemetry service client failed\n");
212                                 // In this case the service has crashed before we could even do anything with it
213                         }
214                         if (pending_usb_device != null) {
215                                 try {
216                                         mService.send(Message.obtain(null, TelemetryService.MSG_OPEN_USB, pending_usb_device));
217                                         pending_usb_device = null;
218                                 } catch (RemoteException e) {
219                                 }
220                         }
221                 }
222
223                 public void onServiceDisconnected(ComponentName className) {
224                         AltosDebug.debug("onServiceDisconnected\n");
225                         // This is called when the connection with the service has been unexpectedly disconnected - process crashed.
226                         mService = null;
227                 }
228         };
229
230         void doBindService() {
231                 AltosDebug.debug("doBindService\n");
232                 bindService(new Intent(this, TelemetryService.class), mConnection, Context.BIND_AUTO_CREATE);
233                 mIsBound = true;
234         }
235
236         void doUnbindService() {
237                 AltosDebug.debug("doUnbindService\n");
238                 if (mIsBound) {
239                         // If we have received the service, and hence registered with it, then now is the time to unregister.
240                         if (mService != null) {
241                                 try {
242                                         Message msg = Message.obtain(null, TelemetryService.MSG_UNREGISTER_CLIENT);
243                                         msg.replyTo = mMessenger;
244                                         mService.send(msg);
245                                 } catch (RemoteException e) {
246                                         // There is nothing special we need to do if the service has crashed.
247                                 }
248                         }
249                         // Detach our existing connection.
250                         unbindService(mConnection);
251                         mIsBound = false;
252                 }
253         }
254
255         public AltosDroidTab findTab(String name) {
256                 for (AltosDroidTab mTab : mTabs)
257                         if (name.equals(mTab.tab_name()))
258                                 return mTab;
259                 return null;
260         }
261
262         public void registerTab(AltosDroidTab mTab) {
263                 mTabs.add(mTab);
264         }
265
266         public void unregisterTab(AltosDroidTab mTab) {
267                 mTabs.remove(mTab);
268         }
269
270         public void units_changed(boolean imperial_units) {
271                 for (AltosDroidTab mTab : mTabs)
272                         mTab.units_changed(imperial_units);
273         }
274
275         void update_title(TelemetryState telemetry_state) {
276                 switch (telemetry_state.connect) {
277                 case TelemetryState.CONNECT_CONNECTED:
278                         if (telemetry_state.config != null) {
279                                 String str = String.format("S/N %d %6.3f MHz%s", telemetry_state.config.serial,
280                                                            telemetry_state.frequency, telemetry_state.idle_mode ? " (idle)" : "");
281                                 if (telemetry_state.telemetry_rate != AltosLib.ao_telemetry_rate_38400)
282                                         str = str.concat(String.format(" %d bps",
283                                                                        AltosLib.ao_telemetry_rate_values[telemetry_state.telemetry_rate]));
284                                 setTitle(str);
285                         } else {
286                                 setTitle(R.string.title_connected_to);
287                         }
288                         break;
289                 case TelemetryState.CONNECT_CONNECTING:
290                         if (telemetry_state.address != null)
291                                 setTitle(String.format("Connecting to %s...", telemetry_state.address.name));
292                         else
293                                 setTitle("Connecting to something...");
294                         break;
295                 case TelemetryState.CONNECT_DISCONNECTED:
296                 case TelemetryState.CONNECT_NONE:
297                         setTitle(R.string.title_not_connected);
298                         break;
299                 }
300         }
301
302         void start_timer() {
303                 if (timer == null) {
304                         timer = new Timer();
305                         timer.scheduleAtFixedRate(new TimerTask(){ public void run() {onTimerTick();}}, 1000L, 1000L);
306                 }
307         }
308
309         void stop_timer() {
310                 if (timer != null) {
311                         timer.cancel();
312                         timer.purge();
313                         timer = null;
314                 }
315         }
316
317         int     selected_serial = 0;
318         long    switch_time;
319
320         void set_switch_time() {
321                 switch_time = System.currentTimeMillis();
322                 selected_serial = 0;
323         }
324
325         boolean registered_units_listener;
326
327         void update_state(TelemetryState new_telemetry_state) {
328
329                 if (new_telemetry_state != null)
330                         telemetry_state = new_telemetry_state;
331
332                 if (selected_frequency != AltosLib.MISSING) {
333                         AltosState selected_state = telemetry_state.get(selected_serial);
334                         AltosState latest_state = telemetry_state.get(telemetry_state.latest_serial);
335
336                         if (selected_state != null && selected_state.frequency == selected_frequency) {
337                                 selected_frequency = AltosLib.MISSING;
338                         } else if ((selected_state == null || selected_state.frequency != selected_frequency) &&
339                                    (latest_state != null && latest_state.frequency == selected_frequency))
340                         {
341                                 selected_frequency = AltosLib.MISSING;
342                                 selected_serial = telemetry_state.latest_serial;
343                         }
344                 }
345
346                 if (!telemetry_state.containsKey(selected_serial)) {
347                         selected_serial = telemetry_state.latest_serial;
348                         AltosDebug.debug("selected serial set to %d", selected_serial);
349                 }
350
351                 int shown_serial = selected_serial;
352
353                 if (telemetry_state.idle_mode)
354                         shown_serial = telemetry_state.latest_serial;
355
356                 if (!registered_units_listener) {
357                         registered_units_listener = true;
358                         AltosPreferences.register_units_listener(this);
359                 }
360
361                 int     num_trackers = 0;
362
363                 for (AltosState s : telemetry_state.values()) {
364                         num_trackers++;
365                 }
366
367                 trackers = new Tracker[num_trackers + 1];
368
369                 int n = 0;
370                 trackers[n++] = new Tracker(0, "auto", 0.0);
371
372                 for (AltosState s : telemetry_state.values())
373                         trackers[n++] = new Tracker(s);
374
375                 Arrays.sort(trackers);
376
377                 if (telemetry_state.frequency != AltosLib.MISSING)
378                         telem_frequency = telemetry_state.frequency;
379
380                 update_title(telemetry_state);
381
382                 AltosState      state = telemetry_state.get(shown_serial);
383
384                 update_ui(telemetry_state, state, telemetry_state.quiet);
385
386                 start_timer();
387         }
388
389         boolean same_string(String a, String b) {
390                 if (a == null) {
391                         if (b == null)
392                                 return true;
393                         return false;
394                 } else {
395                         if (b == null)
396                                 return false;
397                         return a.equals(b);
398                 }
399         }
400
401
402         private int blend_component(int a, int b, double r, int shift, int mask) {
403                 return ((int) (((a >> shift) & mask) * r + ((b >> shift) & mask) * (1 - r)) & mask) << shift;
404         }
405         private int blend_color(int a, int b, double r) {
406                 return (blend_component(a, b, r, 0, 0xff) |
407                         blend_component(a, b, r, 8, 0xff) |
408                         blend_component(a, b, r, 16, 0xff) |
409                         blend_component(a, b, r, 24, 0xff));
410         }
411
412         int state_age(long received_time) {
413                 return (int) ((System.currentTimeMillis() - received_time + 500) / 1000);
414         }
415
416         void set_screen_on(int age) {
417                 if (age < 60)
418                         getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
419                 else
420                         getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
421         }
422
423         static String age_string(int age) {
424                 String  text;
425                 if (age < 60)
426                         text = String.format("%ds", age);
427                 else if (age < 60 * 60)
428                         text = String.format("%dm", age / 60);
429                 else if (age < 60 * 60 * 24)
430                         text = String.format("%dh", age / (60 * 60));
431                 else
432                         text = String.format("%dd", age / (24 * 60 * 60));
433                 return text;
434         }
435
436         void update_age() {
437                 if (saved_state != null) {
438                         int age = state_age(saved_state.received_time);
439
440                         double age_scale = age / 100.0;
441
442                         if (age_scale > 1.0)
443                                 age_scale = 1.0;
444
445                         mAgeView.setTextColor(blend_color(mAgeOldColor, mAgeNewColor, age_scale));
446
447                         set_screen_on(age);
448
449                         mAgeView.setText(age_string(age));
450                 }
451         }
452
453         void update_ui(TelemetryState telem_state, AltosState state, boolean quiet) {
454
455                 AltosDebug.debug("update_ui telem %b state %b quiet %b saved_state %b\n",
456                                  telem_state != null,
457                                  state != null,
458                                  quiet,
459                                  saved_state != null);
460
461                 this.state = state;
462
463                 int prev_state = AltosLib.ao_flight_invalid;
464
465                 AltosGreatCircle from_receiver = null;
466
467                 if (saved_state != null)
468                         prev_state = saved_state.state;
469
470                 if (state != null) {
471                         set_screen_on(state_age(state.received_time));
472
473                         if (state.state() == AltosLib.ao_flight_stateless) {
474                                 boolean prev_locked = false;
475                                 boolean locked = false;
476
477                                 if(state.gps != null)
478                                         locked = state.gps.locked;
479                                 if (saved_state != null)
480                                         prev_locked = saved_state.locked;
481                                 if (prev_locked != locked) {
482                                         String currentTab = mTabHost.getCurrentTabTag();
483                                         if (locked) {
484                                                 if (currentTab.equals(tab_pad_name)) mTabHost.setCurrentTabByTag(tab_flight_name);
485                                         } else {
486                                                 if (currentTab.equals(tab_flight_name)) mTabHost.setCurrentTabByTag(tab_pad_name);
487                                         }
488                                 }
489                         } else {
490                                 if (prev_state != state.state()) {
491                                         String currentTab = mTabHost.getCurrentTabTag();
492                                         switch (state.state()) {
493                                         case AltosLib.ao_flight_boost:
494                                                 if (currentTab.equals(tab_pad_name)) mTabHost.setCurrentTabByTag(tab_flight_name);
495                                                 break;
496                                         case AltosLib.ao_flight_landed:
497                                                 if (currentTab.equals(tab_flight_name)) mTabHost.setCurrentTabByTag(tab_recover_name);
498                                                 break;
499                                         case AltosLib.ao_flight_stateless:
500                                                 if (currentTab.equals(tab_pad_name)) mTabHost.setCurrentTabByTag(tab_flight_name);
501                                                 break;
502                                         }
503                                 }
504                         }
505
506                         if (location != null && state.gps != null && state.gps.locked) {
507                                 double altitude = 0;
508                                 if (location.hasAltitude())
509                                         altitude = location.getAltitude();
510                                 from_receiver = new AltosGreatCircle(location.getLatitude(),
511                                                                      location.getLongitude(),
512                                                                      altitude,
513                                                                      state.gps.lat,
514                                                                      state.gps.lon,
515                                                                      state.gps.alt);
516                         }
517
518                         if (saved_state == null || !same_string(saved_state.callsign, state.cal_data().callsign)) {
519                                 mCallsignView.setText(state.cal_data().callsign);
520                         }
521                         if (saved_state == null || state.cal_data().serial != saved_state.serial) {
522                                 if (state.cal_data().serial == AltosLib.MISSING)
523                                         mSerialView.setText("");
524                                 else
525                                         mSerialView.setText(String.format("%d", state.cal_data().serial));
526                         }
527                         if (saved_state == null || state.cal_data().flight != saved_state.flight) {
528                                 if (state.cal_data().flight == AltosLib.MISSING)
529                                         mFlightView.setText("");
530                                 else
531                                         mFlightView.setText(String.format("%d", state.cal_data().flight));
532                         }
533                         if (saved_state == null || state.state() != saved_state.state) {
534                                 if (state.state() == AltosLib.ao_flight_stateless) {
535                                         mStateLayout.setVisibility(View.GONE);
536                                 } else {
537                                         mStateView.setText(state.state_name());
538                                         mStateLayout.setVisibility(View.VISIBLE);
539                                 }
540                         }
541                         if (saved_state == null || state.rssi != saved_state.rssi) {
542                                 if (state.rssi == AltosLib.MISSING)
543                                         mRSSIView.setText("");
544                                 else
545                                         mRSSIView.setText(String.format("%d", state.rssi));
546                         }
547                         saved_state = new SavedState(state);
548                 }
549
550                 for (AltosDroidTab mTab : mTabs) {
551                         AltosDebug.debug("mTab %s current %s\n",
552                                          mTab, mTabsAdapter.currentItem());
553                         mTab.update_ui(telem_state, state, from_receiver, location, mTab == mTabsAdapter.currentItem());
554                 }
555
556                 if (mAltosVoice != null && mTabsAdapter.currentItem() != null)
557                         mAltosVoice.tell(telem_state, state, from_receiver, location, (AltosDroidTab) mTabsAdapter.currentItem(), quiet);
558
559         }
560
561         private void onTimerTick() {
562                 try {
563                         mMessenger.send(Message.obtain(null, MSG_UPDATE_AGE));
564                 } catch (RemoteException e) {
565                 }
566         }
567
568         static String pos(double p, String pos, String neg) {
569                 String  h = pos;
570                 if (p == AltosLib.MISSING)
571                         return "";
572                 if (p < 0) {
573                         h = neg;
574                         p = -p;
575                 }
576                 int deg = (int) Math.floor(p);
577                 double min = (p - Math.floor(p)) * 60.0;
578                 return String.format("%d° %7.4f\" %s", deg, min, h);
579         }
580
581         static String number(String format, double value) {
582                 if (value == AltosLib.MISSING)
583                         return "";
584                 return String.format(format, value);
585         }
586
587         static String integer(String format, int value) {
588                 if (value == AltosLib.MISSING)
589                         return "";
590                 return String.format(format, value);
591         }
592
593         private View create_tab_view(String label) {
594                 LayoutInflater inflater = (LayoutInflater) this.getLayoutInflater();
595                 View tab_view = inflater.inflate(R.layout.tab_layout, null);
596                 TextView text_view = (TextView) tab_view.findViewById (R.id.tabLabel);
597                 text_view.setText(label);
598                 return tab_view;
599         }
600
601         static public int[] themes = {
602                 R.style.Small,
603                 R.style.Medium,
604                 R.style.Large,
605                 R.style.Extra
606         };
607
608         static public int[] dialog_themes = {
609                 R.style.Small_Dialog,
610                 R.style.Medium_Dialog,
611                 R.style.Large_Dialog,
612                 R.style.Extra_Dialog
613         };
614
615         @Override
616         public void onCreate(Bundle savedInstanceState) {
617                 AltosDebug.init(this);
618                 AltosDebug.debug("+++ ON CREATE +++");
619
620                 // Initialise preferences
621                 AltosDroidPreferences.init(this);
622                 setTheme(themes[AltosDroidPreferences.font_size()]);
623                 super.onCreate(savedInstanceState);
624
625                 fm = getSupportFragmentManager();
626
627                 // Set up the window layout
628                 setContentView(R.layout.altosdroid);
629
630                 // Create the Tabs and ViewPager
631                 mTabHost = (TabHost)findViewById(android.R.id.tabhost);
632                 mTabHost.setup();
633
634                 mViewPager = (AltosViewPager)findViewById(R.id.pager);
635                 mViewPager.setOffscreenPageLimit(4);
636
637                 mTabsAdapter = new TabsAdapter(this, mTabHost, mViewPager);
638
639                 mTabsAdapter.addTab(mTabHost.newTabSpec(tab_pad_name).setIndicator(create_tab_view("Pad")), TabPad.class, null, findTab(tab_pad_name));
640                 mTabsAdapter.addTab(mTabHost.newTabSpec(tab_flight_name).setIndicator(create_tab_view("Flight")), TabFlight.class, null, findTab(tab_flight_name));
641                 mTabsAdapter.addTab(mTabHost.newTabSpec(tab_recover_name).setIndicator(create_tab_view("Recover")), TabRecover.class, null, findTab(tab_recover_name));
642                 mTabsAdapter.addTab(mTabHost.newTabSpec(tab_map_name).setIndicator(create_tab_view("Map")), TabMap.class, null, findTab(tab_map_name));
643
644                 // Display the Version
645                 mVersion = (TextView) findViewById(R.id.version);
646                 mVersion.setText("Version: " + BuildInfo.version +
647                                  " Built: " + BuildInfo.builddate + " " + BuildInfo.buildtime + " " + BuildInfo.buildtz +
648                                  " (" + BuildInfo.branch + "-" + BuildInfo.commitnum + "-" + BuildInfo.commithash + ")");
649
650                 mCallsignView  = (TextView) findViewById(R.id.callsign_value);
651                 mRSSIView      = (TextView) findViewById(R.id.rssi_value);
652                 mSerialView    = (TextView) findViewById(R.id.serial_value);
653                 mFlightView    = (TextView) findViewById(R.id.flight_value);
654                 mStateLayout   = (RelativeLayout) findViewById(R.id.state_container);
655                 mStateView     = (TextView) findViewById(R.id.state_value);
656                 mAgeView       = (TextView) findViewById(R.id.age_value);
657                 mAgeNewColor   = mAgeView.getTextColors().getDefaultColor();
658                 mAgeOldColor   = getResources().getColor(R.color.old_color);
659         }
660
661         private void ensureBluetooth() {
662                 // Get local Bluetooth adapter
663                 mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
664
665                 /* if there is a BT adapter and it isn't turned on, then turn it on */
666                 if (mBluetoothAdapter != null && !mBluetoothAdapter.isEnabled()) {
667                         Intent enableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
668                         startActivityForResult(enableIntent, AltosDroid.REQUEST_ENABLE_BT);
669                 }
670         }
671
672         private boolean check_usb() {
673                 UsbDevice       device = AltosUsb.find_device(this, AltosLib.product_basestation);
674
675                 if (device != null) {
676                         Intent          i = new Intent(this, AltosDroid.class);
677                         PendingIntent   pi = PendingIntent.getActivity(this, 0, new Intent("hello world", null, this, AltosDroid.class), 0);
678
679                         if (AltosUsb.request_permission(this, device, pi)) {
680                                 connectUsb(device);
681                         }
682                         start_with_usb = true;
683                         return true;
684                 }
685
686                 start_with_usb = false;
687
688                 return false;
689         }
690
691         private void noticeIntent(Intent intent) {
692
693                 /* Ok, this is pretty convenient.
694                  *
695                  * When a USB device is plugged in, and our 'hotplug'
696                  * intent registration fires, we get an Intent with
697                  * EXTRA_DEVICE set.
698                  *
699                  * When we start up and see a usb device and request
700                  * permission to access it, that queues a
701                  * PendingIntent, which has the EXTRA_DEVICE added in,
702                  * along with the EXTRA_PERMISSION_GRANTED field as
703                  * well.
704                  *
705                  * So, in both cases, we get the device name using the
706                  * same call. We check to see if access was granted,
707                  * in which case we ignore the device field and do our
708                  * usual startup thing.
709                  */
710
711                 UsbDevice device = (UsbDevice) intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
712                 boolean granted = intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, true);
713
714                 AltosDebug.debug("intent %s device %s granted %s", intent, device, granted);
715
716                 if (!granted)
717                         device = null;
718
719                 if (device != null) {
720                         AltosDebug.debug("intent has usb device " + device.toString());
721                         connectUsb(device);
722                 } else {
723
724                         /* 'granted' is only false if this intent came
725                          * from the request_permission call and
726                          * permission was denied. In which case, we
727                          * don't want to loop forever...
728                          */
729                         if (granted) {
730                                 AltosDebug.debug("check for a USB device at startup");
731                                 if (check_usb())
732                                         return;
733                         }
734                         AltosDebug.debug("Starting by looking for bluetooth devices");
735                         ensureBluetooth();
736                 }
737         }
738
739         @Override
740         public void onStart() {
741                 super.onStart();
742                 AltosDebug.debug("++ ON START ++");
743
744                 set_switch_time();
745
746                 noticeIntent(getIntent());
747
748                 // Start Telemetry Service
749                 String  action = start_with_usb ? ACTION_USB : ACTION_BLUETOOTH;
750
751                 startService(new Intent(action, null, AltosDroid.this, TelemetryService.class));
752
753                 doBindService();
754
755                 if (mAltosVoice == null)
756                         mAltosVoice = new AltosVoice(this);
757
758         }
759
760         @Override
761         public void onNewIntent(Intent intent) {
762                 super.onNewIntent(intent);
763                 AltosDebug.debug("+ ON NEW INTENT +");
764                 noticeIntent(intent);
765         }
766
767         private void enable_location_updates(boolean do_update) {
768                 // Listen for GPS and Network position updates
769                 LocationManager locationManager = (LocationManager) this.getSystemService(Context.LOCATION_SERVICE);
770
771                 if (locationManager != null)
772                 {
773                         try {
774                                 locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 1000, 1, this);
775                                 location = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER);
776                         } catch (Exception e) {
777                                 locationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 1000, 1, this);
778                                 location = locationManager.getLastKnownLocation(LocationManager.NETWORK_PROVIDER);
779                         }
780
781                         if (location != null)
782                                 AltosDebug.debug("Resume, location is %f,%f\n",
783                                                  location.getLatitude(),
784                                                  location.getLongitude());
785                         AltosDebug.debug("Failed to get GPS updates\n");
786                 }
787
788                 if (do_update)
789                         update_ui(telemetry_state, state, true);
790         }
791
792         static final int MY_PERMISSION_REQUEST = 1001;
793
794         public boolean have_location_permission = false;
795         public boolean have_storage_permission = false;
796         public boolean have_bluetooth_permission = false;
797         public boolean have_bluetooth_connect_permission = false;
798         public boolean asked_permission = false;
799
800         static final String BLUETOOTH_CONNECT = "android.permission.BLUETOOTH_CONNECT";
801
802         AltosMapOnline map_online;
803
804         void
805         tell_map_permission(AltosMapOnline map_online) {
806                 this.map_online = map_online;
807         }
808
809         @Override
810         public void onRequestPermissionsResult(int requestCode, String[] permissions,
811                                                int[] grantResults) {
812                 if (requestCode == MY_PERMISSION_REQUEST) {
813                         for (int i = 0; i < grantResults.length; i++) {
814                                 if (grantResults[i] == PackageManager.PERMISSION_GRANTED) {
815                                         if (permissions[i].equals(Manifest.permission.ACCESS_FINE_LOCATION)) {
816                                                 have_location_permission = true;
817                                                 enable_location_updates(true);
818                                                 if (map_online != null)
819                                                         map_online.position_permission();
820                                         }
821                                         if (permissions[i].equals(Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
822                                                 have_storage_permission = true;
823                                         }
824                                         if (permissions[i].equals(Manifest.permission.BLUETOOTH)) {
825                                                 have_bluetooth_permission = true;
826                                         }
827                                         if (permissions[i].equals(BLUETOOTH_CONNECT)) {
828                                                 have_bluetooth_connect_permission = true;
829                                         }
830                                 }
831                         }
832                 }
833         }
834
835         @Override
836         public void onResume() {
837                 AltosDebug.debug("+ ON RESUME +");
838
839                 super.onResume();
840
841                 if (!asked_permission) {
842                         asked_permission = true;
843                         if (ActivityCompat.checkSelfPermission(this,
844                                                               Manifest.permission.ACCESS_FINE_LOCATION)
845                             == PackageManager.PERMISSION_GRANTED)
846                         {
847                                 have_location_permission = true;
848                         }
849                         if (ActivityCompat.checkSelfPermission(this,
850                                                                Manifest.permission.WRITE_EXTERNAL_STORAGE)
851                             == PackageManager.PERMISSION_GRANTED)
852                         {
853                                 have_storage_permission = true;
854                         }
855                         if (ActivityCompat.checkSelfPermission(this,
856                                                                Manifest.permission.BLUETOOTH)
857                             == PackageManager.PERMISSION_GRANTED)
858                         {
859                                 have_bluetooth_permission = true;
860                         }
861                         if (ActivityCompat.checkSelfPermission(this,
862                                                                BLUETOOTH_CONNECT)
863                             == PackageManager.PERMISSION_GRANTED)
864                         {
865                                 have_bluetooth_connect_permission = true;
866                         }
867                         int count = 0;
868                         if (!have_location_permission)
869                                 count += 1;
870                         if (!have_storage_permission)
871                                 count += 1;
872                         if (!have_bluetooth_permission)
873                                 count += 1;
874                         if (!have_bluetooth_connect_permission)
875                                 count += 1;
876                         if (count > 0)
877                         {
878                                 String[] permissions = new String[count];
879                                 int i = 0;
880                                 if (!have_location_permission)
881                                         permissions[i++] = Manifest.permission.ACCESS_FINE_LOCATION;
882                                 if (!have_storage_permission)
883                                         permissions[i++] = Manifest.permission.WRITE_EXTERNAL_STORAGE;
884                                 if (!have_bluetooth_permission)
885                                         permissions[i++] = Manifest.permission.BLUETOOTH;
886                                 if (!have_bluetooth_connect_permission)
887                                         permissions[i++] = BLUETOOTH_CONNECT;
888                                 ActivityCompat.requestPermissions(this, permissions, MY_PERMISSION_REQUEST);
889                         }
890                 }
891                 if (have_location_permission)
892                         enable_location_updates(false);
893         }
894
895         @Override
896         public void onPause() {
897                 AltosDebug.debug("- ON PAUSE -");
898
899                 super.onPause();
900
901                 // Stop listening for location updates
902                 if (have_location_permission)
903                         ((LocationManager) getSystemService(Context.LOCATION_SERVICE)).removeUpdates(this);
904         }
905
906         @Override
907         public void onStop() {
908                 AltosDebug.debug("-- ON STOP --");
909
910                 super.onStop();
911         }
912
913         @Override
914         public void onDestroy() {
915                 AltosDebug.debug("--- ON DESTROY ---");
916
917                 super.onDestroy();
918
919                 saved_state = null;
920
921                 doUnbindService();
922                 if (mAltosVoice != null) {
923                         mAltosVoice.stop();
924                         mAltosVoice = null;
925                 }
926                 stop_timer();
927         }
928
929         protected void onActivityResult(int requestCode, int resultCode, Intent data) {
930                 AltosDebug.debug("onActivityResult request %d result %d", requestCode, resultCode);
931                 switch (requestCode) {
932                 case REQUEST_CONNECT_DEVICE:
933                         // When DeviceListActivity returns with a device to connect to
934                         if (resultCode == Activity.RESULT_OK) {
935                                 connectDevice(data);
936                         }
937                         break;
938                 case REQUEST_ENABLE_BT:
939                         // When the request to enable Bluetooth returns
940                         if (resultCode == Activity.RESULT_OK) {
941                                 // Bluetooth is now enabled, so set up a chat session
942                                 //setupChat();
943                                 AltosDebug.debug("BT enabled");
944                                 bluetoothEnabled(data);
945                         } else {
946                                 // User did not enable Bluetooth or an error occured
947                                 AltosDebug.debug("BT not enabled");
948                         }
949                         break;
950                 case REQUEST_IDLE_MODE:
951                         if (resultCode == Activity.RESULT_OK)
952                                 idle_mode(data);
953                         break;
954                 case REQUEST_IGNITERS:
955                         break;
956                 case REQUEST_SETUP:
957                         if (resultCode == Activity.RESULT_OK)
958                                 note_setup_changes(data);
959                         break;
960                 case REQUEST_SELECT_TRACKER:
961                         if (resultCode == Activity.RESULT_OK)
962                                 select_tracker(data);
963                         break;
964                 case REQUEST_DELETE_TRACKER:
965                         if (resultCode == Activity.RESULT_OK)
966                                 delete_track(data);
967                         break;
968                 }
969         }
970
971         private void note_setup_changes(Intent data) {
972                 int changes = data.getIntExtra(SetupActivity.EXTRA_SETUP_CHANGES, 0);
973
974                 AltosDebug.debug("note_setup_changes changes %d\n", changes);
975
976                 if ((changes & SETUP_BAUD) != 0) {
977                         try {
978                                 mService.send(Message.obtain(null, TelemetryService.MSG_SETBAUD,
979                                                              AltosPreferences.telemetry_rate(1)));
980                         } catch (RemoteException re) {
981                         }
982                 }
983                 if ((changes & SETUP_UNITS) != 0) {
984                         /* nothing to do here */
985                 }
986                 if ((changes & SETUP_MAP_SOURCE) != 0) {
987                         /* nothing to do here */
988                 }
989                 if ((changes & SETUP_MAP_TYPE) != 0) {
990                         /* nothing to do here */
991                 }
992                 set_switch_time();
993                 if ((changes & SETUP_FONT_SIZE) != 0) {
994                         AltosDebug.debug(" ==== Recreate to switch font sizes ==== ");
995                         finish();
996                         startActivity(getIntent());
997                 }
998         }
999
1000         private void connectUsb(UsbDevice device) {
1001                 if (mService == null)
1002                         pending_usb_device = device;
1003                 else {
1004                         // Attempt to connect to the device
1005                         try {
1006                                 mService.send(Message.obtain(null, TelemetryService.MSG_OPEN_USB, device));
1007                                 AltosDebug.debug("Sent OPEN_USB message");
1008                         } catch (RemoteException e) {
1009                                 AltosDebug.debug("connect device message failed");
1010                         }
1011                 }
1012         }
1013
1014         private void bluetoothEnabled(Intent data) {
1015                 if (mService != null) {
1016                         try {
1017                                 mService.send(Message.obtain(null, TelemetryService.MSG_BLUETOOTH_ENABLED, null));
1018                         } catch (RemoteException e) {
1019                                 AltosDebug.debug("send BT enabled message failed");
1020                         }
1021                 }
1022         }
1023
1024         private void connectDevice(Intent data) {
1025                 // Attempt to connect to the device
1026                 try {
1027                         String address = data.getExtras().getString(DeviceListActivity.EXTRA_DEVICE_ADDRESS);
1028                         String name = data.getExtras().getString(DeviceListActivity.EXTRA_DEVICE_NAME);
1029
1030                         AltosDebug.debug("Connecting to " + address + " " + name);
1031                         DeviceAddress   a = new DeviceAddress(address, name);
1032                         mService.send(Message.obtain(null, TelemetryService.MSG_CONNECT, a));
1033                         AltosDebug.debug("Sent connecting message");
1034                 } catch (RemoteException e) {
1035                         AltosDebug.debug("connect device message failed");
1036                 }
1037         }
1038
1039         private void disconnectDevice(boolean remember) {
1040                 try {
1041                         mService.send(Message.obtain(null, TelemetryService.MSG_DISCONNECT, (Boolean) remember));
1042                 } catch (RemoteException e) {
1043                 }
1044         }
1045
1046         private void idle_mode(Intent data) {
1047                 int type = data.getIntExtra(IdleModeActivity.EXTRA_IDLE_RESULT, -1);
1048                 Message msg;
1049
1050                 AltosDebug.debug("intent idle_mode %d", type);
1051                 switch (type) {
1052                 case IdleModeActivity.IDLE_MODE_CONNECT:
1053                         msg = Message.obtain(null, TelemetryService.MSG_MONITOR_IDLE_START);
1054                         try {
1055                                 mService.send(msg);
1056                         } catch (RemoteException re) {
1057                         }
1058                         break;
1059                 case IdleModeActivity.IDLE_MODE_DISCONNECT:
1060                         msg = Message.obtain(null, TelemetryService.MSG_MONITOR_IDLE_STOP);
1061                         try {
1062                                 mService.send(msg);
1063                         } catch (RemoteException re) {
1064                         }
1065                         break;
1066                 case IdleModeActivity.IDLE_MODE_REBOOT:
1067                         msg = Message.obtain(null, TelemetryService.MSG_REBOOT);
1068                         try {
1069                                 mService.send(msg);
1070                         } catch (RemoteException re) {
1071                         }
1072                         break;
1073                 case IdleModeActivity.IDLE_MODE_IGNITERS:
1074                         Intent serverIntent = new Intent(this, IgniterActivity.class);
1075                         startActivityForResult(serverIntent, REQUEST_IGNITERS);
1076                         break;
1077                 }
1078         }
1079
1080         boolean fail_shown;
1081
1082         private void file_failed(File file) {
1083                 if (!fail_shown) {
1084                         fail_shown = true;
1085                         AlertDialog fail = new AlertDialog.Builder(this).create();
1086                         fail.setTitle("Failed to Create Log File");
1087                         fail.setMessage(file.getPath());
1088                         fail.setButton(AlertDialog.BUTTON_NEUTRAL, "OK",
1089                                        new DialogInterface.OnClickListener() {
1090                                                public void onClick(DialogInterface dialog, int which) {
1091                                                        dialog.dismiss();
1092                                                }
1093                                        });
1094                         fail.show();
1095                 }
1096         }
1097
1098         @Override
1099         public boolean onCreateOptionsMenu(Menu menu) {
1100                 MenuInflater inflater = getMenuInflater();
1101                 inflater.inflate(R.menu.option_menu, menu);
1102                 return true;
1103         }
1104
1105         double telem_frequency = 434.550;
1106         double selected_frequency = AltosLib.MISSING;
1107
1108         void setFrequency(double freq) {
1109                 telem_frequency = freq;
1110                 selected_frequency = AltosLib.MISSING;
1111                 try {
1112                         mService.send(Message.obtain(null, TelemetryService.MSG_SETFREQUENCY, freq));
1113                         set_switch_time();
1114                 } catch (RemoteException e) {
1115                 }
1116         }
1117
1118         void setFrequency(AltosFrequency frequency) {
1119                 setFrequency (frequency.frequency);
1120         }
1121
1122         void setBaud(int baud) {
1123                 try {
1124                         mService.send(Message.obtain(null, TelemetryService.MSG_SETBAUD, baud));
1125                         set_switch_time();
1126                 } catch (RemoteException e) {
1127                 }
1128         }
1129
1130         void setBaud(String baud) {
1131                 try {
1132                         int     value = Integer.parseInt(baud);
1133                         int     rate = AltosLib.ao_telemetry_rate_38400;
1134                         switch (value) {
1135                         case 2400:
1136                                 rate = AltosLib.ao_telemetry_rate_2400;
1137                                 break;
1138                         case 9600:
1139                                 rate = AltosLib.ao_telemetry_rate_9600;
1140                                 break;
1141                         case 38400:
1142                                 rate = AltosLib.ao_telemetry_rate_38400;
1143                                 break;
1144                         }
1145                         setBaud(rate);
1146                 } catch (NumberFormatException e) {
1147                 }
1148         }
1149
1150         void select_tracker(int serial, double frequency) {
1151
1152                 AltosDebug.debug("select tracker %d %7.3f\n", serial, frequency);
1153
1154                 if (serial == selected_serial) {
1155                         AltosDebug.debug("%d already selected\n", serial);
1156                         return;
1157                 }
1158
1159                 if (serial != 0) {
1160                         int i;
1161                         for (i = 0; i < trackers.length; i++)
1162                                 if (trackers[i].serial == serial)
1163                                         break;
1164
1165                         if (i == trackers.length) {
1166                                 AltosDebug.debug("attempt to select unknown tracker %d\n", serial);
1167                                 return;
1168                         }
1169                         if (frequency != 0.0 && frequency != AltosLib.MISSING)
1170                                 setFrequency(frequency);
1171                 }
1172
1173                 selected_serial = serial;
1174                 update_state(null);
1175         }
1176
1177         void select_tracker(Intent data) {
1178                 int serial = data.getIntExtra(SelectTrackerActivity.EXTRA_SERIAL_NUMBER, 0);
1179                 double frequency = data.getDoubleExtra(SelectTrackerActivity.EXTRA_FREQUENCY, 0.0);
1180                 select_tracker(serial, frequency);
1181         }
1182
1183         void delete_track(int serial) {
1184                 try {
1185                         mService.send(Message.obtain(null, TelemetryService.MSG_DELETE_SERIAL, (Integer) serial));
1186                 } catch (Exception ex) {
1187                 }
1188         }
1189
1190         void delete_track(Intent data) {
1191                 int serial = data.getIntExtra(SelectTrackerActivity.EXTRA_SERIAL_NUMBER, 0);
1192                 if (serial != 0)
1193                         delete_track(serial);
1194         }
1195
1196         void start_select_tracker(Tracker[] select_trackers, int title_id, int request) {
1197                 Intent intent = new Intent(this, SelectTrackerActivity.class);
1198                 AltosDebug.debug("put title id 0x%x %s", title_id, getResources().getString(title_id));
1199                 intent.putExtra(EXTRA_TRACKERS_TITLE, title_id);
1200                 if (select_trackers != null) {
1201                         ArrayList<Tracker> tracker_array = new ArrayList<Tracker>(Arrays.asList(select_trackers));
1202                         intent.putParcelableArrayListExtra(EXTRA_TRACKERS, tracker_array);
1203                 } else {
1204                         intent.putExtra(EXTRA_TRACKERS, (Parcelable[]) null);
1205                 }
1206                 startActivityForResult(intent, request);
1207         }
1208
1209         void start_select_tracker(Tracker[] select_trackers) {
1210                 start_select_tracker(select_trackers, R.string.select_tracker, REQUEST_SELECT_TRACKER);
1211         }
1212
1213         void touch_trackers(Integer[] serials) {
1214                 Tracker[] my_trackers = new Tracker[serials.length];
1215
1216                 for (int i = 0; i < serials.length; i++) {
1217                         AltosState      s = telemetry_state.get(serials[i]);
1218                         my_trackers[i] = new Tracker(s);
1219                 }
1220                 start_select_tracker(my_trackers);
1221         }
1222
1223         @Override
1224         public boolean onOptionsItemSelected(MenuItem item) {
1225                 Intent serverIntent = null;
1226                 switch (item.getItemId()) {
1227                 case R.id.connect_scan:
1228                         ensureBluetooth();
1229                         // Launch the DeviceListActivity to see devices and do scan
1230                         serverIntent = new Intent(this, DeviceListActivity.class);
1231                         startActivityForResult(serverIntent, REQUEST_CONNECT_DEVICE);
1232                         return true;
1233                 case R.id.disconnect:
1234                         /* Disconnect the device
1235                          */
1236                         disconnectDevice(false);
1237                         return true;
1238                 case R.id.quit:
1239                         AltosDebug.debug("R.id.quit");
1240                         disconnectDevice(true);
1241                         finish();
1242                         return true;
1243                 case R.id.setup:
1244                         serverIntent = new Intent(this, SetupActivity.class);
1245                         startActivityForResult(serverIntent, REQUEST_SETUP);
1246                         return true;
1247                 case R.id.select_freq:
1248                         // Set the TBT radio frequency
1249
1250                         final AltosFrequency[] frequencies = AltosPreferences.common_frequencies();
1251                         String[] frequency_strings = new String[frequencies.length];
1252                         for (int i = 0; i < frequencies.length; i++)
1253                                 frequency_strings[i] = frequencies[i].toString();
1254
1255                         AlertDialog.Builder builder_freq = new AlertDialog.Builder(this);
1256                         builder_freq.setTitle("Select Frequency");
1257                         builder_freq.setItems(frequency_strings,
1258                                          new DialogInterface.OnClickListener() {
1259                                                  public void onClick(DialogInterface dialog, int item) {
1260                                                          setFrequency(frequencies[item]);
1261                                                          selected_frequency = frequencies[item].frequency;
1262                                                  }
1263                                          });
1264                         AlertDialog alert_freq = builder_freq.create();
1265                         alert_freq.show();
1266                         return true;
1267                 case R.id.select_tracker:
1268                         start_select_tracker(trackers);
1269                         return true;
1270                 case R.id.delete_track:
1271                         if (trackers != null && trackers.length > 0)
1272                                 start_select_tracker(trackers, R.string.delete_track, REQUEST_DELETE_TRACKER);
1273                         return true;
1274                 case R.id.idle_mode:
1275                         serverIntent = new Intent(this, IdleModeActivity.class);
1276                         serverIntent.putExtra(EXTRA_IDLE_MODE, idle_mode);
1277                         serverIntent.putExtra(EXTRA_FREQUENCY, telem_frequency);
1278                         startActivityForResult(serverIntent, REQUEST_IDLE_MODE);
1279                         return true;
1280                 }
1281                 return false;
1282         }
1283
1284         static String direction(AltosGreatCircle from_receiver,
1285                                 Location receiver) {
1286                 if (from_receiver == null)
1287                         return null;
1288
1289                 if (receiver == null)
1290                         return null;
1291
1292                 if (!receiver.hasBearing())
1293                         return null;
1294
1295                 float   bearing = receiver.getBearing();
1296                 float   heading = (float) from_receiver.bearing - bearing;
1297
1298                 while (heading <= -180.0f)
1299                         heading += 360.0f;
1300                 while (heading > 180.0f)
1301                         heading -= 360.0f;
1302
1303                 int iheading = (int) (heading + 0.5f);
1304
1305                 if (-1 < iheading && iheading < 1)
1306                         return "ahead";
1307                 else if (iheading < -179 || 179 < iheading)
1308                         return "backwards";
1309                 else if (iheading < 0)
1310                         return String.format("left %d°", -iheading);
1311                 else
1312                         return String.format("right %d°", iheading);
1313         }
1314
1315         public void onLocationChanged(Location location) {
1316                 this.location = location;
1317                 AltosDebug.debug("Location changed to %f,%f",
1318                                  location.getLatitude(),
1319                                  location.getLongitude());
1320                 update_ui(telemetry_state, state, false);
1321         }
1322
1323         public void onStatusChanged(String provider, int status, Bundle extras) {
1324                 AltosDebug.debug("Location status now %d\n", status);
1325         }
1326
1327         public void onProviderEnabled(String provider) {
1328                 AltosDebug.debug("Location provider enabled %s\n", provider);
1329         }
1330
1331         public void onProviderDisabled(String provider) {
1332                 AltosDebug.debug("Location provider disabled %s\n", provider);
1333         }
1334 }