altosdroid: Change tracker selection dialog
authorKeith Packard <keithp@keithp.com>
Sun, 9 Feb 2020 04:44:10 +0000 (20:44 -0800)
committerKeith Packard <keithp@keithp.com>
Sun, 9 Feb 2020 06:52:23 +0000 (22:52 -0800)
Create a table of trackers and allow sorting based on each column.
When a tracker is selected, the app will not change to another tracker
automatically.

Signed-off-by: Keith Packard <keithp@keithp.com>
16 files changed:
altosdroid/app/src/main/AndroidManifest.xml.in
altosdroid/app/src/main/java/org/altusmetrum/AltosDroid/AltosDroid.java
altosdroid/app/src/main/java/org/altusmetrum/AltosDroid/AltosDroidPreferences.java
altosdroid/app/src/main/java/org/altusmetrum/AltosDroid/AltosMapOffline.java
altosdroid/app/src/main/java/org/altusmetrum/AltosDroid/AltosMapOnline.java
altosdroid/app/src/main/java/org/altusmetrum/AltosDroid/IdleModeActivity.java
altosdroid/app/src/main/java/org/altusmetrum/AltosDroid/SelectTrackerActivity.java [new file with mode: 0644]
altosdroid/app/src/main/java/org/altusmetrum/AltosDroid/TelemetryService.java
altosdroid/app/src/main/java/org/altusmetrum/AltosDroid/TelemetryState.java
altosdroid/app/src/main/java/org/altusmetrum/AltosDroid/Tracker.java [new file with mode: 0644]
altosdroid/app/src/main/res/layout/device_name.xml
altosdroid/app/src/main/res/layout/idle_mode.xml
altosdroid/app/src/main/res/layout/tracker_ent.xml [new file with mode: 0644]
altosdroid/app/src/main/res/layout/tracker_list.xml [new file with mode: 0644]
altosdroid/app/src/main/res/values/strings.xml
altoslib/AltosIdleMonitor.java

index d237af4d3edbe62be52b44196f76ca1e032f9c56..efedddaf5555b4526bab4e615143a7b367579a13 100644 (file)
                   android:theme="@android:style/Theme.Dialog"
                   android:configChanges="orientation|keyboardHidden" />
 
+        <activity android:name=".SelectTrackerActivity"
+                  android:label="@string/select_tracker"
+                  android:theme="@android:style/Theme.Dialog"
+                  android:configChanges="orientation|keyboardHidden" />
+
         <activity android:name=".PreloadMapActivity"
                   android:label="@string/preload_maps"
                   android:theme="@android:style/Theme.Dialog"
index 46709f0f7f3c9bff417aad335ac6d85270ac08f5..0b354f637f7f37c45c4deb7d06819a8d55495129 100644 (file)
@@ -36,6 +36,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.*;
@@ -72,75 +73,6 @@ 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, ActivityCompat.OnRequestPermissionsResultCallback {
 
        // Actions sent to the telemetry server at startup time
@@ -162,10 +94,13 @@ 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 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";
 
        // Setup result bits
        public static final int SETUP_BAUD = 1;
@@ -218,7 +153,6 @@ public class AltosDroid extends FragmentActivity implements AltosUnitsListener,
        TelemetryState  telemetry_state;
        Tracker[]       trackers;
 
-
        UsbDevice       pending_usb_device;
        boolean         start_with_usb;
 
@@ -325,7 +259,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]));
@@ -363,7 +297,6 @@ public class AltosDroid extends FragmentActivity implements AltosUnitsListener,
        }
 
        int     selected_serial = 0;
-       int     current_serial;
        long    switch_time;
 
        void set_switch_time() {
@@ -378,11 +311,15 @@ 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_serial == 0 || telemetry_state.get(selected_serial) == null) {
+                       AltosDebug.debug("selected serial set to %d", selected_serial);
+                       selected_serial = telemetry_state.latest_serial;
+               }
+
+               int shown_serial = selected_serial;
 
-               if (current_serial == 0)
-                       current_serial = telemetry_state.latest_serial;
+               if (telemetry_state.idle_mode)
+                       shown_serial = telemetry_state.latest_serial;
 
                if (!registered_units_listener) {
                        registered_units_listener = true;
@@ -390,7 +327,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++;
                }
 
@@ -399,44 +337,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 (telemetry_state.frequency != AltosLib.MISSING)
+                       telem_frequency = telemetry_state.frequency;
 
-               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 (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);
 
@@ -477,6 +388,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);
@@ -490,16 +414,7 @@ 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));
                }
        }
 
@@ -950,6 +865,10 @@ 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;
                }
        }
 
