Initial Commit - Copy from Altus Metrum AltOS

This commit is contained in:
2024-06-25 19:03:04 +02:00
commit 13fc49c923
2048 changed files with 1206748 additions and 0 deletions

View File

@@ -0,0 +1,116 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright © 2012 Mike Beattie <mike@ethernal.org>
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.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.altusmetrum.AltosDroid">
<!-- Google Maps -->
<uses-feature android:glEsVersion="0x00020000" android:required="true"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<!-- Permissions needed to access bluetooth -->
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
<!-- Permissions needed to save Telemetry logs to SD card -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<!-- Permissions needed for GoogleMaps -->
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="com.google.android.providers.gsf.permission.READ_GSERVICES"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<permission android:name="org.altusmetrum.AltosDroid.permission.MAPS_RECEIVE"
android:protectionLevel="signature"/>
<uses-permission android:name="org.altusmetrum.AltosDroid.permission.MAPS_RECEIVE"/>
<!-- Permissions needed to access USB OTG -->
<uses-feature android:name="android.hardware.usb.host" android:required="false" />
<application android:label="@string/app_name"
android:icon="@drawable/app_icon"
android:allowBackup="true"
android:theme="@style/Medium">
<activity android:name="org.altusmetrum.AltosDroid.AltosDroid"
android:label="@string/app_name"
android:configChanges="orientation|keyboardHidden"
android:launchMode="singleTop"
android:exported="true">
<intent-filter>
<category android:name="android.intent.category.LAUNCHER" />
<action android:name="android.intent.action.MAIN" />
<action android:name="android.intent.action.VIEW" />
<action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED"/>
</intent-filter>
<meta-data
android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED"
android:resource="@xml/device_filter" />
</activity>
<activity android:name=".DeviceListActivity"
android:label="@string/select_device"
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"
android:configChanges="orientation|keyboardHidden" />
<activity android:name=".MapTypeActivity"
android:label="@string/map_type"
android:theme="@android:style/Theme.Dialog"
android:configChanges="orientation|keyboardHidden" />
<activity android:name=".IdleModeActivity"
android:label="@string/idle_mode"
android:theme="@android:style/Theme.Dialog"
android:configChanges="orientation|keyboardHidden" />
<activity android:name=".IgniterActivity"
android:label="@string/igniters"
android:theme="@android:style/Theme.Dialog"
android:configChanges="orientation|keyboardHidden" />
<activity android:name=".SetupActivity"
android:label="@string/setup"
android:theme="@android:style/Theme.Dialog"
android:configChanges="orientation" />
<activity android:name=".ManageFrequenciesActivity"
android:label="@string/manage_frequencies"
android:theme="@android:style/Theme.Dialog"
android:configChanges="orientation|keyboard" />
<service android:name=".TelemetryService" />
<meta-data android:name="com.google.android.maps.v2.API_KEY"
android:value="@GOOGLEKEY@"/>
<meta-data android:name="com.google.android.gms.version"
android:value="@integer/google_play_services_version" />
</application>
</manifest>

View File

@@ -0,0 +1,224 @@
/*
* Copyright © 2011 Keith Packard <keithp@keithp.com>
* Copyright © 2012 Mike Beattie <mike@ethernal.org>
*
* 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.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.UUID;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothSocket;
import android.os.Handler;
public class AltosBluetooth extends AltosDroidLink {
private ConnectThread connect_thread = null;
private BluetoothDevice device;
private BluetoothSocket socket;
private InputStream input;
private OutputStream output;
private boolean pause;
// Constructor
public AltosBluetooth(BluetoothDevice device, Handler handler, boolean pause) {
super(handler);
this.device = device;
this.handler = handler;
this.pause = pause;
connect_thread = new ConnectThread();
connect_thread.start();
}
void connected() {
if (closed()) {
AltosDebug.debug("connected after closed");
return;
}
AltosDebug.check_ui("connected\n");
try {
synchronized(this) {
if (socket != null) {
input = socket.getInputStream();
output = socket.getOutputStream();
super.connected();
}
}
} catch (InterruptedException ie) {
connect_failed();
} catch (IOException io) {
connect_failed();
}
}
private void connect_failed() {
if (closed()) {
AltosDebug.debug("connect_failed after closed");
return;
}
close_device();
input = null;
output = null;
handler.obtainMessage(TelemetryService.MSG_CONNECT_FAILED, this).sendToTarget();
AltosDebug.error("ConnectThread: Failed to establish connection");
}
void close_device() {
BluetoothSocket tmp_socket;
synchronized(this) {
tmp_socket = socket;
socket = null;
}
if (tmp_socket != null) {
try {
tmp_socket.close();
} catch (IOException e) {
AltosDebug.error("close_socket failed");
}
}
}
public void close() {
super.close();
input = null;
output = null;
}
private final UUID SPP_UUID = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB");
private void create_socket(BluetoothDevice device) {
BluetoothSocket tmp_socket = null;
AltosDebug.check_ui("create_socket\n");
try {
tmp_socket = device.createInsecureRfcommSocketToServiceRecord(SPP_UUID);
} catch (IOException e) {
e.printStackTrace();
}
if (socket != null) {
AltosDebug.debug("Socket already allocated %s", socket.toString());
close_device();
}
synchronized (this) {
socket = tmp_socket;
}
}
private class ConnectThread extends Thread {
public void run() {
AltosDebug.debug("ConnectThread: BEGIN (pause %b)", pause);
setName("ConnectThread");
if (pause) {
try {
Thread.sleep(4000);
} catch (InterruptedException e) {
}
}
create_socket(device);
// Always cancel discovery because it will slow down a connection
try {
BluetoothAdapter.getDefaultAdapter().cancelDiscovery();
} catch (Exception e) {
AltosDebug.debug("cancelDiscovery exception %s", e.toString());
}
BluetoothSocket local_socket = null;
synchronized (AltosBluetooth.this) {
if (!closed())
local_socket = socket;
}
if (local_socket != null) {
try {
// Make a connection to the BluetoothSocket
// This is a blocking call and will only return on a
// successful connection or an exception
local_socket.connect();
} catch (Exception e) {
AltosDebug.debug("Connect exception %s", e.toString());
try {
local_socket.close();
} catch (Exception ce) {
AltosDebug.debug("Close exception %s", ce.toString());
}
local_socket = null;
}
}
if (local_socket != null) {
connected();
} else {
connect_failed();
}
AltosDebug.debug("ConnectThread: completed");
}
}
private synchronized void wait_connected() throws InterruptedException, IOException {
AltosDebug.check_ui("wait_connected\n");
if (input == null && socket != null) {
AltosDebug.debug("wait_connected...");
wait();
AltosDebug.debug("wait_connected done");
}
if (socket == null)
throw new IOException();
}
int write(byte[] buffer, int len) {
if (output == null)
return -1;
try {
output.write(buffer, 0, len);
} catch (IOException ie) {
return -1;
}
return len;
}
int read(byte[] buffer, int len) {
if (input == null)
return -1;
try {
return input.read(buffer, 0, len);
} catch (IOException ie) {
return -1;
}
}
// Stubs of required methods when extending AltosLink
public boolean can_cancel_reply() { return false; }
public boolean show_reply_timeout() { return true; }
public void hide_reply_timeout() { }
}

View File

@@ -0,0 +1,69 @@
/*
* Copyright © 2015 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.*;
import android.content.*;
import android.util.Log;
import android.os.*;
import android.content.pm.*;
public class AltosDebug {
// Debugging
static final String TAG = "AltosDroid";
static boolean D = true;
static void init(Context context) {
ApplicationInfo app_info = context.getApplicationInfo();
if ((app_info.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0) {
Log.d(TAG, "Enable debugging\n");
D = true;
} else {
Log.d(TAG, "Disable debugging\n");
D = false;
}
}
static void info(String format, Object ... arguments) {
Log.i(TAG, String.format(format, arguments));
}
static void debug(String format, Object ... arguments) {
if (D)
Log.d(TAG, String.format(format, arguments));
}
static void error(String format, Object ... arguments) {
Log.e(TAG, String.format(format, arguments));
}
static void trace(String format, Object ... arguments) {
error(format, arguments);
for (StackTraceElement el : Thread.currentThread().getStackTrace())
Log.e(TAG, "\t" + el.toString() + "\n");
}
static void check_ui(String format, Object ... arguments) {
if (Looper.myLooper() == Looper.getMainLooper())
trace("ON UI THREAD " + format, arguments);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,214 @@
/*
* Copyright © 2015 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 android.os.Handler;
import org.altusmetrum.altoslib_14.*;
public abstract class AltosDroidLink extends AltosLink {
Handler handler;
Thread input_thread = null;
public double frequency() {
return frequency;
}
public int telemetry_rate() {
return telemetry_rate;
}
public void save_frequency() {
AltosPreferences.set_frequency(0, frequency);
}
public void save_telemetry_rate() {
AltosPreferences.set_telemetry_rate(0, telemetry_rate);
}
Object closed_lock = new Object();
boolean closing = false;
boolean closed = false;
public boolean closed() {
synchronized(closed_lock) {
return closing;
}
}
void connected() throws InterruptedException {
input_thread = new Thread(this);
input_thread.start();
// Configure the newly connected device for telemetry
print("~\nE 0\n");
set_monitor(false);
AltosDebug.debug("ConnectThread: connected");
/* Let TelemetryService know we're connected
*/
handler.obtainMessage(TelemetryService.MSG_CONNECTED, this).sendToTarget();
/* Notify other waiting threads that we're connected now
*/
notifyAll();
}
public void closing() {
synchronized(closed_lock) {
AltosDebug.debug("Marked closing true");
closing = true;
}
}
private boolean actually_closed() {
synchronized(closed_lock) {
return closed;
}
}
abstract void close_device();
public void close() {
AltosDebug.debug("close(): begin");
closing();
flush_output();
synchronized (closed_lock) {
AltosDebug.debug("Marked closed true");
closed = true;
}
close_device();
synchronized(this) {
if (input_thread != null) {
AltosDebug.debug("close(): stopping input_thread");
try {
AltosDebug.debug("close(): input_thread.interrupt().....");
input_thread.interrupt();
AltosDebug.debug("close(): input_thread.join().....");
input_thread.join();
} catch (Exception e) {}
input_thread = null;
}
notifyAll();
}
}
abstract int write(byte[] buffer, int len);
abstract int read(byte[] buffer, int len);
private static final int buffer_size = 64;
private byte[] in_buffer = new byte[buffer_size];
private byte[] out_buffer = new byte[buffer_size];
private int buffer_len = 0;
private int buffer_off = 0;
private int out_buffer_off = 0;
private byte[] debug_chars = new byte[buffer_size];
private int debug_off;
private void debug_input(byte b) {
if (b == '\n') {
AltosDebug.debug(" " + new String(debug_chars, 0, debug_off));
debug_off = 0;
} else {
if (debug_off < buffer_size)
debug_chars[debug_off++] = b;
}
}
private void disconnected() {
if (closed()) {
AltosDebug.debug("disconnected after closed");
return;
}
AltosDebug.debug("Sending disconnected message");
handler.obtainMessage(TelemetryService.MSG_DISCONNECTED, this).sendToTarget();
}
public int getchar() {
if (actually_closed())
return ERROR;
while (buffer_off == buffer_len) {
buffer_len = read(in_buffer, buffer_size);
if (buffer_len < 0) {
AltosDebug.debug("ERROR returned from getchar()");
disconnected();
return ERROR;
}
buffer_off = 0;
}
// if (AltosDebug.D)
// debug_input(in_buffer[buffer_off]);
return in_buffer[buffer_off++];
}
public void flush_output() {
super.flush_output();
if (actually_closed()) {
out_buffer_off = 0;
return;
}
while (out_buffer_off != 0) {
int sent = write(out_buffer, out_buffer_off);
if (sent <= 0) {
AltosDebug.debug("flush_output() failed");
out_buffer_off = 0;
break;
}
if (sent < out_buffer_off)
System.arraycopy(out_buffer, 0, out_buffer, sent, out_buffer_off - sent);
out_buffer_off -= sent;
}
}
public void putchar(byte c) {
out_buffer[out_buffer_off++] = c;
if (out_buffer_off == buffer_size)
flush_output();
}
public void print(String data) {
byte[] bytes = data.getBytes();
// AltosDebug.debug(data.replace('\n', '\\'));
for (byte b : bytes)
putchar(b);
}
public AltosDroidLink(Handler handler) {
this.handler = handler;
}
}

View File

@@ -0,0 +1,34 @@
/*
* Copyright © 2015 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 android.location.Location;
import org.altusmetrum.altoslib_14.*;
public interface AltosDroidMapInterface {
public void onCreateView(AltosDroid altos_droid);
public void onDestroyView();
public void set_visible(boolean visible);
public void center(double lat, double lon, double accuracy);
public void show(TelemetryState telem_state, AltosState state, AltosGreatCircle from_receiver, Location receiver);
}

View File

@@ -0,0 +1,23 @@
/*
* Copyright © 2016 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;
public interface AltosDroidMapSourceListener {
public void map_source_changed(int map_source);
}

View File

@@ -0,0 +1,186 @@
/*
* Copyright © 2014 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.content.Context;
import org.altusmetrum.altoslib_14.*;
public class AltosDroidPreferences extends AltosPreferences {
/* Active device preference name */
final static String activeDeviceAddressPreference = "ACTIVE-DEVICE-ADDRESS";
final static String activeDeviceNamePreference = "ACTIVE-DEVICE-NAME";
public static final int font_size_small = 0;
public static final int font_size_medium = 1;
public static final int font_size_large = 2;
public static final int font_size_extra = 3;
final static String fontSizePreference = "FONT-SIZE";
static int font_size = font_size_medium;
static DeviceAddress active_device_address;
/* Map source preference name */
final static String mapSourcePreference = "MAP-SOURCE";
static final int MAP_SOURCE_OFFLINE = 0;
static final int MAP_SOURCE_ONLINE = 1;
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;
AltosPreferences.init(new AltosDroidPreferencesBackend(context));
font_size = backend.getInt(fontSizePreference, font_size_medium);
String address = backend.getString(activeDeviceAddressPreference, null);
String name = backend.getString(activeDeviceNamePreference, null);
if (address != null && name != null)
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) {
if (backend == null)
return;
synchronized(backend) {
active_device_address = address;
if (active_device_address != null) {
backend.putString(activeDeviceAddressPreference, active_device_address.address);
backend.putString(activeDeviceNamePreference, active_device_address.name);
} else {
backend.remove(activeDeviceAddressPreference);
backend.remove(activeDeviceNamePreference);
}
flush_preferences();
}
}
public static DeviceAddress active_device() {
if (backend == null)
return null;
synchronized(backend) {
return active_device_address;
}
}
static LinkedList<AltosDroidMapSourceListener> map_source_listeners;
public static void set_map_source(int map_source) {
if (backend == null)
return;
synchronized(backend) {
AltosDroidPreferences.map_source = map_source;
backend.putInt(mapSourcePreference, map_source);
flush_preferences();
}
if (map_source_listeners != null) {
for (AltosDroidMapSourceListener l : map_source_listeners) {
l.map_source_changed(map_source);
}
}
}
public static int map_source() {
if (backend == null)
return MAP_SOURCE_ONLINE;
synchronized(backend) {
return map_source;
}
}
public static void register_map_source_listener(AltosDroidMapSourceListener l) {
if (backend == null)
return;
synchronized(backend) {
if (map_source_listeners == null)
map_source_listeners = new LinkedList<AltosDroidMapSourceListener>();
map_source_listeners.add(l);
}
}
public static void unregister_map_source_listener(AltosDroidMapSourceListener l) {
if (backend == null)
return;
synchronized(backend) {
map_source_listeners.remove(l);
}
}
public static int font_size() {
if (backend == null)
return font_size_medium;
synchronized (backend) {
return font_size;
}
}
public static void set_font_size(int new_font_size) {
if (backend == null)
return;
synchronized (backend) {
if (font_size != new_font_size) {
font_size = new_font_size;
backend.putInt(fontSizePreference, font_size);
flush_preferences();
}
}
}
public static int tracker_sort() {
if (backend == null)
return 0;
synchronized(backend) {
return tracker_sort;
}
}
public static void set_tracker_sort(int new_tracker_sort) {
if (backend == null)
return;
synchronized(backend) {
if (tracker_sort != new_tracker_sort) {
tracker_sort = new_tracker_sort;
backend.putInt(trackerSortPreference, tracker_sort);
flush_preferences();
}
}
}
}

View File

