altosui: Add config and pyro tabs to graph widget
[fw/altos] / altosdroid / app / src / main / java / org / altusmetrum / AltosDroid / AltosDroid.java
index f2e0acd7d02c0b3b8f6dada5d591cdecc9477f14..6d70872123954b1a88ba3fc3b897529e9b4da9f8 100644 (file)
@@ -20,7 +20,9 @@ package org.altusmetrum.AltosDroid;
 
 import java.lang.ref.WeakReference;
 import java.util.*;
+import java.io.*;
 
+import android.Manifest;
 import android.app.Activity;
 import android.app.PendingIntent;
 import android.bluetooth.BluetoothAdapter;
@@ -35,6 +37,7 @@ import android.os.Handler;
 import android.os.Message;
 import android.os.Messenger;
 import android.os.RemoteException;
+import android.os.Parcelable;
 import androidx.fragment.app.FragmentActivity;
 import androidx.fragment.app.FragmentManager;
 import android.view.*;
@@ -44,8 +47,9 @@ import android.location.Location;
 import android.location.LocationManager;
 import android.location.LocationListener;
 import android.hardware.usb.*;
-
-import org.altusmetrum.altoslib_13.*;
+import android.content.pm.PackageManager;
+import androidx.core.app.ActivityCompat;
+import org.altusmetrum.altoslib_14.*;
 
 class SavedState {
        long    received_time;
@@ -70,76 +74,7 @@ class SavedState {
        }
 }
 
-class Tracker implements CharSequence, Comparable {
-       int     serial;
-       String  call;
-       double  frequency;
-
-       String  display;
-
-       public Tracker(int serial, String call, double frequency) {
-               if (call == null)
-                       call = "none";
-
-               this.serial = serial;
-               this.call = call;
-               this.frequency = frequency;
-               if (frequency == 0.0)
-                       display = "Auto";
-               else if (frequency == AltosLib.MISSING) {
-                       display = String.format("%-8.8s  %6d", call, serial);
-               } else {
-                       display = String.format("%-8.8s %7.3f %6d", call, frequency, serial);
-               }
-       }
-
-       public Tracker(AltosState s) {
-               this(s == null ? 0 : s.cal_data().serial,
-                    s == null ? null : s.cal_data().callsign,
-                    s == null ? 0.0 : s.frequency);
-       }
-
-       /* CharSequence */
-       public char charAt(int index) {
-               return display.charAt(index);
-       }
-
-       public int length() {
-               return display.length();
-       }
-
-       public CharSequence subSequence(int start, int end) throws IndexOutOfBoundsException {
-               return display.subSequence(start, end);
-       }
-
-       public String toString() {
-               return display.toString();
-       }
-
-       /* Comparable */
-       public int compareTo (Object other) {
-               Tracker o = (Tracker) other;
-               if (frequency == 0.0) {
-                       if (o.frequency == 0.0)
-                               return 0;
-                       return -1;
-               }
-               if (o.frequency == 0.0)
-                       return 1;
-
-               int     a = serial - o.serial;
-               int     b = call.compareTo(o.call);
-               int     c = (int) Math.signum(frequency - o.frequency);
-
-               if (b != 0)
-                       return b;
-               if (c != 0)
-                       return c;
-               return a;
-       }
-}
-
-public class AltosDroid extends FragmentActivity implements AltosUnitsListener, LocationListener {
+public class AltosDroid extends FragmentActivity implements AltosUnitsListener, LocationListener, ActivityCompat.OnRequestPermissionsResultCallback {
 
        // Actions sent to the telemetry server at startup time
 
@@ -152,6 +87,7 @@ public class AltosDroid extends FragmentActivity implements AltosUnitsListener,
        public static final int MSG_UPDATE_AGE      = 2;
        public static final int MSG_IDLE_MODE       = 3;
        public static final int MSG_IGNITER_STATUS  = 4;
+       public static final int MSG_FILE_FAILED     = 5;
 
        // Intent request codes
        public static final int REQUEST_CONNECT_DEVICE = 1;
@@ -160,16 +96,22 @@ public class AltosDroid extends FragmentActivity implements AltosUnitsListener,
        public static final int REQUEST_IDLE_MODE      = 5;
        public static final int REQUEST_IGNITERS       = 6;
        public static final int REQUEST_SETUP          = 7;
+       public static final int REQUEST_SELECT_TRACKER = 8;
+       public static final int REQUEST_DELETE_TRACKER = 9;
 
        public static final String EXTRA_IDLE_MODE = "idle_mode";
        public static final String EXTRA_IDLE_RESULT = "idle_result";
+       public static final String EXTRA_FREQUENCY = "frequency";
        public static final String EXTRA_TELEMETRY_SERVICE = "telemetry_service";
+       public static final String EXTRA_TRACKERS = "trackers";
+       public static final String EXTRA_TRACKERS_TITLE = "trackers_title";
 
        // Setup result bits
        public static final int SETUP_BAUD = 1;
        public static final int SETUP_UNITS = 2;
        public static final int SETUP_MAP_SOURCE = 4;
        public static final int SETUP_MAP_TYPE = 8;
+       public static final int SETUP_FONT_SIZE = 16;
 
        public static FragmentManager   fm;
 
@@ -215,7 +157,6 @@ public class AltosDroid extends FragmentActivity implements AltosUnitsListener,
        TelemetryState  telemetry_state;
        Tracker[]       trackers;
 
-
        UsbDevice       pending_usb_device;
        boolean         start_with_usb;
 
@@ -251,19 +192,23 @@ public class AltosDroid extends FragmentActivity implements AltosUnitsListener,
                                ad.idle_mode = (Boolean) msg.obj;
                                ad.update_state(null);
                                break;
+                       case MSG_FILE_FAILED:
+                               ad.file_failed((File) msg.obj);
+                               break;
                        }
                }
        };
 