@@ -1069,7 +988,10 @@ public class AltosDroid extends FragmentActivity implements AltosUnitsListener,
                return true;
        }
 
+       double telem_frequency = 434.550;
+
        void setFrequency(double freq) {
+               telem_frequency = freq;
                try {
                        mService.send(Message.obtain(null, TelemetryService.MSG_SETFREQUENCY, freq));
                        set_switch_time();
@@ -1109,10 +1031,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);
@@ -1120,6 +1041,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;
@@ -1128,12 +1050,20 @@ 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 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 touch_trackers(Integer[] serials) {
                AlertDialog.Builder builder_tracker = new AlertDialog.Builder(this);
                builder_tracker.setTitle("Select Tracker");
@@ -1143,16 +1073,16 @@ public class AltosDroid extends FragmentActivity implements AltosUnitsListener,
                my_trackers[0] = new Tracker(null);
 
                for (int i = 0; i < serials.length; i++) {
-                       AltosState      s = telemetry_state.states.get(serials[i]);
+                       AltosState      s = telemetry_state.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);
+                                                                select_tracker(0, 0.0);
                                                         else
-                                                                select_tracker(my_trackers[item].serial);
+                                                                select_tracker(my_trackers[item].serial, my_trackers[item].frequency);
                                                 }
                                         });
                AlertDialog alert_tracker = builder_tracker.create();
@@ -1210,23 +1140,14 @@ public class AltosDroid extends FragmentActivity implements AltosUnitsListener,
                        alert_freq.show();
                        return true;
                case R.id.select_tracker:
+                       serverIntent = new Intent(this, SelectTrackerActivity.class);
                        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();
-
+                               ArrayList<Tracker> tracker_array = new ArrayList<Tracker>(Arrays.asList(trackers));
+                               serverIntent.putParcelableArrayListExtra(EXTRA_TRACKERS, tracker_array);
+                       } else {
+                               serverIntent.putExtra(EXTRA_TRACKERS, (Parcelable[]) null);
                        }
+                       startActivityForResult(serverIntent, REQUEST_SELECT_TRACKER);
                        return true;
                case R.id.delete_track:
                        if (trackers != null) {
@@ -1249,6 +1170,7 @@ public class AltosDroid extends FragmentActivity implements AltosUnitsListener,
                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;
                }
index f23955685389d43ed93bf4e12adf626676a375e2..876abcc49ba00430755be55c40570a221c3fd07b 100644 (file)
@@ -47,6 +47,11 @@ public class AltosDroidPreferences extends AltosPreferences {
 
        static int      map_source;
 
+       /* Tracker sort selection */
+       final static String trackerSortPreference = "TRACKER-SORT";
+
+       static int      tracker_sort;
+
        public static void init(Context context) {
                if (backend != null)
                        return;
@@ -62,6 +67,8 @@ public class AltosDroidPreferences extends AltosPreferences {
                        active_device_address = new DeviceAddress (address, name);
 
                map_source = backend.getInt(mapSourcePreference, MAP_SOURCE_ONLINE);
+
+               tracker_sort = backend.getInt(trackerSortPreference, 0);
        }
 
        public static void set_active_device(DeviceAddress address) {
@@ -134,4 +141,21 @@ public class AltosDroidPreferences extends AltosPreferences {
                        }
                }
        }
+
+
+       public static int tracker_sort() {
+               synchronized(backend) {
+                       return tracker_sort;
+               }
+       }
+
+       public static void set_tracker_sort(int new_tracker_sort) {
+               synchronized(backend) {
+                       if (tracker_sort != new_tracker_sort) {
+                               tracker_sort = new_tracker_sort;
+                               backend.putInt(trackerSortPreference, tracker_sort);
+                               flush_preferences();
+                       }
+               }
+       }
 }
