2 * Copyright © 2012-2013 Mike Beattie <mike@ethernal.org>
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.
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.
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.
18 package org.altusmetrum.AltosDroid;
20 import java.lang.ref.WeakReference;
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.hardware.usb.*;
49 import android.graphics.*;
50 import android.graphics.drawable.*;
52 import org.altusmetrum.altoslib_7.*;
54 public class AltosDroid extends FragmentActivity implements AltosUnitsListener {
56 // Actions sent to the telemetry server at startup time
58 public static final String ACTION_BLUETOOTH = "org.altusmetrum.AltosDroid.BLUETOOTH";
59 public static final String ACTION_USB = "org.altusmetrum.AltosDroid.USB";
61 // Message types received by our Handler
63 public static final int MSG_STATE = 1;
64 public static final int MSG_UPDATE_AGE = 2;
66 // Intent request codes
67 public static final int REQUEST_CONNECT_DEVICE = 1;
68 public static final int REQUEST_ENABLE_BT = 2;
69 public static final int REQUEST_PRELOAD_MAPS = 3;
70 public static final int REQUEST_MAP_TYPE = 4;
72 public int map_type = AltosMap.maptype_hybrid;
74 public static FragmentManager fm;
76 private BluetoothAdapter mBluetoothAdapter = null;
78 // Flight state values
79 private TextView mCallsignView;
80 private TextView mRSSIView;
81 private TextView mSerialView;
82 private TextView mFlightView;
83 private RelativeLayout mStateLayout;
84 private TextView mStateView;
85 private TextView mAgeView;
86 private boolean mAgeViewOld;
87 private int mAgeNewColor;
88 private int mAgeOldColor;
90 public static final String tab_pad_name = "pad";
91 public static final String tab_flight_name = "flight";
92 public static final String tab_recover_name = "recover";
93 public static final String tab_map_name = "map";
95 // field to display the version at the bottom of the screen
96 private TextView mVersion;
98 private double frequency;
99 private int telemetry_rate;
103 AltosViewPager mViewPager;
104 TabsAdapter mTabsAdapter;
105 ArrayList<AltosDroidTab> mTabs = new ArrayList<AltosDroidTab>();
108 // Timer and Saved flight state for Age calculation
110 AltosState saved_state;
111 TelemetryState telemetry_state;
114 UsbDevice pending_usb_device;
115 boolean start_with_usb;
118 private boolean mIsBound = false;
119 private Messenger mService = null;
120 final Messenger mMessenger = new Messenger(new IncomingHandler(this));
123 private AltosVoice mAltosVoice = null;
125 // The Handler that gets information back from the Telemetry Service
126 static class IncomingHandler extends Handler {
127 private final WeakReference<AltosDroid> mAltosDroid;
128 IncomingHandler(AltosDroid ad) { mAltosDroid = new WeakReference<AltosDroid>(ad); }
131 public void handleMessage(Message msg) {
132 AltosDroid ad = mAltosDroid.get();
136 AltosDebug.debug("MSG_STATE");
137 if (msg.obj == null) {
138 AltosDebug.debug("telemetry_state null!");
141 ad.update_state((TelemetryState) msg.obj);
144 AltosDebug.debug("MSG_UPDATE_AGE");
152 private ServiceConnection mConnection = new ServiceConnection() {
153 public void onServiceConnected(ComponentName className, IBinder service) {
154 mService = new Messenger(service);
156 Message msg = Message.obtain(null, TelemetryService.MSG_REGISTER_CLIENT);
157 msg.replyTo = mMessenger;
159 } catch (RemoteException e) {
160 // In this case the service has crashed before we could even do anything with it
162 if (pending_usb_device != null) {
164 mService.send(Message.obtain(null, TelemetryService.MSG_OPEN_USB, pending_usb_device));
165 pending_usb_device = null;
166 } catch (RemoteException e) {
171 public void onServiceDisconnected(ComponentName className) {
172 // This is called when the connection with the service has been unexpectedly disconnected - process crashed.
177 void doBindService() {
178 bindService(new Intent(this, TelemetryService.class), mConnection, Context.BIND_AUTO_CREATE);
182 void doUnbindService() {
184 // If we have received the service, and hence registered with it, then now is the time to unregister.
185 if (mService != null) {
187 Message msg = Message.obtain(null, TelemetryService.MSG_UNREGISTER_CLIENT);
188 msg.replyTo = mMessenger;
190 } catch (RemoteException e) {
191 // There is nothing special we need to do if the service has crashed.
194 // Detach our existing connection.
195 unbindService(mConnection);
200 public void registerTab(AltosDroidTab mTab) {
204 public void unregisterTab(AltosDroidTab mTab) {
208 public void units_changed(boolean imperial_units) {
209 for (AltosDroidTab mTab : mTabs)
210 mTab.units_changed(imperial_units);
213 void update_title(TelemetryState telemetry_state) {
214 switch (telemetry_state.connect) {
215 case TelemetryState.CONNECT_CONNECTED:
216 if (telemetry_state.config != null) {
217 String str = String.format("S/N %d %6.3f MHz", telemetry_state.config.serial,
218 telemetry_state.frequency);
219 if (telemetry_state.telemetry_rate != AltosLib.ao_telemetry_rate_38400)
220 str = str.concat(String.format(" %d bps",
221 AltosLib.ao_telemetry_rate_values[telemetry_state.telemetry_rate]));
224 setTitle(R.string.title_connected_to);
227 case TelemetryState.CONNECT_CONNECTING:
228 if (telemetry_state.address != null)
229 setTitle(String.format("Connecting to %s...", telemetry_state.address.name));
231 setTitle("Connecting to something...");
233 case TelemetryState.CONNECT_DISCONNECTED:
234 case TelemetryState.CONNECT_NONE:
235 setTitle(R.string.title_not_connected);
243 timer.scheduleAtFixedRate(new TimerTask(){ public void run() {onTimerTick();}}, 1000L, 1000L);
255 int selected_serial = 0;
259 void set_switch_time() {
260 switch_time = System.currentTimeMillis();
264 boolean registered_units_listener;
266 void update_state(TelemetryState new_telemetry_state) {
268 if (new_telemetry_state != null)
269 telemetry_state = new_telemetry_state;
271 if (selected_serial != 0)
272 current_serial = selected_serial;
274 if (current_serial == 0)
275 current_serial = telemetry_state.latest_serial;
277 if (!registered_units_listener) {
278 registered_units_listener = true;
279 AltosPreferences.register_units_listener(this);
282 serials = telemetry_state.states.keySet().toArray(new Integer[0]);
283 Arrays.sort(serials);
285 update_title(telemetry_state);
287 AltosState state = null;
290 if (telemetry_state.states.containsKey(current_serial)) {
291 state = telemetry_state.states.get(current_serial);
292 int age = state_age(state);
295 if (current_serial == selected_serial)
297 else if (switch_time != 0 && (switch_time - state.received_time) > 0)
302 AltosState newest_state = null;
305 for (int serial : telemetry_state.states.keySet()) {
306 AltosState existing = telemetry_state.states.get(serial);
307 int existing_age = state_age(existing);
309 if (newest_state == null || existing_age < newest_age) {
310 newest_state = existing;
311 newest_age = existing_age;
315 if (newest_state != null)
316 state = newest_state;
319 update_ui(telemetry_state, state, telemetry_state.location);
324 boolean same_string(String a, String b) {
337 private int blend_component(int a, int b, double r, int shift, int mask) {
338 return ((int) (((a >> shift) & mask) * r + ((b >> shift) & mask) * (1 - r)) & mask) << shift;
340 private int blend_color(int a, int b, double r) {
341 return (blend_component(a, b, r, 0, 0xff) |
342 blend_component(a, b, r, 8, 0xff) |
343 blend_component(a, b, r, 16, 0xff) |
344 blend_component(a, b, r, 24, 0xff));
347 int state_age(AltosState state) {
348 return (int) ((System.currentTimeMillis() - state.received_time + 500) / 1000);
351 void set_screen_on(int age) {
353 getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
355 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
359 if (saved_state != null) {
360 int age = state_age(saved_state);
362 double age_scale = age / 100.0;
367 mAgeView.setTextColor(blend_color(mAgeOldColor, mAgeNewColor, age_scale));
373 text = String.format("%ds", age);
374 else if (age < 60 * 60)
375 text = String.format("%dm", age / 60);
376 else if (age < 60 * 60 * 24)
377 text = String.format("%dh", age / (60 * 60));
379 text = String.format("%dd", age / (24 * 60 * 60));
380 mAgeView.setText(text);
384 void update_ui(TelemetryState telem_state, AltosState state, Location location) {
386 int prev_state = AltosLib.ao_flight_invalid;
388 AltosGreatCircle from_receiver = null;
390 if (saved_state != null)
391 prev_state = saved_state.state;
394 set_screen_on(state_age(state));
396 if (state.state == AltosLib.ao_flight_stateless) {
397 boolean prev_locked = false;
398 boolean locked = false;
400 if(state.gps != null)
401 locked = state.gps.locked;
402 if (saved_state != null && saved_state.gps != null)
403 prev_locked = saved_state.gps.locked;
404 if (prev_locked != locked) {
405 String currentTab = mTabHost.getCurrentTabTag();
407 if (currentTab.equals(tab_pad_name)) mTabHost.setCurrentTabByTag(tab_flight_name);
409 if (currentTab.equals(tab_flight_name)) mTabHost.setCurrentTabByTag(tab_pad_name);
413 if (prev_state != state.state) {
414 String currentTab = mTabHost.getCurrentTabTag();
415 switch (state.state) {
416 case AltosLib.ao_flight_boost:
417 if (currentTab.equals(tab_pad_name)) mTabHost.setCurrentTabByTag(tab_flight_name);
419 case AltosLib.ao_flight_landed:
420 if (currentTab.equals(tab_flight_name)) mTabHost.setCurrentTabByTag(tab_recover_name);
422 case AltosLib.ao_flight_stateless:
423 if (currentTab.equals(tab_pad_name)) mTabHost.setCurrentTabByTag(tab_flight_name);
429 if (location != null && state.gps != null && state.gps.locked) {
431 if (location.hasAltitude())
432 altitude = location.getAltitude();
433 from_receiver = new AltosGreatCircle(location.getLatitude(),
434 location.getLongitude(),
441 if (saved_state == null || !same_string(saved_state.callsign, state.callsign)) {
442 mCallsignView.setText(state.callsign);
444 if (saved_state == null || state.serial != saved_state.serial) {
445 mSerialView.setText(String.format("%d", state.serial));
447 if (saved_state == null || state.flight != saved_state.flight) {
448 if (state.flight == AltosLib.MISSING)
449 mFlightView.setText("");
451 mFlightView.setText(String.format("%d", state.flight));
453 if (saved_state == null || state.state != saved_state.state) {
454 if (state.state == AltosLib.ao_flight_stateless) {
455 mStateLayout.setVisibility(View.GONE);
457 mStateView.setText(state.state_name());
458 mStateLayout.setVisibility(View.VISIBLE);
461 if (saved_state == null || state.rssi != saved_state.rssi) {
462 mRSSIView.setText(String.format("%d", state.rssi));
466 for (AltosDroidTab mTab : mTabs)
467 mTab.update_ui(telem_state, state, from_receiver, location, mTab == mTabsAdapter.currentItem());
469 if (state != null && mAltosVoice != null)
470 mAltosVoice.tell(state, from_receiver);
475 private void onTimerTick() {
477 mMessenger.send(Message.obtain(null, MSG_UPDATE_AGE));
478 } catch (RemoteException e) {
482 static String pos(double p, String pos, String neg) {
484 if (p == AltosLib.MISSING)
490 int deg = (int) Math.floor(p);
491 double min = (p - Math.floor(p)) * 60.0;
492 return String.format("%d°%9.4f\" %s", deg, min, h);
495 static String number(String format, double value) {
496 if (value == AltosLib.MISSING)
498 return String.format(format, value);
501 static String integer(String format, int value) {
502 if (value == AltosLib.MISSING)
504 return String.format(format, value);
507 private View create_tab_view(String label) {
508 LayoutInflater inflater = (LayoutInflater) this.getLayoutInflater();
509 View tab_view = inflater.inflate(R.layout.tab_layout, null);
510 TextView text_view = (TextView) tab_view.findViewById (R.id.tabLabel);
511 text_view.setText(label);
515 public void set_map_source(int source) {
516 for (AltosDroidTab mTab : mTabs)
517 mTab.set_map_source(source);
521 public void onCreate(Bundle savedInstanceState) {
522 super.onCreate(savedInstanceState);
523 AltosDebug.init(this);
524 AltosDebug.debug("+++ ON CREATE +++");
526 // Initialise preferences
527 AltosDroidPreferences.init(this);
529 fm = getSupportFragmentManager();
531 // Set up the window layout
532 setContentView(R.layout.altosdroid);
534 // Create the Tabs and ViewPager
535 mTabHost = (TabHost)findViewById(android.R.id.tabhost);
538 mViewPager = (AltosViewPager)findViewById(R.id.pager);
539 mViewPager.setOffscreenPageLimit(4);
541 mTabsAdapter = new TabsAdapter(this, mTabHost, mViewPager);
543 mTabsAdapter.addTab(mTabHost.newTabSpec(tab_pad_name).setIndicator(create_tab_view("Pad")), TabPad.class, null);
544 mTabsAdapter.addTab(mTabHost.newTabSpec(tab_flight_name).setIndicator(create_tab_view("Flight")), TabFlight.class, null);
545 mTabsAdapter.addTab(mTabHost.newTabSpec(tab_recover_name).setIndicator(create_tab_view("Recover")), TabRecover.class, null);
546 mTabsAdapter.addTab(mTabHost.newTabSpec(tab_map_name).setIndicator(create_tab_view("Map")), TabMap.class, null);
548 // Display the Version
549 mVersion = (TextView) findViewById(R.id.version);
550 mVersion.setText("Version: " + BuildInfo.version +
551 " Built: " + BuildInfo.builddate + " " + BuildInfo.buildtime + " " + BuildInfo.buildtz +
552 " (" + BuildInfo.branch + "-" + BuildInfo.commitnum + "-" + BuildInfo.commithash + ")");
554 mCallsignView = (TextView) findViewById(R.id.callsign_value);
555 mRSSIView = (TextView) findViewById(R.id.rssi_value);
556 mSerialView = (TextView) findViewById(R.id.serial_value);
557 mFlightView = (TextView) findViewById(R.id.flight_value);
558 mStateLayout = (RelativeLayout) findViewById(R.id.state_container);
559 mStateView = (TextView) findViewById(R.id.state_value);
560 mAgeView = (TextView) findViewById(R.id.age_value);
561 mAgeNewColor = mAgeView.getTextColors().getDefaultColor();
562 mAgeOldColor = getResources().getColor(R.color.old_color);
565 private boolean ensureBluetooth() {
566 // Get local Bluetooth adapter
567 mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
569 // If the adapter is null, then Bluetooth is not supported
570 if (mBluetoothAdapter == null) {
571 Toast.makeText(this, "Bluetooth is not available", Toast.LENGTH_LONG).show();
575 if (!mBluetoothAdapter.isEnabled()) {
576 Intent enableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
577 startActivityForResult(enableIntent, AltosDroid.REQUEST_ENABLE_BT);
583 private boolean check_usb() {
584 UsbDevice device = AltosUsb.find_device(this, AltosLib.product_basestation);
586 if (device != null) {
587 Intent i = new Intent(this, AltosDroid.class);
588 PendingIntent pi = PendingIntent.getActivity(this, 0, new Intent("hello world", null, this, AltosDroid.class), 0);
590 if (AltosUsb.request_permission(this, device, pi)) {
593 start_with_usb = true;
597 start_with_usb = false;
602 private void noticeIntent(Intent intent) {
604 /* Ok, this is pretty convenient.
606 * When a USB device is plugged in, and our 'hotplug'
607 * intent registration fires, we get an Intent with
610 * When we start up and see a usb device and request
611 * permission to access it, that queues a
612 * PendingIntent, which has the EXTRA_DEVICE added in,
613 * along with the EXTRA_PERMISSION_GRANTED field as
616 * So, in both cases, we get the device name using the
617 * same call. We check to see if access was granted,
618 * in which case we ignore the device field and do our
619 * usual startup thing.
622 UsbDevice device = (UsbDevice) intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
623 boolean granted = intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, true);
625 AltosDebug.debug("intent %s device %s granted %s", intent, device, granted);
630 if (device != null) {
631 AltosDebug.debug("intent has usb device " + device.toString());
635 /* 'granted' is only false if this intent came
636 * from the request_permission call and
637 * permission was denied. In which case, we
638 * don't want to loop forever...
641 AltosDebug.debug("check for a USB device at startup");
645 AltosDebug.debug("Starting by looking for bluetooth devices");
646 if (ensureBluetooth())
653 public void onStart() {
655 AltosDebug.debug("++ ON START ++");
657 noticeIntent(getIntent());
659 // Start Telemetry Service
660 String action = start_with_usb ? ACTION_USB : ACTION_BLUETOOTH;
662 startService(new Intent(action, null, AltosDroid.this, TelemetryService.class));
666 if (mAltosVoice == null)
667 mAltosVoice = new AltosVoice(this);
672 public void onNewIntent(Intent intent) {
673 super.onNewIntent(intent);
674 AltosDebug.debug("onNewIntent");
675 noticeIntent(intent);
679 public void onResume() {
681 AltosDebug.debug("+ ON RESUME +");
685 public void onPause() {
687 AltosDebug.debug("- ON PAUSE -");
691 public void onStop() {
693 AltosDebug.debug("-- ON STOP --");
696 if (mAltosVoice != null) {
703 public void onDestroy() {
705 AltosDebug.debug("--- ON DESTROY ---");
707 if (mAltosVoice != null) mAltosVoice.stop();
711 protected void onActivityResult(int requestCode, int resultCode, Intent data) {
712 AltosDebug.debug("onActivityResult " + resultCode);
713 switch (requestCode) {
714 case REQUEST_CONNECT_DEVICE:
715 // When DeviceListActivity returns with a device to connect to
716 if (resultCode == Activity.RESULT_OK) {
720 case REQUEST_ENABLE_BT:
721 // When the request to enable Bluetooth returns
722 if (resultCode == Activity.RESULT_OK) {
723 // Bluetooth is now enabled, so set up a chat session
726 // User did not enable Bluetooth or an error occured
727 AltosDebug.error("BT not enabled");
728 stopService(new Intent(AltosDroid.this, TelemetryService.class));
729 Toast.makeText(this, R.string.bt_not_enabled, Toast.LENGTH_SHORT).show();
733 case REQUEST_MAP_TYPE:
734 if (resultCode == Activity.RESULT_OK)
740 private void connectUsb(UsbDevice device) {
741 if (mService == null)
742 pending_usb_device = device;
744 // Attempt to connect to the device
746 mService.send(Message.obtain(null, TelemetryService.MSG_OPEN_USB, device));
747 AltosDebug.debug("Sent OPEN_USB message");
748 } catch (RemoteException e) {
749 AltosDebug.debug("connect device message failed");
754 private void connectDevice(Intent data) {
755 // Attempt to connect to the device
757 String address = data.getExtras().getString(DeviceListActivity.EXTRA_DEVICE_ADDRESS);
758 String name = data.getExtras().getString(DeviceListActivity.EXTRA_DEVICE_NAME);
760 AltosDebug.debug("Connecting to " + address + " " + name);
761 DeviceAddress a = new DeviceAddress(address, name);
762 mService.send(Message.obtain(null, TelemetryService.MSG_CONNECT, a));
763 AltosDebug.debug("Sent connecting message");
764 } catch (RemoteException e) {
765 AltosDebug.debug("connect device message failed");
769 private void disconnectDevice() {
771 mService.send(Message.obtain(null, TelemetryService.MSG_DISCONNECT, null));
772 } catch (RemoteException e) {
776 private void set_map_type(Intent data) {
777 int type = data.getIntExtra(MapTypeActivity.EXTRA_MAP_TYPE, -1);
779 AltosDebug.debug("intent set_map_type %d\n", type);
782 for (AltosDroidTab mTab : mTabs)
783 mTab.set_map_type(map_type);
788 public boolean onCreateOptionsMenu(Menu menu) {
789 MenuInflater inflater = getMenuInflater();
790 inflater.inflate(R.menu.option_menu, menu);
794 void setFrequency(double freq) {
796 mService.send(Message.obtain(null, TelemetryService.MSG_SETFREQUENCY, freq));
798 } catch (RemoteException e) {
802 void setFrequency(String freq) {
804 setFrequency (AltosParse.parse_double_net(freq.substring(11, 17)));
805 } catch (ParseException e) {
809 void setBaud(int baud) {
811 mService.send(Message.obtain(null, TelemetryService.MSG_SETBAUD, baud));
813 } catch (RemoteException e) {
817 void setBaud(String baud) {
819 int value = Integer.parseInt(baud);
820 int rate = AltosLib.ao_telemetry_rate_38400;
823 rate = AltosLib.ao_telemetry_rate_2400;
826 rate = AltosLib.ao_telemetry_rate_9600;
829 rate = AltosLib.ao_telemetry_rate_38400;
833 } catch (NumberFormatException e) {
837 void select_tracker(int serial) {
840 AltosDebug.debug("select tracker %d\n", serial);
842 if (serial == selected_serial) {
843 AltosDebug.debug("%d already selected\n", serial);
848 for (i = 0; i < serials.length; i++)
849 if (serials[i] == serial)
852 if (i == serials.length) {
853 AltosDebug.debug("attempt to select unknown tracker %d\n", serial);
858 current_serial = selected_serial = serial;
862 void delete_track(int serial) {
864 mService.send(Message.obtain(null, TelemetryService.MSG_DELETE_SERIAL, (Integer) serial));
865 } catch (Exception ex) {
870 public boolean onOptionsItemSelected(MenuItem item) {
871 Intent serverIntent = null;
872 switch (item.getItemId()) {
873 case R.id.connect_scan:
874 if (ensureBluetooth()) {
875 // Launch the DeviceListActivity to see devices and do scan
876 serverIntent = new Intent(this, DeviceListActivity.class);
877 startActivityForResult(serverIntent, REQUEST_CONNECT_DEVICE);
880 case R.id.disconnect:
881 /* Disconnect the device
886 AltosDebug.debug("R.id.quit");
890 case R.id.select_freq:
891 // Set the TBT radio frequency
893 final String[] frequencies = {
894 "Channel 0 (434.550MHz)",
895 "Channel 1 (434.650MHz)",
896 "Channel 2 (434.750MHz)",
897 "Channel 3 (434.850MHz)",
898 "Channel 4 (434.950MHz)",
899 "Channel 5 (435.050MHz)",
900 "Channel 6 (435.150MHz)",
901 "Channel 7 (435.250MHz)",
902 "Channel 8 (435.350MHz)",
903 "Channel 9 (435.450MHz)"
906 AlertDialog.Builder builder_freq = new AlertDialog.Builder(this);
907 builder_freq.setTitle("Pick a frequency");
908 builder_freq.setItems(frequencies,
909 new DialogInterface.OnClickListener() {
910 public void onClick(DialogInterface dialog, int item) {
911 setFrequency(frequencies[item]);
914 AlertDialog alert_freq = builder_freq.create();
917 case R.id.select_rate:
918 // Set the TBT baud rate
920 final String[] rates = {
926 AlertDialog.Builder builder_rate = new AlertDialog.Builder(this);
927 builder_rate.setTitle("Pick a baud rate");
928 builder_rate.setItems(rates,
929 new DialogInterface.OnClickListener() {
930 public void onClick(DialogInterface dialog, int item) {
931 setBaud(rates[item]);
934 AlertDialog alert_rate = builder_rate.create();
937 case R.id.change_units:
938 boolean imperial = AltosPreferences.imperial_units();
939 AltosPreferences.set_imperial_units(!imperial);
941 case R.id.preload_maps:
942 serverIntent = new Intent(this, PreloadMapActivity.class);
943 startActivityForResult(serverIntent, REQUEST_PRELOAD_MAPS);
946 serverIntent = new Intent(this, MapTypeActivity.class);
947 startActivityForResult(serverIntent, REQUEST_MAP_TYPE);
949 case R.id.map_source:
950 int source = AltosDroidPreferences.map_source();
951 int new_source = source == AltosDroidPreferences.MAP_SOURCE_ONLINE ? AltosDroidPreferences.MAP_SOURCE_OFFLINE : AltosDroidPreferences.MAP_SOURCE_ONLINE;
952 AltosDroidPreferences.set_map_source(new_source);
953 set_map_source(new_source);
955 case R.id.select_tracker:
956 if (serials != null) {
957 String[] trackers = new String[serials.length+1];
958 trackers[0] = "Auto";
959 for (int i = 0; i < serials.length; i++)
960 trackers[i+1] = String.format("%d", serials[i]);
961 AlertDialog.Builder builder_serial = new AlertDialog.Builder(this);
962 builder_serial.setTitle("Select a tracker");
963 builder_serial.setItems(trackers,
964 new DialogInterface.OnClickListener() {
965 public void onClick(DialogInterface dialog, int item) {
969 select_tracker(serials[item-1]);
972 AlertDialog alert_serial = builder_serial.create();
977 case R.id.delete_track:
978 if (serials != null) {
979 String[] trackers = new String[serials.length];
980 for (int i = 0; i < serials.length; i++)
981 trackers[i] = String.format("%d", serials[i]);
982 AlertDialog.Builder builder_serial = new AlertDialog.Builder(this);
983 builder_serial.setTitle("Delete a track");
984 builder_serial.setItems(trackers,
985 new DialogInterface.OnClickListener() {
986 public void onClick(DialogInterface dialog, int item) {
987 delete_track(serials[item]);
990 AlertDialog alert_serial = builder_serial.create();
999 static String direction(AltosGreatCircle from_receiver,
1000 Location receiver) {
1001 if (!receiver.hasBearing())
1004 float bearing = receiver.getBearing();
1005 float heading = (float) from_receiver.bearing - bearing;
1007 while (heading <= -180.0f)
1009 while (heading > 180.0f)
1012 int iheading = (int) (heading + 0.5f);
1014 if (-1 < iheading && iheading < 1)
1016 else if (iheading < -179 || 179 < iheading)
1018 else if (iheading < 0)
1019 return String.format("left %d", -iheading);
1021 return String.format("right %d", iheading);