-
        private ServiceConnection mConnection = new ServiceConnection() {
                public void onServiceConnected(ComponentName className, IBinder service) {
+                       AltosDebug.debug("onServiceConnected\n");
                        mService = new Messenger(service);
                        try {
                                Message msg = Message.obtain(null, TelemetryService.MSG_REGISTER_CLIENT);
                                msg.replyTo = mMessenger;
                                mService.send(msg);
                        } catch (RemoteException e) {
+                               AltosDebug.debug("attempt to register telemetry service client failed\n");
                                // In this case the service has crashed before we could even do anything with it
                        }
                        if (pending_usb_device != null) {
@@ -276,17 +221,20 @@ public class AltosDroid extends FragmentActivity implements AltosUnitsListener,
                }
 
                public void onServiceDisconnected(ComponentName className) {
+                       AltosDebug.debug("onServiceDisconnected\n");
                        // This is called when the connection with the service has been unexpectedly disconnected - process crashed.
                        mService = null;
                }
        };
 
        void doBindService() {
+               AltosDebug.debug("doBindService\n");
                bindService(new Intent(this, TelemetryService.class), mConnection, Context.BIND_AUTO_CREATE);
                mIsBound = true;
        }
 
        void doUnbindService() {
+               AltosDebug.debug("doUnbindService\n");
                if (mIsBound) {
                        // If we have received the service, and hence registered with it, then now is the time to unregister.
                        if (mService != null) {
@@ -304,6 +252,13 @@ public class AltosDroid extends FragmentActivity implements AltosUnitsListener,
                }
        }
 
+       public AltosDroidTab findTab(String name) {
+               for (AltosDroidTab mTab : mTabs)
+                       if (name.equals(mTab.tab_name()))
+                               return mTab;
+               return null;
+       }
+
        public void registerTab(AltosDroidTab mTab) {
                mTabs.add(mTab);
        }
@@ -322,7 +277,7 @@ public class AltosDroid extends FragmentActivity implements AltosUnitsListener,
                case TelemetryState.CONNECT_CONNECTED:
                        if (telemetry_state.config != null) {
                                String str = String.format("S/N %d %6.3f MHz%s", telemetry_state.config.serial,
-                                                          telemetry_state.frequency, idle_mode ? " (idle)" : "");
+                                                          telemetry_state.frequency, telemetry_state.idle_mode ? " (idle)" : "");
                                if (telemetry_state.telemetry_rate != AltosLib.ao_telemetry_rate_38400)
                                        str = str.concat(String.format(" %d bps",
                                                                       AltosLib.ao_telemetry_rate_values[telemetry_state.telemetry_rate]));
@@ -360,7 +315,6 @@ public class AltosDroid extends FragmentActivity implements AltosUnitsListener,
        }
 
        int     selected_serial = 0;
-       int     current_serial;
        long    switch_time;
 
        void set_switch_time() {
@@ -375,11 +329,29 @@ public class AltosDroid extends FragmentActivity implements AltosUnitsListener,
                if (new_telemetry_state != null)
                        telemetry_state = new_telemetry_state;
 
-               if (selected_serial != 0)
-                       current_serial = selected_serial;
+               if (selected_frequency != AltosLib.MISSING) {
+                       AltosState selected_state = telemetry_state.get(selected_serial);
+                       AltosState latest_state = telemetry_state.get(telemetry_state.latest_serial);
+
+                       if (selected_state != null && selected_state.frequency == selected_frequency) {
+                               selected_frequency = AltosLib.MISSING;
+                       } else if ((selected_state == null || selected_state.frequency != selected_frequency) &&
+                                  (latest_state != null && latest_state.frequency == selected_frequency))
+                       {
+                               selected_frequency = AltosLib.MISSING;
+                               selected_serial = telemetry_state.latest_serial;
+                       }
+               }
 
-               if (current_serial == 0)
-                       current_serial = telemetry_state.latest_serial;
+               if (!telemetry_state.containsKey(selected_serial)) {
+                       selected_serial = telemetry_state.latest_serial;
+                       AltosDebug.debug("selected serial set to %d", selected_serial);
+               }
+
+               int shown_serial = selected_serial;
+
+               if (telemetry_state.idle_mode)
+                       shown_serial = telemetry_state.latest_serial;
 
                if (!registered_units_listener) {
                        registered_units_listener = true;
@@ -387,7 +359,8 @@ public class AltosDroid extends FragmentActivity implements AltosUnitsListener,
                }
 
                int     num_trackers = 0;
-               for (AltosState s : telemetry_state.states.values()) {
+
+               for (AltosState s : telemetry_state.values()) {
                        num_trackers++;
                }
 
@@ -396,44 +369,17 @@ public class AltosDroid extends FragmentActivity implements AltosUnitsListener,
                int n = 0;
                trackers[n++] = new Tracker(0, "auto", 0.0);
 
-               for (AltosState s : telemetry_state.states.values())
+               for (AltosState s : telemetry_state.values())
                        trackers[n++] = new Tracker(s);
 
                Arrays.sort(trackers);
 
-               update_title(telemetry_state);
-
-               AltosState      state = null;
-               boolean         aged = true;
-
-               if (telemetry_state.states.containsKey(current_serial)) {
-                       state = telemetry_state.states.get(current_serial);
-                       int age = state_age(state.received_time);
-                       if (age < 20)
-                               aged = false;
-                       if (current_serial == selected_serial)
-                               aged = false;
-                       else if (switch_time != 0 && (switch_time - state.received_time) > 0)
-                               aged = true;
-               }
-
-               if (aged) {
-                       AltosState      newest_state = null;
-                       int             newest_age = 0;
-
-                       for (int serial : telemetry_state.states.keySet()) {
-                               AltosState      existing = telemetry_state.states.get(serial);
-                               int             existing_age = state_age(existing.received_time);
+               if (telemetry_state.frequency != AltosLib.MISSING)
+                       telem_frequency = telemetry_state.frequency;
 
-                               if (newest_state == null || existing_age < newest_age) {
-                                       newest_state = existing;
-                                       newest_age = existing_age;
-                               }
-                       }
+               update_title(telemetry_state);
 
-                       if (newest_state != null)
-                               state = newest_state;
-               }
+               AltosState      state = telemetry_state.get(shown_serial);
 
                update_ui(telemetry_state, state, telemetry_state.quiet);
 
@@ -474,6 +420,19 @@ public class AltosDroid extends FragmentActivity implements AltosUnitsListener,
                        getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
        }
 
+       static String age_string(int age) {
+               String  text;
+               if (age < 60)
+                       text = String.format("%ds", age);
+               else if (age < 60 * 60)
+                       text = String.format("%dm", age / 60);
+               else if (age < 60 * 60 * 24)
+                       text = String.format("%dh", age / (60 * 60));
+               else
+                       text = String.format("%dd", age / (24 * 60 * 60));
+               return text;
+       }
+
        void update_age() {
                if (saved_state != null) {
                        int age = state_age(saved_state.received_time);
@@ -487,21 +446,18 @@ public class AltosDroid extends FragmentActivity implements AltosUnitsListener,
 
                        set_screen_on(age);
 
-                       String  text;
-                       if (age < 60)
-                               text = String.format("%ds", age);
-                       else if (age < 60 * 60)
-                               text = String.format("%dm", age / 60);
-                       else if (age < 60 * 60 * 24)
-                               text = String.format("%dh", age / (60 * 60));
-                       else
-                               text = String.format("%dd", age / (24 * 60 * 60));
-                       mAgeView.setText(text);
+                       mAgeView.setText(age_string(age));
                }
        }
 
        void update_ui(TelemetryState telem_state, AltosState state, boolean quiet) {
 
+               AltosDebug.debug("update_ui telem %b state %b quiet %b saved_state %b\n",
+                                telem_state != null,
+                                state != null,
+                                quiet,
+                                saved_state != null);
+
                this.state = state;
 
                int prev_state = AltosLib.ao_flight_invalid;
@@ -591,11 +547,13 @@ public class AltosDroid extends FragmentActivity implements AltosUnitsListener,
                        saved_state = new SavedState(state);
                }
 
-               for (AltosDroidTab mTab : mTabs)
+               for (AltosDroidTab mTab : mTabs) {
+                       AltosDebug.debug("mTab %s current %s\n",
+                                        mTab, mTabsAdapter.currentItem());
                        mTab.update_ui(telem_state, state, from_receiver, location, mTab == mTabsAdapter.currentItem());
+               }
 
-               AltosDebug.debug("quiet %b\n", quiet);
-               if (mAltosVoice != null)
+               if (mAltosVoice != null && mTabsAdapter.currentItem() != null)
                        mAltosVoice.tell(telem_state, state, from_receiver, location, (AltosDroidTab) mTabsAdapter.currentItem(), quiet);
 
        }
@@ -617,7 +575,7 @@ public class AltosDroid extends FragmentActivity implements AltosUnitsListener,
                }
                int deg = (int) Math.floor(p);
                double min = (p - Math.floor(p)) * 60.0;
