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.location.LocationManager;
49 import android.location.LocationListener;
50 import android.hardware.usb.*;
51 import android.graphics.*;
52 import android.graphics.drawable.*;
54 import org.altusmetrum.altoslib_10.*;
56 public class AltosDroid extends FragmentActivity implements AltosUnitsListener, LocationListener {
58 // Actions sent to the telemetry server at startup time
60 public static final String ACTION_BLUETOOTH = "org.altusmetrum.AltosDroid.BLUETOOTH";
61 public static final String ACTION_USB = "org.altusmetrum.AltosDroid.USB";
63 // Message types received by our Handler
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;
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;
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";
82 public int map_type = AltosMap.maptype_hybrid;
84 public static FragmentManager fm;
86 private BluetoothAdapter mBluetoothAdapter = null;
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;
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";
105 // field to display the version at the bottom of the screen
106 private TextView mVersion;
108 private double frequency;
109 private int telemetry_rate;
111 private boolean idle_mode = false;
113 public Location location = null;
117 AltosViewPager mViewPager;
118 TabsAdapter mTabsAdapter;
119 ArrayList<AltosDroidTab> mTabs = new ArrayList<AltosDroidTab>();
122 // Timer and Saved flight state for Age calculation
124 AltosState saved_state;
125 TelemetryState telemetry_state;
128 UsbDevice pending_usb_device;
129 boolean start_with_usb;
132 private boolean mIsBound = false;
133 private Messenger mService = null;
134 final Messenger mMessenger = new Messenger(new IncomingHandler(this));
137 private AltosVoice mAltosVoice = null;
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); }
145 public void handleMessage(Message msg) {
146 AltosDroid ad = mAltosDroid.get();
150 if (msg.obj == null) {
151 AltosDebug.debug("telemetry_state null!");
154 ad.update_state((TelemetryState) msg.obj);
160 ad.idle_mode = (Boolean) msg.obj;
161 ad.update_state(null);
168 private ServiceConnection mConnection = new ServiceConnection() {
169 public void onServiceConnected(ComponentName className, IBinder service) {
170 mService = new Messenger(service);
172 Message msg = Message.obtain(null, TelemetryService.MSG_REGISTER_CLIENT);
173 msg.replyTo = mMessenger;
175 } catch (RemoteException e) {
176 // In this case the service has crashed before we could even do anything with it
178 if (pending_usb_device != null) {
180 mService.send(Message.obtain(null, TelemetryService.MSG_OPEN_USB, pending_usb_device));
181 pending_usb_device = null;
182 } catch (RemoteException e) {
187 public void onServiceDisconnected(ComponentName className) {
188 // This is called when the connection with the service has been unexpectedly disconnected - process crashed.
193 void doBindService() {
194 bindService(new Intent(this, TelemetryService.class), mConnection, Context.BIND_AUTO_CREATE);
198 void doUnbindService() {
200 // If we have received the service, and hence registered with it, then now is the time to unregister.
201 if (mService != null) {
203 Message msg = Message.obtain(null, TelemetryService.MSG_UNREGISTER_CLIENT);
204 msg.replyTo = mMessenger;
206 } catch (RemoteException e) {
207 // There is nothing special we need to do if the service has crashed.
210 // Detach our existing connection.
211 unbindService(mConnection);
216 public void registerTab(AltosDroidTab mTab) {
220 public void unregisterTab(AltosDroidTab mTab) {
224 public void units_changed(boolean imperial_units) {
225 for (AltosDroidTab mTab : mTabs)
226 mTab.units_changed(imperial_units);
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]));
240 setTitle(R.string.title_connected_to);
243 case TelemetryState.CONNECT_CONNECTING:
244 if (telemetry_state.address != null)
245 setTitle(String.format("Connecting to %s...", telemetry_state.address.name));
247 setTitle("Connecting to something...");
249 case TelemetryState.CONNECT_DISCONNECTED:
250 case TelemetryState.CONNECT_NONE:
251 setTitle(R.string.title_not_connected);
259 timer.scheduleAtFixedRate(new TimerTask(){ public void run() {onTimerTick();}}, 1000L, 1000L);
271 int selected_serial = 0;
275 void set_switch_time() {
276 switch_time = System.currentTimeMillis();
280 boolean registered_units_listener;
282 void update_state(TelemetryState new_telemetry_state) {
284 if (new_telemetry_state != null)
285 telemetry_state = new_telemetry_state;
287 if (selected_serial != 0)
288 current_serial = selected_serial;
290 if (current_serial == 0)
291 current_serial = telemetry_state.latest_serial;
293 if (!registered_units_listener) {
294 registered_units_listener = true;
295 AltosPreferences.register_units_listener(this);
298 serials = telemetry_state.states.keySet().toArray(new Integer[0]);
299 Arrays.sort(serials);
301 update_title(telemetry_state);
303 AltosState state = null;
306 if (telemetry_state.states.containsKey(current_serial)) {
307 state = telemetry_state.states.get(current_serial);
308 int age = state_age(state);
311 if (current_serial == selected_serial)
313 else if (switch_time != 0 && (switch_time - state.received_time) > 0)
318 AltosState newest_state = null;
321 for (int serial : telemetry_state.states.keySet()) {
322 AltosState existing = telemetry_state.states.get(serial);
323 int existing_age = state_age(existing);
325 if (newest_state == null || existing_age < newest_age) {
326 newest_state = existing;
327 newest_age = existing_age;
331 if (newest_state != null)
332 state = newest_state;
335 update_ui(telemetry_state, state);
340 boolean same_string(String a, String b) {
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;
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));
363 int state_age(AltosState state) {
364 return (int) ((System.currentTimeMillis() - state.received_time + 500) / 1000);
367 void set_screen_on(int age) {
369 getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
371 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
375 if (saved_state != null) {
376 int age = state_age(saved_state);
378 double age_scale = age / 100.0;
383 mAgeView.setTextColor(blend_color(mAgeOldColor, mAgeNewColor, age_scale));
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));
395 text = String.format("%dd", age / (24 * 60 * 60));
396 mAgeView.setText(text);
400 void update_ui(TelemetryState telem_state, AltosState state) {
402 int prev_state = AltosLib.ao_flight_invalid;
404 AltosGreatCircle from_receiver = null;
406 if (saved_state != null)
407 prev_state = saved_state.state();
410 set_screen_on(state_age(state));
412 if (state.state() == AltosLib.ao_flight_stateless) {
413 boolean prev_locked = false;
414 boolean locked = false;
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();
423 if (currentTab.equals(tab_pad_name)) mTabHost.setCurrentTabByTag(tab_flight_name);
425 if (currentTab.equals(tab_flight_name)) mTabHost.setCurrentTabByTag(tab_pad_name);
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);
435 case AltosLib.ao_flight_landed:
436 if (currentTab.equals(tab_flight_name)) mTabHost.setCurrentTabByTag(tab_recover_name);
438 case AltosLib.ao_flight_stateless:
439 if (currentTab.equals(tab_pad_name)) mTabHost.setCurrentTabByTag(tab_flight_name);
445 if (location != null && state.gps != null && state.gps.locked) {
447 if (location.hasAltitude())
448 altitude = location.getAltitude();
449 from_receiver = new AltosGreatCircle(location.getLatitude(),
450 location.getLongitude(),
457 if (saved_state == null || !same_string(saved_state.callsign, state.callsign)) {
458 mCallsignView.setText(state.callsign);
460 if (saved_state == null || state.serial != saved_state.serial) {
461 if (state.serial == AltosLib.MISSING)
462 mSerialView.setText("");
464 mSerialView.setText(String.format("%d", state.serial));
466 if (saved_state == null || state.flight != saved_state.flight) {
467 if (state.flight == AltosLib.MISSING)
468 mFlightView.setText("");
470 mFlightView.setText(String.format("%d", state.flight));
472 if (saved_state == null || state.state() != saved_state.state()) {
473 if (state.state() == AltosLib.ao_flight_stateless) {
474 mStateLayout.setVisibility(View.GONE);
476 mStateView.setText(state.state_name());
477 mStateLayout.setVisibility(View.VISIBLE);
480 if (saved_state == null || state.rssi != saved_state.rssi) {
481 if (state.rssi == AltosLib.MISSING)
482 mRSSIView.setText("");
484 mRSSIView.setText(String.format("%d", state.rssi));
488 for (AltosDroidTab mTab : mTabs)
489 mTab.update_ui(telem_state, state, from_receiver, location, mTab == mTabsAdapter.currentItem());
491 if (mAltosVoice != null)
492 mAltosVoice.tell(telem_state, state, from_receiver, location, (AltosDroidTab) mTabsAdapter.currentItem());
497 private void onTimerTick() {
499 mMessenger.send(Message.obtain(null, MSG_UPDATE_AGE));
500 } catch (RemoteException e) {
504 static String pos(double p, String pos, String neg) {
506 if (p == AltosLib.MISSING)
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);
517 static String number(String format, double value) {
518 if (value == AltosLib.MISSING)
520 return String.format(format, value);
523 static String integer(String format, int value) {
524 if (value == AltosLib.MISSING)
526 return String.format(format, value);
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);
537 public void set_map_source(int source) {
538 for (AltosDroidTab mTab : mTabs)
539 mTab.set_map_source(source);
543 public void onCreate(Bundle savedInstanceState) {
544 super.onCreate(savedInstanceState);
545 AltosDebug.init(this);
546 AltosDebug.debug("+++ ON CREATE +++");
548 // Initialise preferences
549 AltosDroidPreferences.init(this);
551 fm = getSupportFragmentManager();
553 // Set up the window layout
554 setContentView(R.layout.altosdroid);
556 // Create the Tabs and ViewPager
557 mTabHost = (TabHost)findViewById(android.R.id.tabhost);
560 mViewPager = (AltosViewPager)findViewById(R.id.pager);
561 mViewPager.setOffscreenPageLimit(4);
563 mTabsAdapter = new TabsAdapter(this, mTabHost, mViewPager);
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);
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 + ")");
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);
587 private void ensureBluetooth() {
588 // Get local Bluetooth adapter
589 mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
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);
598 private boolean check_usb() {
599 UsbDevice device = AltosUsb.find_device(this, AltosLib.product_basestation);
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);
605 if (AltosUsb.request_permission(this, device, pi)) {
608 start_with_usb = true;
612 start_with_usb = false;
617 private void noticeIntent(Intent intent) {
619 /* Ok, this is pretty convenient.
621 * When a USB device is plugged in, and our 'hotplug'
622 * intent registration fires, we get an Intent with
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
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.
637 UsbDevice device = (UsbDevice) intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
638 boolean granted = intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, true);
640 AltosDebug.debug("intent %s device %s granted %s", intent, device, granted);
645 if (device != null) {
646 AltosDebug.debug("intent has usb device " + device.toString());
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...
656 AltosDebug.debug("check for a USB device at startup");
660 AltosDebug.debug("Starting by looking for bluetooth devices");
666 public void onStart() {
668 AltosDebug.debug("++ ON START ++");
672 noticeIntent(getIntent());
674 // Start Telemetry Service
675 String action = start_with_usb ? ACTION_USB : ACTION_BLUETOOTH;
677 startService(new Intent(action, null, AltosDroid.this, TelemetryService.class));
681 if (mAltosVoice == null)
682 mAltosVoice = new AltosVoice(this);
687 public void onNewIntent(Intent intent) {
688 super.onNewIntent(intent);
689 AltosDebug.debug("onNewIntent");
690 noticeIntent(intent);
694 public void onResume() {
696 AltosDebug.debug("+ ON RESUME +");
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);
702 location = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER);
704 if (location != null)
705 AltosDebug.debug("Resume, location is %f,%f\n",
706 location.getLatitude(),
707 location.getLongitude());
709 update_ui(telemetry_state, saved_state);
713 public void onPause() {
715 AltosDebug.debug("- ON PAUSE -");
716 // Stop listening for location updates
717 ((LocationManager) getSystemService(Context.LOCATION_SERVICE)).removeUpdates(this);
721 public void onStop() {
723 AltosDebug.debug("-- ON STOP --");
727 public void onDestroy() {
729 AltosDebug.debug("--- ON DESTROY ---");
732 if (mAltosVoice != null) {
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) {
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
753 AltosDebug.debug("BT enabled");
754 bluetoothEnabled(data);
756 // User did not enable Bluetooth or an error occured
757 AltosDebug.debug("BT not enabled");
760 case REQUEST_MAP_TYPE:
761 if (resultCode == Activity.RESULT_OK)
764 case REQUEST_IDLE_MODE:
765 if (resultCode == Activity.RESULT_OK)
768 case REQUEST_IGNITERS:
773 private void connectUsb(UsbDevice device) {
774 if (mService == null)
775 pending_usb_device = device;
777 // Attempt to connect to the device
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");
787 private void bluetoothEnabled(Intent data) {
789 mService.send(Message.obtain(null, TelemetryService.MSG_BLUETOOTH_ENABLED, null));
790 } catch (RemoteException e) {
791 AltosDebug.debug("send BT enabled message failed");
795 private void connectDevice(Intent data) {
796 // Attempt to connect to the device
798 String address = data.getExtras().getString(DeviceListActivity.EXTRA_DEVICE_ADDRESS);
799 String name = data.getExtras().getString(DeviceListActivity.EXTRA_DEVICE_NAME);
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");
810 private void disconnectDevice() {
812 mService.send(Message.obtain(null, TelemetryService.MSG_DISCONNECT, null));
813 } catch (RemoteException e) {
817 private void set_map_type(Intent data) {
818 int type = data.getIntExtra(MapTypeActivity.EXTRA_MAP_TYPE, -1);
820 AltosDebug.debug("intent set_map_type %d\n", type);
823 for (AltosDroidTab mTab : mTabs)
824 mTab.set_map_type(map_type);
828 private void idle_mode(Intent data) {
829 int type = data.getIntExtra(IdleModeActivity.EXTRA_IDLE_RESULT, -1);
832 AltosDebug.debug("intent idle_mode %d", type);
834 case IdleModeActivity.IDLE_MODE_CONNECT:
835 msg = Message.obtain(null, TelemetryService.MSG_MONITOR_IDLE_START);
838 } catch (RemoteException re) {
841 case IdleModeActivity.IDLE_MODE_DISCONNECT:
842 msg = Message.obtain(null, TelemetryService.MSG_MONITOR_IDLE_STOP);
845 } catch (RemoteException re) {
848 case IdleModeActivity.IDLE_MODE_REBOOT:
849 msg = Message.obtain(null, TelemetryService.MSG_REBOOT);
852 } catch (RemoteException re) {
855 case IdleModeActivity.IDLE_MODE_IGNITERS:
856 Intent serverIntent = new Intent(this, IgniterActivity.class);
857 startActivityForResult(serverIntent, REQUEST_IGNITERS);
863 public boolean onCreateOptionsMenu(Menu menu) {
864 MenuInflater inflater = getMenuInflater();
865 inflater.inflate(R.menu.option_menu, menu);
869 void setFrequency(double freq) {
871 mService.send(Message.obtain(null, TelemetryService.MSG_SETFREQUENCY, freq));
873 } catch (RemoteException e) {
877 void setFrequency(String freq) {
879 setFrequency (AltosParse.parse_double_net(freq.substring(11, 17)));
880 } catch (ParseException e) {
884 void setBaud(int baud) {
886 mService.send(Message.obtain(null, TelemetryService.MSG_SETBAUD, baud));
888 } catch (RemoteException e) {
892 void setBaud(String baud) {
894 int value = Integer.parseInt(baud);
895 int rate = AltosLib.ao_telemetry_rate_38400;
898 rate = AltosLib.ao_telemetry_rate_2400;
901 rate = AltosLib.ao_telemetry_rate_9600;
904 rate = AltosLib.ao_telemetry_rate_38400;
908 } catch (NumberFormatException e) {
912 void select_tracker(int serial) {
915 AltosDebug.debug("select tracker %d\n", serial);
917 if (serial == selected_serial) {
918 AltosDebug.debug("%d already selected\n", serial);
923 for (i = 0; i < serials.length; i++)
924 if (serials[i] == serial)
927 if (i == serials.length) {
928 AltosDebug.debug("attempt to select unknown tracker %d\n", serial);
933 current_serial = selected_serial = serial;
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) {
950 select_tracker(Integer.parseInt(trackers[item]));
953 AlertDialog alert_tracker = builder_tracker.create();
954 alert_tracker.show();
957 void delete_track(int serial) {
959 mService.send(Message.obtain(null, TelemetryService.MSG_DELETE_SERIAL, (Integer) serial));
960 } catch (Exception ex) {
965 public boolean onOptionsItemSelected(MenuItem item) {
966 Intent serverIntent = null;
967 switch (item.getItemId()) {
968 case R.id.connect_scan:
970 // Launch the DeviceListActivity to see devices and do scan
971 serverIntent = new Intent(this, DeviceListActivity.class);
972 startActivityForResult(serverIntent, REQUEST_CONNECT_DEVICE);
974 case R.id.disconnect:
975 /* Disconnect the device
980 AltosDebug.debug("R.id.quit");
984 case R.id.select_freq:
985 // Set the TBT radio frequency
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)"
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]);
1008 AlertDialog alert_freq = builder_freq.create();
1011 case R.id.select_rate:
1012 // Set the TBT baud rate
1014 final String[] rates = {
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]);
1028 AlertDialog alert_rate = builder_rate.create();
1031 case R.id.change_units:
1032 boolean imperial = AltosPreferences.imperial_units();
1033 AltosPreferences.set_imperial_units(!imperial);
1035 case R.id.preload_maps:
1036 serverIntent = new Intent(this, PreloadMapActivity.class);
1037 startActivityForResult(serverIntent, REQUEST_PRELOAD_MAPS);
1040 serverIntent = new Intent(this, MapTypeActivity.class);
1041 startActivityForResult(serverIntent, REQUEST_MAP_TYPE);
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);
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) {
1063 select_tracker(serials[item-1]);
1066 AlertDialog alert_serial = builder_serial.create();
1067 alert_serial.show();
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]);
1084 AlertDialog alert_serial = builder_serial.create();
1085 alert_serial.show();
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);
1098 static String direction(AltosGreatCircle from_receiver,
1099 Location receiver) {
1100 if (from_receiver == null)
1103 if (receiver == null)
1106 if (!receiver.hasBearing())
1109 float bearing = receiver.getBearing();
1110 float heading = (float) from_receiver.bearing - bearing;
1112 while (heading <= -180.0f)
1114 while (heading > 180.0f)
1117 int iheading = (int) (heading + 0.5f);
1119 if (-1 < iheading && iheading < 1)
1121 else if (iheading < -179 || 179 < iheading)
1123 else if (iheading < 0)
1124 return String.format("left %d°", -iheading);
1126 return String.format("right %d°", iheading);
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);
1137 public void onStatusChanged(String provider, int status, Bundle extras) {
1138 AltosDebug.debug("Location status now %d\n", status);
1141 public void onProviderEnabled(String provider) {
1142 AltosDebug.debug("Location provider enabled %s\n", provider);
1145 public void onProviderDisabled(String provider) {
1146 AltosDebug.debug("Location provider disabled %s\n", provider);