@@ -0,0 +1,147 @@
/*
* Copyright © 2012 Mike Beattie <mike@ethernal.org>
*
* 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.io.File;
import java.util.Map;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Environment;
import android.util.*;
import org.altusmetrum.altoslib_14.*;
public class AltosDroidPreferencesBackend extends AltosPreferencesBackend {
public final static String NAME = "org.altusmetrum.AltosDroid";
private Context context = null;
private SharedPreferences prefs = null;
private SharedPreferences.Editor editor = null;
public AltosDroidPreferencesBackend(Context in_context) {
this(in_context, NAME);
}
public AltosDroidPreferencesBackend(Context in_context, String in_prefs) {
context = in_context;
prefs = context.getSharedPreferences(in_prefs, 0);
editor = prefs.edit();
}
public String[] keys() {
Map<String, ?> all = prefs.getAll();
Object[] ao = all.keySet().toArray();
String[] as = new String[ao.length];
for (int i = 0; i < ao.length; i++)
as[i] = (String) ao[i];
return as;
}
public AltosPreferencesBackend node(String key) {
if (!nodeExists(key))
putBoolean(key, true);
return new AltosDroidPreferencesBackend(context, key);
}
public boolean nodeExists(String key) {
return prefs.contains(key);
}
public boolean getBoolean(String key, boolean def) {
return prefs.getBoolean(key, def);
}
public double getDouble(String key, double def) {
Float f = Float.valueOf(prefs.getFloat(key, (float)def));
return f.doubleValue();
}
public int getInt(String key, int def) {
return prefs.getInt(key, def);
}
public String getString(String key, String def) {
String ret;
if (key.equals(AltosPreferences.logdirPreference))
ret = null;
else
ret = prefs.getString(key, def);
AltosDebug.debug("AltosDroidPreferencesBackend get string %s:\n", key);
if (ret == null)
AltosDebug.debug(" (null)\n");
else {
String[] lines = ret.split("\n");
for (String l : lines)
AltosDebug.debug(" %s\n", l);
}
return ret;
}
public byte[] getBytes(String key, byte[] def) {
String save = prefs.getString(key, null);
if (save == null)
return def;
byte[] bytes = Base64.decode(save, Base64.DEFAULT);
return bytes;
}
public void putBoolean(String key, boolean value) {
editor.putBoolean(key, value);
}
public void putDouble(String key, double value) {
editor.putFloat(key, (float)value);
}
public void putInt(String key, int value) {
editor.putInt(key, value);
}
public void putString(String key, String value) {
// AltosDebug.debug("AltosDroidPreferencesBackend put string %s:\n", key);
// String[] lines = value.split("\n");
// for (String l : lines)
// AltosDebug.debug(" %s\n", l);
editor.putString(key, value);
}
public void putBytes(String key, byte[] bytes) {
String save = Base64.encodeToString(bytes, Base64.DEFAULT);
editor.putString(key, save);
}
public void remove(String key) {
AltosDebug.debug("remove preference %s\n", key);
editor.remove(key);
}
public void flush() {
editor.apply();
}
public File homeDirectory() {
return context.getExternalMediaDirs()[0];
}
public void debug(String format, Object ... arguments) {
AltosDebug.debug(format, arguments);
}
}

View File

@@ -0,0 +1,105 @@
/*
* Copyright © 2013 Mike Beattie <mike@ethernal.org>
*
* 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 org.altusmetrum.altoslib_14.*;
import android.location.Location;
import android.app.Activity;
import android.content.*;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentTransaction;
import android.widget.TextView;
public abstract class AltosDroidTab extends Fragment implements AltosUnitsListener {
TelemetryState last_telem_state;
AltosState last_state;
AltosGreatCircle last_from_receiver;
Location last_receiver;
AltosDroid altos_droid;
public abstract void show(TelemetryState telem_state, AltosState state, AltosGreatCircle from_receiver, Location receiver);
public abstract String tab_name();
public void units_changed(boolean imperial_units) {
if (!isHidden())
show(last_telem_state, last_state, last_from_receiver, last_receiver);
}
public void set_value(TextView text_view,
AltosUnits units,
int width,
double value) {
if (value == AltosLib.MISSING)
text_view.setText("");
else
text_view.setText(units.show(width, value));
}
public void set_visible(boolean visible) {
FragmentTransaction ft = AltosDroid.fm.beginTransaction();
AltosDebug.debug("set visible %b %s\n", visible, tab_name());
if (visible) {
ft.show(this);
show(last_telem_state, last_state, last_from_receiver, last_receiver);
} else
ft.hide(this);
try {
ft.commitAllowingStateLoss();
} catch (IllegalStateException ie) {
}
}
@Override
public void onAttach(Context context) {
AltosDebug.debug("tab onAttach %s %s\n", tab_name(), this);
super.onAttach(context);
altos_droid = (AltosDroid) context;
altos_droid.registerTab(this);
}
@Override
public void onDetach() {
AltosDebug.debug("tab onDetach %s %s\n", tab_name(), this);
super.onDetach();
altos_droid.unregisterTab(this);
altos_droid = null;
}
@Override
public void onResume() {
super.onResume();
AltosDebug.debug("onResume tab %s %s\n", tab_name(), this);
set_visible(true);
}
public void update_ui(TelemetryState telem_state, AltosState state,
AltosGreatCircle from_receiver, Location receiver, boolean is_current)
{
AltosDebug.debug("update_ui %s is_current %b\n", tab_name(), is_current);
last_telem_state = telem_state;
last_state = state;
last_from_receiver = from_receiver;
last_receiver = receiver;
if (is_current)
show(telem_state, state, from_receiver, receiver);
else
return;
}
}

View File

@@ -0,0 +1,527 @@
/*
* Copyright © 2015 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 java.io.*;
import org.altusmetrum.altoslib_14.*;
import android.graphics.*;
import android.view.*;
import android.location.Location;
import android.content.*;
import android.util.*;
class Rocket implements Comparable {
AltosLatLon position;
String name;
int serial;
long last_packet;
boolean active;
AltosMapOffline map_offline;
void paint() {
map_offline.draw_bitmap(position, map_offline.rocket_bitmap, map_offline.rocket_off_x, map_offline.rocket_off_y);
map_offline.draw_text(position, name, 0, 3*map_offline.rocket_bitmap.getHeight()/4);
}
void set_position(AltosLatLon position, long last_packet) {
this.position = position;
this.last_packet = last_packet;
}
void set_active(boolean active) {
this.active = active;
}
public int compareTo(Object o) {
Rocket other = (Rocket) o;
if (active && !other.active)
return 1;
if (other.active && !active)
return -1;
long diff = last_packet - other.last_packet;
if (diff > 0)
return 1;
if (diff < 0)
return -1;
return 0;
}
Rocket(int serial, AltosMapOffline map_offline) {
this.serial = serial;
this.name = String.format("%d", serial);
this.map_offline = map_offline;
}
}
public class AltosMapOffline extends View implements ScaleGestureDetector.OnScaleGestureListener, AltosMapInterface, AltosDroidMapInterface, AltosMapTypeListener {
ScaleGestureDetector scale_detector;
boolean scaling;
AltosMap map;
AltosDroid altos_droid;
static int scale = 1;
AltosLatLon here;
AltosLatLon there;
AltosLatLon pad;
Canvas canvas;
Paint paint;
Bitmap pad_bitmap;
int pad_off_x, pad_off_y;
Bitmap rocket_bitmap;
int rocket_off_x, rocket_off_y;
Bitmap here_bitmap;
int here_off_x, here_off_y;
static final int WHITE = 0xffffffff;
static final int RED = 0xffff0000;
static final int PINK = 0xffff8080;
static final int YELLOW= 0xffffff00;
static final int CYAN = 0xff00ffff;
static final int BLUE = 0xff0000ff;
static final int BLACK = 0xff000000;
public static final int stateColors[] = {
WHITE, // startup
WHITE, // idle
WHITE, // pad
RED, // boost
PINK, // fast
YELLOW, // coast
CYAN, // drogue
BLUE, // main
BLACK, // landed
BLACK, // invalid
CYAN, // stateless
};
/* AltosMapInterface */
public void debug(String format, Object ... arguments) {
AltosDebug.debug(format, arguments);
}
class MapTile extends AltosMapTile {
public void paint(AltosMapTransform t) {
AltosPointInt pt = new AltosPointInt(t.screen(upper_left));
if (canvas.quickReject(pt.x, pt.y, pt.x + px_size, pt.y + px_size, Canvas.EdgeType.AA))
return;
AltosImage altos_image = this.get_image();
MapImage map_image = (MapImage) altos_image;
Bitmap bitmap = null;
if (map_image != null)
bitmap = map_image.bitmap;
if (bitmap != null) {
canvas.drawBitmap(bitmap, pt.x, pt.y, paint);
} else {
paint.setColor(0xff808080);
canvas.drawRect(pt.x, pt.y, pt.x + px_size, pt.y + px_size, paint);
if (t.has_location()) {
String message = null;
switch (status) {
case AltosMapTile.fetching:
message = "Fetching...";
break;
case AltosMapTile.bad_request:
message = "Internal error";
break;
case AltosMapTile.failed:
message = "Network error";
break;
case AltosMapTile.forbidden:
message = "Outside of known launch areas";
break;
}
if (message != null) {
Rect bounds = new Rect();
paint.getTextBounds(message, 0, message.length(), bounds);
int width = bounds.right - bounds.left;
int height = bounds.bottom - bounds.top;
float x = pt.x + px_size / 2.0f;
float y = pt.y + px_size / 2.0f;
x = x - width / 2.0f;
y = y + height / 2.0f;
paint.setColor(0xff000000);
canvas.drawText(message, 0, message.length(), x, y, paint);
}
}
}
}
public MapTile(AltosMapCache cache, AltosLatLon upper_left, AltosLatLon center, int zoom, int maptype, int px_size, int scale) {
super(cache, upper_left, center, zoom, maptype, px_size, scale);
}
}
public AltosMapTile new_tile(AltosMapCache cache, AltosLatLon upper_left, AltosLatLon center, int zoom, int maptype, int px_size, int scale) {
return new MapTile(cache, upper_left, center, zoom, maptype, px_size, scale);
}
public AltosMapPath new_path() {
return null;
}
public AltosMapLine new_line() {
return null;
}
class MapImage implements AltosImage {
public Bitmap bitmap;
public void flush() {
if (bitmap != null) {
bitmap.recycle();
bitmap = null;
}
}
public MapImage(File file) {
bitmap = BitmapFactory.decodeFile(file.getPath());
}
}
public AltosImage load_image(File file) throws Exception {
return new MapImage(file);
}
class MapMark extends AltosMapMark {
public void paint(AltosMapTransform t) {
}
MapMark(double lat, double lon, int state) {
super(lat, lon, state);
}
MapMark(double lat, double lon, int state, String label) {
super(lat, lon, state, label);
}
}
public AltosMapMark new_mark(double lat, double lon, int state) {
return new MapMark(lat, lon, state);
}
public AltosMapMark new_mark(double lat, double lon, int state, String label) {
return new MapMark(lat, lon, state, label);
}
public int width() {
return getWidth();
}
public int height() {
return getHeight();
}
public void repaint() {
postInvalidate();
}
public void repaint(AltosRectangle damage) {
postInvalidate(damage.x, damage.y, damage.x + damage.width, damage.y + damage.height);
}
public void set_zoom_label(String label) {
}
public void select_object(AltosLatLon latlon) {
if (map.transform == null)
return;
ArrayList<Integer> near = new ArrayList<Integer>();
for (Rocket rocket : sorted_rockets()) {
if (rocket.position == null) {
debug("rocket %d has no position\n", rocket.serial);
continue;
}
double distance = map.transform.hypot(latlon, rocket.position);
debug("check select %d distance %g width %d\n", rocket.serial, distance, rocket_bitmap.getWidth());
if (distance < rocket_bitmap.getWidth() * 2.0) {
debug("selecting %d\n", rocket.serial);
near.add(rocket.serial);
}
}
if (near.size() != 0)
altos_droid.touch_trackers(near.toArray(new Integer[0]));
}
class Line {
AltosLatLon a, b;
void paint() {
if (a != null && b != null) {
AltosPointDouble a_screen = map.transform.screen(a);
AltosPointDouble b_screen = map.transform.screen(b);
paint.setColor(0xff8080ff);
canvas.drawLine((float) a_screen.x, (float) a_screen.y,
(float) b_screen.x, (float) b_screen.y,
paint);
}
}
void set_a(AltosLatLon a) {
this.a = a;
}
void set_b(AltosLatLon b) {
this.b = b;
}
Line() {
}
}
Line line = new Line();
int stroke_width = 20;
void draw_text(AltosLatLon lat_lon, String text, int off_x, int off_y) {
if (lat_lon != null && map != null && map.transform != null) {
AltosPointInt pt = new AltosPointInt(map.transform.screen(lat_lon));
Rect bounds = new Rect();
paint.getTextBounds(text, 0, text.length(), bounds);
int width = bounds.right - bounds.left;
int height = bounds.bottom - bounds.top;
float x = pt.x;
float y = pt.y;
x = x - width / 2.0f - off_x;
y = y + height / 2.0f - off_y;
paint.setColor(0xff000000);
canvas.drawText(text, 0, text.length(), x, y, paint);
}
}
HashMap<Integer,Rocket> rockets = new HashMap<Integer,Rocket>();
void draw_bitmap(AltosLatLon lat_lon, Bitmap bitmap, int off_x, int off_y) {
if (lat_lon != null && map != null && map.transform != null) {
AltosPointInt pt = new AltosPointInt(map.transform.screen(lat_lon));
canvas.drawBitmap(bitmap, pt.x - off_x, pt.y - off_y, paint);
}
}
private Rocket[] sorted_rockets() {
Rocket[] rocket_array = rockets.values().toArray(new Rocket[0]);
Arrays.sort(rocket_array);
return rocket_array;
}
private void draw_positions() {
line.set_a(there);
line.set_b(here);
line.paint();
draw_bitmap(pad, pad_bitmap, pad_off_x, pad_off_y);
for (Rocket rocket : sorted_rockets())
rocket.paint();
draw_bitmap(here, here_bitmap, here_off_x, here_off_y);
}
@Override
protected void onDraw(Canvas view_canvas) {
if (map == null) {
debug("MapView draw without map\n");
return;
}
if (map.transform == null) {
debug("MapView draw without transform\n");
return;
}
canvas = view_canvas;
paint = new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setStrokeWidth(stroke_width);
paint.setStrokeCap(Paint.Cap.ROUND);
paint.setStrokeJoin(Paint.Join.ROUND);
paint.setTextSize(40);
map.paint();
draw_positions();
canvas = null;
}
public boolean onScale(ScaleGestureDetector detector) {
float f = detector.getScaleFactor();
if (f <= 0.8) {
map.set_zoom_centre(map.get_zoom() - 1, new AltosPointInt((int) detector.getFocusX(), (int) detector.getFocusY()));
return true;
}
if (f >= 1.2) {
map.set_zoom_centre(map.get_zoom() + 1, new AltosPointInt((int) detector.getFocusX(), (int) detector.getFocusY()));
return true;
}
return false;
}
public boolean onScaleBegin(ScaleGestureDetector detector) {
return true;
}
public void onScaleEnd(ScaleGestureDetector detector) {
}
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
scale_detector.onTouchEvent(event);
if (scale_detector.isInProgress()) {
scaling = true;
}
if (scaling) {
if (event.getAction() == MotionEvent.ACTION_UP) {
scaling = false;
}
return true;
}
if (event.getAction() == MotionEvent.ACTION_DOWN) {
map.touch_start((int) event.getX(), (int) event.getY(), true);
} else if (event.getAction() == MotionEvent.ACTION_MOVE) {
map.touch_continue((int) event.getX(), (int) event.getY(), true);
} else if (event.getAction() == MotionEvent.ACTION_UP) {
map.touch_stop((int) event.getX(), (int) event.getY(), true);
}
return true;
}
double mapAccuracy;
public void center(double lat, double lon, double accuracy) {
if (mapAccuracy <= 0 || accuracy < mapAccuracy/10 || (map != null && !map.has_centre())) {
if (map != null)
map.maybe_centre(lat, lon);
mapAccuracy = accuracy;
}
}
public void set_visible(boolean visible) {
if (visible)
setVisibility(VISIBLE);
else
setVisibility(GONE);
}
public void show(TelemetryState telem_state, AltosState state, AltosGreatCircle from_receiver, Location receiver) {
boolean changed = false;
if (state != null) {
map.show(state, null);
if (state.pad_lat != AltosLib.MISSING && pad == null)
pad = new AltosLatLon(state.pad_lat, state.pad_lon);
}
if (telem_state != null) {
Integer[] old_serial = rockets.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.containsKey(serial))
rockets.remove(serial);
}
/* set remaining keys */
for (int serial : new_serial) {
Rocket rocket;
AltosState t_state = telem_state.get(serial);
if (rockets.containsKey(serial))
rocket = rockets.get(serial);
else {
rocket = new Rocket(serial, this);
rockets.put(serial, rocket);
}
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 != null && state.cal_data().serial == serial)
there = latlon;
}
if (state != null)
rocket.set_active(state.cal_data().serial == serial);
}
}
if (receiver != null) {
AltosLatLon new_here = new AltosLatLon(receiver.getLatitude(), receiver.getLongitude());
if (!new_here.equals(here)) {
here = new_here;
AltosDebug.debug("Location changed, redraw");
repaint();
}
}
}
public void onCreateView(AltosDroid altos_droid) {
this.altos_droid = altos_droid;
map = new AltosMap(this, scale);
AltosPreferences.register_map_type_listener(this);
map.set_maptype(AltosPreferences.map_type());
pad_bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.pad);
/* arrow at the bottom of the launchpad image */
pad_off_x = pad_bitmap.getWidth() / 2;
pad_off_y = pad_bitmap.getHeight();
rocket_bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.rocket);
/* arrow at the bottom of the rocket image */
rocket_off_x = rocket_bitmap.getWidth() / 2;
rocket_off_y = rocket_bitmap.getHeight();
here_bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.ic_maps_indicator_current_position);
/* Center of the dot */
here_off_x = here_bitmap.getWidth() / 2;
here_off_y = here_bitmap.getHeight() / 2;
}
public void onDestroyView() {
AltosPreferences.unregister_map_type_listener(this);
}
public void map_type_changed(int map_type) {
if (map != null)
map.set_maptype(map_type);
}
public AltosMapOffline(Context context, AttributeSet attrs) {
super(context, attrs);
this.altos_droid = altos_droid;
scale_detector = new ScaleGestureDetector(context, this);
}
}

View File

@@ -0,0 +1,358 @@
/*
* Copyright © 2013 Mike Beattie <mike@ethernal.org>
*
* 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 org.altusmetrum.altoslib_14.*;
import com.google.android.gms.maps.*;
import com.google.android.gms.maps.model.*;
import android.graphics.Color;
import android.graphics.*;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.location.Location;
import android.content.*;
class RocketOnline implements Comparable {
Marker marker;
int serial;
long last_packet;
int size;
void set_position(AltosLatLon position, long last_packet) {
marker.setPosition(new LatLng(position.lat, position.lon));
this.last_packet = last_packet;
}
private Bitmap rocket_bitmap(Context context, String text) {
/* From: http://mapicons.nicolasmollet.com/markers/industry/military/missile-2/
*/
Bitmap orig_bitmap = BitmapFactory.decodeResource(context.getResources(), R.drawable.rocket);
Bitmap bitmap = orig_bitmap.copy(Bitmap.Config.ARGB_8888, true);
Canvas canvas = new Canvas(bitmap);
Paint paint = new Paint();
paint.setTextSize(40);
paint.setColor(0xff000000);
Rect bounds = new Rect();
paint.getTextBounds(text, 0, text.length(), bounds);
int width = bounds.right - bounds.left;
int height = bounds.bottom - bounds.top;
float x = bitmap.getWidth() / 2.0f - width / 2.0f;
float y = bitmap.getHeight() / 2.0f - height / 2.0f;
size = bitmap.getWidth();
canvas.drawText(text, 0, text.length(), x, y, paint);
return bitmap;
}
public void remove() {
marker.remove();
}
public int compareTo(Object o) {
RocketOnline other = (RocketOnline) o;
long diff = last_packet - other.last_packet;
if (diff > 0)
return 1;
if (diff < 0)
return -1;
return 0;
}
RocketOnline(Context context, int serial, GoogleMap map, double lat, double lon, long last_packet) {
this.serial = serial;
String name = String.format("%d", serial);
this.marker = map.addMarker(new MarkerOptions()
.icon(BitmapDescriptorFactory.fromBitmap(rocket_bitmap(context, name)))
.position(new LatLng(lat, lon))
.visible(true));
this.last_packet = last_packet;
}
}
public class AltosMapOnline implements AltosDroidMapInterface, GoogleMap.OnMarkerClickListener, GoogleMap.OnMapClickListener, OnMapReadyCallback, AltosMapTypeListener {
public AltosOnlineMapFragment mMapFragment;
private GoogleMap mMap;
private boolean mapLoaded = false;
Context context;
private HashMap<Integer,RocketOnline> rockets = new HashMap<Integer,RocketOnline>();
private Marker mPadMarker;
private boolean pad_set;
private Polyline mPolyline;
private double mapAccuracy = -1;
private AltosLatLon my_position = null;
private AltosLatLon target_position = null;
private AltosDroid altos_droid;
public static class AltosOnlineMapFragment extends SupportMapFragment {
AltosMapOnline c;
View map_view;
public AltosOnlineMapFragment(AltosMapOnline c) {
this.c = c;
}
public AltosOnlineMapFragment() {
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
if (c != null)
getMapAsync(c);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
map_view = super.onCreateView(inflater, container, savedInstanceState);
return map_view;
}
@Override
public void onDestroyView() {
super.onDestroyView();
map_view = null;
}
public void set_visible(boolean visible) {
if (map_view == null)
return;
if (visible)
map_view.setVisibility(View.VISIBLE);
else
map_view.setVisibility(View.GONE);
}
}
public void onCreateView(AltosDroid altos_droid) {
this.altos_droid = altos_droid;
AltosPreferences.register_map_type_listener(this);
mMapFragment = new AltosOnlineMapFragment(this);
}
public void onDestroyView() {
AltosPreferences.unregister_map_type_listener(this);
}
private double pixel_distance(LatLng a, LatLng b) {
Projection projection = mMap.getProjection();
Point a_pt = projection.toScreenLocation(a);
Point b_pt = projection.toScreenLocation(b);
return Math.hypot((double) (a_pt.x - b_pt.x), (double) (a_pt.y - b_pt.y));
}
private RocketOnline[] sorted_rockets() {
synchronized(rockets) {
RocketOnline[] rocket_array = rockets.values().toArray(new RocketOnline[0]);
Arrays.sort(rocket_array);
return rocket_array;
}
}
public void onMapClick(LatLng lat_lng) {
ArrayList<Integer> near = new ArrayList<Integer>();
for (RocketOnline rocket : sorted_rockets()) {
LatLng pos = rocket.marker.getPosition();
if (pos == null)
continue;
double distance = pixel_distance(lat_lng, pos);
if (distance < rocket.size * 2)
near.add(rocket.serial);
}
if (near.size() != 0)
altos_droid.touch_trackers(near.toArray(new Integer[0]));
}
public boolean onMarkerClick(Marker marker) {
onMapClick(marker.getPosition());
return true;
}
void
position_permission() {
if (mMap != null)
mMap.setMyLocationEnabled(true);
}
@Override
public void onMapReady(GoogleMap googleMap) {
final int map_type = AltosPreferences.map_type();
mMap = googleMap;
if (mMap != null) {
map_type_changed(map_type);
if (altos_droid.have_location_permission)
mMap.setMyLocationEnabled(true);
else
altos_droid.tell_map_permission(this);
mMap.getUiSettings().setTiltGesturesEnabled(false);
mMap.getUiSettings().setZoomControlsEnabled(false);
mMap.setOnMarkerClickListener(this);
mMap.setOnMapClickListener(this);
mPadMarker = mMap.addMarker(
new MarkerOptions().icon(BitmapDescriptorFactory.fromResource(R.drawable.pad))
.position(new LatLng(0,0))
.visible(false)
);
mPolyline = mMap.addPolyline(
new PolylineOptions().add(new LatLng(0,0), new LatLng(0,0))
.width(20)
.color(Color.BLUE)
.visible(false)
);
mapLoaded = true;
}
}
public void center(double lat, double lon, double accuracy) {
if (mMap == null)
return;
if (mapAccuracy < 0 || accuracy < mapAccuracy/10) {
mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(new LatLng(lat, lon),14));
mapAccuracy = accuracy;
}
}
private void set_rocket(int serial, AltosState state) {
RocketOnline rocket;
if (state.gps == null || state.gps.lat == AltosLib.MISSING)
return;
if (mMap == null)
return;
synchronized(rockets) {
if (rockets.containsKey(serial)) {
rocket = rockets.get(serial);
rocket.set_position(new AltosLatLon(state.gps.lat, state.gps.lon), state.received_time);
} else {
rocket = new RocketOnline(context,
serial,
mMap, state.gps.lat, state.gps.lon,
state.received_time);
rockets.put(serial, rocket);
}
}
}
private void remove_rocket(int serial) {
synchronized(rockets) {
RocketOnline rocket = rockets.get(serial);
rocket.remove();
rockets.remove(serial);
}
}
public void set_visible(boolean visible) {
if (mMapFragment != null)
mMapFragment.set_visible(visible);
}
public void show(TelemetryState telem_state, AltosState state, AltosGreatCircle from_receiver, Location receiver) {
if (telem_state != null) {
synchronized(rockets) {
for (int serial : rockets.keySet()) {
if (!telem_state.containsKey(serial))
remove_rocket(serial);
}
for (int serial : telem_state.keySet()) {
set_rocket(serial, telem_state.get(serial));
}
}
}
if (state != null) {
if (mapLoaded) {
if (!pad_set && state.pad_lat != AltosLib.MISSING) {
pad_set = true;
mPadMarker.setPosition(new LatLng(state.pad_lat, state.pad_lon));
mPadMarker.setVisible(true);
}
}
if (state.gps != null && state.gps.lat != AltosLib.MISSING) {
target_position = new AltosLatLon(state.gps.lat, state.gps.lon);
if (state.gps.locked && state.gps.nsat >= 4)
center (state.gps.lat, state.gps.lon, 10);
}
}
if (receiver != null) {
double accuracy;
if (receiver.hasAccuracy())
accuracy = receiver.getAccuracy();
else
accuracy = 1000;
my_position = new AltosLatLon(receiver.getLatitude(), receiver.getLongitude());
center (my_position.lat, my_position.lon, accuracy);
}
if (my_position != null && target_position != null && mPolyline != null) {
mPolyline.setPoints(Arrays.asList(new LatLng(my_position.lat, my_position.lon), new LatLng(target_position.lat, target_position.lon)));
mPolyline.setVisible(true);
}
}
public void map_type_changed(int map_type) {
if (mMap != null) {
if (map_type == AltosMap.maptype_hybrid)
mMap.setMapType(GoogleMap.MAP_TYPE_HYBRID);
else if (map_type == AltosMap.maptype_satellite)
mMap.setMapType(GoogleMap.MAP_TYPE_SATELLITE);
else if (map_type == AltosMap.maptype_terrain)
mMap.setMapType(GoogleMap.MAP_TYPE_TERRAIN);
else
mMap.setMapType(GoogleMap.MAP_TYPE_NORMAL);
}
}
public AltosMapOnline(Context context) {
this.context = context;
}
}

View File

@@ -0,0 +1,233 @@
/*
* Copyright © 2015 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.io.InputStream;
import java.io.OutputStream;
import java.util.HashMap;
import android.content.Context;
import android.hardware.usb.*;
import android.app.*;
import android.os.Handler;
import org.altusmetrum.altoslib_14.*;
public class AltosUsb extends AltosDroidLink {
private Thread input_thread = null;
private Handler handler;
private UsbManager manager;
private UsbDevice device;
private UsbDeviceConnection connection;
private UsbInterface iface;
private UsbEndpoint in, out;
private InputStream input;
private OutputStream output;
// Constructor
public AltosUsb(Context context, UsbDevice device, Handler handler) {
super(handler);
// set_debug(D);
this.handler = handler;
iface = null;
in = null;
out = null;
int niface = device.getInterfaceCount();
for (int i = 0; i < niface; i++) {
iface = device.getInterface(i);
in = null;
out = null;
int nendpoints = iface.getEndpointCount();
for (int e = 0; e < nendpoints; e++) {
UsbEndpoint endpoint = iface.getEndpoint(e);
if (endpoint.getType() == UsbConstants.USB_ENDPOINT_XFER_BULK) {
switch (endpoint.getDirection()) {
case UsbConstants.USB_DIR_OUT:
out = endpoint;
break;
case UsbConstants.USB_DIR_IN:
in = endpoint;
break;
}
}
}
if (in != null && out != null)
break;
}
if (in != null && out != null) {
AltosDebug.debug("\tin %s out %s\n", in.toString(), out.toString());
manager = (UsbManager) context.getSystemService(Context.USB_SERVICE);
if (manager == null) {
AltosDebug.debug("USB_SERVICE failed");
return;
}
connection = manager.openDevice(device);
if (connection == null) {
AltosDebug.debug("openDevice failed");
return;
}
connection.claimInterface(iface, true);
input_thread = new Thread(this);
input_thread.start();
// Configure the newly connected device for telemetry
print("~\nE 0\n");
set_monitor(false);
}
}
static private boolean isAltusMetrum(UsbDevice device) {
if (device.getVendorId() != AltosLib.vendor_altusmetrum)
return false;
if (device.getProductId() < AltosLib.product_altusmetrum_min)
return false;
if (device.getProductId() > AltosLib.product_altusmetrum_max)
return false;
return true;
}
static boolean matchProduct(int want_product, UsbDevice device) {
if (!isAltusMetrum(device))
return false;
if (want_product == AltosLib.product_any)
return true;
int have_product = device.getProductId();
if (want_product == AltosLib.product_basestation)
return have_product == AltosLib.product_teledongle ||
have_product == AltosLib.product_telebt ||
have_product == AltosLib.product_megadongle;
if (want_product == AltosLib.product_altimeter)
return have_product == AltosLib.product_telemetrum ||
have_product == AltosLib.product_telemega ||
have_product == AltosLib.product_easymega ||
have_product == AltosLib.product_telegps ||
have_product == AltosLib.product_easymini ||
have_product == AltosLib.product_telemini ||
have_product == AltosLib.product_easytimer;
if (have_product == AltosLib.product_altusmetrum) /* old devices match any request */
return true;
if (want_product == have_product)
return true;
return false;
}
static public boolean request_permission(Context context, UsbDevice device, PendingIntent pi) {
UsbManager manager = (UsbManager) context.getSystemService(Context.USB_SERVICE);
// if (manager.hasPermission(device))
// return true;
AltosDebug.debug("request permission for USB device " + device.toString());
manager.requestPermission(device, pi);
return false;
}
static public UsbDevice find_device(Context context, int match_product) {
UsbManager manager = (UsbManager) context.getSystemService(Context.USB_SERVICE);
HashMap<String,UsbDevice> devices = manager.getDeviceList();
for (UsbDevice device : devices.values()) {
int vendor = device.getVendorId();
int product = device.getProductId();
if (matchProduct(match_product, device)) {
AltosDebug.debug("found USB device " + device.toString());
return device;
}
}
return null;
}
private void disconnected() {
if (closed()) {
AltosDebug.debug("disconnected after closed");
return;
}
AltosDebug.debug("Sending disconnected message");
handler.obtainMessage(TelemetryService.MSG_DISCONNECTED, this).sendToTarget();
}
void close_device() {
UsbDeviceConnection tmp_connection;
synchronized(this) {
tmp_connection = connection;
connection = null;
}
if (tmp_connection != null) {
AltosDebug.debug("Closing USB device");
tmp_connection.close();
}
}
int read(byte[] buffer, int len) {
if (connection == null)
return 0;
int ret = connection.bulkTransfer(in, buffer, len, -1);
AltosDebug.debug("read(%d) = %d\n", len, ret);
return ret;
}
int write(byte[] buffer, int len) {
if (connection == null)
return 0;
int ret = connection.bulkTransfer(out, buffer, len, -1);
AltosDebug.debug("write(%d) = %d\n", len, ret);
return ret;
}
// Stubs of required methods when extending AltosLink
public boolean can_cancel_reply() { return false; }
public boolean show_reply_timeout() { return true; }
public void hide_reply_timeout() { }
}

View File

