From 1ac1cce9d88bd451cd061491c469b7539da922bc Mon Sep 17 00:00:00 2001 From: scottlandis Date: Fri, 7 Sep 2018 13:34:29 -0700 Subject: [PATCH 1/5] - TorgiService created to handle receiving and processing the GNSS info and allow collection when app is not open - Permissions and initial stability issues addressed - layouts and strings adjusted to support for better cross-device appearance and norms - basic Google Maps integration added --- build.gradle | 1 + torgi/build.gradle | 14 +- torgi/src/main/AndroidManifest.xml | 32 +- .../torgi/GnssMeasurementListener.java | 17 + .../java/org/sofwerx/torgi/MainActivity.java | 1154 +++++------------ .../java/org/sofwerx/torgi/SatStatus.java | 56 + .../java/org/sofwerx/torgi/TorgiService.java | 918 +++++++++++++ .../drawable-hdpi/ic_notification_torgi.png | Bin 0 -> 737 bytes .../drawable-mdpi/ic_notification_torgi.png | Bin 0 -> 414 bytes .../drawable-xhdpi/ic_notification_torgi.png | Bin 0 -> 1120 bytes .../drawable-xxhdpi/ic_notification_torgi.png | Bin 0 -> 2060 bytes torgi/src/main/res/layout/activity_main.xml | 106 +- torgi/src/main/res/values/strings.xml | 9 +- 13 files changed, 1373 insertions(+), 934 deletions(-) create mode 100644 torgi/src/main/java/org/sofwerx/torgi/GnssMeasurementListener.java create mode 100644 torgi/src/main/java/org/sofwerx/torgi/SatStatus.java create mode 100644 torgi/src/main/java/org/sofwerx/torgi/TorgiService.java create mode 100644 torgi/src/main/res/drawable-hdpi/ic_notification_torgi.png create mode 100644 torgi/src/main/res/drawable-mdpi/ic_notification_torgi.png create mode 100644 torgi/src/main/res/drawable-xhdpi/ic_notification_torgi.png create mode 100644 torgi/src/main/res/drawable-xxhdpi/ic_notification_torgi.png diff --git a/build.gradle b/build.gradle index 077cb2f..5587d1f 100644 --- a/build.gradle +++ b/build.gradle @@ -5,6 +5,7 @@ buildscript { repositories { google() jcenter() + mavenLocal() } dependencies { classpath 'com.android.tools.build:gradle:3.1.4' diff --git a/torgi/build.gradle b/torgi/build.gradle index 68947fc..de564fc 100644 --- a/torgi/build.gradle +++ b/torgi/build.gradle @@ -6,8 +6,8 @@ android { applicationId "org.sofwerx.torgi" minSdkVersion 24 targetSdkVersion 27 - versionCode 1 - versionName "1.0" + versionCode 2 + versionName "0.3" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" externalNativeBuild { cmake { @@ -29,13 +29,15 @@ android { } dependencies { - implementation fileTree(include: ['*.jar'], dir: 'libs') - implementation project(':geopackage-sdk') + //implementation fileTree(include: ['*.jar'], dir: 'libs') + //implementation project(':geopackage-sdk') + implementation 'mil.nga.geopackage:geopackage-android:3.0.2' implementation 'com.android.support:appcompat-v7:27.1.1' - implementation 'com.android.support.constraint:constraint-layout:1.1.2' + implementation 'com.android.support.constraint:constraint-layout:1.1.3' + implementation 'com.google.android.gms:play-services-maps:15.0.1' implementation 'com.android.support:design:27.1.1' testImplementation 'junit:junit:4.12' androidTestImplementation 'com.android.support.test:runner:1.0.2' androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' -} +} \ No newline at end of file diff --git a/torgi/src/main/AndroidManifest.xml b/torgi/src/main/AndroidManifest.xml index bbb540d..a9b48a7 100644 --- a/torgi/src/main/AndroidManifest.xml +++ b/torgi/src/main/AndroidManifest.xml @@ -2,9 +2,10 @@ - - + + + + + + + + - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/torgi/src/main/java/org/sofwerx/torgi/GnssMeasurementListener.java b/torgi/src/main/java/org/sofwerx/torgi/GnssMeasurementListener.java new file mode 100644 index 0000000..a1dd798 --- /dev/null +++ b/torgi/src/main/java/org/sofwerx/torgi/GnssMeasurementListener.java @@ -0,0 +1,17 @@ +package org.sofwerx.torgi; + +import android.location.GnssMeasurement; +import android.location.Location; + +import java.util.ArrayList; +import java.util.Collection; + +/** + * This listener is used primarily to report changes detected by TorgiService to any display activity + */ +public interface GnssMeasurementListener { + void onSatStatusUpdated(ArrayList sats); + void onLocationChanged(Location loc); + void onGnssMeasurementReceived(Collection measurement); + void onProviderChanged(String provider, boolean enabled); +} diff --git a/torgi/src/main/java/org/sofwerx/torgi/MainActivity.java b/torgi/src/main/java/org/sofwerx/torgi/MainActivity.java index 0e9850f..0c03d9d 100644 --- a/torgi/src/main/java/org/sofwerx/torgi/MainActivity.java +++ b/torgi/src/main/java/org/sofwerx/torgi/MainActivity.java @@ -1,923 +1,357 @@ package org.sofwerx.torgi; import android.Manifest; -import android.annotation.TargetApi; +import android.app.AlertDialog; +import android.content.ComponentName; import android.content.Context; -import android.location.GnssClock; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.ServiceConnection; +import android.content.SharedPreferences; +import android.content.pm.PackageManager; +import android.graphics.Color; +import android.location.GnssMeasurement; import android.os.Build; import android.os.Bundle; -import android.os.Environment; -import android.os.PowerManager; -import android.text.TextUtils; -import android.util.Base64; -import android.view.Window; -import android.view.WindowManager; -import android.webkit.WebSettings; -import android.webkit.WebView; +import android.os.IBinder; +import android.preference.PreferenceManager; +import android.support.v4.app.FragmentActivity; +import android.support.v4.content.ContextCompat; +import android.util.Log; +import android.view.View; import android.widget.TextView; import android.support.v7.app.AppCompatActivity; -import android.support.v7.widget.Toolbar; -import android.view.Menu; -import android.view.MenuItem; -import java.sql.SQLException; -import java.text.DateFormat; +import java.text.DecimalFormat; import java.text.SimpleDateFormat; import java.util.ArrayList; -import java.util.Calendar; import java.util.Collection; -import java.util.Date; -import java.util.HashMap; -import java.util.LinkedList; -import java.util.List; -import java.util.Locale; -import java.util.UUID; - -import android.content.pm.PackageManager; -import android.telephony.SmsManager; - -import android.support.annotation.RequiresApi; -import android.support.v4.content.ContextCompat; import android.support.v4.app.ActivityCompat; import android.location.Location; -import android.location.Criteria; -import android.location.LocationManager; -import android.location.LocationListener; -import android.location.GnssStatus; -import android.location.GnssMeasurement; -import android.location.GnssMeasurementsEvent; - -import static java.time.Instant.now; -import static mil.nga.geopackage.db.GeoPackageDataType.INT; -import static mil.nga.geopackage.db.GeoPackageDataType.INTEGER; -import static mil.nga.geopackage.db.GeoPackageDataType.TEXT; -import static mil.nga.geopackage.db.GeoPackageDataType.REAL; -import static mil.nga.geopackage.db.GeoPackageDataType.BOOLEAN; -import static mil.nga.geopackage.db.GeoPackageDataType.DATETIME; - - +import android.widget.Toast; + +import com.google.android.gms.maps.CameraUpdateFactory; +import com.google.android.gms.maps.GoogleMap; +import com.google.android.gms.maps.OnMapReadyCallback; +import com.google.android.gms.maps.SupportMapFragment; +import com.google.android.gms.maps.model.LatLng; +import com.google.android.gms.maps.model.MapStyleOptions; +import com.google.android.gms.maps.model.Marker; +import com.google.android.gms.maps.model.MarkerOptions; +import com.google.android.gms.maps.model.Polyline; +import com.google.android.gms.maps.model.PolylineOptions; + +public class MainActivity extends FragmentActivity implements OnMapReadyCallback, GnssMeasurementListener { + private LatLng CENTER_US = new LatLng(39.181071, -99.938295); + private final static String TAG = "TORGIact"; + private final static String PREF_LAT = "lat"; + private final static String PREF_LNG = "lng"; + private final static int MAX_HISTORY_LENGTH = 50; + private final static int PERM_REQUEST_CODE = 1; + private final static SimpleDateFormat fmtTime = new SimpleDateFormat("HH:mm:ss"); + private final static DecimalFormat fmtAccuracy = new DecimalFormat("#.##"); + private TextView stat_tv; + private TextView cur_tv; + private TextView meas_tv; + private boolean serviceBound = false; + private TorgiService torgiService = null; + private GoogleMap mMap; + private boolean mapReady = false; + private Marker current = null; + private Polyline historyPolyline = null; + private ArrayList history = null; + private LatLng currentFixLatLng = null; -import android.app.AlertDialog; + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + stat_tv = findViewById(R.id.status_text); + cur_tv = findViewById(R.id.current_text); + meas_tv = findViewById(R.id.measurement_text); -import mil.nga.geopackage.BoundingBox; -import mil.nga.geopackage.GeoPackage; -import mil.nga.geopackage.GeoPackageException; -import mil.nga.geopackage.GeoPackageManager; -import mil.nga.geopackage.attributes.AttributesCursor; -import mil.nga.geopackage.attributes.AttributesDao; -import mil.nga.geopackage.attributes.AttributesRow; -import mil.nga.geopackage.core.contents.Contents; -import mil.nga.geopackage.core.contents.ContentsDao; -import mil.nga.geopackage.core.contents.ContentsDataType; -import mil.nga.geopackage.core.srs.SpatialReferenceSystem; -import mil.nga.geopackage.core.srs.SpatialReferenceSystemDao; -import mil.nga.geopackage.db.GeoPackageCoreConnection; -import mil.nga.geopackage.db.GeoPackageDataType; -import mil.nga.geopackage.extension.related.ExtendedRelation; -import mil.nga.geopackage.extension.related.ExtendedRelationsDao; -import mil.nga.geopackage.extension.related.RelatedTablesExtension; -import mil.nga.geopackage.extension.related.UserMappingDao; -import mil.nga.geopackage.extension.related.UserMappingRow; -import mil.nga.geopackage.extension.related.UserMappingTable; -import mil.nga.geopackage.extension.related.dublin.DublinCoreType; -import mil.nga.geopackage.extension.related.simple.SimpleAttributesDao; -import mil.nga.geopackage.extension.related.simple.SimpleAttributesRow; -import mil.nga.geopackage.extension.related.simple.SimpleAttributesTable; -import mil.nga.geopackage.factory.GeoPackageFactory; -import mil.nga.geopackage.features.columns.GeometryColumns; -import mil.nga.geopackage.features.columns.GeometryColumnsDao; -import mil.nga.geopackage.features.user.FeatureColumn; -import mil.nga.geopackage.features.user.FeatureDao; -import mil.nga.geopackage.features.user.FeatureRow; -import mil.nga.geopackage.features.user.FeatureTable; -import mil.nga.geopackage.geom.GeoPackageGeometryData; -import mil.nga.geopackage.schema.TableColumnKey; -import mil.nga.geopackage.user.UserTable; -import mil.nga.geopackage.user.custom.UserCustomColumn; -import mil.nga.geopackage.user.custom.UserCustomTable; -import mil.nga.sf.Geometry; -import mil.nga.sf.GeometryEnvelope; -import mil.nga.sf.GeometryType; -import mil.nga.sf.Point; -import mil.nga.sf.proj.ProjectionConstants; -import mil.nga.sf.util.GeometryEnvelopeBuilder; - -class SatStatus { -String constellation; -int svid; - -double cn0; -boolean in_fix; - -boolean has_almanac; -boolean has_ephemeris; -boolean has_carrier_freq; - -double elevation_deg; -double azimuth_deg; -double carrier_freq_hz; - -} - -public class MainActivity extends AppCompatActivity { - - HashMap SatType = new HashMap() { - { - put(0, "Unknown"); - put(1, "GPS"); - put(2, "SBAS"); - put(3, "Glonass"); - put(4, "QZSS"); - put(5, "Beidou"); - put(6, "Galileo"); + meas_tv.setText("Acquiring satellites...\n", TextView.BufferType.EDITABLE); + stat_tv.setText("Acquiring satellites...\n", TextView.BufferType.EDITABLE); + cur_tv.setText("Acquiring satellites...\n", TextView.BufferType.EDITABLE); + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { + meas_tv.setText("(individual sat measurements unavailable on this platform)"); + stat_tv.setText("(rcvr clock measurements unavailable on this platform)"); } - }; - - final static long WGS84_SRS = 4326; - final static int MIN_SDK_GNSS = 26; // min. SDK level for certain GNSS measurement methods - - private static final String ID_COLUMN = "id"; - private static final String GEOMETRY_COLUMN = "geom"; - - String GpkgFilename = "TORGI-GNSS"; - String GpkgFolder = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getAbsolutePath(); - - private static final String PtsTableName = "gps_observation_points"; - private static final String satTblName = "sat_data"; - private static final String clkTblName = "rcvr_clock"; - private static final String motionTblName = "motion"; - private static final String satmapTblName = PtsTableName + "_" + satTblName; - private static final String clkmapTblName = satTblName + "_" + clkTblName; - private static final String motionmapTblName = PtsTableName + "_" + motionTblName; + SupportMapFragment mapFragment = (SupportMapFragment) getSupportFragmentManager().findFragmentById(R.id.map); + mapFragment.getMapAsync(this); - HashMap SatStatus = new HashMap<>(); - HashMap SatInfo = new HashMap<>(); - HashMap SatRowsToMap = new HashMap<>(); - - GeoPackage GPSgpkg = null; - RelatedTablesExtension RTE = null; - ExtendedRelation SatExtRel = null; - ExtendedRelation ClkExtRel = null; - UserTable PtsTable = null; - UserTable SatTable = null; - UserTable ClkTable = null; - UserTable MotionTable = null; - - - @RequiresApi(26) - private final GnssStatus.Callback StatusListener = new GnssStatus.Callback() { - public void onSatelliteStatusChanged(final GnssStatus status) { - - String displayTxt = ""; - int numSats = status.getSatelliteCount(); - - for (int i = 0; i < numSats; ++i) { - SatStatus thisSat = new SatStatus(); - thisSat.constellation = SatType.get(status.getConstellationType(i)); - thisSat.svid = status.getSvid(i); - thisSat.cn0 = status.getCn0DbHz(i); - - thisSat.has_almanac = status.hasAlmanacData(i); - thisSat.has_ephemeris = status.hasEphemerisData(i); - - if (Build.VERSION.SDK_INT >= MIN_SDK_GNSS) { - thisSat.has_carrier_freq = status.hasCarrierFrequencyHz(i); - } else { - thisSat.has_carrier_freq = false; - } - - thisSat.azimuth_deg = status.getAzimuthDegrees(i); - thisSat.elevation_deg = status.getElevationDegrees(i); - thisSat.cn0 = status.getCn0DbHz(i); - - String hashkey = thisSat.constellation + status.getSvid(i); - SatStatus.put(hashkey, thisSat); - - if (status.usedInFix(i)) { - displayTxt = displayTxt + thisSat.constellation + thisSat.svid + "\n"; - } - } - - TextView stat_tv = findViewById(R.id.status_text); - stat_tv.setText(displayTxt); - } + checkPermissions(); + startService(); }; - @RequiresApi(26) - private final GnssMeasurementsEvent.Callback MeasurementListener = new GnssMeasurementsEvent.Callback() { - - public void onGnssMeasurementsReceived(GnssMeasurementsEvent event) { - Collection gm = event.getMeasurements(); - String displayTxt = ""; - - TextView meas_tv = findViewById(R.id.measurement_text); - - GnssClock clk = event.getClock(); - - SimpleAttributesDao clkDao = RTE.getSimpleAttributesDao(clkTblName); - SimpleAttributesRow clkrow = clkDao.newRow(); - - clkrow.setValue("time_nanos", (double) clk.getTimeNanos()); - if (clk.hasTimeUncertaintyNanos()) { - clkrow.setValue("time_uncertainty_nanos", (double) clk.getTimeUncertaintyNanos()); - clkrow.setValue("has_time_uncertainty_nanos", 1); - } else { - clkrow.setValue("time_uncertainty_nanos", (double) 0.0); - clkrow.setValue("has_time_uncertainty_nanos", 0); - } - - if (clk.hasBiasNanos()) { - clkrow.setValue("bias_nanos", (double) clk.getBiasNanos()); - clkrow.setValue("has_bias_nanos", 1); - } else { - clkrow.setValue("bias_nanos", (double) 0.0); - clkrow.setValue("has_bias_nanos", 0); - } - if (clk.hasFullBiasNanos()) { - clkrow.setValue("full_bias_nanos", clk.getFullBiasNanos()); - clkrow.setValue("has_full_bias_nanos", 1); - } else { - clkrow.setValue("full_bias_nanos", 0); - clkrow.setValue("has_full_bias_nanos", 0); - } - if (clk.hasBiasUncertaintyNanos()) { - clkrow.setValue("bias_uncertainty_nanos", (double) clk.getBiasUncertaintyNanos()); - clkrow.setValue("has_bias_uncertainty_nanos", 1); - } else { - clkrow.setValue("bias_uncertainty_nanos", (double) 0.0); - clkrow.setValue("has_bias_uncertainty_nanos", 0); - } - if (clk.hasDriftNanosPerSecond()) { - clkrow.setValue("drift_nanos_per_sec", (double) clk.getDriftNanosPerSecond()); - clkrow.setValue("has_drift_nanos_per_sec", 1); - } else { - clkrow.setValue("drift_nanos_per_sec", (double) 0.0); - clkrow.setValue("has_drift_nanos_per_sec", 0); - } - if (clk.hasDriftUncertaintyNanosPerSecond()) { - clkrow.setValue("drift_uncertainty_nps", (double) clk.getDriftUncertaintyNanosPerSecond()); - clkrow.setValue("has_drift_uncertainty_nps", 1); - } else { - clkrow.setValue("drift_uncertainty_nps", (double) 0.0); - clkrow.setValue("has_drift_uncertainty_nps", 0); - } - if (clk.hasLeapSecond()) { - clkrow.setValue("leap_second", clk.getLeapSecond()); - clkrow.setValue("has_leap_second", 1); - } else { - clkrow.setValue("leap_second", 0); - clkrow.setValue("has_leap_second", 0); - } - clkrow.setValue("hw_clock_discontinuity_count", clk.getHardwareClockDiscontinuityCount()); - - clkrow.setValue("data_dump", clk.toString()); - clkDao.insert(clkrow); - - UserMappingDao clkMapDAO = RTE.getMappingDao(ClkExtRel); - - SatRowsToMap.clear(); - - for(final GnssMeasurement g : gm) { - - String con = SatType.get(g.getConstellationType()); - String hashkey = con + g.getSvid(); - - HashMap thisSat = new HashMap(); - - SimpleAttributesDao satDao = RTE.getSimpleAttributesDao(satTblName); - SimpleAttributesRow satrow = satDao.newRow(); - - satrow.setValue("svid", g.getSvid()); - satrow.setValue("constellation", con); - satrow.setValue("cn0", (double) g.getCn0DbHz()); - - if (Build.VERSION.SDK_INT >= MIN_SDK_GNSS) { - if (g.hasAutomaticGainControlLevelDb()) { - satrow.setValue("agc", (double) g.getAutomaticGainControlLevelDb()); - satrow.setValue("has_agc", 1); - } else { - satrow.setValue("agc", 0); - satrow.setValue("has_agc", 0); - } - } else { - satrow.setValue("agc", (double) 0.0); - satrow.setValue("has_agc", 0); - } - satrow.setValue("sync_state_flags", g.getState()); - satrow.setValue("sync_state_txt", " "); - satrow.setValue("sat_time_nanos", (double) g.getReceivedSvTimeNanos()); - satrow.setValue("sat_time_1sigma_nanos",(double) g.getReceivedSvTimeUncertaintyNanos()); - satrow.setValue("rcvr_time_offset_nanos", (double) g.getTimeOffsetNanos()); - satrow.setValue("multipath", g.getMultipathIndicator()); - if (g.hasCarrierFrequencyHz()) { - satrow.setValue("carrier_freq_hz", (double) g.getCarrierFrequencyHz()); - satrow.setValue("has_carrier_freq", 1); - } else { - satrow.setValue("carrier_freq_hz", (double) 0.0); - satrow.setValue("has_carrier_freq", 0); - } - satrow.setValue("accum_delta_range", (double) g.getAccumulatedDeltaRangeMeters()); - satrow.setValue("accum_delta_range_1sigma", (double) g.getAccumulatedDeltaRangeUncertaintyMeters()); - satrow.setValue("accum_delta_range_state_flags", g.getAccumulatedDeltaRangeState()); - satrow.setValue("accum_delta_range_state_txt", " "); - satrow.setValue("pseudorange_rate_mps", (double) g.getPseudorangeRateMetersPerSecond()); - satrow.setValue("pseudorange_rate_1sigma", (double) g.getPseudorangeRateUncertaintyMetersPerSecond()); - - if (SatStatus.containsKey(hashkey)) { - satrow.setValue("in_fix", SatStatus.get(hashkey).in_fix ? 0 : 1); - - satrow.setValue("has_almanac", SatStatus.get(hashkey).has_almanac ? 0 : 1); - satrow.setValue("has_ephemeris", SatStatus.get(hashkey).has_ephemeris ? 0 : 1); - satrow.setValue("has_carrier_freq", SatStatus.get(hashkey).has_carrier_freq ? 0 : 1); - - satrow.setValue("elevation_deg", (double) SatStatus.get(hashkey).elevation_deg); - satrow.setValue("azimuth_deg", (double) SatStatus.get(hashkey).azimuth_deg); - } else { - satrow.setValue("in_fix", 0); - - satrow.setValue("has_almanac", 0); - satrow.setValue("has_ephemeris", 0); - satrow.setValue("has_carrier_freq", 0); - - satrow.setValue("elevation_deg", 0.0); - satrow.setValue("azimuth_deg", 0.0); - } - displayTxt = displayTxt + g.toString() + "\n"; + @Override + public void onResume() { + super.onResume(); + if (serviceBound && (torgiService != null)) + torgiService.setListener(this); + } - satrow.setValue("data_dump", g.toString()); - satDao.insert(satrow); + @Override + public void onPause() { + super.onPause(); + saveLastLocation(); + if (serviceBound && (torgiService != null)) + torgiService.setListener(null); + } - UserMappingRow clkmaprow = clkMapDAO.newRow(); - clkmaprow.setBaseId(satrow.getId()); - clkmaprow.setRelatedId(clkrow.getId()); - clkMapDAO.create(clkmaprow); + @Override + public void onStop() { + super.onStop(); + if (serviceBound && (torgiService != null)) + unbindService(mConnection); + } - SatInfo.put(hashkey, g); - SatRowsToMap.put(hashkey, satrow.getId()); - meas_tv.setText(g.toString()); - } + protected ServiceConnection mConnection = new ServiceConnection() { + @Override + public void onServiceConnected(ComponentName className, IBinder service) { + Log.d(TAG,"MdxService bound to this activity"); + TorgiService.TorgiBinder binder = (TorgiService.TorgiBinder) service; + torgiService = binder.getService(); + serviceBound = true; + onTorgiServiceConnected(); } - }; - LocationListener locListener = new LocationListener() { - public void onProviderEnabled(String provider) { - TextView cur_tv = findViewById(R.id.current_text); - cur_tv.setText(provider + " enabled"); + @Override + public void onServiceDisconnected(ComponentName arg0) { + serviceBound = false; } + }; - public void onProviderDisabled(String provider) { - TextView cur_tv = findViewById(R.id.current_text); - cur_tv.setText(provider + " disabled"); - } + private void onTorgiServiceConnected() { + torgiService.setListener(this); + } - public void onStatusChanged(final String provider, int status, Bundle extras) { - TextView cur_tv = findViewById(R.id.current_text); - // cur_tv.setText(provider + " status changed"); + private void startService() { + if (serviceBound) + torgiService.startGnssRecorder(); + else { + startService(new Intent(this, TorgiService.class)); + Intent intent = new Intent(this, TorgiService.class); + bindService(intent, mConnection, Context.BIND_AUTO_CREATE); } + } - - @TargetApi(26) - public void onLocationChanged(final Location loc) { - HashMap maprows = (HashMap)SatRowsToMap.clone(); - SatRowsToMap.clear(); - TextView cur_tv = findViewById(R.id.current_text); - - HashMap locData = new HashMap() { - { - put("Lat", String.valueOf(loc.getLatitude())); - put("Lon", String.valueOf(loc.getLongitude())); - put("Alt", String.valueOf(loc.getAltitude())); - put("Provider", String.valueOf(loc.getProvider())); - put("Time", String.valueOf(loc.getTime())); - put("FixSatCount", String.valueOf(loc.getExtras().getInt("satellites"))); - put("HasRadialAccuracy", String.valueOf(loc.hasAccuracy())); - put("RadialAccuracy", String.valueOf(loc.getAccuracy())); - if (Build.VERSION.SDK_INT >= MIN_SDK_GNSS) { - put("HasVerticalAccuracy", String.valueOf(loc.hasVerticalAccuracy())); - put("VerticalAccuracy", String.valueOf(loc.getVerticalAccuracyMeters())); + private void drawMarker(LatLng pos, String info) { + if ((pos != null) && mapReady) { + if (history == null) + history = new ArrayList<>(); + history.add(pos); + if (history.size() > 1) { + if (history.size() > MAX_HISTORY_LENGTH) + history.remove(0); + if (historyPolyline == null) { + PolylineOptions opts = new PolylineOptions(); + for (LatLng pt:history) { + opts.add(pt); } - } - }; - - String txt = locData.toString() + "\n\n" + loc.toString() + "\n\n" + GpkgFilename + " SDK v" + Build.VERSION.SDK_INT; - cur_tv.setText(txt); - - FeatureDao featDao = GPSgpkg.getFeatureDao(PtsTableName); - UserMappingDao satMapDAO = RTE.getMappingDao(SatExtRel); - - FeatureRow frow = featDao.newRow(); - - Point fix = new Point(loc.getLongitude(), loc.getLatitude(), loc.getAltitude()); - - GeoPackageGeometryData geomData = new GeoPackageGeometryData(WGS84_SRS); - geomData.setGeometry(fix); - - frow.setGeometry(geomData); - - frow.setValue("Lat", (double) loc.getLatitude()); - frow.setValue("Lon", (double) loc.getLongitude()); - frow.setValue("Alt", (double) loc.getAltitude()); - frow.setValue("Provider", loc.getProvider()); - frow.setValue("GPSTime", loc.getTime()); - frow.setValue("FixSatCount", loc.getExtras().getInt("satellites")); - if (loc.hasAccuracy()) { - frow.setValue("RadialAccuracy", (double) loc.getAccuracy()); - frow.setValue("HasRadialAccuracy", 1); - } else { - frow.setValue("RadialAccuracy", (double) 0.0); - frow.setValue("HasRadialAccuracy", 0); - } - - if (loc.hasSpeed()) { - frow.setValue("Speed", (double) loc.getAccuracy()); - frow.setValue("HasSpeed", 1); - } else { - frow.setValue("Speed", (double) 0.0); - frow.setValue("HasSpeed", 0); - } - - if (loc.hasBearing()) { - frow.setValue("Bearing", (double) loc.getAccuracy()); - frow.setValue("HasBearing", 1); - } else { - frow.setValue("Bearing", (double) 0.0); - frow.setValue("HasBearing", 0); - } - - if (Build.VERSION.SDK_INT >= MIN_SDK_GNSS) { - frow.setValue("SysTime", now().toString()); - - if (loc.hasVerticalAccuracy()) { - frow.setValue("VerticalAccuracy", (double) loc.getVerticalAccuracyMeters()); - frow.setValue("HasVerticalAccuracy", 1); - } else { - frow.setValue("VerticalAccuracy", (double) 0.0); - frow.setValue("HasVerticalAccuracy", 0); - } - - if (loc.hasSpeedAccuracy()) { - frow.setValue("SpeedAccuracy", (double) loc.getAccuracy()); - frow.setValue("HasSpeedAccuracy", 1); - } else { - frow.setValue("SpeedAccuracy", (double) 0.0); - frow.setValue("HasSpeedAccuracy", 0); - } - - if (loc.hasBearingAccuracy()) { - frow.setValue("BearingAccuracy", (double) loc.getAccuracy()); - frow.setValue("HasBearingAccuracy", 1); - } else { - frow.setValue("BearingAccuracy", (double) 0.0); - frow.setValue("HasBearingAccuracy", 0); - } - } else { - Date currentTime = Calendar.getInstance().getTime(); - SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ", Locale.US); - frow.setValue("SysTime", df.format(currentTime)); - frow.setValue("HasVerticalAccuracy", 0); - frow.setValue("VerticalAccuracy", (double) 0.0); - } - - frow.setValue("data_dump", loc.toString() + " " + loc.describeContents()); - - featDao.insert(frow); - - for (long id : maprows.values()) { - UserMappingRow satmaprow = satMapDAO.newRow(); - satmaprow.setBaseId(frow.getId()); - satmaprow.setRelatedId(id); - satMapDAO.create(satmaprow); - } - - // update feature table bounding box if necessary - boolean dirty = false; - BoundingBox bb = featDao.getBoundingBox(); - if (loc.getLatitude() < bb.getMinLatitude()) { - bb.setMinLatitude(loc.getLatitude()); - dirty = true; - } - if (loc.getLatitude() > bb.getMaxLatitude()) { - bb.setMaxLatitude(loc.getLatitude()); - dirty = true; + opts.width(5); + opts.color(Color.YELLOW); + historyPolyline = mMap.addPolyline(opts); + } else + historyPolyline.setPoints(history); } - - if (loc.getLongitude() < bb.getMinLongitude()) { - bb.setMinLongitude(loc.getLongitude()); - } - if (loc.getLongitude() > bb.getMaxLongitude()) { - bb.setMaxLongitude(loc.getLongitude()); - } - - if (dirty) { - String bbsql = "UPDATE gpkg_contents SET " + - " min_x = " + bb.getMinLongitude() + - ", max_x = " + bb.getMaxLongitude() + - ", min_y = " + bb.getMinLatitude() + - ", max_y = " + bb.getMaxLatitude() + - " WHERE table_name = '" + PtsTableName + "';"; - GPSgpkg.execSQL(bbsql); - } - -// - // - // communicate new observation - // -// String phoneNumber = ""; -// -// SmsManager smsMgr = SmsManager.getDefault(); -// smsMgr.sendTextMessage(phoneNumber, null, txt, null, null); + currentFixLatLng = pos; + if (current == null) { + current = mMap.addMarker(new MarkerOptions() + .position(pos) + .title("GPS")); + mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(pos, 19)); + } else + current.setPosition(pos); + current.setSnippet(info); } - }; - - - ////////////////////////////////////////////////////////////////////////////////////// - @RequiresApi(26) - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_main); - - TextView cur_tv = findViewById(R.id.current_text); - - TextView meas_tv = findViewById(R.id.measurement_text); - TextView stat_tv = findViewById(R.id.status_text); - meas_tv.setText("Acquiring satellites...\n", TextView.BufferType.EDITABLE); - stat_tv.setText("Acquiring satellites...\n", TextView.BufferType.EDITABLE); - cur_tv.setText("Acquiring satellites...\n", TextView.BufferType.EDITABLE); + } - Toolbar toolbar = findViewById(R.id.main_toolbar); - setSupportActionBar(toolbar); - - String[] perms = {Manifest.permission.ACCESS_FINE_LOCATION, - Manifest.permission.WRITE_EXTERNAL_STORAGE, -// Manifest.permission.SEND_SMS, - Manifest.permission.WAKE_LOCK - }; - ActivityCompat.requestPermissions(this, perms, 1); - - if (GPSgpkg == null) { - try { - GPSgpkg = setupGpkgDB(this, GpkgFolder, GpkgFilename); - } catch (SQLException e) { - // TODO: handle this + @Override + public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) { + if (requestCode == PERM_REQUEST_CODE) { + if (checkPermissions()) + startService(); + else { + Toast.makeText(this, "Both Location and Storage permissions are needed", Toast.LENGTH_LONG).show(); } } + } - List tbls = GPSgpkg.getTables(); - tbls.add(GPSgpkg.getApplicationId()); - - String dlgMsg = "SignalMonitor: " + TextUtils.join(" - ", tbls); - - toolbar.setTitle(dlgMsg); - -// getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); - - if (Build.VERSION.SDK_INT < MIN_SDK_GNSS) { // won't need extra text areas - int meas_h = meas_tv.getHeight(); - int stat_h = meas_tv.getHeight(); - - int new_meas = meas_h / 4; - int new_stat = stat_h / 4; - meas_tv.setHeight(new_meas); - stat_tv.setHeight(new_stat); - stat_tv.setTop(stat_tv.getTop() - (meas_h - new_meas)); - - cur_tv.setHeight(cur_tv.getHeight() + (meas_h - new_meas) + (stat_h - new_stat)); - - meas_tv.setText("(individual sat measurements unavailable on this platform)\n", TextView.BufferType.EDITABLE); - stat_tv.setText("(rcvr clock measurements unavailable on this platform)\n", TextView.BufferType.EDITABLE); - } - - if (ContextCompat.checkSelfPermission(this, android.Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) { - stat_tv.setText("Location access denied."); - } else { - PowerManager powerManager = (PowerManager) getSystemService(POWER_SERVICE); - PowerManager.WakeLock wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, - "MyApp::MyWakelockTag"); - wakeLock.acquire(); - - LocationManager locMgr = getSystemService(LocationManager.class); - if (Build.VERSION.SDK_INT >= MIN_SDK_GNSS) { - locMgr.registerGnssMeasurementsCallback(MeasurementListener); - locMgr.registerGnssStatusCallback(StatusListener); + /** + * Returns true once all required permissions are granted, otherwise returns false and requests the + * required permission + * @return + */ + private boolean checkPermissions() { + ArrayList needed = new ArrayList<>(); + if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) + needed.add(Manifest.permission.ACCESS_FINE_LOCATION); + if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) + needed.add(Manifest.permission.WRITE_EXTERNAL_STORAGE); + if (needed.isEmpty()) + return true; + else { + String[] perms = new String[needed.size()]; + perms = new String[perms.length]; + for (int i=0;i'%23' is the percent code for ‘#‘ "; -// String encodedHtml = Base64.encodeToString(unencodedHtml.getBytes(), Base64.NO_PADDING); -// map.loadData(encodedHtml, "text/html", "base64"); - -// WebView map = new WebView(this); -// setContentView(map); - }; -////////////////////////////////////////////////////////////////////////////////////// - - @RequiresApi(26) - private GeoPackage setupGpkgDB(Context context, String folder, String file) throws GeoPackageException, SQLException { - - // Create database file - String fileTime = ""; - if (Build.VERSION.SDK_INT >= MIN_SDK_GNSS) { - fileTime = now().toString(); - } else { - Date currentTime = Calendar.getInstance().getTime(); - fileTime = currentTime.toString(); - } - fileTime = fileTime.replace('/', '-'); - fileTime = fileTime.replace(':', '-'); - fileTime = fileTime.replace(' ', '-'); - - fileTime = fileTime.replace("-", ""); - - GpkgFilename = folder + "/" + file + "-" + fileTime + ".gpkg"; - - GeoPackageManager gpkgMgr = GeoPackageFactory.getManager(context); - if (!gpkgMgr.exists(GpkgFilename)) { - gpkgMgr.create(GpkgFilename); - - } - GeoPackage gpkg = gpkgMgr.open(GpkgFilename, true); - if (gpkg == null) { - throw new GeoPackageException("Failed to open GeoPackage database " + GpkgFilename); + private void saveLastLocation() { + if ((current != null) && (current.getPosition() != null)) { + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getApplicationContext()); + SharedPreferences.Editor edit = prefs.edit(); + edit.putFloat(PREF_LAT,(float)current.getPosition().latitude); + edit.putFloat(PREF_LNG,(float)current.getPosition().longitude); + edit.apply(); } - - // create SRS & feature tables - SpatialReferenceSystemDao srsDao = gpkg.getSpatialReferenceSystemDao(); - - SpatialReferenceSystem srs = srsDao.getOrCreateCode(ProjectionConstants.AUTHORITY_EPSG, (long) ProjectionConstants.EPSG_WORLD_GEODETIC_SYSTEM); - - gpkg.createGeometryColumnsTable(); - - PtsTable = createObservationTable(gpkg, srs, PtsTableName, GeometryType.POINT); - String bbsql = "UPDATE gpkg_contents SET min_x = 180.0, max_x = -180.0, min_y = 90.0, max_y = -90.0 WHERE table_name = '" + PtsTableName + "';"; - gpkg.execSQL(bbsql); - - Contents contents = new Contents(); - RTE = new RelatedTablesExtension(gpkg); - - SatTable = createSatelliteTable(contents, RTE, srs, satTblName, satmapTblName, PtsTableName); - ClkTable = createClockTable(contents, RTE, srs, clkTblName, clkmapTblName, satTblName); - - MotionTable = createMotionTable(contents, RTE, srs, motionTblName, motionmapTblName, PtsTableName); - - return gpkg; } - - private UserTable createObservationTable(GeoPackage geoPackage, SpatialReferenceSystem srs, String tableName, GeometryType type) throws SQLException { - - ContentsDao contentsDao = geoPackage.getContentsDao(); - - Contents contents = new Contents(); - contents.setTableName(tableName); - contents.setDataType(ContentsDataType.FEATURES); - contents.setIdentifier(tableName); - contents.setDescription(tableName); - contents.setSrs(srs); - - int colNum = 0; - List tblcols = new LinkedList<>(); - tblcols.add(FeatureColumn.createPrimaryKeyColumn(colNum++, ID_COLUMN)); - tblcols.add(FeatureColumn.createGeometryColumn(colNum++, GEOMETRY_COLUMN, GeometryType.POINT, false, null)); - tblcols.add(FeatureColumn.createColumn(colNum++, "SysTime", DATETIME, false, null)); - tblcols.add(FeatureColumn.createColumn(colNum++, "Lat", REAL, false, null)); - tblcols.add(FeatureColumn.createColumn(colNum++, "Lon", REAL, false, null)); - tblcols.add(FeatureColumn.createColumn(colNum++, "Alt", REAL, false, null)); - tblcols.add(FeatureColumn.createColumn(colNum++, "Provider", TEXT, false, null)); - tblcols.add(FeatureColumn.createColumn(colNum++, "GPSTime", INTEGER, false, null)); - tblcols.add(FeatureColumn.createColumn(colNum++, "FixSatCount", INTEGER, false, null)); - tblcols.add(FeatureColumn.createColumn(colNum++, "HasRadialAccuracy", INTEGER, false, null)); - tblcols.add(FeatureColumn.createColumn(colNum++, "HasVerticalAccuracy", INTEGER, false, null)); - tblcols.add(FeatureColumn.createColumn(colNum++, "RadialAccuracy", REAL, false, null)); - tblcols.add(FeatureColumn.createColumn(colNum++, "VerticalAccuracy", REAL, false, null)); - - tblcols.add(FeatureColumn.createColumn(colNum++, "ElapsedRealtimeNanos", REAL, false, null)); - - tblcols.add(FeatureColumn.createColumn(colNum++, "HasSpeed", INTEGER, false, null)); - tblcols.add(FeatureColumn.createColumn(colNum++, "HasSpeedAccuracy", INTEGER, false, null)); - tblcols.add(FeatureColumn.createColumn(colNum++, "Speed", REAL, false, null)); - tblcols.add(FeatureColumn.createColumn(colNum++, "SpeedAccuracy", REAL, false, null)); - - tblcols.add(FeatureColumn.createColumn(colNum++, "HasBearing", INTEGER, false, null)); - tblcols.add(FeatureColumn.createColumn(colNum++, "HasBearingAccuracy", INTEGER, false, null)); - tblcols.add(FeatureColumn.createColumn(colNum++, "Bearing", REAL, false, null)); - tblcols.add(FeatureColumn.createColumn(colNum++, "BearingAccuracy", REAL, false, null)); - - tblcols.add(FeatureColumn.createColumn(colNum++, "data_dump", TEXT, false, null)); - - FeatureTable table = new FeatureTable(tableName, tblcols); - geoPackage.createFeatureTable(table); - - contentsDao.create(contents); - - GeometryColumnsDao geometryColumnsDao = geoPackage.getGeometryColumnsDao(); - - GeometryColumns geometryColumns = new GeometryColumns(); - geometryColumns.setContents(contents); - geometryColumns.setColumnName(GEOMETRY_COLUMN); - geometryColumns.setGeometryType(type); - geometryColumns.setSrs(srs); - geometryColumns.setZ((byte) 0); - geometryColumns.setM((byte) 0); - geometryColumnsDao.create(geometryColumns); - - return (table); + private LatLng getLastLatLng() { + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getApplicationContext()); + float lat = prefs.getFloat(PREF_LAT,Float.NaN); + float lng = prefs.getFloat(PREF_LNG,Float.NaN); + if (!Float.isNaN(lat) && !Float.isNaN(lng)) + return new LatLng(lat,lng); + else + return null; } - - private UserTable createSatelliteTable(Contents contents, RelatedTablesExtension rte, SpatialReferenceSystem srs, String tableName, String mapTblName, String baseTblName) { - contents.setTableName(tableName); - contents.setDataType(ContentsDataType.FEATURES); - contents.setIdentifier(tableName); - contents.setDescription(tableName); - contents.setSrs(srs); - - int colNum = 1; - List tblcols = new LinkedList<>(); -// tblcols.add(UserCustomColumn.createPrimaryKeyColumn(colNum++, ID_COLUMN)); - // Dublin Core metadata descriptor profile -// tblcols.add(UserCustomColumn.createColumn(colNum++, DublinCoreType.DATE.getName(), GeoPackageDataType.DATETIME, false, null)); -// tblcols.add(FeatureColumn.createColumn(colNum++, DublinCoreType.TITLE.getName(), GeoPackageDataType.TEXT, false, null)); -// tblcols.add(FeatureColumn.createColumn(colNum++, DublinCoreType.SOURCE.getName(), GeoPackageDataType.TEXT, false, null)); -// tblcols.add(FeatureColumn.createColumn(colNum++, DublinCoreType.DESCRIPTION.getName(), GeoPackageDataType.TEXT, false, null)); - - // android GNSS measurements - tblcols.add(UserCustomColumn.createColumn(colNum++, "svid", INTEGER, true, null)); - tblcols.add(UserCustomColumn.createColumn(colNum++, "constellation", TEXT, true, null)); - tblcols.add(UserCustomColumn.createColumn(colNum++, "cn0", REAL, true, null)); - tblcols.add(UserCustomColumn.createColumn(colNum++, "agc", REAL, true, null)); - tblcols.add(UserCustomColumn.createColumn(colNum++, "has_agc", INTEGER, true, null)); - tblcols.add(UserCustomColumn.createColumn(colNum++, "in_fix", INTEGER, true, null)); - - tblcols.add(UserCustomColumn.createColumn(colNum++, "sync_state_flags", INTEGER, true, null)); - tblcols.add(UserCustomColumn.createColumn(colNum++, "sync_state_txt", TEXT, true, null)); - tblcols.add(UserCustomColumn.createColumn(colNum++, "sat_time_nanos", REAL, true, null)); - tblcols.add(UserCustomColumn.createColumn(colNum++, "sat_time_1sigma_nanos", REAL, true, null)); - tblcols.add(UserCustomColumn.createColumn(colNum++, "rcvr_time_offset_nanos", REAL, true, null)); - tblcols.add(UserCustomColumn.createColumn(colNum++, "multipath", INTEGER, true, null)); - - tblcols.add(UserCustomColumn.createColumn(colNum++, "has_carrier_freq", INTEGER, true, null)); - tblcols.add(UserCustomColumn.createColumn(colNum++, "carrier_freq_hz", REAL, true, null)); - tblcols.add(UserCustomColumn.createColumn(colNum++, "accum_delta_range", REAL, true, null)); - tblcols.add(UserCustomColumn.createColumn(colNum++, "accum_delta_range_1sigma", REAL, true, null)); - tblcols.add(UserCustomColumn.createColumn(colNum++, "accum_delta_range_state_flags", INTEGER, true, null)); - tblcols.add(UserCustomColumn.createColumn(colNum++, "accum_delta_range_state_txt", TEXT, true, null)); - tblcols.add(UserCustomColumn.createColumn(colNum++, "pseudorange_rate_mps", REAL, true, null)); - tblcols.add(UserCustomColumn.createColumn(colNum++, "pseudorange_rate_1sigma", REAL, true, null)); - - tblcols.add(UserCustomColumn.createColumn(colNum++, "has_ephemeris", INTEGER, true, null)); - tblcols.add(UserCustomColumn.createColumn(colNum++, "has_almanac", INTEGER, true, null)); - tblcols.add(UserCustomColumn.createColumn(colNum++, "azimuth_deg", REAL, true, null)); - tblcols.add(UserCustomColumn.createColumn(colNum++, "elevation_deg", REAL, true, null)); - - tblcols.add(UserCustomColumn.createColumn(colNum++, "data_dump", TEXT, true, null)); - - SimpleAttributesTable table = SimpleAttributesTable.create(tableName, tblcols); - - UserMappingTable mapTbl = UserMappingTable.create(mapTblName); - SatExtRel = rte.addSimpleAttributesRelationship(baseTblName, table, mapTbl); - - return (table); + @Override + public void onBackPressed() { + new AlertDialog.Builder(this) + .setTitle(R.string.quit_torgi) + .setMessage(R.string.quit_torgi_narrative) + .setNegativeButton(R.string.quit_yes, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + if (serviceBound && (torgiService != null)) + torgiService.shutdown(); + MainActivity.this.finish(); + } + }) + .setPositiveButton(R.string.quit_run_in_background, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface arg0, int arg1) { + MainActivity.this.finish(); + } + }).create().show(); } - - private UserTable createClockTable(Contents contents, RelatedTablesExtension rte, SpatialReferenceSystem srs, String tableName, String mapTblName, String baseTblName) { - contents.setTableName(tableName); - contents.setDataType(ContentsDataType.FEATURES); - contents.setIdentifier(tableName); - contents.setDescription(tableName); - contents.setSrs(srs); - - int colNum = 1; - List tblcols = new LinkedList<>(); -// tblcols.add(UserCustomColumn.createPrimaryKeyColumn(colNum++, ID_COLUMN)); - // Dublin Core metadata descriptor profile -// tblcols.add(UserCustomColumn.createColumn(colNum++, DublinCoreType.DATE.getName(), DATETIME, false, null)); -// tblcols.add(FeatureColumn.createColumn(colNum++, DublinCoreType.TITLE.getName(), TEXT, false, null)); -// tblcols.add(FeatureColumn.createColumn(colNum++, DublinCoreType.SOURCE.getName(), TEXT, false, null)); -// tblcols.add(FeatureColumn.createColumn(colNum++, DublinCoreType.DESCRIPTION.getName(), TEXT, false, null)); - - // android GNSS measurements - tblcols.add(UserCustomColumn.createColumn(colNum++, "time_nanos", REAL, true, null)); - tblcols.add(UserCustomColumn.createColumn(colNum++, "time_uncertainty_nanos", REAL, true, null)); - tblcols.add(UserCustomColumn.createColumn(colNum++, "has_time_uncertainty_nanos", INTEGER, true, null)); - tblcols.add(UserCustomColumn.createColumn(colNum++, "bias_nanos", REAL, true, null)); - tblcols.add(UserCustomColumn.createColumn(colNum++, "has_bias_nanos", INTEGER, true, null)); - tblcols.add(UserCustomColumn.createColumn(colNum++, "bias_uncertainty_nanos", REAL, true, null)); - tblcols.add(UserCustomColumn.createColumn(colNum++, "has_bias_uncertainty_nanos", INTEGER, true, null)); - tblcols.add(UserCustomColumn.createColumn(colNum++, "full_bias_nanos", INTEGER, true, null)); - tblcols.add(UserCustomColumn.createColumn(colNum++, "has_full_bias_nanos", INTEGER, true, null)); - - tblcols.add(UserCustomColumn.createColumn(colNum++, "drift_nanos_per_sec", REAL, true, null)); - tblcols.add(UserCustomColumn.createColumn(colNum++, "has_drift_nanos_per_sec", INTEGER, true, null)); - tblcols.add(UserCustomColumn.createColumn(colNum++, "drift_uncertainty_nps", REAL, true, null)); - tblcols.add(UserCustomColumn.createColumn(colNum++, "has_drift_uncertainty_nps", INTEGER, true, null)); - tblcols.add(UserCustomColumn.createColumn(colNum++, "hw_clock_discontinuity_count", INTEGER, true, null)); - tblcols.add(UserCustomColumn.createColumn(colNum++, "leap_second", INTEGER, true, null)); - tblcols.add(UserCustomColumn.createColumn(colNum++, "has_leap_second", INTEGER, true, null)); - - tblcols.add(UserCustomColumn.createColumn(colNum++, "data_dump", TEXT, true, null)); - - SimpleAttributesTable table = SimpleAttributesTable.create(tableName, tblcols); - - UserMappingTable mapTbl = UserMappingTable.create(mapTblName); - ClkExtRel = rte.addSimpleAttributesRelationship(baseTblName, table, mapTbl); - - return (table); + @Override + public void onMapReady(GoogleMap googleMap) { + mMap = googleMap; + mMap.setMapType(GoogleMap.MAP_TYPE_HYBRID); + mMap.setBuildingsEnabled(false); + if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) + mMap.setMyLocationEnabled(false); + } else + mMap.setMyLocationEnabled(false); + LatLng lastLatLng = getLastLatLng(); + if (lastLatLng != null) + mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(lastLatLng,19)); + else + mMap.moveCamera(CameraUpdateFactory.newLatLng(CENTER_US)); + mapReady = true; + mMap.getUiSettings().setMapToolbarEnabled(false); } - private UserTable createMotionTable(Contents contents, RelatedTablesExtension rte, SpatialReferenceSystem srs, String tableName, String mapTblName, String baseTblName) { - contents.setTableName(tableName); - contents.setDataType(ContentsDataType.FEATURES); - contents.setIdentifier(tableName); - contents.setDescription(tableName); - contents.setSrs(srs); - - int colNum = 1; - List tblcols = new LinkedList<>(); -// tblcols.add(UserCustomColumn.createPrimaryKeyColumn(colNum++, ID_COLUMN)); - // Dublin Core metadata descriptor profile -// tblcols.add(UserCustomColumn.createColumn(colNum++, DublinCoreType.DATE.getName(), DATETIME, false, null)); -// tblcols.add(FeatureColumn.createColumn(colNum++, DublinCoreType.TITLE.getName(), TEXT, false, null)); -// tblcols.add(FeatureColumn.createColumn(colNum++, DublinCoreType.SOURCE.getName(), TEXT, false, null)); -// tblcols.add(FeatureColumn.createColumn(colNum++, DublinCoreType.DESCRIPTION.getName(), TEXT, false, null)); - - // android intertial sensor measurements - tblcols.add(UserCustomColumn.createColumn(colNum++, "accel_x", REAL, true, null)); - tblcols.add(UserCustomColumn.createColumn(colNum++, "accel_y", REAL, true, null)); - tblcols.add(UserCustomColumn.createColumn(colNum++, "accel_z", REAL, true, null)); - - tblcols.add(UserCustomColumn.createColumn(colNum++, "linear_accel_x", REAL, true, null)); - tblcols.add(UserCustomColumn.createColumn(colNum++, "linear_accel_y", REAL, true, null)); - tblcols.add(UserCustomColumn.createColumn(colNum++, "linear_accel_z", REAL, true, null)); - - tblcols.add(UserCustomColumn.createColumn(colNum++, "mag_x", REAL, true, null)); - tblcols.add(UserCustomColumn.createColumn(colNum++, "mag_y", REAL, true, null)); - tblcols.add(UserCustomColumn.createColumn(colNum++, "mag_z", REAL, true, null)); - - tblcols.add(UserCustomColumn.createColumn(colNum++, "gyro_x", REAL, true, null)); - tblcols.add(UserCustomColumn.createColumn(colNum++, "gyro_y", REAL, true, null)); - tblcols.add(UserCustomColumn.createColumn(colNum++, "gyro_z", REAL, true, null)); - - tblcols.add(UserCustomColumn.createColumn(colNum++, "gravity_x", REAL, true, null)); - tblcols.add(UserCustomColumn.createColumn(colNum++, "gravity_y", REAL, true, null)); - tblcols.add(UserCustomColumn.createColumn(colNum++, "gravity_z", REAL, true, null)); - - tblcols.add(UserCustomColumn.createColumn(colNum++, "rot_vec_x", REAL, true, null)); - tblcols.add(UserCustomColumn.createColumn(colNum++, "rot_vec_y", REAL, true, null)); - tblcols.add(UserCustomColumn.createColumn(colNum++, "rot_vec_z", REAL, true, null)); - tblcols.add(UserCustomColumn.createColumn(colNum++, "rot_vec_cos", REAL, true, null)); - tblcols.add(UserCustomColumn.createColumn(colNum++, "rot_vec_hdg_acc", REAL, true, null)); - - tblcols.add(UserCustomColumn.createColumn(colNum++, "baro", REAL, true, null)); - tblcols.add(UserCustomColumn.createColumn(colNum++, "humidity", REAL, true, null)); - tblcols.add(UserCustomColumn.createColumn(colNum++, "temp", REAL, true, null)); - - tblcols.add(UserCustomColumn.createColumn(colNum++, "lux", REAL, true, null)); - tblcols.add(UserCustomColumn.createColumn(colNum++, "prox", REAL, true, null)); - - tblcols.add(UserCustomColumn.createColumn(colNum++, "stationary", INTEGER, true, null)); - tblcols.add(UserCustomColumn.createColumn(colNum++, "motion", INTEGER, true, null)); - - tblcols.add(UserCustomColumn.createColumn(colNum++, "data_dump", TEXT, true, null)); - - SimpleAttributesTable table = SimpleAttributesTable.create(tableName, tblcols); - - UserMappingTable mapTbl = UserMappingTable.create(mapTblName); - ClkExtRel = rte.addSimpleAttributesRelationship(baseTblName, table, mapTbl); + @Override + public void onSatStatusUpdated(final ArrayList statuses) { + runOnUiThread(new Runnable() { + @Override + public void run() { + if ((statuses == null) || statuses.isEmpty()) + stat_tv.setVisibility(View.GONE); + else { + StringBuffer out = new StringBuffer(); + + boolean first = true; + for (SatStatus status:statuses) { + if (status.isUsedInFix()) { + if (first) + first = false; + else + out.append('\n'); + out.append(status.constellation + status.svid); + } + } - return (table); + stat_tv.setText(out.toString()); + stat_tv.setVisibility(View.VISIBLE); + } + } + }); } @Override - public boolean onCreateOptionsMenu(Menu menu) { - // Inflate the menu; this adds items to the action bar if it is present. - getMenuInflater().inflate(R.menu.menu_main, menu); - return true; + public void onGnssMeasurementReceived(final Collection measurements) { + runOnUiThread(new Runnable() { + @Override + public void run() { + if ((measurements == null) || measurements.isEmpty()) + meas_tv.setVisibility(View.GONE); + else { + StringBuffer out = new StringBuffer(); + boolean first = true; + for (GnssMeasurement measurement:measurements) { + if (first) + first = false; + else + out.append('\n'); + out.append(measurement.toString()); + } + meas_tv.setText(out.toString()); + meas_tv.setVisibility(View.VISIBLE); + } + } + }); } @Override - public boolean onOptionsItemSelected(MenuItem item) { - // Handle action bar item clicks here. The action bar will - // automatically handle clicks on the Home/Up button, so long - // as you specify a parent activity in AndroidManifest.xml. - int id = item.getItemId(); - - //noinspection SimplifiableIfStatement - if (id == R.id.action_settings) { - return true; - } + public void onLocationChanged(final Location loc) { + runOnUiThread(new Runnable() { + @Override + public void run() { + StringBuffer out = new StringBuffer(); + + out.append("Lat: "+ String.valueOf(loc.getLatitude()) + "\n"); + out.append("Lon: " + String.valueOf(loc.getLongitude()) + "\n"); + out.append("Alt: " + String.valueOf(loc.getAltitude()) + "\n"); + out.append("Provider: " + String.valueOf(loc.getProvider()) + "\n"); + out.append("Time: " + fmtTime.format(loc.getTime()) + "\n"); + int sats = loc.getExtras().getInt("satellites"); + if (sats > 0) + out.append("FixSatCount: " + String.valueOf(sats) + "\n"); + if (loc.hasAccuracy()) + out.append("RadialAccuracy: " + String.valueOf(loc.getAccuracy()) + "\n"); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + if (loc.hasVerticalAccuracy()) + out.append("VerticalAccuracy: " + String.valueOf(loc.getVerticalAccuracyMeters()) + "\n"); + } - return super.onOptionsItemSelected(item); + String txt = out.toString() + "\n" + (serviceBound?torgiService.getGpkgFilename():"") + " SDK v" + Build.VERSION.SDK_INT; + cur_tv.setText(txt); + drawMarker(new LatLng(loc.getLatitude(), loc.getLongitude()),fmtTime.format(loc.getTime())+", ±"+(loc.hasAccuracy()?fmtAccuracy.format(loc.getAccuracy()):"")+"m"); + } + }); + } + + @Override + public void onProviderChanged(final String provider, final boolean enabled) { + runOnUiThread(new Runnable() { + @Override + public void run() { + cur_tv.setText(provider+": "+(enabled?"Enabled":"Disabled")); + } + }); } } \ No newline at end of file diff --git a/torgi/src/main/java/org/sofwerx/torgi/SatStatus.java b/torgi/src/main/java/org/sofwerx/torgi/SatStatus.java new file mode 100644 index 0000000..701e5da --- /dev/null +++ b/torgi/src/main/java/org/sofwerx/torgi/SatStatus.java @@ -0,0 +1,56 @@ +package org.sofwerx.torgi; + +public class SatStatus { + String constellation; + int svid; + + double cn0; + boolean in_fix; + + boolean has_almanac; + boolean has_ephemeris; + boolean has_carrier_freq; + + double elevation_deg; + double azimuth_deg; + double carrier_freq_hz; + + private boolean usedInFix = false; + + public boolean equals(SatStatus status) { + if (status == null) + return false; + if (svid == status.svid) { + if ((constellation == null) && (status.constellation == null)) + return true; + else { + if (constellation == null) + return false; + return constellation.equalsIgnoreCase(status.constellation); + } + } + return false; + } + + public void update(SatStatus status) { + if (status != null) { + cn0 = status.cn0; + in_fix = status.in_fix; + has_almanac = status.has_almanac; + has_ephemeris = status.has_ephemeris; + has_carrier_freq = status.has_carrier_freq; + elevation_deg = status.elevation_deg; + azimuth_deg = status.azimuth_deg; + carrier_freq_hz = status.carrier_freq_hz; + usedInFix = status.usedInFix; + } + } + + public boolean isUsedInFix() { + return usedInFix; + } + + public void setUsedInFix(boolean usedInFix) { + this.usedInFix = usedInFix; + } +} diff --git a/torgi/src/main/java/org/sofwerx/torgi/TorgiService.java b/torgi/src/main/java/org/sofwerx/torgi/TorgiService.java new file mode 100644 index 0000000..98dabbf --- /dev/null +++ b/torgi/src/main/java/org/sofwerx/torgi/TorgiService.java @@ -0,0 +1,918 @@ +package org.sofwerx.torgi; + +import android.Manifest; +import android.annotation.TargetApi; +import android.app.Notification; +import android.app.NotificationChannel; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.app.Service; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.database.sqlite.SQLiteCantOpenDatabaseException; +import android.location.Criteria; +import android.location.GnssClock; +import android.location.GnssMeasurement; +import android.location.GnssMeasurementsEvent; +import android.location.GnssStatus; +import android.location.Location; +import android.location.LocationListener; +import android.location.LocationManager; +import android.os.Binder; +import android.os.Build; +import android.os.Bundle; +import android.os.Environment; +import android.os.IBinder; +import android.os.PowerManager; +import android.support.annotation.Nullable; +import android.support.annotation.RequiresApi; +import android.support.v4.content.ContextCompat; +import android.util.Log; +import android.widget.TextView; + +import java.sql.SQLException; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Collection; +import java.util.Date; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Locale; + +import mil.nga.geopackage.BoundingBox; +import mil.nga.geopackage.GeoPackage; +import mil.nga.geopackage.GeoPackageException; +import mil.nga.geopackage.GeoPackageManager; +import mil.nga.geopackage.core.contents.Contents; +import mil.nga.geopackage.core.contents.ContentsDao; +import mil.nga.geopackage.core.contents.ContentsDataType; +import mil.nga.geopackage.core.srs.SpatialReferenceSystem; +import mil.nga.geopackage.core.srs.SpatialReferenceSystemDao; +import mil.nga.geopackage.extension.related.ExtendedRelation; +import mil.nga.geopackage.extension.related.RelatedTablesExtension; +import mil.nga.geopackage.extension.related.UserMappingDao; +import mil.nga.geopackage.extension.related.UserMappingRow; +import mil.nga.geopackage.extension.related.UserMappingTable; +import mil.nga.geopackage.extension.related.simple.SimpleAttributesDao; +import mil.nga.geopackage.extension.related.simple.SimpleAttributesRow; +import mil.nga.geopackage.extension.related.simple.SimpleAttributesTable; +import mil.nga.geopackage.factory.GeoPackageFactory; +import mil.nga.geopackage.features.columns.GeometryColumns; +import mil.nga.geopackage.features.columns.GeometryColumnsDao; +import mil.nga.geopackage.features.user.FeatureColumn; +import mil.nga.geopackage.features.user.FeatureDao; +import mil.nga.geopackage.features.user.FeatureRow; +import mil.nga.geopackage.features.user.FeatureTable; +import mil.nga.geopackage.geom.GeoPackageGeometryData; +import mil.nga.geopackage.user.UserTable; +import mil.nga.geopackage.user.custom.UserCustomColumn; +import mil.nga.sf.GeometryType; +import mil.nga.sf.Point; +import mil.nga.sf.proj.ProjectionConstants; + +import static java.time.Instant.now; +import static java.time.Instant.parse; +import static mil.nga.geopackage.db.GeoPackageDataType.DATETIME; +import static mil.nga.geopackage.db.GeoPackageDataType.INTEGER; +import static mil.nga.geopackage.db.GeoPackageDataType.REAL; +import static mil.nga.geopackage.db.GeoPackageDataType.TEXT; + +/** + * Torgi service handles getting information from the GPS receiver (and eventually accepting data + * from other sensors as well) and then storing that data in the GeoPackage as well as making + * this info available to any listenng UI element. + * + * TODO this is currently on the main thread - move to a separate thread to support more responsive UI + */ +public class TorgiService extends Service { + private final static String TAG = "TORGISvc"; + private final static int TORGI_NOTIFICATION_ID = 1; + private final static String NOTIFICATION_CHANNEL = "torgi_report"; + public final static String ACTION_STOP = "STOP"; + private final static SimpleDateFormat fmtFilenameFriendlyTime = new SimpleDateFormat("YYYYMMdd HHmmss"); + + HashMap SatType = new HashMap() { + { + put(0, "Unknown"); + put(1, "GPS"); + put(2, "SBAS"); + put(3, "Glonass"); + put(4, "QZSS"); + put(5, "Beidou"); + put(6, "Galileo"); + + } + }; + + public void setListener(GnssMeasurementListener listener) { + this.listener = listener; + } + + private LocationManager locMgr = null; + private GnssMeasurementListener listener = null; + + private final IBinder mBinder = new TorgiBinder(); + final static long WGS84_SRS = 4326; + + private static final String ID_COLUMN = "id"; + private static final String GEOMETRY_COLUMN = "geom"; + + String GpkgFilename = "TORGI-GNSS"; + String GpkgFolder = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getAbsolutePath(); + + private static final String PtsTableName = "gps_observation_points"; + private static final String satTblName = "sat_data"; + private static final String clkTblName = "rcvr_clock"; + private static final String motionTblName = "motion"; + private static final String satmapTblName = PtsTableName + "_" + satTblName; + private static final String clkmapTblName = satTblName + "_" + clkTblName; + private static final String motionmapTblName = PtsTableName + "_" + motionTblName; + + private ArrayList sats = new ArrayList<>(); + private GeoPackage gpkg = null; + + HashMap SatStatus = new HashMap<>(); + HashMap SatInfo = new HashMap<>(); + HashMap SatRowsToMap = new HashMap<>(); + + GeoPackage GPSgpkg = null; + RelatedTablesExtension RTE = null; + ExtendedRelation SatExtRel = null; + ExtendedRelation ClkExtRel = null; + UserTable PtsTable = null; + UserTable SatTable = null; + UserTable ClkTable = null; + UserTable MotionTable = null; + + public String getGpkgFilename() { + return GpkgFilename; + } + + @RequiresApi(26) + private final GnssStatus.Callback StatusListener = new GnssStatus.Callback() { + public void onSatelliteStatusChanged(final GnssStatus status) { + int numSats = status.getSatelliteCount(); + + for (int i = 0; i < numSats; ++i) { + SatStatus thisSat = new SatStatus(); + thisSat.constellation = SatType.get(status.getConstellationType(i)); + thisSat.svid = status.getSvid(i); + thisSat.cn0 = status.getCn0DbHz(i); + + thisSat.has_almanac = status.hasAlmanacData(i); + thisSat.has_ephemeris = status.hasEphemerisData(i); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + thisSat.has_carrier_freq = status.hasCarrierFrequencyHz(i); + } else { + thisSat.has_carrier_freq = false; + } + + thisSat.azimuth_deg = status.getAzimuthDegrees(i); + thisSat.elevation_deg = status.getElevationDegrees(i); + thisSat.cn0 = status.getCn0DbHz(i); + + String hashkey = thisSat.constellation + status.getSvid(i); + SatStatus.put(hashkey, thisSat); + + thisSat.setUsedInFix(status.usedInFix(i)); + update(thisSat); + } + + if (listener != null) { + //TODO this is where later dynamic calculations dealing with this data should be called from + listener.onSatStatusUpdated(sats); + } + } + }; + + /** + * Updates the current list of satellite statuses; currently a bit redundant but exists to + * eventually help provide less jitter in GUI options like a ListView and to help in + * dynamic computations rather than always pulling from the GeoPackage + * @param sat + */ + private void update(SatStatus sat) { + if (sat != null) { + boolean found = false; + for (SatStatus status:sats) { + if (status.equals(sat)) { + found = true; + status.update(sat); + } + } + if (!found) + sats.add(sat); + } + } + + /** + * Starts the location listener and starts saving GNSS data to the GeoPackage + */ + public void startGnssRecorder() { + boolean permissionsPassed = ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED; + permissionsPassed = permissionsPassed && ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED; + + if (permissionsPassed) { + if (locMgr == null) { + locMgr = getSystemService(LocationManager.class); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + locMgr.registerGnssMeasurementsCallback(MeasurementListener); + locMgr.registerGnssStatusCallback(StatusListener); + } + + locMgr.requestLocationUpdates(LocationManager.GPS_PROVIDER, 1000, 0, locListener); + setForeground(); + } + + if (GPSgpkg == null) { + try { + GPSgpkg = setupGpkgDB(this, GpkgFolder, GpkgFilename); + } catch (SQLException e) { + e.printStackTrace(); + } + } + + List tbls = GPSgpkg.getTables(); + tbls.add(GPSgpkg.getApplicationId()); + } + } + + @RequiresApi(26) + private final GnssMeasurementsEvent.Callback MeasurementListener = new GnssMeasurementsEvent.Callback() { + public void onGnssMeasurementsReceived(GnssMeasurementsEvent event) { + Collection gm = event.getMeasurements(); + + GnssClock clk = event.getClock(); + + SimpleAttributesDao clkDao = RTE.getSimpleAttributesDao(clkTblName); + SimpleAttributesRow clkrow = clkDao.newRow(); + + clkrow.setValue("time_nanos", (double) clk.getTimeNanos()); + if (clk.hasTimeUncertaintyNanos()) { + clkrow.setValue("time_uncertainty_nanos", (double) clk.getTimeUncertaintyNanos()); + clkrow.setValue("has_time_uncertainty_nanos", 1); + } else { + clkrow.setValue("time_uncertainty_nanos", (double) 0.0); + clkrow.setValue("has_time_uncertainty_nanos", 0); + } + + if (clk.hasBiasNanos()) { + clkrow.setValue("bias_nanos", (double) clk.getBiasNanos()); + clkrow.setValue("has_bias_nanos", 1); + } else { + clkrow.setValue("bias_nanos", (double) 0.0); + clkrow.setValue("has_bias_nanos", 0); + } + if (clk.hasFullBiasNanos()) { + clkrow.setValue("full_bias_nanos", clk.getFullBiasNanos()); + clkrow.setValue("has_full_bias_nanos", 1); + } else { + clkrow.setValue("full_bias_nanos", 0); + clkrow.setValue("has_full_bias_nanos", 0); + } + if (clk.hasBiasUncertaintyNanos()) { + clkrow.setValue("bias_uncertainty_nanos", (double) clk.getBiasUncertaintyNanos()); + clkrow.setValue("has_bias_uncertainty_nanos", 1); + } else { + clkrow.setValue("bias_uncertainty_nanos", (double) 0.0); + clkrow.setValue("has_bias_uncertainty_nanos", 0); + } + if (clk.hasDriftNanosPerSecond()) { + clkrow.setValue("drift_nanos_per_sec", (double) clk.getDriftNanosPerSecond()); + clkrow.setValue("has_drift_nanos_per_sec", 1); + } else { + clkrow.setValue("drift_nanos_per_sec", (double) 0.0); + clkrow.setValue("has_drift_nanos_per_sec", 0); + } + if (clk.hasDriftUncertaintyNanosPerSecond()) { + clkrow.setValue("drift_uncertainty_nps", (double) clk.getDriftUncertaintyNanosPerSecond()); + clkrow.setValue("has_drift_uncertainty_nps", 1); + } else { + clkrow.setValue("drift_uncertainty_nps", (double) 0.0); + clkrow.setValue("has_drift_uncertainty_nps", 0); + } + if (clk.hasLeapSecond()) { + clkrow.setValue("leap_second", clk.getLeapSecond()); + clkrow.setValue("has_leap_second", 1); + } else { + clkrow.setValue("leap_second", 0); + clkrow.setValue("has_leap_second", 0); + } + clkrow.setValue("hw_clock_discontinuity_count", clk.getHardwareClockDiscontinuityCount()); + + clkrow.setValue("data_dump", clk.toString()); + clkDao.insert(clkrow); + + UserMappingDao clkMapDAO = RTE.getMappingDao(ClkExtRel); + + SatRowsToMap.clear(); + + for(final GnssMeasurement g : gm) { + + String con = SatType.get(g.getConstellationType()); + String hashkey = con + g.getSvid(); + + HashMap thisSat = new HashMap(); + + SimpleAttributesDao satDao = RTE.getSimpleAttributesDao(satTblName); + SimpleAttributesRow satrow = satDao.newRow(); + + satrow.setValue("svid", g.getSvid()); + satrow.setValue("constellation", con); + satrow.setValue("cn0", (double) g.getCn0DbHz()); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + if (g.hasAutomaticGainControlLevelDb()) { + satrow.setValue("agc", (double) g.getAutomaticGainControlLevelDb()); + satrow.setValue("has_agc", 1); + } else { + satrow.setValue("agc", 0); + satrow.setValue("has_agc", 0); + } + } else { + satrow.setValue("agc", (double) 0.0); + satrow.setValue("has_agc", 0); + } + satrow.setValue("sync_state_flags", g.getState()); + satrow.setValue("sync_state_txt", " "); + satrow.setValue("sat_time_nanos", (double) g.getReceivedSvTimeNanos()); + satrow.setValue("sat_time_1sigma_nanos",(double) g.getReceivedSvTimeUncertaintyNanos()); + satrow.setValue("rcvr_time_offset_nanos", (double) g.getTimeOffsetNanos()); + satrow.setValue("multipath", g.getMultipathIndicator()); + if (g.hasCarrierFrequencyHz()) { + satrow.setValue("carrier_freq_hz", (double) g.getCarrierFrequencyHz()); + satrow.setValue("has_carrier_freq", 1); + } else { + satrow.setValue("carrier_freq_hz", (double) 0.0); + satrow.setValue("has_carrier_freq", 0); + } + satrow.setValue("accum_delta_range", (double) g.getAccumulatedDeltaRangeMeters()); + satrow.setValue("accum_delta_range_1sigma", (double) g.getAccumulatedDeltaRangeUncertaintyMeters()); + satrow.setValue("accum_delta_range_state_flags", g.getAccumulatedDeltaRangeState()); + satrow.setValue("accum_delta_range_state_txt", " "); + satrow.setValue("pseudorange_rate_mps", (double) g.getPseudorangeRateMetersPerSecond()); + satrow.setValue("pseudorange_rate_1sigma", (double) g.getPseudorangeRateUncertaintyMetersPerSecond()); + + if (SatStatus.containsKey(hashkey)) { + satrow.setValue("in_fix", SatStatus.get(hashkey).in_fix ? 0 : 1); + + satrow.setValue("has_almanac", SatStatus.get(hashkey).has_almanac ? 0 : 1); + satrow.setValue("has_ephemeris", SatStatus.get(hashkey).has_ephemeris ? 0 : 1); + satrow.setValue("has_carrier_freq", SatStatus.get(hashkey).has_carrier_freq ? 0 : 1); + + satrow.setValue("elevation_deg", (double) SatStatus.get(hashkey).elevation_deg); + satrow.setValue("azimuth_deg", (double) SatStatus.get(hashkey).azimuth_deg); + } else { + satrow.setValue("in_fix", 0); + + satrow.setValue("has_almanac", 0); + satrow.setValue("has_ephemeris", 0); + satrow.setValue("has_carrier_freq", 0); + + satrow.setValue("elevation_deg", 0.0); + satrow.setValue("azimuth_deg", 0.0); + } + + satrow.setValue("data_dump", g.toString()); + satDao.insert(satrow); + + UserMappingRow clkmaprow = clkMapDAO.newRow(); + clkmaprow.setBaseId(satrow.getId()); + clkmaprow.setRelatedId(clkrow.getId()); + clkMapDAO.create(clkmaprow); + + SatInfo.put(hashkey, g); + SatRowsToMap.put(hashkey, satrow.getId()); + } + if (listener != null) { + //TODO this is where later dynamic calculations for this data should be called from + listener.onGnssMeasurementReceived(gm); + } + } + }; + + LocationListener locListener = new LocationListener() { + public void onProviderEnabled(String provider) { + if (listener != null) + listener.onProviderChanged(provider,true); + } + + public void onProviderDisabled(String provider) { + if (listener != null) + listener.onProviderChanged(provider,false); + } + + public void onStatusChanged(final String provider, int status, Bundle extras) {} + + public void onLocationChanged(final Location loc) { + HashMap maprows = (HashMap)SatRowsToMap.clone(); + SatRowsToMap.clear(); + + HashMap locData = new HashMap() { + { + put("Lat", String.valueOf(loc.getLatitude())); + put("Lon", String.valueOf(loc.getLongitude())); + put("Alt", String.valueOf(loc.getAltitude())); + put("Provider", String.valueOf(loc.getProvider())); + put("Time", String.valueOf(loc.getTime())); + put("FixSatCount", String.valueOf(loc.getExtras().getInt("satellites"))); + put("HasRadialAccuracy", String.valueOf(loc.hasAccuracy())); + put("RadialAccuracy", String.valueOf(loc.getAccuracy())); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + put("HasVerticalAccuracy", String.valueOf(loc.hasVerticalAccuracy())); + put("VerticalAccuracy", String.valueOf(loc.getVerticalAccuracyMeters())); + } + } + }; + + if (listener != null) { + //TODO this is where later dynamic calculations dealing with this data should be called from + listener.onLocationChanged(loc); + } + + if (GPSgpkg != null) { + FeatureDao featDao = GPSgpkg.getFeatureDao(PtsTableName); + FeatureRow frow = featDao.newRow(); + UserMappingDao satMapDAO = RTE.getMappingDao(SatExtRel); + + Point fix = new Point(loc.getLongitude(), loc.getLatitude(), loc.getAltitude()); + + GeoPackageGeometryData geomData = new GeoPackageGeometryData(WGS84_SRS); + geomData.setGeometry(fix); + + frow.setGeometry(geomData); + + frow.setValue("Lat", (double) loc.getLatitude()); + frow.setValue("Lon", (double) loc.getLongitude()); + frow.setValue("Alt", (double) loc.getAltitude()); + frow.setValue("Provider", loc.getProvider()); + frow.setValue("GPSTime", loc.getTime()); + frow.setValue("FixSatCount", loc.getExtras().getInt("satellites")); + if (loc.hasAccuracy()) { + frow.setValue("RadialAccuracy", (double) loc.getAccuracy()); + frow.setValue("HasRadialAccuracy", 1); + } else { + frow.setValue("RadialAccuracy", (double) 0.0); + frow.setValue("HasRadialAccuracy", 0); + } + + if (loc.hasSpeed()) { + frow.setValue("Speed", (double) loc.getAccuracy()); + frow.setValue("HasSpeed", 1); + } else { + frow.setValue("Speed", (double) 0.0); + frow.setValue("HasSpeed", 0); + } + + if (loc.hasBearing()) { + frow.setValue("Bearing", (double) loc.getAccuracy()); + frow.setValue("HasBearing", 1); + } else { + frow.setValue("Bearing", (double) 0.0); + frow.setValue("HasBearing", 0); + } + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + frow.setValue("SysTime", now().toString()); + + if (loc.hasVerticalAccuracy()) { + frow.setValue("VerticalAccuracy", (double) loc.getVerticalAccuracyMeters()); + frow.setValue("HasVerticalAccuracy", 1); + } else { + frow.setValue("VerticalAccuracy", (double) 0.0); + frow.setValue("HasVerticalAccuracy", 0); + } + + if (loc.hasSpeedAccuracy()) { + frow.setValue("SpeedAccuracy", (double) loc.getAccuracy()); + frow.setValue("HasSpeedAccuracy", 1); + } else { + frow.setValue("SpeedAccuracy", (double) 0.0); + frow.setValue("HasSpeedAccuracy", 0); + } + + if (loc.hasBearingAccuracy()) { + frow.setValue("BearingAccuracy", (double) loc.getAccuracy()); + frow.setValue("HasBearingAccuracy", 1); + } else { + frow.setValue("BearingAccuracy", (double) 0.0); + frow.setValue("HasBearingAccuracy", 0); + } + } else { + Date currentTime = Calendar.getInstance().getTime(); + SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ", Locale.US); + frow.setValue("SysTime", df.format(currentTime)); + frow.setValue("HasVerticalAccuracy", 0); + frow.setValue("VerticalAccuracy", (double) 0.0); + } + + frow.setValue("data_dump", loc.toString() + " " + loc.describeContents()); + + featDao.insert(frow); + + for (long id : maprows.values()) { + UserMappingRow satmaprow = satMapDAO.newRow(); + satmaprow.setBaseId(frow.getId()); + satmaprow.setRelatedId(id); + satMapDAO.create(satmaprow); + } + + // update feature table bounding box if necessary + boolean dirty = false; + BoundingBox bb = featDao.getBoundingBox(); + if (loc.getLatitude() < bb.getMinLatitude()) { + bb.setMinLatitude(loc.getLatitude()); + dirty = true; + } + if (loc.getLatitude() > bb.getMaxLatitude()) { + bb.setMaxLatitude(loc.getLatitude()); + dirty = true; + } + + if (loc.getLongitude() < bb.getMinLongitude()) { + bb.setMinLongitude(loc.getLongitude()); + } + if (loc.getLongitude() > bb.getMaxLongitude()) { + bb.setMaxLongitude(loc.getLongitude()); + } + + if (dirty) { + String bbsql = "UPDATE gpkg_contents SET " + + " min_x = " + bb.getMinLongitude() + + ", max_x = " + bb.getMaxLongitude() + + ", min_y = " + bb.getMinLatitude() + + ", max_y = " + bb.getMaxLatitude() + + " WHERE table_name = '" + PtsTableName + "';"; + GPSgpkg.execSQL(bbsql); + } + } + } + }; + + private GeoPackage setupGpkgDB(Context context, String folder, String file) throws GeoPackageException, SQLException { + GpkgFilename = folder + "/" + file + "-" + fmtFilenameFriendlyTime.format(System.currentTimeMillis()) + ".gpkg"; + + GeoPackageManager gpkgMgr = GeoPackageFactory.getManager(context); + if (!gpkgMgr.exists(GpkgFilename)) { + try { + gpkgMgr.create(GpkgFilename); + } catch (Exception e) { + e.printStackTrace(); + return null; + } + + } + gpkg = gpkgMgr.open(GpkgFilename, true); + if (gpkg == null) { + throw new GeoPackageException("Failed to open GeoPackage database " + GpkgFilename); + } + + // create SRS & feature tables + SpatialReferenceSystemDao srsDao = gpkg.getSpatialReferenceSystemDao(); + + SpatialReferenceSystem srs = srsDao.getOrCreateCode(ProjectionConstants.AUTHORITY_EPSG, (long) ProjectionConstants.EPSG_WORLD_GEODETIC_SYSTEM); + + gpkg.createGeometryColumnsTable(); + + PtsTable = createObservationTable(gpkg, srs, PtsTableName, GeometryType.POINT); + String bbsql = "UPDATE gpkg_contents SET min_x = 180.0, max_x = -180.0, min_y = 90.0, max_y = -90.0 WHERE table_name = '" + PtsTableName + "';"; + gpkg.execSQL(bbsql); + + Contents contents = new Contents(); + RTE = new RelatedTablesExtension(gpkg); + + SatTable = createSatelliteTable(contents, RTE, srs, satTblName, satmapTblName, PtsTableName); + ClkTable = createClockTable(contents, RTE, srs, clkTblName, clkmapTblName, satTblName); + + MotionTable = createMotionTable(contents, RTE, srs, motionTblName, motionmapTblName, PtsTableName); + + return gpkg; + } + + + private UserTable createObservationTable(GeoPackage geoPackage, SpatialReferenceSystem srs, String tableName, GeometryType type) throws SQLException { + ContentsDao contentsDao = geoPackage.getContentsDao(); + + Contents contents = new Contents(); + contents.setTableName(tableName); + contents.setDataType(ContentsDataType.FEATURES); + contents.setIdentifier(tableName); + contents.setDescription(tableName); + contents.setSrs(srs); + + int colNum = 0; + List tblcols = new LinkedList<>(); + tblcols.add(FeatureColumn.createPrimaryKeyColumn(colNum++, ID_COLUMN)); + tblcols.add(FeatureColumn.createGeometryColumn(colNum++, GEOMETRY_COLUMN, GeometryType.POINT, false, null)); + tblcols.add(FeatureColumn.createColumn(colNum++, "SysTime", DATETIME, false, null)); + tblcols.add(FeatureColumn.createColumn(colNum++, "Lat", REAL, false, null)); + tblcols.add(FeatureColumn.createColumn(colNum++, "Lon", REAL, false, null)); + tblcols.add(FeatureColumn.createColumn(colNum++, "Alt", REAL, false, null)); + tblcols.add(FeatureColumn.createColumn(colNum++, "Provider", TEXT, false, null)); + tblcols.add(FeatureColumn.createColumn(colNum++, "GPSTime", INTEGER, false, null)); + tblcols.add(FeatureColumn.createColumn(colNum++, "FixSatCount", INTEGER, false, null)); + tblcols.add(FeatureColumn.createColumn(colNum++, "HasRadialAccuracy", INTEGER, false, null)); + tblcols.add(FeatureColumn.createColumn(colNum++, "HasVerticalAccuracy", INTEGER, false, null)); + tblcols.add(FeatureColumn.createColumn(colNum++, "RadialAccuracy", REAL, false, null)); + tblcols.add(FeatureColumn.createColumn(colNum++, "VerticalAccuracy", REAL, false, null)); + + tblcols.add(FeatureColumn.createColumn(colNum++, "ElapsedRealtimeNanos", REAL, false, null)); + + tblcols.add(FeatureColumn.createColumn(colNum++, "HasSpeed", INTEGER, false, null)); + tblcols.add(FeatureColumn.createColumn(colNum++, "HasSpeedAccuracy", INTEGER, false, null)); + tblcols.add(FeatureColumn.createColumn(colNum++, "Speed", REAL, false, null)); + tblcols.add(FeatureColumn.createColumn(colNum++, "SpeedAccuracy", REAL, false, null)); + + tblcols.add(FeatureColumn.createColumn(colNum++, "HasBearing", INTEGER, false, null)); + tblcols.add(FeatureColumn.createColumn(colNum++, "HasBearingAccuracy", INTEGER, false, null)); + tblcols.add(FeatureColumn.createColumn(colNum++, "Bearing", REAL, false, null)); + tblcols.add(FeatureColumn.createColumn(colNum++, "BearingAccuracy", REAL, false, null)); + + tblcols.add(FeatureColumn.createColumn(colNum++, "data_dump", TEXT, false, null)); + + FeatureTable table = new FeatureTable(tableName, tblcols); + geoPackage.createFeatureTable(table); + + contentsDao.create(contents); + + GeometryColumnsDao geometryColumnsDao = geoPackage.getGeometryColumnsDao(); + + GeometryColumns geometryColumns = new GeometryColumns(); + geometryColumns.setContents(contents); + geometryColumns.setColumnName(GEOMETRY_COLUMN); + geometryColumns.setGeometryType(type); + geometryColumns.setSrs(srs); + geometryColumns.setZ((byte) 0); + geometryColumns.setM((byte) 0); + geometryColumnsDao.create(geometryColumns); + + return (table); + } + + /** + * Clean-up TORGI and then stop this service + */ + public void shutdown() { + stopSelf(); + } + + private UserTable createSatelliteTable(Contents contents, RelatedTablesExtension rte, SpatialReferenceSystem srs, String tableName, String mapTblName, String baseTblName) { + contents.setTableName(tableName); + contents.setDataType(ContentsDataType.FEATURES); + contents.setIdentifier(tableName); + contents.setDescription(tableName); + contents.setSrs(srs); + + int colNum = 1; + List tblcols = new LinkedList<>(); +// tblcols.add(UserCustomColumn.createPrimaryKeyColumn(colNum++, ID_COLUMN)); + // Dublin Core metadata descriptor profile +// tblcols.add(UserCustomColumn.createColumn(colNum++, DublinCoreType.DATE.getName(), GeoPackageDataType.DATETIME, false, null)); +// tblcols.add(FeatureColumn.createColumn(colNum++, DublinCoreType.TITLE.getName(), GeoPackageDataType.TEXT, false, null)); +// tblcols.add(FeatureColumn.createColumn(colNum++, DublinCoreType.SOURCE.getName(), GeoPackageDataType.TEXT, false, null)); +// tblcols.add(FeatureColumn.createColumn(colNum++, DublinCoreType.DESCRIPTION.getName(), GeoPackageDataType.TEXT, false, null)); + + // android GNSS measurements + tblcols.add(UserCustomColumn.createColumn(colNum++, "svid", INTEGER, true, null)); + tblcols.add(UserCustomColumn.createColumn(colNum++, "constellation", TEXT, true, null)); + tblcols.add(UserCustomColumn.createColumn(colNum++, "cn0", REAL, true, null)); + tblcols.add(UserCustomColumn.createColumn(colNum++, "agc", REAL, true, null)); + tblcols.add(UserCustomColumn.createColumn(colNum++, "has_agc", INTEGER, true, null)); + tblcols.add(UserCustomColumn.createColumn(colNum++, "in_fix", INTEGER, true, null)); + + tblcols.add(UserCustomColumn.createColumn(colNum++, "sync_state_flags", INTEGER, true, null)); + tblcols.add(UserCustomColumn.createColumn(colNum++, "sync_state_txt", TEXT, true, null)); + tblcols.add(UserCustomColumn.createColumn(colNum++, "sat_time_nanos", REAL, true, null)); + tblcols.add(UserCustomColumn.createColumn(colNum++, "sat_time_1sigma_nanos", REAL, true, null)); + tblcols.add(UserCustomColumn.createColumn(colNum++, "rcvr_time_offset_nanos", REAL, true, null)); + tblcols.add(UserCustomColumn.createColumn(colNum++, "multipath", INTEGER, true, null)); + + tblcols.add(UserCustomColumn.createColumn(colNum++, "has_carrier_freq", INTEGER, true, null)); + tblcols.add(UserCustomColumn.createColumn(colNum++, "carrier_freq_hz", REAL, true, null)); + tblcols.add(UserCustomColumn.createColumn(colNum++, "accum_delta_range", REAL, true, null)); + tblcols.add(UserCustomColumn.createColumn(colNum++, "accum_delta_range_1sigma", REAL, true, null)); + tblcols.add(UserCustomColumn.createColumn(colNum++, "accum_delta_range_state_flags", INTEGER, true, null)); + tblcols.add(UserCustomColumn.createColumn(colNum++, "accum_delta_range_state_txt", TEXT, true, null)); + tblcols.add(UserCustomColumn.createColumn(colNum++, "pseudorange_rate_mps", REAL, true, null)); + tblcols.add(UserCustomColumn.createColumn(colNum++, "pseudorange_rate_1sigma", REAL, true, null)); + + tblcols.add(UserCustomColumn.createColumn(colNum++, "has_ephemeris", INTEGER, true, null)); + tblcols.add(UserCustomColumn.createColumn(colNum++, "has_almanac", INTEGER, true, null)); + tblcols.add(UserCustomColumn.createColumn(colNum++, "azimuth_deg", REAL, true, null)); + tblcols.add(UserCustomColumn.createColumn(colNum++, "elevation_deg", REAL, true, null)); + + tblcols.add(UserCustomColumn.createColumn(colNum++, "data_dump", TEXT, true, null)); + + SimpleAttributesTable table = SimpleAttributesTable.create(tableName, tblcols); + + UserMappingTable mapTbl = UserMappingTable.create(mapTblName); + SatExtRel = rte.addSimpleAttributesRelationship(baseTblName, table, mapTbl); + + return (table); + } + + + private UserTable createClockTable(Contents contents, RelatedTablesExtension rte, SpatialReferenceSystem srs, String tableName, String mapTblName, String baseTblName) { + contents.setTableName(tableName); + contents.setDataType(ContentsDataType.FEATURES); + contents.setIdentifier(tableName); + contents.setDescription(tableName); + contents.setSrs(srs); + + int colNum = 1; + List tblcols = new LinkedList<>(); +// tblcols.add(UserCustomColumn.createPrimaryKeyColumn(colNum++, ID_COLUMN)); + // Dublin Core metadata descriptor profile +// tblcols.add(UserCustomColumn.createColumn(colNum++, DublinCoreType.DATE.getName(), DATETIME, false, null)); +// tblcols.add(FeatureColumn.createColumn(colNum++, DublinCoreType.TITLE.getName(), TEXT, false, null)); +// tblcols.add(FeatureColumn.createColumn(colNum++, DublinCoreType.SOURCE.getName(), TEXT, false, null)); +// tblcols.add(FeatureColumn.createColumn(colNum++, DublinCoreType.DESCRIPTION.getName(), TEXT, false, null)); + + // android GNSS measurements + tblcols.add(UserCustomColumn.createColumn(colNum++, "time_nanos", REAL, true, null)); + tblcols.add(UserCustomColumn.createColumn(colNum++, "time_uncertainty_nanos", REAL, true, null)); + tblcols.add(UserCustomColumn.createColumn(colNum++, "has_time_uncertainty_nanos", INTEGER, true, null)); + tblcols.add(UserCustomColumn.createColumn(colNum++, "bias_nanos", REAL, true, null)); + tblcols.add(UserCustomColumn.createColumn(colNum++, "has_bias_nanos", INTEGER, true, null)); + tblcols.add(UserCustomColumn.createColumn(colNum++, "bias_uncertainty_nanos", REAL, true, null)); + tblcols.add(UserCustomColumn.createColumn(colNum++, "has_bias_uncertainty_nanos", INTEGER, true, null)); + tblcols.add(UserCustomColumn.createColumn(colNum++, "full_bias_nanos", INTEGER, true, null)); + tblcols.add(UserCustomColumn.createColumn(colNum++, "has_full_bias_nanos", INTEGER, true, null)); + + tblcols.add(UserCustomColumn.createColumn(colNum++, "drift_nanos_per_sec", REAL, true, null)); + tblcols.add(UserCustomColumn.createColumn(colNum++, "has_drift_nanos_per_sec", INTEGER, true, null)); + tblcols.add(UserCustomColumn.createColumn(colNum++, "drift_uncertainty_nps", REAL, true, null)); + tblcols.add(UserCustomColumn.createColumn(colNum++, "has_drift_uncertainty_nps", INTEGER, true, null)); + tblcols.add(UserCustomColumn.createColumn(colNum++, "hw_clock_discontinuity_count", INTEGER, true, null)); + tblcols.add(UserCustomColumn.createColumn(colNum++, "leap_second", INTEGER, true, null)); + tblcols.add(UserCustomColumn.createColumn(colNum++, "has_leap_second", INTEGER, true, null)); + + tblcols.add(UserCustomColumn.createColumn(colNum++, "data_dump", TEXT, true, null)); + + SimpleAttributesTable table = SimpleAttributesTable.create(tableName, tblcols); + + UserMappingTable mapTbl = UserMappingTable.create(mapTblName); + ClkExtRel = rte.addSimpleAttributesRelationship(baseTblName, table, mapTbl); + + return (table); + } + + private UserTable createMotionTable(Contents contents, RelatedTablesExtension rte, SpatialReferenceSystem srs, String tableName, String mapTblName, String baseTblName) { + contents.setTableName(tableName); + contents.setDataType(ContentsDataType.FEATURES); + contents.setIdentifier(tableName); + contents.setDescription(tableName); + contents.setSrs(srs); + + int colNum = 1; + List tblcols = new LinkedList<>(); +// tblcols.add(UserCustomColumn.createPrimaryKeyColumn(colNum++, ID_COLUMN)); + // Dublin Core metadata descriptor profile +// tblcols.add(UserCustomColumn.createColumn(colNum++, DublinCoreType.DATE.getName(), DATETIME, false, null)); +// tblcols.add(FeatureColumn.createColumn(colNum++, DublinCoreType.TITLE.getName(), TEXT, false, null)); +// tblcols.add(FeatureColumn.createColumn(colNum++, DublinCoreType.SOURCE.getName(), TEXT, false, null)); +// tblcols.add(FeatureColumn.createColumn(colNum++, DublinCoreType.DESCRIPTION.getName(), TEXT, false, null)); + + // android inertial sensor measurements + tblcols.add(UserCustomColumn.createColumn(colNum++, "accel_x", REAL, true, null)); + tblcols.add(UserCustomColumn.createColumn(colNum++, "accel_y", REAL, true, null)); + tblcols.add(UserCustomColumn.createColumn(colNum++, "accel_z", REAL, true, null)); + + tblcols.add(UserCustomColumn.createColumn(colNum++, "linear_accel_x", REAL, true, null)); + tblcols.add(UserCustomColumn.createColumn(colNum++, "linear_accel_y", REAL, true, null)); + tblcols.add(UserCustomColumn.createColumn(colNum++, "linear_accel_z", REAL, true, null)); + + tblcols.add(UserCustomColumn.createColumn(colNum++, "mag_x", REAL, true, null)); + tblcols.add(UserCustomColumn.createColumn(colNum++, "mag_y", REAL, true, null)); + tblcols.add(UserCustomColumn.createColumn(colNum++, "mag_z", REAL, true, null)); + + tblcols.add(UserCustomColumn.createColumn(colNum++, "gyro_x", REAL, true, null)); + tblcols.add(UserCustomColumn.createColumn(colNum++, "gyro_y", REAL, true, null)); + tblcols.add(UserCustomColumn.createColumn(colNum++, "gyro_z", REAL, true, null)); + + tblcols.add(UserCustomColumn.createColumn(colNum++, "gravity_x", REAL, true, null)); + tblcols.add(UserCustomColumn.createColumn(colNum++, "gravity_y", REAL, true, null)); + tblcols.add(UserCustomColumn.createColumn(colNum++, "gravity_z", REAL, true, null)); + + tblcols.add(UserCustomColumn.createColumn(colNum++, "rot_vec_x", REAL, true, null)); + tblcols.add(UserCustomColumn.createColumn(colNum++, "rot_vec_y", REAL, true, null)); + tblcols.add(UserCustomColumn.createColumn(colNum++, "rot_vec_z", REAL, true, null)); + tblcols.add(UserCustomColumn.createColumn(colNum++, "rot_vec_cos", REAL, true, null)); + tblcols.add(UserCustomColumn.createColumn(colNum++, "rot_vec_hdg_acc", REAL, true, null)); + + tblcols.add(UserCustomColumn.createColumn(colNum++, "baro", REAL, true, null)); + tblcols.add(UserCustomColumn.createColumn(colNum++, "humidity", REAL, true, null)); + tblcols.add(UserCustomColumn.createColumn(colNum++, "temp", REAL, true, null)); + + tblcols.add(UserCustomColumn.createColumn(colNum++, "lux", REAL, true, null)); + tblcols.add(UserCustomColumn.createColumn(colNum++, "prox", REAL, true, null)); + + tblcols.add(UserCustomColumn.createColumn(colNum++, "stationary", INTEGER, true, null)); + tblcols.add(UserCustomColumn.createColumn(colNum++, "motion", INTEGER, true, null)); + + tblcols.add(UserCustomColumn.createColumn(colNum++, "data_dump", TEXT, true, null)); + + SimpleAttributesTable table = SimpleAttributesTable.create(tableName, tblcols); + + UserMappingTable mapTbl = UserMappingTable.create(mapTblName); + ClkExtRel = rte.addSimpleAttributesRelationship(baseTblName, table, mapTbl); + + return (table); + } + + @Nullable + @Override + public IBinder onBind(Intent intent) { + startGnssRecorder(); + return mBinder; + } + + @Override + public void onCreate() { + super.onCreate(); + createNotificationChannel(); + } + + @Override + public void onDestroy() { + if (locMgr != null) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + locMgr.unregisterGnssMeasurementsCallback(MeasurementListener); + locMgr.unregisterGnssStatusCallback(StatusListener); + } + if (locListener != null) + locMgr.removeUpdates(locListener); + } + if (GPSgpkg != null) + GPSgpkg.close(); + super.onDestroy(); + } + + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + synchronized (this) { + if (intent != null) { + String action = intent.getAction(); + if (ACTION_STOP.equalsIgnoreCase(action)) { + Log.d(TAG,"Shutting down GNSS recorder"); + stopSelf(); + return START_NOT_STICKY; + } + } + return START_STICKY; + } + } + + public class TorgiBinder extends Binder { + public TorgiService getService() { + return TorgiService.this; + } + } + + /** + * Running TORGI as a foreground service allows TORGI to stay active on API level 26+ devices. Depending + * on desired collection rates, could also consider migrating to a JobScheduler + */ + private void setForeground() { + PendingIntent pendingIntent = null; + try { + Intent notificationIntent = new Intent(this, Class.forName("org.sofwerx.torgi.MainActivity")); + pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0); + } catch (ClassNotFoundException ignore) { + } + + Notification.Builder builder; + builder = new Notification.Builder(this); + builder.setContentIntent(pendingIntent); + builder.setSmallIcon(R.drawable.ic_notification_torgi); + builder.setContentTitle(getResources().getString(R.string.app_name)); + builder.setTicker(getResources().getString(R.string.notification)); + builder.setContentText(getResources().getString(R.string.notification)); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) + builder.setChannelId(NOTIFICATION_CHANNEL); + + Intent intentStop = new Intent(this,TorgiService.class); + intentStop.setAction(ACTION_STOP); + PendingIntent pIntentShutdown = PendingIntent.getService(this,0,intentStop,PendingIntent.FLAG_UPDATE_CURRENT); + builder.addAction(android.R.drawable.ic_lock_power_off, "Stop TORGI", pIntentShutdown); + + startForeground(TORGI_NOTIFICATION_ID, builder.build()); + } + + private void createNotificationChannel() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + CharSequence name = "TORGI"; + String description = "GNSS Recording data"; + int importance = NotificationManager.IMPORTANCE_DEFAULT; + NotificationChannel channel = new NotificationChannel(NOTIFICATION_CHANNEL, name, importance); + channel.setDescription(description); + NotificationManager notificationManager = getSystemService(NotificationManager.class); + notificationManager.createNotificationChannel(channel); + } + } +} diff --git a/torgi/src/main/res/drawable-hdpi/ic_notification_torgi.png b/torgi/src/main/res/drawable-hdpi/ic_notification_torgi.png new file mode 100644 index 0000000000000000000000000000000000000000..324af6cc015c484b7d7acfdb840eeca541ea04c8 GIT binary patch literal 737 zcmV<70v`Q|P)!4C<1wRI(6$hOJEuu~x{2E+z&{ajj#;uEv zN;@c6P*5ik{Aj6KnkpE3{vY}~3?Vr%y-PFP13#{Jyl?Nh=i~ppsjCYuw9snY9LI@> zgxDmS?S8EtiisgHDKa8ntrs_xdA?4Zbyn=USYHiiI>afl)lCU$8WiJV8i!`YWwBSZ z1(H6C49@%zk3^rS7e~Yg@mu^6zqsaE`%)YfEkQVCk(m(lT(=XoF>zIliUZ;p_s_+G z_#&=|?jSm8r+6iPimbhav?bj7B64=WO4vMu78#tG!?{$c^yyiXxJdWiq{d?ZEeUKKLiDDoozX&p)ZYW*u~-rh zL}&4faDId}))eO}k^^Sbr1Q^AXGTh_OH)%0PNhn$86=XfO^KdL3OU0#m?1)$;!Ys~ zojA2b`udC33{ykzNV^GbMK^i-w~~%aaChQoA;r- z&rI|@)(sSOUl&brT704gBYw1fJF{tlj){7#?Zv5O#Iu|ICX$=|RO&Cao`|&EoEj})eiqLX44cYYOICdWfqklj9Mi<_J1ASj5dD0HxsQ2H5k5ELmEFIYjH^usvmq+RUjpo8e3Zr!VRFL*)e z3MtJ_X*8px<|(xQ55-@LlMD$EZAojjV<1f3QjQ4y4TL) zr&vRXRXpNHUa*e|^tiTT9rq|>49h4Wk8NDz7Blc%ij3nK-&nL;qBb4j4q2BS?4gDo zG_<48dd|${QiXyvi<>V#Y4Jd9s_X1o&#&MeLx~2jC{{Ggg07iCZ{Q8{@gjWAp^0OZ zk`yQ^il#t5cHP5*ij-UJ;yk4SXXZyFDPUSMh*xY`_dM;Su9^5s(I-aI&fx%8CN0*z zP&;TSU8C5w1CXT1q;h^}hW^JcH}R!ny)N&8X~(Q`exbDF)bk~rD?JZ-<9*j6S*7Yj z!)&VORZMFKH})>m77CQ}s?stXOOfB!;@03xN>R}L1M8gQ7jt(shUO*HXaE2J07*qo IM6N<$f~zCILjV8( literal 0 HcmV?d00001 diff --git a/torgi/src/main/res/drawable-xhdpi/ic_notification_torgi.png b/torgi/src/main/res/drawable-xhdpi/ic_notification_torgi.png new file mode 100644 index 0000000000000000000000000000000000000000..53f9cfec6495d17ea364209e1791959cb357cfe6 GIT binary patch literal 1120 zcmV-m1fTnfP)6A*b2pVX+KdWC|f8L z>Lvz=St3si6$$^714$xZTo+|xrAX~c3k~8c{EHh>R|=aFH#Bh?I7|OA)=r zIB`IH7T?54F+udfx%kd#+A%gl)g?h(%>l#wI# zBzG?_c7HlrwuQeDr^IudsS+DSW`s^mBN8qAc9C`QSSRsr@rC++L<|=zL^YClAy!*b zM{vKMG`UaQ??g-+9!=_|W1%)KB%u9G{LeIfPpox|1QDG=6s#aT69Z#0m?hCc$I+zP zD3!U6>oVyJ_tCXgsm=U0;L;qAb5D!H3^pV=Dgz+pFQ3`w0K4BtolpMj+8la8B~9zC zTXiBAO6V&s$jBtPL=20D1G6ZZH8h_fPVDO7dW*ys@s2Y45GRuU@eJ!LAGvn}&oUV< zjfMkxl)HLD97G}|F5~n<#*Gk>*xXT0&~^l*#thX;Zk6EFsAxElKmupbeCmk8Inm#x!XzQ5N}_fTeP=qZv-=(u zzsZ9I$k3kIJ=ztiFVpBRv_0Exwr4SgRHr6N?Z$9oTiFz6aPT#AR3KJ;AdShg0iD`N zoD;$Ca*)pp3P4qO>>vUr(L3U_HMdwcvUn!P3UcADm>J0li%4vuV+Wa?3h3_HVznr55mV&FP*S5H(y+J91}mWxa0JV zBG^W$ETv)D?ti7xy{36R=D3>br4x2k98YDcrc<{fjYbB)TAI{vzO&Eo#W4!P2D)w& zrFl$eysfY?d$Hqi^PPwU9WrS|?`KKm{^Ud!PS_#ovKZ{C1AmR(rt?9R)|2#$AB+dK z+qooqLOa!IEQ{})mtO5GXtm?jVhMd9;1bC|Tvsp-BTJZlo{-30=jCpM1K=>5=WQgm msWR$=nQy-N=9_Q6$^HONrK%rH*^Fuc0000|&n5kwXZu0X)V6%z{>7cd$X7lI0jiUb2D8W+TB zuxQ#Ml7OvL14b4NCJ3UkmP$(tEnt@!&xzlSH)Bg@#-zNKdy+qpnb$Y({`Z{!EcZo4 zIdkUBnKNh3oH=vm%$c+E&H{lzj37~vA;>s5nLkZ><9`3 z;ss+M<~NWlm>}rp2nqzc3dRan1^%@n!5xC+wzk+f)eV6!#OtG#1i?hXCl)~^f*FEd zdQDp(Mi<&yFj6q+YyvBvx99u5f?;^id_l2A(9eRG2(uA_eu5O$tJX@8l5>^dOTh<% zy9BAhyHCa%EVx&&0#Dg5I4P*L2&xy<2o4Fh3f>dU7F;3d6pojAC9Fn5PKBUckT1Aj zkZu+t|F$DDtrhGRltF@$;SV%!4T96$Q^k9W;4L;Iu~md0yEzz_(DOBV4xL- z7YG&#b_%K?#?RlV2b~a93I5_UzlD0pI=}`zD!5$G#naxT4~M2q6%;_u8NwqUk_K>H zEGd049#_OQ8bZtX{8hnH!MlPy!f+p9=!eKkK3|B2De*C0_o6hx11$Wor6Ee%!-77L zlpwfXaGzin>3j@=oe8v(9woRL?ODowf5D6Vc-wZqnW+sqSuL#WgVjtJI4 zNHS!aET-r|H60z~m2q6xAI+JLcWs9pBji5{-W1%Zi!IhpBZg#yBDSSN(u8RP1+#1LBIEESN^hKFQjO;DHVLn zgWE-50j)$@q`OUeSCYOb%vA4=Qf?%8%8L-xp@?%0JNmoeZNY7V@pS0*kfdq)E3(^w zNESo54HrC5YvN~Pb`Zv0yhKoUveX`{perKn_eOIjQaqaMR*EOhq0o$kB(0`zMcXO} z>4Sn7w9)kvL7J{t3jSm#58yr5S{;55hGH8a=M;;{hophwl7#MEO2N3x(xP=V%|0)X z6HSp)RMv%%Q*Du?pZAU6_w}B46SAh#R!<(y=J;BgkQf9xo?%xq zF~8qxJaxghYo=Cls61|!0Uuf<#fI@r3+Vj!^Cc8&rU=xw;tj3~MMF#p5KK}9!kyDx2|$QFOQJ9h z@;>^rVy2iHf>|v=P!gll35ehf2JAJ~!@Yy2#f8}9wzEhoLX&FAZuw^`CN0rlLJ?{j z^hP+~UkC0ZcJ@2u~`dTApD_0It>lPhm(`4T>oaBigTBu>xOfgxCXH-Jk1RCIWgym@Zu^o7uX4&3en+0WY$Y;WAH}j#(A{m2PZAlId zZk07pT8*Ha8FxG)tR!uu!%vLzk|EqH=;Uh&i*1}=B}8~nADS~0PpY@dfIMr)?c+#0 z-OPeKH=AZBv#ptrpMOgTmDvB01bUDubAx8w>&aH;NGax=bns#9w(7y-j9JamOgW;N zgO^1|NWN;$IKdZ?6JYke&P*nwyl!;XdQQIx!8Ehta>lW<$u0@uWEec7D_L=kMNk9v zQb8ycw^vAt=MYa1^L|2$>d8P`DEX%_2~2IutMJHs#4W*w-2N|UKY|i|(&-xgzH2QW5(ob5)+=aYVlIrOf zKnV#Y=_W=WTP;m86Te*)e(y8vdXA2K9)v8ReJFsKGV83Z8t>eQS015BF2j?in(XFv zPfDOZdWu@6jPS5uZK=Zx4pKYqrYoY1Xi_xC zLgN@`>?9mcSOhd>hQMm7X&tM4&J>{+*{vVPONE5oX}oI^!tQGg+0AFQ1uEb#GV9rk zN1TF);5W-EIe`3{Fq{I}aYl&QfHc~gHIVZVwNvYOO)rS3ctms%B0L?v*=7;6kE;A0 zOT;gPU|$n;QZivR8FE_pTl&^JKz#_AwH85J(3gzhcZZs}UkBz?zNoffEUK{=$%~l; zriA$(k+02=V=XFybF1YH*N@^&lOq|Gv^nF%b2x2CE?B_$BHj`7kHB{4jr#E*i$b$~ qlrv|}oH=vm%$YN1&YU?r@9b}!3yrb7#}vu{0000 - - + android:padding="4dp"/> + + - + android:layout_height="wrap_content" + android:orientation="vertical"> - + + + + - + android:layout_height="0dp" + android:layout_weight="0.5" + android:orientation="horizontal" + android:padding="4dp"> - + - - - \ No newline at end of file + + + + + + + + + + + \ No newline at end of file diff --git a/torgi/src/main/res/values/strings.xml b/torgi/src/main/res/values/strings.xml index 6962fa0..f38b0a9 100644 --- a/torgi/src/main/res/values/strings.xml +++ b/torgi/src/main/res/values/strings.xml @@ -1,4 +1,11 @@ TORGI + TORGI is collecting data... Settings - + Quit TORGI? + Measurements: + Used in current fix: + Do you want to shutdown TORGI or do you want TORGI to keep collecting data in the background? + Shutdown + Run in background + \ No newline at end of file From 0c8421cae478f1600d5ceb690e956e1e8a1b495b Mon Sep 17 00:00:00 2001 From: scottlandis Date: Fri, 7 Sep 2018 21:44:19 -0700 Subject: [PATCH 2/5] - Beginning to implement basseline collection against GNNS C/N0 and AGC values to detect jamming/spoofing --- torgi/build.gradle | 1 + torgi/src/main/AndroidManifest.xml | 4 +- .../java/org/sofwerx/torgi/SatStatus.java | 20 +-- .../org/sofwerx/torgi/gnss/Constellation.java | 34 +++++ .../org/sofwerx/torgi/gnss/DataPoint.java | 44 ++++++ .../org/sofwerx/torgi/gnss/EWDetection.java | 51 +++++++ .../org/sofwerx/torgi/gnss/GNSSEWValues.java | 139 ++++++++++++++++++ .../sofwerx/torgi/gnss/SatMeasurement.java | 52 +++++++ .../org/sofwerx/torgi/gnss/Satellite.java | 31 ++++ .../org/sofwerx/torgi/gnss/SpaceTime.java | 62 ++++++++ .../GnssMeasurementListener.java | 4 +- .../torgi/{ => service}/TorgiService.java | 23 ++- .../sofwerx/torgi/{ => ui}/MainActivity.java | 77 ++++++++-- torgi/src/main/res/layout/activity_main.xml | 10 +- 14 files changed, 514 insertions(+), 38 deletions(-) create mode 100644 torgi/src/main/java/org/sofwerx/torgi/gnss/Constellation.java create mode 100644 torgi/src/main/java/org/sofwerx/torgi/gnss/DataPoint.java create mode 100644 torgi/src/main/java/org/sofwerx/torgi/gnss/EWDetection.java create mode 100644 torgi/src/main/java/org/sofwerx/torgi/gnss/GNSSEWValues.java create mode 100644 torgi/src/main/java/org/sofwerx/torgi/gnss/SatMeasurement.java create mode 100644 torgi/src/main/java/org/sofwerx/torgi/gnss/Satellite.java create mode 100644 torgi/src/main/java/org/sofwerx/torgi/gnss/SpaceTime.java rename torgi/src/main/java/org/sofwerx/torgi/{ => listener}/GnssMeasurementListener.java (87%) rename torgi/src/main/java/org/sofwerx/torgi/{ => service}/TorgiService.java (98%) rename torgi/src/main/java/org/sofwerx/torgi/{ => ui}/MainActivity.java (81%) diff --git a/torgi/build.gradle b/torgi/build.gradle index de564fc..99b2a3e 100644 --- a/torgi/build.gradle +++ b/torgi/build.gradle @@ -32,6 +32,7 @@ dependencies { //implementation fileTree(include: ['*.jar'], dir: 'libs') //implementation project(':geopackage-sdk') + implementation 'org.osmdroid:osmdroid-android:6.0.2' implementation 'mil.nga.geopackage:geopackage-android:3.0.2' implementation 'com.android.support:appcompat-v7:27.1.1' implementation 'com.android.support.constraint:constraint-layout:1.1.3' diff --git a/torgi/src/main/AndroidManifest.xml b/torgi/src/main/AndroidManifest.xml index a9b48a7..61f11b9 100644 --- a/torgi/src/main/AndroidManifest.xml +++ b/torgi/src/main/AndroidManifest.xml @@ -18,10 +18,10 @@ android:name="com.google.android.geo.API_KEY" android:value="@string/google_maps_key" /> - + diff --git a/torgi/src/main/java/org/sofwerx/torgi/SatStatus.java b/torgi/src/main/java/org/sofwerx/torgi/SatStatus.java index 701e5da..ff74825 100644 --- a/torgi/src/main/java/org/sofwerx/torgi/SatStatus.java +++ b/torgi/src/main/java/org/sofwerx/torgi/SatStatus.java @@ -1,19 +1,19 @@ package org.sofwerx.torgi; public class SatStatus { - String constellation; - int svid; + public String constellation; + public int svid; - double cn0; - boolean in_fix; + public double cn0; + public boolean in_fix; - boolean has_almanac; - boolean has_ephemeris; - boolean has_carrier_freq; + public boolean has_almanac; + public boolean has_ephemeris; + public boolean has_carrier_freq; - double elevation_deg; - double azimuth_deg; - double carrier_freq_hz; + public double elevation_deg; + public double azimuth_deg; + public double carrier_freq_hz; private boolean usedInFix = false; diff --git a/torgi/src/main/java/org/sofwerx/torgi/gnss/Constellation.java b/torgi/src/main/java/org/sofwerx/torgi/gnss/Constellation.java new file mode 100644 index 0000000..f6b1a4b --- /dev/null +++ b/torgi/src/main/java/org/sofwerx/torgi/gnss/Constellation.java @@ -0,0 +1,34 @@ +package org.sofwerx.torgi.gnss; + +/** + * The GNSS constellation type + */ +public enum Constellation { + Unknown(0), + GPS(1), + SBAS(2), + Glonass(3), + QZSS(4), + Beidou(5), + Galileo(6); + + private int value; + + Constellation(int value) { + this.value = value; + } + + public int getValue() { + return value; + } + + public Constellation get(int value) { + if ((value > 0) && (value < Constellation.values().length)) { + for (Constellation constellation:Constellation.values()) { + if (constellation.value == value) + return constellation; + } + } + return Unknown; + } +} diff --git a/torgi/src/main/java/org/sofwerx/torgi/gnss/DataPoint.java b/torgi/src/main/java/org/sofwerx/torgi/gnss/DataPoint.java new file mode 100644 index 0000000..cca3262 --- /dev/null +++ b/torgi/src/main/java/org/sofwerx/torgi/gnss/DataPoint.java @@ -0,0 +1,44 @@ +package org.sofwerx.torgi.gnss; + +import java.util.ArrayList; + +/** + * Represents relevant EW indicator values at one place in spacetime + */ +public class DataPoint { + private SpaceTime spaceTime; + private ArrayList measurements = null; + + public DataPoint(SpaceTime spaceTime, ArrayList measurements) { + this.spaceTime = spaceTime; + this.measurements = measurements; + } + + public DataPoint(SpaceTime spaceTime) { + this.spaceTime = spaceTime; + } + + public GNSSEWValues getAverageMeasurements() { + return SatMeasurement.getAverage(measurements); + } + + public ArrayList getMeasurements() { + return measurements; + } + + public void add(SatMeasurement measurement) { + if (measurement != null) { + if (measurements == null) + measurements = new ArrayList<>(); + measurements.add(measurement); + } + } + + public SpaceTime getSpaceTime() { + return spaceTime; + } + + public boolean isValid() { + return (spaceTime != null) && (measurements != null) && !measurements.isEmpty() && spaceTime.isValid(); + } +} diff --git a/torgi/src/main/java/org/sofwerx/torgi/gnss/EWDetection.java b/torgi/src/main/java/org/sofwerx/torgi/gnss/EWDetection.java new file mode 100644 index 0000000..4f16289 --- /dev/null +++ b/torgi/src/main/java/org/sofwerx/torgi/gnss/EWDetection.java @@ -0,0 +1,51 @@ +package org.sofwerx.torgi.gnss; + +import java.util.ArrayList; + +/** + * Collects EW indicator levels and provides estimates on likelihood of EW activity + */ +public class EWDetection { + private ArrayList points = null; + private ArrayList satellites = null; + + public void add(DataPoint point) { + if (point != null) { + if (points == null) + points = new ArrayList<>(); + points.add(point); + ArrayList sats = point.getMeasurements(); + if ((sats != null) && !sats.isEmpty()) { + for (SatMeasurement meas:sats) { + update(meas.getSat()); + } + } + } + } + + public void updateBaseline() { + //TODO update the baseline discarding any significantly different values + } + + public Satellite find(Satellite satellite) { + if ((satellite != null) && (satellites != null) && !satellites.isEmpty()) { + for (Satellite sat : satellites) { + if (satellite.equals(sat)) + return sat; + } + } + return null; + } + + public void update(Satellite satellite) { + if (satellite != null) { + if (satellites == null) { + satellites = new ArrayList<>(); + satellites.add(satellite); + } else { + if (find(satellite) == null) + satellites.add(satellite); + } + } + } +} diff --git a/torgi/src/main/java/org/sofwerx/torgi/gnss/GNSSEWValues.java b/torgi/src/main/java/org/sofwerx/torgi/gnss/GNSSEWValues.java new file mode 100644 index 0000000..d956afa --- /dev/null +++ b/torgi/src/main/java/org/sofwerx/torgi/gnss/GNSSEWValues.java @@ -0,0 +1,139 @@ +package org.sofwerx.torgi.gnss; + +import java.util.ArrayList; + +/** + * Single measurement of significant values for determining EW effects for one measurement from one sat + */ +public class GNSSEWValues { + private static int significantDifferencePercentCN0 = 5; + private static int significantDifferencePercentAGC = 5; + private float cn0 = Float.NaN; + private double agc = Double.NaN; + + private GNSSEWValues() {} + + public GNSSEWValues(float cn0, double agc) { + this.cn0 = cn0; + this.agc = agc; + } + + /** + * Sets the percentage of difference that indicates this is a significant variation in C/N0 + * @param percent (i.e. 100 == 100%) + */ + public static void setSignificantDifferencePercentCN0(int percent) { + GNSSEWValues.significantDifferencePercentCN0 = Math.abs(percent); + } + + /** + * Sets the percentage of difference that indicates this is a significant variation in AGC + * @param percent (i.e. 100 == 100%) + */ + public static void setSignificantDifferencePercentAGC(int percent) { + GNSSEWValues.significantDifferencePercentAGC = Math.abs(percent); + } + + /** + * Gets the Carrier-to-Noise density + * @return in dB-Hz + */ + public float getCn0() { + return cn0; + } + + /** + * Sets the Carrier-to-Noise density + * @param cn0 in dB-Hz + */ + public void setCn0(float cn0) { + this.cn0 = cn0; + } + + /** + * Gets the Automatic Gain Control level + * @return in dB + */ + public double getAgc() { + return agc; + } + + /** + * Sets the Automatic Gain Control level + * @param agc in dB + */ + public void setAgc(double agc) { + this.agc = agc; + } + + /** + * Does this measurement have all required values + * @return + */ + public boolean isComplete() { + return !Float.isNaN(cn0) && !Double.isNaN(agc); + } + + /** + * Is this C/N0 value different enough from the reference to be significant + * @param referenceValue + * @return + */ + public boolean isCNODeviationSignificant(GNSSEWValues referenceValue) { + return Math.abs(getCN0PercentDeviation(referenceValue))>significantDifferencePercentCN0; + } + + /** + * Is this AGC value different enough from the reference to be significant + * @param referenceValue + * @return + */ + public boolean isAGCDeviationSignificant(GNSSEWValues referenceValue) { + return Math.abs(getAGCPercentDeviation(referenceValue))>significantDifferencePercentAGC; + } + + /** + * Gets the percentage C/N0 deviation from the reference value + * @param referenceValue + * @return the percentage deviation (or 0 if unable to compare) + */ + public int getCN0PercentDeviation(GNSSEWValues referenceValue) { + if ((referenceValue == null) || Float.isNaN(cn0) || Float.isNaN(referenceValue.cn0) || (referenceValue.cn0 == 0f)) + return 0; + return Math.round((cn0-referenceValue.cn0)*100f/referenceValue.cn0); + } + + /** + * Gets the percentage AGC deviation from the reference value + * @param referenceValue + * @return the percentage deviation (or 0 if unable to compare) + */ + public int getAGCPercentDeviation(GNSSEWValues referenceValue) { + if ((referenceValue == null) || Double.isNaN(agc) || Double.isNaN(referenceValue.agc) || (Double.compare(0d,referenceValue.agc) == 0)) + return 0; + return (int)Math.round((agc-referenceValue.agc)*100d/referenceValue.agc); + } + + public static GNSSEWValues getAverage(ArrayList values) { + if ((values == null) || values.isEmpty()) + return null; + float sumCN0 = 0f; + int numCN0 = 0; + double sumAGC = 0d; + int numAGC = 0; + for (GNSSEWValues value:values) { + if (!Float.isNaN(value.cn0)) { + sumCN0 += value.cn0; + numCN0++; + } + if (!Double.isNaN(value.agc)) { + sumAGC += value.agc; + numAGC++; + } + } + if ((numCN0 == 0) || (numAGC == 0)) + return null; + else + return new GNSSEWValues(sumCN0/numCN0,sumAGC/numAGC); + } +} diff --git a/torgi/src/main/java/org/sofwerx/torgi/gnss/SatMeasurement.java b/torgi/src/main/java/org/sofwerx/torgi/gnss/SatMeasurement.java new file mode 100644 index 0000000..362ce13 --- /dev/null +++ b/torgi/src/main/java/org/sofwerx/torgi/gnss/SatMeasurement.java @@ -0,0 +1,52 @@ +package org.sofwerx.torgi.gnss; + +import android.location.GnssNavigationMessage; + +import java.util.ArrayList; + +/** + * A single instance of the EW relevant data for one sat + */ +public class SatMeasurement { + private Satellite sat = null; + private GNSSEWValues values = null; + + public SatMeasurement(Satellite sat, GNSSEWValues values) { + this.sat = sat; + this.values = values; + } + + public Satellite getSat() { + return sat; + } + + public static GNSSEWValues getAverage(ArrayList satMeasurements) { + if ((satMeasurements == null) || satMeasurements.isEmpty()) + return null; + ArrayList values = new ArrayList<>(); + for (SatMeasurement sat:satMeasurements) { + if (sat.values != null) + values.add(sat.values); + } + if (values.isEmpty()) + return null; + else + return GNSSEWValues.getAverage(values); + } + + public void setSat(Satellite sat) { + this.sat = sat; + } + + public GNSSEWValues getValues() { + return values; + } + + public void setValues(GNSSEWValues values) { + this.values = values; + } + + public boolean isValid() { + return ((sat != null) && (values != null) && values.isComplete()); + } +} diff --git a/torgi/src/main/java/org/sofwerx/torgi/gnss/Satellite.java b/torgi/src/main/java/org/sofwerx/torgi/gnss/Satellite.java new file mode 100644 index 0000000..9c9055d --- /dev/null +++ b/torgi/src/main/java/org/sofwerx/torgi/gnss/Satellite.java @@ -0,0 +1,31 @@ +package org.sofwerx.torgi.gnss; + +public class Satellite { + private Constellation constellation; + private int svid; + private GNSSEWValues baseline = null; + + public Satellite(Constellation constellation, int svid) { + this.constellation = constellation; + this.svid = svid; + } + + public boolean equals(Satellite sat) { + if (sat == null) + return false; + return (constellation == sat.constellation) && (svid == sat.svid); + } + + @Override + public String toString() { + return constellation.name()+svid; + } + + public GNSSEWValues getBaseline() { + return baseline; + } + + public void setBaseline(GNSSEWValues baseline) { + this.baseline = baseline; + } +} diff --git a/torgi/src/main/java/org/sofwerx/torgi/gnss/SpaceTime.java b/torgi/src/main/java/org/sofwerx/torgi/gnss/SpaceTime.java new file mode 100644 index 0000000..b0d059f --- /dev/null +++ b/torgi/src/main/java/org/sofwerx/torgi/gnss/SpaceTime.java @@ -0,0 +1,62 @@ +package org.sofwerx.torgi.gnss; + +/** + * One point in space and time + */ +public class SpaceTime { + private double longitude = Double.NaN; + private double latitude = Double.NaN; + private double altitude = Double.NaN; //m MSL + private float horzUncertainty = Float.NaN; //m + private float vertUncertainty = Float.NaN; //m + private long time = Long.MIN_VALUE; + + public SpaceTime(double latitude, double longitude, double altitude, long time) { + this.latitude = latitude; + this.longitude = longitude; + this.altitude = altitude; + this.time = time; + } + + public SpaceTime(double latitude, double longitude) { + this.latitude = latitude; + this.longitude = longitude; + time = System.currentTimeMillis(); + } + + public double getLongitude() { + return longitude; + } + + public void setLongitude(double longitude) { + this.longitude = longitude; + } + + public double getLatitude() { + return latitude; + } + + public void setLatitude(double latitude) { + this.latitude = latitude; + } + + public double getAltitude() { + return altitude; + } + + public void setAltitude(double altitude) { + this.altitude = altitude; + } + + public long getTime() { + return time; + } + + public void setTime(long time) { + this.time = time; + } + + public boolean isValid() { + return !Double.isNaN(latitude) && !Double.isNaN(longitude) && (time > 0l); + } +} diff --git a/torgi/src/main/java/org/sofwerx/torgi/GnssMeasurementListener.java b/torgi/src/main/java/org/sofwerx/torgi/listener/GnssMeasurementListener.java similarity index 87% rename from torgi/src/main/java/org/sofwerx/torgi/GnssMeasurementListener.java rename to torgi/src/main/java/org/sofwerx/torgi/listener/GnssMeasurementListener.java index a1dd798..722cae5 100644 --- a/torgi/src/main/java/org/sofwerx/torgi/GnssMeasurementListener.java +++ b/torgi/src/main/java/org/sofwerx/torgi/listener/GnssMeasurementListener.java @@ -1,8 +1,10 @@ -package org.sofwerx.torgi; +package org.sofwerx.torgi.listener; import android.location.GnssMeasurement; import android.location.Location; +import org.sofwerx.torgi.SatStatus; + import java.util.ArrayList; import java.util.Collection; diff --git a/torgi/src/main/java/org/sofwerx/torgi/TorgiService.java b/torgi/src/main/java/org/sofwerx/torgi/service/TorgiService.java similarity index 98% rename from torgi/src/main/java/org/sofwerx/torgi/TorgiService.java rename to torgi/src/main/java/org/sofwerx/torgi/service/TorgiService.java index 98dabbf..878dd8d 100644 --- a/torgi/src/main/java/org/sofwerx/torgi/TorgiService.java +++ b/torgi/src/main/java/org/sofwerx/torgi/service/TorgiService.java @@ -1,7 +1,6 @@ -package org.sofwerx.torgi; +package org.sofwerx.torgi.service; import android.Manifest; -import android.annotation.TargetApi; import android.app.Notification; import android.app.NotificationChannel; import android.app.NotificationManager; @@ -10,8 +9,6 @@ import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; -import android.database.sqlite.SQLiteCantOpenDatabaseException; -import android.location.Criteria; import android.location.GnssClock; import android.location.GnssMeasurement; import android.location.GnssMeasurementsEvent; @@ -24,12 +21,14 @@ import android.os.Bundle; import android.os.Environment; import android.os.IBinder; -import android.os.PowerManager; import android.support.annotation.Nullable; import android.support.annotation.RequiresApi; import android.support.v4.content.ContextCompat; import android.util.Log; -import android.widget.TextView; + +import org.sofwerx.torgi.listener.GnssMeasurementListener; +import org.sofwerx.torgi.R; +import org.sofwerx.torgi.SatStatus; import java.sql.SQLException; import java.text.SimpleDateFormat; @@ -74,7 +73,6 @@ import mil.nga.sf.proj.ProjectionConstants; import static java.time.Instant.now; -import static java.time.Instant.parse; import static mil.nga.geopackage.db.GeoPackageDataType.DATETIME; import static mil.nga.geopackage.db.GeoPackageDataType.INTEGER; import static mil.nga.geopackage.db.GeoPackageDataType.REAL; @@ -83,7 +81,7 @@ /** * Torgi service handles getting information from the GPS receiver (and eventually accepting data * from other sensors as well) and then storing that data in the GeoPackage as well as making - * this info available to any listenng UI element. + * this info available to any listening UI element. * * TODO this is currently on the main thread - move to a separate thread to support more responsive UI */ @@ -131,7 +129,7 @@ public void setListener(GnssMeasurementListener listener) { private static final String clkmapTblName = satTblName + "_" + clkTblName; private static final String motionmapTblName = PtsTableName + "_" + motionTblName; - private ArrayList sats = new ArrayList<>(); + private ArrayList sats = new ArrayList<>(); private GeoPackage gpkg = null; HashMap SatStatus = new HashMap<>(); @@ -312,12 +310,9 @@ public void onGnssMeasurementsReceived(GnssMeasurementsEvent event) { SatRowsToMap.clear(); for(final GnssMeasurement g : gm) { - String con = SatType.get(g.getConstellationType()); String hashkey = con + g.getSvid(); - HashMap thisSat = new HashMap(); - SimpleAttributesDao satDao = RTE.getSimpleAttributesDao(satTblName); SimpleAttributesRow satrow = satDao.newRow(); @@ -327,7 +322,7 @@ public void onGnssMeasurementsReceived(GnssMeasurementsEvent event) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { if (g.hasAutomaticGainControlLevelDb()) { - satrow.setValue("agc", (double) g.getAutomaticGainControlLevelDb()); + satrow.setValue("agc", g.getAutomaticGainControlLevelDb()); satrow.setValue("has_agc", 1); } else { satrow.setValue("agc", 0); @@ -880,7 +875,7 @@ public TorgiService getService() { private void setForeground() { PendingIntent pendingIntent = null; try { - Intent notificationIntent = new Intent(this, Class.forName("org.sofwerx.torgi.MainActivity")); + Intent notificationIntent = new Intent(this, Class.forName("org.sofwerx.torgi.ui.MainActivity")); pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0); } catch (ClassNotFoundException ignore) { } diff --git a/torgi/src/main/java/org/sofwerx/torgi/MainActivity.java b/torgi/src/main/java/org/sofwerx/torgi/ui/MainActivity.java similarity index 81% rename from torgi/src/main/java/org/sofwerx/torgi/MainActivity.java rename to torgi/src/main/java/org/sofwerx/torgi/ui/MainActivity.java index 0c03d9d..dda418f 100644 --- a/torgi/src/main/java/org/sofwerx/torgi/MainActivity.java +++ b/torgi/src/main/java/org/sofwerx/torgi/ui/MainActivity.java @@ -1,4 +1,4 @@ -package org.sofwerx.torgi; +package org.sofwerx.torgi.ui; import android.Manifest; import android.app.AlertDialog; @@ -21,8 +21,6 @@ import android.view.View; import android.widget.TextView; -import android.support.v7.app.AppCompatActivity; - import java.text.DecimalFormat; import java.text.SimpleDateFormat; import java.util.ArrayList; @@ -36,13 +34,20 @@ import com.google.android.gms.maps.GoogleMap; import com.google.android.gms.maps.OnMapReadyCallback; import com.google.android.gms.maps.SupportMapFragment; +import com.google.android.gms.maps.model.BitmapDescriptorFactory; import com.google.android.gms.maps.model.LatLng; -import com.google.android.gms.maps.model.MapStyleOptions; import com.google.android.gms.maps.model.Marker; import com.google.android.gms.maps.model.MarkerOptions; import com.google.android.gms.maps.model.Polyline; import com.google.android.gms.maps.model.PolylineOptions; +import org.osmdroid.util.GeoPoint; +import org.osmdroid.views.overlay.gestures.RotationGestureOverlay; +import org.sofwerx.torgi.listener.GnssMeasurementListener; +import org.sofwerx.torgi.R; +import org.sofwerx.torgi.SatStatus; +import org.sofwerx.torgi.service.TorgiService; + public class MainActivity extends FragmentActivity implements OnMapReadyCallback, GnssMeasurementListener { private LatLng CENTER_US = new LatLng(39.181071, -99.938295); private final static String TAG = "TORGIact"; @@ -58,11 +63,13 @@ public class MainActivity extends FragmentActivity implements OnMapReadyCallback private boolean serviceBound = false; private TorgiService torgiService = null; private GoogleMap mMap; + private org.osmdroid.views.MapView osmMap = null; private boolean mapReady = false; private Marker current = null; + private org.osmdroid.views.overlay.Marker currentOSM = null; private Polyline historyPolyline = null; + private org.osmdroid.views.overlay.Polyline historyPolylineOSM = null; private ArrayList history = null; - private LatLng currentFixLatLng = null; protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -83,10 +90,23 @@ protected void onCreate(Bundle savedInstanceState) { SupportMapFragment mapFragment = (SupportMapFragment) getSupportFragmentManager().findFragmentById(R.id.map); mapFragment.getMapAsync(this); + osmMapSetup(); + checkPermissions(); startService(); }; + private void osmMapSetup() { + osmMap = findViewById(R.id.maposm); + + RotationGestureOverlay mRotationGestureOverlay = new RotationGestureOverlay(osmMap); + mRotationGestureOverlay.setEnabled(true); + osmMap.getOverlays().add(mRotationGestureOverlay); + osmMap.setBuiltInZoomControls(false); + osmMap.setMultiTouchControls(true); //needed for pinch zooms + osmMap.setTilesScaledToDpi(true); //scales tiles to the current screen's DPI, helps with readability of labels + } + @Override public void onResume() { super.onResume(); @@ -157,15 +177,45 @@ private void drawMarker(LatLng pos, String info) { historyPolyline = mMap.addPolyline(opts); } else historyPolyline.setPoints(history); + + if (historyPolylineOSM == null) { + historyPolylineOSM = new org.osmdroid.views.overlay.Polyline(); + ArrayList list = new ArrayList<>(); + for (LatLng pt:history) { + list.add(new GeoPoint(pt.latitude,pt.longitude)); + } + historyPolylineOSM.setPoints(list); + historyPolylineOSM.setColor(Color.YELLOW); + osmMap.getOverlays().add(historyPolylineOSM); + } else + historyPolylineOSM.addPoint(new GeoPoint(pos.latitude,pos.longitude)); } - currentFixLatLng = pos; + if (current == null) { current = mMap.addMarker(new MarkerOptions() .position(pos) + .anchor(0.5f,0.5f) + .icon(BitmapDescriptorFactory.fromResource(R.drawable.map_icon)) .title("GPS")); mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(pos, 19)); } else current.setPosition(pos); + if (currentOSM == null) { + currentOSM = new org.osmdroid.views.overlay.Marker(osmMap); + currentOSM.setPosition(new GeoPoint(pos.latitude,pos.longitude)); + currentOSM.setAnchor(org.osmdroid.views.overlay.Marker.ANCHOR_CENTER,org.osmdroid.views.overlay.Marker.ANCHOR_CENTER); + currentOSM.setIcon(getResources().getDrawable(R.drawable.map_icon)); + currentOSM.setTitle("GPS"); + osmMap.getOverlays().add(currentOSM); + if (osmMap != null) { + osmMap.getController().setZoom(18d); + osmMap.setExpectedCenter(new GeoPoint(pos.latitude, pos.longitude)); + } + } else { + currentOSM.setPosition(new GeoPoint(pos.latitude,pos.longitude)); + osmMap.invalidate(); + } + current.setSnippet(info); } } @@ -256,10 +306,19 @@ public void onMapReady(GoogleMap googleMap) { } else mMap.setMyLocationEnabled(false); LatLng lastLatLng = getLastLatLng(); - if (lastLatLng != null) - mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(lastLatLng,19)); - else + if (lastLatLng != null) { + mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(lastLatLng, 19)); + if (osmMap != null) { + osmMap.getController().setZoom(19d); + osmMap.setExpectedCenter(new GeoPoint(lastLatLng.latitude, lastLatLng.longitude)); + } + } else { mMap.moveCamera(CameraUpdateFactory.newLatLng(CENTER_US)); + if (osmMap != null) { + osmMap.getController().setZoom(1d); + osmMap.setExpectedCenter(new GeoPoint(CENTER_US.latitude, CENTER_US.longitude)); + } + } mapReady = true; mMap.getUiSettings().setMapToolbarEnabled(false); } diff --git a/torgi/src/main/res/layout/activity_main.xml b/torgi/src/main/res/layout/activity_main.xml index d3409f5..ea4a3c0 100644 --- a/torgi/src/main/res/layout/activity_main.xml +++ b/torgi/src/main/res/layout/activity_main.xml @@ -6,7 +6,7 @@ android:layout_height="match_parent" android:orientation="vertical" android:theme="@style/AppTheme" - tools:context=".MainActivity"> + tools:context=".ui.MainActivity"> + + + tools:context=".ui.MainActivity" /> \ No newline at end of file From 480af9d5de91a86608a75948f432452a2d92ef4e Mon Sep 17 00:00:00 2001 From: scottlandis Date: Sun, 9 Sep 2018 23:39:44 -0700 Subject: [PATCH 3/5] - Google Maps replaced with Open Street Map to address API key issue - Edge computing of C/N0 and AGC added for real time detection of indicators of jamming/spoofing - GeoPackage writing moved off of Main thread to support more fluid GUI - Initial onboard Sensor integration (just magnetic at this point) --- torgi/build.gradle | 6 +- .../org/sofwerx/torgi/gnss/Constellation.java | 2 +- .../org/sofwerx/torgi/gnss/EWDetection.java | 52 +- .../org/sofwerx/torgi/gnss/GNSSEWValues.java | 12 +- .../java/org/sofwerx/torgi/gnss/LatLng.java | 10 + .../org/sofwerx/torgi/gnss/SpaceTime.java | 19 + .../listener/GnssMeasurementListener.java | 6 +- .../torgi/listener/SensorListener.java | 7 + .../torgi/service/GNSSMeasurementService.java | 87 ++ .../torgi/service/GeoPackageRecorder.java | 771 +++++++++++++++++ .../sofwerx/torgi/service/SensorService.java | 54 ++ .../sofwerx/torgi/service/TorgiService.java | 792 ++---------------- .../org/sofwerx/torgi/ui/MainActivity.java | 249 +++--- torgi/src/main/res/drawable/map_icon.png | Bin 0 -> 2004 bytes torgi/src/main/res/layout/activity_main.xml | 13 +- torgi/src/main/res/values/strings.xml | 4 + 16 files changed, 1218 insertions(+), 866 deletions(-) create mode 100644 torgi/src/main/java/org/sofwerx/torgi/gnss/LatLng.java create mode 100644 torgi/src/main/java/org/sofwerx/torgi/listener/SensorListener.java create mode 100644 torgi/src/main/java/org/sofwerx/torgi/service/GNSSMeasurementService.java create mode 100644 torgi/src/main/java/org/sofwerx/torgi/service/GeoPackageRecorder.java create mode 100644 torgi/src/main/java/org/sofwerx/torgi/service/SensorService.java create mode 100644 torgi/src/main/res/drawable/map_icon.png diff --git a/torgi/build.gradle b/torgi/build.gradle index 99b2a3e..747d869 100644 --- a/torgi/build.gradle +++ b/torgi/build.gradle @@ -26,6 +26,10 @@ android { // path "CMakeLists.txt" // } // } + compileOptions { + targetCompatibility 1.8 + sourceCompatibility 1.8 + } } dependencies { @@ -36,7 +40,7 @@ dependencies { implementation 'mil.nga.geopackage:geopackage-android:3.0.2' implementation 'com.android.support:appcompat-v7:27.1.1' implementation 'com.android.support.constraint:constraint-layout:1.1.3' - implementation 'com.google.android.gms:play-services-maps:15.0.1' + //implementation 'com.google.android.gms:play-services-maps:15.0.1' implementation 'com.android.support:design:27.1.1' testImplementation 'junit:junit:4.12' androidTestImplementation 'com.android.support.test:runner:1.0.2' diff --git a/torgi/src/main/java/org/sofwerx/torgi/gnss/Constellation.java b/torgi/src/main/java/org/sofwerx/torgi/gnss/Constellation.java index f6b1a4b..2898d19 100644 --- a/torgi/src/main/java/org/sofwerx/torgi/gnss/Constellation.java +++ b/torgi/src/main/java/org/sofwerx/torgi/gnss/Constellation.java @@ -22,7 +22,7 @@ public int getValue() { return value; } - public Constellation get(int value) { + public static Constellation get(int value) { if ((value > 0) && (value < Constellation.values().length)) { for (Constellation constellation:Constellation.values()) { if (constellation.value == value) diff --git a/torgi/src/main/java/org/sofwerx/torgi/gnss/EWDetection.java b/torgi/src/main/java/org/sofwerx/torgi/gnss/EWDetection.java index 4f16289..6b94040 100644 --- a/torgi/src/main/java/org/sofwerx/torgi/gnss/EWDetection.java +++ b/torgi/src/main/java/org/sofwerx/torgi/gnss/EWDetection.java @@ -23,8 +23,56 @@ public void add(DataPoint point) { } } - public void updateBaseline() { - //TODO update the baseline discarding any significantly different values + /** + * Gets all the measurements for one satellite + * @param sat + * @param discardSignificantDifference true == ignore any measurement that is significantly different from that satellite's baseline + * @return vlues or null if none found + */ + public ArrayList getAllSatMeasurementsForOneSat(Satellite sat, boolean discardSignificantDifference) { + ArrayList values = null; + if ((sat != null) && (points != null) && !points.isEmpty()) { + values = new ArrayList<>(); + + ArrayList measurements; + boolean hasBaseline = sat.getBaseline() != null; + for (DataPoint pt:points) { + measurements = pt.getMeasurements(); + if ((measurements != null) && !measurements.isEmpty()) { + for (SatMeasurement measurement:measurements) { + if ((measurement.getValues() != null) && sat.equals(measurement.getSat())) { + if (discardSignificantDifference && hasBaseline) { + if (!measurement.getValues().isDeviationSignificant(sat.getBaseline())) + values.add(measurement.getValues()); + } else + values.add(measurement.getValues()); + } + } + } + } + + if (values.isEmpty()) + values = null; + } + return values; + } + + /** + * Updates the baseline for all satellites + * @param discardSignificantDifference true = igonore any measurement that is significantly deviated from the baseline + */ + public void updateBaseline(boolean discardSignificantDifference) { + if ((points != null) && !points.isEmpty() && (satellites != null) && !satellites.isEmpty()) { + for (Satellite sat:satellites) { + ArrayList values; + if (sat.getBaseline() == null) + values = getAllSatMeasurementsForOneSat(sat,false); + else + values = getAllSatMeasurementsForOneSat(sat,discardSignificantDifference); + if ((values != null) && !values.isEmpty()) + sat.setBaseline(GNSSEWValues.getAverage(values)); + } + } } public Satellite find(Satellite satellite) { diff --git a/torgi/src/main/java/org/sofwerx/torgi/gnss/GNSSEWValues.java b/torgi/src/main/java/org/sofwerx/torgi/gnss/GNSSEWValues.java index d956afa..d3404c0 100644 --- a/torgi/src/main/java/org/sofwerx/torgi/gnss/GNSSEWValues.java +++ b/torgi/src/main/java/org/sofwerx/torgi/gnss/GNSSEWValues.java @@ -6,10 +6,11 @@ * Single measurement of significant values for determining EW effects for one measurement from one sat */ public class GNSSEWValues { + public final static double NA = Double.NaN; private static int significantDifferencePercentCN0 = 5; private static int significantDifferencePercentAGC = 5; private float cn0 = Float.NaN; - private double agc = Double.NaN; + private double agc = NA; private GNSSEWValues() {} @@ -92,6 +93,15 @@ public boolean isAGCDeviationSignificant(GNSSEWValues referenceValue) { return Math.abs(getAGCPercentDeviation(referenceValue))>significantDifferencePercentAGC; } + /** + * Is any value in this GNSSEWValues significantly different that the reference + * @param referenceValue + * @return + */ + public boolean isDeviationSignificant(GNSSEWValues referenceValue) { + return isCNODeviationSignificant(referenceValue) || isAGCDeviationSignificant(referenceValue); + } + /** * Gets the percentage C/N0 deviation from the reference value * @param referenceValue diff --git a/torgi/src/main/java/org/sofwerx/torgi/gnss/LatLng.java b/torgi/src/main/java/org/sofwerx/torgi/gnss/LatLng.java new file mode 100644 index 0000000..1dc5886 --- /dev/null +++ b/torgi/src/main/java/org/sofwerx/torgi/gnss/LatLng.java @@ -0,0 +1,10 @@ +package org.sofwerx.torgi.gnss; + +public class LatLng { + public double latitude; + public double longitude; + public LatLng(double latitude, double longitude) { + this.latitude = latitude; + this.longitude = longitude; + } +} diff --git a/torgi/src/main/java/org/sofwerx/torgi/gnss/SpaceTime.java b/torgi/src/main/java/org/sofwerx/torgi/gnss/SpaceTime.java index b0d059f..341fbb3 100644 --- a/torgi/src/main/java/org/sofwerx/torgi/gnss/SpaceTime.java +++ b/torgi/src/main/java/org/sofwerx/torgi/gnss/SpaceTime.java @@ -1,5 +1,8 @@ package org.sofwerx.torgi.gnss; +import android.location.Location; +import android.os.Build; + /** * One point in space and time */ @@ -18,6 +21,22 @@ public SpaceTime(double latitude, double longitude, double altitude, long time) this.time = time; } + public SpaceTime(Location loc) { + if (loc != null) { + this.latitude = loc.getLatitude(); + this.longitude = loc.getLongitude(); + if (loc.hasAltitude()) + this.altitude = loc.getAltitude(); + if (loc.hasAccuracy()) + this.horzUncertainty = loc.getAccuracy(); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + if (loc.hasVerticalAccuracy()) + this.vertUncertainty = loc.getVerticalAccuracyMeters(); + } + this.time = loc.getTime(); + } + } + public SpaceTime(double latitude, double longitude) { this.latitude = latitude; this.longitude = longitude; diff --git a/torgi/src/main/java/org/sofwerx/torgi/listener/GnssMeasurementListener.java b/torgi/src/main/java/org/sofwerx/torgi/listener/GnssMeasurementListener.java index 722cae5..bfdeddf 100644 --- a/torgi/src/main/java/org/sofwerx/torgi/listener/GnssMeasurementListener.java +++ b/torgi/src/main/java/org/sofwerx/torgi/listener/GnssMeasurementListener.java @@ -1,6 +1,8 @@ package org.sofwerx.torgi.listener; import android.location.GnssMeasurement; +import android.location.GnssMeasurementsEvent; +import android.location.GnssStatus; import android.location.Location; import org.sofwerx.torgi.SatStatus; @@ -12,8 +14,8 @@ * This listener is used primarily to report changes detected by TorgiService to any display activity */ public interface GnssMeasurementListener { - void onSatStatusUpdated(ArrayList sats); + void onSatStatusUpdated(GnssStatus status); void onLocationChanged(Location loc); - void onGnssMeasurementReceived(Collection measurement); + void onGnssMeasurementReceived(GnssMeasurementsEvent event); void onProviderChanged(String provider, boolean enabled); } diff --git a/torgi/src/main/java/org/sofwerx/torgi/listener/SensorListener.java b/torgi/src/main/java/org/sofwerx/torgi/listener/SensorListener.java new file mode 100644 index 0000000..278cc85 --- /dev/null +++ b/torgi/src/main/java/org/sofwerx/torgi/listener/SensorListener.java @@ -0,0 +1,7 @@ +package org.sofwerx.torgi.listener; + +import android.hardware.SensorEvent; + +public interface SensorListener { + void onSensorUpdated(SensorEvent event); +} diff --git a/torgi/src/main/java/org/sofwerx/torgi/service/GNSSMeasurementService.java b/torgi/src/main/java/org/sofwerx/torgi/service/GNSSMeasurementService.java new file mode 100644 index 0000000..ce1917a --- /dev/null +++ b/torgi/src/main/java/org/sofwerx/torgi/service/GNSSMeasurementService.java @@ -0,0 +1,87 @@ +package org.sofwerx.torgi.service; + +import android.location.GnssMeasurement; +import android.location.GnssMeasurementsEvent; +import android.location.Location; +import android.os.Build; +import android.os.Handler; +import android.os.Looper; +import android.util.Log; + +import org.sofwerx.torgi.gnss.Constellation; +import org.sofwerx.torgi.gnss.DataPoint; +import org.sofwerx.torgi.gnss.EWDetection; +import org.sofwerx.torgi.gnss.GNSSEWValues; +import org.sofwerx.torgi.gnss.SatMeasurement; +import org.sofwerx.torgi.gnss.Satellite; +import org.sofwerx.torgi.gnss.SpaceTime; + +import java.util.Collection; + +public class GNSSMeasurementService extends Thread { + private final static String TAG = "TORGI.MsrSrc"; + private Handler handler; + private long HELPER_INTERVAL = 1000l; + private long BASELINE_UPDATE_INTERVAL = 1000l * 60l; + private long lastBaselineUpdate = Long.MIN_VALUE; + private TorgiService torgiService; + private EWDetection ewDetection = null; + private Looper looper = null; + + public GNSSMeasurementService(TorgiService torgiService) { + this.torgiService = torgiService; + ewDetection = new EWDetection(); + } + + private final Runnable periodicHelper = new Runnable() { + @Override + public void run() { + Log.d(TAG,"GNSSMeasurementService - periodicHelper"); + if (System.currentTimeMillis() > lastBaselineUpdate + BASELINE_UPDATE_INTERVAL) { + ewDetection.updateBaseline(true); + lastBaselineUpdate = System.currentTimeMillis(); + Log.d(TAG,"GNSS measurement baseline updated"); + } + if (handler != null) + handler.postDelayed(this, HELPER_INTERVAL); + } + }; + + @Override + public void run() { + Looper.prepare(); + looper = Looper.myLooper(); + handler = new Handler(); + handler.postDelayed(periodicHelper,HELPER_INTERVAL); + Looper.loop(); + } + + public void shutdown() { + if (handler != null) + handler.removeCallbacks(periodicHelper); + if (looper != null) + looper.quit(); + } + + public void onGnssMeasurementsReceived(final Location loc, GnssMeasurementsEvent event) { + final Collection measurements = event.getMeasurements(); + if ((loc != null) && (measurements != null) && !measurements.isEmpty()) { + handler.post(() -> { + synchronized (ewDetection) { + Log.d(TAG,"EW Measurement recorded"); + DataPoint dp = new DataPoint(new SpaceTime(loc)); + for (GnssMeasurement measurement : measurements) { + Satellite sat = new Satellite(Constellation.get(measurement.getConstellationType()),measurement.getSvid()); + SatMeasurement satMeasurement; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) + satMeasurement = new SatMeasurement(sat, new GNSSEWValues((float)measurement.getCn0DbHz(),measurement.getAutomaticGainControlLevelDb())); + else + satMeasurement = new SatMeasurement(sat, new GNSSEWValues((float)measurement.getCn0DbHz(),GNSSEWValues.NA)); + dp.add(satMeasurement); + } + ewDetection.add(dp); + } + }); + } + } +} diff --git a/torgi/src/main/java/org/sofwerx/torgi/service/GeoPackageRecorder.java b/torgi/src/main/java/org/sofwerx/torgi/service/GeoPackageRecorder.java new file mode 100644 index 0000000..05438ad --- /dev/null +++ b/torgi/src/main/java/org/sofwerx/torgi/service/GeoPackageRecorder.java @@ -0,0 +1,771 @@ +package org.sofwerx.torgi.service; + +import android.content.Context; +import android.hardware.Sensor; +import android.hardware.SensorEvent; +import android.location.GnssClock; +import android.location.GnssMeasurement; +import android.location.GnssMeasurementsEvent; +import android.location.GnssStatus; +import android.location.Location; +import android.os.Build; +import android.os.Environment; +import android.os.Handler; +import android.os.HandlerThread; +import android.util.Log; + +import org.sofwerx.torgi.SatStatus; + +import java.sql.SQLException; +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Collection; +import java.util.Date; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Locale; +import java.util.concurrent.atomic.AtomicBoolean; + +import mil.nga.geopackage.BoundingBox; +import mil.nga.geopackage.GeoPackage; +import mil.nga.geopackage.GeoPackageException; +import mil.nga.geopackage.GeoPackageManager; +import mil.nga.geopackage.core.contents.Contents; +import mil.nga.geopackage.core.contents.ContentsDao; +import mil.nga.geopackage.core.contents.ContentsDataType; +import mil.nga.geopackage.core.srs.SpatialReferenceSystem; +import mil.nga.geopackage.core.srs.SpatialReferenceSystemDao; +import mil.nga.geopackage.extension.related.ExtendedRelation; +import mil.nga.geopackage.extension.related.RelatedTablesExtension; +import mil.nga.geopackage.extension.related.UserMappingDao; +import mil.nga.geopackage.extension.related.UserMappingRow; +import mil.nga.geopackage.extension.related.UserMappingTable; +import mil.nga.geopackage.extension.related.simple.SimpleAttributesDao; +import mil.nga.geopackage.extension.related.simple.SimpleAttributesRow; +import mil.nga.geopackage.extension.related.simple.SimpleAttributesTable; +import mil.nga.geopackage.factory.GeoPackageFactory; +import mil.nga.geopackage.features.columns.GeometryColumns; +import mil.nga.geopackage.features.columns.GeometryColumnsDao; +import mil.nga.geopackage.features.user.FeatureColumn; +import mil.nga.geopackage.features.user.FeatureDao; +import mil.nga.geopackage.features.user.FeatureRow; +import mil.nga.geopackage.features.user.FeatureTable; +import mil.nga.geopackage.geom.GeoPackageGeometryData; +import mil.nga.geopackage.user.UserTable; +import mil.nga.geopackage.user.custom.UserCustomColumn; +import mil.nga.sf.GeometryType; +import mil.nga.sf.Point; +import mil.nga.sf.proj.ProjectionConstants; + +import static java.time.Instant.now; +import static mil.nga.geopackage.db.GeoPackageDataType.DATETIME; +import static mil.nga.geopackage.db.GeoPackageDataType.INTEGER; +import static mil.nga.geopackage.db.GeoPackageDataType.REAL; +import static mil.nga.geopackage.db.GeoPackageDataType.TEXT; + +/** + * Saves GNSS data into a GeoPackage + */ +public class GeoPackageRecorder extends HandlerThread { + private final static String TAG = "TORGI.GpkgRec"; + private Handler handler; + private final Context context; + //private Looper looper = null; + private AtomicBoolean ready = new AtomicBoolean(false); + + private final static SimpleDateFormat fmtFilenameFriendlyTime = new SimpleDateFormat("YYYYMMdd HHmmss"); + + HashMap SatType = new HashMap() { + { + put(0, "Unknown"); + put(1, "GPS"); + put(2, "SBAS"); + put(3, "Glonass"); + put(4, "QZSS"); + put(5, "Beidou"); + put(6, "Galileo"); + + } + }; + + public GeoPackageRecorder(Context context) { + super("GeoPkgRcdr"); + this.context = context; + } + + @Override + protected void onLooperPrepared() { + handler = new Handler(); + if (GPSgpkg == null) { + try { + GPSgpkg = setupGpkgDB(context, GpkgFolder, GpkgFilename); + } catch (SQLException e) { + e.printStackTrace(); + } + } + + List tbls = GPSgpkg.getTables(); + tbls.add(GPSgpkg.getApplicationId()); + ready.set(true); + } + + final static long WGS84_SRS = 4326; + + private static final String ID_COLUMN = "id"; + private static final String GEOMETRY_COLUMN = "geom"; + + String GpkgFilename = "TORGI-GNSS"; + String GpkgFolder = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getAbsolutePath(); + + private static final String PtsTableName = "gps_observation_points"; + private static final String satTblName = "sat_data"; + private static final String clkTblName = "rcvr_clock"; + private static final String motionTblName = "motion"; + private static final String satmapTblName = PtsTableName + "_" + satTblName; + private static final String clkmapTblName = satTblName + "_" + clkTblName; + private static final String motionmapTblName = PtsTableName + "_" + motionTblName; + + private GeoPackage gpkg = null; + + HashMap SatStatus = new HashMap<>(); + HashMap SatInfo = new HashMap<>(); + HashMap SatRowsToMap = new HashMap<>(); + + GeoPackage GPSgpkg = null; + RelatedTablesExtension RTE = null; + ExtendedRelation SatExtRel = null; + ExtendedRelation ClkExtRel = null; + UserTable PtsTable = null; + UserTable SatTable = null; + UserTable ClkTable = null; + UserTable MotionTable = null; + + public String getGpkgFilename() { + return GpkgFilename; + } + + public void shutdown() { + Log.d(TAG,"GeoPackageRecorder.shutdown()"); + ready.set(false); + getLooper().quit(); + if (GPSgpkg != null) { + GPSgpkg.close(); + GPSgpkg = null; + } + } + + public void onSatelliteStatusChanged(final GnssStatus status) { + if (ready.get() && (handler != null)) { + handler.post(() -> { + try { + int numSats = status.getSatelliteCount(); + + for (int i = 0; i < numSats; ++i) { + SatStatus thisSat = new SatStatus(); + thisSat.constellation = SatType.get(status.getConstellationType(i)); + thisSat.svid = status.getSvid(i); + thisSat.cn0 = status.getCn0DbHz(i); + + thisSat.has_almanac = status.hasAlmanacData(i); + thisSat.has_ephemeris = status.hasEphemerisData(i); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) + thisSat.has_carrier_freq = status.hasCarrierFrequencyHz(i); + else + thisSat.has_carrier_freq = false; + + thisSat.azimuth_deg = status.getAzimuthDegrees(i); + thisSat.elevation_deg = status.getElevationDegrees(i); + thisSat.cn0 = status.getCn0DbHz(i); + + String hashkey = thisSat.constellation + status.getSvid(i); + SatStatus.put(hashkey, thisSat); + + thisSat.setUsedInFix(status.usedInFix(i)); + } + } catch (Exception e) { + e.printStackTrace(); + } + }); + } + } + + public void onGnssMeasurementsReceived(final GnssMeasurementsEvent event) { + if (ready.get() && (handler != null)) { + handler.post(() -> { + try { + Collection gm = event.getMeasurements(); + + GnssClock clk = event.getClock(); + + SimpleAttributesDao clkDao = RTE.getSimpleAttributesDao(clkTblName); + SimpleAttributesRow clkrow = clkDao.newRow(); + + clkrow.setValue("time_nanos", (double) clk.getTimeNanos()); + if (clk.hasTimeUncertaintyNanos()) { + clkrow.setValue("time_uncertainty_nanos", (double) clk.getTimeUncertaintyNanos()); + clkrow.setValue("has_time_uncertainty_nanos", 1); + } else { + clkrow.setValue("time_uncertainty_nanos", (double) 0.0); + clkrow.setValue("has_time_uncertainty_nanos", 0); + } + + if (clk.hasBiasNanos()) { + clkrow.setValue("bias_nanos", (double) clk.getBiasNanos()); + clkrow.setValue("has_bias_nanos", 1); + } else { + clkrow.setValue("bias_nanos", (double) 0.0); + clkrow.setValue("has_bias_nanos", 0); + } + if (clk.hasFullBiasNanos()) { + clkrow.setValue("full_bias_nanos", clk.getFullBiasNanos()); + clkrow.setValue("has_full_bias_nanos", 1); + } else { + clkrow.setValue("full_bias_nanos", 0); + clkrow.setValue("has_full_bias_nanos", 0); + } + if (clk.hasBiasUncertaintyNanos()) { + clkrow.setValue("bias_uncertainty_nanos", (double) clk.getBiasUncertaintyNanos()); + clkrow.setValue("has_bias_uncertainty_nanos", 1); + } else { + clkrow.setValue("bias_uncertainty_nanos", (double) 0.0); + clkrow.setValue("has_bias_uncertainty_nanos", 0); + } + if (clk.hasDriftNanosPerSecond()) { + clkrow.setValue("drift_nanos_per_sec", (double) clk.getDriftNanosPerSecond()); + clkrow.setValue("has_drift_nanos_per_sec", 1); + } else { + clkrow.setValue("drift_nanos_per_sec", (double) 0.0); + clkrow.setValue("has_drift_nanos_per_sec", 0); + } + if (clk.hasDriftUncertaintyNanosPerSecond()) { + clkrow.setValue("drift_uncertainty_nps", (double) clk.getDriftUncertaintyNanosPerSecond()); + clkrow.setValue("has_drift_uncertainty_nps", 1); + } else { + clkrow.setValue("drift_uncertainty_nps", (double) 0.0); + clkrow.setValue("has_drift_uncertainty_nps", 0); + } + if (clk.hasLeapSecond()) { + clkrow.setValue("leap_second", clk.getLeapSecond()); + clkrow.setValue("has_leap_second", 1); + } else { + clkrow.setValue("leap_second", 0); + clkrow.setValue("has_leap_second", 0); + } + clkrow.setValue("hw_clock_discontinuity_count", clk.getHardwareClockDiscontinuityCount()); + + clkrow.setValue("data_dump", clk.toString()); + clkDao.insert(clkrow); + + UserMappingDao clkMapDAO = RTE.getMappingDao(ClkExtRel); + + SatRowsToMap.clear(); + + for (final GnssMeasurement g : gm) { + String con = SatType.get(g.getConstellationType()); + String hashkey = con + g.getSvid(); + + SimpleAttributesDao satDao = RTE.getSimpleAttributesDao(satTblName); + SimpleAttributesRow satrow = satDao.newRow(); + + satrow.setValue("svid", g.getSvid()); + satrow.setValue("constellation", con); + satrow.setValue("cn0", (double) g.getCn0DbHz()); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + if (g.hasAutomaticGainControlLevelDb()) { + satrow.setValue("agc", g.getAutomaticGainControlLevelDb()); + satrow.setValue("has_agc", 1); + } else { + satrow.setValue("agc", 0); + satrow.setValue("has_agc", 0); + } + } else { + satrow.setValue("agc", (double) 0.0); + satrow.setValue("has_agc", 0); + } + satrow.setValue("sync_state_flags", g.getState()); + satrow.setValue("sync_state_txt", " "); + satrow.setValue("sat_time_nanos", (double) g.getReceivedSvTimeNanos()); + satrow.setValue("sat_time_1sigma_nanos", (double) g.getReceivedSvTimeUncertaintyNanos()); + satrow.setValue("rcvr_time_offset_nanos", (double) g.getTimeOffsetNanos()); + satrow.setValue("multipath", g.getMultipathIndicator()); + if (g.hasCarrierFrequencyHz()) { + satrow.setValue("carrier_freq_hz", (double) g.getCarrierFrequencyHz()); + satrow.setValue("has_carrier_freq", 1); + } else { + satrow.setValue("carrier_freq_hz", (double) 0.0); + satrow.setValue("has_carrier_freq", 0); + } + satrow.setValue("accum_delta_range", (double) g.getAccumulatedDeltaRangeMeters()); + satrow.setValue("accum_delta_range_1sigma", (double) g.getAccumulatedDeltaRangeUncertaintyMeters()); + satrow.setValue("accum_delta_range_state_flags", g.getAccumulatedDeltaRangeState()); + satrow.setValue("accum_delta_range_state_txt", " "); + satrow.setValue("pseudorange_rate_mps", (double) g.getPseudorangeRateMetersPerSecond()); + satrow.setValue("pseudorange_rate_1sigma", (double) g.getPseudorangeRateUncertaintyMetersPerSecond()); + + if (SatStatus.containsKey(hashkey)) { + satrow.setValue("in_fix", SatStatus.get(hashkey).in_fix ? 0 : 1); + + satrow.setValue("has_almanac", SatStatus.get(hashkey).has_almanac ? 0 : 1); + satrow.setValue("has_ephemeris", SatStatus.get(hashkey).has_ephemeris ? 0 : 1); + satrow.setValue("has_carrier_freq", SatStatus.get(hashkey).has_carrier_freq ? 0 : 1); + + satrow.setValue("elevation_deg", (double) SatStatus.get(hashkey).elevation_deg); + satrow.setValue("azimuth_deg", (double) SatStatus.get(hashkey).azimuth_deg); + } else { + satrow.setValue("in_fix", 0); + + satrow.setValue("has_almanac", 0); + satrow.setValue("has_ephemeris", 0); + satrow.setValue("has_carrier_freq", 0); + + satrow.setValue("elevation_deg", 0.0); + satrow.setValue("azimuth_deg", 0.0); + } + + satrow.setValue("data_dump", g.toString()); + satDao.insert(satrow); + + UserMappingRow clkmaprow = clkMapDAO.newRow(); + clkmaprow.setBaseId(satrow.getId()); + clkmaprow.setRelatedId(clkrow.getId()); + clkMapDAO.create(clkmaprow); + + SatInfo.put(hashkey, g); + SatRowsToMap.put(hashkey, satrow.getId()); + } + } catch (Exception e) { + e.printStackTrace(); + } + }); + } + } + + public void onSensorUpdated(final SensorEvent event) { + if (ready.get() && (handler != null) && (event != null)) { + handler.post(() -> { + if (event.sensor.getType() == Sensor.TYPE_MAGNETIC_FIELD_UNCALIBRATED) { + float[] values = event.values; + if ((values != null) && (values.length >= 3)) { + float x = values[0]; + float y = values[1]; + float z = values[2]; + Log.d(TAG,"Magnetic sensor detects "+Float.toString(x)+","+Float.toString(y)+","+Float.toString(z)+"μT"); + //TODO if we want these persisted, then this is where they would be added + } + } + }); + } + } + + public void onLocationChanged(final Location loc) { + if (ready.get() && (handler != null)) { + handler.post(() -> { + try { + HashMap maprows = (HashMap) SatRowsToMap.clone(); + SatRowsToMap.clear(); + + HashMap locData = new HashMap() { + { + put("Lat", String.valueOf(loc.getLatitude())); + put("Lon", String.valueOf(loc.getLongitude())); + put("Alt", String.valueOf(loc.getAltitude())); + put("Provider", String.valueOf(loc.getProvider())); + put("Time", String.valueOf(loc.getTime())); + put("FixSatCount", String.valueOf(loc.getExtras().getInt("satellites"))); + put("HasRadialAccuracy", String.valueOf(loc.hasAccuracy())); + put("RadialAccuracy", String.valueOf(loc.getAccuracy())); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + put("HasVerticalAccuracy", String.valueOf(loc.hasVerticalAccuracy())); + put("VerticalAccuracy", String.valueOf(loc.getVerticalAccuracyMeters())); + } + } + }; + + if (GPSgpkg != null) { + FeatureDao featDao = GPSgpkg.getFeatureDao(PtsTableName); + FeatureRow frow = featDao.newRow(); + UserMappingDao satMapDAO = RTE.getMappingDao(SatExtRel); + + Point fix = new Point(loc.getLongitude(), loc.getLatitude(), loc.getAltitude()); + + GeoPackageGeometryData geomData = new GeoPackageGeometryData(WGS84_SRS); + geomData.setGeometry(fix); + + frow.setGeometry(geomData); + + frow.setValue("Lat", (double) loc.getLatitude()); + frow.setValue("Lon", (double) loc.getLongitude()); + frow.setValue("Alt", (double) loc.getAltitude()); + frow.setValue("Provider", loc.getProvider()); + frow.setValue("GPSTime", loc.getTime()); + frow.setValue("FixSatCount", loc.getExtras().getInt("satellites")); + if (loc.hasAccuracy()) { + frow.setValue("RadialAccuracy", (double) loc.getAccuracy()); + frow.setValue("HasRadialAccuracy", 1); + } else { + frow.setValue("RadialAccuracy", (double) 0.0); + frow.setValue("HasRadialAccuracy", 0); + } + + if (loc.hasSpeed()) { + frow.setValue("Speed", (double) loc.getAccuracy()); + frow.setValue("HasSpeed", 1); + } else { + frow.setValue("Speed", (double) 0.0); + frow.setValue("HasSpeed", 0); + } + + if (loc.hasBearing()) { + frow.setValue("Bearing", (double) loc.getAccuracy()); + frow.setValue("HasBearing", 1); + } else { + frow.setValue("Bearing", (double) 0.0); + frow.setValue("HasBearing", 0); + } + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + frow.setValue("SysTime", now().toString()); + + if (loc.hasVerticalAccuracy()) { + frow.setValue("VerticalAccuracy", (double) loc.getVerticalAccuracyMeters()); + frow.setValue("HasVerticalAccuracy", 1); + } else { + frow.setValue("VerticalAccuracy", (double) 0.0); + frow.setValue("HasVerticalAccuracy", 0); + } + + if (loc.hasSpeedAccuracy()) { + frow.setValue("SpeedAccuracy", (double) loc.getAccuracy()); + frow.setValue("HasSpeedAccuracy", 1); + } else { + frow.setValue("SpeedAccuracy", (double) 0.0); + frow.setValue("HasSpeedAccuracy", 0); + } + + if (loc.hasBearingAccuracy()) { + frow.setValue("BearingAccuracy", (double) loc.getAccuracy()); + frow.setValue("HasBearingAccuracy", 1); + } else { + frow.setValue("BearingAccuracy", (double) 0.0); + frow.setValue("HasBearingAccuracy", 0); + } + } else { + Date currentTime = Calendar.getInstance().getTime(); + SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ", Locale.US); + frow.setValue("SysTime", df.format(currentTime)); + frow.setValue("HasVerticalAccuracy", 0); + frow.setValue("VerticalAccuracy", (double) 0.0); + } + + frow.setValue("data_dump", loc.toString() + " " + loc.describeContents()); + + featDao.insert(frow); + + for (long id : maprows.values()) { + UserMappingRow satmaprow = satMapDAO.newRow(); + satmaprow.setBaseId(frow.getId()); + satmaprow.setRelatedId(id); + satMapDAO.create(satmaprow); + } + + // update feature table bounding box if necessary + boolean dirty = false; + BoundingBox bb = featDao.getBoundingBox(); + if (loc.getLatitude() < bb.getMinLatitude()) { + bb.setMinLatitude(loc.getLatitude()); + dirty = true; + } + if (loc.getLatitude() > bb.getMaxLatitude()) { + bb.setMaxLatitude(loc.getLatitude()); + dirty = true; + } + + if (loc.getLongitude() < bb.getMinLongitude()) + bb.setMinLongitude(loc.getLongitude()); + if (loc.getLongitude() > bb.getMaxLongitude()) + bb.setMaxLongitude(loc.getLongitude()); + + if (dirty) { + String bbsql = "UPDATE gpkg_contents SET " + + " min_x = " + bb.getMinLongitude() + + ", max_x = " + bb.getMaxLongitude() + + ", min_y = " + bb.getMinLatitude() + + ", max_y = " + bb.getMaxLatitude() + + " WHERE table_name = '" + PtsTableName + "';"; + GPSgpkg.execSQL(bbsql); + } + } + } catch (Exception e) { + e.printStackTrace(); + } + }); + } + } + + private GeoPackage setupGpkgDB(Context context, String folder, String file) throws GeoPackageException, SQLException { + GpkgFilename = folder + "/" + file + "-" + fmtFilenameFriendlyTime.format(System.currentTimeMillis()) + ".gpkg"; + + GeoPackageManager gpkgMgr = GeoPackageFactory.getManager(context); + if (!gpkgMgr.exists(GpkgFilename)) { + try { + gpkgMgr.create(GpkgFilename); + } catch (Exception e) { + e.printStackTrace(); + return null; + } + + } + gpkg = gpkgMgr.open(GpkgFilename, true); + if (gpkg == null) { + throw new GeoPackageException("Failed to open GeoPackage database " + GpkgFilename); + } + + // create SRS & feature tables + SpatialReferenceSystemDao srsDao = gpkg.getSpatialReferenceSystemDao(); + + SpatialReferenceSystem srs = srsDao.getOrCreateCode(ProjectionConstants.AUTHORITY_EPSG, (long) ProjectionConstants.EPSG_WORLD_GEODETIC_SYSTEM); + + gpkg.createGeometryColumnsTable(); + + PtsTable = createObservationTable(gpkg, srs, PtsTableName, GeometryType.POINT); + String bbsql = "UPDATE gpkg_contents SET min_x = 180.0, max_x = -180.0, min_y = 90.0, max_y = -90.0 WHERE table_name = '" + PtsTableName + "';"; + gpkg.execSQL(bbsql); + + Contents contents = new Contents(); + RTE = new RelatedTablesExtension(gpkg); + + SatTable = createSatelliteTable(contents, RTE, srs, satTblName, satmapTblName, PtsTableName); + ClkTable = createClockTable(contents, RTE, srs, clkTblName, clkmapTblName, satTblName); + + MotionTable = createMotionTable(contents, RTE, srs, motionTblName, motionmapTblName, PtsTableName); + + return gpkg; + } + + + private UserTable createObservationTable(GeoPackage geoPackage, SpatialReferenceSystem srs, String tableName, GeometryType type) throws SQLException { + ContentsDao contentsDao = geoPackage.getContentsDao(); + + Contents contents = new Contents(); + contents.setTableName(tableName); + contents.setDataType(ContentsDataType.FEATURES); + contents.setIdentifier(tableName); + contents.setDescription(tableName); + contents.setSrs(srs); + + int colNum = 0; + List tblcols = new LinkedList<>(); + tblcols.add(FeatureColumn.createPrimaryKeyColumn(colNum++, ID_COLUMN)); + tblcols.add(FeatureColumn.createGeometryColumn(colNum++, GEOMETRY_COLUMN, GeometryType.POINT, false, null)); + tblcols.add(FeatureColumn.createColumn(colNum++, "SysTime", DATETIME, false, null)); + tblcols.add(FeatureColumn.createColumn(colNum++, "Lat", REAL, false, null)); + tblcols.add(FeatureColumn.createColumn(colNum++, "Lon", REAL, false, null)); + tblcols.add(FeatureColumn.createColumn(colNum++, "Alt", REAL, false, null)); + tblcols.add(FeatureColumn.createColumn(colNum++, "Provider", TEXT, false, null)); + tblcols.add(FeatureColumn.createColumn(colNum++, "GPSTime", INTEGER, false, null)); + tblcols.add(FeatureColumn.createColumn(colNum++, "FixSatCount", INTEGER, false, null)); + tblcols.add(FeatureColumn.createColumn(colNum++, "HasRadialAccuracy", INTEGER, false, null)); + tblcols.add(FeatureColumn.createColumn(colNum++, "HasVerticalAccuracy", INTEGER, false, null)); + tblcols.add(FeatureColumn.createColumn(colNum++, "RadialAccuracy", REAL, false, null)); + tblcols.add(FeatureColumn.createColumn(colNum++, "VerticalAccuracy", REAL, false, null)); + + tblcols.add(FeatureColumn.createColumn(colNum++, "ElapsedRealtimeNanos", REAL, false, null)); + + tblcols.add(FeatureColumn.createColumn(colNum++, "HasSpeed", INTEGER, false, null)); + tblcols.add(FeatureColumn.createColumn(colNum++, "HasSpeedAccuracy", INTEGER, false, null)); + tblcols.add(FeatureColumn.createColumn(colNum++, "Speed", REAL, false, null)); + tblcols.add(FeatureColumn.createColumn(colNum++, "SpeedAccuracy", REAL, false, null)); + + tblcols.add(FeatureColumn.createColumn(colNum++, "HasBearing", INTEGER, false, null)); + tblcols.add(FeatureColumn.createColumn(colNum++, "HasBearingAccuracy", INTEGER, false, null)); + tblcols.add(FeatureColumn.createColumn(colNum++, "Bearing", REAL, false, null)); + tblcols.add(FeatureColumn.createColumn(colNum++, "BearingAccuracy", REAL, false, null)); + + tblcols.add(FeatureColumn.createColumn(colNum++, "data_dump", TEXT, false, null)); + + FeatureTable table = new FeatureTable(tableName, tblcols); + geoPackage.createFeatureTable(table); + + contentsDao.create(contents); + + GeometryColumnsDao geometryColumnsDao = geoPackage.getGeometryColumnsDao(); + + GeometryColumns geometryColumns = new GeometryColumns(); + geometryColumns.setContents(contents); + geometryColumns.setColumnName(GEOMETRY_COLUMN); + geometryColumns.setGeometryType(type); + geometryColumns.setSrs(srs); + geometryColumns.setZ((byte) 0); + geometryColumns.setM((byte) 0); + geometryColumnsDao.create(geometryColumns); + + return (table); + } + + private UserTable createSatelliteTable(Contents contents, RelatedTablesExtension rte, SpatialReferenceSystem srs, String tableName, String mapTblName, String baseTblName) { + contents.setTableName(tableName); + contents.setDataType(ContentsDataType.FEATURES); + contents.setIdentifier(tableName); + contents.setDescription(tableName); + contents.setSrs(srs); + + int colNum = 1; + List tblcols = new LinkedList<>(); +// tblcols.add(UserCustomColumn.createPrimaryKeyColumn(colNum++, ID_COLUMN)); + // Dublin Core metadata descriptor profile +// tblcols.add(UserCustomColumn.createColumn(colNum++, DublinCoreType.DATE.getName(), GeoPackageDataType.DATETIME, false, null)); +// tblcols.add(FeatureColumn.createColumn(colNum++, DublinCoreType.TITLE.getName(), GeoPackageDataType.TEXT, false, null)); +// tblcols.add(FeatureColumn.createColumn(colNum++, DublinCoreType.SOURCE.getName(), GeoPackageDataType.TEXT, false, null)); +// tblcols.add(FeatureColumn.createColumn(colNum++, DublinCoreType.DESCRIPTION.getName(), GeoPackageDataType.TEXT, false, null)); + + // android GNSS measurements + tblcols.add(UserCustomColumn.createColumn(colNum++, "svid", INTEGER, true, null)); + tblcols.add(UserCustomColumn.createColumn(colNum++, "constellation", TEXT, true, null)); + tblcols.add(UserCustomColumn.createColumn(colNum++, "cn0", REAL, true, null)); + tblcols.add(UserCustomColumn.createColumn(colNum++, "agc", REAL, true, null)); + tblcols.add(UserCustomColumn.createColumn(colNum++, "has_agc", INTEGER, true, null)); + tblcols.add(UserCustomColumn.createColumn(colNum++, "in_fix", INTEGER, true, null)); + + tblcols.add(UserCustomColumn.createColumn(colNum++, "sync_state_flags", INTEGER, true, null)); + tblcols.add(UserCustomColumn.createColumn(colNum++, "sync_state_txt", TEXT, true, null)); + tblcols.add(UserCustomColumn.createColumn(colNum++, "sat_time_nanos", REAL, true, null)); + tblcols.add(UserCustomColumn.createColumn(colNum++, "sat_time_1sigma_nanos", REAL, true, null)); + tblcols.add(UserCustomColumn.createColumn(colNum++, "rcvr_time_offset_nanos", REAL, true, null)); + tblcols.add(UserCustomColumn.createColumn(colNum++, "multipath", INTEGER, true, null)); + + tblcols.add(UserCustomColumn.createColumn(colNum++, "has_carrier_freq", INTEGER, true, null)); + tblcols.add(UserCustomColumn.createColumn(colNum++, "carrier_freq_hz", REAL, true, null)); + tblcols.add(UserCustomColumn.createColumn(colNum++, "accum_delta_range", REAL, true, null)); + tblcols.add(UserCustomColumn.createColumn(colNum++, "accum_delta_range_1sigma", REAL, true, null)); + tblcols.add(UserCustomColumn.createColumn(colNum++, "accum_delta_range_state_flags", INTEGER, true, null)); + tblcols.add(UserCustomColumn.createColumn(colNum++, "accum_delta_range_state_txt", TEXT, true, null)); + tblcols.add(UserCustomColumn.createColumn(colNum++, "pseudorange_rate_mps", REAL, true, null)); + tblcols.add(UserCustomColumn.createColumn(colNum++, "pseudorange_rate_1sigma", REAL, true, null)); + + tblcols.add(UserCustomColumn.createColumn(colNum++, "has_ephemeris", INTEGER, true, null)); + tblcols.add(UserCustomColumn.createColumn(colNum++, "has_almanac", INTEGER, true, null)); + tblcols.add(UserCustomColumn.createColumn(colNum++, "azimuth_deg", REAL, true, null)); + tblcols.add(UserCustomColumn.createColumn(colNum++, "elevation_deg", REAL, true, null)); + + tblcols.add(UserCustomColumn.createColumn(colNum++, "data_dump", TEXT, true, null)); + + SimpleAttributesTable table = SimpleAttributesTable.create(tableName, tblcols); + + UserMappingTable mapTbl = UserMappingTable.create(mapTblName); + SatExtRel = rte.addSimpleAttributesRelationship(baseTblName, table, mapTbl); + + return (table); + } + + + private UserTable createClockTable(Contents contents, RelatedTablesExtension rte, SpatialReferenceSystem srs, String tableName, String mapTblName, String baseTblName) { + contents.setTableName(tableName); + contents.setDataType(ContentsDataType.FEATURES); + contents.setIdentifier(tableName); + contents.setDescription(tableName); + contents.setSrs(srs); + + int colNum = 1; + List tblcols = new LinkedList<>(); +// tblcols.add(UserCustomColumn.createPrimaryKeyColumn(colNum++, ID_COLUMN)); + // Dublin Core metadata descriptor profile +// tblcols.add(UserCustomColumn.createColumn(colNum++, DublinCoreType.DATE.getName(), DATETIME, false, null)); +// tblcols.add(FeatureColumn.createColumn(colNum++, DublinCoreType.TITLE.getName(), TEXT, false, null)); +// tblcols.add(FeatureColumn.createColumn(colNum++, DublinCoreType.SOURCE.getName(), TEXT, false, null)); +// tblcols.add(FeatureColumn.createColumn(colNum++, DublinCoreType.DESCRIPTION.getName(), TEXT, false, null)); + + // android GNSS measurements + tblcols.add(UserCustomColumn.createColumn(colNum++, "time_nanos", REAL, true, null)); + tblcols.add(UserCustomColumn.createColumn(colNum++, "time_uncertainty_nanos", REAL, true, null)); + tblcols.add(UserCustomColumn.createColumn(colNum++, "has_time_uncertainty_nanos", INTEGER, true, null)); + tblcols.add(UserCustomColumn.createColumn(colNum++, "bias_nanos", REAL, true, null)); + tblcols.add(UserCustomColumn.createColumn(colNum++, "has_bias_nanos", INTEGER, true, null)); + tblcols.add(UserCustomColumn.createColumn(colNum++, "bias_uncertainty_nanos", REAL, true, null)); + tblcols.add(UserCustomColumn.createColumn(colNum++, "has_bias_uncertainty_nanos", INTEGER, true, null)); + tblcols.add(UserCustomColumn.createColumn(colNum++, "full_bias_nanos", INTEGER, true, null)); + tblcols.add(UserCustomColumn.createColumn(colNum++, "has_full_bias_nanos", INTEGER, true, null)); + + tblcols.add(UserCustomColumn.createColumn(colNum++, "drift_nanos_per_sec", REAL, true, null)); + tblcols.add(UserCustomColumn.createColumn(colNum++, "has_drift_nanos_per_sec", INTEGER, true, null)); + tblcols.add(UserCustomColumn.createColumn(colNum++, "drift_uncertainty_nps", REAL, true, null)); + tblcols.add(UserCustomColumn.createColumn(colNum++, "has_drift_uncertainty_nps", INTEGER, true, null)); + tblcols.add(UserCustomColumn.createColumn(colNum++, "hw_clock_discontinuity_count", INTEGER, true, null)); + tblcols.add(UserCustomColumn.createColumn(colNum++, "leap_second", INTEGER, true, null)); + tblcols.add(UserCustomColumn.createColumn(colNum++, "has_leap_second", INTEGER, true, null)); + + tblcols.add(UserCustomColumn.createColumn(colNum++, "data_dump", TEXT, true, null)); + + SimpleAttributesTable table = SimpleAttributesTable.create(tableName, tblcols); + + UserMappingTable mapTbl = UserMappingTable.create(mapTblName); + ClkExtRel = rte.addSimpleAttributesRelationship(baseTblName, table, mapTbl); + + return (table); + } + + private UserTable createMotionTable(Contents contents, RelatedTablesExtension rte, SpatialReferenceSystem srs, String tableName, String mapTblName, String baseTblName) { + contents.setTableName(tableName); + contents.setDataType(ContentsDataType.FEATURES); + contents.setIdentifier(tableName); + contents.setDescription(tableName); + contents.setSrs(srs); + + int colNum = 1; + List tblcols = new LinkedList<>(); +// tblcols.add(UserCustomColumn.createPrimaryKeyColumn(colNum++, ID_COLUMN)); + // Dublin Core metadata descriptor profile +// tblcols.add(UserCustomColumn.createColumn(colNum++, DublinCoreType.DATE.getName(), DATETIME, false, null)); +// tblcols.add(FeatureColumn.createColumn(colNum++, DublinCoreType.TITLE.getName(), TEXT, false, null)); +// tblcols.add(FeatureColumn.createColumn(colNum++, DublinCoreType.SOURCE.getName(), TEXT, false, null)); +// tblcols.add(FeatureColumn.createColumn(colNum++, DublinCoreType.DESCRIPTION.getName(), TEXT, false, null)); + + // android inertial sensor measurements + tblcols.add(UserCustomColumn.createColumn(colNum++, "accel_x", REAL, true, null)); + tblcols.add(UserCustomColumn.createColumn(colNum++, "accel_y", REAL, true, null)); + tblcols.add(UserCustomColumn.createColumn(colNum++, "accel_z", REAL, true, null)); + + tblcols.add(UserCustomColumn.createColumn(colNum++, "linear_accel_x", REAL, true, null)); + tblcols.add(UserCustomColumn.createColumn(colNum++, "linear_accel_y", REAL, true, null)); + tblcols.add(UserCustomColumn.createColumn(colNum++, "linear_accel_z", REAL, true, null)); + + tblcols.add(UserCustomColumn.createColumn(colNum++, "mag_x", REAL, true, null)); + tblcols.add(UserCustomColumn.createColumn(colNum++, "mag_y", REAL, true, null)); + tblcols.add(UserCustomColumn.createColumn(colNum++, "mag_z", REAL, true, null)); + + tblcols.add(UserCustomColumn.createColumn(colNum++, "gyro_x", REAL, true, null)); + tblcols.add(UserCustomColumn.createColumn(colNum++, "gyro_y", REAL, true, null)); + tblcols.add(UserCustomColumn.createColumn(colNum++, "gyro_z", REAL, true, null)); + + tblcols.add(UserCustomColumn.createColumn(colNum++, "gravity_x", REAL, true, null)); + tblcols.add(UserCustomColumn.createColumn(colNum++, "gravity_y", REAL, true, null)); + tblcols.add(UserCustomColumn.createColumn(colNum++, "gravity_z", REAL, true, null)); + + tblcols.add(UserCustomColumn.createColumn(colNum++, "rot_vec_x", REAL, true, null)); + tblcols.add(UserCustomColumn.createColumn(colNum++, "rot_vec_y", REAL, true, null)); + tblcols.add(UserCustomColumn.createColumn(colNum++, "rot_vec_z", REAL, true, null)); + tblcols.add(UserCustomColumn.createColumn(colNum++, "rot_vec_cos", REAL, true, null)); + tblcols.add(UserCustomColumn.createColumn(colNum++, "rot_vec_hdg_acc", REAL, true, null)); + + tblcols.add(UserCustomColumn.createColumn(colNum++, "baro", REAL, true, null)); + tblcols.add(UserCustomColumn.createColumn(colNum++, "humidity", REAL, true, null)); + tblcols.add(UserCustomColumn.createColumn(colNum++, "temp", REAL, true, null)); + + tblcols.add(UserCustomColumn.createColumn(colNum++, "lux", REAL, true, null)); + tblcols.add(UserCustomColumn.createColumn(colNum++, "prox", REAL, true, null)); + + tblcols.add(UserCustomColumn.createColumn(colNum++, "stationary", INTEGER, true, null)); + tblcols.add(UserCustomColumn.createColumn(colNum++, "motion", INTEGER, true, null)); + + tblcols.add(UserCustomColumn.createColumn(colNum++, "data_dump", TEXT, true, null)); + + SimpleAttributesTable table = SimpleAttributesTable.create(tableName, tblcols); + + UserMappingTable mapTbl = UserMappingTable.create(mapTblName); + ClkExtRel = rte.addSimpleAttributesRelationship(baseTblName, table, mapTbl); + + return (table); + } +} diff --git a/torgi/src/main/java/org/sofwerx/torgi/service/SensorService.java b/torgi/src/main/java/org/sofwerx/torgi/service/SensorService.java new file mode 100644 index 0000000..721c4df --- /dev/null +++ b/torgi/src/main/java/org/sofwerx/torgi/service/SensorService.java @@ -0,0 +1,54 @@ +package org.sofwerx.torgi.service; + +import android.content.Context; +import android.hardware.Sensor; +import android.hardware.SensorEvent; +import android.hardware.SensorEventListener; +import android.hardware.SensorManager; + +import org.sofwerx.torgi.listener.SensorListener; + +import static android.content.Context.SENSOR_SERVICE; + +public class SensorService implements SensorEventListener { + private final static String TAG = "TORGI.SensorSvc"; + + private SensorManager sensorManager = null; + private SensorListener listener = null; + private long lastUpdate = Long.MIN_VALUE; + + private final static int SAMPLING_PERIOD_US = 1000000 * 2; //desired sensor report rate in microseconds + private final static int MAX_LATENCY_US = 1000000 * 4; //max delay between sensor reports in microseconds + private final static long MAX_REPORT_RATE = 1000l; //fastest that the listener will be provided updates in seconds + + public SensorService(Context context, SensorListener listener) { + if (context != null) { + this.listener = listener; + sensorManager = (SensorManager) context.getApplicationContext().getSystemService(SENSOR_SERVICE); + sensorManager.registerListener(this, sensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD_UNCALIBRATED), SAMPLING_PERIOD_US, MAX_LATENCY_US); + } + } + + public void setListener(SensorListener listener) { + this.listener = listener; + } + + public void shutdown() { + listener = null; + if (sensorManager != null) + sensorManager.unregisterListener(this, sensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD_UNCALIBRATED)); + } + + @Override + public void onSensorChanged(SensorEvent event) { + if ((listener != null) && (System.currentTimeMillis() > lastUpdate + MAX_REPORT_RATE)) { + listener.onSensorUpdated(event); + lastUpdate = System.currentTimeMillis(); + } + } + + @Override + public void onAccuracyChanged(Sensor sensor, int accuracy) { + //ignore + } +} diff --git a/torgi/src/main/java/org/sofwerx/torgi/service/TorgiService.java b/torgi/src/main/java/org/sofwerx/torgi/service/TorgiService.java index 878dd8d..2570a65 100644 --- a/torgi/src/main/java/org/sofwerx/torgi/service/TorgiService.java +++ b/torgi/src/main/java/org/sofwerx/torgi/service/TorgiService.java @@ -6,11 +6,9 @@ import android.app.NotificationManager; import android.app.PendingIntent; import android.app.Service; -import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; -import android.location.GnssClock; -import android.location.GnssMeasurement; +import android.hardware.SensorEvent; import android.location.GnssMeasurementsEvent; import android.location.GnssStatus; import android.location.Location; @@ -19,7 +17,6 @@ import android.os.Binder; import android.os.Build; import android.os.Bundle; -import android.os.Environment; import android.os.IBinder; import android.support.annotation.Nullable; import android.support.annotation.RequiresApi; @@ -28,82 +25,24 @@ import org.sofwerx.torgi.listener.GnssMeasurementListener; import org.sofwerx.torgi.R; -import org.sofwerx.torgi.SatStatus; - -import java.sql.SQLException; -import java.text.SimpleDateFormat; -import java.util.ArrayList; -import java.util.Calendar; -import java.util.Collection; -import java.util.Date; -import java.util.HashMap; -import java.util.LinkedList; -import java.util.List; -import java.util.Locale; - -import mil.nga.geopackage.BoundingBox; -import mil.nga.geopackage.GeoPackage; -import mil.nga.geopackage.GeoPackageException; -import mil.nga.geopackage.GeoPackageManager; -import mil.nga.geopackage.core.contents.Contents; -import mil.nga.geopackage.core.contents.ContentsDao; -import mil.nga.geopackage.core.contents.ContentsDataType; -import mil.nga.geopackage.core.srs.SpatialReferenceSystem; -import mil.nga.geopackage.core.srs.SpatialReferenceSystemDao; -import mil.nga.geopackage.extension.related.ExtendedRelation; -import mil.nga.geopackage.extension.related.RelatedTablesExtension; -import mil.nga.geopackage.extension.related.UserMappingDao; -import mil.nga.geopackage.extension.related.UserMappingRow; -import mil.nga.geopackage.extension.related.UserMappingTable; -import mil.nga.geopackage.extension.related.simple.SimpleAttributesDao; -import mil.nga.geopackage.extension.related.simple.SimpleAttributesRow; -import mil.nga.geopackage.extension.related.simple.SimpleAttributesTable; -import mil.nga.geopackage.factory.GeoPackageFactory; -import mil.nga.geopackage.features.columns.GeometryColumns; -import mil.nga.geopackage.features.columns.GeometryColumnsDao; -import mil.nga.geopackage.features.user.FeatureColumn; -import mil.nga.geopackage.features.user.FeatureDao; -import mil.nga.geopackage.features.user.FeatureRow; -import mil.nga.geopackage.features.user.FeatureTable; -import mil.nga.geopackage.geom.GeoPackageGeometryData; -import mil.nga.geopackage.user.UserTable; -import mil.nga.geopackage.user.custom.UserCustomColumn; -import mil.nga.sf.GeometryType; -import mil.nga.sf.Point; -import mil.nga.sf.proj.ProjectionConstants; +import org.sofwerx.torgi.listener.SensorListener; import static java.time.Instant.now; -import static mil.nga.geopackage.db.GeoPackageDataType.DATETIME; -import static mil.nga.geopackage.db.GeoPackageDataType.INTEGER; -import static mil.nga.geopackage.db.GeoPackageDataType.REAL; -import static mil.nga.geopackage.db.GeoPackageDataType.TEXT; /** * Torgi service handles getting information from the GPS receiver (and eventually accepting data * from other sensors as well) and then storing that data in the GeoPackage as well as making * this info available to any listening UI element. - * - * TODO this is currently on the main thread - move to a separate thread to support more responsive UI */ public class TorgiService extends Service { private final static String TAG = "TORGISvc"; private final static int TORGI_NOTIFICATION_ID = 1; private final static String NOTIFICATION_CHANNEL = "torgi_report"; public final static String ACTION_STOP = "STOP"; - private final static SimpleDateFormat fmtFilenameFriendlyTime = new SimpleDateFormat("YYYYMMdd HHmmss"); - - HashMap SatType = new HashMap() { - { - put(0, "Unknown"); - put(1, "GPS"); - put(2, "SBAS"); - put(3, "Glonass"); - put(4, "QZSS"); - put(5, "Beidou"); - put(6, "Galileo"); - - } - }; + private GNSSMeasurementService gnssMeasurementService = null; + private GeoPackageRecorder geoPackageRecorder = null; + private SensorService sensorService = null; + private Location currentLocation = null; public void setListener(GnssMeasurementListener listener) { this.listener = listener; @@ -113,284 +52,71 @@ public void setListener(GnssMeasurementListener listener) { private GnssMeasurementListener listener = null; private final IBinder mBinder = new TorgiBinder(); - final static long WGS84_SRS = 4326; - - private static final String ID_COLUMN = "id"; - private static final String GEOMETRY_COLUMN = "geom"; - - String GpkgFilename = "TORGI-GNSS"; - String GpkgFolder = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getAbsolutePath(); - - private static final String PtsTableName = "gps_observation_points"; - private static final String satTblName = "sat_data"; - private static final String clkTblName = "rcvr_clock"; - private static final String motionTblName = "motion"; - private static final String satmapTblName = PtsTableName + "_" + satTblName; - private static final String clkmapTblName = satTblName + "_" + clkTblName; - private static final String motionmapTblName = PtsTableName + "_" + motionTblName; - - private ArrayList sats = new ArrayList<>(); - private GeoPackage gpkg = null; - - HashMap SatStatus = new HashMap<>(); - HashMap SatInfo = new HashMap<>(); - HashMap SatRowsToMap = new HashMap<>(); - - GeoPackage GPSgpkg = null; - RelatedTablesExtension RTE = null; - ExtendedRelation SatExtRel = null; - ExtendedRelation ClkExtRel = null; - UserTable PtsTable = null; - UserTable SatTable = null; - UserTable ClkTable = null; - UserTable MotionTable = null; - - public String getGpkgFilename() { - return GpkgFilename; - } - - @RequiresApi(26) - private final GnssStatus.Callback StatusListener = new GnssStatus.Callback() { - public void onSatelliteStatusChanged(final GnssStatus status) { - int numSats = status.getSatelliteCount(); - - for (int i = 0; i < numSats; ++i) { - SatStatus thisSat = new SatStatus(); - thisSat.constellation = SatType.get(status.getConstellationType(i)); - thisSat.svid = status.getSvid(i); - thisSat.cn0 = status.getCn0DbHz(i); - - thisSat.has_almanac = status.hasAlmanacData(i); - thisSat.has_ephemeris = status.hasEphemerisData(i); - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - thisSat.has_carrier_freq = status.hasCarrierFrequencyHz(i); - } else { - thisSat.has_carrier_freq = false; - } - - thisSat.azimuth_deg = status.getAzimuthDegrees(i); - thisSat.elevation_deg = status.getElevationDegrees(i); - thisSat.cn0 = status.getCn0DbHz(i); - String hashkey = thisSat.constellation + status.getSvid(i); - SatStatus.put(hashkey, thisSat); - - thisSat.setUsedInFix(status.usedInFix(i)); - update(thisSat); - } - - if (listener != null) { - //TODO this is where later dynamic calculations dealing with this data should be called from - listener.onSatStatusUpdated(sats); - } - } - }; - - /** - * Updates the current list of satellite statuses; currently a bit redundant but exists to - * eventually help provide less jitter in GUI options like a ListView and to help in - * dynamic computations rather than always pulling from the GeoPackage - * @param sat - */ - private void update(SatStatus sat) { - if (sat != null) { - boolean found = false; - for (SatStatus status:sats) { - if (status.equals(sat)) { - found = true; - status.update(sat); - } - } - if (!found) - sats.add(sat); - } + public GeoPackageRecorder getGeoPackageRecorder() { + return geoPackageRecorder; } - /** - * Starts the location listener and starts saving GNSS data to the GeoPackage - */ - public void startGnssRecorder() { + public void start() { boolean permissionsPassed = ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED; permissionsPassed = permissionsPassed && ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED; if (permissionsPassed) { + if (geoPackageRecorder == null) { + geoPackageRecorder = new GeoPackageRecorder(this); + geoPackageRecorder.start(); + } + if (gnssMeasurementService == null) { + gnssMeasurementService = new GNSSMeasurementService(this); + gnssMeasurementService.start(); + } + if (sensorService == null) + sensorService = new SensorService(this, sensorListener); if (locMgr == null) { locMgr = getSystemService(LocationManager.class); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - locMgr.registerGnssMeasurementsCallback(MeasurementListener); - locMgr.registerGnssStatusCallback(StatusListener); + locMgr.registerGnssMeasurementsCallback(measurementListener); + locMgr.registerGnssStatusCallback(statusListener); } locMgr.requestLocationUpdates(LocationManager.GPS_PROVIDER, 1000, 0, locListener); + currentLocation = locMgr.getLastKnownLocation(LocationManager.GPS_PROVIDER); setForeground(); } + } + } - if (GPSgpkg == null) { - try { - GPSgpkg = setupGpkgDB(this, GpkgFolder, GpkgFilename); - } catch (SQLException e) { - e.printStackTrace(); - } - } + private final SensorListener sensorListener = new SensorListener() { + @Override + public void onSensorUpdated(SensorEvent event) { + if (geoPackageRecorder != null) + geoPackageRecorder.onSensorUpdated(event); + } + }; - List tbls = GPSgpkg.getTables(); - tbls.add(GPSgpkg.getApplicationId()); + @RequiresApi(26) + private final GnssStatus.Callback statusListener = new GnssStatus.Callback() { + public void onSatelliteStatusChanged(final GnssStatus status) { + if (geoPackageRecorder != null) + geoPackageRecorder.onSatelliteStatusChanged(status); + if (listener != null) + listener.onSatStatusUpdated(status); } - } + }; @RequiresApi(26) - private final GnssMeasurementsEvent.Callback MeasurementListener = new GnssMeasurementsEvent.Callback() { + private final GnssMeasurementsEvent.Callback measurementListener = new GnssMeasurementsEvent.Callback() { public void onGnssMeasurementsReceived(GnssMeasurementsEvent event) { - Collection gm = event.getMeasurements(); - - GnssClock clk = event.getClock(); - - SimpleAttributesDao clkDao = RTE.getSimpleAttributesDao(clkTblName); - SimpleAttributesRow clkrow = clkDao.newRow(); - - clkrow.setValue("time_nanos", (double) clk.getTimeNanos()); - if (clk.hasTimeUncertaintyNanos()) { - clkrow.setValue("time_uncertainty_nanos", (double) clk.getTimeUncertaintyNanos()); - clkrow.setValue("has_time_uncertainty_nanos", 1); - } else { - clkrow.setValue("time_uncertainty_nanos", (double) 0.0); - clkrow.setValue("has_time_uncertainty_nanos", 0); - } - - if (clk.hasBiasNanos()) { - clkrow.setValue("bias_nanos", (double) clk.getBiasNanos()); - clkrow.setValue("has_bias_nanos", 1); - } else { - clkrow.setValue("bias_nanos", (double) 0.0); - clkrow.setValue("has_bias_nanos", 0); - } - if (clk.hasFullBiasNanos()) { - clkrow.setValue("full_bias_nanos", clk.getFullBiasNanos()); - clkrow.setValue("has_full_bias_nanos", 1); - } else { - clkrow.setValue("full_bias_nanos", 0); - clkrow.setValue("has_full_bias_nanos", 0); - } - if (clk.hasBiasUncertaintyNanos()) { - clkrow.setValue("bias_uncertainty_nanos", (double) clk.getBiasUncertaintyNanos()); - clkrow.setValue("has_bias_uncertainty_nanos", 1); - } else { - clkrow.setValue("bias_uncertainty_nanos", (double) 0.0); - clkrow.setValue("has_bias_uncertainty_nanos", 0); - } - if (clk.hasDriftNanosPerSecond()) { - clkrow.setValue("drift_nanos_per_sec", (double) clk.getDriftNanosPerSecond()); - clkrow.setValue("has_drift_nanos_per_sec", 1); - } else { - clkrow.setValue("drift_nanos_per_sec", (double) 0.0); - clkrow.setValue("has_drift_nanos_per_sec", 0); - } - if (clk.hasDriftUncertaintyNanosPerSecond()) { - clkrow.setValue("drift_uncertainty_nps", (double) clk.getDriftUncertaintyNanosPerSecond()); - clkrow.setValue("has_drift_uncertainty_nps", 1); - } else { - clkrow.setValue("drift_uncertainty_nps", (double) 0.0); - clkrow.setValue("has_drift_uncertainty_nps", 0); - } - if (clk.hasLeapSecond()) { - clkrow.setValue("leap_second", clk.getLeapSecond()); - clkrow.setValue("has_leap_second", 1); - } else { - clkrow.setValue("leap_second", 0); - clkrow.setValue("has_leap_second", 0); - } - clkrow.setValue("hw_clock_discontinuity_count", clk.getHardwareClockDiscontinuityCount()); - - clkrow.setValue("data_dump", clk.toString()); - clkDao.insert(clkrow); - - UserMappingDao clkMapDAO = RTE.getMappingDao(ClkExtRel); - - SatRowsToMap.clear(); - - for(final GnssMeasurement g : gm) { - String con = SatType.get(g.getConstellationType()); - String hashkey = con + g.getSvid(); - - SimpleAttributesDao satDao = RTE.getSimpleAttributesDao(satTblName); - SimpleAttributesRow satrow = satDao.newRow(); - - satrow.setValue("svid", g.getSvid()); - satrow.setValue("constellation", con); - satrow.setValue("cn0", (double) g.getCn0DbHz()); - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - if (g.hasAutomaticGainControlLevelDb()) { - satrow.setValue("agc", g.getAutomaticGainControlLevelDb()); - satrow.setValue("has_agc", 1); - } else { - satrow.setValue("agc", 0); - satrow.setValue("has_agc", 0); - } - } else { - satrow.setValue("agc", (double) 0.0); - satrow.setValue("has_agc", 0); - } - satrow.setValue("sync_state_flags", g.getState()); - satrow.setValue("sync_state_txt", " "); - satrow.setValue("sat_time_nanos", (double) g.getReceivedSvTimeNanos()); - satrow.setValue("sat_time_1sigma_nanos",(double) g.getReceivedSvTimeUncertaintyNanos()); - satrow.setValue("rcvr_time_offset_nanos", (double) g.getTimeOffsetNanos()); - satrow.setValue("multipath", g.getMultipathIndicator()); - if (g.hasCarrierFrequencyHz()) { - satrow.setValue("carrier_freq_hz", (double) g.getCarrierFrequencyHz()); - satrow.setValue("has_carrier_freq", 1); - } else { - satrow.setValue("carrier_freq_hz", (double) 0.0); - satrow.setValue("has_carrier_freq", 0); - } - satrow.setValue("accum_delta_range", (double) g.getAccumulatedDeltaRangeMeters()); - satrow.setValue("accum_delta_range_1sigma", (double) g.getAccumulatedDeltaRangeUncertaintyMeters()); - satrow.setValue("accum_delta_range_state_flags", g.getAccumulatedDeltaRangeState()); - satrow.setValue("accum_delta_range_state_txt", " "); - satrow.setValue("pseudorange_rate_mps", (double) g.getPseudorangeRateMetersPerSecond()); - satrow.setValue("pseudorange_rate_1sigma", (double) g.getPseudorangeRateUncertaintyMetersPerSecond()); - - if (SatStatus.containsKey(hashkey)) { - satrow.setValue("in_fix", SatStatus.get(hashkey).in_fix ? 0 : 1); - - satrow.setValue("has_almanac", SatStatus.get(hashkey).has_almanac ? 0 : 1); - satrow.setValue("has_ephemeris", SatStatus.get(hashkey).has_ephemeris ? 0 : 1); - satrow.setValue("has_carrier_freq", SatStatus.get(hashkey).has_carrier_freq ? 0 : 1); - - satrow.setValue("elevation_deg", (double) SatStatus.get(hashkey).elevation_deg); - satrow.setValue("azimuth_deg", (double) SatStatus.get(hashkey).azimuth_deg); - } else { - satrow.setValue("in_fix", 0); - - satrow.setValue("has_almanac", 0); - satrow.setValue("has_ephemeris", 0); - satrow.setValue("has_carrier_freq", 0); - - satrow.setValue("elevation_deg", 0.0); - satrow.setValue("azimuth_deg", 0.0); - } - - satrow.setValue("data_dump", g.toString()); - satDao.insert(satrow); - - UserMappingRow clkmaprow = clkMapDAO.newRow(); - clkmaprow.setBaseId(satrow.getId()); - clkmaprow.setRelatedId(clkrow.getId()); - clkMapDAO.create(clkmaprow); - - SatInfo.put(hashkey, g); - SatRowsToMap.put(hashkey, satrow.getId()); - } - if (listener != null) { - //TODO this is where later dynamic calculations for this data should be called from - listener.onGnssMeasurementReceived(gm); - } + if (geoPackageRecorder != null) + geoPackageRecorder.onGnssMeasurementsReceived(event); + if (gnssMeasurementService != null) + gnssMeasurementService.onGnssMeasurementsReceived(currentLocation,event); + if (listener != null) + listener.onGnssMeasurementReceived(event); } }; - LocationListener locListener = new LocationListener() { + private LocationListener locListener = new LocationListener() { public void onProviderEnabled(String provider) { if (listener != null) listener.onProviderChanged(provider,true); @@ -404,250 +130,14 @@ public void onProviderDisabled(String provider) { public void onStatusChanged(final String provider, int status, Bundle extras) {} public void onLocationChanged(final Location loc) { - HashMap maprows = (HashMap)SatRowsToMap.clone(); - SatRowsToMap.clear(); - - HashMap locData = new HashMap() { - { - put("Lat", String.valueOf(loc.getLatitude())); - put("Lon", String.valueOf(loc.getLongitude())); - put("Alt", String.valueOf(loc.getAltitude())); - put("Provider", String.valueOf(loc.getProvider())); - put("Time", String.valueOf(loc.getTime())); - put("FixSatCount", String.valueOf(loc.getExtras().getInt("satellites"))); - put("HasRadialAccuracy", String.valueOf(loc.hasAccuracy())); - put("RadialAccuracy", String.valueOf(loc.getAccuracy())); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - put("HasVerticalAccuracy", String.valueOf(loc.hasVerticalAccuracy())); - put("VerticalAccuracy", String.valueOf(loc.getVerticalAccuracyMeters())); - } - } - }; - - if (listener != null) { - //TODO this is where later dynamic calculations dealing with this data should be called from + currentLocation = loc; + if (geoPackageRecorder != null) + geoPackageRecorder.onLocationChanged(loc); + if (listener != null) listener.onLocationChanged(loc); - } - - if (GPSgpkg != null) { - FeatureDao featDao = GPSgpkg.getFeatureDao(PtsTableName); - FeatureRow frow = featDao.newRow(); - UserMappingDao satMapDAO = RTE.getMappingDao(SatExtRel); - - Point fix = new Point(loc.getLongitude(), loc.getLatitude(), loc.getAltitude()); - - GeoPackageGeometryData geomData = new GeoPackageGeometryData(WGS84_SRS); - geomData.setGeometry(fix); - - frow.setGeometry(geomData); - - frow.setValue("Lat", (double) loc.getLatitude()); - frow.setValue("Lon", (double) loc.getLongitude()); - frow.setValue("Alt", (double) loc.getAltitude()); - frow.setValue("Provider", loc.getProvider()); - frow.setValue("GPSTime", loc.getTime()); - frow.setValue("FixSatCount", loc.getExtras().getInt("satellites")); - if (loc.hasAccuracy()) { - frow.setValue("RadialAccuracy", (double) loc.getAccuracy()); - frow.setValue("HasRadialAccuracy", 1); - } else { - frow.setValue("RadialAccuracy", (double) 0.0); - frow.setValue("HasRadialAccuracy", 0); - } - - if (loc.hasSpeed()) { - frow.setValue("Speed", (double) loc.getAccuracy()); - frow.setValue("HasSpeed", 1); - } else { - frow.setValue("Speed", (double) 0.0); - frow.setValue("HasSpeed", 0); - } - - if (loc.hasBearing()) { - frow.setValue("Bearing", (double) loc.getAccuracy()); - frow.setValue("HasBearing", 1); - } else { - frow.setValue("Bearing", (double) 0.0); - frow.setValue("HasBearing", 0); - } - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - frow.setValue("SysTime", now().toString()); - - if (loc.hasVerticalAccuracy()) { - frow.setValue("VerticalAccuracy", (double) loc.getVerticalAccuracyMeters()); - frow.setValue("HasVerticalAccuracy", 1); - } else { - frow.setValue("VerticalAccuracy", (double) 0.0); - frow.setValue("HasVerticalAccuracy", 0); - } - - if (loc.hasSpeedAccuracy()) { - frow.setValue("SpeedAccuracy", (double) loc.getAccuracy()); - frow.setValue("HasSpeedAccuracy", 1); - } else { - frow.setValue("SpeedAccuracy", (double) 0.0); - frow.setValue("HasSpeedAccuracy", 0); - } - - if (loc.hasBearingAccuracy()) { - frow.setValue("BearingAccuracy", (double) loc.getAccuracy()); - frow.setValue("HasBearingAccuracy", 1); - } else { - frow.setValue("BearingAccuracy", (double) 0.0); - frow.setValue("HasBearingAccuracy", 0); - } - } else { - Date currentTime = Calendar.getInstance().getTime(); - SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ", Locale.US); - frow.setValue("SysTime", df.format(currentTime)); - frow.setValue("HasVerticalAccuracy", 0); - frow.setValue("VerticalAccuracy", (double) 0.0); - } - - frow.setValue("data_dump", loc.toString() + " " + loc.describeContents()); - - featDao.insert(frow); - - for (long id : maprows.values()) { - UserMappingRow satmaprow = satMapDAO.newRow(); - satmaprow.setBaseId(frow.getId()); - satmaprow.setRelatedId(id); - satMapDAO.create(satmaprow); - } - - // update feature table bounding box if necessary - boolean dirty = false; - BoundingBox bb = featDao.getBoundingBox(); - if (loc.getLatitude() < bb.getMinLatitude()) { - bb.setMinLatitude(loc.getLatitude()); - dirty = true; - } - if (loc.getLatitude() > bb.getMaxLatitude()) { - bb.setMaxLatitude(loc.getLatitude()); - dirty = true; - } - - if (loc.getLongitude() < bb.getMinLongitude()) { - bb.setMinLongitude(loc.getLongitude()); - } - if (loc.getLongitude() > bb.getMaxLongitude()) { - bb.setMaxLongitude(loc.getLongitude()); - } - - if (dirty) { - String bbsql = "UPDATE gpkg_contents SET " + - " min_x = " + bb.getMinLongitude() + - ", max_x = " + bb.getMaxLongitude() + - ", min_y = " + bb.getMinLatitude() + - ", max_y = " + bb.getMaxLatitude() + - " WHERE table_name = '" + PtsTableName + "';"; - GPSgpkg.execSQL(bbsql); - } - } } }; - private GeoPackage setupGpkgDB(Context context, String folder, String file) throws GeoPackageException, SQLException { - GpkgFilename = folder + "/" + file + "-" + fmtFilenameFriendlyTime.format(System.currentTimeMillis()) + ".gpkg"; - - GeoPackageManager gpkgMgr = GeoPackageFactory.getManager(context); - if (!gpkgMgr.exists(GpkgFilename)) { - try { - gpkgMgr.create(GpkgFilename); - } catch (Exception e) { - e.printStackTrace(); - return null; - } - - } - gpkg = gpkgMgr.open(GpkgFilename, true); - if (gpkg == null) { - throw new GeoPackageException("Failed to open GeoPackage database " + GpkgFilename); - } - - // create SRS & feature tables - SpatialReferenceSystemDao srsDao = gpkg.getSpatialReferenceSystemDao(); - - SpatialReferenceSystem srs = srsDao.getOrCreateCode(ProjectionConstants.AUTHORITY_EPSG, (long) ProjectionConstants.EPSG_WORLD_GEODETIC_SYSTEM); - - gpkg.createGeometryColumnsTable(); - - PtsTable = createObservationTable(gpkg, srs, PtsTableName, GeometryType.POINT); - String bbsql = "UPDATE gpkg_contents SET min_x = 180.0, max_x = -180.0, min_y = 90.0, max_y = -90.0 WHERE table_name = '" + PtsTableName + "';"; - gpkg.execSQL(bbsql); - - Contents contents = new Contents(); - RTE = new RelatedTablesExtension(gpkg); - - SatTable = createSatelliteTable(contents, RTE, srs, satTblName, satmapTblName, PtsTableName); - ClkTable = createClockTable(contents, RTE, srs, clkTblName, clkmapTblName, satTblName); - - MotionTable = createMotionTable(contents, RTE, srs, motionTblName, motionmapTblName, PtsTableName); - - return gpkg; - } - - - private UserTable createObservationTable(GeoPackage geoPackage, SpatialReferenceSystem srs, String tableName, GeometryType type) throws SQLException { - ContentsDao contentsDao = geoPackage.getContentsDao(); - - Contents contents = new Contents(); - contents.setTableName(tableName); - contents.setDataType(ContentsDataType.FEATURES); - contents.setIdentifier(tableName); - contents.setDescription(tableName); - contents.setSrs(srs); - - int colNum = 0; - List tblcols = new LinkedList<>(); - tblcols.add(FeatureColumn.createPrimaryKeyColumn(colNum++, ID_COLUMN)); - tblcols.add(FeatureColumn.createGeometryColumn(colNum++, GEOMETRY_COLUMN, GeometryType.POINT, false, null)); - tblcols.add(FeatureColumn.createColumn(colNum++, "SysTime", DATETIME, false, null)); - tblcols.add(FeatureColumn.createColumn(colNum++, "Lat", REAL, false, null)); - tblcols.add(FeatureColumn.createColumn(colNum++, "Lon", REAL, false, null)); - tblcols.add(FeatureColumn.createColumn(colNum++, "Alt", REAL, false, null)); - tblcols.add(FeatureColumn.createColumn(colNum++, "Provider", TEXT, false, null)); - tblcols.add(FeatureColumn.createColumn(colNum++, "GPSTime", INTEGER, false, null)); - tblcols.add(FeatureColumn.createColumn(colNum++, "FixSatCount", INTEGER, false, null)); - tblcols.add(FeatureColumn.createColumn(colNum++, "HasRadialAccuracy", INTEGER, false, null)); - tblcols.add(FeatureColumn.createColumn(colNum++, "HasVerticalAccuracy", INTEGER, false, null)); - tblcols.add(FeatureColumn.createColumn(colNum++, "RadialAccuracy", REAL, false, null)); - tblcols.add(FeatureColumn.createColumn(colNum++, "VerticalAccuracy", REAL, false, null)); - - tblcols.add(FeatureColumn.createColumn(colNum++, "ElapsedRealtimeNanos", REAL, false, null)); - - tblcols.add(FeatureColumn.createColumn(colNum++, "HasSpeed", INTEGER, false, null)); - tblcols.add(FeatureColumn.createColumn(colNum++, "HasSpeedAccuracy", INTEGER, false, null)); - tblcols.add(FeatureColumn.createColumn(colNum++, "Speed", REAL, false, null)); - tblcols.add(FeatureColumn.createColumn(colNum++, "SpeedAccuracy", REAL, false, null)); - - tblcols.add(FeatureColumn.createColumn(colNum++, "HasBearing", INTEGER, false, null)); - tblcols.add(FeatureColumn.createColumn(colNum++, "HasBearingAccuracy", INTEGER, false, null)); - tblcols.add(FeatureColumn.createColumn(colNum++, "Bearing", REAL, false, null)); - tblcols.add(FeatureColumn.createColumn(colNum++, "BearingAccuracy", REAL, false, null)); - - tblcols.add(FeatureColumn.createColumn(colNum++, "data_dump", TEXT, false, null)); - - FeatureTable table = new FeatureTable(tableName, tblcols); - geoPackage.createFeatureTable(table); - - contentsDao.create(contents); - - GeometryColumnsDao geometryColumnsDao = geoPackage.getGeometryColumnsDao(); - - GeometryColumns geometryColumns = new GeometryColumns(); - geometryColumns.setContents(contents); - geometryColumns.setColumnName(GEOMETRY_COLUMN); - geometryColumns.setGeometryType(type); - geometryColumns.setSrs(srs); - geometryColumns.setZ((byte) 0); - geometryColumns.setM((byte) 0); - geometryColumnsDao.create(geometryColumns); - - return (table); - } - /** * Clean-up TORGI and then stop this service */ @@ -655,174 +145,10 @@ public void shutdown() { stopSelf(); } - private UserTable createSatelliteTable(Contents contents, RelatedTablesExtension rte, SpatialReferenceSystem srs, String tableName, String mapTblName, String baseTblName) { - contents.setTableName(tableName); - contents.setDataType(ContentsDataType.FEATURES); - contents.setIdentifier(tableName); - contents.setDescription(tableName); - contents.setSrs(srs); - - int colNum = 1; - List tblcols = new LinkedList<>(); -// tblcols.add(UserCustomColumn.createPrimaryKeyColumn(colNum++, ID_COLUMN)); - // Dublin Core metadata descriptor profile -// tblcols.add(UserCustomColumn.createColumn(colNum++, DublinCoreType.DATE.getName(), GeoPackageDataType.DATETIME, false, null)); -// tblcols.add(FeatureColumn.createColumn(colNum++, DublinCoreType.TITLE.getName(), GeoPackageDataType.TEXT, false, null)); -// tblcols.add(FeatureColumn.createColumn(colNum++, DublinCoreType.SOURCE.getName(), GeoPackageDataType.TEXT, false, null)); -// tblcols.add(FeatureColumn.createColumn(colNum++, DublinCoreType.DESCRIPTION.getName(), GeoPackageDataType.TEXT, false, null)); - - // android GNSS measurements - tblcols.add(UserCustomColumn.createColumn(colNum++, "svid", INTEGER, true, null)); - tblcols.add(UserCustomColumn.createColumn(colNum++, "constellation", TEXT, true, null)); - tblcols.add(UserCustomColumn.createColumn(colNum++, "cn0", REAL, true, null)); - tblcols.add(UserCustomColumn.createColumn(colNum++, "agc", REAL, true, null)); - tblcols.add(UserCustomColumn.createColumn(colNum++, "has_agc", INTEGER, true, null)); - tblcols.add(UserCustomColumn.createColumn(colNum++, "in_fix", INTEGER, true, null)); - - tblcols.add(UserCustomColumn.createColumn(colNum++, "sync_state_flags", INTEGER, true, null)); - tblcols.add(UserCustomColumn.createColumn(colNum++, "sync_state_txt", TEXT, true, null)); - tblcols.add(UserCustomColumn.createColumn(colNum++, "sat_time_nanos", REAL, true, null)); - tblcols.add(UserCustomColumn.createColumn(colNum++, "sat_time_1sigma_nanos", REAL, true, null)); - tblcols.add(UserCustomColumn.createColumn(colNum++, "rcvr_time_offset_nanos", REAL, true, null)); - tblcols.add(UserCustomColumn.createColumn(colNum++, "multipath", INTEGER, true, null)); - - tblcols.add(UserCustomColumn.createColumn(colNum++, "has_carrier_freq", INTEGER, true, null)); - tblcols.add(UserCustomColumn.createColumn(colNum++, "carrier_freq_hz", REAL, true, null)); - tblcols.add(UserCustomColumn.createColumn(colNum++, "accum_delta_range", REAL, true, null)); - tblcols.add(UserCustomColumn.createColumn(colNum++, "accum_delta_range_1sigma", REAL, true, null)); - tblcols.add(UserCustomColumn.createColumn(colNum++, "accum_delta_range_state_flags", INTEGER, true, null)); - tblcols.add(UserCustomColumn.createColumn(colNum++, "accum_delta_range_state_txt", TEXT, true, null)); - tblcols.add(UserCustomColumn.createColumn(colNum++, "pseudorange_rate_mps", REAL, true, null)); - tblcols.add(UserCustomColumn.createColumn(colNum++, "pseudorange_rate_1sigma", REAL, true, null)); - - tblcols.add(UserCustomColumn.createColumn(colNum++, "has_ephemeris", INTEGER, true, null)); - tblcols.add(UserCustomColumn.createColumn(colNum++, "has_almanac", INTEGER, true, null)); - tblcols.add(UserCustomColumn.createColumn(colNum++, "azimuth_deg", REAL, true, null)); - tblcols.add(UserCustomColumn.createColumn(colNum++, "elevation_deg", REAL, true, null)); - - tblcols.add(UserCustomColumn.createColumn(colNum++, "data_dump", TEXT, true, null)); - - SimpleAttributesTable table = SimpleAttributesTable.create(tableName, tblcols); - - UserMappingTable mapTbl = UserMappingTable.create(mapTblName); - SatExtRel = rte.addSimpleAttributesRelationship(baseTblName, table, mapTbl); - - return (table); - } - - - private UserTable createClockTable(Contents contents, RelatedTablesExtension rte, SpatialReferenceSystem srs, String tableName, String mapTblName, String baseTblName) { - contents.setTableName(tableName); - contents.setDataType(ContentsDataType.FEATURES); - contents.setIdentifier(tableName); - contents.setDescription(tableName); - contents.setSrs(srs); - - int colNum = 1; - List tblcols = new LinkedList<>(); -// tblcols.add(UserCustomColumn.createPrimaryKeyColumn(colNum++, ID_COLUMN)); - // Dublin Core metadata descriptor profile -// tblcols.add(UserCustomColumn.createColumn(colNum++, DublinCoreType.DATE.getName(), DATETIME, false, null)); -// tblcols.add(FeatureColumn.createColumn(colNum++, DublinCoreType.TITLE.getName(), TEXT, false, null)); -// tblcols.add(FeatureColumn.createColumn(colNum++, DublinCoreType.SOURCE.getName(), TEXT, false, null)); -// tblcols.add(FeatureColumn.createColumn(colNum++, DublinCoreType.DESCRIPTION.getName(), TEXT, false, null)); - - // android GNSS measurements - tblcols.add(UserCustomColumn.createColumn(colNum++, "time_nanos", REAL, true, null)); - tblcols.add(UserCustomColumn.createColumn(colNum++, "time_uncertainty_nanos", REAL, true, null)); - tblcols.add(UserCustomColumn.createColumn(colNum++, "has_time_uncertainty_nanos", INTEGER, true, null)); - tblcols.add(UserCustomColumn.createColumn(colNum++, "bias_nanos", REAL, true, null)); - tblcols.add(UserCustomColumn.createColumn(colNum++, "has_bias_nanos", INTEGER, true, null)); - tblcols.add(UserCustomColumn.createColumn(colNum++, "bias_uncertainty_nanos", REAL, true, null)); - tblcols.add(UserCustomColumn.createColumn(colNum++, "has_bias_uncertainty_nanos", INTEGER, true, null)); - tblcols.add(UserCustomColumn.createColumn(colNum++, "full_bias_nanos", INTEGER, true, null)); - tblcols.add(UserCustomColumn.createColumn(colNum++, "has_full_bias_nanos", INTEGER, true, null)); - - tblcols.add(UserCustomColumn.createColumn(colNum++, "drift_nanos_per_sec", REAL, true, null)); - tblcols.add(UserCustomColumn.createColumn(colNum++, "has_drift_nanos_per_sec", INTEGER, true, null)); - tblcols.add(UserCustomColumn.createColumn(colNum++, "drift_uncertainty_nps", REAL, true, null)); - tblcols.add(UserCustomColumn.createColumn(colNum++, "has_drift_uncertainty_nps", INTEGER, true, null)); - tblcols.add(UserCustomColumn.createColumn(colNum++, "hw_clock_discontinuity_count", INTEGER, true, null)); - tblcols.add(UserCustomColumn.createColumn(colNum++, "leap_second", INTEGER, true, null)); - tblcols.add(UserCustomColumn.createColumn(colNum++, "has_leap_second", INTEGER, true, null)); - - tblcols.add(UserCustomColumn.createColumn(colNum++, "data_dump", TEXT, true, null)); - - SimpleAttributesTable table = SimpleAttributesTable.create(tableName, tblcols); - - UserMappingTable mapTbl = UserMappingTable.create(mapTblName); - ClkExtRel = rte.addSimpleAttributesRelationship(baseTblName, table, mapTbl); - - return (table); - } - - private UserTable createMotionTable(Contents contents, RelatedTablesExtension rte, SpatialReferenceSystem srs, String tableName, String mapTblName, String baseTblName) { - contents.setTableName(tableName); - contents.setDataType(ContentsDataType.FEATURES); - contents.setIdentifier(tableName); - contents.setDescription(tableName); - contents.setSrs(srs); - - int colNum = 1; - List tblcols = new LinkedList<>(); -// tblcols.add(UserCustomColumn.createPrimaryKeyColumn(colNum++, ID_COLUMN)); - // Dublin Core metadata descriptor profile -// tblcols.add(UserCustomColumn.createColumn(colNum++, DublinCoreType.DATE.getName(), DATETIME, false, null)); -// tblcols.add(FeatureColumn.createColumn(colNum++, DublinCoreType.TITLE.getName(), TEXT, false, null)); -// tblcols.add(FeatureColumn.createColumn(colNum++, DublinCoreType.SOURCE.getName(), TEXT, false, null)); -// tblcols.add(FeatureColumn.createColumn(colNum++, DublinCoreType.DESCRIPTION.getName(), TEXT, false, null)); - - // android inertial sensor measurements - tblcols.add(UserCustomColumn.createColumn(colNum++, "accel_x", REAL, true, null)); - tblcols.add(UserCustomColumn.createColumn(colNum++, "accel_y", REAL, true, null)); - tblcols.add(UserCustomColumn.createColumn(colNum++, "accel_z", REAL, true, null)); - - tblcols.add(UserCustomColumn.createColumn(colNum++, "linear_accel_x", REAL, true, null)); - tblcols.add(UserCustomColumn.createColumn(colNum++, "linear_accel_y", REAL, true, null)); - tblcols.add(UserCustomColumn.createColumn(colNum++, "linear_accel_z", REAL, true, null)); - - tblcols.add(UserCustomColumn.createColumn(colNum++, "mag_x", REAL, true, null)); - tblcols.add(UserCustomColumn.createColumn(colNum++, "mag_y", REAL, true, null)); - tblcols.add(UserCustomColumn.createColumn(colNum++, "mag_z", REAL, true, null)); - - tblcols.add(UserCustomColumn.createColumn(colNum++, "gyro_x", REAL, true, null)); - tblcols.add(UserCustomColumn.createColumn(colNum++, "gyro_y", REAL, true, null)); - tblcols.add(UserCustomColumn.createColumn(colNum++, "gyro_z", REAL, true, null)); - - tblcols.add(UserCustomColumn.createColumn(colNum++, "gravity_x", REAL, true, null)); - tblcols.add(UserCustomColumn.createColumn(colNum++, "gravity_y", REAL, true, null)); - tblcols.add(UserCustomColumn.createColumn(colNum++, "gravity_z", REAL, true, null)); - - tblcols.add(UserCustomColumn.createColumn(colNum++, "rot_vec_x", REAL, true, null)); - tblcols.add(UserCustomColumn.createColumn(colNum++, "rot_vec_y", REAL, true, null)); - tblcols.add(UserCustomColumn.createColumn(colNum++, "rot_vec_z", REAL, true, null)); - tblcols.add(UserCustomColumn.createColumn(colNum++, "rot_vec_cos", REAL, true, null)); - tblcols.add(UserCustomColumn.createColumn(colNum++, "rot_vec_hdg_acc", REAL, true, null)); - - tblcols.add(UserCustomColumn.createColumn(colNum++, "baro", REAL, true, null)); - tblcols.add(UserCustomColumn.createColumn(colNum++, "humidity", REAL, true, null)); - tblcols.add(UserCustomColumn.createColumn(colNum++, "temp", REAL, true, null)); - - tblcols.add(UserCustomColumn.createColumn(colNum++, "lux", REAL, true, null)); - tblcols.add(UserCustomColumn.createColumn(colNum++, "prox", REAL, true, null)); - - tblcols.add(UserCustomColumn.createColumn(colNum++, "stationary", INTEGER, true, null)); - tblcols.add(UserCustomColumn.createColumn(colNum++, "motion", INTEGER, true, null)); - - tblcols.add(UserCustomColumn.createColumn(colNum++, "data_dump", TEXT, true, null)); - - SimpleAttributesTable table = SimpleAttributesTable.create(tableName, tblcols); - - UserMappingTable mapTbl = UserMappingTable.create(mapTblName); - ClkExtRel = rte.addSimpleAttributesRelationship(baseTblName, table, mapTbl); - - return (table); - } - @Nullable @Override public IBinder onBind(Intent intent) { - startGnssRecorder(); + start(); return mBinder; } @@ -836,14 +162,18 @@ public void onCreate() { public void onDestroy() { if (locMgr != null) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - locMgr.unregisterGnssMeasurementsCallback(MeasurementListener); - locMgr.unregisterGnssStatusCallback(StatusListener); + locMgr.unregisterGnssMeasurementsCallback(measurementListener); + locMgr.unregisterGnssStatusCallback(statusListener); } if (locListener != null) locMgr.removeUpdates(locListener); } - if (GPSgpkg != null) - GPSgpkg.close(); + if (sensorService != null) + sensorService.shutdown(); + if (gnssMeasurementService != null) + gnssMeasurementService.shutdown(); + if (geoPackageRecorder != null) + geoPackageRecorder.shutdown(); super.onDestroy(); } @@ -853,7 +183,7 @@ public int onStartCommand(Intent intent, int flags, int startId) { if (intent != null) { String action = intent.getAction(); if (ACTION_STOP.equalsIgnoreCase(action)) { - Log.d(TAG,"Shutting down GNSS recorder"); + Log.d(TAG,"Shutting down TorgiService"); stopSelf(); return START_NOT_STICKY; } diff --git a/torgi/src/main/java/org/sofwerx/torgi/ui/MainActivity.java b/torgi/src/main/java/org/sofwerx/torgi/ui/MainActivity.java index dda418f..55ae1cf 100644 --- a/torgi/src/main/java/org/sofwerx/torgi/ui/MainActivity.java +++ b/torgi/src/main/java/org/sofwerx/torgi/ui/MainActivity.java @@ -2,6 +2,7 @@ import android.Manifest; import android.app.AlertDialog; +import android.content.ActivityNotFoundException; import android.content.ComponentName; import android.content.Context; import android.content.DialogInterface; @@ -11,10 +12,15 @@ import android.content.pm.PackageManager; import android.graphics.Color; import android.location.GnssMeasurement; +import android.location.GnssMeasurementsEvent; +import android.location.GnssStatus; +import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.os.IBinder; +import android.os.PowerManager; import android.preference.PreferenceManager; +import android.provider.Settings; import android.support.v4.app.FragmentActivity; import android.support.v4.content.ContextCompat; import android.util.Log; @@ -30,29 +36,22 @@ import android.location.Location; import android.widget.Toast; -import com.google.android.gms.maps.CameraUpdateFactory; -import com.google.android.gms.maps.GoogleMap; -import com.google.android.gms.maps.OnMapReadyCallback; -import com.google.android.gms.maps.SupportMapFragment; -import com.google.android.gms.maps.model.BitmapDescriptorFactory; -import com.google.android.gms.maps.model.LatLng; -import com.google.android.gms.maps.model.Marker; -import com.google.android.gms.maps.model.MarkerOptions; -import com.google.android.gms.maps.model.Polyline; -import com.google.android.gms.maps.model.PolylineOptions; - import org.osmdroid.util.GeoPoint; import org.osmdroid.views.overlay.gestures.RotationGestureOverlay; +import org.sofwerx.torgi.gnss.Constellation; +import org.sofwerx.torgi.gnss.LatLng; import org.sofwerx.torgi.listener.GnssMeasurementListener; import org.sofwerx.torgi.R; -import org.sofwerx.torgi.SatStatus; import org.sofwerx.torgi.service.TorgiService; -public class MainActivity extends FragmentActivity implements OnMapReadyCallback, GnssMeasurementListener { - private LatLng CENTER_US = new LatLng(39.181071, -99.938295); +public class MainActivity extends FragmentActivity implements GnssMeasurementListener { + protected static final int REQUEST_DISABLE_BATTERY_OPTIMIZATION = 401; + private double CENTER_US_LAT = 39.181071d; + private double CENTER_US_LNG = -99.938295d; private final static String TAG = "TORGIact"; private final static String PREF_LAT = "lat"; private final static String PREF_LNG = "lng"; + private final static String PREF_BATTERY_OPT_IGNORE = "nvroptbat"; private final static int MAX_HISTORY_LENGTH = 50; private final static int PERM_REQUEST_CODE = 1; private final static SimpleDateFormat fmtTime = new SimpleDateFormat("HH:mm:ss"); @@ -62,14 +61,11 @@ public class MainActivity extends FragmentActivity implements OnMapReadyCallback private TextView meas_tv; private boolean serviceBound = false; private TorgiService torgiService = null; - private GoogleMap mMap; private org.osmdroid.views.MapView osmMap = null; - private boolean mapReady = false; - private Marker current = null; private org.osmdroid.views.overlay.Marker currentOSM = null; - private Polyline historyPolyline = null; private org.osmdroid.views.overlay.Polyline historyPolylineOSM = null; private ArrayList history = null; + private LatLng current = null; protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -87,13 +83,11 @@ protected void onCreate(Bundle savedInstanceState) { stat_tv.setText("(rcvr clock measurements unavailable on this platform)"); } - SupportMapFragment mapFragment = (SupportMapFragment) getSupportFragmentManager().findFragmentById(R.id.map); - mapFragment.getMapAsync(this); - osmMapSetup(); checkPermissions(); startService(); + openBatteryOptimizationDialogIfNeeded(); }; private void osmMapSetup() { @@ -116,10 +110,10 @@ public void onResume() { @Override public void onPause() { + if (torgiService != null) + torgiService.setListener(null); super.onPause(); saveLastLocation(); - if (serviceBound && (torgiService != null)) - torgiService.setListener(null); } @Override @@ -151,7 +145,7 @@ private void onTorgiServiceConnected() { private void startService() { if (serviceBound) - torgiService.startGnssRecorder(); + torgiService.start(); else { startService(new Intent(this, TorgiService.class)); Intent intent = new Intent(this, TorgiService.class); @@ -160,23 +154,13 @@ private void startService() { } private void drawMarker(LatLng pos, String info) { - if ((pos != null) && mapReady) { + if (pos != null) { if (history == null) history = new ArrayList<>(); history.add(pos); if (history.size() > 1) { if (history.size() > MAX_HISTORY_LENGTH) history.remove(0); - if (historyPolyline == null) { - PolylineOptions opts = new PolylineOptions(); - for (LatLng pt:history) { - opts.add(pt); - } - opts.width(5); - opts.color(Color.YELLOW); - historyPolyline = mMap.addPolyline(opts); - } else - historyPolyline.setPoints(history); if (historyPolylineOSM == null) { historyPolylineOSM = new org.osmdroid.views.overlay.Polyline(); @@ -191,15 +175,6 @@ private void drawMarker(LatLng pos, String info) { historyPolylineOSM.addPoint(new GeoPoint(pos.latitude,pos.longitude)); } - if (current == null) { - current = mMap.addMarker(new MarkerOptions() - .position(pos) - .anchor(0.5f,0.5f) - .icon(BitmapDescriptorFactory.fromResource(R.drawable.map_icon)) - .title("GPS")); - mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(pos, 19)); - } else - current.setPosition(pos); if (currentOSM == null) { currentOSM = new org.osmdroid.views.overlay.Marker(osmMap); currentOSM.setPosition(new GeoPoint(pos.latitude,pos.longitude)); @@ -215,8 +190,61 @@ private void drawMarker(LatLng pos, String info) { currentOSM.setPosition(new GeoPoint(pos.latitude,pos.longitude)); osmMap.invalidate(); } + } + } + + /** + * Request battery optimization exception so that the system doesn't throttle back our app + */ + private void openBatteryOptimizationDialogIfNeeded() { + if (isOptimizingBattery() && isAllowAskAboutBattery()) { + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setTitle(R.string.enable_battery_optimization); + builder.setMessage(R.string.battery_optimizations_narrative); + builder.setPositiveButton(R.string.battery_optimize_yes, (dialog, which) -> { + Intent intent = new Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS); + Uri uri = Uri.parse("package:" + getPackageName()); + intent.setData(uri); + try { + startActivityForResult(intent, REQUEST_DISABLE_BATTERY_OPTIMIZATION); + } catch (ActivityNotFoundException e) { + Toast.makeText(this, R.string.does_not_support_battery_optimization, Toast.LENGTH_SHORT).show(); + } + }); + builder.setOnDismissListener(dialog -> setNeverAskBatteryOptimize()); + final AlertDialog dialog = builder.create(); + dialog.setCanceledOnTouchOutside(false); + dialog.show(); + } + } + + protected boolean isOptimizingBattery() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + final PowerManager pm = (PowerManager) getSystemService(POWER_SERVICE); + return pm != null && !pm.isIgnoringBatteryOptimizations(getPackageName()); + } else + return false; + } + + private boolean isAllowAskAboutBattery() { + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getApplicationContext()); + return !prefs.getBoolean(PREF_BATTERY_OPT_IGNORE,false); + } + + private void setNeverAskBatteryOptimize() { + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getApplicationContext()); + SharedPreferences.Editor edit = prefs.edit(); + edit.putBoolean(PREF_BATTERY_OPT_IGNORE,true); + edit.apply(); + } - current.setSnippet(info); + @Override + public void onActivityResult(int requestCode, int resultCode, final Intent data) { + super.onActivityResult(requestCode, resultCode, data); + switch (requestCode) { + case REQUEST_DISABLE_BATTERY_OPTIMIZATION: + setNeverAskBatteryOptimize(); + break; } } @@ -256,11 +284,11 @@ private boolean checkPermissions() { } private void saveLastLocation() { - if ((current != null) && (current.getPosition() != null)) { + if (current != null) { SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getApplicationContext()); SharedPreferences.Editor edit = prefs.edit(); - edit.putFloat(PREF_LAT,(float)current.getPosition().latitude); - edit.putFloat(PREF_LNG,(float)current.getPosition().longitude); + edit.putFloat(PREF_LAT,(float)current.latitude); + edit.putFloat(PREF_LNG,(float)current.longitude); edit.apply(); } } @@ -296,83 +324,68 @@ public void onClick(DialogInterface arg0, int arg1) { } @Override - public void onMapReady(GoogleMap googleMap) { - mMap = googleMap; - mMap.setMapType(GoogleMap.MAP_TYPE_HYBRID); - mMap.setBuildingsEnabled(false); - if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) - mMap.setMyLocationEnabled(false); - } else - mMap.setMyLocationEnabled(false); - LatLng lastLatLng = getLastLatLng(); - if (lastLatLng != null) { - mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(lastLatLng, 19)); - if (osmMap != null) { - osmMap.getController().setZoom(19d); - osmMap.setExpectedCenter(new GeoPoint(lastLatLng.latitude, lastLatLng.longitude)); - } - } else { - mMap.moveCamera(CameraUpdateFactory.newLatLng(CENTER_US)); - if (osmMap != null) { - osmMap.getController().setZoom(1d); - osmMap.setExpectedCenter(new GeoPoint(CENTER_US.latitude, CENTER_US.longitude)); - } - } - mapReady = true; - mMap.getUiSettings().setMapToolbarEnabled(false); - } - - @Override - public void onSatStatusUpdated(final ArrayList statuses) { - runOnUiThread(new Runnable() { - @Override - public void run() { - if ((statuses == null) || statuses.isEmpty()) - stat_tv.setVisibility(View.GONE); - else { - StringBuffer out = new StringBuffer(); - - boolean first = true; - for (SatStatus status:statuses) { - if (status.isUsedInFix()) { - if (first) - first = false; - else - out.append('\n'); - out.append(status.constellation + status.svid); + public void onSatStatusUpdated(final GnssStatus status) { + if (status != null) { + runOnUiThread(new Runnable() { + @Override + public void run() { + int numSats = status.getSatelliteCount(); + + if (numSats == 0) + stat_tv.setVisibility(View.GONE); + else { + StringBuffer out = new StringBuffer(); + + boolean first = true; + for (int i = 0; i < numSats; ++i) { + if (status.usedInFix(i)) { + if (first) + first = false; + else + out.append('\n'); + out.append(Constellation.get(status.getConstellationType(i)).name() + status.getSvid(i)); + } } - } - stat_tv.setText(out.toString()); - stat_tv.setVisibility(View.VISIBLE); + stat_tv.setText(out.toString()); + stat_tv.setVisibility(View.VISIBLE); + } } - } - }); + }); + } } @Override - public void onGnssMeasurementReceived(final Collection measurements) { - runOnUiThread(new Runnable() { - @Override - public void run() { - if ((measurements == null) || measurements.isEmpty()) - meas_tv.setVisibility(View.GONE); - else { - StringBuffer out = new StringBuffer(); - boolean first = true; - for (GnssMeasurement measurement:measurements) { - if (first) - first = false; - else - out.append('\n'); - out.append(measurement.toString()); - } - meas_tv.setText(out.toString()); - meas_tv.setVisibility(View.VISIBLE); + public void onGnssMeasurementReceived(GnssMeasurementsEvent event) { + if (event != null) { + Collection measurements = event.getMeasurements(); + final String values; + if ((measurements == null) || measurements.isEmpty()) + values = null; + else { + StringBuffer out = new StringBuffer(); + boolean first = true; + for (GnssMeasurement measurement : measurements) { + if (first) + first = false; + else + out.append('\n'); + out.append(measurement.toString()); } + values = out.toString(); } - }); + runOnUiThread(new Runnable() { + @Override + public void run() { + if (values == null) + meas_tv.setVisibility(View.GONE); + else { + meas_tv.setText(values); + meas_tv.setVisibility(View.VISIBLE); + } + } + }); + } } @Override @@ -397,7 +410,7 @@ public void run() { out.append("VerticalAccuracy: " + String.valueOf(loc.getVerticalAccuracyMeters()) + "\n"); } - String txt = out.toString() + "\n" + (serviceBound?torgiService.getGpkgFilename():"") + " SDK v" + Build.VERSION.SDK_INT; + String txt = out.toString() + "\n" + (serviceBound?torgiService.getGeoPackageRecorder().getGpkgFilename():"") + " SDK v" + Build.VERSION.SDK_INT; cur_tv.setText(txt); drawMarker(new LatLng(loc.getLatitude(), loc.getLongitude()),fmtTime.format(loc.getTime())+", ±"+(loc.hasAccuracy()?fmtAccuracy.format(loc.getAccuracy()):"")+"m"); } diff --git a/torgi/src/main/res/drawable/map_icon.png b/torgi/src/main/res/drawable/map_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..3b5b67a999280608a5527c434c8bc4942fba819b GIT binary patch literal 2004 zcmV;_2P^oAP) zK~z}7?U-9^RM!=TzkSZ!>I*R>85`)(Cr0ES^;===Zt$j?XB))IT>3`qwM zGG+E`VgL`|_;-R*fO5Hpu1fvWP<1u#NQ8Gq==S^ByLBs>z!N~+L_rx31G94{j{JOF zp%6}|6NktUFu?HpiH$@^0ulxWnBLncUD) zqzS2xL0!1O zQ?^aD2x3`W`uuZ#Jvd0M2Kd#HBNPEY0OlA5PXd#G$4nD1knzeZEDZ$si+=u#En8R* zECQx#0evu^va|2ksLoE7i4Yc{@APR70k7@eO|b}HX(JcP%UJ_F1EiWJahiJ`x0}g8 zN?je>ZJWCyNKFk}fR#Wdkf78!ytHZ+>iBWoAAii0zCL~~f&_!K78mm-@JnE0X(>My z;erm|`N~QPwUHFlBnEJ5V3W3Osnu{t@KERTnzaVh%6i^PV*yc-I! zI~?Ye2-4Ms4_E`d0jvgIJa&vi+vdCmb+Njdb)$gt04~eog!bt9mKI8H-C|!(4p~6* z!$7GsXGjvEJ6h~oTWJ9{D8)LZcpjJw%&V-V(6+fa463S%wb7s)4qO1iAV)-qM6a2f z%g=!MKw`8Mj3kxv`3Uv)Qg-()<(9=2txEz#w6^$_;x2G6Hqn1rEi;qB#6*_b zHi4!l&NnpB4cq~GOp~Aps-%SO>};CS(wM0fi_+6c&(6ko_%MDEWZyo#ix=~{VUTE9 zlob|Iw|_s4J|A6?2mzo6u(URfDE&i+D~~=x%sqUVA`v7UrnkJD4}d(=WG?U+;4)2O zffOLKqJlNH{STtoRaH?4%-OYz<&nrx`KhYnP2d;6R$!G5nKwFQ_jyR}*UfN72CP`Y zEW==SQWD|MKI4lUH|RG_`a}qc5H?LhA{eEm^kiq_OG{(6Qp`_FN) zc)bj~|31xibp(J8;A@~0=mkO#Qf;MKAG2TqDb39kYa&|h?ff2i7RYeBNzke7)LE4R z%&o4bFcKLex(FA`$|#75A!qAWUiW(03={xM^z*UfRqSC<@!PkvU~rH>Y6D#z9qi7@ z;RRsYFex1lT$;GaK>E3JY!JaWtmq$p$a>(%z-py<5lGkXI>%i;G@sl+;(-IaBtoS& z&=&|$Q(Mbh8#nSy^rG*)Luy?eZ&()PBJ@P1`IV9q)&Va^m5%R?RourMrirNw;mkdI zSQQHKr(q+uO=mF3-zJhpVr*?CKK8{Y#$|CNg zk0_Wvo%Jm(oZ7UB%9AG<00y)N?g7D=7zQR!Cbqqu7~7^7Xa(8 - - - + tilesource="openstreetmaps" /> \ No newline at end of file diff --git a/torgi/src/main/res/values/strings.xml b/torgi/src/main/res/values/strings.xml index f38b0a9..3aa77ff 100644 --- a/torgi/src/main/res/values/strings.xml +++ b/torgi/src/main/res/values/strings.xml @@ -8,4 +8,8 @@ Do you want to shutdown TORGI or do you want TORGI to keep collecting data in the background? Shutdown Run in background + This device does not support battery optimization. + Settings adjustment recommended + Can I turn off battery optimization for TORGI? This will allow better updates from sensors but will drain the battery a bit more. + Ok \ No newline at end of file From 6bee10b5c4b9af511c60227689c0c141bd854954 Mon Sep 17 00:00:00 2001 From: scottlandis Date: Mon, 10 Sep 2018 06:16:51 -0700 Subject: [PATCH 4/5] - limited EWDetection arraylist size to reduce ever expanding mememory and baseline compute times --- .../java/org/sofwerx/torgi/gnss/EWDetection.java | 12 ++++++++++++ .../torgi/service/GNSSMeasurementService.java | 1 + 2 files changed, 13 insertions(+) diff --git a/torgi/src/main/java/org/sofwerx/torgi/gnss/EWDetection.java b/torgi/src/main/java/org/sofwerx/torgi/gnss/EWDetection.java index 6b94040..1f11754 100644 --- a/torgi/src/main/java/org/sofwerx/torgi/gnss/EWDetection.java +++ b/torgi/src/main/java/org/sofwerx/torgi/gnss/EWDetection.java @@ -6,6 +6,7 @@ * Collects EW indicator levels and provides estimates on likelihood of EW activity */ public class EWDetection { + private final static int MAX_POINT_STORAGE = 50; private ArrayList points = null; private ArrayList satellites = null; @@ -20,9 +21,20 @@ public void add(DataPoint point) { update(meas.getSat()); } } + if (points.size() > MAX_POINT_STORAGE) + points.remove(0); } } + /** + * Clears out the old data points, but not the stored satellite information; helpful as a way + * to refresh totals after recalculating a baseline + */ + public void emptyDataPoints() { + if (points != null) + points = new ArrayList<>(); + } + /** * Gets all the measurements for one satellite * @param sat diff --git a/torgi/src/main/java/org/sofwerx/torgi/service/GNSSMeasurementService.java b/torgi/src/main/java/org/sofwerx/torgi/service/GNSSMeasurementService.java index ce1917a..6cd6001 100644 --- a/torgi/src/main/java/org/sofwerx/torgi/service/GNSSMeasurementService.java +++ b/torgi/src/main/java/org/sofwerx/torgi/service/GNSSMeasurementService.java @@ -39,6 +39,7 @@ public void run() { Log.d(TAG,"GNSSMeasurementService - periodicHelper"); if (System.currentTimeMillis() > lastBaselineUpdate + BASELINE_UPDATE_INTERVAL) { ewDetection.updateBaseline(true); + ewDetection.emptyDataPoints(); lastBaselineUpdate = System.currentTimeMillis(); Log.d(TAG,"GNSS measurement baseline updated"); } From efeb8ea4fdece4fd40b24fcebdf63eedad52aaa0 Mon Sep 17 00:00:00 2001 From: scottlandis Date: Mon, 10 Sep 2018 13:42:27 -0700 Subject: [PATCH 5/5] - added real-time monitoring activity for viewing C/N0 and AGC (Service interactions can be slightly buggy at start-up and shutdown, but the bugs should be hidden right now by excessive exception catching) --- build.gradle | 1 + torgi/build.gradle | 1 + torgi/src/main/AndroidManifest.xml | 12 +- .../org/sofwerx/torgi/gnss/SpaceTime.java | 4 +- .../torgi/service/GeoPackageRecorder.java | 2 +- .../org/sofwerx/torgi/ui/AboutActivity.java | 52 ++ .../org/sofwerx/torgi/ui/MainActivity.java | 78 ++- .../org/sofwerx/torgi/ui/MonitorActivity.java | 565 ++++++++++++++++++ .../sofwerx/torgi/util/Acknowledgements.java | 51 ++ .../res/drawable/icon_expandable_white.xml | 5 + .../main/res/drawable/icon_expanded_white.xml | 5 + torgi/src/main/res/drawable/icon_gnns.xml | 4 + torgi/src/main/res/drawable/icon_monitor.xml | 5 + torgi/src/main/res/layout/about_activity.xml | 103 ++++ torgi/src/main/res/layout/activity_main.xml | 25 +- .../src/main/res/layout/activity_monitor.xml | 31 + torgi/src/main/res/menu/menu_main.xml | 5 + torgi/src/main/res/values/colors.xml | 2 + torgi/src/main/res/values/strings.xml | 6 + torgi/src/main/res/values/styles.xml | 4 +- 20 files changed, 925 insertions(+), 36 deletions(-) create mode 100644 torgi/src/main/java/org/sofwerx/torgi/ui/AboutActivity.java create mode 100644 torgi/src/main/java/org/sofwerx/torgi/ui/MonitorActivity.java create mode 100644 torgi/src/main/java/org/sofwerx/torgi/util/Acknowledgements.java create mode 100644 torgi/src/main/res/drawable/icon_expandable_white.xml create mode 100644 torgi/src/main/res/drawable/icon_expanded_white.xml create mode 100644 torgi/src/main/res/drawable/icon_gnns.xml create mode 100644 torgi/src/main/res/drawable/icon_monitor.xml create mode 100644 torgi/src/main/res/layout/about_activity.xml create mode 100644 torgi/src/main/res/layout/activity_monitor.xml diff --git a/build.gradle b/build.gradle index 5587d1f..0ed91b4 100644 --- a/build.gradle +++ b/build.gradle @@ -20,6 +20,7 @@ allprojects { repositories { google() jcenter() + maven { url 'https://jitpack.io' } //for charts } } diff --git a/torgi/build.gradle b/torgi/build.gradle index 747d869..74fd90c 100644 --- a/torgi/build.gradle +++ b/torgi/build.gradle @@ -36,6 +36,7 @@ dependencies { //implementation fileTree(include: ['*.jar'], dir: 'libs') //implementation project(':geopackage-sdk') + implementation 'com.github.PhilJay:MPAndroidChart:v3.0.3' //for charts implementation 'org.osmdroid:osmdroid-android:6.0.2' implementation 'mil.nga.geopackage:geopackage-android:3.0.2' implementation 'com.android.support:appcompat-v7:27.1.1' diff --git a/torgi/src/main/AndroidManifest.xml b/torgi/src/main/AndroidManifest.xml index 61f11b9..c1ec8db 100644 --- a/torgi/src/main/AndroidManifest.xml +++ b/torgi/src/main/AndroidManifest.xml @@ -22,12 +22,20 @@ + android:label="@string/app_name"> + + + + + \ No newline at end of file diff --git a/torgi/src/main/java/org/sofwerx/torgi/gnss/SpaceTime.java b/torgi/src/main/java/org/sofwerx/torgi/gnss/SpaceTime.java index 341fbb3..de01d21 100644 --- a/torgi/src/main/java/org/sofwerx/torgi/gnss/SpaceTime.java +++ b/torgi/src/main/java/org/sofwerx/torgi/gnss/SpaceTime.java @@ -22,7 +22,9 @@ public SpaceTime(double latitude, double longitude, double altitude, long time) } public SpaceTime(Location loc) { - if (loc != null) { + if (loc == null) { + this.time = System.currentTimeMillis(); + } else { this.latitude = loc.getLatitude(); this.longitude = loc.getLongitude(); if (loc.hasAltitude()) diff --git a/torgi/src/main/java/org/sofwerx/torgi/service/GeoPackageRecorder.java b/torgi/src/main/java/org/sofwerx/torgi/service/GeoPackageRecorder.java index 05438ad..9b26fe3 100644 --- a/torgi/src/main/java/org/sofwerx/torgi/service/GeoPackageRecorder.java +++ b/torgi/src/main/java/org/sofwerx/torgi/service/GeoPackageRecorder.java @@ -271,7 +271,7 @@ public void onGnssMeasurementsReceived(final GnssMeasurementsEvent event) { satrow.setValue("svid", g.getSvid()); satrow.setValue("constellation", con); - satrow.setValue("cn0", (double) g.getCn0DbHz()); + satrow.setValue("cn0", g.getCn0DbHz()); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { if (g.hasAutomaticGainControlLevelDb()) { diff --git a/torgi/src/main/java/org/sofwerx/torgi/ui/AboutActivity.java b/torgi/src/main/java/org/sofwerx/torgi/ui/AboutActivity.java new file mode 100644 index 0000000..3db0997 --- /dev/null +++ b/torgi/src/main/java/org/sofwerx/torgi/ui/AboutActivity.java @@ -0,0 +1,52 @@ +package org.sofwerx.torgi.ui; + +import android.app.Activity; +import android.os.Bundle; +import android.text.Html; +import android.text.method.LinkMovementMethod; +import android.view.View; +import android.widget.TextView; + +import org.sofwerx.torgi.R; +import org.sofwerx.torgi.util.Acknowledgements; + +public class AboutActivity extends Activity { + private TextView ackTitle; + private TextView ack; + private TextView licTitle; + private TextView lic; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.about_activity); + //Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); + //setSupportActionBar(toolbar); + ackTitle = findViewById(R.id.legalAckTitle); + ack = findViewById(R.id.legalAck); + licTitle = findViewById(R.id.legalLicenseTitle); + lic = findViewById(R.id.legalLicense); + ackTitle.setOnClickListener(v -> { + if (ack.getVisibility() == View.VISIBLE) { + ack.setVisibility(View.GONE); + ackTitle.setCompoundDrawablesWithIntrinsicBounds(R.drawable.icon_expanded_white, 0, 0, 0); + } else { + ack.setVisibility(View.VISIBLE); + ackTitle.setCompoundDrawablesWithIntrinsicBounds(R.drawable.icon_expandable_white, 0, 0, 0); + } + }); + licTitle.setOnClickListener(v -> { + if (lic.getVisibility() == View.VISIBLE) { + lic.setVisibility(View.GONE); + licTitle.setCompoundDrawablesWithIntrinsicBounds(R.drawable.icon_expanded_white, 0, 0, 0); + } else { + lic.setVisibility(View.VISIBLE); + licTitle.setCompoundDrawablesWithIntrinsicBounds(R.drawable.icon_expandable_white, 0, 0, 0); + } + }); + ack.setText(Html.fromHtml(Acknowledgements.getCredits())); + ack.setMovementMethod(LinkMovementMethod.getInstance()); + lic.setText(Html.fromHtml(Acknowledgements.getLicenses())); + lic.setMovementMethod(LinkMovementMethod.getInstance()); + } +} \ No newline at end of file diff --git a/torgi/src/main/java/org/sofwerx/torgi/ui/MainActivity.java b/torgi/src/main/java/org/sofwerx/torgi/ui/MainActivity.java index 55ae1cf..ceabb38 100644 --- a/torgi/src/main/java/org/sofwerx/torgi/ui/MainActivity.java +++ b/torgi/src/main/java/org/sofwerx/torgi/ui/MainActivity.java @@ -1,6 +1,7 @@ package org.sofwerx.torgi.ui; import android.Manifest; +import android.app.Activity; import android.app.AlertDialog; import android.content.ActivityNotFoundException; import android.content.ComponentName; @@ -24,6 +25,9 @@ import android.support.v4.app.FragmentActivity; import android.support.v4.content.ContextCompat; import android.util.Log; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; import android.view.View; import android.widget.TextView; @@ -44,7 +48,7 @@ import org.sofwerx.torgi.R; import org.sofwerx.torgi.service.TorgiService; -public class MainActivity extends FragmentActivity implements GnssMeasurementListener { +public class MainActivity extends Activity implements GnssMeasurementListener { protected static final int REQUEST_DISABLE_BATTERY_OPTIMIZATION = 401; private double CENTER_US_LAT = 39.181071d; private double CENTER_US_LNG = -99.938295d; @@ -73,6 +77,11 @@ protected void onCreate(Bundle savedInstanceState) { stat_tv = findViewById(R.id.status_text); cur_tv = findViewById(R.id.current_text); meas_tv = findViewById(R.id.measurement_text); + View buttonMonitor = findViewById(R.id.mainButtonMonitor); + buttonMonitor.setOnClickListener(v -> { + startActivity(new Intent(MainActivity.this, MonitorActivity.class)); + finish(); + }); meas_tv.setText("Acquiring satellites...\n", TextView.BufferType.EDITABLE); stat_tv.setText("Acquiring satellites...\n", TextView.BufferType.EDITABLE); @@ -101,6 +110,28 @@ private void osmMapSetup() { osmMap.setTilesScaledToDpi(true); //scales tiles to the current screen's DPI, helps with readability of labels } + @Override + public boolean onCreateOptionsMenu(Menu menu) { + MenuInflater inflater = getMenuInflater(); + inflater.inflate(R.menu.menu_main, menu); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + // Handle item selection + switch (item.getItemId()) { + case R.id.action_settings: + //TODO + return true; + case R.id.action_about: + startActivity(new Intent(this,AboutActivity.class)); + return true; + default: + return super.onOptionsItemSelected(item); + } + } + @Override public void onResume() { super.onResume(); @@ -180,7 +211,7 @@ private void drawMarker(LatLng pos, String info) { currentOSM.setPosition(new GeoPoint(pos.latitude,pos.longitude)); currentOSM.setAnchor(org.osmdroid.views.overlay.Marker.ANCHOR_CENTER,org.osmdroid.views.overlay.Marker.ANCHOR_CENTER); currentOSM.setIcon(getResources().getDrawable(R.drawable.map_icon)); - currentOSM.setTitle("GPS"); + currentOSM.setTitle("GNSS Fix"); osmMap.getOverlays().add(currentOSM); if (osmMap != null) { osmMap.getController().setZoom(18d); @@ -390,30 +421,27 @@ public void run() { @Override public void onLocationChanged(final Location loc) { - runOnUiThread(new Runnable() { - @Override - public void run() { - StringBuffer out = new StringBuffer(); - - out.append("Lat: "+ String.valueOf(loc.getLatitude()) + "\n"); - out.append("Lon: " + String.valueOf(loc.getLongitude()) + "\n"); - out.append("Alt: " + String.valueOf(loc.getAltitude()) + "\n"); - out.append("Provider: " + String.valueOf(loc.getProvider()) + "\n"); - out.append("Time: " + fmtTime.format(loc.getTime()) + "\n"); - int sats = loc.getExtras().getInt("satellites"); - if (sats > 0) - out.append("FixSatCount: " + String.valueOf(sats) + "\n"); - if (loc.hasAccuracy()) - out.append("RadialAccuracy: " + String.valueOf(loc.getAccuracy()) + "\n"); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - if (loc.hasVerticalAccuracy()) - out.append("VerticalAccuracy: " + String.valueOf(loc.getVerticalAccuracyMeters()) + "\n"); - } - - String txt = out.toString() + "\n" + (serviceBound?torgiService.getGeoPackageRecorder().getGpkgFilename():"") + " SDK v" + Build.VERSION.SDK_INT; - cur_tv.setText(txt); - drawMarker(new LatLng(loc.getLatitude(), loc.getLongitude()),fmtTime.format(loc.getTime())+", ±"+(loc.hasAccuracy()?fmtAccuracy.format(loc.getAccuracy()):"")+"m"); + runOnUiThread(() -> { + StringBuffer out = new StringBuffer(); + + out.append("Lat: "+ String.valueOf(loc.getLatitude()) + "\n"); + out.append("Lon: " + String.valueOf(loc.getLongitude()) + "\n"); + out.append("Alt: " + String.valueOf(loc.getAltitude()) + "\n"); + out.append("Provider: " + String.valueOf(loc.getProvider()) + "\n"); + out.append("Time: " + fmtTime.format(loc.getTime()) + "\n"); + int sats = loc.getExtras().getInt("satellites"); + if (sats > 0) + out.append("FixSatCount: " + String.valueOf(sats) + "\n"); + if (loc.hasAccuracy()) + out.append("RadialAccuracy: " + String.valueOf(loc.getAccuracy()) + "\n"); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + if (loc.hasVerticalAccuracy()) + out.append("VerticalAccuracy: " + String.valueOf(loc.getVerticalAccuracyMeters()) + "\n"); } + + String txt = out.toString() + "\n" + (serviceBound?torgiService.getGeoPackageRecorder().getGpkgFilename():"") + " SDK v" + Build.VERSION.SDK_INT; + cur_tv.setText(txt); + drawMarker(new LatLng(loc.getLatitude(), loc.getLongitude()),fmtTime.format(loc.getTime())+", ±"+(loc.hasAccuracy()?fmtAccuracy.format(loc.getAccuracy()):"")+"m"); }); } diff --git a/torgi/src/main/java/org/sofwerx/torgi/ui/MonitorActivity.java b/torgi/src/main/java/org/sofwerx/torgi/ui/MonitorActivity.java new file mode 100644 index 0000000..d71dc25 --- /dev/null +++ b/torgi/src/main/java/org/sofwerx/torgi/ui/MonitorActivity.java @@ -0,0 +1,565 @@ +package org.sofwerx.torgi.ui; + +import android.Manifest; +import android.app.AlertDialog; +import android.content.ActivityNotFoundException; +import android.content.ComponentName; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.ServiceConnection; +import android.content.SharedPreferences; +import android.content.pm.PackageManager; +import android.graphics.Color; +import android.location.GnssMeasurement; +import android.location.GnssMeasurementsEvent; +import android.location.GnssStatus; +import android.location.Location; +import android.net.Uri; +import android.os.Build; +import android.os.Bundle; +import android.os.IBinder; +import android.os.PowerManager; +import android.preference.PreferenceManager; +import android.provider.Settings; +import android.support.v4.app.ActivityCompat; +import android.support.v4.app.FragmentActivity; +import android.support.v4.content.ContextCompat; +import android.util.Log; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.widget.TextView; +import android.widget.Toast; + +import com.github.mikephil.charting.charts.CombinedChart; +import com.github.mikephil.charting.components.Legend; +import com.github.mikephil.charting.components.XAxis; +import com.github.mikephil.charting.components.YAxis; +import com.github.mikephil.charting.data.BarData; +import com.github.mikephil.charting.data.BarDataSet; +import com.github.mikephil.charting.data.BarEntry; +import com.github.mikephil.charting.data.CombinedData; +import com.github.mikephil.charting.data.Entry; +import com.github.mikephil.charting.data.LineData; +import com.github.mikephil.charting.data.LineDataSet; + +import org.osmdroid.util.GeoPoint; +import org.osmdroid.views.overlay.gestures.RotationGestureOverlay; +import org.sofwerx.torgi.R; +import org.sofwerx.torgi.gnss.Constellation; +import org.sofwerx.torgi.gnss.DataPoint; +import org.sofwerx.torgi.gnss.GNSSEWValues; +import org.sofwerx.torgi.gnss.LatLng; +import org.sofwerx.torgi.gnss.SatMeasurement; +import org.sofwerx.torgi.gnss.Satellite; +import org.sofwerx.torgi.gnss.SpaceTime; +import org.sofwerx.torgi.listener.GnssMeasurementListener; +import org.sofwerx.torgi.service.TorgiService; + +import java.text.DecimalFormat; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Collection; + +public class MonitorActivity extends FragmentActivity implements GnssMeasurementListener { + protected static final int REQUEST_DISABLE_BATTERY_OPTIMIZATION = 401; + private final static long MAX_CHART_UPDATE_RATE = 500l; + private long lastChartUpdate = Long.MIN_VALUE; + private float chartIndex = 0f; + private double CENTER_US_LAT = 39.181071d; + private double CENTER_US_LNG = -99.938295d; + private final static String TAG = "TORGI.monitor"; + private final static String PREF_LAT = "lat"; + private final static String PREF_LNG = "lng"; + private final static String PREF_BATTERY_OPT_IGNORE = "nvroptbat"; + private final static int MAX_HISTORY_LENGTH = 50; + private final static int PERM_REQUEST_CODE = 1; + private final static SimpleDateFormat fmtTime = new SimpleDateFormat("HH:mm:ss"); + private final static DecimalFormat fmtAccuracy = new DecimalFormat("#.##"); + private boolean serviceBound = false; + private TorgiService torgiService = null; + private org.osmdroid.views.MapView osmMap = null; + private org.osmdroid.views.overlay.Marker currentOSM = null; + private org.osmdroid.views.overlay.Polyline historyPolylineOSM = null; + private ArrayList history = null; + private LatLng current = null; + private CombinedChart chartEW = null; + private CombinedData chartData = null; + private TextView textOverview; + + private LineDataSet setCN0 = null; + private BarDataSet setAGC = null; + private Location currentLoc = null; + + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_monitor); + textOverview = findViewById(R.id.monitorTextOverview); + + osmMapSetup(); + + checkPermissions(); + startService(); + openBatteryOptimizationDialogIfNeeded(); + }; + + private void setupEWchart(Entry entryCNO, BarEntry entryAGC) { + if ((chartEW == null) && (entryCNO != null) && (entryAGC != null)) { + ArrayList entriesCNO = new ArrayList<>(); + ArrayList entriesAGC = new ArrayList<>(); + entriesCNO.add(entryCNO); + entriesAGC.add(entryAGC); + + chartEW = findViewById(R.id.chartEW); + chartEW.getDescription().setEnabled(false); + chartEW.setBackgroundColor(Color.BLACK); + chartEW.setDrawGridBackground(false); + chartEW.setDrawBarShadow(false); + chartEW.setHighlightFullBarEnabled(false); + chartEW.setPinchZoom(false); + // draw bars behind lines + //chartEW.setDrawOrder(new CombinedChart.DrawOrder[]{CombinedChart.DrawOrder.BAR, CombinedChart.DrawOrder.BUBBLE, CombinedChart.DrawOrder.CANDLE, CombinedChart.DrawOrder.LINE, CombinedChart.DrawOrder.SCATTER}); + chartEW.setDrawOrder(new CombinedChart.DrawOrder[]{CombinedChart.DrawOrder.BAR, CombinedChart.DrawOrder.LINE}); + + Legend l = chartEW.getLegend(); + l.setWordWrapEnabled(true); + l.setVerticalAlignment(Legend.LegendVerticalAlignment.BOTTOM); + l.setHorizontalAlignment(Legend.LegendHorizontalAlignment.CENTER); + l.setOrientation(Legend.LegendOrientation.HORIZONTAL); + l.setDrawInside(false); + l.setTextColor(Color.WHITE); + + YAxis rightAxis = chartEW.getAxisRight(); //AGC + rightAxis.setDrawGridLines(false); + rightAxis.setAxisMinimum(-1f); + rightAxis.setAxisMaximum(5f); + rightAxis.setTextColor(getColor(R.color.agc)); + rightAxis.setDrawLabels(true); + rightAxis.setValueFormatter((value, axis) -> (int) value + "dB"); + + YAxis leftAxis = chartEW.getAxisLeft(); //CNO + leftAxis.setDrawGridLines(false); + leftAxis.setAxisMinimum(10f); + leftAxis.setAxisMaximum(40f); + leftAxis.setTextColor(Color.rgb(255, 150, 150)); + leftAxis.setValueFormatter((value, axis) -> (int) value + "dB-Hz"); + + XAxis xAxis = chartEW.getXAxis(); + xAxis.setPosition(XAxis.XAxisPosition.BOTH_SIDED); + //xAxis.setAxisMinimum(0f); + //xAxis.setGranularity(1f); + + chartData = new CombinedData(); + + chartData.setData(generateLineData(entriesCNO)); + chartData.setData(generateBarData(entriesAGC)); + //data.setValueTypeface(mTfLight); + + //xAxis.setAxisMaximum(data.getXMax() + 0.25f); + + chartEW.setData(chartData); + //chartEW.invalidate(); + } + } + + private LineData generateLineData(ArrayList entriesCNO) { + LineData d = new LineData(); + + setCN0 = new LineDataSet(entriesCNO, "Avg C/N₀"); + setCN0.setColor(getColor(R.color.cn0)); + setCN0.setLineWidth(2.5f); + setCN0.setMode(LineDataSet.Mode.CUBIC_BEZIER); + setCN0.setDrawValues(false); + setCN0.setDrawCircles(false); + //setCN0.setValueTextSize(10f); + //setCN0.setValueTextColor(getColor(R.color.cn0)); + setCN0.setAxisDependency(YAxis.AxisDependency.LEFT); + d.addDataSet(setCN0); + + return d; + } + + private BarData generateBarData(ArrayList entriesAGC) { + BarData d = new BarData(); + + setAGC = new BarDataSet(entriesAGC, "Avg AGC"); + setAGC.setColor(getColor(R.color.agc)); + setAGC.setDrawIcons(false); + setAGC.setDrawValues(false); + //setAGC.setValueTextColor(getColor(R.color.agc)); + //setAGC.setValueTextSize(10f); + setAGC.setAxisDependency(YAxis.AxisDependency.RIGHT); + + d.addDataSet(setAGC); + return d; + } + + private void osmMapSetup() { + osmMap = findViewById(R.id.maposm); + + RotationGestureOverlay mRotationGestureOverlay = new RotationGestureOverlay(osmMap); + mRotationGestureOverlay.setEnabled(true); + osmMap.getOverlays().add(mRotationGestureOverlay); + osmMap.setBuiltInZoomControls(false); + osmMap.setMultiTouchControls(true); //needed for pinch zooms + osmMap.setTilesScaledToDpi(true); //scales tiles to the current screen's DPI, helps with readability of labels + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + MenuInflater inflater = getMenuInflater(); + inflater.inflate(R.menu.menu_main, menu); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + // Handle item selection + switch (item.getItemId()) { + case R.id.action_settings: + //TODO + return true; + case R.id.action_about: + startActivity(new Intent(this,AboutActivity.class)); + return true; + default: + return super.onOptionsItemSelected(item); + } + } + + @Override + public void onResume() { + super.onResume(); + if (serviceBound && (torgiService != null)) + torgiService.setListener(this); + } + + @Override + public void onPause() { + if (torgiService != null) + torgiService.setListener(null); + super.onPause(); + saveLastLocation(); + } + + @Override + public void onStop() { + super.onStop(); + if (serviceBound && (torgiService != null)) { + try { + unbindService(mConnection); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + + protected ServiceConnection mConnection = new ServiceConnection() { + @Override + public void onServiceConnected(ComponentName className, IBinder service) { + Log.d(TAG,"MdxService bound to this activity"); + TorgiService.TorgiBinder binder = (TorgiService.TorgiBinder) service; + torgiService = binder.getService(); + serviceBound = true; + onTorgiServiceConnected(); + } + + @Override + public void onServiceDisconnected(ComponentName arg0) { + serviceBound = false; + } + }; + + private void onTorgiServiceConnected() { + torgiService.setListener(this); + } + + private void startService() { + if (serviceBound) + torgiService.start(); + else { + startService(new Intent(this, TorgiService.class)); + Intent intent = new Intent(this, TorgiService.class); + bindService(intent, mConnection, Context.BIND_AUTO_CREATE); + } + } + + private void drawMarker(LatLng pos, String info) { + if (pos != null) { + if (history == null) + history = new ArrayList<>(); + history.add(pos); + if (history.size() > 1) { + if (history.size() > MAX_HISTORY_LENGTH) + history.remove(0); + + if (historyPolylineOSM == null) { + historyPolylineOSM = new org.osmdroid.views.overlay.Polyline(); + ArrayList list = new ArrayList<>(); + for (LatLng pt:history) { + list.add(new GeoPoint(pt.latitude,pt.longitude)); + } + historyPolylineOSM.setPoints(list); + historyPolylineOSM.setColor(Color.YELLOW); + osmMap.getOverlays().add(historyPolylineOSM); + } else { + historyPolylineOSM.addPoint(new GeoPoint(pos.latitude, pos.longitude)); + if (historyPolylineOSM.getPoints().size() > MAX_HISTORY_LENGTH) + historyPolylineOSM.getPoints().remove(0); + } + } + + if (currentOSM == null) { + currentOSM = new org.osmdroid.views.overlay.Marker(osmMap); + currentOSM.setPosition(new GeoPoint(pos.latitude,pos.longitude)); + currentOSM.setAnchor(org.osmdroid.views.overlay.Marker.ANCHOR_CENTER,org.osmdroid.views.overlay.Marker.ANCHOR_CENTER); + currentOSM.setIcon(getResources().getDrawable(R.drawable.map_icon)); + currentOSM.setTitle("GPS"); + osmMap.getOverlays().add(currentOSM); + if (osmMap != null) { + osmMap.getController().setZoom(18d); + osmMap.setExpectedCenter(new GeoPoint(pos.latitude, pos.longitude)); + } + } else { + currentOSM.setPosition(new GeoPoint(pos.latitude,pos.longitude)); + osmMap.invalidate(); + } + } + } + + /** + * Request battery optimization exception so that the system doesn't throttle back our app + */ + private void openBatteryOptimizationDialogIfNeeded() { + if (isOptimizingBattery() && isAllowAskAboutBattery()) { + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setTitle(R.string.enable_battery_optimization); + builder.setMessage(R.string.battery_optimizations_narrative); + builder.setPositiveButton(R.string.battery_optimize_yes, (dialog, which) -> { + Intent intent = new Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS); + Uri uri = Uri.parse("package:" + getPackageName()); + intent.setData(uri); + try { + startActivityForResult(intent, REQUEST_DISABLE_BATTERY_OPTIMIZATION); + } catch (ActivityNotFoundException e) { + Toast.makeText(this, R.string.does_not_support_battery_optimization, Toast.LENGTH_SHORT).show(); + } + }); + builder.setOnDismissListener(dialog -> setNeverAskBatteryOptimize()); + final AlertDialog dialog = builder.create(); + dialog.setCanceledOnTouchOutside(false); + dialog.show(); + } + } + + protected boolean isOptimizingBattery() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + final PowerManager pm = (PowerManager) getSystemService(POWER_SERVICE); + return pm != null && !pm.isIgnoringBatteryOptimizations(getPackageName()); + } else + return false; + } + + private boolean isAllowAskAboutBattery() { + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getApplicationContext()); + return !prefs.getBoolean(PREF_BATTERY_OPT_IGNORE,false); + } + + private void setNeverAskBatteryOptimize() { + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getApplicationContext()); + SharedPreferences.Editor edit = prefs.edit(); + edit.putBoolean(PREF_BATTERY_OPT_IGNORE,true); + edit.apply(); + } + + @Override + public void onActivityResult(int requestCode, int resultCode, final Intent data) { + super.onActivityResult(requestCode, resultCode, data); + switch (requestCode) { + case REQUEST_DISABLE_BATTERY_OPTIMIZATION: + setNeverAskBatteryOptimize(); + break; + } + } + + @Override + public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) { + if (requestCode == PERM_REQUEST_CODE) { + if (checkPermissions()) + startService(); + else { + Toast.makeText(this, "Both Location and Storage permissions are needed", Toast.LENGTH_LONG).show(); + } + } + } + + /** + * Returns true once all required permissions are granted, otherwise returns false and requests the + * required permission + * @return + */ + private boolean checkPermissions() { + ArrayList needed = new ArrayList<>(); + if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) + needed.add(Manifest.permission.ACCESS_FINE_LOCATION); + if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) + needed.add(Manifest.permission.WRITE_EXTERNAL_STORAGE); + if (needed.isEmpty()) + return true; + else { + String[] perms = new String[needed.size()]; + perms = new String[perms.length]; + for (int i=0;i lastChartUpdate + MAX_CHART_UPDATE_RATE) { + if (event != null) { + final Collection measurements = event.getMeasurements(); + if (measurements != null) { + lastChartUpdate = System.currentTimeMillis(); + DataPoint dp = new DataPoint(new SpaceTime(currentLoc)); + for (GnssMeasurement measurement : measurements) { + Satellite sat = new Satellite(Constellation.get(measurement.getConstellationType()), measurement.getSvid()); + SatMeasurement satMeasurement; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) + satMeasurement = new SatMeasurement(sat, new GNSSEWValues((float) measurement.getCn0DbHz(), measurement.getAutomaticGainControlLevelDb())); + else + satMeasurement = new SatMeasurement(sat, new GNSSEWValues((float) measurement.getCn0DbHz(), GNSSEWValues.NA)); + dp.add(satMeasurement); + } + GNSSEWValues avg = dp.getAverageMeasurements(); + if (avg != null) + addChartEntry(dp.getSpaceTime().getTime(), avg); + } + } + } + } + + private void addChartEntry(final long time, final GNSSEWValues values) { + if ((time > 0l) && (values != null)) { + runOnUiThread(() -> { + Log.d(TAG,"Chart update #"+(int)chartIndex); + Entry entryCN0 = null; + BarEntry entryAGC = null; + boolean updatedAGC = false; + boolean updatedCN0 = false; + if (!Double.isNaN(values.getAgc())) { + //entryAGC = new BarEntry((float) time, (float) values.getAgc()); + entryAGC = new BarEntry(chartIndex, (float) values.getAgc()); + if (setAGC != null) { + setAGC.addEntry(entryAGC); + if (setAGC.getValues().size() > MAX_HISTORY_LENGTH) + setAGC.removeFirst(); + setAGC.notifyDataSetChanged(); + } + updatedAGC = true; + } + if (!Float.isNaN(values.getCn0())) { + //entryCN0 = new Entry((float)time,values.getCn0()); + entryCN0 = new Entry(chartIndex,values.getCn0()); + if (setCN0 != null) { + setCN0.addEntry(entryCN0); + if (setCN0.getValues().size() > MAX_HISTORY_LENGTH) + setCN0.removeFirst(); + setCN0.notifyDataSetChanged(); + } + updatedCN0 = true; + } + if ((chartEW == null) && updatedAGC && updatedCN0) { + setupEWchart(entryCN0,entryAGC); + } + if (updatedAGC || updatedCN0) { + chartData.notifyDataChanged(); + chartEW.notifyDataSetChanged(); + chartEW.invalidate(); + chartIndex += 1f; + } + }); + } + } + + @Override + public void onLocationChanged(final Location loc) { + runOnUiThread(() -> { + currentLoc = loc; + drawMarker(new LatLng(loc.getLatitude(), loc.getLongitude()),fmtTime.format(loc.getTime())+", ±"+(loc.hasAccuracy()?fmtAccuracy.format(loc.getAccuracy()):"")+"m"); + int sats = loc.getExtras().getInt("satellites"); + StringBuffer label = new StringBuffer(); + if (sats > 0) + label.append(sats+" satellites in fix"); + if (loc.hasAccuracy()) { + if (sats > 0) + label.append(", "); + label.append("±" + (int)loc.getAccuracy() + "m"); + } + textOverview.setText(label.toString()); + }); + } + + @Override + public void onProviderChanged(final String provider, final boolean enabled) { + runOnUiThread(() -> { + //TODO + }); + } +} \ No newline at end of file diff --git a/torgi/src/main/java/org/sofwerx/torgi/util/Acknowledgements.java b/torgi/src/main/java/org/sofwerx/torgi/util/Acknowledgements.java new file mode 100644 index 0000000..f228545 --- /dev/null +++ b/torgi/src/main/java/org/sofwerx/torgi/util/Acknowledgements.java @@ -0,0 +1,51 @@ +package org.sofwerx.torgi.util; + +public class Acknowledgements { + private final static String[] CREDITS = { + "Charts generated from MPAndroidChart, Phillip Jahoda licensed under Apache License, Version 2.0", + "GNSS icon derived from The Noun Project, Weltenraser licensed under Creative Commons" + }; + + public final static String getCredits() { + StringBuffer out = new StringBuffer(); + boolean first = true; + for (String credit:CREDITS) { + if (first) + first = false; + else + out.append("
"); + out.append("• "); + out.append(credit); + } + return out.toString(); + } + + public final static String getLicenses() { + StringBuffer out = new StringBuffer(); + boolean first = true; + for (String license:LICENSES) { + if (first) + first = false; + else + out.append("

"); + out.append(license); + } + return out.toString(); + } + + public final static String[] LICENSES = { + "Some code derived from:
MPAndroidChart
"+ + "
======================================
" + + "
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.
" + + //"
=========================================================" + "
======================================" + }; +} diff --git a/torgi/src/main/res/drawable/icon_expandable_white.xml b/torgi/src/main/res/drawable/icon_expandable_white.xml new file mode 100644 index 0000000..3d57778 --- /dev/null +++ b/torgi/src/main/res/drawable/icon_expandable_white.xml @@ -0,0 +1,5 @@ + + + diff --git a/torgi/src/main/res/drawable/icon_expanded_white.xml b/torgi/src/main/res/drawable/icon_expanded_white.xml new file mode 100644 index 0000000..ff5a73f --- /dev/null +++ b/torgi/src/main/res/drawable/icon_expanded_white.xml @@ -0,0 +1,5 @@ + + + diff --git a/torgi/src/main/res/drawable/icon_gnns.xml b/torgi/src/main/res/drawable/icon_gnns.xml new file mode 100644 index 0000000..d292b4d --- /dev/null +++ b/torgi/src/main/res/drawable/icon_gnns.xml @@ -0,0 +1,4 @@ + + + diff --git a/torgi/src/main/res/drawable/icon_monitor.xml b/torgi/src/main/res/drawable/icon_monitor.xml new file mode 100644 index 0000000..13cfc8f --- /dev/null +++ b/torgi/src/main/res/drawable/icon_monitor.xml @@ -0,0 +1,5 @@ + + + diff --git a/torgi/src/main/res/layout/about_activity.xml b/torgi/src/main/res/layout/about_activity.xml new file mode 100644 index 0000000..3d1c157 --- /dev/null +++ b/torgi/src/main/res/layout/about_activity.xml @@ -0,0 +1,103 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/torgi/src/main/res/layout/activity_main.xml b/torgi/src/main/res/layout/activity_main.xml index 5994d1a..c3f9691 100644 --- a/torgi/src/main/res/layout/activity_main.xml +++ b/torgi/src/main/res/layout/activity_main.xml @@ -1,6 +1,5 @@ - + android:layout_height="wrap_content"> + + + + + + + + + + + + + + \ No newline at end of file diff --git a/torgi/src/main/res/menu/menu_main.xml b/torgi/src/main/res/menu/menu_main.xml index 06c8560..474f89c 100644 --- a/torgi/src/main/res/menu/menu_main.xml +++ b/torgi/src/main/res/menu/menu_main.xml @@ -7,4 +7,9 @@ android:orderInCategory="100" android:title="@string/action_settings" app:showAsAction="never" /> + diff --git a/torgi/src/main/res/values/colors.xml b/torgi/src/main/res/values/colors.xml index 6641156..f148400 100644 --- a/torgi/src/main/res/values/colors.xml +++ b/torgi/src/main/res/values/colors.xml @@ -3,4 +3,6 @@ #3F51B5 #303f9f #FF4081 + #FFAAAA + #FFFFAA diff --git a/torgi/src/main/res/values/strings.xml b/torgi/src/main/res/values/strings.xml index 3aa77ff..93964b3 100644 --- a/torgi/src/main/res/values/strings.xml +++ b/torgi/src/main/res/values/strings.xml @@ -12,4 +12,10 @@ Settings adjustment recommended Can I turn off battery optimization for TORGI? This will allow better updates from sensors but will drain the battery a bit more. Ok + + + Legal + Acknowledgements + Licenses + ©2018 SOFWERX, all rights reserved \ No newline at end of file diff --git a/torgi/src/main/res/values/styles.xml b/torgi/src/main/res/values/styles.xml index a14f40c..8b1c927 100644 --- a/torgi/src/main/res/values/styles.xml +++ b/torgi/src/main/res/values/styles.xml @@ -15,9 +15,9 @@ @android:color/background_dark - + -->