index a2b0b25b75cf2da4f9d63d3d4d4fd852109f83a1..822f1f79036f2328858fe6075fc419bc45749143 100644 (file)
@@ -437,11 +437,11 @@ public class AltosMapOffline extends View implements ScaleGestureDetector.OnScal
 
                if (telem_state != null) {
                        Integer[] old_serial = rockets.keySet().toArray(new Integer[0]);
-                       Integer[] new_serial = telem_state.states.keySet().toArray(new Integer[0]);
+                       Integer[] new_serial = telem_state.keySet().toArray(new Integer[0]);
 
                        /* remove deleted keys */
                        for (int serial : old_serial) {
-                               if (!telem_state.states.containsKey(serial))
+                               if (!telem_state.containsKey(serial))
                                        rockets.remove(serial);
                        }
 
@@ -449,7 +449,7 @@ public class AltosMapOffline extends View implements ScaleGestureDetector.OnScal
 
                        for (int serial : new_serial) {
                                Rocket          rocket;
-                               AltosState      t_state = telem_state.states.get(serial);
+                               AltosState      t_state = telem_state.get(serial);
                                if (rockets.containsKey(serial))
                                        rocket = rockets.get(serial);
                                else {
@@ -459,7 +459,7 @@ public class AltosMapOffline extends View implements ScaleGestureDetector.OnScal
                                if (t_state.gps != null) {
                                        AltosLatLon     latlon = new AltosLatLon(t_state.gps.lat, t_state.gps.lon);
                                        rocket.set_position(latlon, t_state.received_time);
-                                       if (state.cal_data().serial == serial)
+                                       if (state != null && state.cal_data().serial == serial)
                                                there = latlon;
                                }
                                if (state != null)
index 3ebae8fecb6497248ef1531e21a492f6b89ea485..8361b278f93b8cc65022effc3c3b8dc86ccbce1b 100644 (file)
@@ -287,12 +287,12 @@ public class AltosMapOnline implements AltosDroidMapInterface, GoogleMap.OnMarke
 
                if (telem_state != null) {
                        for (int serial : rockets.keySet()) {
-                               if (!telem_state.states.containsKey(serial))
+                               if (!telem_state.containsKey(serial))
                                        remove_rocket(serial);
                        }
 
-                       for (int serial : telem_state.states.keySet()) {
-                               set_rocket(serial, telem_state.states.get(serial));
+                       for (int serial : telem_state.keySet()) {
+                               set_rocket(serial, telem_state.get(serial));
                        }
                }
 
index 9c786ac4e04dfac39d16465b802407166407f7f7..763ba3f1933cac07e690ba20c616bf0ac087a73c 100644 (file)
@@ -29,11 +29,13 @@ import android.widget.*;
 import org.altusmetrum.altoslib_13.*;
 
 public class IdleModeActivity extends Activity {
-       private EditText callsign;
+       private EditText callsignText;
+       private TextView frequencyView;
        private Button connect;
        private Button disconnect;
        private Button reboot;
        private Button igniters;
+       private double frequency;
 
        public static final String EXTRA_IDLE_MODE = "idle_mode";
        public static final String EXTRA_IDLE_RESULT = "idle_result";
@@ -52,7 +54,7 @@ public class IdleModeActivity extends Activity {
        }
 
        private String callsign() {
-               return callsign.getEditableText().toString();
+               return callsignText.getEditableText().toString();
        }
 
        public void connect_idle() {
@@ -80,8 +82,12 @@ public class IdleModeActivity extends Activity {
                // Setup the window
                setContentView(R.layout.idle_mode);
 
-               callsign = (EditText) findViewById(R.id.set_callsign);
-               callsign.setText(new StringBuffer(AltosPreferences.callsign()));
+               callsignText = (EditText) findViewById(R.id.set_callsign);
+               callsignText.setText(new StringBuffer(AltosPreferences.callsign()));
+
+               frequency = getIntent().getDoubleExtra(AltosDroid.EXTRA_FREQUENCY, 0.0);
+               frequencyView = (TextView) findViewById(R.id.frequency);
+               frequencyView.setText(String.format("Frequency: %7.3f MHz", frequency));
 
                connect = (Button) findViewById(R.id.connect_idle);
                connect.setOnClickListener(new OnClickListener() {
diff --git a/altosdroid/app/src/main/java/org/altusmetrum/AltosDroid/SelectTrackerActivity.java b/altosdroid/app/src/main/java/org/altusmetrum/AltosDroid/SelectTrackerActivity.java
new file mode 100644 (file)
index 0000000..aa52756
--- /dev/null
@@ -0,0 +1,294 @@
+/*
+ * Copyright © 2020 Keith Packard <keithp@keithp.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+package org.altusmetrum.AltosDroid;
+
+import java.util.*;
+import android.app.Activity;
+import android.content.*;
+import android.os.*;
+import android.util.*;
+import android.view.*;
+import android.view.View.*;
+import android.widget.*;
+import android.graphics.*;
+import android.graphics.drawable.*;
+import android.widget.CompoundButton.OnCheckedChangeListener;
+
+import org.altusmetrum.altoslib_13.*;
+
+class TrackerComparatorCall implements Comparator<Tracker> {
+       public int compare(Tracker a, Tracker b) {
+               int v;
+
+               v = a.compareCall(b);
+               if (v != 0)
+                       return v;
+               v = a.compareAge(b);
+               if (v != 0)
+                       return v;
+               v = a.compareSerial(b);
+               if (v != 0)
+                       return v;
+               return a.compareFrequency(b);
+       }
+       public boolean equals(Object o) {
+               return o instanceof TrackerComparatorCall;
+       }
+}
+
+class TrackerComparatorSerial implements Comparator<Tracker> {
+       public int compare(Tracker a, Tracker b) {
+               int v;
+
+               v = a.compareSerial(b);
+               if (v != 0)
+                       return v;
+               v = a.compareAge(b);
+               if (v != 0)
+                       return v;
+               v = a.compareCall(b);
+               if (v != 0)
+                       return v;
+               return a.compareFrequency(b);
+       }
+       public boolean equals(Object o) {
+               return o instanceof TrackerComparatorSerial;
+       }
+}
+
+class TrackerComparatorAge implements Comparator<Tracker> {
+       public int compare(Tracker a, Tracker b) {
+               int v;
+
+               v = a.compareAge(b);
+               if (v != 0)
+                       return v;
+               v = a.compareCall(b);
+               if (v != 0)
+                       return v;
+               v = a.compareSerial(b);
+               if (v != 0)
+                       return v;
+               return a.compareFrequency(b);
+       }
+       public boolean equals(Object o) {
+               return o instanceof TrackerComparatorAge;
+       }
+}
+
+class TrackerComparatorFrequency implements Comparator<Tracker> {
+       public int compare(Tracker a, Tracker b) {
+               int v;
+
+               v = a.compareFrequency(b);
+               if (v != 0)
+                       return v;
+               v = a.compareAge(b);
+               if (v != 0)
+                       return v;
+               v = a.compareCall(b);
+               if (v != 0)
+                       return v;
+               return a.compareSerial(b);
+       }
+       public boolean equals(Object o) {
+               return o instanceof TrackerComparatorFrequency;
+       }
+}
+
+public class SelectTrackerActivity extends Activity implements OnTouchListener {
+       // Return Intent extra
+       public static final String EXTRA_SERIAL_NUMBER = "serial_number";
+       public static final String EXTRA_FREQUENCY = "frequency";
+
+       private int button_ids[] = {
+               R.id.call_button,
+               R.id.serial_button,
+               R.id.frequency_button,
+               R.id.age_button
+       };
+
+       private static final int call_button = 0;
+       private static final int serial_button = 1;
+       private static final int freq_button = 2;
+       private static final int age_button = 3;
+       private RadioButton radio_buttons[] = new RadioButton[4];
+       private TableLayout table;
+
+       private Tracker[] trackers;
+
+       private void set_sort(int id) {
+               AltosDroidPreferences.set_tracker_sort(id);
+               resort();
+       }
+
+       private void resort() {
+               Comparator<Tracker> compare;
+               int tracker_sort = AltosDroidPreferences.tracker_sort();
+               AltosDebug.debug("sort %d", tracker_sort);
+               switch (tracker_sort) {
+               case call_button:
+               default:
+                       compare = new TrackerComparatorCall();
+                       break;
+               case serial_button:
+                       compare = new TrackerComparatorSerial();
+                       break;
+               case freq_button:
+                       compare = new TrackerComparatorFrequency();
+                       break;
+               case age_button:
+                       compare = new TrackerComparatorAge();
+                       break;
+               }
+               Arrays.sort(trackers, compare);
+               set_trackers();
+       }
+
+       void init_button_state() {
+               int tracker_sort = AltosDroidPreferences.tracker_sort();
+               for (int i = 0; i < 4; i++)
+                       radio_buttons[i].setChecked(i == tracker_sort);
+       }
+
+       OnCheckedChangeListener button_listener = new OnCheckedChangeListener() {
+                       @Override
+                       public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+                               int id = buttonView.getId();
+                               if (isChecked) {
+                                       int sort_id = -1;
+                                       for (int i = 0; i < 4; i++) {
+                                               if (id == button_ids[i])
+                                                       sort_id = i;
+                                               else
+                                                       radio_buttons[i].setChecked(false);
+                                       }
+                                       if (sort_id != -1)
+                                               set_sort(sort_id);
+                               }
+                       }
+               };
+
+       long start_time;
+
+       private void
+       insert_tracker(Tracker tracker) {
+               TableRow row = (TableRow) getLayoutInflater().inflate(R.layout.tracker_ent, null);
+
+               ((TextView) row.findViewById(R.id.call_view)).setText(tracker.call);
+               if (tracker.serial == 0)
+                       ((TextView) row.findViewById(R.id.serial_view)).setText("");
+               else
+                       ((TextView) row.findViewById(R.id.serial_view)).setText(String.format("%d", tracker.serial));
+               if (tracker.frequency == 0.0)
+                       ((TextView) row.findViewById(R.id.frequency_view)).setText("");
+               else if (tracker.frequency == AltosLib.MISSING)
+                       ((TextView) row.findViewById(R.id.frequency_view)).setText("");
+               else
+                       ((TextView) row.findViewById(R.id.frequency_view)).setText(String.format("%7.3f", tracker.frequency));
+               if (tracker.received_time != 0) {
+                       int age = (int) ((start_time - tracker.received_time + 500) / 1000);
+                       ((TextView) row.findViewById(R.id.age_view)).setText(AltosDroid.age_string(age));
+               } else {
+                       ((TextView) row.findViewById(R.id.age_view)).setText("");
+               }
+               row.setClickable(true);
+               row.setOnTouchListener(this);
+               table.addView(row);
+       }
+
+       private void set_trackers() {
+               for (int i = table.getChildCount() - 1; i >= 1; i--)
+                       table.removeViewAt(i);
+               for (Tracker tracker : trackers)
+                       insert_tracker(tracker);
+       }
+
+       private void done(View v) {
+               int result = Activity.RESULT_CANCELED;
+               Intent intent = new Intent();
+               for (int i = 1; i < table.getChildCount(); i++) {
+                       View child = table.getChildAt(i);
+                       if (child == v) {
+                               Tracker tracker = trackers[i - 1];
+                               intent.putExtra(EXTRA_SERIAL_NUMBER, tracker.serial);
+                               intent.putExtra(EXTRA_FREQUENCY, tracker.frequency);
+                               result = Activity.RESULT_OK;
+                               break;
+                       }
+               }
+               setResult(Activity.RESULT_OK, intent);
+               finish();
+       }
+
+       @Override
+       protected void onCreate(Bundle savedInstanceState) {
+               setTheme(AltosDroid.dialog_themes[AltosDroidPreferences.font_size()]);
+               super.onCreate(savedInstanceState);
+
+               setContentView(R.layout.tracker_list);
+               // Set result CANCELED incase the user backs out
+               setResult(Activity.RESULT_CANCELED);
+
+               for (int i = 0; i < 4; i++) {
+                       radio_buttons[i] = (RadioButton) findViewById(button_ids[i]);
+                       radio_buttons[i].setOnCheckedChangeListener(button_listener);
+               }
+
+               ArrayList<Parcelable> tracker_array = (ArrayList<Parcelable>) getIntent().getParcelableArrayListExtra(AltosDroid.EXTRA_TRACKERS);
+               if (tracker_array != null) {
+                       Object[] array = tracker_array.toArray();
+                       trackers = new Tracker[array.length];
+                       for (int i = 0; i < array.length; i++)
+                               trackers[i] = (Tracker) array[i];
+               }
+
+               start_time = System.currentTimeMillis();
+
+               table = (TableLayout) findViewById(R.id.tracker_list);
+
+               init_button_state();
+
+               resort();
+
+               set_trackers();
+       }
+
+       @Override
+       public boolean onTouch(View v, MotionEvent event) {
+               int action = event.getAction() & MotionEvent.ACTION_MASK;
+               switch (action) {
+               case MotionEvent.ACTION_UP:
+               case MotionEvent.ACTION_CANCEL:
+               case MotionEvent.ACTION_OUTSIDE:
+                       v.setBackgroundColor(0);
+                       v.invalidate();
+                       break;
+               case MotionEvent.ACTION_DOWN:
+                       v.setBackgroundColor(Color.RED);
+                       v.invalidate();
+                       break;
+               }
+               if (action == MotionEvent.ACTION_UP) {
+                       done(v);
+                       return true;
+               }
+               return false;
+       }
+}
index d4f72b3f7421851d8d4051b4f6a15d590d360af9..2bec95bc4cf1d05de95ac472ff4c73ec81cb1e1d 100644 (file)
@@ -251,12 +251,11 @@ public class TelemetryService extends Service implements AltosIdleMonitorListene
        private void telemetry(AltosTelemetry telem) {
                AltosState      state;
 
-               if (telemetry_state.states.containsKey(telem.serial()))
-                       state = telemetry_state.states.get(telem.serial());
-               else
+               state = telemetry_state.get(telem.serial());
+               if (state == null)
                        state = new AltosState(new AltosCalData());
                telem.provide_data(state);
-               telemetry_state.states.put(telem.serial(), state);
+               telemetry_state.put(telem.serial(), state);
                telemetry_state.quiet = false;
                if (state != null) {
                        AltosPreferences.set_state(state,telem.serial());
@@ -269,8 +268,6 @@ public class TelemetryService extends Service implements AltosIdleMonitorListene
        private Message message() {
                if (telemetry_state == null)
                        AltosDebug.debug("telemetry_state null!");
-               if (telemetry_state.states == null)
-                       AltosDebug.debug("telemetry_state.states null!");
                return Message.obtain(null, AltosDroid.MSG_STATE, telemetry_state);
        }
 
@@ -411,7 +408,7 @@ public class TelemetryService extends Service implements AltosIdleMonitorListene
        }
 
        private void delete_serial(int serial) {
-               telemetry_state.states.remove((Integer) serial);
+               telemetry_state.remove(serial);
                AltosPreferences.remove_state(serial);
                send_to_clients();
        }
@@ -438,6 +435,8 @@ public class TelemetryService extends Service implements AltosIdleMonitorListene
                        telemetry_stop();
                        idle_monitor = new AltosIdleMonitor(this, altos_link, true, false);
                        idle_monitor.set_callsign(AltosPreferences.callsign());
+                       idle_monitor.set_frequency(telemetry_state.frequency);
+                       telemetry_state.idle_mode = true;
                        idle_monitor.start();
                        send_idle_mode_to_clients();
                }
@@ -450,6 +449,7 @@ public class TelemetryService extends Service implements AltosIdleMonitorListene
                        } catch (InterruptedException ie) {
                        }
                        idle_monitor = null;
+                       telemetry_state.idle_mode = false;
                        telemetry_start();
                        send_idle_mode_to_clients();
                }
@@ -615,9 +615,6 @@ public class TelemetryService extends Service implements AltosIdleMonitorListene
                for (int serial : serials) {
                        AltosState saved_state = AltosPreferences.state(serial);
                        if (saved_state != null) {
-                               if (telemetry_state.latest_serial == 0)
-                                       telemetry_state.latest_serial = serial;
-
                                AltosDebug.debug("recovered old state serial %d flight %d",
                                                 serial,
                                                 saved_state.cal_data().flight);
@@ -625,7 +622,7 @@ public class TelemetryService extends Service implements AltosIdleMonitorListene
                                        AltosDebug.debug("\tposition %f,%f",
                                                         saved_state.gps.lat,
                                                         saved_state.gps.lon);
-                               telemetry_state.states.put(serial, saved_state);
+                               telemetry_state.put(serial, saved_state);
                        } else {
                                AltosDebug.debug("Failed to recover state for %d", serial);
                                AltosPreferences.remove_state(serial);
@@ -710,7 +707,9 @@ public class TelemetryService extends Service implements AltosIdleMonitorListene
 
        /* AltosIdleMonitorListener */
        public void update(AltosState state, AltosListenerState listener_state) {
-               telemetry_state.states.put(state.cal_data().serial, state);
+               if (state != null)
+                       AltosDebug.debug("update call %s freq %7.3f", state.cal_data().callsign, state.frequency);
+               telemetry_state.put(state.cal_data().serial, state);
                telemetry_state.receiver_battery = listener_state.battery;
                send_to_clients();
        }
index d292c7bff6331924f2933696b7041bf724f39c12..4ce644e54afcdb3fd9e8d8f0df415c62827e4a6b 100644 (file)
@@ -35,11 +35,44 @@ public class TelemetryState {
        double          frequency;
        int             telemetry_rate;
 
+       boolean         idle_mode;
        boolean         quiet;
 
-       HashMap<Integer,AltosState>     states;
+       private HashMap<Integer,AltosState>     states;
 
        int             latest_serial;
+       long            latest_received_time;
+
+       public void put(int serial, AltosState state) {
+               long received_time = state.received_time;
+               if (received_time > latest_received_time || latest_serial == 0) {
+                       latest_serial = serial;
+                       latest_received_time = received_time;
+               }
+               states.put(serial, state);
+       }
+
+       public AltosState get(int serial) {
+               if (states.containsKey(serial))
+                       return states.get(serial);
+               return null;
+       }
+
+       public void remove(int serial) {
+               states.remove((Integer) serial);
+       }
+
+       public Set<Integer> keySet() {
+               return states.keySet();
+       }
+
+       public Collection<AltosState> values() {
+               return states.values();
+       }
+
+       public boolean containsKey(int serial) {
+               return states.containsKey(serial);
+       }
 
        public TelemetryState() {
                connect = CONNECT_NONE;
diff --git a/altosdroid/app/src/main/java/org/altusmetrum/AltosDroid/Tracker.java b/altosdroid/app/src/main/java/org/altusmetrum/AltosDroid/Tracker.java
new file mode 100644 (file)
index 0000000..72042e6
--- /dev/null
@@ -0,0 +1,201 @@
+/*
+ * Copyright © 2020 Keith Packard <keithp@keithp.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+package org.altusmetrum.AltosDroid;
+
+import java.lang.ref.WeakReference;
+import java.util.*;
+
+import android.Manifest;
+import android.app.Activity;
+import android.app.PendingIntent;
+import android.bluetooth.BluetoothAdapter;
+import android.content.Intent;
+import android.content.Context;
+import android.content.ComponentName;
+import android.content.ServiceConnection;
+import android.content.DialogInterface;
+import android.os.IBinder;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.RemoteException;
+import android.os.Parcelable;
+import android.os.Parcel;
+import androidx.fragment.app.FragmentActivity;
+import androidx.fragment.app.FragmentManager;
+import android.view.*;
+import android.widget.*;
+import android.app.AlertDialog;
+import android.location.Location;
+import android.location.LocationManager;
+import android.location.LocationListener;
+import android.hardware.usb.*;
+import android.content.pm.PackageManager;
+import androidx.core.app.ActivityCompat;
+import org.altusmetrum.altoslib_13.*;
+
+public class Tracker implements CharSequence, Comparable, Parcelable {
+       int     serial;
+       String  call;
+       double  frequency;
+       long    received_time;
+       String  display;
+
+       private void make_display() {
+               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(int serial, String call, double frequency, long received_time) {
+               if (call == null)
+                       call = "none";
+
+               this.serial = serial;
+               this.call = call;
+               this.frequency = frequency;
+               this.received_time = received_time;
+               make_display();
+       }
+
+       public Tracker(int serial, String call, double frequency) {
+               this(serial, call, frequency, 0);
+       }
+
+       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,
+                    s == null ? 0 : s.received_time);
+       }
+
+       /* 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;
+       }
+
+       /* Parcelable */
+
+       public int describeContents() {
+               AltosDebug.debug("describe contents %d", serial);
+               return 0;
+       }
+
+       public void writeToParcel(Parcel out, int flags) {
+               AltosDebug.debug("write to parcel %s", display);
+               out.writeInt(serial);
+               out.writeString(call);
+               out.writeDouble(frequency);
+               out.writeLong(received_time);
+       }
+
+       public static final Parcelable.Creator<Tracker> CREATOR
+       = new Parcelable.Creator<Tracker>() {
+                       public Tracker createFromParcel(Parcel in) {
+                               AltosDebug.debug("createFromParcel");
+                               return new Tracker(in);
+                       }
+
+                       public Tracker[] newArray(int size) {
+                               AltosDebug.debug("newArray %d", size);
+                               return new Tracker[size];
+                       }
+               };
+
+       /* newer (-1), same (0), older(1) */
+       public int compareAge(Tracker o) {
+               if (received_time == o.received_time)
+                       return 0;
+               if (received_time == 0)
+                       return -1;
+               if (o.received_time == 0)
+                       return 1;
+               if (received_time > o.received_time)
+                       return -1;
+               return 1;
+       }
+
+       public int compareCall(Tracker o) {
+               int v = call.compareTo(o.call);
+               if (v == 0)
+                       return v;
+               if (call.equals("auto"))
+                       return -1;
+               if (o.call.equals("auto"))
+                       return 1;
+               return v;
+       }
+
+       public int compareSerial(Tracker o) {
+               return serial - o.serial;
+       }
+
+       public int compareFrequency(Tracker o) {
+               return (int) Math.signum(frequency - o.frequency);
+       }
+
+       private Tracker(Parcel in) {
+               serial = in.readInt();
+               call = in.readString();
+               frequency = in.readDouble();
+               received_time = in.readLong();
+               make_display();
+               AltosDebug.debug("Create from parcel %s", display);
+       }
+}
+
index 8fa358c97c8ae018fc6eca7528ffc11214e382ff..fa09eaa332d280f49978acfd44aa5b266dbd576c 100644 (file)
@@ -16,6 +16,5 @@
 <TextView xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
-    android:textSize="18sp"
     android:padding="5dp"
-/>
\ No newline at end of file
+/>
index 6c598c1cbb4d5cdc2e3d5fffaa87f860ef807d82..11b5528b8d4f820bbd002dce91022a1152679438 100644 (file)
                android:layout_width="fill_parent"
                android:layout_height="wrap_content"
                android:hint="@string/set_callsign_label"/>
+      <TextView android:id="@+id/frequency"
+               android:layout_width="fill_parent"
+               android:layout_height="wrap_content"
+               android:text=""
+               />
       <Button android:id="@+id/connect_idle"
               android:layout_width="match_parent"
               android:layout_height="wrap_content"
diff --git a/altosdroid/app/src/main/res/layout/tracker_ent.xml b/altosdroid/app/src/main/res/layout/tracker_ent.xml
new file mode 100644 (file)
index 0000000..4b06663
--- /dev/null
@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+
+ Copyright © 2020 Keith Packard <keithp@keithp.com>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+
+-->
+<TableRow
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_gravity="center"
+    android:layout_weight="1"
+    android:padding="2dip"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    >
+    <TextView
+       android:id="@+id/call_view"
+       android:layout_width="wrap_content"
+       android:layout_height="wrap_content"
+       android:paddingRight="3dp"
+       android:text="" />
+    <TextView
+       android:id="@+id/serial_view"
+       android:layout_width="wrap_content"
+       android:layout_height="wrap_content"
+       android:paddingRight="3dp"
+       android:text="" />
+    <TextView
+       android:id="@+id/frequency_view"
+       android:layout_width="wrap_content"
+       android:layout_height="wrap_content"
+       android:paddingRight="3dp"
+       android:text="" />
+    <TextView
+       android:id="@+id/age_view"
+       android:layout_width="wrap_content"
+       android:layout_height="wrap_content"
+       android:text="" />
+</TableRow>
diff --git a/altosdroid/app/src/main/res/layout/tracker_list.xml b/altosdroid/app/src/main/res/layout/tracker_list.xml
new file mode 100644 (file)
index 0000000..a5123a7
--- /dev/null
@@ -0,0 +1,68 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+
+ Copyright © 2020 Keith Packard <keithp@keithp.com>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="vertical"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    >
+    <ScrollView
+       android:layout_width="match_parent"
+       android:layout_height="match_parent"
+       >
+       <TableLayout
+           xmlns:android="http://schemas.android.com/apk/res/android"
+           android:id="@+id/tracker_list"
+           android:orientation="vertical"
+           android:layout_width="match_parent"
+           android:layout_height="wrap_content"
+           android:stretchColumns="1,2,3,4">
+           <TableRow
+               android:id="@+id/tracker_row"
+               android:layout_gravity="center"
+               android:layout_weight="1"
+               android:padding="2dip"
+               android:layout_width="match_parent"
+               android:layout_height="wrap_content"
+               >
+               <RadioButton
+                   android:id="@+id/call_button"
+                   android:layout_width="wrap_content"
+                   android:layout_height="wrap_content"
+                   android:text="@string/callsign_label" />
+               <RadioButton
+                   android:id="@+id/serial_button"
+                   android:layout_width="wrap_content"
+                   android:layout_height="wrap_content"
+                   android:text="@string/serial_label" />
+               <RadioButton
+                   android:id="@+id/frequency_button"
+                   android:layout_width="wrap_content"
+                   android:layout_height="wrap_content"
+                   android:text="@string/freq_label" />
+               <RadioButton
+                   android:id="@+id/age_button"
+                   android:layout_width="wrap_content"
+                   android:layout_height="wrap_content"
+                   android:text="@string/age_label" />
+           </TableRow>
+       </TableLayout>
+    </ScrollView>
+</LinearLayout>
index 5e8c91c4c2f640703f2fbff31e9d8c75e5f0f054..162ef1f425db2f74113a4a1f0ce58909cd37de6c 100644 (file)
@@ -52,6 +52,9 @@
        <string name="title_other_devices">Other Available Devices</string>
        <string name="button_scan">Scan for devices</string>
 
+       <!-- TrackerListActivity -->
+       <string name="freq_label">Freq</string>
+
        <!-- Service -->
        <string name="telemetry_service_label">AltosDroid Telemetry Service</string>
        <string name="telemetry_service_started">Telemetry Service Started</string>
index 99d3192133f116e3cdbc5a89f48b9c34d548d160..505fed561c3138dd695251b215716839a30e8a6d 100644 (file)
@@ -30,7 +30,7 @@ public class AltosIdleMonitor extends Thread {
 
        boolean                 remote;
        boolean                 close_on_exit;
-       double                  frequency;
+       double                  frequency = AltosLib.MISSING;
        String                  callsign;
 
        AltosState              state;
@@ -63,6 +63,8 @@ public class AltosIdleMonitor extends Thread {
                        if (state == null)
                                state = new AltosState(new AltosCalData(link.config_data()));
                        fetch.provide_data(state);
+                       if (frequency != AltosLib.MISSING)
+                               state.set_frequency(frequency);
                        if (!link.has_error && !link.reply_abort)
                                worked = true;
                } finally {