@@ -0,0 +1,53 @@
/*
* Copyright © 2013 Mike Beattie <mike@ethernal.org>
*
* 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 android.content.Context;
import androidx.viewpager.widget.ViewPager;
import android.util.AttributeSet;
import android.view.View;
public class AltosViewPager extends ViewPager {
public AltosViewPager(Context context) {
super(context);
}
public AltosViewPager(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
protected boolean canScroll(View v, boolean checkV, int dx, int x, int y) {
if (v.getClass() != null &&
v.getClass().getName() != null &&
v.getClass().getName().endsWith("MapOffline"))
return true;
if(v.getClass() != null &&
v.getClass().getPackage() != null &&
v.getClass().getPackage().getName() != null &&
v.getClass().getPackage().getName().startsWith("maps."))
return true;
return super.canScroll(v, checkV, dx, x, y);
}
}

View File

@@ -0,0 +1,329 @@
/*
* Copyright © 2011 Keith Packard <keithp@keithp.com>
* Copyright © 2012 Mike Beattie <mike@ethernal.org>
*
* 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 android.speech.tts.TextToSpeech;
import android.speech.tts.TextToSpeech.OnInitListener;
import android.location.Location;
import org.altusmetrum.altoslib_14.*;
public class AltosVoice {
private TextToSpeech tts = null;
private boolean tts_enabled = false;
static final int TELL_MODE_NONE = 0;
static final int TELL_MODE_PAD = 1;
static final int TELL_MODE_FLIGHT = 2;
static final int TELL_MODE_RECOVER = 3;
static final int TELL_FLIGHT_NONE = 0;
static final int TELL_FLIGHT_STATE = 1;
static final int TELL_FLIGHT_SPEED = 2;
static final int TELL_FLIGHT_HEIGHT = 3;
static final int TELL_FLIGHT_TRACK = 4;
private int last_tell_mode;
private int last_tell_serial = AltosLib.MISSING;
private int last_state;
private AltosGPS last_gps;
private double last_height = AltosLib.MISSING;
private Location last_receiver;
private long last_speak_time;
private int last_flight_tell = TELL_FLIGHT_NONE;
private boolean quiet = false;
private long now() {
return System.currentTimeMillis();
}
private void reset_last() {
last_tell_mode = TELL_MODE_NONE;
last_speak_time = now() - 100 * 1000;
last_gps = null;
last_height = AltosLib.MISSING;
last_receiver = null;
last_state = AltosLib.ao_flight_invalid;
last_flight_tell = TELL_FLIGHT_NONE;
}
public AltosVoice(AltosDroid a) {
tts = new TextToSpeech(a, new OnInitListener() {
public void onInit(int status) {
if (status == TextToSpeech.SUCCESS) tts_enabled = true;
}
});
reset_last();
}
public synchronized void set_enable(boolean enable) {
tts_enabled = enable;
}
public synchronized void speak(String s) {
if (!tts_enabled) return;
last_speak_time = now();
if (!quiet)
tts.speak(s, TextToSpeech.QUEUE_ADD, null, null);
}
public synchronized long time_since_speak() {
return now() - last_speak_time;
}
public synchronized void speak(String format, Object ... arguments) {
speak(String.format(format, arguments));
}
public synchronized boolean is_speaking() {
return tts.isSpeaking();
}
public void stop() {
if (tts != null) {
tts.stop();
tts.shutdown();
}
}
private boolean last_apogee_good;
private boolean last_main_good;
private boolean last_gps_good;
private boolean tell_gonogo(String name,
boolean current,
boolean previous,
boolean new_mode) {
if (current != previous || new_mode)
speak("%s %s.", name, current ? "ready" : "not ready");
return current;
}
private boolean tell_pad(TelemetryState telem_state, AltosState state,
AltosGreatCircle from_receiver, Location receiver) {
if (state == null)
return false;
if (state.apogee_voltage != AltosLib.MISSING)
last_apogee_good = tell_gonogo("apogee",
state.apogee_voltage >= AltosLib.ao_igniter_good,
last_apogee_good,
last_tell_mode != TELL_MODE_PAD);
if (state.main_voltage != AltosLib.MISSING)
last_main_good = tell_gonogo("main",
state.main_voltage >= AltosLib.ao_igniter_good,
last_main_good,
last_tell_mode != TELL_MODE_PAD);
if (state.gps != null)
last_gps_good = tell_gonogo("G P S",
state.gps_ready,
last_gps_good,
last_tell_mode != TELL_MODE_PAD);
return true;
}
private boolean descending(int state) {
return AltosLib.ao_flight_drogue <= state && state <= AltosLib.ao_flight_landed;
}
private boolean target_moved(AltosState state) {
if (last_gps != null && state != null && state.gps != null) {
AltosGreatCircle moved = new AltosGreatCircle(last_gps.lat, last_gps.lon, last_gps.alt,
state.gps.lat, state.gps.lon, state.gps.alt);
double height_change = 0;
double height = state.height();
if (height != AltosLib.MISSING && last_height != AltosLib.MISSING)
height_change = Math.abs(last_height - height);
if (moved.range < 10 && height_change < 10)
return false;
}
return true;
}
private boolean receiver_moved(Location receiver) {
if (last_receiver != null && receiver != null) {
AltosGreatCircle moved = new AltosGreatCircle(last_receiver.getLatitude(),
last_receiver.getLongitude(),
last_receiver.getAltitude(),
receiver.getLatitude(),
receiver.getLongitude(),
receiver.getAltitude());
if (moved.range < 10)
return false;
}
return true;
}
private boolean tell_flight(TelemetryState telem_state, AltosState state,
AltosGreatCircle from_receiver, Location receiver) {
boolean spoken = false;
if (state == null)
return false;
if (last_tell_mode != TELL_MODE_FLIGHT)
last_flight_tell = TELL_FLIGHT_NONE;
if (state.state() != last_state && AltosLib.ao_flight_boost <= state.state() && state.state() <= AltosLib.ao_flight_landed) {
speak(state.state_name());
if (descending(state.state()) && !descending(last_state)) {
if (state.max_height() != AltosLib.MISSING) {
speak("max height: %s.",
AltosConvert.height.say_units(state.max_height()));
}
}
last_flight_tell = TELL_FLIGHT_STATE;
return true;
}
if (last_tell_mode == TELL_MODE_FLIGHT && last_flight_tell == TELL_FLIGHT_TRACK) {
if (time_since_speak() < 10 * 1000)
return false;
if (!target_moved(state) && !receiver_moved(receiver))
return false;
}
double speed;
double height;
if (last_flight_tell == TELL_FLIGHT_NONE || last_flight_tell == TELL_FLIGHT_STATE || last_flight_tell == TELL_FLIGHT_TRACK) {
last_flight_tell = TELL_FLIGHT_SPEED;
if (state.state() <= AltosLib.ao_flight_coast) {
speed = state.speed();
} else {
speed = state.gps_speed();
if (speed == AltosLib.MISSING)
speed = state.speed();
}
if (speed != AltosLib.MISSING) {
speak("speed: %s.", AltosConvert.speed.say_units(speed));
return true;
}
}
if (last_flight_tell == TELL_FLIGHT_SPEED) {
last_flight_tell = TELL_FLIGHT_HEIGHT;
height = state.height();
if (height != AltosLib.MISSING) {
speak("height: %s.", AltosConvert.height.say_units(height));
return true;
}
}
if (last_flight_tell == TELL_FLIGHT_HEIGHT) {
last_flight_tell = TELL_FLIGHT_TRACK;
if (from_receiver != null) {
speak("bearing %s %d, elevation %d, distance %s.",
from_receiver.bearing_words(
AltosGreatCircle.BEARING_VOICE),
(int) (from_receiver.bearing + 0.5),
(int) (from_receiver.elevation + 0.5),
AltosConvert.distance.say(from_receiver.distance));
return true;
}
}
return spoken;
}
private boolean tell_recover(TelemetryState telem_state, AltosState state,
AltosGreatCircle from_receiver, Location receiver) {
if (from_receiver == null)
return false;
if (last_tell_mode == TELL_MODE_RECOVER) {
if (!target_moved(state) && !receiver_moved(receiver))
return false;
if (time_since_speak() <= 10 * 1000)
return false;
}
String direction = AltosDroid.direction(from_receiver, receiver);
if (direction == null)
direction = String.format("Bearing %d", (int) (from_receiver.bearing + 0.5));
speak("%s, distance %s.", direction,
AltosConvert.distance.say_units(from_receiver.distance));
return true;
}
public void tell(TelemetryState telem_state, AltosState state,
AltosGreatCircle from_receiver, Location receiver,
AltosDroidTab tab, boolean quiet) {
this.quiet = quiet;
boolean spoken = false;
if (!tts_enabled) return;
if (is_speaking()) return;
int tell_serial = last_tell_serial;
if (state != null)
tell_serial = state.cal_data().serial;
if (tell_serial != last_tell_serial)
reset_last();
int tell_mode = TELL_MODE_NONE;
if (tab.tab_name().equals(AltosDroid.tab_pad_name))
tell_mode = TELL_MODE_PAD;
else if (tab.tab_name().equals(AltosDroid.tab_flight_name))
tell_mode = TELL_MODE_FLIGHT;
else
tell_mode = TELL_MODE_RECOVER;
if (tell_mode == TELL_MODE_PAD)
spoken = tell_pad(telem_state, state, from_receiver, receiver);
else if (tell_mode == TELL_MODE_FLIGHT)
spoken = tell_flight(telem_state, state, from_receiver, receiver);
else
spoken = tell_recover(telem_state, state, from_receiver, receiver);
if (spoken) {
last_tell_mode = tell_mode;
last_tell_serial = tell_serial;
if (state != null) {
last_state = state.state();
last_height = state.height();
if (state.gps != null)
last_gps = state.gps;
}
if (receiver != null)
last_receiver = receiver;
}
}
}

View File

@@ -0,0 +1,31 @@
/*
* Copyright © 2012 Mike Beattie <mike@ethernal.org>
*
* 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;
public class BuildInfo {
public static final String version = "@VERSION@";
public static final String git_describe = "@DESCRIBE@";
public static final String branch = "@BRANCH@";
public static final String commitnum = "@COMMITNUM@";
public static final String commithash = "@COMMITHASH@";
public static final String builddate = "@BUILDDATE@";
public static final String buildtime = "@BUILDTIME@";
public static final String buildtz = "@BUILDTZ@";
}

View File

@@ -0,0 +1,29 @@
/*
* Copyright © 2015 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;
public class DeviceAddress {
public String address;
public String name;
public DeviceAddress(String address, String name) {
this.address = address;
this.name = name;
}
}

View File

@@ -0,0 +1,224 @@
/*
* Copyright (C) 2009 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.altusmetrum.AltosDroid;
import java.util.Set;
import android.app.Activity;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.view.View;
import android.view.Window;
import android.view.View.OnClickListener;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.AdapterView.OnItemClickListener;
/**
* This Activity appears as a dialog. It lists any paired devices and
* devices detected in the area after discovery. When a device is chosen
* by the user, the MAC address of the device is sent back to the parent
* Activity in the result Intent.
*/
public class DeviceListActivity extends Activity {
// Return Intent extra
public static final String EXTRA_DEVICE_ADDRESS = "device_address";
public static final String EXTRA_DEVICE_NAME = "device_name";
// Member fields
private BluetoothAdapter mBtAdapter;
private ArrayAdapter<String> mPairedDevicesArrayAdapter;
private ArrayAdapter<String> mNewDevicesArrayAdapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
setTheme(AltosDroid.dialog_themes[AltosDroidPreferences.font_size()]);
super.onCreate(savedInstanceState);
// Setup the window
setContentView(R.layout.device_list);
// Set result CANCELED incase the user backs out
setResult(Activity.RESULT_CANCELED);
// Initialize the button to perform device discovery
Button scanButton = (Button) findViewById(R.id.button_scan);
scanButton.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
doDiscovery();
v.setVisibility(View.GONE);
}
});
// Initialize array adapters. One for already paired devices and
// one for newly discovered devices
mPairedDevicesArrayAdapter = new ArrayAdapter<String>(this, R.layout.device_name);
mNewDevicesArrayAdapter = new ArrayAdapter<String>(this, R.layout.device_name);
// Find and set up the ListView for paired devices
ListView pairedListView = (ListView) findViewById(R.id.paired_devices);
pairedListView.setAdapter(mPairedDevicesArrayAdapter);
pairedListView.setOnItemClickListener(mDeviceClickListener);
// Find and set up the ListView for newly discovered devices
ListView newDevicesListView = (ListView) findViewById(R.id.new_devices);
newDevicesListView.setAdapter(mNewDevicesArrayAdapter);
newDevicesListView.setOnItemClickListener(mDeviceClickListener);
// Register for broadcasts when a device is discovered
IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
this.registerReceiver(mReceiver, filter);
// Register for broadcasts when discovery has finished
filter = new IntentFilter(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
this.registerReceiver(mReceiver, filter);
// Get the local Bluetooth adapter
mBtAdapter = BluetoothAdapter.getDefaultAdapter();
// Get a set of currently paired devices
Set<BluetoothDevice> pairedDevices = mBtAdapter.getBondedDevices();
// If there are paired devices, add each one to the ArrayAdapter
if (pairedDevices.size() > 0) {
findViewById(R.id.title_paired_devices).setVisibility(View.VISIBLE);
for (BluetoothDevice device : pairedDevices) {
String name = device.getName();
if (name != null && name.startsWith("TeleBT"))
mPairedDevicesArrayAdapter.add(name + "\n" + device.getAddress());
}
} else {
String noDevices = getResources().getText(R.string.none_paired).toString();
mPairedDevicesArrayAdapter.add(noDevices);
}
}
@Override
protected void onDestroy() {
super.onDestroy();
// Make sure we're not doing discovery anymore
if (mBtAdapter != null) {
mBtAdapter.cancelDiscovery();
}
// Unregister broadcast listeners
this.unregisterReceiver(mReceiver);
}
/**
* Start device discover with the BluetoothAdapter
*/
private void doDiscovery() {
AltosDebug.debug("doDiscovery()");
// Indicate scanning in the title
setTitle(R.string.scanning);
// Turn on sub-title for new devices
findViewById(R.id.title_new_devices).setVisibility(View.VISIBLE);
// If we're already discovering, stop it
if (mBtAdapter.isDiscovering()) {
mBtAdapter.cancelDiscovery();
}
// Request discover from BluetoothAdapter
mBtAdapter.startDiscovery();
}
// The on-click listener for all devices in the ListViews
private OnItemClickListener mDeviceClickListener = new OnItemClickListener() {
public void onItemClick(AdapterView<?> av, View v, int arg2, long arg3) {
// Get the device MAC address, which is the last 17 chars in the View
String info = ((TextView) v).getText().toString();
/* Ignore clicks on items that are too short */
if (info.length() <= 17)
return;
// Cancel discovery because it's costly and we're about to connect
mBtAdapter.cancelDiscovery();
String address = info.substring(info.length() - 17);
int newline = info.indexOf('\n');
String name = null;
if (newline > 0)
name = info.substring(0, newline);
else
name = info;
AltosDebug.debug("******* selected item '%s'", info);
// Create the result Intent and include the MAC address
Intent intent = new Intent();
intent.putExtra(EXTRA_DEVICE_ADDRESS, address);
intent.putExtra(EXTRA_DEVICE_NAME, name);
// Set result and finish this Activity
setResult(Activity.RESULT_OK, intent);
finish();
}
};
// The BroadcastReceiver that listens for discovered devices and
// changes the title when discovery is finished
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
// When discovery finds a device
if (BluetoothDevice.ACTION_FOUND.equals(action)) {
/* Get the BluetoothDevice object from the Intent
*/
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
/* If it's already paired, skip it, because it's been listed already
*/
if (device != null && device.getBondState() != BluetoothDevice.BOND_BONDED)
{
String name = device.getName();
if (name != null && name.startsWith("TeleBT"))
mNewDevicesArrayAdapter.add(name + "\n" + device.getAddress());
}
/* When discovery is finished, change the Activity title
*/
} else if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equals(action)) {
setTitle(R.string.select_device);
if (mNewDevicesArrayAdapter.getCount() == 0) {
String noDevices = getResources().getText(R.string.none_found).toString();
mNewDevicesArrayAdapter.add(noDevices);
}
}
}
};
}

View File

@@ -0,0 +1,183 @@
package org.altusmetrum.AltosDroid;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.util.HashMap;
public class Dumper {
private static Dumper instance = new Dumper();
protected static Dumper getInstance() {
return instance;
}
class DumpContext {
int maxDepth = 0;
int maxArrayElements = 0;
int callCount = 0;
HashMap<String, String> ignoreList = new HashMap<String, String>();
HashMap<Object, Integer> visited = new HashMap<Object, Integer>();
}
public static String dump(Object o) {
return dump(o, 0, 0, null);
}
public static String dump(Object o, int maxDepth, int maxArrayElements, String[] ignoreList) {
DumpContext ctx = Dumper.getInstance().new DumpContext();
ctx.maxDepth = maxDepth;
ctx.maxArrayElements = maxArrayElements;
if (ignoreList != null) {
for (int i = 0; i < Array.getLength(ignoreList); i++) {
int colonIdx = ignoreList[i].indexOf(':');
if (colonIdx == -1)
ignoreList[i] = ignoreList[i] + ":";
ctx.ignoreList.put(ignoreList[i], ignoreList[i]);
}
}
return dump(o, ctx);
}
protected static String dump(Object o, DumpContext ctx) {
if (o == null) {
return "<null>";
}
ctx.callCount++;
StringBuffer tabs = new StringBuffer();
for (int k = 0; k < ctx.callCount; k++) {
tabs.append("\t");
}
StringBuffer buffer = new StringBuffer();
@SuppressWarnings("rawtypes")
Class oClass = o.getClass();
String oSimpleName = getSimpleNameWithoutArrayQualifier(oClass);
if (ctx.ignoreList.get(oSimpleName + ":") != null)
return "<Ignored>";
if (oClass.isArray()) {
buffer.append("\n");
buffer.append(tabs.toString().substring(1));
buffer.append("[\n");
int rowCount = ctx.maxArrayElements == 0 ? Array.getLength(o) : Math.min(ctx.maxArrayElements, Array.getLength(o));
for (int i = 0; i < rowCount; i++) {
buffer.append(tabs.toString());
try {
Object value = Array.get(o, i);
buffer.append(dumpValue(value, ctx));
} catch (Exception e) {
buffer.append(e.getMessage());
}
if (i < Array.getLength(o) - 1)
buffer.append(",");
buffer.append("\n");
}
if (rowCount < Array.getLength(o)) {
buffer.append(tabs.toString());
buffer.append(Array.getLength(o) - rowCount + " more array elements...");
buffer.append("\n");
}
buffer.append(tabs.toString().substring(1));
buffer.append("]");
} else {
buffer.append("\n");
buffer.append(tabs.toString().substring(1));
buffer.append("{\n");
buffer.append(tabs.toString());
buffer.append("hashCode: " + o.hashCode());
buffer.append("\n");
while (oClass != null && oClass != Object.class) {
Field[] fields = oClass.getDeclaredFields();
if (ctx.ignoreList.get(oClass.getSimpleName()) == null) {
if (oClass != o.getClass()) {
buffer.append(tabs.toString().substring(1));
buffer.append(" Inherited from superclass " + oSimpleName + ":\n");
}
for (int i = 0; i < fields.length; i++) {
String fSimpleName = getSimpleNameWithoutArrayQualifier(fields[i].getType());
String fName = fields[i].getName();
fields[i].setAccessible(true);
buffer.append(tabs.toString());
buffer.append(fName + "(" + fSimpleName + ")");
buffer.append("=");
if (ctx.ignoreList.get(":" + fName) == null &&
ctx.ignoreList.get(fSimpleName + ":" + fName) == null &&
ctx.ignoreList.get(fSimpleName + ":") == null) {
try {
Object value = fields[i].get(o);
buffer.append(dumpValue(value, ctx));
} catch (Exception e) {
buffer.append(e.getMessage());
}
buffer.append("\n");
} else {
buffer.append("<Ignored>");
buffer.append("\n");
}
}
oClass = oClass.getSuperclass();
oSimpleName = oClass.getSimpleName();
} else {
oClass = null;
oSimpleName = "";
}
}
buffer.append(tabs.toString().substring(1));
buffer.append("}");
}
ctx.callCount--;
return buffer.toString();
}
protected static String dumpValue(Object value, DumpContext ctx) {
if (value == null) {
return "<null>";
}
if (value.getClass().isPrimitive() ||
value.getClass() == java.lang.Short.class ||
value.getClass() == java.lang.Long.class ||
value.getClass() == java.lang.String.class ||
value.getClass() == java.lang.Integer.class ||
value.getClass() == java.lang.Float.class ||
value.getClass() == java.lang.Byte.class ||
value.getClass() == java.lang.Character.class ||
value.getClass() == java.lang.Double.class ||
value.getClass() == java.lang.Boolean.class) {
return value.toString();
} else {
Integer visitedIndex = ctx.visited.get(value);
if (visitedIndex == null) {
ctx.visited.put(value, ctx.callCount);
if (ctx.maxDepth == 0 || ctx.callCount < ctx.maxDepth) {
return dump(value, ctx);
} else {
return "<Reached max recursion depth>";
}
} else {
return "<Previously visited - see hashCode " + value.hashCode() + ">";
}
}
}
private static String getSimpleNameWithoutArrayQualifier(@SuppressWarnings("rawtypes") Class clazz) {
String simpleName = clazz.getSimpleName();
int indexOfBracket = simpleName.indexOf('[');
if (indexOfBracket != -1)
return simpleName.substring(0, indexOfBracket);
return simpleName;
}
}

View File