-               return String.format("%d°%9.4f\" %s", deg, min, h);
+               return String.format("%d° %7.4f\" %s", deg, min, h);
        }
 
        static String number(String format, double value) {
@@ -640,14 +598,29 @@ public class AltosDroid extends FragmentActivity implements AltosUnitsListener,
                return tab_view;
        }
 
+       static public int[] themes = {
+               R.style.Small,
+               R.style.Medium,
+               R.style.Large,
+               R.style.Extra
+       };
+
+       static public int[] dialog_themes = {
+               R.style.Small_Dialog,
+               R.style.Medium_Dialog,
+               R.style.Large_Dialog,
+               R.style.Extra_Dialog
+       };
+
        @Override
        public void onCreate(Bundle savedInstanceState) {
-               super.onCreate(savedInstanceState);
                AltosDebug.init(this);
                AltosDebug.debug("+++ ON CREATE +++");
 
                // Initialise preferences
                AltosDroidPreferences.init(this);
+               setTheme(themes[AltosDroidPreferences.font_size()]);
+               super.onCreate(savedInstanceState);
 
                fm = getSupportFragmentManager();
 
@@ -663,10 +636,10 @@ public class AltosDroid extends FragmentActivity implements AltosUnitsListener,
 
                mTabsAdapter = new TabsAdapter(this, mTabHost, mViewPager);
 
-               mTabsAdapter.addTab(mTabHost.newTabSpec(tab_pad_name).setIndicator(create_tab_view("Pad")), TabPad.class, null);
-               mTabsAdapter.addTab(mTabHost.newTabSpec(tab_flight_name).setIndicator(create_tab_view("Flight")), TabFlight.class, null);
-               mTabsAdapter.addTab(mTabHost.newTabSpec(tab_recover_name).setIndicator(create_tab_view("Recover")), TabRecover.class, null);
-               mTabsAdapter.addTab(mTabHost.newTabSpec(tab_map_name).setIndicator(create_tab_view("Map")), TabMap.class, null);
+               mTabsAdapter.addTab(mTabHost.newTabSpec(tab_pad_name).setIndicator(create_tab_view("Pad")), TabPad.class, null, findTab(tab_pad_name));
+               mTabsAdapter.addTab(mTabHost.newTabSpec(tab_flight_name).setIndicator(create_tab_view("Flight")), TabFlight.class, null, findTab(tab_flight_name));
+               mTabsAdapter.addTab(mTabHost.newTabSpec(tab_recover_name).setIndicator(create_tab_view("Recover")), TabRecover.class, null, findTab(tab_recover_name));
+               mTabsAdapter.addTab(mTabHost.newTabSpec(tab_map_name).setIndicator(create_tab_view("Map")), TabMap.class, null, findTab(tab_map_name));
 
                // Display the Version
                mVersion = (TextView) findViewById(R.id.version);
@@ -701,7 +674,13 @@ public class AltosDroid extends FragmentActivity implements AltosUnitsListener,
 
                if (device != null) {
                        Intent          i = new Intent(this, AltosDroid.class);
-                       PendingIntent   pi = PendingIntent.getActivity(this, 0, new Intent("hello world", null, this, AltosDroid.class), 0);
+                       int             flag;
+
+                       if (android.os.Build.VERSION.SDK_INT >= 31) // android.os.Build.VERSION_CODES.S
+                               flag = 33554432; // PendingIntent.FLAG_MUTABLE
+                       else
+                               flag = 0;
+                       PendingIntent   pi = PendingIntent.getActivity(this, 0, new Intent("hello world", null, this, AltosDroid.class), flag);
 
                        if (AltosUsb.request_permission(this, device, pi)) {
                                connectUsb(device);
@@ -787,48 +766,179 @@ public class AltosDroid extends FragmentActivity implements AltosUnitsListener,
        @Override
        public void onNewIntent(Intent intent) {
                super.onNewIntent(intent);
-               AltosDebug.debug("onNewIntent");
+               AltosDebug.debug("+ ON NEW INTENT +");
                noticeIntent(intent);
        }
 
-       @Override
-       public void onResume() {
-               super.onResume();
-               AltosDebug.debug("+ ON RESUME +");
-
+       private void enable_location_updates(boolean do_update) {
                // Listen for GPS and Network position updates
                LocationManager locationManager = (LocationManager) this.getSystemService(Context.LOCATION_SERVICE);
-               locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 1000, 1, this);
 
-               location = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER);
+               if (locationManager != null)
+               {
+                       try {
+                               locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 1000, 1, this);
+                               location = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER);
+                       } catch (Exception e) {
+                               locationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 1000, 1, this);
+                               location = locationManager.getLastKnownLocation(LocationManager.NETWORK_PROVIDER);
+                       }
+
+                       if (location != null)
+                               AltosDebug.debug("Resume, location is %f,%f\n",
+                                                location.getLatitude(),
+                                                location.getLongitude());
+                       AltosDebug.debug("Failed to get GPS updates\n");
+               }
+
+               if (do_update)
+                       update_ui(telemetry_state, state, true);
+       }
+
+       static final int MY_PERMISSION_REQUEST = 1001;
+
+       public boolean have_location_permission = false;
+       public boolean have_storage_permission = false;
+       public boolean have_bluetooth_permission = false;
+       public boolean have_bluetooth_connect_permission = false;
+       public boolean have_bluetooth_scan_permission = false;
+       public boolean asked_permission = false;
+
+       static final String BLUETOOTH_CONNECT = "android.permission.BLUETOOTH_CONNECT";
+       static final String BLUETOOTH_SCAN = "android.permission.BLUETOOTH_SCAN";
+
+       AltosMapOnline map_online;
+
+       void
+       tell_map_permission(AltosMapOnline map_online) {
+               this.map_online = map_online;
+       }
+
+       @Override
+       public void onRequestPermissionsResult(int requestCode, String[] permissions,
+                                              int[] grantResults) {
+               if (requestCode == MY_PERMISSION_REQUEST) {
+                       for (int i = 0; i < grantResults.length; i++) {
+                               if (grantResults[i] == PackageManager.PERMISSION_GRANTED) {
+                                       if (permissions[i].equals(Manifest.permission.ACCESS_FINE_LOCATION)) {
+                                               have_location_permission = true;
+                                               enable_location_updates(true);
+                                               if (map_online != null)
+                                                       map_online.position_permission();
+                                       }
+                                       if (permissions[i].equals(Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
+                                               have_storage_permission = true;
+                                       }
+                                       if (permissions[i].equals(Manifest.permission.BLUETOOTH)) {
+                                               have_bluetooth_permission = true;
+                                       }
+                                       if (permissions[i].equals(BLUETOOTH_CONNECT)) {
+                                               have_bluetooth_connect_permission = true;
+                                       }
+                                       if (permissions[i].equals(BLUETOOTH_SCAN)) {
+                                               have_bluetooth_scan_permission = true;
+                                       }
+                               }
+                       }
+               }
+       }
 
-               if (location != null)
-                       AltosDebug.debug("Resume, location is %f,%f\n",
-                                        location.getLatitude(),
-                                        location.getLongitude());
+       @Override
+       public void onResume() {
+               AltosDebug.debug("+ ON RESUME +");
+
+               super.onResume();
 
-               update_ui(telemetry_state, state, true);
+               if (!asked_permission) {
+                       asked_permission = true;
+                       if (ActivityCompat.checkSelfPermission(this,
+                                                             Manifest.permission.ACCESS_FINE_LOCATION)
+                           == PackageManager.PERMISSION_GRANTED)
+                       {
+                               have_location_permission = true;
+                       }
+                       if (ActivityCompat.checkSelfPermission(this,
+                                                              Manifest.permission.WRITE_EXTERNAL_STORAGE)
+                           == PackageManager.PERMISSION_GRANTED)
+                       {
+                               have_storage_permission = true;
+                       }
+                       if (ActivityCompat.checkSelfPermission(this,
+                                                              Manifest.permission.BLUETOOTH)
+                           == PackageManager.PERMISSION_GRANTED)
+                       {
+                               have_bluetooth_permission = true;
+                       }
+                       if (ActivityCompat.checkSelfPermission(this,
+                                                              BLUETOOTH_CONNECT)
+                           == PackageManager.PERMISSION_GRANTED)
+                       {
+                               have_bluetooth_connect_permission = true;
+                       }
+                       if (ActivityCompat.checkSelfPermission(this,
+                                                              BLUETOOTH_SCAN)
+                           == PackageManager.PERMISSION_GRANTED)
+                       {
+                               have_bluetooth_scan_permission = true;
+                       }
+                       int count = 0;
+                       if (!have_location_permission)
+                               count += 1;
+                       if (!have_storage_permission)
+                               count += 1;
+                       if (!have_bluetooth_permission)
+                               count += 1;
+                       if (!have_bluetooth_connect_permission)
+                               count += 1;
+                       if (!have_bluetooth_scan_permission)
+                               count += 1;
+                       if (count > 0)
+                       {
+                               String[] permissions = new String[count];
+                               int i = 0;
+                               if (!have_location_permission)
+                                       permissions[i++] = Manifest.permission.ACCESS_FINE_LOCATION;
+                               if (!have_storage_permission)
+                                       permissions[i++] = Manifest.permission.WRITE_EXTERNAL_STORAGE;
+                               if (!have_bluetooth_permission)
+                                       permissions[i++] = Manifest.permission.BLUETOOTH;
+                               if (!have_bluetooth_connect_permission)
+                                       permissions[i++] = BLUETOOTH_CONNECT;
+                               if (!have_bluetooth_scan_permission)
+                                       permissions[i++] = BLUETOOTH_SCAN;
+                               ActivityCompat.requestPermissions(this, permissions, MY_PERMISSION_REQUEST);
+                       }
+               }
+               if (have_location_permission)
+                       enable_location_updates(false);
        }
 
        @Override
        public void onPause() {
-               super.onPause();
                AltosDebug.debug("- ON PAUSE -");
+
+               super.onPause();
+
                // Stop listening for location updates
-               ((LocationManager) getSystemService(Context.LOCATION_SERVICE)).removeUpdates(this);
+               if (have_location_permission)
+                       ((LocationManager) getSystemService(Context.LOCATION_SERVICE)).removeUpdates(this);
        }
 
        @Override
        public void onStop() {
-               super.onStop();
                AltosDebug.debug("-- ON STOP --");
+
+               super.onStop();
        }
 
        @Override
        public void onDestroy() {
-               super.onDestroy();
                AltosDebug.debug("--- ON DESTROY ---");
 
+               super.onDestroy();
+
+               saved_state = null;
+
                doUnbindService();
                if (mAltosVoice != null) {
                        mAltosVoice.stop();
@@ -838,7 +948,7 @@ public class AltosDroid extends FragmentActivity implements AltosUnitsListener,
        }
 
        protected void onActivityResult(int requestCode, int resultCode, Intent data) {
-               AltosDebug.debug("onActivityResult " + resultCode);
+               AltosDebug.debug("onActivityResult request %d result %d", requestCode, resultCode);
                switch (requestCode) {
                case REQUEST_CONNECT_DEVICE:
                        // When DeviceListActivity returns with a device to connect to
@@ -868,12 +978,22 @@ public class AltosDroid extends FragmentActivity implements AltosUnitsListener,
                        if (resultCode == Activity.RESULT_OK)
                                note_setup_changes(data);
                        break;
+               case REQUEST_SELECT_TRACKER:
+                       if (resultCode == Activity.RESULT_OK)
+                               select_tracker(data);
+                       break;
+               case REQUEST_DELETE_TRACKER:
+                       if (resultCode == Activity.RESULT_OK)
+                               delete_track(data);
+                       break;
                }
        }
 
        private void note_setup_changes(Intent data) {
                int changes = data.getIntExtra(SetupActivity.EXTRA_SETUP_CHANGES, 0);
 
+               AltosDebug.debug("note_setup_changes changes %d\n", changes);
+
                if ((changes & SETUP_BAUD) != 0) {
                        try {
                                mService.send(Message.obtain(null, TelemetryService.MSG_SETBAUD,
@@ -891,6 +1011,11 @@ public class AltosDroid extends FragmentActivity implements AltosUnitsListener,
                        /* nothing to do here */
                }
                set_switch_time();
+               if ((changes & SETUP_FONT_SIZE) != 0) {
+                       AltosDebug.debug(" ==== Recreate to switch font sizes ==== ");
+                       finish();
+                       startActivity(getIntent());
+               }
        }
 
        private void connectUsb(UsbDevice device) {
@@ -908,10 +1033,12 @@ public class AltosDroid extends FragmentActivity implements AltosUnitsListener,
        }
 
        private void bluetoothEnabled(Intent data) {
-               try {
-                       mService.send(Message.obtain(null, TelemetryService.MSG_BLUETOOTH_ENABLED, null));
-               } catch (RemoteException e) {
-                       AltosDebug.debug("send BT enabled message failed");
+               if (mService != null) {
+                       try {
+                               mService.send(Message.obtain(null, TelemetryService.MSG_BLUETOOTH_ENABLED, null));
+                       } catch (RemoteException e) {
+                               AltosDebug.debug("send BT enabled message failed");
+                       }
                }
        }
 
@@ -971,6 +1098,24 @@ public class AltosDroid extends FragmentActivity implements AltosUnitsListener,
                }
        }
 
+       boolean fail_shown;
+
+       private void file_failed(File file) {
+               if (!fail_shown) {
+                       fail_shown = true;
+                       AlertDialog fail = new AlertDialog.Builder(this).create();
+                       fail.setTitle("Failed to Create Log File");
+                       fail.setMessage(file.getPath());
+                       fail.setButton(AlertDialog.BUTTON_NEUTRAL, "OK",
+                                      new DialogInterface.OnClickListener() {
+                                              public void onClick(DialogInterface dialog, int which) {
+                                                      dialog.dismiss();
+                                              }
+                                      });
+                       fail.show();
+               }
+       }
+
        @Override
        public boolean onCreateOptionsMenu(Menu menu) {
                MenuInflater inflater = getMenuInflater();
@@ -978,7 +1123,12 @@ public class AltosDroid extends FragmentActivity implements AltosUnitsListener,
                return true;
        }
 
+       double telem_frequency = 434.550;
+       double selected_frequency = AltosLib.MISSING;
+
        void setFrequency(double freq) {
+               telem_frequency = freq;
+               selected_frequency = AltosLib.MISSING;
                try {
                        mService.send(Message.obtain(null, TelemetryService.MSG_SETFREQUENCY, freq));
                        set_switch_time();
@@ -1018,10 +1168,9 @@ public class AltosDroid extends FragmentActivity implements AltosUnitsListener,
                }
        }
 
-       void select_tracker(int serial) {
-               int i;
+       void select_tracker(int serial, double frequency) {
 
-               AltosDebug.debug("select tracker %d\n", serial);
+               AltosDebug.debug("select tracker %d %7.3f\n", serial, frequency);
 
                if (serial == selected_serial) {
                        AltosDebug.debug("%d already selected\n", serial);
@@ -1029,6 +1178,7 @@ public class AltosDroid extends FragmentActivity implements AltosUnitsListener,
                }
 
                if (serial != 0) {
+                       int i;
                        for (i = 0; i < trackers.length; i++)
                                if (trackers[i].serial == serial)
                                        break;
@@ -1037,35 +1187,18 @@ public class AltosDroid extends FragmentActivity implements AltosUnitsListener,
                                AltosDebug.debug("attempt to select unknown tracker %d\n", serial);
                                return;
                        }
+                       if (frequency != 0.0 && frequency != AltosLib.MISSING)
+                               setFrequency(frequency);
                }
 
-               current_serial = selected_serial = serial;
+               selected_serial = serial;
                update_state(null);
        }
 
-       void touch_trackers(Integer[] serials) {
-               AlertDialog.Builder builder_tracker = new AlertDialog.Builder(this);
-               builder_tracker.setTitle("Select Tracker");
-
-               final Tracker[] my_trackers = new Tracker[serials.length + 1];
-
-               my_trackers[0] = new Tracker(null);
-
-               for (int i = 0; i < serials.length; i++) {
-                       AltosState      s = telemetry_state.states.get(serials[i]);
-                       my_trackers[i+1] = new Tracker(s);
-               }
-               builder_tracker.setItems(my_trackers,
-                                        new DialogInterface.OnClickListener() {
-                                                public void onClick(DialogInterface dialog, int item) {
-                                                        if (item == 0)
-                                                                select_tracker(0);
-                                                        else
-                                                                select_tracker(my_trackers[item].serial);
-                                                }
-                                        });
-               AlertDialog alert_tracker = builder_tracker.create();
-               alert_tracker.show();
+       void select_tracker(Intent data) {
+               int serial = data.getIntExtra(SelectTrackerActivity.EXTRA_SERIAL_NUMBER, 0);
+               double frequency = data.getDoubleExtra(SelectTrackerActivity.EXTRA_FREQUENCY, 0.0);
+               select_tracker(serial, frequency);
        }
 
        void delete_track(int serial) {
@@ -1075,6 +1208,39 @@ public class AltosDroid extends FragmentActivity implements AltosUnitsListener,
                }
        }
 
+       void delete_track(Intent data) {
+               int serial = data.getIntExtra(SelectTrackerActivity.EXTRA_SERIAL_NUMBER, 0);
+               if (serial != 0)
+                       delete_track(serial);
+       }
+
+       void start_select_tracker(Tracker[] select_trackers, int title_id, int request) {
+               Intent intent = new Intent(this, SelectTrackerActivity.class);
+               AltosDebug.debug("put title id 0x%x %s", title_id, getResources().getString(title_id));
+               intent.putExtra(EXTRA_TRACKERS_TITLE, title_id);
+               if (select_trackers != null) {
+                       ArrayList<Tracker> tracker_array = new ArrayList<Tracker>(Arrays.asList(select_trackers));
+                       intent.putParcelableArrayListExtra(EXTRA_TRACKERS, tracker_array);
+               } else {
+                       intent.putExtra(EXTRA_TRACKERS, (Parcelable[]) null);
+               }
+               startActivityForResult(intent, request);
+       }
+
+       void start_select_tracker(Tracker[] select_trackers) {
+               start_select_tracker(select_trackers, R.string.select_tracker, REQUEST_SELECT_TRACKER);
+       }
+
+       void touch_trackers(Integer[] serials) {
+               Tracker[] my_trackers = new Tracker[serials.length];
+
+               for (int i = 0; i < serials.length; i++) {
+                       AltosState      s = telemetry_state.get(serials[i]);
+                       my_trackers[i] = new Tracker(s);
+               }
+               start_select_tracker(my_trackers);
+       }
+
        @Override
        public boolean onOptionsItemSelected(MenuItem item) {
                Intent serverIntent = null;
@@ -1108,56 +1274,28 @@ public class AltosDroid extends FragmentActivity implements AltosUnitsListener,
                                frequency_strings[i] = frequencies[i].toString();
 
                        AlertDialog.Builder builder_freq = new AlertDialog.Builder(this);
-                       builder_freq.setTitle("Pick a frequency");
+                       builder_freq.setTitle("Select Frequency");
                        builder_freq.setItems(frequency_strings,
                                         new DialogInterface.OnClickListener() {
                                                 public void onClick(DialogInterface dialog, int item) {
                                                         setFrequency(frequencies[item]);
+                                                        selected_frequency = frequencies[item].frequency;
                                                 }
                                         });
                        AlertDialog alert_freq = builder_freq.create();
                        alert_freq.show();
                        return true;
                case R.id.select_tracker:
-                       if (trackers != null) {
-                               AlertDialog.Builder builder_serial = new AlertDialog.Builder(this);
-                               builder_serial.setTitle("Select a tracker");
-                               builder_serial.setItems(trackers,
-                                                       new DialogInterface.OnClickListener() {
-                                                               public void onClick(DialogInterface dialog, int item) {
-                                                                       System.out.printf("select item %d %s\n", item, trackers[item].display);
-                                                                       if (item == 0)
-                                                                               select_tracker(0);
-                                                                       else
-                                                                               select_tracker(trackers[item].serial);
-                                                               }
-                                                       });
-                               AlertDialog alert_serial = builder_serial.create();
-                               alert_serial.show();
-
-                       }
+                       start_select_tracker(trackers);
                        return true;
                case R.id.delete_track:
-                       if (trackers != null) {
-                               AlertDialog.Builder builder_serial = new AlertDialog.Builder(this);
-                               builder_serial.setTitle("Delete a track");
-                               final Tracker[] my_trackers = new Tracker[trackers.length - 1];
-                               for (int i = 0; i < trackers.length - 1; i++)
-                                       my_trackers[i] = trackers[i+1];
-                               builder_serial.setItems(my_trackers,
-                                                       new DialogInterface.OnClickListener() {
-                                                               public void onClick(DialogInterface dialog, int item) {
-                                                                       delete_track(my_trackers[item].serial);
-                                                               }
-                                                       });
-                               AlertDialog alert_serial = builder_serial.create();
-                               alert_serial.show();
-
-                       }
+                       if (trackers != null && trackers.length > 0)
+                               start_select_tracker(trackers, R.string.delete_track, REQUEST_DELETE_TRACKER);
                        return true;
                case R.id.idle_mode:
                        serverIntent = new Intent(this, IdleModeActivity.class);
                        serverIntent.putExtra(EXTRA_IDLE_MODE, idle_mode);
+                       serverIntent.putExtra(EXTRA_FREQUENCY, telem_frequency);
                        startActivityForResult(serverIntent, REQUEST_IDLE_MODE);
                        return true;
                }