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