@@ -0,0 +1,65 @@
/*
* Copyright © 2013 Mike Beattie <mike@ethernal.org>
*
* 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 android.content.res.Resources;
import android.graphics.drawable.Drawable;
import android.widget.ImageView;
public class GoNoGoLights {
private Boolean state;
private Boolean missing;
private Boolean set;
private ImageView red;
private ImageView green;
private Drawable dRed;
private Drawable dGreen;
private Drawable dGray;
public GoNoGoLights(ImageView in_red, ImageView in_green, Resources r) {
red = in_red;
green = in_green;
state = false;
missing = true;
set = false;
dRed = in_red.getContext().getDrawable(R.drawable.redled);
dGreen = in_red.getContext().getDrawable(R.drawable.greenled);
dGray = in_red.getContext().getDrawable(R.drawable.grayled);
}
public void set(Boolean s, Boolean m) {
if (set && s == state && m == missing) return;
state = s;
missing = m;
set = true;
if (missing) {
red.setImageDrawable(dGray);
green.setImageDrawable(dGray);
} else if (state) {
red.setImageDrawable(dGray);
green.setImageDrawable(dGreen);
} else {
red.setImageDrawable(dRed);
green.setImageDrawable(dGray);
}
}
}

View File

@@ -0,0 +1,128 @@
/*
* Copyright © 2016 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 android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.view.Window;
import android.view.View.OnClickListener;
import android.widget.*;
import org.altusmetrum.altoslib_14.*;
public class IdleModeActivity extends Activity {
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";
public static final int IDLE_MODE_CONNECT = 1;
public static final int IDLE_MODE_REBOOT = 2;
public static final int IDLE_MODE_IGNITERS = 3;
public static final int IDLE_MODE_DISCONNECT = 4;
private void done(int type) {
AltosPreferences.set_callsign(callsign());
Intent intent = new Intent();
intent.putExtra(EXTRA_IDLE_RESULT, type);
setResult(Activity.RESULT_OK, intent);
finish();
}
private String callsign() {
return callsignText.getEditableText().toString();
}
public void connect_idle() {
done(IDLE_MODE_CONNECT);
}
public void disconnect_idle() {
AltosDebug.debug("Disconnect idle button pressed");
done(IDLE_MODE_DISCONNECT);
}
public void reboot_idle() {
done(IDLE_MODE_REBOOT);
}
public void igniters_idle() {
done(IDLE_MODE_IGNITERS);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
setTheme(AltosDroid.dialog_themes[AltosDroidPreferences.font_size()]);
super.onCreate(savedInstanceState);
// Setup the window
setContentView(R.layout.idle_mode);
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() {
public void onClick(View v) {
connect_idle();
}
});
disconnect = (Button) findViewById(R.id.disconnect_idle);
disconnect.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
disconnect_idle();
}
});
boolean idle_mode = getIntent().getBooleanExtra(AltosDroid.EXTRA_IDLE_MODE, false);
if (idle_mode)
connect.setVisibility(View.GONE);
else
disconnect.setVisibility(View.GONE);
reboot = (Button) findViewById(R.id.reboot_idle);
reboot.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
reboot_idle();
}
});
igniters = (Button) findViewById(R.id.igniters_idle);
igniters.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
igniters_idle();
}
});
// Set result CANCELED incase the user backs out
setResult(Activity.RESULT_CANCELED);
}
}

View File

@@ -0,0 +1,411 @@
/*
* Copyright © 2016 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.app.Activity;
import android.content.*;
import android.graphics.*;
import android.os.*;
import android.view.*;
import android.view.View.*;
import android.widget.*;
import org.altusmetrum.altoslib_14.*;
class IgniterItem {
public String name;
public String pretty;
public String status;
public LinearLayout igniter_view = null;
public TextView pretty_view = null;
public TextView status_view = null;
private void update() {
if (pretty_view != null)
pretty_view.setText(pretty);
if (status_view != null)
status_view.setText(status);
}
public void set(String name, String pretty, String status) {
if (!name.equals(this.name) ||
!pretty.equals(this.pretty) ||
!status.equals(this.status))
{
this.name = name;
this.pretty = pretty;
this.status = status;
update();
}
}
public void realize(LinearLayout igniter_view,
TextView pretty_view,
TextView status_view) {
if (igniter_view != this.igniter_view ||
pretty_view != this.pretty_view ||
status_view != this.status_view)
{
this.igniter_view = igniter_view;
this.pretty_view = pretty_view;
this.status_view = status_view;
update();
}
}
public IgniterItem() {
}
}
class IgniterAdapter extends ArrayAdapter<IgniterItem> {
int resource;
int selected_item = -1;
public IgniterAdapter(Context context, int in_resource) {
super(context, in_resource);
resource = in_resource;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
IgniterItem item = getItem(position);
if (item.igniter_view == null) {
LinearLayout igniter_view = new LinearLayout(getContext());
String inflater = Context.LAYOUT_INFLATER_SERVICE;
LayoutInflater li = (LayoutInflater) getContext().getSystemService(inflater);
li.inflate(resource, igniter_view, true);
item.realize(igniter_view,
(TextView) igniter_view.findViewById(R.id.igniter_name),
(TextView) igniter_view.findViewById(R.id.igniter_status));
}
if (position == selected_item)
item.igniter_view.setBackgroundColor(Color.RED);
else
item.igniter_view.setBackgroundColor(0);
return item.igniter_view;
}
}
public class IgniterActivity extends Activity {
private ListView igniters_view;
private ToggleButton arm;
private Button fire;
private HashMap<String,IgniterItem> igniters = new HashMap<String,IgniterItem>();;
private IgniterAdapter igniters_adapter;
private boolean is_bound;
private Messenger service = null;
private final Messenger messenger = new Messenger(new IncomingHandler(this));
private Timer query_timer;
private boolean query_timer_running;
private Timer arm_timer;
private int arm_remaining;
public static final int IGNITER_QUERY = 1;
public static final int IGNITER_FIRE = 2;
// The Handler that gets information back from the Telemetry Service
static class IncomingHandler extends Handler {
private final WeakReference<IgniterActivity> igniter_activity;
IncomingHandler(IgniterActivity ia) { igniter_activity = new WeakReference<IgniterActivity>(ia); }
@Override
public void handleMessage(Message msg) {
IgniterActivity ia = igniter_activity.get();
switch (msg.what) {
case AltosDroid.MSG_IGNITER_STATUS:
@SuppressWarnings("unchecked") HashMap<String,Integer> map = (HashMap <String,Integer>) msg.obj;
ia.igniter_status(map);
break;
}
}
};
private ServiceConnection connection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder binder) {
service = new Messenger(binder);
query_timer_tick();
}
public void onServiceDisconnected(ComponentName className) {
// This is called when the connection with the service has been unexpectedly disconnected - process crashed.
service = null;
}
};
void doBindService() {
bindService(new Intent(this, TelemetryService.class), connection, Context.BIND_AUTO_CREATE);
is_bound = true;
}
void doUnbindService() {
if (is_bound) {
// If we have received the service, and hence registered with it, then now is the time to unregister.
unbindService(connection);
is_bound = false;
}
}
private void done() {
Intent intent = new Intent();
setResult(Activity.RESULT_OK, intent);
finish();
}
class FireThread extends Thread {
private final String igniter;
@Override
public void run() {
Message msg = Message.obtain(null, TelemetryService.MSG_IGNITER_FIRE, igniter);
try {
service.send(msg);
} catch (RemoteException re) {
}
}
public FireThread(String igniter) {
this.igniter = igniter;
}
}
private void fire_igniter() {
if (igniters_adapter.selected_item >= 0) {
IgniterItem item = igniters_adapter.getItem(igniters_adapter.selected_item);
FireThread ft = new FireThread(item.name);
ft.run();
arm.setChecked(false);
}
}
private void arm_igniter(boolean is_checked) {
if (is_checked) {
arm_timer_stop();
arm_timer = new Timer();
arm_remaining = 10;
arm_set_text();
fire.setEnabled(true);
arm_timer.scheduleAtFixedRate(new TimerTask() {
public void run() {
arm_timer_tick();
}},
1000L, 1000L);
} else {
arm_timer_stop();
fire.setEnabled(false);
}
}
private synchronized void query_timer_tick() {
if (query_timer_running)
return;
if (service == null)
return;
query_timer_running = true;
Thread thread = new Thread(new Runnable() {
public void run() {
try {
Message msg = Message.obtain(null, TelemetryService.MSG_IGNITER_QUERY);
msg.replyTo = messenger;
if (service == null) {
synchronized(IgniterActivity.this) {
query_timer_running = false;
}
} else
service.send(msg);
} catch (RemoteException re) {
AltosDebug.debug("igniter query thread failed");
synchronized(IgniterActivity.this) {
query_timer_running = false;
}
}
}
});
thread.start();
}
private boolean set_igniter(HashMap <String,Integer> status, String name, String pretty) {
if (!status.containsKey(name))
return false;
IgniterItem item;
if (!igniters.containsKey(name)) {
item = new IgniterItem();
igniters.put(name, item);
igniters_adapter.add(item);
} else
item = igniters.get(name);
item.set(name, pretty, AltosIgnite.status_string(status.get(name)));
return true;
}
private synchronized void igniter_status(HashMap <String,Integer> status) {
query_timer_running = false;
if (status == null) {
AltosDebug.debug("no igniter status");
return;
}
set_igniter(status, "drogue", "Apogee");
set_igniter(status, "main", "Main");
for (int extra = 0;; extra++) {
String name = String.format("%d", extra);
String pretty = String.format("%c", 'A' + extra);
if (!set_igniter(status, name, pretty))
break;
}
}
private synchronized void arm_timer_stop() {
if (arm_timer != null) {
arm_timer.cancel();
arm_timer = null;
}
arm_remaining = 0;
}
private void arm_set_text() {
String text = String.format("Armed %d", arm_remaining);
if (arm.isChecked())
arm.setText(text);
arm.setTextOn(text);
}
private void arm_timer_tick() {
--arm_remaining;
if (arm_remaining <= 0) {
arm_timer_stop();
runOnUiThread(new Runnable() {
public void run() {
arm.setChecked(false);
fire.setEnabled(false);
}
});
} else {
runOnUiThread(new Runnable() {
public void run() {
arm_set_text();
}
});
}
}
private void select_item(int position) {
if (position != igniters_adapter.selected_item) {
if (igniters_adapter.selected_item >= 0)
igniters_view.setItemChecked(igniters_adapter.selected_item, false);
if (position >= 0) {
igniters_view.setItemChecked(position, true);
arm.setEnabled(true);
} else
arm.setEnabled(false);
igniters_adapter.selected_item = position;
}
}
private class IgniterItemClickListener implements ListView.OnItemClickListener {
@Override
public void onItemClick(AdapterView<?> av, View v, int position, long id) {
AltosDebug.debug("select %d\n", position);
select_item(position);
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
setTheme(AltosDroid.dialog_themes[AltosDroidPreferences.font_size()]);
super.onCreate(savedInstanceState);
// Setup the window
setContentView(R.layout.igniters);
igniters_view = (ListView) findViewById(R.id.igniters);
igniters_view.setClickable(true);
igniters_adapter = new IgniterAdapter(this, R.layout.igniter_status);
igniters_view.setAdapter(igniters_adapter);
igniters_view.setOnItemClickListener(new IgniterItemClickListener());
fire = (Button) findViewById(R.id.igniter_fire);
fire.setEnabled(false);
fire.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
fire_igniter();
}
});
arm = (ToggleButton) findViewById(R.id.igniter_arm);
arm.setEnabled(false);
arm.setOnCheckedChangeListener(new ToggleButton.OnCheckedChangeListener() {
public void onCheckedChanged(CompoundButton v, boolean is_checked) {
arm_igniter(is_checked);
}
});
// Set result CANCELED incase the user backs out
setResult(Activity.RESULT_CANCELED);
}
@Override
protected void onStart() {
super.onStart();
doBindService();
}
@Override
protected void onResume() {
super.onResume();
query_timer = new Timer(true);
query_timer.scheduleAtFixedRate(new TimerTask() {
public void run() {
query_timer_tick();
}},
0L, 5000L);
}
@Override
protected void onPause() {
super.onPause();
if (query_timer != null) {
query_timer.cancel();
query_timer = null;
}
arm_timer_stop();
arm.setChecked(false);
fire.setEnabled(false);
}
@Override
protected void onStop() {
super.onStop();
doUnbindService();
}
}

View File

@@ -0,0 +1,304 @@
/*
* Copyright © 2016 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 java.text.*;
import android.app.Activity;
import android.content.*;
import android.graphics.*;
import android.os.*;
import android.view.*;
import android.view.View.*;
import android.view.inputmethod.*;
import android.widget.*;
import org.altusmetrum.altoslib_14.*;
class FrequencyItem {
public AltosFrequency frequency;
public LinearLayout frequency_view = null;
public TextView pretty_view = null;
private void update() {
if (pretty_view != null && frequency != null)
pretty_view.setText(frequency.toString());
}
public void realize(LinearLayout frequency_view,
TextView pretty_view) {
if (frequency_view != this.frequency_view ||
pretty_view != this.pretty_view)
{
this.frequency_view = frequency_view;
this.pretty_view = pretty_view;
update();
}
}
public void set_frequency(AltosFrequency frequency) {
this.frequency = frequency;
update();
}
public FrequencyItem(AltosFrequency frequency) {
this.frequency = frequency;
}
}
class FrequencyAdapter extends ArrayAdapter<FrequencyItem> {
int resource;
int selected_item = -1;
public FrequencyAdapter(Context context, int in_resource) {
super(context, in_resource);
resource = in_resource;
}
public int count() {
int count;
for (count = 0;; count++) {
try {
getItem(count);
} catch (IndexOutOfBoundsException ie) {
return count;
}
}
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
FrequencyItem item = getItem(position);
if (item.frequency_view == null) {
LinearLayout frequency_view = new LinearLayout(getContext());
String inflater = Context.LAYOUT_INFLATER_SERVICE;
LayoutInflater li = (LayoutInflater) getContext().getSystemService(inflater);
li.inflate(resource, frequency_view, true);
item.realize(frequency_view,
(TextView) frequency_view.findViewById(R.id.frequency));
}
if (position == selected_item)
item.frequency_view.setBackgroundColor(Color.RED);
else
item.frequency_view.setBackgroundColor(0);
return item.frequency_view;
}
}
public class ManageFrequenciesActivity extends Activity {
private ListView frequencies_view;
private Button set;
private Button remove;
private Button done;
private EditText set_frequency;
private EditText set_description;
private HashMap<String,FrequencyItem> frequencies = new HashMap<String,FrequencyItem>();;
private FrequencyAdapter frequencies_adapter;
private boolean is_bound;
private boolean changed = false;
private void done() {
set();
if (changed) {
AltosFrequency[] frequencies = new AltosFrequency[frequencies_adapter.count()];
for (int i = 0; i < frequencies.length; i++)
frequencies[i] = frequencies_adapter.getItem(i).frequency;
AltosPreferences.set_common_frequencies(frequencies);
}
Intent intent = new Intent();
setResult(Activity.RESULT_OK, intent);
finish();
}
private void load_item() {
if (frequencies_adapter.selected_item >= 0) {
FrequencyItem item = frequencies_adapter.getItem(frequencies_adapter.selected_item);
set_frequency.setText(item.frequency.frequency_string());
set_description.setText(item.frequency.description);
} else {
set_frequency.setText("");
set_description.setText("");
}
}
private void select_item(int position) {
if (position != frequencies_adapter.selected_item) {
if (frequencies_adapter.selected_item >= 0)
frequencies_view.setItemChecked(frequencies_adapter.selected_item, false);
if (position >= 0)
frequencies_view.setItemChecked(position, true);
frequencies_adapter.selected_item = position;
} else {
if (frequencies_adapter.selected_item >= 0)
frequencies_view.setItemChecked(frequencies_adapter.selected_item, false);
frequencies_adapter.selected_item = -1;
}
load_item();
}
private int find(AltosFrequency frequency) {
for (int pos = 0; pos < frequencies_adapter.getCount(); pos++) {
FrequencyItem item = frequencies_adapter.getItem(pos);
if (item.frequency.frequency == frequency.frequency &&
item.frequency.description.equals(frequency.description))
return pos;
}
return -1;
}
private int insert_item(AltosFrequency frequency) {
FrequencyItem new_item = new FrequencyItem(frequency);
int pos;
for (pos = 0; pos < frequencies_adapter.getCount(); pos++) {
FrequencyItem item = frequencies_adapter.getItem(pos);
if (item.frequency.frequency == new_item.frequency.frequency) {
item.set_frequency(frequency);
return pos;
}
if (item.frequency.frequency > new_item.frequency.frequency)
break;
}
frequencies_adapter.insert(new_item, pos);
return pos;
}
private class FrequencyItemClickListener implements ListView.OnItemClickListener {
@Override
public void onItemClick(AdapterView<?> av, View v, int position, long id) {
select_item(position);
}
}
private void hide_keyboard() {
InputMethodManager imm = (InputMethodManager) getSystemService(Activity.INPUT_METHOD_SERVICE);
View view = getCurrentFocus();
if (view != null)
imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
}
private void set() {
String frequency_text = set_frequency.getEditableText().toString();
String description_text = set_description.getEditableText().toString();
try {
double f = AltosParse.parse_double_locale(frequency_text);
AltosFrequency frequency = new AltosFrequency(f, description_text);
int pos;
pos = find(frequency);
if (pos < 0) {
pos = insert_item(frequency);
changed = true;
}
frequencies_adapter.selected_item = -1;
select_item(pos);
} catch (ParseException pe) {
}
hide_keyboard();
}
private void remove() {
if (frequencies_adapter.selected_item >= 0) {
frequencies_adapter.remove(frequencies_adapter.getItem(frequencies_adapter.selected_item));
select_item(-1);
frequencies_view.setAdapter(frequencies_adapter);
changed = true;
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
setTheme(AltosDroid.dialog_themes[AltosDroidPreferences.font_size()]);
super.onCreate(savedInstanceState);
// Setup the window
setContentView(R.layout.manage_frequencies);
frequencies_view = (ListView) findViewById(R.id.frequencies);
frequencies_view.setClickable(true);
frequencies_adapter = new FrequencyAdapter(this, R.layout.frequency);
frequencies_view.setAdapter(frequencies_adapter);
frequencies_view.setOnItemClickListener(new FrequencyItemClickListener());
AltosFrequency[] frequencies = AltosPreferences.common_frequencies();
for (AltosFrequency frequency : frequencies)
insert_item(frequency);
set_frequency = (EditText) findViewById(R.id.set_frequency);
set_description = (EditText) findViewById(R.id.set_description);
set = (Button) findViewById(R.id.set);
set.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
set();
}
});
remove = (Button) findViewById(R.id.remove);
remove.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
remove();
}
});
done = (Button) findViewById(R.id.done);
done.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
done();
}
});
// Set result CANCELED incase the user backs out
setResult(Activity.RESULT_CANCELED);
}
@Override
protected void onStart() {
super.onStart();
}
@Override
protected void onResume() {
super.onResume();
}
@Override
protected void onPause() {
super.onPause();
}
@Override
protected void onStop() {
super.onStop();
}
}

View File

@@ -0,0 +1,75 @@
/*
* Copyright © 2015 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 android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.view.Window;
import android.widget.*;
import org.altusmetrum.altoslib_14.*;
public class MapTypeActivity extends Activity {
private Button hybrid;
private Button satellite;
private Button roadmap;
private Button terrain;
private int selected_type;
public static final String EXTRA_MAP_TYPE = "map_type";
private void done(int type) {
Intent intent = new Intent();
intent.putExtra(EXTRA_MAP_TYPE, type);
setResult(Activity.RESULT_OK, intent);
finish();
}
public void selectType(View view) {
AltosDebug.debug("selectType %s", view.toString());
if (view == hybrid)
done(AltosMap.maptype_hybrid);
if (view == satellite)
done(AltosMap.maptype_satellite);
if (view == roadmap)
done(AltosMap.maptype_roadmap);
if (view == terrain)
done(AltosMap.maptype_terrain);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
setTheme(AltosDroid.dialog_themes[AltosDroidPreferences.font_size()]);
super.onCreate(savedInstanceState);
// Setup the window
setContentView(R.layout.map_type);
hybrid = (Button) findViewById(R.id.map_type_hybrid);
satellite = (Button) findViewById(R.id.map_type_satellite);
roadmap = (Button) findViewById(R.id.map_type_roadmap);
terrain = (Button) findViewById(R.id.map_type_terrain);
// Set result CANCELED incase the user backs out
setResult(Activity.RESULT_CANCELED);
}
}

View File

@@ -0,0 +1,378 @@
/*
* Copyright © 2015 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 java.text.*;
import android.app.Activity;
import android.content.Context;
import android.os.Bundle;
import android.view.View;
import android.view.Window;
import android.view.View.OnClickListener;
import android.widget.*;
import android.widget.AdapterView.*;
import android.location.Location;
import android.location.LocationManager;
import android.location.LocationListener;
import org.altusmetrum.altoslib_14.*;
/**
* This Activity appears as a dialog. It lists any paired devices and
* devices detected in the area after discovery. When a device is chosen
* by the user, the MAC address of the device is sent back to the parent
* Activity in the result Intent.
*/
public class PreloadMapActivity extends Activity implements AltosLaunchSiteListener, AltosMapLoaderListener, LocationListener {
private ArrayAdapter<AltosLaunchSite> known_sites_adapter;
/*
private CheckBox hybrid;
private CheckBox satellite;
private CheckBox roadmap;
private CheckBox terrain;
*/
private Spinner known_sites_spinner;
private Spinner min_zoom;
private Spinner max_zoom;
private TextView radius_label;
private Spinner radius;
private EditText latitude;
private EditText longitude;
private ProgressBar progress;
private AltosMapLoader loader;
long loader_notify_time;
/* AltosMapLoaderListener interfaces */
public void loader_start(final int max) {
loader_notify_time = System.currentTimeMillis();
this.runOnUiThread(new Runnable() {
public void run() {
progress.setMax(max);
progress.setProgress(0);
}
});
}
public void loader_notify(final int cur, final int max, final String name) {
long now = System.currentTimeMillis();
if (now - loader_notify_time < 100)
return;
loader_notify_time = now;
this.runOnUiThread(new Runnable() {
public void run() {
progress.setProgress(cur);
}
});
}
public void loader_done(int max) {
loader = null;
this.runOnUiThread(new Runnable() {
public void run() {
progress.setProgress(0);
finish();
}
});
}
public void debug(String format, Object ... arguments) {
AltosDebug.debug(format, arguments);
}
/* AltosLaunchSiteListener interface */
public void notify_launch_sites(final List<AltosLaunchSite> sites) {
this.runOnUiThread(new Runnable() {
public void run() {
for (AltosLaunchSite site : sites)
known_sites_adapter.add(site);
}
});
}
/* LocationProvider interface */
AltosLaunchSite current_location_site;
public void onLocationChanged(Location location) {
AltosDebug.debug("location changed");
if (current_location_site == null) {
AltosLaunchSite selected_item = (AltosLaunchSite) known_sites_spinner.getSelectedItem();
current_location_site = new AltosLaunchSite("Current Location", location.getLatitude(), location.getLongitude());
known_sites_adapter.insert(current_location_site, 0);
if (selected_item != null)
known_sites_spinner.setSelection(known_sites_adapter.getPosition(selected_item));
else {
latitude.setText(new StringBuffer(String.format("%12.6f", current_location_site.latitude)));
longitude.setText(new StringBuffer(String.format("%12.6f", current_location_site.longitude)));
}
} else {
current_location_site.latitude = location.getLatitude();
current_location_site.longitude = location.getLongitude();
}
}
public void onStatusChanged(String provider, int status, Bundle extras) {
}
public void onProviderEnabled(String provider) {
}
public void onProviderDisabled(String provider) {
}
private double text(EditText view) throws ParseException {
return AltosParse.parse_double_locale(view.getEditableText().toString());
}
private double latitude() throws ParseException {
return text(latitude);
}
private double longitude() throws ParseException {
return text(longitude);
}
private int value(Spinner spinner) {
return (Integer) spinner.getSelectedItem();
}
private int min_z() {
return value(min_zoom);
}
private int max_z() {
return value(max_zoom);
}
private double value_distance(Spinner spinner) {
return (Double) spinner.getSelectedItem();
}
private double radius() {
double r = value_distance(radius);
if (AltosPreferences.imperial_units())
r = AltosConvert.miles_to_meters(r);
else
r = r * 1000;
return r;
}
/*
private int bit(CheckBox box, int value) {
if (box.isChecked())
return 1 << value;
return 0;
}
*/
private int types() {
/*
return (bit(hybrid, AltosMap.maptype_hybrid) |
bit(satellite, AltosMap.maptype_satellite) |
bit(roadmap, AltosMap.maptype_roadmap) |
bit(terrain, AltosMap.maptype_terrain));
*/
return 1 << AltosMap.maptype_hybrid;
}
private void load() {
if (loader != null)
return;
try {
double lat = latitude();
double lon = longitude();
int min = min_z();
int max = max_z();
double r = radius();
int t = types();
AltosDebug.debug("PreloadMap load %f %f %d %d %f %d\n",
lat, lon, min, max, r, t);
loader = new AltosMapLoader(this, lat, lon, min, max, r, t, AltosMapOffline.scale);
} catch (ParseException e) {
AltosDebug.debug("PreloadMap load raised exception %s", e.toString());
}
}
private void add_numbers(Spinner spinner, int min, int max, int def) {
ArrayAdapter<Integer> adapter = new ArrayAdapter<Integer>(this, android.R.layout.simple_spinner_item);
int spinner_def = 0;
int pos = 0;
for (int i = min; i <= max; i++) {
adapter.add(new Integer(i));
if (i == def)
spinner_def = pos;
pos++;
}
spinner.setAdapter(adapter);
spinner.setSelection(spinner_def);
}
private void add_distance(Spinner spinner, double[] distances_km, double def_km, double[] distances_mi, double def_mi) {
ArrayAdapter<Double> adapter = new ArrayAdapter<Double>(this, android.R.layout.simple_spinner_item);
int spinner_def = 0;
int pos = 0;
double[] distances;
double def;
if (AltosPreferences.imperial_units()) {
distances = distances_mi;
def = def_mi;
} else {
distances = distances_km;
def = def_km;
}
for (int i = 0; i < distances.length; i++) {
adapter.add(distances[i]);
if (distances[i] == def)
spinner_def = pos;
pos++;
}
spinner.setAdapter(adapter);
spinner.setSelection(spinner_def);
}
class SiteListListener implements OnItemSelectedListener {
public void onItemSelected(AdapterView<?> parent, View view, int pos, long id) {
AltosLaunchSite site = (AltosLaunchSite) parent.getItemAtPosition(pos);
latitude.setText(new StringBuffer(String.format("%12.6f", site.latitude)));
longitude.setText(new StringBuffer(String.format("%12.6f", site.longitude)));
}
public void onNothingSelected(AdapterView<?> parent) {
}
public SiteListListener() {
}
}
double[] radius_mi = { 1, 2, 5, 10, 20 };
double radius_def_mi = 2;
double[] radius_km = { 1, 2, 5, 10, 20, 30 };
double radius_def_km = 2;
@Override
protected void onCreate(Bundle savedInstanceState) {
setTheme(AltosDroid.dialog_themes[AltosDroidPreferences.font_size()]);
super.onCreate(savedInstanceState);
// Setup the window
setContentView(R.layout.map_preload);
// Set result CANCELED incase the user backs out
setResult(Activity.RESULT_CANCELED);
// Initialize the button to perform device discovery
Button loadButton = (Button) findViewById(R.id.preload_load);
loadButton.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
load();
}
});
latitude = (EditText) findViewById(R.id.preload_latitude);
longitude = (EditText) findViewById(R.id.preload_longitude);
/*
hybrid = (CheckBox) findViewById(R.id.preload_hybrid);
satellite = (CheckBox) findViewById(R.id.preload_satellite);
roadmap = (CheckBox) findViewById(R.id.preload_roadmap);
terrain = (CheckBox) findViewById(R.id.preload_terrain);
hybrid.setChecked(true);
*/
min_zoom = (Spinner) findViewById(R.id.preload_min_zoom);
add_numbers(min_zoom,
AltosMap.min_zoom - AltosMap.default_zoom,
AltosMap.max_zoom - AltosMap.default_zoom, -2);
max_zoom = (Spinner) findViewById(R.id.preload_max_zoom);
add_numbers(max_zoom,
AltosMap.min_zoom - AltosMap.default_zoom,
AltosMap.max_zoom - AltosMap.default_zoom, 2);
radius_label = (TextView) findViewById(R.id.preload_radius_label);
radius = (Spinner) findViewById(R.id.preload_radius);
if (AltosPreferences.imperial_units())
radius_label.setText("Radius (miles)");
else
radius_label.setText("Radius (km)");
add_distance(radius, radius_km, radius_def_km, radius_mi, radius_def_mi);
progress = (ProgressBar) findViewById(R.id.preload_progress);
// Initialize array adapters. One for already paired devices and
// one for newly discovered devices
known_sites_spinner = (Spinner) findViewById(R.id.preload_site_list);
known_sites_adapter = new ArrayAdapter<AltosLaunchSite>(this, android.R.layout.simple_spinner_item);
known_sites_adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
known_sites_spinner.setAdapter(known_sites_adapter);
known_sites_spinner.setOnItemSelectedListener(new SiteListListener());
// Listen for GPS and Network position updates
LocationManager locationManager = (LocationManager) this.getSystemService(Context.LOCATION_SERVICE);
try {
locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 1000, 1, this);
} catch (Exception e) {
locationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 1000, 1, this);
}
new AltosLaunchSites(this);
}
@Override
protected void onDestroy() {
super.onDestroy();
if (loader != null)
loader.abort();
// Stop listening for location updates
((LocationManager) getSystemService(Context.LOCATION_SERVICE)).removeUpdates(this);
}
}

View File

