X-Git-Url: https://git.gag.com/?p=fw%2Faltos;a=blobdiff_plain;f=altosdroid%2Fsrc%2Forg%2Faltusmetrum%2FAltosDroid%2FAltosDroid.java;h=71ac298e77b6d6000d4620db1a16eddd57871b5b;hp=eff24b10cefb8cc3dc23e0c163905c343d5163e6;hb=251263f72a1c189aac709d3d0410eb916a9f66d6;hpb=cb23b992be8ba40c97d8988c134a814a13ccd58c diff --git a/altosdroid/src/org/altusmetrum/AltosDroid/AltosDroid.java b/altosdroid/src/org/altusmetrum/AltosDroid/AltosDroid.java index eff24b10..71ac298e 100644 --- a/altosdroid/src/org/altusmetrum/AltosDroid/AltosDroid.java +++ b/altosdroid/src/org/altusmetrum/AltosDroid/AltosDroid.java @@ -18,10 +18,9 @@ package org.altusmetrum.AltosDroid; import java.lang.ref.WeakReference; -import java.util.ArrayList; -import java.util.Timer; -import java.util.TimerTask; import java.text.*; +import java.util.*; +import java.io.*; import android.app.Activity; import android.app.PendingIntent; @@ -42,27 +41,17 @@ import android.content.res.Resources; import android.support.v4.app.FragmentActivity; import android.support.v4.app.FragmentManager; import android.util.DisplayMetrics; -import android.util.Log; -import android.view.Menu; -import android.view.MenuInflater; -import android.view.MenuItem; -import android.view.Window; -import android.view.View; -import android.view.LayoutInflater; -import android.widget.TabHost; -import android.widget.TextView; -import android.widget.RelativeLayout; -import android.widget.Toast; +import android.view.*; +import android.widget.*; import android.app.AlertDialog; import android.location.Location; import android.hardware.usb.*; +import android.graphics.*; +import android.graphics.drawable.*; import org.altusmetrum.altoslib_7.*; public class AltosDroid extends FragmentActivity implements AltosUnitsListener { - // Debugging - static final String TAG = "AltosDroid"; - static final boolean D = true; // Actions sent to the telemetry server at startup time @@ -77,14 +66,15 @@ public class AltosDroid extends FragmentActivity implements AltosUnitsListener { // Intent request codes public static final int REQUEST_CONNECT_DEVICE = 1; public static final int REQUEST_ENABLE_BT = 2; + public static final int REQUEST_PRELOAD_MAPS = 3; + public static final int REQUEST_MAP_TYPE = 4; + + public int map_type = AltosMap.maptype_hybrid; public static FragmentManager fm; private BluetoothAdapter mBluetoothAdapter = null; - // Layout Views - private TextView mTitle; - // Flight state values private TextView mCallsignView; private TextView mRSSIView; @@ -93,6 +83,14 @@ public class AltosDroid extends FragmentActivity implements AltosUnitsListener { private RelativeLayout mStateLayout; private TextView mStateView; private TextView mAgeView; + private boolean mAgeViewOld; + private int mAgeNewColor; + private int mAgeOldColor; + + public static final String tab_pad_name = "pad"; + public static final String tab_flight_name = "flight"; + public static final String tab_recover_name = "recover"; + public static final String tab_map_name = "map"; // field to display the version at the bottom of the screen private TextView mVersion; @@ -110,6 +108,8 @@ public class AltosDroid extends FragmentActivity implements AltosUnitsListener { // Timer and Saved flight state for Age calculation private Timer timer; AltosState saved_state; + TelemetryState telemetry_state; + Integer[] serials; UsbDevice pending_usb_device; boolean start_with_usb; @@ -133,17 +133,13 @@ public class AltosDroid extends FragmentActivity implements AltosUnitsListener { switch (msg.what) { case MSG_STATE: - if(D) Log.d(TAG, "MSG_STATE"); - TelemetryState telemetry_state = (TelemetryState) msg.obj; - if (telemetry_state == null) { - Log.d(TAG, "telemetry_state null!"); + if (msg.obj == null) { + AltosDebug.debug("telemetry_state null!"); return; } - - ad.update_state(telemetry_state); + ad.update_state((TelemetryState) msg.obj); break; case MSG_UPDATE_AGE: - if(D) Log.d(TAG, "MSG_UPDATE_AGE"); ad.update_age(); break; } @@ -221,20 +217,20 @@ public class AltosDroid extends FragmentActivity implements AltosUnitsListener { if (telemetry_state.telemetry_rate != AltosLib.ao_telemetry_rate_38400) str = str.concat(String.format(" %d bps", AltosLib.ao_telemetry_rate_values[telemetry_state.telemetry_rate])); - mTitle.setText(str); + setTitle(str); } else { - mTitle.setText(R.string.title_connected_to); + setTitle(R.string.title_connected_to); } break; case TelemetryState.CONNECT_CONNECTING: if (telemetry_state.address != null) - mTitle.setText(String.format("Connecting to %s...", telemetry_state.address.name)); + setTitle(String.format("Connecting to %s...", telemetry_state.address.name)); else - mTitle.setText("Connecting to something..."); + setTitle("Connecting to something..."); break; case TelemetryState.CONNECT_DISCONNECTED: case TelemetryState.CONNECT_NONE: - mTitle.setText(R.string.title_not_connected); + setTitle(R.string.title_not_connected); break; } } @@ -254,19 +250,73 @@ public class AltosDroid extends FragmentActivity implements AltosUnitsListener { } } + int selected_serial = 0; + int current_serial; + long switch_time; + + void set_switch_time() { + switch_time = System.currentTimeMillis(); + selected_serial = 0; + } + boolean registered_units_listener; - void update_state(TelemetryState telemetry_state) { + void update_state(TelemetryState new_telemetry_state) { + + if (new_telemetry_state != null) + telemetry_state = new_telemetry_state; + + if (selected_serial != 0) + current_serial = selected_serial; + + if (current_serial == 0) + current_serial = telemetry_state.latest_serial; if (!registered_units_listener) { registered_units_listener = true; AltosPreferences.register_units_listener(this); } + serials = telemetry_state.states.keySet().toArray(new Integer[0]); + Arrays.sort(serials); + update_title(telemetry_state); - update_ui(telemetry_state.state, telemetry_state.location); - if (telemetry_state.connect == TelemetryState.CONNECT_CONNECTED) - start_timer(); + + AltosState state = null; + boolean aged = true; + + if (telemetry_state.states.containsKey(current_serial)) { + state = telemetry_state.states.get(current_serial); + int age = state_age(state); + if (age < 20) + aged = false; + if (current_serial == selected_serial) + aged = false; + else if (switch_time != 0 && (switch_time - state.received_time) > 0) + aged = true; + } + + if (aged) { + AltosState newest_state = null; + int newest_age = 0; + + for (int serial : telemetry_state.states.keySet()) { + AltosState existing = telemetry_state.states.get(serial); + int existing_age = state_age(existing); + + if (newest_state == null || existing_age < newest_age) { + newest_state = existing; + newest_age = existing_age; + } + } + + if (newest_state != null) + state = newest_state; + } + + update_ui(telemetry_state, state, telemetry_state.location); + + start_timer(); } boolean same_string(String a, String b) { @@ -281,12 +331,55 @@ public class AltosDroid extends FragmentActivity implements AltosUnitsListener { } } + + private int blend_component(int a, int b, double r, int shift, int mask) { + return ((int) (((a >> shift) & mask) * r + ((b >> shift) & mask) * (1 - r)) & mask) << shift; + } + private int blend_color(int a, int b, double r) { + return (blend_component(a, b, r, 0, 0xff) | + blend_component(a, b, r, 8, 0xff) | + blend_component(a, b, r, 16, 0xff) | + blend_component(a, b, r, 24, 0xff)); + } + + int state_age(AltosState state) { + return (int) ((System.currentTimeMillis() - state.received_time + 500) / 1000); + } + + void set_screen_on(int age) { + if (age < 60) + getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + else + getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + } + void update_age() { - if (saved_state != null) - mAgeView.setText(String.format("%d", (System.currentTimeMillis() - saved_state.received_time + 500) / 1000)); + if (saved_state != null) { + int age = state_age(saved_state); + + double age_scale = age / 100.0; + + if (age_scale > 1.0) + age_scale = 1.0; + + mAgeView.setTextColor(blend_color(mAgeOldColor, mAgeNewColor, age_scale)); + + set_screen_on(age); + + String text; + if (age < 60) + text = String.format("%ds", age); + else if (age < 60 * 60) + text = String.format("%dm", age / 60); + else if (age < 60 * 60 * 24) + text = String.format("%dh", age / (60 * 60)); + else + text = String.format("%dd", age / (24 * 60 * 60)); + mAgeView.setText(text); + } } - void update_ui(AltosState state, Location location) { + void update_ui(TelemetryState telem_state, AltosState state, Location location) { int prev_state = AltosLib.ao_flight_invalid; @@ -296,6 +389,8 @@ public class AltosDroid extends FragmentActivity implements AltosUnitsListener { prev_state = saved_state.state; if (state != null) { + set_screen_on(state_age(state)); + if (state.state == AltosLib.ao_flight_stateless) { boolean prev_locked = false; boolean locked = false; @@ -307,9 +402,9 @@ public class AltosDroid extends FragmentActivity implements AltosUnitsListener { if (prev_locked != locked) { String currentTab = mTabHost.getCurrentTabTag(); if (locked) { - if (currentTab.equals("pad")) mTabHost.setCurrentTabByTag("descent"); + if (currentTab.equals(tab_pad_name)) mTabHost.setCurrentTabByTag(tab_flight_name); } else { - if (currentTab.equals("descent")) mTabHost.setCurrentTabByTag("pad"); + if (currentTab.equals(tab_flight_name)) mTabHost.setCurrentTabByTag(tab_pad_name); } } } else { @@ -317,16 +412,13 @@ public class AltosDroid extends FragmentActivity implements AltosUnitsListener { String currentTab = mTabHost.getCurrentTabTag(); switch (state.state) { case AltosLib.ao_flight_boost: - if (currentTab.equals("pad")) mTabHost.setCurrentTabByTag("ascent"); - break; - case AltosLib.ao_flight_drogue: - if (currentTab.equals("ascent")) mTabHost.setCurrentTabByTag("descent"); + if (currentTab.equals(tab_pad_name)) mTabHost.setCurrentTabByTag(tab_flight_name); break; case AltosLib.ao_flight_landed: - if (currentTab.equals("descent")) mTabHost.setCurrentTabByTag("landed"); + if (currentTab.equals(tab_flight_name)) mTabHost.setCurrentTabByTag(tab_recover_name); break; case AltosLib.ao_flight_stateless: - if (currentTab.equals("pad")) mTabHost.setCurrentTabByTag("descent"); + if (currentTab.equals(tab_pad_name)) mTabHost.setCurrentTabByTag(tab_flight_name); break; } } @@ -370,10 +462,10 @@ public class AltosDroid extends FragmentActivity implements AltosUnitsListener { } for (AltosDroidTab mTab : mTabs) - mTab.update_ui(state, from_receiver, location, mTab == mTabsAdapter.currentItem()); + mTab.update_ui(telem_state, state, from_receiver, location, mTab == mTabsAdapter.currentItem()); - if (state != null && mAltosVoice != null) - mAltosVoice.tell(state, from_receiver); + if (mAltosVoice != null) + mAltosVoice.tell(telem_state, state, from_receiver, location, (AltosDroidTab) mTabsAdapter.currentItem()); saved_state = state; } @@ -418,17 +510,24 @@ public class AltosDroid extends FragmentActivity implements AltosUnitsListener { return tab_view; } + public void set_map_source(int source) { + for (AltosDroidTab mTab : mTabs) + mTab.set_map_source(source); + } + @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - if(D) Log.e(TAG, "+++ ON CREATE +++"); + AltosDebug.init(this); + AltosDebug.debug("+++ ON CREATE +++"); + + // Initialise preferences + AltosDroidPreferences.init(this); fm = getSupportFragmentManager(); // Set up the window layout - requestWindowFeature(Window.FEATURE_CUSTOM_TITLE); setContentView(R.layout.altosdroid); - getWindow().setFeatureInt(Window.FEATURE_CUSTOM_TITLE, R.layout.custom_title); // Create the Tabs and ViewPager mTabHost = (TabHost)findViewById(android.R.id.tabhost); @@ -439,16 +538,10 @@ public class AltosDroid extends FragmentActivity implements AltosUnitsListener { mTabsAdapter = new TabsAdapter(this, mTabHost, mViewPager); - mTabsAdapter.addTab(mTabHost.newTabSpec("pad").setIndicator(create_tab_view("Pad")), TabPad.class, null); - mTabsAdapter.addTab(mTabHost.newTabSpec("ascent").setIndicator(create_tab_view("Ascent")), TabAscent.class, null); - mTabsAdapter.addTab(mTabHost.newTabSpec("descent").setIndicator(create_tab_view("Descent")), TabDescent.class, null); - mTabsAdapter.addTab(mTabHost.newTabSpec("landed").setIndicator(create_tab_view("Landed")), TabLanded.class, null); - mTabsAdapter.addTab(mTabHost.newTabSpec("map").setIndicator(create_tab_view("Map")), TabMap.class, null); - - // Set up the custom title - mTitle = (TextView) findViewById(R.id.title_left_text); - mTitle.setText(R.string.app_name); - mTitle = (TextView) findViewById(R.id.title_right_text); + mTabsAdapter.addTab(mTabHost.newTabSpec(tab_pad_name).setIndicator(create_tab_view("Pad")), TabPad.class, null); + mTabsAdapter.addTab(mTabHost.newTabSpec(tab_flight_name).setIndicator(create_tab_view("Flight")), TabFlight.class, null); + mTabsAdapter.addTab(mTabHost.newTabSpec(tab_recover_name).setIndicator(create_tab_view("Recover")), TabRecover.class, null); + mTabsAdapter.addTab(mTabHost.newTabSpec(tab_map_name).setIndicator(create_tab_view("Map")), TabMap.class, null); // Display the Version mVersion = (TextView) findViewById(R.id.version); @@ -463,6 +556,8 @@ public class AltosDroid extends FragmentActivity implements AltosUnitsListener { mStateLayout = (RelativeLayout) findViewById(R.id.state_container); mStateView = (TextView) findViewById(R.id.state_value); mAgeView = (TextView) findViewById(R.id.age_value); + mAgeNewColor = mAgeView.getTextColors().getDefaultColor(); + mAgeOldColor = getResources().getColor(R.color.old_color); } private boolean ensureBluetooth() { @@ -525,13 +620,13 @@ public class AltosDroid extends FragmentActivity implements AltosUnitsListener { UsbDevice device = (UsbDevice) intent.getParcelableExtra(UsbManager.EXTRA_DEVICE); boolean granted = intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, true); - if (D) Log.e(TAG, "intent " + intent + " device " + device + " granted " + granted); + AltosDebug.debug("intent %s device %s granted %s", intent, device, granted); if (!granted) device = null; if (device != null) { - if (D) Log.d(TAG, "intent has usb device " + device.toString()); + AltosDebug.debug("intent has usb device " + device.toString()); connectUsb(device); } else { @@ -541,11 +636,11 @@ public class AltosDroid extends FragmentActivity implements AltosUnitsListener { * don't want to loop forever... */ if (granted) { - if (D) Log.d(TAG, "check for a USB device at startup"); + AltosDebug.debug("check for a USB device at startup"); if (check_usb()) return; } - if (D) Log.d(TAG, "Starting by looking for bluetooth devices"); + AltosDebug.debug("Starting by looking for bluetooth devices"); if (ensureBluetooth()) return; finish(); @@ -555,7 +650,9 @@ public class AltosDroid extends FragmentActivity implements AltosUnitsListener { @Override public void onStart() { super.onStart(); - if(D) Log.e(TAG, "++ ON START ++"); + AltosDebug.debug("++ ON START ++"); + + set_switch_time(); noticeIntent(getIntent()); @@ -574,45 +671,43 @@ public class AltosDroid extends FragmentActivity implements AltosUnitsListener { @Override public void onNewIntent(Intent intent) { super.onNewIntent(intent); - if(D) Log.d(TAG, "onNewIntent"); + AltosDebug.debug("onNewIntent"); noticeIntent(intent); } @Override public void onResume() { super.onResume(); - if(D) Log.e(TAG, "+ ON RESUME +"); + AltosDebug.debug("+ ON RESUME +"); } @Override public void onPause() { super.onPause(); - if(D) Log.e(TAG, "- ON PAUSE -"); + AltosDebug.debug("- ON PAUSE -"); } @Override public void onStop() { super.onStop(); - if(D) Log.e(TAG, "-- ON STOP --"); - - doUnbindService(); - if (mAltosVoice != null) { - mAltosVoice.stop(); - mAltosVoice = null; - } + AltosDebug.debug("-- ON STOP --"); } @Override public void onDestroy() { super.onDestroy(); - if(D) Log.e(TAG, "--- ON DESTROY ---"); + AltosDebug.debug("--- ON DESTROY ---"); - if (mAltosVoice != null) mAltosVoice.stop(); + doUnbindService(); + if (mAltosVoice != null) { + mAltosVoice.stop(); + mAltosVoice = null; + } stop_timer(); } protected void onActivityResult(int requestCode, int resultCode, Intent data) { - if(D) Log.d(TAG, "onActivityResult " + resultCode); + AltosDebug.debug("onActivityResult " + resultCode); switch (requestCode) { case REQUEST_CONNECT_DEVICE: // When DeviceListActivity returns with a device to connect to @@ -627,12 +722,16 @@ public class AltosDroid extends FragmentActivity implements AltosUnitsListener { //setupChat(); } else { // User did not enable Bluetooth or an error occured - Log.e(TAG, "BT not enabled"); + AltosDebug.error("BT not enabled"); stopService(new Intent(AltosDroid.this, TelemetryService.class)); Toast.makeText(this, R.string.bt_not_enabled, Toast.LENGTH_SHORT).show(); finish(); } break; + case REQUEST_MAP_TYPE: + if (resultCode == Activity.RESULT_OK) + set_map_type(data); + break; } } @@ -643,9 +742,9 @@ public class AltosDroid extends FragmentActivity implements AltosUnitsListener { // Attempt to connect to the device try { mService.send(Message.obtain(null, TelemetryService.MSG_OPEN_USB, device)); - if (D) Log.d(TAG, "Sent OPEN_USB message"); + AltosDebug.debug("Sent OPEN_USB message"); } catch (RemoteException e) { - if (D) Log.e(TAG, "connect device message failed"); + AltosDebug.debug("connect device message failed"); } } } @@ -656,12 +755,12 @@ public class AltosDroid extends FragmentActivity implements AltosUnitsListener { String address = data.getExtras().getString(DeviceListActivity.EXTRA_DEVICE_ADDRESS); String name = data.getExtras().getString(DeviceListActivity.EXTRA_DEVICE_NAME); - if (D) Log.d(TAG, "Connecting to " + address + " " + name); + AltosDebug.debug("Connecting to " + address + " " + name); DeviceAddress a = new DeviceAddress(address, name); mService.send(Message.obtain(null, TelemetryService.MSG_CONNECT, a)); - if (D) Log.d(TAG, "Sent connecting message"); + AltosDebug.debug("Sent connecting message"); } catch (RemoteException e) { - if (D) Log.e(TAG, "connect device message failed"); + AltosDebug.debug("connect device message failed"); } } @@ -672,6 +771,17 @@ public class AltosDroid extends FragmentActivity implements AltosUnitsListener { } } + private void set_map_type(Intent data) { + int type = data.getIntExtra(MapTypeActivity.EXTRA_MAP_TYPE, -1); + + AltosDebug.debug("intent set_map_type %d\n", type); + if (type != -1) { + map_type = type; + for (AltosDroidTab mTab : mTabs) + mTab.set_map_type(map_type); + } + } + @Override public boolean onCreateOptionsMenu(Menu menu) { MenuInflater inflater = getMenuInflater(); @@ -682,6 +792,7 @@ public class AltosDroid extends FragmentActivity implements AltosUnitsListener { void setFrequency(double freq) { try { mService.send(Message.obtain(null, TelemetryService.MSG_SETFREQUENCY, freq)); + set_switch_time(); } catch (RemoteException e) { } } @@ -696,6 +807,7 @@ public class AltosDroid extends FragmentActivity implements AltosUnitsListener { void setBaud(int baud) { try { mService.send(Message.obtain(null, TelemetryService.MSG_SETBAUD, baud)); + set_switch_time(); } catch (RemoteException e) { } } @@ -720,6 +832,58 @@ public class AltosDroid extends FragmentActivity implements AltosUnitsListener { } } + void select_tracker(int serial) { + int i; + + AltosDebug.debug("select tracker %d\n", serial); + + if (serial == selected_serial) { + AltosDebug.debug("%d already selected\n", serial); + return; + } + + if (serial != 0) { + for (i = 0; i < serials.length; i++) + if (serials[i] == serial) + break; + + if (i == serials.length) { + AltosDebug.debug("attempt to select unknown tracker %d\n", serial); + return; + } + } + + current_serial = selected_serial = serial; + update_state(null); + } + + void touch_trackers(Integer[] serials) { + AlertDialog.Builder builder_tracker = new AlertDialog.Builder(this); + builder_tracker.setTitle("Select Tracker"); + final String[] trackers = new String[serials.length + 1]; + trackers[0] = "Auto"; + for (int i = 0; i < serials.length; i++) + trackers[i+1] = String.format("%d", serials[i]); + builder_tracker.setItems(trackers, + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int item) { + if (item == 0) + select_tracker(0); + else + select_tracker(Integer.parseInt(trackers[item])); + } + }); + AlertDialog alert_tracker = builder_tracker.create(); + alert_tracker.show(); + } + + void delete_track(int serial) { + try { + mService.send(Message.obtain(null, TelemetryService.MSG_DELETE_SERIAL, (Integer) serial)); + } catch (Exception ex) { + } + } + @Override public boolean onOptionsItemSelected(MenuItem item) { Intent serverIntent = null; @@ -737,7 +901,7 @@ public class AltosDroid extends FragmentActivity implements AltosUnitsListener { disconnectDevice(); return true; case R.id.quit: - Log.d(TAG, "R.id.quit"); + AltosDebug.debug("R.id.quit"); disconnectDevice(); finish(); return true; @@ -792,8 +956,92 @@ public class AltosDroid extends FragmentActivity implements AltosUnitsListener { boolean imperial = AltosPreferences.imperial_units(); AltosPreferences.set_imperial_units(!imperial); return true; + case R.id.preload_maps: + serverIntent = new Intent(this, PreloadMapActivity.class); + startActivityForResult(serverIntent, REQUEST_PRELOAD_MAPS); + return true; + case R.id.map_type: + serverIntent = new Intent(this, MapTypeActivity.class); + startActivityForResult(serverIntent, REQUEST_MAP_TYPE); + return true; + case R.id.map_source: + int source = AltosDroidPreferences.map_source(); + int new_source = source == AltosDroidPreferences.MAP_SOURCE_ONLINE ? AltosDroidPreferences.MAP_SOURCE_OFFLINE : AltosDroidPreferences.MAP_SOURCE_ONLINE; + AltosDroidPreferences.set_map_source(new_source); + set_map_source(new_source); + return true; + case R.id.select_tracker: + if (serials != null) { + String[] trackers = new String[serials.length+1]; + trackers[0] = "Auto"; + for (int i = 0; i < serials.length; i++) + trackers[i+1] = String.format("%d", serials[i]); + AlertDialog.Builder builder_serial = new AlertDialog.Builder(this); + builder_serial.setTitle("Select a tracker"); + builder_serial.setItems(trackers, + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int item) { + if (item == 0) + select_tracker(0); + else + select_tracker(serials[item-1]); + } + }); + AlertDialog alert_serial = builder_serial.create(); + alert_serial.show(); + + } + return true; + case R.id.delete_track: + if (serials != null) { + String[] trackers = new String[serials.length]; + for (int i = 0; i < serials.length; i++) + trackers[i] = String.format("%d", serials[i]); + AlertDialog.Builder builder_serial = new AlertDialog.Builder(this); + builder_serial.setTitle("Delete a track"); + builder_serial.setItems(trackers, + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int item) { + delete_track(serials[item]); + } + }); + AlertDialog alert_serial = builder_serial.create(); + alert_serial.show(); + + } + return true; } return false; } + static String direction(AltosGreatCircle from_receiver, + Location receiver) { + if (from_receiver == null) + return null; + + if (receiver == null) + return null; + + if (!receiver.hasBearing()) + return null; + + float bearing = receiver.getBearing(); + float heading = (float) from_receiver.bearing - bearing; + + while (heading <= -180.0f) + heading += 360.0f; + while (heading > 180.0f) + heading -= 360.0f; + + int iheading = (int) (heading + 0.5f); + + if (-1 < iheading && iheading < 1) + return "ahead"; + else if (iheading < -179 || 179 < iheading) + return "backwards"; + else if (iheading < 0) + return String.format("left %d°", -iheading); + else + return String.format("right %d°", iheading); + } }