@@ -0,0 +1,297 @@
/*
* 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_14.*;
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) {
int title_id = getIntent().getIntExtra(AltosDroid.EXTRA_TRACKERS_TITLE, R.id.select_tracker);
AltosDebug.debug("get title id 0x%x %s", title_id, getResources().getText(title_id));
setTitle(getResources().getText(title_id));
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;
}
}

View File

@@ -0,0 +1,365 @@
/*
* Copyright © 2016 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 android.app.Activity;
import android.content.*;
import android.os.*;
import android.view.*;
import android.view.View.*;
import android.widget.*;
import android.widget.AdapterView.*;
import org.altusmetrum.altoslib_14.*;
public class SetupActivity extends Activity {
private Spinner select_rate;
private Spinner set_units;
private Spinner font_size;
private Spinner map_type;
private Spinner map_source;
private Button manage_frequencies;
private Button preload_maps;
private Button done;
private boolean is_bound;
private Messenger service = null;
public final static String EXTRA_SETUP_CHANGES = "setup_changes";
private ServiceConnection connection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder binder) {
service = new Messenger(binder);
}
public void onServiceDisconnected(ComponentName className) {
// This is called when the connection with the service has been unexpectedly disconnected - process crashed.
service = null;
}
};
void doBindService() {
bindService(new Intent(this, TelemetryService.class), connection, Context.BIND_AUTO_CREATE);
is_bound = true;
}
void doUnbindService() {
if (is_bound) {
// If we have received the service, and hence registered with it, then now is the time to unregister.
unbindService(connection);
is_bound = false;
}
}
static final String[] rates = {
"38400",
"9600",
"2400",
};
static final String[] sizes = {
"Small",
"Medium",
"Large",
"Extra"
};
static final String[] map_types = {
"Hybrid",
"Satellite",
"Roadmap",
"Terrain"
};
static final int[] map_type_values = {
AltosMap.maptype_hybrid,
AltosMap.maptype_satellite,
AltosMap.maptype_roadmap,
AltosMap.maptype_terrain,
};
static final String[] map_sources = {
"Online",
"Offline"
};
private int set_telemetry_rate;
private int set_map_source;
private int set_map_type;
private boolean set_imperial_units;
private int set_font_size;
private void done() {
int changes = 0;
Intent intent = new Intent();
if (set_telemetry_rate != AltosPreferences.telemetry_rate(1)) {
changes |= AltosDroid.SETUP_BAUD;
AltosPreferences.set_telemetry_rate(1, set_telemetry_rate);
}
if (set_imperial_units != AltosPreferences.imperial_units()) {
changes |= AltosDroid.SETUP_UNITS;
AltosPreferences.set_imperial_units(set_imperial_units);
}
if (set_map_source != AltosDroidPreferences.map_source()) {
changes |= AltosDroid.SETUP_MAP_SOURCE;
AltosDroidPreferences.set_map_source(set_map_source);
}
if (set_map_type != AltosPreferences.map_type()) {
changes |= AltosDroid.SETUP_MAP_TYPE;
AltosPreferences.set_map_type(set_map_type);
}
if (set_font_size != AltosDroidPreferences.font_size()) {
changes |= AltosDroid.SETUP_FONT_SIZE;
AltosDroidPreferences.set_font_size(set_font_size);
}
intent.putExtra(EXTRA_SETUP_CHANGES, changes);
setResult(Activity.RESULT_OK, intent);
finish();
}
private void add_strings(Spinner spinner, String[] strings, int def) {
ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, android.R.layout.simple_spinner_dropdown_item);
for (int i = 0; i < strings.length; i++)
adapter.add(strings[i]);
spinner.setAdapter(adapter);
if (def >= 0)
spinner.setSelection(def);
}
private int default_rate_pos() {
int default_rate = AltosPreferences.telemetry_rate(1);
for (int pos = 0; pos < rates.length; pos++) {
if (string_to_rate(rates[pos]) == default_rate)
return pos;
}
return -1;
}
private void setBaud(int baud) {
set_telemetry_rate = baud;
}
private int string_to_rate(String baud) {
int rate = AltosLib.ao_telemetry_rate_38400;
try {
int value = Integer.parseInt(baud);
switch (value) {
case 2400:
rate = AltosLib.ao_telemetry_rate_2400;
break;
case 9600:
rate = AltosLib.ao_telemetry_rate_9600;
break;
case 38400:
rate = AltosLib.ao_telemetry_rate_38400;
break;
}
} catch (NumberFormatException e) {
}
return rate;
}
private void setBaud(String baud) {
setBaud(string_to_rate(baud));
}
private void select_rate(int pos) {
setBaud(rates[pos]);
}
static final String[] units = {
"Metric",
"Imperial"
};
private int default_units_pos() {
boolean imperial = AltosPreferences.imperial_units();
if (imperial)
return 1;
return 0;
}
private void set_units(int pos) {
switch (pos) {
default:
set_imperial_units = false;
break;
case 1:
set_imperial_units = true;
break;
}
}
private void set_font_size(int pos) {
set_font_size = pos;
}
private int default_map_type_pos() {
int default_map_type = AltosPreferences.map_type();
for (int pos = 0; pos < map_types.length; pos++)
if (map_type_values[pos] == default_map_type)
return pos;
return 0;
}
private void select_map_type(int pos) {
set_map_type = map_type_values[pos];
}
private int default_map_source_pos() {
int default_source = AltosDroidPreferences.map_source();
switch (default_source) {
case AltosDroidPreferences.MAP_SOURCE_OFFLINE:
return 1;
default:
return 0;
}
}
private void select_map_source(int pos) {
switch (pos) {
default:
set_map_source = AltosDroidPreferences.MAP_SOURCE_ONLINE;
break;
case 1:
set_map_source = AltosDroidPreferences.MAP_SOURCE_OFFLINE;
break;
}
}
private void manage_frequencies(){
Intent intent = new Intent(this, ManageFrequenciesActivity.class);
startActivity(intent);
}
private void preload_maps(){
Intent intent = new Intent(this, PreloadMapActivity.class);
startActivity(intent);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
setTheme(AltosDroid.dialog_themes[AltosDroidPreferences.font_size()]);
super.onCreate(savedInstanceState);
AltosDebug.init(this);
AltosDebug.debug("+++ ON CREATE +++");
// Initialise preferences
AltosDroidPreferences.init(this);
// Setup the window
setContentView(R.layout.setup);
select_rate = (Spinner) findViewById(R.id.select_rate);
add_strings(select_rate, rates, default_rate_pos());
select_rate.setOnItemSelectedListener(new OnItemSelectedListener() {
public void onItemSelected(AdapterView<?> parent, View view, int pos, long id) {
AltosDebug.debug("rate selected pos %d id %d", pos, id);
select_rate(pos);
}
public void onNothingSelected(AdapterView<?> parent) {
}
});
set_units = (Spinner) findViewById(R.id.set_units);
add_strings(set_units, units, default_units_pos());
set_units.setOnItemSelectedListener(new OnItemSelectedListener() {
public void onItemSelected(AdapterView<?> parent, View view, int pos, long id) {
set_units(pos);
}
public void onNothingSelected(AdapterView<?> parent) {
}
});
font_size = (Spinner) findViewById(R.id.font_size);
add_strings(font_size, sizes, AltosDroidPreferences.font_size());
font_size.setOnItemSelectedListener(new OnItemSelectedListener() {
public void onItemSelected(AdapterView<?> parent, View view, int pos, long id) {
set_font_size(pos);
}
public void onNothingSelected(AdapterView<?> parent) {
}
});
map_type = (Spinner) findViewById(R.id.map_type);
add_strings(map_type, map_types, default_map_type_pos());
map_type.setOnItemSelectedListener(new OnItemSelectedListener() {
public void onItemSelected(AdapterView<?> parent, View view, int pos, long id) {
select_map_type(pos);
}
public void onNothingSelected(AdapterView<?> parent) {
}
});
map_source = (Spinner) findViewById(R.id.map_source);
add_strings(map_source, map_sources, default_map_source_pos());
map_source.setOnItemSelectedListener(new OnItemSelectedListener() {
public void onItemSelected(AdapterView<?> parent, View view, int pos, long id) {
select_map_source(pos);
}
public void onNothingSelected(AdapterView<?> parent) {
}
});
manage_frequencies = (Button) findViewById(R.id.manage_frequencies);
manage_frequencies.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
manage_frequencies();
}
});
preload_maps = (Button) findViewById(R.id.preload_maps);
preload_maps.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
preload_maps();
}
});
done = (Button) findViewById(R.id.done);
done.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
done();
}
});
// Set result for when the user backs out
setResult(Activity.RESULT_CANCELED);
}
@Override
protected void onStart() {
super.onStart();
doBindService();
}
@Override
protected void onStop() {
super.onStop();
doUnbindService();
}
}

View File

@@ -0,0 +1,143 @@
/*
* Copyright © 2013 Mike Beattie <mike@ethernal.org>
*
* 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 org.altusmetrum.altoslib_14.*;
import android.os.Bundle;
import android.view.*;
import android.widget.*;
import android.location.Location;
public class TabFlight extends AltosDroidTab {
private TextView speed_view;
private TextView height_view;
private TextView altitude_view;
private View tilt_view;
private TextView tilt_value;
private TextView max_speed_view;
private TextView max_height_view;
private TextView max_altitude_view;
private TextView elevation_view;
private TextView range_view;
private TextView bearing_view;
private TextView compass_view;
private TextView distance_view;
private TextView latitude_view;
private TextView longitude_view;
private View apogee_view;
private TextView apogee_voltage_view;
private TextView apogee_voltage_label;
private GoNoGoLights apogee_lights;
private View main_view;
private TextView main_voltage_view;
private TextView main_voltage_label;
private GoNoGoLights main_lights;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.tab_flight, container, false);
speed_view = (TextView) v.findViewById(R.id.speed_value);
height_view = (TextView) v.findViewById(R.id.height_value);
altitude_view = (TextView) v.findViewById(R.id.altitude_value);
tilt_view = (View) v.findViewById(R.id.tilt_view);
tilt_value = (TextView) v.findViewById(R.id.tilt_value);
max_speed_view = (TextView) v.findViewById(R.id.max_speed_value);
max_height_view = (TextView) v.findViewById(R.id.max_height_value);
max_altitude_view= (TextView) v.findViewById(R.id.max_altitude_value);
elevation_view = (TextView) v.findViewById(R.id.elevation_value);
range_view = (TextView) v.findViewById(R.id.range_value);
bearing_view = (TextView) v.findViewById(R.id.bearing_value);
compass_view = (TextView) v.findViewById(R.id.compass_value);
distance_view = (TextView) v.findViewById(R.id.distance_value);
latitude_view = (TextView) v.findViewById(R.id.lat_value);
longitude_view = (TextView) v.findViewById(R.id.lon_value);
apogee_view = v.findViewById(R.id.apogee_view);
apogee_voltage_view = (TextView) v.findViewById(R.id.apogee_voltage_value);
apogee_lights = new GoNoGoLights((ImageView) v.findViewById(R.id.apogee_redled),
(ImageView) v.findViewById(R.id.apogee_greenled),
getResources());
apogee_voltage_label = (TextView) v.findViewById(R.id.apogee_voltage_label);
main_view = v.findViewById(R.id.main_view);
main_voltage_view = (TextView) v.findViewById(R.id.main_voltage_value);
main_lights = new GoNoGoLights((ImageView) v.findViewById(R.id.main_redled),
(ImageView) v.findViewById(R.id.main_greenled),
getResources());
main_voltage_label = (TextView) v.findViewById(R.id.main_voltage_label);
return v;
}
public String tab_name() { return AltosDroid.tab_flight_name; }
public void show(TelemetryState telem_state, AltosState state, AltosGreatCircle from_receiver, Location receiver) {
if (state != null) {
set_value(speed_view, AltosConvert.speed, 1, state.speed());
set_value(height_view, AltosConvert.height, 1, state.height());
set_value(altitude_view, AltosConvert.height, 1, state.altitude());
double orient = state.orient();
if (orient == AltosLib.MISSING) {
tilt_view.setVisibility(View.GONE);
} else {
tilt_value.setText(AltosDroid.number("%1.0f°", orient));
tilt_view.setVisibility(View.VISIBLE);
}
set_value(max_speed_view, AltosConvert.speed, 1, state.max_speed());
set_value(max_height_view, AltosConvert.height, 1, state.max_height());
set_value(max_altitude_view, AltosConvert.height, 1, state.max_altitude());
if (from_receiver != null) {
elevation_view.setText(AltosDroid.number("%1.0f°", from_receiver.elevation));
set_value(range_view, AltosConvert.distance, 1, from_receiver.range);
bearing_view.setText(AltosDroid.number("%1.0f°", from_receiver.bearing));
compass_view.setText(from_receiver.bearing_words(AltosGreatCircle.BEARING_LONG));
set_value(distance_view, AltosConvert.distance, 1, from_receiver.distance);
} else {
elevation_view.setText("<unknown>");
range_view.setText("<unknown>");
bearing_view.setText("<unknown>");
compass_view.setText("<unknown>");
distance_view.setText("<unknown>");
}
if (state.gps != null) {
latitude_view.setText(AltosDroid.pos(state.gps.lat, "N", "S"));
longitude_view.setText(AltosDroid.pos(state.gps.lon, "E", "W"));
}
if (state.apogee_voltage == AltosLib.MISSING) {
apogee_view.setVisibility(View.GONE);
} else {
apogee_voltage_view.setText(AltosDroid.number("%1.2f V", state.apogee_voltage));
apogee_lights.set(state.apogee_voltage > 3.2, state.apogee_voltage == AltosLib.MISSING);
apogee_view.setVisibility(View.VISIBLE);
}
if (state.main_voltage == AltosLib.MISSING) {
main_view.setVisibility(View.GONE);
} else {
main_voltage_view.setText(AltosDroid.number("%1.2f V", state.main_voltage));
main_lights.set(state.main_voltage > 3.2, state.main_voltage == AltosLib.MISSING);
main_view.setVisibility(View.VISIBLE);
}
}
}
}

View File

@@ -0,0 +1,156 @@
/*
* Copyright © 2013 Mike Beattie <mike@ethernal.org>
*
* 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 org.altusmetrum.altoslib_14.*;
import android.app.Activity;
import android.os.Bundle;
import android.view.*;
import android.widget.*;
import android.location.Location;
public class TabMap extends AltosDroidTab implements AltosDroidMapSourceListener {
AltosLatLon here;
private TextView mDistanceView;
private TextView mBearingLabel;
private TextView mBearingView;
private TextView mTargetLatitudeView;
private TextView mTargetLongitudeView;
private TextView mReceiverLatitudeView;
private TextView mReceiverLongitudeView;
private AltosMapOffline map_offline;
private AltosMapOnline map_online;
private View view;
private int map_source;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
view = inflater.inflate(R.layout.tab_map, container, false);
int map_source = AltosDroidPreferences.map_source();
mDistanceView = (TextView)view.findViewById(R.id.distance_value);
mBearingLabel = (TextView)view.findViewById(R.id.bearing_label);
mBearingView = (TextView)view.findViewById(R.id.bearing_value);
mTargetLatitudeView = (TextView)view.findViewById(R.id.target_lat_value);
mTargetLongitudeView = (TextView)view.findViewById(R.id.target_lon_value);
mReceiverLatitudeView = (TextView)view.findViewById(R.id.receiver_lat_value);
mReceiverLongitudeView = (TextView)view.findViewById(R.id.receiver_lon_value);
map_offline = (AltosMapOffline)view.findViewById(R.id.map_offline);
map_offline.onCreateView(altos_droid);
map_online = new AltosMapOnline(view.getContext());
map_online.onCreateView(altos_droid);
map_source_changed(AltosDroidPreferences.map_source());
AltosDroidPreferences.register_map_source_listener(this);
return view;
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
if (map_online != null)
getChildFragmentManager().beginTransaction().add(R.id.map_online, map_online.mMapFragment).commit();
}
@Override
public void onDestroyView() {
super.onDestroyView();
map_offline.onDestroyView();
map_online.onDestroyView();
AltosDroidPreferences.unregister_map_source_listener(this);
}
public String tab_name() { return AltosDroid.tab_map_name; }
private void center(double lat, double lon, double accuracy) {
if (map_offline != null)
map_offline.center(lat, lon, accuracy);
if (map_online != null)
map_online.center(lat, lon, accuracy);
}
public void show(TelemetryState telem_state, AltosState state, AltosGreatCircle from_receiver, Location receiver) {
if (from_receiver != null) {
String direction = AltosDroid.direction(from_receiver, receiver);
if (direction != null) {
mBearingLabel.setText("Direction");
mBearingView.setText(direction);
} else {
mBearingLabel.setText("Bearing");
mBearingView.setText(String.format("%3.0f°", from_receiver.bearing));
}
set_value(mDistanceView, AltosConvert.distance, 6, from_receiver.distance);
} else {
mBearingLabel.setText("Bearing");
mBearingView.setText("");
set_value(mDistanceView, AltosConvert.distance, 6, AltosLib.MISSING);
}
if (state != null) {
if (state.gps != null) {
mTargetLatitudeView.setText(AltosDroid.pos(state.gps.lat, "N", "S"));
mTargetLongitudeView.setText(AltosDroid.pos(state.gps.lon, "E", "W"));
}
}
if (receiver != null) {
double accuracy;
here = new AltosLatLon(receiver.getLatitude(), receiver.getLongitude());
if (receiver.hasAccuracy())
accuracy = receiver.getAccuracy();
else
accuracy = 1000;
mReceiverLatitudeView.setText(AltosDroid.pos(here.lat, "N", "S"));
mReceiverLongitudeView.setText(AltosDroid.pos(here.lon, "E", "W"));
center (here.lat, here.lon, accuracy);
}
if (map_source == AltosDroidPreferences.MAP_SOURCE_OFFLINE) {
if (map_offline != null)
map_offline.show(telem_state, state, from_receiver, receiver);
} else {
if (map_online != null)
map_online.show(telem_state, state, from_receiver, receiver);
}
}
public void map_source_changed(int map_source) {
this.map_source = map_source;
if (map_source == AltosDroidPreferences.MAP_SOURCE_OFFLINE) {
if (map_online != null)
map_online.set_visible(false);
if (map_offline != null) {
map_offline.set_visible(true);
map_offline.show(last_telem_state, last_state, last_from_receiver, last_receiver);
}
} else {
if (map_offline != null)
map_offline.set_visible(false);
if (map_online != null) {
map_online.set_visible(true);
map_online.show(last_telem_state, last_state, last_from_receiver, last_receiver);
}
}
}
public TabMap() {
}
}

View File

@@ -0,0 +1,259 @@
/*
* Copyright © 2013 Mike Beattie <mike@ethernal.org>
*
* 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 org.altusmetrum.altoslib_14.*;
import android.os.Bundle;
import android.view.*;
import android.widget.*;
import android.location.Location;
public class TabPad extends AltosDroidTab {
private TextView battery_voltage_view;
private GoNoGoLights battery_lights;
private TableRow receiver_row;
private TextView receiver_voltage_view;
private TextView receiver_voltage_label;
private GoNoGoLights receiver_voltage_lights;
private TableRow apogee_row;
private TextView apogee_voltage_view;
private TextView apogee_voltage_label;
private GoNoGoLights apogee_lights;
private TableRow main_row;
private TextView main_voltage_view;
private TextView main_voltage_label;
private GoNoGoLights main_lights;
private TextView data_logging_view;
private GoNoGoLights data_logging_lights;
private TextView gps_locked_view;
private GoNoGoLights gps_locked_lights;
private TextView gps_ready_view;
private GoNoGoLights gps_ready_lights;
private TextView receiver_latitude_view;
private TextView receiver_longitude_view;
private TextView receiver_altitude_view;
private TableRow[] ignite_row = new TableRow[4];
private TextView[] ignite_voltage_view = new TextView[4];
private TextView[] ignite_voltage_label = new TextView[4];
private GoNoGoLights[] ignite_lights = new GoNoGoLights[4];
private View tilt_view;
private TextView tilt_value;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
AltosDebug.debug("TabPad onCreateView\n");
View v = inflater.inflate(R.layout.tab_pad, container, false);
battery_voltage_view = (TextView) v.findViewById(R.id.battery_voltage_value);
battery_lights = new GoNoGoLights((ImageView) v.findViewById(R.id.battery_redled),
(ImageView) v.findViewById(R.id.battery_greenled),
getResources());
receiver_row = (TableRow) v.findViewById(R.id.receiver_row);
receiver_voltage_view = (TextView) v.findViewById(R.id.receiver_voltage_value);
receiver_voltage_label = (TextView) v.findViewById(R.id.receiver_voltage_label);
receiver_voltage_lights = new GoNoGoLights((ImageView) v.findViewById(R.id.receiver_redled),
(ImageView) v.findViewById(R.id.receiver_greenled),
getResources());
apogee_row = (TableRow) v.findViewById(R.id.apogee_row);
apogee_voltage_view = (TextView) v.findViewById(R.id.apogee_voltage_value);
apogee_voltage_label = (TextView) v.findViewById(R.id.apogee_voltage_label);
apogee_lights = new GoNoGoLights((ImageView) v.findViewById(R.id.apogee_redled),
(ImageView) v.findViewById(R.id.apogee_greenled),
getResources());
main_row = (TableRow) v.findViewById(R.id.main_row);
main_voltage_view = (TextView) v.findViewById(R.id.main_voltage_value);
main_voltage_label = (TextView) v.findViewById(R.id.main_voltage_label);
main_lights = new GoNoGoLights((ImageView) v.findViewById(R.id.main_redled),
(ImageView) v.findViewById(R.id.main_greenled),
getResources());
data_logging_view = (TextView) v.findViewById(R.id.logging_value);
data_logging_lights = new GoNoGoLights((ImageView) v.findViewById(R.id.logging_redled),
(ImageView) v.findViewById(R.id.logging_greenled),
getResources());
gps_locked_view = (TextView) v.findViewById(R.id.gps_locked_value);
gps_locked_lights = new GoNoGoLights((ImageView) v.findViewById(R.id.gps_locked_redled),
(ImageView) v.findViewById(R.id.gps_locked_greenled),
getResources());
gps_ready_view = (TextView) v.findViewById(R.id.gps_ready_value);
gps_ready_lights = new GoNoGoLights((ImageView) v.findViewById(R.id.gps_ready_redled),
(ImageView) v.findViewById(R.id.gps_ready_greenled),
getResources());
tilt_view = (View) v.findViewById(R.id.tilt_view);
tilt_value = (TextView) v.findViewById(R.id.tilt_value);
for (int i = 0; i < 4; i++) {
int row_id, view_id, label_id, lights_id;
int red_id, green_id;
switch (i) {
case 0:
default:
row_id = R.id.ignite_a_row;
view_id = R.id.ignite_a_voltage_value;
label_id = R.id.ignite_a_voltage_label;
red_id = R.id.ignite_a_redled;
green_id = R.id.ignite_a_greenled;
break;
case 1:
row_id = R.id.ignite_b_row;
view_id = R.id.ignite_b_voltage_value;
label_id = R.id.ignite_b_voltage_label;
red_id = R.id.ignite_b_redled;
green_id = R.id.ignite_b_greenled;
break;
case 2:
row_id = R.id.ignite_c_row;
view_id = R.id.ignite_c_voltage_value;
label_id = R.id.ignite_c_voltage_label;
red_id = R.id.ignite_c_redled;
green_id = R.id.ignite_c_greenled;
break;
case 3:
row_id = R.id.ignite_d_row;
view_id = R.id.ignite_d_voltage_value;
label_id = R.id.ignite_d_voltage_label;
red_id = R.id.ignite_d_redled;
green_id = R.id.ignite_d_greenled;
break;
}
ignite_row[i] = (TableRow) v.findViewById(row_id);
ignite_voltage_view[i] = (TextView) v.findViewById(view_id);
ignite_voltage_label[i] = (TextView) v.findViewById(label_id);
ignite_lights[i] = new GoNoGoLights((ImageView) v.findViewById(red_id),
(ImageView) v.findViewById(green_id),
getResources());
}
receiver_latitude_view = (TextView) v.findViewById(R.id.receiver_lat_value);
receiver_longitude_view = (TextView) v.findViewById(R.id.receiver_lon_value);
receiver_altitude_view = (TextView) v.findViewById(R.id.receiver_alt_value);
AltosDebug.debug("TabPad onCreateView done battery_voltage_view %s\n", battery_voltage_view);
return v;
}
public String tab_name() { return AltosDroid.tab_pad_name; }
public void show(TelemetryState telem_state, AltosState state, AltosGreatCircle from_receiver, Location receiver) {
AltosDebug.debug("pad show state %b bvv %s\n", state != null, battery_voltage_view);
if (state != null) {
battery_voltage_view.setText(AltosDroid.number("%1.2f V", state.battery_voltage));
battery_lights.set(state.battery_voltage >= AltosLib.ao_battery_good, state.battery_voltage == AltosLib.MISSING);
if (state.apogee_voltage == AltosLib.MISSING) {
apogee_row.setVisibility(View.GONE);
} else {
apogee_voltage_view.setText(AltosDroid.number("%1.2f V", state.apogee_voltage));
apogee_row.setVisibility(View.VISIBLE);
}
apogee_lights.set(state.apogee_voltage >= AltosLib.ao_igniter_good, state.apogee_voltage == AltosLib.MISSING);
if (state.main_voltage == AltosLib.MISSING) {
main_row.setVisibility(View.GONE);
} else {
main_voltage_view.setText(AltosDroid.number("%1.2f V", state.main_voltage));
main_row.setVisibility(View.VISIBLE);
}
main_lights.set(state.main_voltage >= AltosLib.ao_igniter_good, state.main_voltage == AltosLib.MISSING);
int num_igniter = state.igniter_voltage == null ? 0 : state.igniter_voltage.length;
for (int i = 0; i < 4; i++) {
double voltage = i >= num_igniter ? AltosLib.MISSING : state.igniter_voltage[i];
if (voltage == AltosLib.MISSING) {
ignite_row[i].setVisibility(View.GONE);
} else {
ignite_voltage_view[i].setText(AltosDroid.number("%1.2f V", voltage));
ignite_row[i].setVisibility(View.VISIBLE);
}
ignite_lights[i].set(voltage >= AltosLib.ao_igniter_good, voltage == AltosLib.MISSING);
}
if (state.cal_data().flight != 0) {
if (state.state() <= AltosLib.ao_flight_pad)
data_logging_view.setText("Ready to record");
else if (state.state() < AltosLib.ao_flight_landed)
data_logging_view.setText("Recording data");
else
data_logging_view.setText("Recorded data");
} else {
data_logging_view.setText("Storage full");
}
data_logging_lights.set(state.cal_data().flight != 0, state.cal_data().flight == AltosLib.MISSING);
if (state.gps != null) {
int soln = state.gps.nsat;
int nsat = state.gps.cc_gps_sat != null ? state.gps.cc_gps_sat.length : 0;
gps_locked_view.setText(String.format("%d in soln, %d in view", soln, nsat));
gps_locked_lights.set(state.gps.locked && state.gps.nsat >= 4, false);
if (state.gps_ready)
gps_ready_view.setText("Ready");
else
gps_ready_view.setText(AltosDroid.integer("Waiting %d", state.gps_waiting));
} else
gps_locked_lights.set(false, true);
gps_ready_lights.set(state.gps_ready, state.gps == null);
double orient = state.orient();
if (orient == AltosLib.MISSING) {
tilt_view.setVisibility(View.GONE);
} else {
tilt_value.setText(AltosDroid.number("%1.0f°", orient));
tilt_view.setVisibility(View.VISIBLE);
}
}
if (telem_state != null) {
if (telem_state.receiver_battery == AltosLib.MISSING) {
receiver_row.setVisibility(View.GONE);
} else {
receiver_voltage_view.setText(AltosDroid.number("%1.2f V", telem_state.receiver_battery));
receiver_row.setVisibility(View.VISIBLE);
}
receiver_voltage_lights.set(telem_state.receiver_battery >= AltosLib.ao_battery_good, telem_state.receiver_battery == AltosLib.MISSING);
}
if (receiver != null) {
double altitude = AltosLib.MISSING;
if (receiver.hasAltitude())
altitude = receiver.getAltitude();
String lat_text = AltosDroid.pos(receiver.getLatitude(), "N", "S");
String lon_text = AltosDroid.pos(receiver.getLongitude(), "E", "W");
AltosDebug.debug("lat %s lon %s\n", lat_text, lon_text);
receiver_latitude_view.setText(lat_text);
receiver_longitude_view.setText(lon_text);
set_value(receiver_altitude_view, AltosConvert.height, 1, altitude);
}
}
}

View File

@@ -0,0 +1,88 @@
/*
* Copyright © 2013 Mike Beattie <mike@ethernal.org>
*
* 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 org.altusmetrum.altoslib_14.*;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import android.location.Location;
public class TabRecover extends AltosDroidTab {
private TextView mBearingView;
private TextView mDirectionView;
private TextView mDistanceView;
private TextView mTargetLatitudeView;
private TextView mTargetLongitudeView;
private TextView mReceiverLatitudeView;
private TextView mReceiverLongitudeView;
private TextView mMaxHeightView;
private TextView mMaxSpeedView;
private TextView mMaxAccelView;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.tab_recover, container, false);
mBearingView = (TextView) v.findViewById(R.id.bearing_value);
mDirectionView = (TextView) v.findViewById(R.id.direction_value);
mDistanceView = (TextView) v.findViewById(R.id.distance_value);
mTargetLatitudeView = (TextView) v.findViewById(R.id.target_lat_value);
mTargetLongitudeView = (TextView) v.findViewById(R.id.target_lon_value);
mReceiverLatitudeView = (TextView) v.findViewById(R.id.receiver_lat_value);
mReceiverLongitudeView = (TextView) v.findViewById(R.id.receiver_lon_value);
mMaxHeightView = (TextView) v.findViewById(R.id.max_height_value);
mMaxSpeedView = (TextView) v.findViewById(R.id.max_speed_value);
mMaxAccelView = (TextView) v.findViewById(R.id.max_accel_value);
return v;
}
public String tab_name() { return AltosDroid.tab_recover_name; }
public void show(TelemetryState telem_state, AltosState state, AltosGreatCircle from_receiver, Location receiver) {
if (from_receiver != null) {
mBearingView.setText(String.format("%1.0f°", from_receiver.bearing));
set_value(mDistanceView, AltosConvert.distance, 1, from_receiver.distance);
String direction = AltosDroid.direction(from_receiver, receiver);
if (direction == null)
mDirectionView.setText("");
else
mDirectionView.setText(direction);
}
if (state != null && state.gps != null) {
mTargetLatitudeView.setText(AltosDroid.pos(state.gps.lat, "N", "S"));
mTargetLongitudeView.setText(AltosDroid.pos(state.gps.lon, "E", "W"));
}
if (receiver != null) {
mReceiverLatitudeView.setText(AltosDroid.pos(receiver.getLatitude(), "N", "S"));
mReceiverLongitudeView.setText(AltosDroid.pos(receiver.getLongitude(), "E", "W"));
}
if (state != null) {
set_value(mMaxHeightView, AltosConvert.height, 1, state.max_height());
set_value(mMaxAccelView, AltosConvert.accel, 1, state.max_acceleration());
set_value(mMaxSpeedView, AltosConvert.speed, 1, state.max_speed());
}
}
}

View File

@@ -0,0 +1,160 @@
/*
* Copyright (C) 2009 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.altusmetrum.AltosDroid;
import java.util.ArrayList;
import android.content.Context;
import android.os.Bundle;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
import androidx.fragment.app.FragmentPagerAdapter;
import androidx.viewpager.widget.ViewPager;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TabHost;
import android.widget.TabWidget;
/**
* This is a helper class that implements the management of tabs and all
* details of connecting a ViewPager with associated TabHost. It relies on a
* trick. Normally a tab host has a simple API for supplying a View or
* Intent that each tab will show. This is not sufficient for switching
* between pages. So instead we make the content part of the tab host
* 0dp high (it is not shown) and the TabsAdapter supplies its own dummy
* view to show as the tab content. It listens to changes in tabs, and takes
* care of switch to the correct paged in the ViewPager whenever the selected
* tab changes.
*/
public class TabsAdapter extends FragmentPagerAdapter
implements TabHost.OnTabChangeListener, ViewPager.OnPageChangeListener {
private final Context mContext;
private final TabHost mTabHost;
private final ViewPager mViewPager;
private final ArrayList<TabInfo> mTabs = new ArrayList<TabInfo>();
private int position;
static class TabInfo {
private final String tag;
private final Class<?> clss;
private final Bundle args;
private Fragment fragment;
TabInfo(String _tag, Class<?> _class, Bundle _args, Fragment _fragment) {
tag = _tag;
clss = _class;
args = _args;
fragment = _fragment;
}
}
static class DummyTabFactory implements TabHost.TabContentFactory {
private final Context mContext;
public DummyTabFactory(Context context) {
mContext = context;
}
public View createTabContent(String tag) {
View v = new View(mContext);
v.setMinimumWidth(0);
v.setMinimumHeight(0);
return v;
}
}
public TabsAdapter(FragmentActivity activity, TabHost tabHost, ViewPager pager) {
super(activity.getSupportFragmentManager());
mContext = activity;
mTabHost = tabHost;
mViewPager = pager;
mTabHost.setOnTabChangedListener(this);
mViewPager.setAdapter(this);
mViewPager.addOnPageChangeListener(this);
}
public void addTab(TabHost.TabSpec tabSpec, Class<?> clss, Bundle args, Fragment fragment) {
tabSpec.setContent(new DummyTabFactory(mContext));
String tag = tabSpec.getTag();
TabInfo info = new TabInfo(tag, clss, args, fragment);
mTabs.add(info);
mTabHost.addTab(tabSpec);
notifyDataSetChanged();
}
@Override
public int getCount() {
return mTabs.size();
}
@Override
public Fragment getItem(int position) {
TabInfo info = mTabs.get(position);
AltosDebug.debug("TabsAdapter.getItem(%d)", position);
if (info.fragment == null)
info.fragment = Fragment.instantiate(mContext, info.clss.getName(), info.args);
return info.fragment;
}
public Fragment currentItem() {
TabInfo info = mTabs.get(position);
return info.fragment;
}
public void onTabChanged(String tabId) {
AltosDroidTab prev_frag = (AltosDroidTab) mTabs.get(position).fragment;
position = mTabHost.getCurrentTab();
AltosDroidTab cur_frag = (AltosDroidTab) mTabs.get(position).fragment;
AltosDebug.debug("TabsAdapter.onTabChanged(%s) = %d cur %s prev %s", tabId, position, cur_frag, prev_frag);
if (prev_frag != cur_frag) {
if (prev_frag != null) {
prev_frag.set_visible(false);
}
}
/* This happens when the tab is selected before any of them
* have been created, like during rotation
*/
if (cur_frag != null)
cur_frag.set_visible(true);
mViewPager.setCurrentItem(position);
}
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
public void onPageSelected(int position) {
// Unfortunately when TabHost changes the current tab, it kindly
// also takes care of putting focus on it when not in touch mode.
// The jerk.
// This hack tries to prevent this from pulling focus out of our
// ViewPager.
TabWidget widget = mTabHost.getTabWidget();
int oldFocusability = widget.getDescendantFocusability();
widget.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
mTabHost.setCurrentTab(position);
widget.setDescendantFocusability(oldFocusability);
}
public void onPageScrollStateChanged(int state) {
}
}

View File

@@ -0,0 +1,97 @@
/*
* Copyright © 2021 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.io.*;
import org.altusmetrum.altoslib_14.*;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Environment;
public class TelemetryLogger implements AltosLogTrace {
private TelemetryService service = null;
private AltosLink link = null;
private AltosLog logger = null;
private BroadcastReceiver mExternalStorageReceiver;
/* AltosLogTrace interface */
public void trace(String format, Object ... arguments) {
AltosDebug.debug(format, arguments);
}
public void open_failed(File file) {
service.send_file_failed_to_clients(file);
}
public TelemetryLogger(TelemetryService in_service, AltosLink in_link) {
service = in_service;
link = in_link;
startWatchingExternalStorage();
}
public void stop() {
stopWatchingExternalStorage();
close();
}
private void close() {
if (logger != null) {
AltosDebug.debug("Shutting down Telemetry Logging");
logger.close();
logger = null;
}
}
void handleExternalStorageState() {
String state = Environment.getExternalStorageState();
if (Environment.MEDIA_MOUNTED.equals(state)) {
if (logger == null) {
AltosDebug.debug("Starting up Telemetry Logging");
logger = new AltosLog(link,this);
}
} else {
AltosDebug.debug("External Storage not present - stopping");
close();
}
}
void startWatchingExternalStorage() {
mExternalStorageReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
handleExternalStorageState();
}
};
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_MEDIA_MOUNTED);
filter.addAction(Intent.ACTION_MEDIA_REMOVED);
service.registerReceiver(mExternalStorageReceiver, filter);
handleExternalStorageState();
}
void stopWatchingExternalStorage() {
service.unregisterReceiver(mExternalStorageReceiver);
}
}

View File

@@ -0,0 +1,91 @@
/*
* Copyright © 2011 Keith Packard <keithp@keithp.com>
* Copyright © 2012 Mike Beattie <mike@ethernal.org>
*
* 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.text.*;
import java.io.*;
import java.util.concurrent.*;
import android.os.Handler;
import org.altusmetrum.altoslib_14.*;
public class TelemetryReader extends Thread {
int crc_errors;
Handler handler;
AltosLink link;
LinkedBlockingQueue<AltosLine> telemQueue;
public AltosTelemetry read() throws ParseException, AltosCRCException, InterruptedException, IOException {
AltosLine l = telemQueue.take();
if (l.line == null)
throw new IOException("IO error");
AltosTelemetry telem = AltosTelemetryLegacy.parse(l.line);
return telem;
}
public void close() {
link.remove_monitor(telemQueue);
link = null;
telemQueue.clear();
telemQueue = null;
}
public void run() {
try {
AltosDebug.debug("starting loop");
while (telemQueue != null) {
try {
AltosTelemetry telem = read();
telem.set_frequency(link.frequency);
handler.obtainMessage(TelemetryService.MSG_TELEMETRY, telem).sendToTarget();
} catch (ParseException pp) {
AltosDebug.error("Parse error: %d \"%s\"", pp.getErrorOffset(), pp.getMessage());
} catch (AltosCRCException ce) {
++crc_errors;
handler.obtainMessage(TelemetryService.MSG_CRC_ERROR, new Integer(crc_errors)).sendToTarget();
}
}
} catch (InterruptedException ee) {
} catch (IOException ie) {
AltosDebug.error("IO exception in telemetry reader");
handler.obtainMessage(TelemetryService.MSG_DISCONNECTED, link).sendToTarget();
} finally {
close();
}
}
public TelemetryReader (AltosLink in_link, Handler in_handler) {
AltosDebug.debug("connected TelemetryReader create started");
link = in_link;
handler = in_handler;
telemQueue = new LinkedBlockingQueue<AltosLine>();
link.add_monitor(telemQueue);
link.set_telemetry(AltosLib.ao_telemetry_standard);
AltosDebug.debug("connected TelemetryReader created");
}
}

View File

@@ -0,0 +1,748 @@
/*
* Copyright © 2012 Mike Beattie <mike@ethernal.org>
*
* 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.concurrent.TimeoutException;
import java.io.*;
import java.util.*;
import android.app.*;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothAdapter;
import android.graphics.Color;
import android.hardware.usb.*;
import android.content.Intent;
import android.content.Context;
import android.os.*;
import android.os.Build.*;
import android.widget.Toast;
import androidx.core.app.NotificationCompat;
import org.altusmetrum.altoslib_14.*;
public class TelemetryService extends Service implements AltosIdleMonitorListener {
static final int MSG_REGISTER_CLIENT = 1;
static final int MSG_UNREGISTER_CLIENT = 2;
static final int MSG_CONNECT = 3;
static final int MSG_OPEN_USB = 4;
static final int MSG_CONNECTED = 5;
static final int MSG_CONNECT_FAILED = 6;
static final int MSG_DISCONNECTED = 7;
static final int MSG_TELEMETRY = 8;
static final int MSG_SETFREQUENCY = 9;
static final int MSG_CRC_ERROR = 10;
static final int MSG_SETBAUD = 11;
static final int MSG_DISCONNECT = 12;
static final int MSG_DELETE_SERIAL = 13;
static final int MSG_BLUETOOTH_ENABLED = 14;
static final int MSG_MONITOR_IDLE_START= 15;
static final int MSG_MONITOR_IDLE_STOP = 16;
static final int MSG_REBOOT = 17;
static final int MSG_IGNITER_QUERY = 18;
static final int MSG_IGNITER_FIRE = 19;
// Unique Identification Number for the Notification.
// We use it on Notification start, and to cancel it.
private int NOTIFICATION = R.string.telemetry_service_label;
//private NotificationManager mNM;
ArrayList<Messenger> clients = new ArrayList<Messenger>(); // Keeps track of all current registered clients.
final Handler handler = new IncomingHandler(this);
final Messenger messenger = new Messenger(handler); // Target we publish for clients to send messages to IncomingHandler.
// Name of the connected device
DeviceAddress address;
private AltosDroidLink altos_link = null;
private TelemetryReader telemetry_reader = null;
private TelemetryLogger telemetry_logger = null;
// Local Bluetooth adapter
private BluetoothAdapter bluetooth_adapter = null;
// Last data seen; send to UI when it starts
private TelemetryState telemetry_state;
// Idle monitor if active
AltosIdleMonitor idle_monitor = null;
// Igniter bits
AltosIgnite ignite = null;
boolean ignite_running;
// Handler of incoming messages from clients.
static class IncomingHandler extends Handler {
private final WeakReference<TelemetryService> service;
IncomingHandler(TelemetryService s) { service = new WeakReference<TelemetryService>(s); }
@Override
public void handleMessage(Message msg) {
DeviceAddress address;
TelemetryService s = service.get();
AltosDroidLink bt = null;
if (s == null)
return;
switch (msg.what) {
/* Messages from application */
case MSG_REGISTER_CLIENT:
s.add_client(msg.replyTo);
break;
case MSG_UNREGISTER_CLIENT:
s.remove_client(msg.replyTo);
break;
case MSG_CONNECT:
AltosDebug.debug("Connect command received");
address = (DeviceAddress) msg.obj;
AltosDroidPreferences.set_active_device(address);
s.start_altos_bluetooth(address, false);
break;
case MSG_OPEN_USB:
AltosDebug.debug("Open USB command received");
UsbDevice device = (UsbDevice) msg.obj;
s.start_usb(device);
break;
case MSG_DISCONNECT:
AltosDebug.debug("Disconnect command received");
s.address = null;
if (!(Boolean) msg.obj)
AltosDroidPreferences.set_active_device(null);
s.disconnect(true);
break;
case MSG_DELETE_SERIAL:
AltosDebug.debug("Delete Serial command received");
s.delete_serial((Integer) msg.obj);
break;
case MSG_SETFREQUENCY:
AltosDebug.debug("MSG_SETFREQUENCY");
s.telemetry_state.frequency = (Double) msg.obj;
if (s.idle_monitor != null) {
s.idle_monitor.set_frequency(s.telemetry_state.frequency);
} else if (s.telemetry_state.connect == TelemetryState.CONNECT_CONNECTED) {
try {
s.altos_link.set_radio_frequency(s.telemetry_state.frequency);
s.altos_link.save_frequency();
} catch (InterruptedException e) {
} catch (TimeoutException e) {
}
}
s.send_to_clients();
break;
case MSG_SETBAUD:
AltosDebug.debug("MSG_SETBAUD");
s.telemetry_state.telemetry_rate = (Integer) msg.obj;
if (s.telemetry_state.connect == TelemetryState.CONNECT_CONNECTED) {
s.altos_link.set_telemetry_rate(s.telemetry_state.telemetry_rate);
s.altos_link.save_telemetry_rate();
}
s.send_to_clients();
break;
/*
*Messages from AltosBluetooth
*/
case MSG_CONNECTED:
AltosDebug.debug("MSG_CONNECTED");
bt = (AltosDroidLink) msg.obj;
if (bt != s.altos_link) {
AltosDebug.debug("Stale message");
break;
}
AltosDebug.debug("Connected to device");
try {
s.connected();
} catch (InterruptedException ie) {
}
break;
case MSG_CONNECT_FAILED:
AltosDebug.debug("MSG_CONNECT_FAILED");
bt = (AltosDroidLink) msg.obj;
if (bt != s.altos_link) {
AltosDebug.debug("Stale message");
break;
}
if (s.address != null) {
AltosDebug.debug("Connection failed... retrying");
s.start_altos_bluetooth(s.address, true);
} else {
s.disconnect(true);
}
break;
case MSG_DISCONNECTED:
/* This can be sent by either AltosDroidLink or TelemetryReader */
AltosDebug.debug("MSG_DISCONNECTED");
bt = (AltosDroidLink) msg.obj;
if (bt != s.altos_link) {
AltosDebug.debug("Stale message");
break;
}
if (s.address != null) {
AltosDebug.debug("Connection lost... retrying");
s.start_altos_bluetooth(s.address, true);
} else {
s.disconnect(true);
}
break;
/*
* Messages from TelemetryReader
*/
case MSG_TELEMETRY:
s.telemetry((AltosTelemetry) msg.obj);
break;
case MSG_CRC_ERROR:
// forward crc error messages
s.telemetry_state.crc_errors = (Integer) msg.obj;
s.send_to_clients();
break;
case MSG_BLUETOOTH_ENABLED:
AltosDebug.debug("TelemetryService notes that BT is now enabled");
address = AltosDroidPreferences.active_device();
if (address != null && !address.address.startsWith("USB"))
s.start_altos_bluetooth(address, false);
break;
case MSG_MONITOR_IDLE_START:
AltosDebug.debug("start monitor idle");
s.start_idle_monitor();
break;
case MSG_MONITOR_IDLE_STOP:
AltosDebug.debug("stop monitor idle");
s.stop_idle_monitor();
break;
case MSG_REBOOT:
AltosDebug.debug("reboot");
s.reboot_remote();
break;
case MSG_IGNITER_QUERY:
AltosDebug.debug("igniter query");
s.igniter_query(msg.replyTo);
break;
case MSG_IGNITER_FIRE:
AltosDebug.debug("igniter fire");
s.igniter_fire((String) msg.obj);
break;
default:
super.handleMessage(msg);
}
}
}
/* Handle telemetry packet
*/
private void telemetry(AltosTelemetry telem) {
AltosState state;
state = telemetry_state.get(telem.serial());
if (state == null)
state = new AltosState(new AltosCalData());
telem.provide_data(state);
telemetry_state.put(telem.serial(), state);
telemetry_state.quiet = false;
if (state != null) {
AltosPreferences.set_state(state,telem.serial());
}
send_to_clients();
}
/* Construct the message to deliver to clients
*/
private Message message() {
if (telemetry_state == null)
AltosDebug.debug("telemetry_state null!");
return Message.obtain(null, AltosDroid.MSG_STATE, telemetry_state);
}
/* A new friend has connected
*/
private void add_client(Messenger client) {
clients.add(client);
AltosDebug.debug("Client bound to service");
/* On connect, send the current state to the new client
*/
send_to_client(client);
send_idle_mode_to_client(client);
/* If we've got an address from a previous session, then
* go ahead and try to reconnect to the device
*/
if (address != null && telemetry_state.connect == TelemetryState.CONNECT_DISCONNECTED) {
AltosDebug.debug("Reconnecting now...");
start_altos_bluetooth(address, false);
}
}
/* A client has disconnected, clean up
*/
private void remove_client(Messenger client) {
clients.remove(client);
AltosDebug.debug("Client unbound from service");
/* When the list of clients is empty, stop the service if
* we have no current telemetry source
*/
if (clients.isEmpty() && telemetry_state.connect == TelemetryState.CONNECT_DISCONNECTED) {
AltosDebug.debug("No clients, no connection. Stopping\n");
stopSelf();
}
}
private void send_to_client(Messenger client) {
Message m = message();
try {
client.send(m);
} catch (RemoteException e) {
AltosDebug.error("Client %s disappeared", client.toString());
remove_client(client);
}
}
private void send_to_clients() {
for (Messenger client : clients)
send_to_client(client);
}
private void send_idle_mode_to_client(Messenger client) {
Message m = Message.obtain(null, AltosDroid.MSG_IDLE_MODE, idle_monitor != null);
try {
client.send(m);
} catch (RemoteException e) {
AltosDebug.error("Client %s disappeared", client.toString());
remove_client(client);
}
}
private void send_idle_mode_to_clients() {
for (Messenger client : clients)
send_idle_mode_to_client(client);
}
private void send_file_failed_to_client(Messenger client, File f) {
Message m = Message.obtain(null, AltosDroid.MSG_FILE_FAILED, f);
try {
client.send(m);
} catch (RemoteException e) {
AltosDebug.error("Client %s disappeared", client.toString());
remove_client(client);
}
}
public void send_file_failed_to_clients(File f) {
for (Messenger client : clients)
send_file_failed_to_client(client, f);
}
private void telemetry_start() {
if (telemetry_reader == null && idle_monitor == null && !ignite_running) {
telemetry_reader = new TelemetryReader(altos_link, handler);
telemetry_reader.start();
}
}
private void telemetry_stop() {
if (telemetry_reader != null) {
AltosDebug.debug("disconnect(): stopping TelemetryReader");
telemetry_reader.interrupt();
try {
telemetry_reader.join();
} catch (InterruptedException e) {
}
telemetry_reader = null;
}
}
private void disconnect(boolean notify) {
AltosDebug.debug("disconnect(): begin");
telemetry_state.connect = TelemetryState.CONNECT_DISCONNECTED;
telemetry_state.address = null;
if (idle_monitor != null)
stop_idle_monitor();
if (altos_link != null)
altos_link.closing();
stop_receiver_voltage_timer();
telemetry_stop();
if (telemetry_logger != null) {
AltosDebug.debug("disconnect(): stopping TelemetryLogger");
telemetry_logger.stop();
telemetry_logger = null;
}
if (altos_link != null) {
AltosDebug.debug("disconnect(): stopping AltosDroidLink");
altos_link.close();
altos_link = null;
ignite = null;
}
telemetry_state.config = null;
if (notify) {
AltosDebug.debug("disconnect(): send message to clients");
send_to_clients();
if (clients.isEmpty()) {
AltosDebug.debug("disconnect(): no clients, terminating");
stopSelf();
}
}
}
private void start_usb(UsbDevice device) {
AltosUsb d = new AltosUsb(this, device, handler);
if (d != null) {
disconnect(false);
altos_link = d;
try {
connected();
} catch (InterruptedException ie) {
}
}
}
private void delete_serial(int serial) {
telemetry_state.remove(serial);
AltosPreferences.remove_state(serial);
send_to_clients();
}
private void start_altos_bluetooth(DeviceAddress address, boolean pause) {
if (bluetooth_adapter == null || !bluetooth_adapter.isEnabled() || address.address == null)
return;
disconnect(false);
// Get the BluetoothDevice object
BluetoothDevice device = bluetooth_adapter.getRemoteDevice(address.address);
this.address = address;
AltosDebug.debug("start_altos_bluetooth(): Connecting to %s (%s)", device.getName(), device.getAddress());
altos_link = new AltosBluetooth(device, handler, pause);
telemetry_state.connect = TelemetryState.CONNECT_CONNECTING;
telemetry_state.address = address;
send_to_clients();
}
private void start_idle_monitor() {
if (altos_link != null && idle_monitor == null) {
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();
}
}
private void stop_idle_monitor() {
if (idle_monitor != null) {
try {
idle_monitor.abort();
} catch (InterruptedException ie) {
}
idle_monitor = null;
telemetry_state.idle_mode = false;
telemetry_start();
send_idle_mode_to_clients();
}
}
private void reboot_remote() {
if (altos_link != null) {
stop_idle_monitor();
try {
altos_link.start_remote();
altos_link.printf("r eboot\n");
altos_link.flush_output();
} catch (TimeoutException te) {
} catch (InterruptedException ie) {
} finally {
try {
altos_link.stop_remote();
} catch (InterruptedException ie) {
}
}
}
}
private void ensure_ignite() {
if (ignite == null)
ignite = new AltosIgnite(altos_link, true, false);
}
private synchronized void igniter_query(Messenger client) {
ensure_ignite();
HashMap<String,Integer> status_map = null;
ignite_running = true;
try {
stop_idle_monitor();
try {
status_map = ignite.status();
} catch (InterruptedException ie) {
AltosDebug.debug("ignite.status interrupted");
} catch (TimeoutException te) {
AltosDebug.debug("ignite.status timeout");
}
} finally {
ignite_running = false;
}
Message m = Message.obtain(null, AltosDroid.MSG_IGNITER_STATUS, status_map);
try {
client.send(m);
} catch (RemoteException e) {
}
}
private synchronized void igniter_fire(String igniter) {
ensure_ignite();
ignite_running = true;
stop_idle_monitor();
try {
ignite.fire(igniter);
} catch (InterruptedException ie) {
} finally {
ignite_running = false;
}
}
// Timer for receiver battery voltage monitoring
Timer receiver_voltage_timer;
private void update_receiver_voltage() {
if (altos_link != null && idle_monitor == null && !ignite_running) {
try {
double voltage = altos_link.monitor_battery();
telemetry_state.receiver_battery = voltage;
send_to_clients();
} catch (InterruptedException ie) {
}
}
}
private void stop_receiver_voltage_timer() {
if (receiver_voltage_timer != null) {
receiver_voltage_timer.cancel();
receiver_voltage_timer.purge();
receiver_voltage_timer = null;
}
}
private void start_receiver_voltage_timer() {
if (receiver_voltage_timer == null && altos_link.has_monitor_battery()) {
receiver_voltage_timer = new Timer();
receiver_voltage_timer.scheduleAtFixedRate(new TimerTask() { public void run() {update_receiver_voltage();}}, 1000L, 10000L);
}
}
private void connected() throws InterruptedException {
AltosDebug.debug("connected top");
AltosDebug.check_ui("connected\n");
try {
if (altos_link == null)
throw new InterruptedException("no bluetooth");
telemetry_state.config = altos_link.config_data();
altos_link.set_radio_frequency(telemetry_state.frequency);
altos_link.set_telemetry_rate(telemetry_state.telemetry_rate);
} catch (TimeoutException e) {
// If this timed out, then we really want to retry it, but
// probably safer to just retry the connection from scratch.
AltosDebug.debug("connected timeout");
if (address != null) {
AltosDebug.debug("connected timeout, retrying");
start_altos_bluetooth(address, true);
} else {
handler.obtainMessage(MSG_CONNECT_FAILED).sendToTarget();
disconnect(true);
}
return;
}
AltosDebug.debug("connected bluetooth configured");
telemetry_state.connect = TelemetryState.CONNECT_CONNECTED;
telemetry_state.address = address;
telemetry_start();
AltosDebug.debug("connected TelemetryReader started");
telemetry_logger = new TelemetryLogger(this, altos_link);
start_receiver_voltage_timer();
AltosDebug.debug("Notify UI of connection");
send_to_clients();
}
@Override
public void onCreate() {
AltosDebug.init(this);
// Initialise preferences
AltosDroidPreferences.init(this);
// Get local Bluetooth adapter
bluetooth_adapter = BluetoothAdapter.getDefaultAdapter();
telemetry_state = new TelemetryState();
// Create a reference to the NotificationManager so that we can update our notifcation text later
//mNM = (NotificationManager)getSystemService(NOTIFICATION_SERVICE);
telemetry_state.connect = TelemetryState.CONNECT_DISCONNECTED;
telemetry_state.address = null;
/* Pull the saved state information out of the preferences database
*/
ArrayList<Integer> serials = AltosPreferences.list_states();
telemetry_state.latest_serial = AltosPreferences.latest_state();
telemetry_state.quiet = true;
AltosDebug.debug("latest serial %d\n", telemetry_state.latest_serial);
for (int serial : serials) {
AltosState saved_state = AltosPreferences.state(serial);
if (saved_state != null) {
AltosDebug.debug("recovered old state serial %d flight %d",
serial,
saved_state.cal_data().flight);
if (saved_state.gps != null)
AltosDebug.debug("\tposition %f,%f",
saved_state.gps.lat,
saved_state.gps.lon);
telemetry_state.put(serial, saved_state);
} else {
AltosDebug.debug("Failed to recover state for %d", serial);
AltosPreferences.remove_state(serial);
}
}
}
private String createNotificationChannel(String channelId, String channelName) {
NotificationChannel chan = new NotificationChannel(
channelId, channelName, NotificationManager.IMPORTANCE_NONE);
chan.setLightColor(Color.BLUE);
chan.setLockscreenVisibility(Notification.VISIBILITY_PRIVATE);
NotificationManager service = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
service.createNotificationChannel(chan);
return channelId;
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
AltosDebug.debug("Received start id %d: %s", startId, intent);
int flag;
if (android.os.Build.VERSION.SDK_INT >= 31) // android.os.Build.VERSION_CODES.S
flag = 33554432; // PendingIntent.FLAG_MUTABLE
else
flag = 0;
// The PendingIntent to launch our activity if the user selects this notification
PendingIntent contentIntent = PendingIntent.getActivity(this, 0,
new Intent(this, AltosDroid.class), flag);
String channelId =
(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
? createNotificationChannel("altosdroid_telemetry", "AltosDroid Telemetry Service")
: "";
// Create notification to be displayed while the service runs
Notification notification = new NotificationCompat.Builder(this, channelId)
.setContentTitle(getText(R.string.telemetry_service_label))
.setContentText(getText(R.string.telemetry_service_started))
.setContentIntent(contentIntent)
.setWhen(System.currentTimeMillis())
.setOngoing(true)
.setSmallIcon(R.drawable.am_status_c)
// .setLargeIcon(R.drawable.am_status_c)
.build();
// Move us into the foreground.
startForeground(NOTIFICATION, notification);
/* Start bluetooth if we don't have a connection already */
if (intent != null &&
(telemetry_state.connect == TelemetryState.CONNECT_NONE ||
telemetry_state.connect == TelemetryState.CONNECT_DISCONNECTED))
{
String action = intent.getAction();
if (action.equals(AltosDroid.ACTION_BLUETOOTH)) {
DeviceAddress address = AltosDroidPreferences.active_device();
if (address != null && !address.address.startsWith("USB"))
start_altos_bluetooth(address, false);
}
}
// We want this service to continue running until it is explicitly
// stopped, so return sticky.
return START_STICKY;
}
@Override
public void onDestroy() {
// Stop the bluetooth Comms threads
disconnect(true);
// Demote us from the foreground, and cancel the persistent notification.
stopForeground(true);
// Tell the user we stopped.
Toast.makeText(this, R.string.telemetry_service_stopped, Toast.LENGTH_SHORT).show();
}
@Override
public IBinder onBind(Intent intent) {
return messenger.getBinder();
}
/* AltosIdleMonitorListener */
public void update(AltosState state, AltosListenerState listener_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();
}
public void failed() {
}
public void error(String reason) {
stop_idle_monitor();
}
}

View File

@@ -0,0 +1,86 @@
/*
* Copyright © 2012 Mike Beattie <mike@ethernal.org>
*
* 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 org.altusmetrum.altoslib_14.*;
public class TelemetryState {
public static final int CONNECT_NONE = 0;
public static final int CONNECT_DISCONNECTED = 1;
public static final int CONNECT_CONNECTING = 2;
public static final int CONNECT_CONNECTED = 3;
int connect;
DeviceAddress address;
AltosConfigData config;
int crc_errors;
double receiver_battery;
double frequency;
int telemetry_rate;
boolean idle_mode;
boolean quiet;
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;
config = null;
states = new HashMap<Integer,AltosState>();
crc_errors = 0;
receiver_battery = AltosLib.MISSING;
frequency = AltosPreferences.frequency(0);
telemetry_rate = AltosPreferences.telemetry_rate(0);
}
}

View File

@@ -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_14.*;
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);
}
}

View File

@@ -0,0 +1,5 @@
all:
make -C ../../../../../../.. $@
%:
make -C ../../../../../../.. $@

Binary file not shown.

After

Width:  |  Height:  |  Size: 994 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 804 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 703 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 595 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 584 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -0,0 +1,199 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright © 2012-2013 Mike Beattie <mike@ethernal.org>
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:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight="0"
android:orientation="vertical" >
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight="0"
android:baselineAligned="true"
android:orientation="horizontal" >
<RelativeLayout
android:id="@+id/callsign_container"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1" >
<TextView
android:id="@+id/callsign_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/callsign_label" />
<TextView
android:id="@+id/callsign_value"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/callsign_label"
android:text=""
android:textAppearance="?android:attr/textAppearanceSmall" />
</RelativeLayout>
<RelativeLayout
android:id="@+id/serial_container"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1" >
<TextView
android:id="@+id/serial_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/serial_label" />
<TextView
android:id="@+id/serial_value"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/serial_label"
android:textAppearance="?android:attr/textAppearanceSmall" />
</RelativeLayout>
<RelativeLayout
android:id="@+id/flight_container"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1" >
<TextView
android:id="@+id/flight_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/flight_label" />
<TextView
android:id="@+id/flight_value"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/flight_label"
android:textAppearance="?android:attr/textAppearanceSmall" />
</RelativeLayout>
<RelativeLayout
android:id="@+id/state_container"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1" >
<TextView
android:id="@+id/state_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/state_label" />
<TextView
android:id="@+id/state_value"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/state_label"
android:textAppearance="?android:attr/textAppearanceSmall" />
</RelativeLayout>
<RelativeLayout
android:id="@+id/rssi_container"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1" >
<TextView
android:id="@+id/rssi_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/rssi_label" />
<TextView
android:id="@+id/rssi_value"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/rssi_label"
android:textAppearance="?android:attr/textAppearanceSmall" />
</RelativeLayout>
<RelativeLayout
android:id="@+id/age_container"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1" >
<TextView
android:id="@+id/age_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/age_label" />
<TextView
android:id="@+id/age_value"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/age_label"
android:textAppearance="?android:attr/textAppearanceSmall" />
</RelativeLayout>
</LinearLayout>
<TabHost
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@android:id/tabhost"
android:layout_width="fill_parent"
android:layout_height="0dp"
android:layout_weight="1" >
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<TabWidget
android:id="@android:id/tabs"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="0"
android:orientation="horizontal" />
<FrameLayout
android:id="@android:id/tabcontent"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_weight="0" />
<org.altusmetrum.AltosDroid.AltosViewPager
android:id="@+id/pager"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
</LinearLayout>
</TabHost>
<TextView
android:id="@+id/version"
android:layout_width="fill_parent"
android:layout_height="10dip"
android:layout_weight="0"
android:gravity="bottom|right"
android:textSize="7sp"
android:typeface="monospace" />
</LinearLayout>

View File

@@ -0,0 +1,39 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2009 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_vertical"
>
<TextView android:id="@+id/title_left_text"
android:layout_alignParentLeft="true"
android:ellipsize="end"
android:singleLine="true"
style="?android:attr/windowTitleStyle"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_weight="1"
/>
<TextView android:id="@+id/title_right_text"
android:layout_alignParentRight="true"
android:ellipsize="end"
android:singleLine="true"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:textColor="#fff"
android:layout_weight="1"
/>
</RelativeLayout>

View File

@@ -0,0 +1,63 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright © 2016 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"
>
<Button android:id="@+id/button_scan"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/button_scan"
/>
<TextView android:id="@+id/title_new_devices"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/title_other_devices"
android:visibility="gone"
android:background="#666"
android:textColor="#fff"
android:paddingLeft="5dp"
/>
<ListView android:id="@+id/new_devices"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:fadeScrollbars="false"
android:scrollbars="vertical"
/>
<TextView android:id="@+id/title_paired_devices"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/title_paired_devices"
android:visibility="gone"
android:background="#666"
android:textColor="#fff"
android:paddingLeft="5dp"
/>
<ListView android:id="@+id/paired_devices"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:fadeScrollbars="false"
android:scrollbars="vertical"
/>
</LinearLayout>

View File

@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2009 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="5dp"
/>

View File

@@ -0,0 +1,34 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright © 2016 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:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="horizontal"
>
<TextView
android:id="@+id/frequency"
android:layout_width="wrap_content"
android:layout_height="fill_parent"
android:padding="10dp"
android:layout_weight="1"
/>
</LinearLayout>

View File

@@ -0,0 +1,67 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright © 2016 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"
>
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
>
<TextView android:id="@+id/set_callsign_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/set_callsign_label"
/>
<EditText android:id="@+id/set_callsign"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:hint="@string/set_callsign_label"/>
</LinearLayout>
<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"
android:text="@string/connect_idle"
/>
<Button android:id="@+id/disconnect_idle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/disconnect_idle"
/>
<Button android:id="@+id/reboot_idle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/reboot_idle"
/>
<Button android:id="@+id/igniters_idle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/igniters_idle"
/>
</LinearLayout>

View File

@@ -0,0 +1,39 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright © 2016 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.
-->
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<TextView
android:id="@+id/igniter_status"
android:layout_width="wrap_content"
android:layout_height="fill_parent"
android:padding="10dp"
android:layout_alignParentRight="true"
/>
<TextView
android:id="@+id/igniter_name"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:padding="10dp"
android:layout_alignParentLeft="@+id/igniter_status"
/>
</RelativeLayout>

View File

@@ -0,0 +1,49 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright © 2016 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"
>
<ListView android:id="@+id/igniters"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:fadeScrollbars="false"
android:scrollbars="vertical"
android:choiceMode="singleChoice"
/>
<ToggleButton android:id="@+id/igniter_arm"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textOn="@string/igniter_armed"
android:textOff="@string/igniter_arm"
/>
<Button android:id="@+id/igniter_fire"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/igniter_fire"
/>
</LinearLayout>

View File

@@ -0,0 +1,97 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright © 2016 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"
>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/set_layout"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
>
<EditText
android:id="@+id/set_frequency"
android:layout_width="wrap_content"
android:layout_height="fill_parent"
android:padding="10dp"
android:layout_weight="1"
android:hint="@string/frequency"
android:inputType="number|numberDecimal"/>
/>
<TextView
android:id="@+id/mhz"
android:layout_width="wrap_content"
android:layout_height="fill_parent"
android:padding="10dp"
android:layout_weight="0"
android:text="@string/mhz"
/>
<EditText
android:id="@+id/set_description"
android:layout_width="wrap_content"
android:layout_height="fill_parent"
android:padding="10dp"
android:layout_weight="2"
android:hint="@string/description"
/>
</LinearLayout>
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
>
<Button android:id="@+id/set"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/set"
android:layout_weight="1"
/>
<Button android:id="@+id/remove"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/remove"
android:layout_weight="1"
/>
<Button android:id="@+id/done"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/done"
android:layout_weight="1"
/>
</LinearLayout>
<ListView android:id="@+id/frequencies"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:fadeScrollbars="false"
android:scrollbars="vertical"
android:choiceMode="singleChoice"
/>
</LinearLayout>

View File

@@ -0,0 +1,132 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright © 2015 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="fill_parent"
android:layout_height="wrap_content">
<LinearLayout android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView android:id="@+id/preload_site_label"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/preload_site_label"
/>
<Spinner android:id="@+id/preload_site_list"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:prompt="@string/preload_site_label"
android:spinnerMode="dropdown"
/>
<TextView android:id="@+id/preload_latitude_label"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/preload_latitude_label"
/>
<EditText android:id="@+id/preload_latitude"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:hint="@string/preload_latitude_label"
android:inputType="number|numberSigned|numberDecimal"/>
<TextView android:id="@+id/preload_longitude_label"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/preload_longitude_label"
/>
<EditText android:id="@+id/preload_longitude"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:hint="@string/preload_longitude_label"
android:inputType="number|numberSigned|numberDecimal"/>
<TextView android:id="@+id/preload_types"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/preload_types"
/>
<!--
<CheckBox android:id="@+id/preload_hybrid"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/preload_hybrid"
/>
<CheckBox android:id="@+id/preload_satellite"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/preload_satellite"
/>
<CheckBox android:id="@+id/preload_roadmap"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/preload_roadmap"
/>
<CheckBox android:id="@+id/preload_terrain"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/preload_terrain"
/>
-->
<TextView android:id="@+id/preload_min_zoom_label"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/preload_min_zoom"
/>
<Spinner android:id="@+id/preload_min_zoom"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:prompt="@string/preload_min_zoom"
android:spinnerMode="dropdown"
/>
<TextView android:id="@+id/preload_max_zoom_label"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/preload_max_zoom"
/>
<Spinner android:id="@+id/preload_max_zoom"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:prompt="@string/preload_max_zoom"
android:spinnerMode="dropdown"
/>
<TextView android:id="@+id/preload_radius_label"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/preload_radius"
/>
<Spinner android:id="@+id/preload_radius"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:prompt="@string/preload_radius"
android:spinnerMode="dropdown"
/>
<Button android:id="@+id/preload_load"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/preload_load"
/>
<ProgressBar android:id="@+id/preload_progress"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="@android:style/Widget.ProgressBar.Horizontal"
/>
</LinearLayout>
</ScrollView>
</LinearLayout>

View File

@@ -0,0 +1,48 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright © 2015 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"
>
<Button android:id="@+id/map_type_hybrid"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/preload_hybrid"
android:onClick="selectType"
/>
<Button android:id="@+id/map_type_satellite"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/preload_satellite"
android:onClick="selectType"
/>
<Button android:id="@+id/map_type_roadmap"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/preload_roadmap"
android:onClick="selectType"
/>
<Button android:id="@+id/map_type_terrain"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/preload_terrain"
android:onClick="selectType"
/>
</LinearLayout>

View File

@@ -0,0 +1,144 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright © 2016 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"
>
<TableLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:stretchColumns="2,3"
android:layout_weight="0"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
<TableRow
android:layout_gravity="center"
android:layout_weight="1"
android:padding="2dip"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<TextView
android:id="@+id/select_rate_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/telemetry_rate"
/>
<Spinner android:id="@+id/select_rate"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:prompt="@string/telemetry_rate"
android:spinnerMode="dropdown"
/>
</TableRow>
<TableRow
android:layout_gravity="center"
android:layout_weight="1"
android:padding="2dip"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<TextView
android:id="@+id/set_units_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/set_units"
/>
<Spinner android:id="@+id/set_units"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:prompt="@string/set_units"
android:spinnerMode="dropdown"
/>
</TableRow>
<TableRow
android:layout_gravity="center"
android:layout_weight="1"
android:padding="2dip"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<TextView
android:id="@+id/font_size_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/font_size"
/>
<Spinner android:id="@+id/font_size"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:prompt="@string/font_size"
android:spinnerMode="dropdown"
/>
</TableRow>
<TableRow
android:layout_gravity="center"
android:layout_weight="1"
android:padding="2dip"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<TextView
android:id="@+id/map_type_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/map_type"
/>
<Spinner android:id="@+id/map_type"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:prompt="@string/map_type"
android:spinnerMode="dropdown"
/>
</TableRow>
<TableRow
android:layout_gravity="center"
android:layout_weight="1"
android:padding="2dip"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<TextView
android:id="@+id/map_source_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/map_source"
/>
<Spinner android:id="@+id/map_source"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:prompt="@string/map_source"
android:spinnerMode="dropdown"
/>
</TableRow>
</TableLayout>
<Button android:id="@+id/preload_maps"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/preload_maps"
/>
<Button android:id="@+id/manage_frequencies"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/manage_frequencies"
/>
<Button android:id="@+id/done"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/done"
/>
</LinearLayout>

View File

@@ -0,0 +1,430 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright © 2013 Mike Beattie <mike@ethernal.org>
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:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="vertical" >
<TableLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:stretchColumns="2,3"
android:layout_weight="0"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<TableRow
android:layout_gravity="center"
android:layout_weight="1"
android:padding="2dip"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
>
<TextView
android:id="@+id/speed_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_column="2"
android:text="@string/speed_label" />
<TextView
android:id="@+id/speed_value"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text=""
android:textAppearance="?android:attr/textAppearanceSmall" />
</TableRow>
<TableRow
android:layout_weight="1"
android:padding="2dip"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
>
<TextView
android:id="@+id/height_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_column="2"
android:text="@string/height_label" />
<TextView
android:id="@+id/height_value"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:text=""
android:textAppearance="?android:attr/textAppearanceSmall" />
</TableRow>
<TableRow
android:layout_weight="1"
android:padding="2dip"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
>
<TextView
android:id="@+id/altitude_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_column="2"
android:text="@string/altitude_label" />
<TextView
android:id="@+id/altitude_value"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:text=""
android:textAppearance="?android:attr/textAppearanceSmall" />
</TableRow>
<TableRow
android:id="@+id/tilt_view"
android:visibility="gone"
android:layout_gravity="center"
android:layout_weight="1"
android:padding="2dip"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
>
<TextView
android:id="@+id/tilt_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_column="2"
android:text="@string/tilt_label" />
<TextView
android:id="@+id/tilt_value"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text=""
android:textAppearance="?android:attr/textAppearanceSmall" />
</TableRow>
<TableRow
android:layout_gravity="center"
android:layout_weight="1"
android:padding="2dip"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
>
<TextView
android:id="@+id/max_speed_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_column="2"
android:text="@string/max_speed_label" />
<TextView
android:id="@+id/max_speed_value"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="" />
</TableRow>
<TableRow
android:layout_gravity="center"
android:layout_weight="1"
android:padding="2dip"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
>
<TextView
android:id="@+id/max_height_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_column="2"
android:text="@string/max_height_label" />
<TextView
android:id="@+id/max_height_value"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="" />
</TableRow>
<TableRow
android:layout_gravity="center"
android:layout_weight="1"
android:padding="2dip"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
>
<TextView
android:id="@+id/max_altitude_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_column="2"
android:text="@string/max_altitude_label" />
<TextView
android:id="@+id/max_altitude_value"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="" />
</TableRow>
<TableRow
android:layout_weight="1"
android:padding="2dip"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
>
<TextView
android:id="@+id/elevation_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_column="2"
android:text="@string/elevation_label" />
<TextView
android:id="@+id/elevation_value"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="" />
</TableRow>
<TableRow
android:layout_weight="1"
android:padding="2dip"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
>
<TextView
android:id="@+id/range_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_column="2"
android:text="@string/range_label" />
<TextView
android:id="@+id/range_value"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:text=""
android:textAppearance="?android:attr/textAppearanceSmall" />
</TableRow>
<TableRow
android:layout_weight="1"
android:padding="2dip"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
>
<TextView
android:id="@+id/bearing_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_column="2"
android:text="@string/bearing_label" />
<TextView
android:id="@+id/bearing_value"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:text=""
android:textAppearance="?android:attr/textAppearanceSmall" />
</TableRow>
<TableRow
android:layout_weight="1"
android:padding="2dip"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
>
<TextView
android:id="@+id/compass_value"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_column="3"
android:text=""
android:textAppearance="?android:attr/textAppearanceSmall" />
</TableRow>
<TableRow
android:layout_weight="1"
android:padding="2dip"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
>
<TextView
android:id="@+id/distance_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_column="2"
android:text="@string/gnd_distance_label" />
<TextView
android:id="@+id/distance_value"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:text=""
android:textAppearance="?android:attr/textAppearanceSmall" />
</TableRow>
<TableRow
android:layout_weight="1"
android:padding="2dip"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
>
<TextView
android:id="@+id/lat_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_column="2"
android:text="@string/latitude_label" />
<TextView
android:id="@+id/lat_value"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:text=""
android:textAppearance="?android:attr/textAppearanceSmall" />
</TableRow>
<TableRow
android:layout_weight="1"
android:padding="2dip"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
>
<TextView
android:id="@+id/lon_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_column="2"
android:text="@string/longitude_label" />
<TextView
android:id="@+id/lon_value"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:text=""
android:textAppearance="?android:attr/textAppearanceSmall" />
</TableRow>
<TableRow
android:id="@+id/apogee_view"
android:visibility="gone"
android:layout_weight="1"
android:padding="2dip"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
>
<ImageView
android:id="@+id/apogee_redled"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerInside"
android:contentDescription="@string/apogee_voltage_label"
android:src="@drawable/grayled" />
<ImageView
android:id="@+id/apogee_greenled"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerInside"
android:contentDescription="@string/apogee_voltage_label"
android:src="@drawable/grayled" />
<TextView
android:id="@+id/apogee_voltage_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toRightOf="@id/apogee_greenled"
android:text="@string/apogee_voltage_label" />
<TextView
android:id="@+id/apogee_voltage_value"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:text=""
android:textAppearance="?android:attr/textAppearanceSmall" />
</TableRow>
<TableRow
android:id="@+id/main_view"
android:visibility="gone"
android:layout_weight="1"
android:padding="2dip"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
>
<ImageView
android:id="@+id/main_redled"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerInside"
android:paddingRight="5dp"
android:contentDescription="@string/main_voltage_label"
android:src="@drawable/grayled" />
<ImageView
android:id="@+id/main_greenled"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerInside"
android:contentDescription="@string/main_voltage_label"
android:paddingRight="5dp"
android:src="@drawable/grayled" />
<TextView
android:id="@+id/main_voltage_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toRightOf="@id/main_greenled"
android:text="@string/main_voltage_label" />
<TextView
android:id="@+id/main_voltage_value"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:text=""
android:textAppearance="?android:attr/textAppearanceSmall" />
</TableRow>
</TableLayout>
</LinearLayout>

View File

@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/customTabLayout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/tabLabel"
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:textColor="#ffffff"
android:gravity="center_horizontal"
android:background="#808080"
android:layout_centerVertical="true"
android:layout_centerHorizontal="true" />
</RelativeLayout>

View File

@@ -0,0 +1,151 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright © 2013 Mike Beattie <mike@ethernal.org>
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:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<FrameLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:baselineAligned="true"
android:orientation="horizontal"
android:layout_weight="1">
<LinearLayout
android:id="@+id/map_online"
android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_weight="1">
</LinearLayout>
<org.altusmetrum.AltosDroid.AltosMapOffline
android:id="@+id/map_offline"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight="1"/>
</FrameLayout>
<TableLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:stretchColumns="1,3"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:baselineAligned="true"
android:orientation="horizontal" >
<TableRow
android:layout_gravity="center"
android:layout_weight="1"
android:padding="2dip"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<TextView
android:id="@+id/distance_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingRight="4dp"
android:text="@string/distance_label" />
<TextView
android:id="@+id/distance_value"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="" />
<TextView
android:id="@+id/bearing_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingRight="4dp"
android:text="@string/bearing_label" />
<TextView
android:id="@+id/bearing_value"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="" />
</TableRow>
</TableLayout>
<TableLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:stretchColumns="1,2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:baselineAligned="true"
android:orientation="horizontal" >
<TableRow
android:layout_gravity="center"
android:layout_weight="1"
android:padding="2dip"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<TextView
android:id="@+id/target_pos_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingRight="4dp"
android:text="@string/target_pos_label" />
<TextView
android:id="@+id/target_lat_value"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="" />
<TextView
android:id="@+id/target_lon_value"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="" />
</TableRow>
<TableRow
android:layout_gravity="center"
android:layout_weight="1"
android:padding="2dip"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<TextView
android:id="@+id/receiver_pos_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingRight="4dp"
android:text="@string/receiver_pos_label" />
<TextView
android:id="@+id/receiver_lat_value"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="" />
<TextView
android:id="@+id/receiver_lon_value"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="" />
</TableRow>
</TableLayout>
</LinearLayout>

View File

@@ -0,0 +1,565 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright © 2013 Mike Beattie <mike@ethernal.org>
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:layout_width="wrap_content"
android:layout_height="wrap_content">
<TableLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:stretchColumns="2,3"
android:layout_weight="0"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<TableRow
android:layout_gravity="center"
android:layout_weight="1"
android:padding="2dip"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/battery_redled"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerInside"
android:paddingRight="5dp"
android:contentDescription="@string/battery_voltage_label"
android:src="@drawable/grayled"
/>
<ImageView
android:id="@+id/battery_greenled"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerInside"
android:contentDescription="@string/battery_voltage_label"
android:paddingRight="5dp"
android:src="@drawable/grayled" />
<TextView
android:id="@+id/battery_voltage_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/battery_voltage_label" />
<TextView
android:id="@+id/battery_voltage_value"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text=""
android:textAppearance="?android:attr/textAppearanceSmall"
/>
</TableRow>
<TableRow
android:id="@+id/receiver_row"
android:visibility="gone"
android:layout_gravity="center"
android:layout_weight="1"
android:padding="2dip"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/receiver_redled"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerInside"
android:paddingRight="5dp"
android:contentDescription="@string/receiver_voltage_label"
android:src="@drawable/grayled" />
<ImageView
android:id="@+id/receiver_greenled"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerInside"
android:paddingRight="5dp"
android:contentDescription="@string/receiver_voltage_label"
android:src="@drawable/grayled" />
<TextView
android:id="@+id/receiver_voltage_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/receiver_voltage_label" />
<TextView
android:id="@+id/receiver_voltage_value"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text=""
android:textAppearance="?android:attr/textAppearanceSmall" />
</TableRow>
<TableRow
android:layout_gravity="center"
android:layout_weight="1"
android:padding="2dip"
android:layout_width="wrap_content"
android:layout_height="wrap_content" >
<ImageView
android:id="@+id/logging_redled"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerInside"
android:paddingRight="5dp"
android:contentDescription="@string/logging_label"
android:src="@drawable/grayled" />
<ImageView
android:id="@+id/logging_greenled"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerInside"
android:paddingRight="5dp"
android:contentDescription="@string/logging_label"
android:src="@drawable/grayled" />
<TextView
android:id="@+id/logging_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/logging_label" />
<TextView
android:id="@+id/logging_value"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/logging_label"
android:text=""
android:textAppearance="?android:attr/textAppearanceSmall" />
</TableRow>
<TableRow
android:layout_gravity="center"
android:layout_weight="1"
android:padding="2dip"
android:layout_width="wrap_content"
android:layout_height="wrap_content" >
<ImageView
android:id="@+id/gps_locked_redled"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerInside"
android:paddingRight="5dp"
android:contentDescription="@string/gps_locked_label"
android:src="@drawable/grayled" />
<ImageView
android:id="@+id/gps_locked_greenled"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerInside"
android:paddingRight="5dp"
android:contentDescription="@string/gps_locked_label"
android:src="@drawable/grayled" />
<TextView
android:id="@+id/gps_locked_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/gps_locked_label" />
<TextView
android:id="@+id/gps_locked_value"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/gps_locked_label"
android:text=""
android:textAppearance="?android:attr/textAppearanceSmall" />
</TableRow>
<TableRow
android:layout_gravity="center"
android:layout_weight="1"
android:padding="2dip"
android:layout_width="wrap_content"
android:layout_height="wrap_content" >
<ImageView
android:id="@+id/gps_ready_redled"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerInside"
android:paddingRight="5dp"
android:contentDescription="@string/gps_ready_label"
android:src="@drawable/grayled" />
<ImageView
android:id="@+id/gps_ready_greenled"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerInside"
android:paddingRight="5dp"
android:contentDescription="@string/gps_ready_label"
android:src="@drawable/grayled" />
<TextView
android:id="@+id/gps_ready_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/gps_ready_label" />
<TextView
android:id="@+id/gps_ready_value"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/gps_ready_label"
android:text=""
android:textAppearance="?android:attr/textAppearanceSmall" />
</TableRow>
<TableRow
android:id="@+id/apogee_row"
android:visibility="gone"
android:layout_gravity="center"
android:layout_weight="1"
android:padding="2dip"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/apogee_redled"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerInside"
android:paddingRight="5dp"
android:contentDescription="@string/apogee_voltage_label"
android:src="@drawable/grayled" />
<ImageView
android:id="@+id/apogee_greenled"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerInside"
android:paddingRight="5dp"
android:contentDescription="@string/apogee_voltage_label"
android:src="@drawable/grayled" />
<TextView
android:id="@+id/apogee_voltage_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/apogee_voltage_label" />
<TextView
android:id="@+id/apogee_voltage_value"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text=""
android:textAppearance="?android:attr/textAppearanceSmall" />
</TableRow>
<TableRow
android:id="@+id/main_row"
android:visibility="gone"
android:layout_gravity="center"
android:layout_weight="1"
android:padding="2dip"
android:layout_width="wrap_content"
android:layout_height="wrap_content" >
<ImageView
android:id="@+id/main_redled"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerInside"
android:paddingRight="5dp"
android:contentDescription="@string/main_voltage_label"
android:src="@drawable/grayled" />
<ImageView
android:id="@+id/main_greenled"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerInside"
android:paddingRight="5dp"
android:contentDescription="@string/main_voltage_label"
android:src="@drawable/grayled" />
<TextView
android:id="@+id/main_voltage_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/main_voltage_label" />
<TextView
android:id="@+id/main_voltage_value"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text=""
android:textAppearance="?android:attr/textAppearanceSmall" />
</TableRow>
<TableRow
android:id="@+id/ignite_a_row"
android:visibility="gone"
android:layout_gravity="center"
android:layout_weight="1"
android:padding="2dip"
android:layout_width="wrap_content"
android:layout_height="wrap_content" >
<ImageView
android:id="@+id/ignite_a_redled"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerInside"
android:paddingRight="5dp"
android:contentDescription="@string/ignite_a_voltage_label"
android:src="@drawable/grayled" />
<ImageView
android:id="@+id/ignite_a_greenled"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerInside"
android:paddingRight="5dp"
android:contentDescription="@string/ignite_a_voltage_label"
android:src="@drawable/grayled" />
<TextView
android:id="@+id/ignite_a_voltage_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/ignite_a_voltage_label" />
<TextView
android:id="@+id/ignite_a_voltage_value"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text=""
android:textAppearance="?android:attr/textAppearanceSmall" />
</TableRow>
<TableRow
android:id="@+id/ignite_b_row"
android:visibility="gone"
android:layout_gravity="center"
android:layout_weight="1"
android:padding="2dip"
android:layout_width="wrap_content"
android:layout_height="wrap_content" >
<ImageView
android:id="@+id/ignite_b_redled"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerInside"
android:paddingRight="5dp"
android:contentDescription="@string/ignite_b_voltage_label"
android:src="@drawable/grayled" />
<ImageView
android:id="@+id/ignite_b_greenled"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerInside"
android:paddingRight="5dp"
android:contentDescription="@string/ignite_b_voltage_label"
android:src="@drawable/grayled" />
<TextView
android:id="@+id/ignite_b_voltage_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/ignite_b_voltage_label" />
<TextView
android:id="@+id/ignite_b_voltage_value"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text=""
android:textAppearance="?android:attr/textAppearanceSmall" />
</TableRow>
<TableRow
android:id="@+id/ignite_c_row"
android:visibility="gone"
android:layout_gravity="center"
android:layout_weight="1"
android:padding="2dip"
android:layout_width="wrap_content"
android:layout_height="wrap_content" >
<ImageView
android:id="@+id/ignite_c_redled"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerInside"
android:paddingRight="5dp"
android:contentDescription="@string/ignite_c_voltage_label"
android:src="@drawable/grayled" />
<ImageView
android:id="@+id/ignite_c_greenled"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerInside"
android:paddingRight="5dp"
android:contentDescription="@string/ignite_c_voltage_label"
android:src="@drawable/grayled" />
<TextView
android:id="@+id/ignite_c_voltage_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/ignite_c_voltage_label" />
<TextView
android:id="@+id/ignite_c_voltage_value"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text=""
android:textAppearance="?android:attr/textAppearanceSmall" />
</TableRow>
<TableRow
android:id="@+id/ignite_d_row"
android:visibility="gone"
android:layout_gravity="center"
android:layout_weight="1"
android:padding="2dip"
android:layout_width="wrap_content"
android:layout_height="wrap_content" >
<ImageView
android:id="@+id/ignite_d_redled"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerInside"
android:paddingRight="5dp"
android:contentDescription="@string/ignite_d_voltage_label"
android:src="@drawable/grayled" />
<ImageView
android:id="@+id/ignite_d_greenled"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerInside"
android:paddingRight="5dp"
android:contentDescription="@string/ignite_d_voltage_label"
android:src="@drawable/grayled" />
<TextView
android:id="@+id/ignite_d_voltage_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/ignite_d_voltage_label" />
<TextView
android:id="@+id/ignite_d_voltage_value"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text=""
android:textAppearance="?android:attr/textAppearanceSmall" />
</TableRow>
<TableRow
android:id="@+id/tilt_view"
android:visibility="gone"
android:layout_gravity="center"
android:layout_weight="1"
android:padding="2dip"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
>
<TextView
android:id="@+id/tilt_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_column="2"
android:text="@string/tilt_label" />
<TextView
android:id="@+id/tilt_value"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text=""
android:textAppearance="?android:attr/textAppearanceSmall" />
</TableRow>
<TableRow
android:padding="2dip"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<TextView
android:id="@+id/receiver_lat_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_column="2"
android:text="@string/receiver_latitude_label" />
<TextView
android:id="@+id/receiver_lat_value"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text=""
android:textAppearance="?android:attr/textAppearanceSmall" />
</TableRow>
<TableRow
android:padding="2dip"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<TextView
android:id="@+id/receiver_lon_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_column="2"
android:text="@string/receiver_longitude_label" />
<TextView
android:id="@+id/receiver_lon_value"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text=""
android:textAppearance="?android:attr/textAppearanceSmall" />
</TableRow>
<TableRow
android:padding="2dip"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<TextView
android:id="@+id/receiver_alt_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_column="2"
android:text="@string/receiver_altitude_label" />
<TextView
android:id="@+id/receiver_alt_value"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text=""
android:textAppearance="?android:attr/textAppearanceSmall" />
</TableRow>
</TableLayout>
</LinearLayout>

View File

@@ -0,0 +1,244 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright © 2013 Mike Beattie <mike@ethernal.org>
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:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="vertical" >
<TableLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:stretchColumns="0,1"
android:layout_weight="0"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<TableRow
android:layout_gravity="center"
android:layout_weight="1"
android:padding="2dip"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
>
<TextView
android:id="@+id/bearing_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/bearing_label" />
<TextView
android:id="@+id/bearing_value"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="" />
</TableRow>
<TableRow
android:layout_gravity="center"
android:layout_weight="1"
android:padding="2dip"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
>
<TextView
android:id="@+id/direction_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/direction_label" />
<TextView
android:id="@+id/direction_value"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="" />
</TableRow>
<TableRow
android:layout_gravity="center"
android:layout_weight="1"
android:padding="2dip"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
>
<TextView
android:id="@+id/distance_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/distance_label" />
<TextView
android:id="@+id/distance_value"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="" />
</TableRow>
<TableRow
android:layout_gravity="center"
android:layout_weight="1"
android:padding="2dip"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
>
<TextView
android:id="@+id/target_lat_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/target_latitude_label" />
<TextView
android:id="@+id/target_lat_value"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="" />
</TableRow>
<TableRow
android:layout_gravity="center"
android:layout_weight="1"
android:padding="2dip"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
>
<TextView
android:id="@+id/target_lon_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/target_longitude_label" />
<TextView
android:id="@+id/target_lon_value"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="" />
</TableRow>
<TableRow
android:layout_gravity="center"
android:layout_weight="1"
android:padding="2dip"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
>
<TextView
android:id="@+id/receiver_lat_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/receiver_latitude_label" />
<TextView
android:id="@+id/receiver_lat_value"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="" />
</TableRow>
<TableRow
android:layout_gravity="center"
android:layout_weight="1"
android:padding="2dip"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
>
<TextView
android:id="@+id/receiver_lon_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/receiver_longitude_label" />
<TextView
android:id="@+id/receiver_lon_value"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text=""/>
</TableRow>
<TableRow
android:layout_gravity="center"
android:layout_weight="1"
android:padding="2dip"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
>
<TextView
android:id="@+id/max_height_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/max_height_label" />
<TextView
android:id="@+id/max_height_value"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="" />
</TableRow>
<TableRow
android:layout_gravity="center"
android:layout_weight="1"
android:padding="2dip"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
>
<TextView
android:id="@+id/max_speed_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/max_speed_label" />
<TextView
android:id="@+id/max_speed_value"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="" />
</TableRow>
<TableRow
android:layout_gravity="center"
android:layout_weight="1"
android:padding="2dip"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
>
<TextView
android:id="@+id/max_accel_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/max_accel_label" />
<TextView
android:id="@+id/max_accel_value"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="" />
</TableRow>
</TableLayout>
</LinearLayout>

View File

@@ -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>

View File

@@ -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>

View File

@@ -0,0 +1,47 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright © 2015 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.
-->
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@+id/connect_scan"
android:icon="@android:drawable/ic_menu_search"
android:title="@string/connect_device" />
<item android:id="@+id/disconnect"
android:icon="@android:drawable/ic_notification_clear_all"
android:title="@string/disconnect_device" />
<item android:id="@+id/select_freq"
android:icon="@android:drawable/ic_menu_preferences"
android:title="@string/select_freq" />
<item android:id="@+id/select_tracker"
android:icon="@android:drawable/ic_menu_view"
android:title="@string/select_tracker"/>
<item android:id="@+id/delete_track"
android:icon="@android:drawable/ic_notification_clear_all"
android:title="@string/delete_track"/>
<item android:id="@+id/setup"
android:icon="@android:drawable/ic_menu_preferences"
android:title="@string/setup" />
<item android:id="@+id/idle_mode"
android:icon="@android:drawable/ic_menu_preferences"
android:title="@string/idle_mode" />
<item android:id="@+id/quit"
android:icon="@android:drawable/ic_menu_close_clear_cancel"
android:title="@string/quit" />
</menu>

View File

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright © 2015 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.
-->
<resources>
<color name="old_color">#ffff4040</color>
</resources>

View File

@@ -0,0 +1,44 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="Small" parent="android:Theme.Material.Light">
<item name="android:textColor">#000000</item>
<item name="android:textSize">12sp</item>
<item name="android:lineHeight">14sp</item>
</style>
<style name="Medium" parent="android:Theme.Material.Light">
<item name="android:textColor">#000000</item>
<item name="android:textSize">15sp</item>
<item name="android:lineHeight">18sp</item>
</style>
<style name="Large" parent="android:Theme.Material.Light">
<item name="android:textColor">#000000</item>
<item name="android:textSize">20sp</item>
<item name="android:lineHeight">40sp</item>
</style>
<style name="Extra" parent="android:Theme.Material.Light">
<item name="android:textColor">#000000</item>
<item name="android:textSize">27sp</item>
<item name="android:lineHeight">32sp</item>
</style>
<style name="Small.Dialog" parent="android:Theme.Material.Light.Dialog">
<item name="android:textColor">#000000</item>
<item name="android:textSize">12sp</item>
<item name="android:lineHeight">14sp</item>
</style>
<style name="Medium.Dialog" parent="android:Theme.Material.Light.Dialog">
<item name="android:textColor">#000000</item>
<item name="android:textSize">15sp</item>
<item name="android:lineHeight">18sp</item>
</style>
<style name="Large.Dialog" parent="android:Theme.Material.Light.Dialog">
<item name="android:textColor">#000000</item>
<item name="android:textSize">20sp</item>
<item name="android:lineHeight">40sp</item>
</style>
<style name="Extra.Dialog" parent="android:Theme.Material.Light.Dialog">
<item name="android:textColor">#000000</item>
<item name="android:textSize">27sp</item>
<item name="android:lineHeight">32sp</item>
</style>
</resources>

View File

@@ -0,0 +1,156 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright © 2012-2013 Mike Beattie <mike@ethernal.org>
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.
-->
<resources>
<string name="app_name">AltosDroid</string>
<!-- AltosDroid -->
<string name="bt_not_enabled">Bluetooth was not enabled.</string>
<string name="title_connecting">connecting…</string>
<string name="title_connected_to">connected</string>
<string name="title_not_connected">not connected</string>
<!-- Options Menu -->
<string name="connect_device">Connect a device</string>
<string name="disconnect_device">Disconnect device</string>
<string name="quit">Quit</string>
<string name="setup">Setup</string>
<string name="select_freq">Select radio frequency</string>
<string name="select_rate">Select data rate</string>
<string name="change_units">Change units</string>
<string name="preload_maps">Load Maps</string>
<string name="select_tracker">Select Tracker</string>
<string name="delete_track">Delete Track</string>
<string name="map_type">Map Type</string>
<string name="map_source">Map Source</string>
<!-- MapTypeActivity -->
<!-- <string name="map_type">Map Type</string> -->
<!-- DeviceListActivity -->
<string name="scanning">Scanning…</string>
<string name="select_device">Select device</string>
<string name="none_paired">No devices have been paired</string>
<string name="none_found">No devices found</string>
<string name="title_paired_devices">Paired Devices</string>
<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>
<string name="telemetry_service_stopped">Telemetry Service Stopped</string>
<!-- UI fields -->
<!-- Header -->
<string name="callsign_label">Call</string>
<string name="serial_label">Serial</string>
<string name="flight_label">Flight</string>
<string name="state_label">State</string>
<string name="rssi_label">RSSI</string>
<string name="age_label">Age</string>
<!-- Tab fields -->
<string name="height_label">Height</string>
<string name="altitude_label">Altitude</string>
<string name="speed_label">Speed</string>
<string name="accel_label">Acceleration</string>
<string name="tilt_label">Tilt</string>
<string name="bearing_label">Bearing</string>
<string name="direction_label">Direction</string>
<string name="elevation_label">Elevation</string>
<string name="range_label">Range</string>
<string name="distance_label">Distance</string>
<string name="gnd_distance_label">Ground Distance</string>
<string name="max_height_label">Max Height</string>
<string name="max_altitude_label">Max Altitude</string>
<string name="max_speed_label">Max Speed</string>
<string name="max_accel_label">Max Accel</string>
<string name="battery_voltage_label">Battery</string>
<string name="receiver_voltage_label">Receiver Battery</string>
<string name="apogee_voltage_label">Apogee Igniter</string>
<string name="main_voltage_label">Main Igniter</string>
<string name="ignite_a_voltage_label">Igniter A</string>
<string name="ignite_b_voltage_label">Igniter B</string>
<string name="ignite_c_voltage_label">Igniter C</string>
<string name="ignite_d_voltage_label">Igniter D</string>
<string name="logging_label">Data Logging</string>
<string name="gps_locked_label">GPS Locked</string>
<string name="gps_ready_label">GPS Ready</string>
<string name="latitude_label">Latitude</string>
<string name="longitude_label">Longitude</string>
<string name="target_pos_label">Tar</string>
<string name="receiver_pos_label">Me</string>
<string name="target_latitude_label">Tar Lat</string>
<string name="target_longitude_label">Tar Lon</string>
<string name="receiver_latitude_label">My Lat</string>
<string name="receiver_longitude_label">My Lon</string>
<string name="receiver_altitude_label">My Alt</string>
<!-- Map preload -->
<string name="preload_site_label">Known Launch Sites</string>
<string name="preload_latitude_label">Latitude</string>
<string name="preload_longitude_label">Longitude</string>
<string name="preload_types">Map Types</string>
<string name="preload_hybrid">Hybrid</string>
<string name="preload_satellite">Satellite</string>
<string name="preload_roadmap">Roadmap</string>
<string name="preload_terrain">Terrain</string>
<string name="preload_min_zoom">Minimum Zoom</string>
<string name="preload_max_zoom">Maximum Zoom</string>
<string name="preload_radius">Radius</string>
<string name="preload_load">Load Map</string>
<!-- Idle mode -->
<string name="idle_mode">Idle Mode</string>
<string name="set_callsign_label">Callsign: </string>
<string name="connect_idle">Monitor</string>
<string name="disconnect_idle">Disconnect</string>
<string name="reboot_idle">Reboot</string>
<string name="igniters_idle">Fire Igniters</string>
<!-- igniters -->
<string name="igniters">Igniters</string>
<string name="igniter_arm">Arm</string>
<string name="igniter_armed">Armed</string>
<string name="igniter_fire">Fire</string>
<!-- setup -->
<string name="telemetry_rate">Telemetry Rate</string>
<string name="set_units">Units</string>
<!-- <string name="map_type">Map Type</string> -->
<!-- <string name="map_source">Map Source</string> -->
<!-- <string name="preload_maps">Preload Maps</string> -->
<string name="font_size">Text Size</string>
<string name="manage_frequencies">Manage Frequencies</string>
<string name="done">OK</string>
<!-- manage frequencies -->
<string name="set">Set</string>
<string name="mhz">MHz</string>
<string name="remove">Remove</string>
<!-- <string name="done">OK</string> -->
<string name="frequency">Frequency</string>
<string name="description">Description</string>
</resources>

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<usb-device vendor-id="65534" />
<usb-device vendor-id="1027" />
</resources>