feat: Add Wear OS Interface for GPSTest (#605)
* Add Wear OS Interface GPSTest * Change the module and package name to wear * Refactor packages to gpstest * Change Time format * Implement library in gpstest * Move common data from app to library with cross-module dependency injections failures * Solve the cross-module dependency injections problem about context * Watch face with flashed problem * Implement progress bar * Add permissions request * Resolve column overlap * Add progress bar for Wear OS * Update data from location on Wear OS * Update "# Sats", "PDOP" and "H/V Dop" on Wear OS. * Update timestamp from location. * fix: OSMDroid build flavor * Fix test bugs. Co-authored-by: barbeau <barbeaus@google.com>
@@ -4,7 +4,7 @@ apply plugin: 'kotlin-kapt'
|
||||
apply plugin: 'dagger.hilt.android.plugin'
|
||||
|
||||
android {
|
||||
compileSdkVersion 31
|
||||
compileSdkVersion 33
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion 24
|
||||
@@ -104,6 +104,9 @@ android {
|
||||
}
|
||||
}
|
||||
}
|
||||
dataBinding {
|
||||
enabled = true
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
@@ -119,10 +122,11 @@ android {
|
||||
}
|
||||
|
||||
composeOptions {
|
||||
kotlinCompilerExtensionVersion '1.1.0-beta02'
|
||||
kotlinCompilerExtensionVersion '1.1.0-rc02'
|
||||
}
|
||||
|
||||
buildFeatures {
|
||||
dataBinding true
|
||||
viewBinding true
|
||||
}
|
||||
|
||||
@@ -167,6 +171,7 @@ dependencies {
|
||||
|
||||
// Multidex - Needed for APIs < 21
|
||||
implementation 'androidx.multidex:multidex:2.0.1'
|
||||
implementation project(path: ':library')
|
||||
|
||||
// To observe flows via co-routines within the Service
|
||||
def lifecycle_version = "2.4.0-rc01"
|
||||
@@ -195,7 +200,7 @@ dependencies {
|
||||
|
||||
// Integration with activities
|
||||
implementation 'androidx.activity:activity-compose:1.4.0'
|
||||
implementation "androidx.compose.compiler:compiler:1.1.0-beta02"
|
||||
implementation "androidx.compose.compiler:compiler:1.1.0-rc02"
|
||||
// Compose Material Design
|
||||
implementation 'androidx.compose.material:material:1.0.5'
|
||||
// Bridging XML themes to Compose
|
||||
@@ -205,7 +210,7 @@ dependencies {
|
||||
// Tooling support (Previews, etc.)
|
||||
implementation 'androidx.compose.ui:ui-tooling:1.0.5'
|
||||
// Integration with ViewModels
|
||||
implementation 'androidx.lifecycle:lifecycle-viewmodel-compose:2.4.0'
|
||||
implementation 'androidx.lifecycle:lifecycle-viewmodel-compose:2.5.1'
|
||||
//Integration with LiveData
|
||||
implementation 'androidx.compose.runtime:runtime-livedata:1.1.0-beta02'
|
||||
// UI Tests
|
||||
|
||||
@@ -18,12 +18,12 @@ package com.android.gpstest
|
||||
|
||||
import android.os.Build
|
||||
import androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner
|
||||
import com.android.gpstest.model.GnssType
|
||||
import com.android.gpstest.model.SatelliteStatus
|
||||
import com.android.gpstest.model.SbasType
|
||||
import com.android.gpstest.util.CarrierFreqUtils
|
||||
import com.android.gpstest.util.CarrierFreqUtils.CF_UNKNOWN
|
||||
import com.android.gpstest.util.CarrierFreqUtils.CF_UNSUPPORTED
|
||||
import com.android.gpstest.library.model.GnssType
|
||||
import com.android.gpstest.library.model.SatelliteStatus
|
||||
import com.android.gpstest.library.model.SbasType
|
||||
import com.android.gpstest.library.util.CarrierFreqUtils
|
||||
import com.android.gpstest.library.util.CarrierFreqUtils.CF_UNKNOWN
|
||||
import com.android.gpstest.library.util.CarrierFreqUtils.CF_UNSUPPORTED
|
||||
import junit.framework.Assert.assertEquals
|
||||
import org.junit.Assert.assertFalse
|
||||
import org.junit.Assert.assertTrue
|
||||
|
||||
@@ -20,14 +20,14 @@ import android.location.Location
|
||||
import android.os.Build
|
||||
import androidx.test.filters.SdkSuppress
|
||||
import androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner
|
||||
import com.android.gpstest.model.GnssType
|
||||
import com.android.gpstest.model.Orientation
|
||||
import com.android.gpstest.model.SatelliteStatus
|
||||
import com.android.gpstest.util.FormatUtils.toLog
|
||||
import com.android.gpstest.util.IOUtils
|
||||
import com.android.gpstest.util.SatelliteUtil.isBearingAccuracySupported
|
||||
import com.android.gpstest.util.SatelliteUtil.isSpeedAccuracySupported
|
||||
import com.android.gpstest.util.SatelliteUtil.isVerticalAccuracySupported
|
||||
import com.android.gpstest.library.model.GnssType
|
||||
import com.android.gpstest.library.model.Orientation
|
||||
import com.android.gpstest.library.model.SatelliteStatus
|
||||
import com.android.gpstest.library.util.FormatUtils.toLog
|
||||
import com.android.gpstest.library.util.IOUtils
|
||||
import com.android.gpstest.library.util.SatelliteUtil.isBearingAccuracySupported
|
||||
import com.android.gpstest.library.util.SatelliteUtil.isSpeedAccuracySupported
|
||||
import com.android.gpstest.library.util.SatelliteUtil.isVerticalAccuracySupported
|
||||
import junit.framework.Assert
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Test
|
||||
|
||||
@@ -17,8 +17,9 @@ package com.android.gpstest
|
||||
|
||||
import android.content.Intent
|
||||
import android.location.Location
|
||||
import androidx.test.InstrumentationRegistry.getTargetContext
|
||||
import androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner
|
||||
import com.android.gpstest.util.IOUtils
|
||||
import com.android.gpstest.library.util.IOUtils
|
||||
import junit.framework.Assert.*
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
@@ -37,11 +38,11 @@ class IOUtilsTest {
|
||||
fun testIsShowRadarIntent() {
|
||||
// SHOW_RADAR intent
|
||||
val intent = Intent("com.google.android.radar.SHOW_RADAR")
|
||||
assertTrue(IOUtils.isShowRadarIntent(intent))
|
||||
assertTrue(IOUtils.isShowRadarIntent(getTargetContext(),intent))
|
||||
|
||||
// Not SHOW_RADAR intents
|
||||
assertFalse(IOUtils.isShowRadarIntent(Intent("not.show.radar.intent")))
|
||||
assertFalse(IOUtils.isShowRadarIntent(null));
|
||||
assertFalse(IOUtils.isShowRadarIntent(getTargetContext(),Intent("not.show.radar.intent")))
|
||||
assertFalse(IOUtils.isShowRadarIntent(getTargetContext(),null));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -58,7 +59,7 @@ class IOUtilsTest {
|
||||
intent.putExtra("latitude", 28.0527222f)
|
||||
intent.putExtra("longitude", -82.4331001f)
|
||||
|
||||
val location = IOUtils.getLocationFromIntent(intent)
|
||||
val location = IOUtils.getLocationFromIntent(getTargetContext(),intent)
|
||||
assertEquals(28.0527222, location.latitude, delta)
|
||||
assertEquals(-82.433100, location.longitude, delta)
|
||||
assertFalse(location.hasAltitude())
|
||||
@@ -69,7 +70,7 @@ class IOUtilsTest {
|
||||
intentWithAltitude.putExtra("longitude", -82.4331001f)
|
||||
intentWithAltitude.putExtra("altitude", 20.3f)
|
||||
|
||||
val locationWithAltitude = IOUtils.getLocationFromIntent(intentWithAltitude)
|
||||
val locationWithAltitude = IOUtils.getLocationFromIntent(getTargetContext(),intentWithAltitude)
|
||||
assertEquals(28.0527222, locationWithAltitude.latitude, delta)
|
||||
assertEquals(-82.433100, locationWithAltitude.longitude, delta)
|
||||
assertEquals(20.3, locationWithAltitude.altitude, delta)
|
||||
@@ -79,7 +80,7 @@ class IOUtilsTest {
|
||||
intentDouble.putExtra("latitude", 28.0527222)
|
||||
intentDouble.putExtra("longitude", -82.4331001)
|
||||
|
||||
val locationDouble = IOUtils.getLocationFromIntent(intentDouble)
|
||||
val locationDouble = IOUtils.getLocationFromIntent(getTargetContext(), intentDouble)
|
||||
assertEquals(28.0527222, locationDouble.latitude, delta)
|
||||
assertEquals(-82.433100, locationDouble.longitude, delta)
|
||||
assertFalse(locationDouble.hasAltitude())
|
||||
@@ -90,7 +91,7 @@ class IOUtilsTest {
|
||||
intentDoubleWithAltitude.putExtra("longitude", -82.4331001)
|
||||
intentDoubleWithAltitude.putExtra("altitude", 20.3)
|
||||
|
||||
val locationDoubleWithAltitude = IOUtils.getLocationFromIntent(intentDoubleWithAltitude)
|
||||
val locationDoubleWithAltitude = IOUtils.getLocationFromIntent(getTargetContext(), intentDoubleWithAltitude)
|
||||
assertEquals(28.0527222, locationDoubleWithAltitude.latitude, delta)
|
||||
assertEquals(-82.433100, locationDoubleWithAltitude.longitude, delta)
|
||||
assertEquals(20.3, locationDoubleWithAltitude.altitude, delta)
|
||||
@@ -101,7 +102,7 @@ class IOUtilsTest {
|
||||
intentNullAltitude.putExtra("longitude", -82.4331001)
|
||||
intentNullAltitude.putExtra("altitude", Double.NaN)
|
||||
|
||||
val locationNullAltitude = IOUtils.getLocationFromIntent(intentNullAltitude)
|
||||
val locationNullAltitude = IOUtils.getLocationFromIntent(getTargetContext(), intentNullAltitude)
|
||||
assertEquals(28.0527222, locationNullAltitude.latitude, delta)
|
||||
assertEquals(-82.433100, locationNullAltitude.longitude, delta)
|
||||
assertFalse(locationNullAltitude.hasAltitude())
|
||||
@@ -112,7 +113,7 @@ class IOUtilsTest {
|
||||
intentDoubleWithFloatAltitude.putExtra("longitude", -82.4331001)
|
||||
intentDoubleWithFloatAltitude.putExtra("altitude", 20.3f)
|
||||
|
||||
val locationDoubleWithFloatAltitude = IOUtils.getLocationFromIntent(intentDoubleWithFloatAltitude)
|
||||
val locationDoubleWithFloatAltitude = IOUtils.getLocationFromIntent(getTargetContext(),intentDoubleWithFloatAltitude)
|
||||
assertEquals(28.0527222, locationDoubleWithFloatAltitude.latitude, delta)
|
||||
assertEquals(-82.433100, locationDoubleWithFloatAltitude.longitude, delta)
|
||||
assertEquals(20.3, locationDoubleWithFloatAltitude.altitude, delta)
|
||||
@@ -123,12 +124,12 @@ class IOUtilsTest {
|
||||
*/
|
||||
@Test
|
||||
fun testCreateShowRadarIntent() {
|
||||
val resultNoAltitude = IOUtils.createShowRadarIntent(24.5253, 87.23434, null)
|
||||
val resultNoAltitude = IOUtils.createShowRadarIntent(getTargetContext(),24.5253, 87.23434, null)
|
||||
assertEquals(24.5253, resultNoAltitude?.extras?.get("latitude"))
|
||||
assertEquals(87.23434, resultNoAltitude?.extras?.get("longitude"))
|
||||
assertFalse(resultNoAltitude.hasExtra("altitude"))
|
||||
|
||||
val resultWithAltitude = IOUtils.createShowRadarIntent(24.5253, 87.23434, 15.5)
|
||||
val resultWithAltitude = IOUtils.createShowRadarIntent(getTargetContext(),24.5253, 87.23434, 15.5)
|
||||
assertEquals(24.5253, resultWithAltitude.extras?.get("latitude"))
|
||||
assertEquals(87.23434, resultWithAltitude.extras?.get("longitude"))
|
||||
assertEquals(15.5, resultWithAltitude.extras?.get("altitude"))
|
||||
@@ -137,7 +138,7 @@ class IOUtilsTest {
|
||||
locationNoAltitude.latitude = -20.8373
|
||||
locationNoAltitude.longitude = -120.8273
|
||||
|
||||
val resultFromLocationNoAltitude = IOUtils.createShowRadarIntent(locationNoAltitude)
|
||||
val resultFromLocationNoAltitude = IOUtils.createShowRadarIntent(getTargetContext(),locationNoAltitude)
|
||||
assertEquals(-20.8373, resultFromLocationNoAltitude?.extras?.get("latitude"))
|
||||
assertEquals(-120.8273, resultFromLocationNoAltitude?.extras?.get("longitude"))
|
||||
assertFalse(resultNoAltitude.hasExtra("altitude"))
|
||||
@@ -147,7 +148,7 @@ class IOUtilsTest {
|
||||
locationWithAltitude.longitude = -126.8273
|
||||
locationWithAltitude.altitude = -13.5
|
||||
|
||||
val resultFromLocationWithAltitude = IOUtils.createShowRadarIntent(locationWithAltitude)
|
||||
val resultFromLocationWithAltitude = IOUtils.createShowRadarIntent(getTargetContext(),locationWithAltitude)
|
||||
assertEquals(-26.8373, resultFromLocationWithAltitude.extras?.get("latitude"))
|
||||
assertEquals(-126.8273, resultFromLocationWithAltitude.extras?.get("longitude"))
|
||||
assertEquals(-13.5, resultFromLocationWithAltitude.extras?.get("altitude"))
|
||||
@@ -160,60 +161,60 @@ class IOUtilsTest {
|
||||
@Test
|
||||
fun testGetLocationFromGeoUri() {
|
||||
val geoUriLatLon = "geo:37.786971,-122.399677"
|
||||
val result1 = IOUtils.getLocationFromGeoUri(geoUriLatLon)
|
||||
val result1 = IOUtils.getLocationFromGeoUri(getTargetContext(),geoUriLatLon)
|
||||
assertEquals(37.786971, result1.latitude)
|
||||
assertEquals(-122.399677, result1.longitude)
|
||||
assertFalse(result1.hasAltitude())
|
||||
|
||||
val geoUriLatLonAlt = "geo:-28.9876,87.1937,15"
|
||||
val result2 = IOUtils.getLocationFromGeoUri(geoUriLatLonAlt)
|
||||
val result2 = IOUtils.getLocationFromGeoUri(getTargetContext(),geoUriLatLonAlt)
|
||||
assertEquals(-28.9876, result2.latitude)
|
||||
assertEquals(87.1937, result2.longitude)
|
||||
assertEquals(15.0, result2.altitude)
|
||||
|
||||
val geoUriLatLonZoom = "geo:-28.9876,87.1937?z=14"
|
||||
val resultWithZoom = IOUtils.getLocationFromGeoUri(geoUriLatLonZoom)
|
||||
val resultWithZoom = IOUtils.getLocationFromGeoUri(getTargetContext(),geoUriLatLonZoom)
|
||||
assertEquals(-28.9876, resultWithZoom.latitude)
|
||||
assertEquals(87.1937, resultWithZoom.longitude)
|
||||
assertFalse(resultWithZoom.hasAltitude())
|
||||
|
||||
val geoUriLatLonAltZoom = "geo:-28.9876,87.1937,15?z=14"
|
||||
val resultWithAltZoom = IOUtils.getLocationFromGeoUri(geoUriLatLonAltZoom)
|
||||
val resultWithAltZoom = IOUtils.getLocationFromGeoUri(getTargetContext(), geoUriLatLonAltZoom)
|
||||
assertEquals(-28.9876, resultWithAltZoom.latitude)
|
||||
assertEquals(87.1937, resultWithAltZoom.longitude)
|
||||
assertEquals(15.0, resultWithAltZoom.altitude)
|
||||
assertTrue(resultWithAltZoom.hasAltitude())
|
||||
|
||||
val geoUriLatLonCrs = "geo:32.3482,43.06480;crs=EPSG:32618"
|
||||
val resultLatLonCrs = IOUtils.getLocationFromGeoUri(geoUriLatLonCrs)
|
||||
val resultLatLonCrs = IOUtils.getLocationFromGeoUri(getTargetContext(),geoUriLatLonCrs)
|
||||
assertEquals(32.3482, resultLatLonCrs.latitude)
|
||||
assertEquals(43.06480, resultLatLonCrs.longitude)
|
||||
assertFalse(resultLatLonCrs.hasAltitude())
|
||||
|
||||
val geoUriLatLonAltCrs = "geo:32.3482,43.06480,15;crs=EPSG:32618"
|
||||
val resultLatLonAltCrs = IOUtils.getLocationFromGeoUri(geoUriLatLonAltCrs)
|
||||
val resultLatLonAltCrs = IOUtils.getLocationFromGeoUri(getTargetContext(),geoUriLatLonAltCrs)
|
||||
assertEquals(32.3482, resultLatLonAltCrs.latitude)
|
||||
assertEquals(43.06480, resultLatLonAltCrs.longitude)
|
||||
assertEquals(15.0, resultLatLonAltCrs.altitude)
|
||||
assertTrue(resultLatLonAltCrs.hasAltitude())
|
||||
|
||||
val invalidGeoUri = "http://not.a.geo.uri"
|
||||
val result3 = IOUtils.getLocationFromGeoUri(invalidGeoUri)
|
||||
val result3 = IOUtils.getLocationFromGeoUri(getTargetContext(), invalidGeoUri)
|
||||
assertNull(result3)
|
||||
|
||||
val invalidLatLon = "geo:-999.9876,999.1937"
|
||||
val result4 = IOUtils.getLocationFromGeoUri(invalidLatLon)
|
||||
val result4 = IOUtils.getLocationFromGeoUri(getTargetContext(),invalidLatLon)
|
||||
assertNull(result4)
|
||||
|
||||
val result5 = IOUtils.getLocationFromGeoUri(null)
|
||||
val result5 = IOUtils.getLocationFromGeoUri(getTargetContext(), null)
|
||||
assertNull(result5)
|
||||
|
||||
val invalidData2 = ""
|
||||
val result6 = IOUtils.getLocationFromGeoUri(invalidData2)
|
||||
val result6 = IOUtils.getLocationFromGeoUri(getTargetContext(), invalidData2)
|
||||
assertNull(result6)
|
||||
|
||||
val invalidGeoUri2 = "http://not,a,geo,uri"
|
||||
val result7 = IOUtils.getLocationFromGeoUri(invalidGeoUri2)
|
||||
val result7 = IOUtils.getLocationFromGeoUri(getTargetContext(),invalidGeoUri2)
|
||||
assertNull(result7)
|
||||
}
|
||||
|
||||
@@ -225,17 +226,17 @@ class IOUtilsTest {
|
||||
val l = Location("geouri-no-alt")
|
||||
l.latitude = 28.12345
|
||||
l.longitude = -82.1345
|
||||
val geoUri = IOUtils.createGeoUri(l, true)
|
||||
val geoUri = IOUtils.createGeoUri(getTargetContext(), l, true)
|
||||
assertEquals("geo:28.12345,-82.1345", geoUri)
|
||||
|
||||
val lAlt = Location("geouri-with-alt")
|
||||
lAlt.latitude = 28.12345
|
||||
lAlt.longitude = -82.1345
|
||||
lAlt.altitude = 104.2
|
||||
val geoUriWithAlt = IOUtils.createGeoUri(lAlt, true)
|
||||
val geoUriWithAlt = IOUtils.createGeoUri(getTargetContext(),lAlt, true)
|
||||
assertEquals("geo:28.12345,-82.1345,104.2", geoUriWithAlt)
|
||||
|
||||
val geoUriAltExcluded = IOUtils.createGeoUri(lAlt, false)
|
||||
val geoUriAltExcluded = IOUtils.createGeoUri(getTargetContext(), lAlt, false)
|
||||
assertEquals("geo:28.12345,-82.1345", geoUriAltExcluded)
|
||||
}
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ import android.content.res.Resources;
|
||||
|
||||
import androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner;
|
||||
|
||||
import com.android.gpstest.util.LocationUtils;
|
||||
import com.android.gpstest.library.util.LocationUtils;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
@@ -17,7 +17,7 @@ package com.android.gpstest;
|
||||
|
||||
import androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner;
|
||||
|
||||
import com.android.gpstest.util.MathUtils;
|
||||
import com.android.gpstest.library.util.MathUtils;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
@@ -18,10 +18,10 @@ package com.android.gpstest
|
||||
import android.location.GnssMeasurement.*
|
||||
import android.os.Build
|
||||
import androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner
|
||||
import com.android.gpstest.model.GnssType
|
||||
import com.android.gpstest.model.SatelliteStatus
|
||||
import com.android.gpstest.model.SbasType
|
||||
import com.android.gpstest.util.SatelliteUtils
|
||||
import com.android.gpstest.library.model.GnssType
|
||||
import com.android.gpstest.library.model.SatelliteStatus
|
||||
import com.android.gpstest.library.model.SbasType
|
||||
import com.android.gpstest.library.util.SatelliteUtils
|
||||
import org.junit.Assert.*
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
@@ -17,14 +17,17 @@
|
||||
package com.android.gpstest
|
||||
|
||||
import android.app.Application
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import android.preference.PreferenceManager
|
||||
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
|
||||
import androidx.test.InstrumentationRegistry
|
||||
import androidx.test.InstrumentationRegistry.getTargetContext
|
||||
import androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner
|
||||
import com.android.gpstest.data.*
|
||||
import com.android.gpstest.model.GnssType
|
||||
import com.android.gpstest.model.SbasType
|
||||
import com.android.gpstest.ui.SignalInfoViewModel
|
||||
import com.android.gpstest.library.data.*
|
||||
import com.android.gpstest.library.model.GnssType
|
||||
import com.android.gpstest.library.model.SbasType
|
||||
import com.android.gpstest.library.ui.SignalInfoViewModel
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import org.junit.Assert.*
|
||||
import org.junit.Rule
|
||||
@@ -39,13 +42,13 @@ class SignalInfoViewModelTest {
|
||||
val instantTaskExecutorRule = InstantTaskExecutorRule()
|
||||
|
||||
private val repository = LocationRepository(
|
||||
SharedLocationManager(InstrumentationRegistry.getTargetContext().applicationContext, GlobalScope),
|
||||
SharedGnssStatusManager(InstrumentationRegistry.getTargetContext().applicationContext, GlobalScope),
|
||||
SharedNmeaManager(InstrumentationRegistry.getTargetContext().applicationContext, GlobalScope),
|
||||
SharedSensorManager(InstrumentationRegistry.getTargetContext().applicationContext, GlobalScope),
|
||||
SharedNavMessageManager(InstrumentationRegistry.getTargetContext().applicationContext, GlobalScope),
|
||||
SharedGnssMeasurementManager(InstrumentationRegistry.getTargetContext().applicationContext, GlobalScope),
|
||||
SharedAntennaManager(InstrumentationRegistry.getTargetContext().applicationContext, GlobalScope)
|
||||
SharedLocationManager(InstrumentationRegistry.getTargetContext().applicationContext, GlobalScope, PreferenceManager.getDefaultSharedPreferences(getTargetContext())),
|
||||
SharedGnssStatusManager(InstrumentationRegistry.getTargetContext().applicationContext, GlobalScope, PreferenceManager.getDefaultSharedPreferences(getTargetContext())),
|
||||
SharedNmeaManager(InstrumentationRegistry.getTargetContext().applicationContext, GlobalScope, PreferenceManager.getDefaultSharedPreferences(getTargetContext())),
|
||||
SharedSensorManager(PreferenceManager.getDefaultSharedPreferences(getTargetContext()),InstrumentationRegistry.getTargetContext().applicationContext, GlobalScope),
|
||||
SharedNavMessageManager(InstrumentationRegistry.getTargetContext().applicationContext, GlobalScope, PreferenceManager.getDefaultSharedPreferences(getTargetContext())),
|
||||
SharedGnssMeasurementManager(PreferenceManager.getDefaultSharedPreferences(getTargetContext()), InstrumentationRegistry.getTargetContext().applicationContext, GlobalScope),
|
||||
SharedAntennaManager(InstrumentationRegistry.getTargetContext().applicationContext, GlobalScope, PreferenceManager.getDefaultSharedPreferences(getTargetContext()))
|
||||
)
|
||||
|
||||
/**
|
||||
@@ -53,12 +56,13 @@ class SignalInfoViewModelTest {
|
||||
*/
|
||||
@Test
|
||||
fun testDeviceInfoViewModel() {
|
||||
val modelEmpty = SignalInfoViewModel(InstrumentationRegistry.getTargetContext().applicationContext as Application, repository)
|
||||
modelEmpty.updateStatus(emptyList())
|
||||
val context = getTargetContext()
|
||||
val modelEmpty = SignalInfoViewModel(context, context.applicationContext as Application, repository, PreferenceManager.getDefaultSharedPreferences(context))
|
||||
modelEmpty.updateStatus(context,emptyList(), PreferenceManager.getDefaultSharedPreferences(context))
|
||||
|
||||
// Test GPS L1 - should be 1 satellite, no L5 or dual-frequency
|
||||
val modelGpsL1 = SignalInfoViewModel(InstrumentationRegistry.getTargetContext().applicationContext as Application, repository)
|
||||
modelGpsL1.updateStatus(listOf(gpsL1(1, true)))
|
||||
val modelGpsL1 = SignalInfoViewModel(context, InstrumentationRegistry.getTargetContext().applicationContext as Application, repository, PreferenceManager.getDefaultSharedPreferences(context))
|
||||
modelGpsL1.updateStatus(context, listOf(gpsL1(1, true)), PreferenceManager.getDefaultSharedPreferences(getTargetContext()))
|
||||
assertEquals(1, modelGpsL1.filteredGnssSatellites.value?.size)
|
||||
assertFalse(modelGpsL1.isNonPrimaryCarrierFreqInView)
|
||||
assertFalse(modelGpsL1.isNonPrimaryCarrierFreqInUse)
|
||||
@@ -84,7 +88,7 @@ class SignalInfoViewModelTest {
|
||||
modelGpsL1.reset();
|
||||
|
||||
// Test GPS L1 no signal - should be 1 satellite, no L5 or dual-frequency
|
||||
modelGpsL1.updateStatus(listOf(gpsL1NoSignal(1)))
|
||||
modelGpsL1.updateStatus(context, listOf(gpsL1NoSignal(1)), PreferenceManager.getDefaultSharedPreferences(context))
|
||||
assertEquals(1, modelGpsL1.filteredGnssSatellites.value?.size)
|
||||
assertFalse(modelGpsL1.isNonPrimaryCarrierFreqInView)
|
||||
assertFalse(modelGpsL1.isNonPrimaryCarrierFreqInUse)
|
||||
@@ -109,8 +113,8 @@ class SignalInfoViewModelTest {
|
||||
|
||||
|
||||
// Test GPS L1 + L5 same sv - should be 1 satellite, dual frequency in view and but not in use
|
||||
val modelGpsL1L5 = SignalInfoViewModel(InstrumentationRegistry.getTargetContext().applicationContext as Application, repository)
|
||||
modelGpsL1L5.updateStatus(listOf(gpsL1(1, false), gpsL5(1, true)))
|
||||
val modelGpsL1L5 = SignalInfoViewModel(context, context.applicationContext as Application, repository , PreferenceManager.getDefaultSharedPreferences(context))
|
||||
modelGpsL1L5.updateStatus(context, listOf(gpsL1(1, false), gpsL5(1, true)), PreferenceManager.getDefaultSharedPreferences(context))
|
||||
assertEquals(1, modelGpsL1L5.filteredGnssSatellites.value?.size)
|
||||
assertEquals(1, modelGpsL1L5.getSupportedGnss().size)
|
||||
assertEquals(0, modelGpsL1L5.getSupportedSbas().size)
|
||||
@@ -143,7 +147,7 @@ class SignalInfoViewModelTest {
|
||||
modelGpsL1L5.reset();
|
||||
|
||||
// Test GPS L1 + L5 same sv - should be 1 satellite, dual-frequency in view and use
|
||||
modelGpsL1L5.updateStatus(listOf(gpsL1(1, true), gpsL5(1, true)))
|
||||
modelGpsL1L5.updateStatus(context, listOf(gpsL1(1, true), gpsL5(1, true)), PreferenceManager.getDefaultSharedPreferences(context))
|
||||
assertEquals(1, modelGpsL1L5.filteredGnssSatellites.value?.size)
|
||||
assertEquals(1, modelGpsL1L5.getSupportedGnss().size)
|
||||
assertEquals(0, modelGpsL1L5.getSupportedSbas().size)
|
||||
@@ -176,7 +180,7 @@ class SignalInfoViewModelTest {
|
||||
modelGpsL1L5.reset();
|
||||
|
||||
// Test GPS L1 + L5 same sv - should be 1 satellite, dual-frequency in view and but not used (only 1 sv in use)
|
||||
modelGpsL1L5.updateStatus(listOf(gpsL1(1, true), gpsL5(1, false)))
|
||||
modelGpsL1L5.updateStatus(context, listOf(gpsL1(1, true), gpsL5(1, false)), PreferenceManager.getDefaultSharedPreferences(context))
|
||||
assertEquals(1, modelGpsL1L5.filteredGnssSatellites.value?.size)
|
||||
assertEquals(1, modelGpsL1L5.getSupportedGnss().size)
|
||||
assertEquals(0, modelGpsL1L5.getSupportedSbas().size)
|
||||
@@ -209,7 +213,7 @@ class SignalInfoViewModelTest {
|
||||
modelGpsL1L5.reset();
|
||||
|
||||
// Test GPS L1 + L5 but different satellites - should be 2 satellites, non-primary frequency in view and in use, but not dual-frequency in view or use
|
||||
modelGpsL1L5.updateStatus(listOf(gpsL1(1, true), gpsL5(2, true)))
|
||||
modelGpsL1L5.updateStatus(context, listOf(gpsL1(1, true), gpsL5(2, true)), PreferenceManager.getDefaultSharedPreferences(context))
|
||||
assertEquals(2, modelGpsL1L5.filteredGnssSatellites.value?.size)
|
||||
assertEquals(1, modelGpsL1L5.getSupportedGnss().size)
|
||||
assertEquals(0, modelGpsL1L5.getSupportedSbas().size)
|
||||
@@ -246,7 +250,7 @@ class SignalInfoViewModelTest {
|
||||
modelGpsL1L5.reset();
|
||||
|
||||
// Test GPS L1 + L5 same sv, but no L1 signal - should be 1 satellite, dual-frequency not in view or in use
|
||||
modelGpsL1L5.updateStatus(listOf(gpsL1NoSignal(1), gpsL5(1, true)))
|
||||
modelGpsL1L5.updateStatus(context, listOf(gpsL1NoSignal(1), gpsL5(1, true)), PreferenceManager.getDefaultSharedPreferences(context))
|
||||
assertEquals(1, modelGpsL1L5.filteredGnssSatellites.value?.size)
|
||||
assertEquals(1, modelGpsL1L5.getSupportedGnss().size)
|
||||
assertEquals(0, modelGpsL1L5.getSupportedSbas().size)
|
||||
@@ -279,8 +283,8 @@ class SignalInfoViewModelTest {
|
||||
modelGpsL1L5.reset();
|
||||
|
||||
// Test GPS L5 not in use - should be 1 satellites, non-primary frequency in view, but not dual-frequency in view or use
|
||||
val modelGpsL5 = SignalInfoViewModel(InstrumentationRegistry.getTargetContext().applicationContext as Application, repository)
|
||||
modelGpsL5.updateStatus(listOf(gpsL5(1, false)))
|
||||
val modelGpsL5 = SignalInfoViewModel(context, context.applicationContext as Application, repository, PreferenceManager.getDefaultSharedPreferences(context))
|
||||
modelGpsL5.updateStatus(context, listOf(gpsL5(1, false)), PreferenceManager.getDefaultSharedPreferences(context))
|
||||
assertEquals(1, modelGpsL5.filteredGnssSatellites.value?.size)
|
||||
assertEquals(1, modelGpsL5.getSupportedGnss().size)
|
||||
assertEquals(0, modelGpsL5.getSupportedSbas().size)
|
||||
@@ -314,8 +318,8 @@ class SignalInfoViewModelTest {
|
||||
}
|
||||
|
||||
// Test GPS L1 + GLONASS L1 - should be 2 satellites, no non-primary carrier of dual-freq
|
||||
val modelGpsL1GlonassL1 = SignalInfoViewModel(InstrumentationRegistry.getTargetContext().applicationContext as Application, repository)
|
||||
modelGpsL1GlonassL1.updateStatus(listOf(gpsL1(1, true), glonassL1variant1()))
|
||||
val modelGpsL1GlonassL1 = SignalInfoViewModel(context, context.applicationContext as Application, repository, PreferenceManager.getDefaultSharedPreferences(context))
|
||||
modelGpsL1GlonassL1.updateStatus(context, listOf(gpsL1(1, true), glonassL1variant1()), PreferenceManager.getDefaultSharedPreferences(context))
|
||||
assertEquals(2, modelGpsL1GlonassL1.filteredGnssSatellites.value?.size)
|
||||
assertFalse(modelGpsL1GlonassL1.isNonPrimaryCarrierFreqInView)
|
||||
assertFalse(modelGpsL1GlonassL1.isNonPrimaryCarrierFreqInUse)
|
||||
@@ -340,8 +344,8 @@ class SignalInfoViewModelTest {
|
||||
}
|
||||
|
||||
// Test Galileo E1 + E5a - should be 2 satellites, dual frequency not in use, non-primary carrier of dual-freq
|
||||
val modelGalileoE1E5a = SignalInfoViewModel(InstrumentationRegistry.getTargetContext().applicationContext as Application, repository)
|
||||
modelGalileoE1E5a.updateStatus(listOf(galileoE1(1, true), galileoE5a(2, true)))
|
||||
val modelGalileoE1E5a = SignalInfoViewModel(context, context.applicationContext as Application, repository, PreferenceManager.getDefaultSharedPreferences(context))
|
||||
modelGalileoE1E5a.updateStatus(context, listOf(galileoE1(1, true), galileoE5a(2, true)), PreferenceManager.getDefaultSharedPreferences(context))
|
||||
assertEquals(2, modelGalileoE1E5a.filteredGnssSatellites.value?.size)
|
||||
assertEquals(1, modelGalileoE1E5a.getSupportedGnss().size)
|
||||
assertEquals(0, modelGalileoE1E5a.getSupportedSbas().size)
|
||||
@@ -378,7 +382,7 @@ class SignalInfoViewModelTest {
|
||||
modelGalileoE1E5a.reset()
|
||||
|
||||
// Test Galileo E1 + E5a - should be 1 satellites, dual frequency in use, non-primary carrier of dual-freq
|
||||
modelGalileoE1E5a.updateStatus(listOf(galileoE1(1, true), galileoE5a(1, true)))
|
||||
modelGalileoE1E5a.updateStatus(context, listOf(galileoE1(1, true), galileoE5a(1, true)), PreferenceManager.getDefaultSharedPreferences(context))
|
||||
assertEquals(1, modelGalileoE1E5a.filteredGnssSatellites.value?.size)
|
||||
assertEquals(1, modelGalileoE1E5a.getSupportedGnss().size)
|
||||
assertEquals(0, modelGalileoE1E5a.getSupportedSbas().size)
|
||||
@@ -410,8 +414,8 @@ class SignalInfoViewModelTest {
|
||||
modelGalileoE1E5a.reset()
|
||||
|
||||
// Test WAAS SBAS - L1 - should be 1 satellite, dual frequency not in use, no non-primary carrier of dual-freq
|
||||
val modelWaasL1L5 = SignalInfoViewModel(InstrumentationRegistry.getTargetContext().applicationContext as Application, repository)
|
||||
modelWaasL1L5.updateStatus(listOf(galaxy15_135L1(true)))
|
||||
val modelWaasL1L5 = SignalInfoViewModel(context, InstrumentationRegistry.getTargetContext().applicationContext as Application, repository, PreferenceManager.getDefaultSharedPreferences(context))
|
||||
modelWaasL1L5.updateStatus(context, listOf(galaxy15_135L1(true)), PreferenceManager.getDefaultSharedPreferences(context))
|
||||
assertEquals(1, modelWaasL1L5.filteredSbasSatellites.value?.size)
|
||||
assertFalse(modelWaasL1L5.isNonPrimaryCarrierFreqInView)
|
||||
assertFalse(modelWaasL1L5.isNonPrimaryCarrierFreqInUse)
|
||||
@@ -437,7 +441,7 @@ class SignalInfoViewModelTest {
|
||||
modelWaasL1L5.reset()
|
||||
|
||||
// Test WAAS SBAS - L1 + L5 - should be 1 satellites, dual frequency in use, non-primary carrier of dual-freq
|
||||
modelWaasL1L5.updateStatus(listOf(galaxy15_135L1(true), galaxy15_135L5(true)))
|
||||
modelWaasL1L5.updateStatus(context, listOf(galaxy15_135L1(true), galaxy15_135L5(true)), PreferenceManager.getDefaultSharedPreferences(context))
|
||||
assertEquals(1, modelWaasL1L5.filteredSbasSatellites.value?.size)
|
||||
assertEquals(0, modelWaasL1L5.getSupportedGnss().size)
|
||||
assertEquals(0, modelWaasL1L5.getSupportedGnssCfs().size)
|
||||
|
||||
@@ -15,10 +15,10 @@
|
||||
*/
|
||||
package com.android.gpstest
|
||||
|
||||
import com.android.gpstest.model.GnssType
|
||||
import com.android.gpstest.model.SatelliteStatus
|
||||
import com.android.gpstest.model.SatelliteStatus.Companion.NO_DATA
|
||||
import com.android.gpstest.model.SbasType
|
||||
import com.android.gpstest.library.model.GnssType
|
||||
import com.android.gpstest.library.model.SatelliteStatus
|
||||
import com.android.gpstest.library.model.SatelliteStatus.Companion.NO_DATA
|
||||
import com.android.gpstest.library.model.SbasType
|
||||
|
||||
/**
|
||||
* Returns a status for a GPS NAVSTAR L1 signal
|
||||
|
||||
@@ -23,8 +23,8 @@ import android.content.res.Resources;
|
||||
|
||||
import androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner;
|
||||
|
||||
import com.android.gpstest.model.CoordinateType;
|
||||
import com.android.gpstest.util.UIUtils;
|
||||
import com.android.gpstest.library.model.CoordinateType;
|
||||
import com.android.gpstest.library.util.LibUIUtils;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
@@ -39,13 +39,13 @@ public class UIUtilsAndroidTest {
|
||||
// Test German
|
||||
setLocale("de", "DE");
|
||||
|
||||
String dms = UIUtils.getDMSFromLocation(getTargetContext(), -42.853583, CoordinateType.LATITUDE);
|
||||
String dms = LibUIUtils.getDMSFromLocation(getTargetContext(), -42.853583, CoordinateType.LATITUDE);
|
||||
assertEquals("S\t\u200742° 51' 12,90\"", dms);
|
||||
|
||||
// Test English
|
||||
setLocale("en", "US");
|
||||
|
||||
dms = UIUtils.getDMSFromLocation(getTargetContext(), -42.853583, CoordinateType.LATITUDE);
|
||||
dms = LibUIUtils.getDMSFromLocation(getTargetContext(), -42.853583, CoordinateType.LATITUDE);
|
||||
assertEquals("S\t\u200742° 51' 12.90\"", dms);
|
||||
}
|
||||
|
||||
@@ -54,13 +54,13 @@ public class UIUtilsAndroidTest {
|
||||
// Test German
|
||||
setLocale("de", "DE");
|
||||
|
||||
String dms = UIUtils.getDMSFromLocation(getTargetContext(), 47.64896, CoordinateType.LONGITUDE);
|
||||
String dms = LibUIUtils.getDMSFromLocation(getTargetContext(), 47.64896, CoordinateType.LONGITUDE);
|
||||
assertEquals("E\t047° 38' 56,26\"", dms);
|
||||
|
||||
// Test English
|
||||
setLocale("en", "US");
|
||||
|
||||
dms = UIUtils.getDMSFromLocation(getTargetContext(), 47.64896, CoordinateType.LONGITUDE);
|
||||
dms = LibUIUtils.getDMSFromLocation(getTargetContext(), 47.64896, CoordinateType.LONGITUDE);
|
||||
assertEquals("E\t047° 38' 56.26\"", dms);
|
||||
}
|
||||
|
||||
@@ -69,13 +69,13 @@ public class UIUtilsAndroidTest {
|
||||
// Test German
|
||||
setLocale("de", "DE");
|
||||
|
||||
String ddm = UIUtils.getDDMFromLocation(getTargetContext(), 24.15346, CoordinateType.LATITUDE);
|
||||
String ddm = LibUIUtils.getDDMFromLocation(getTargetContext(), 24.15346, CoordinateType.LATITUDE);
|
||||
assertEquals("N\t\u200724° 09,208", ddm);
|
||||
|
||||
// Test English
|
||||
setLocale("en", "US");
|
||||
|
||||
ddm = UIUtils.getDDMFromLocation(getTargetContext(), 24.15346, CoordinateType.LATITUDE);
|
||||
ddm = LibUIUtils.getDDMFromLocation(getTargetContext(), 24.15346, CoordinateType.LATITUDE);
|
||||
assertEquals("N\t\u200724° 09.208", ddm);
|
||||
}
|
||||
|
||||
@@ -84,13 +84,13 @@ public class UIUtilsAndroidTest {
|
||||
// Test English
|
||||
setLocale("en", "US");
|
||||
|
||||
String ddm = UIUtils.getDDMFromLocation(getTargetContext(), -150.94523, CoordinateType.LONGITUDE);
|
||||
String ddm = LibUIUtils.getDDMFromLocation(getTargetContext(), -150.94523, CoordinateType.LONGITUDE);
|
||||
assertEquals("W\t150° 56.714", ddm);
|
||||
|
||||
// Test German
|
||||
setLocale("de", "DE");
|
||||
|
||||
ddm = UIUtils.getDDMFromLocation(getTargetContext(), -150.94523, CoordinateType.LONGITUDE);
|
||||
ddm = LibUIUtils.getDDMFromLocation(getTargetContext(), -150.94523, CoordinateType.LONGITUDE);
|
||||
assertEquals("W\t150° 56,714", ddm);
|
||||
}
|
||||
|
||||
|
||||
@@ -32,15 +32,16 @@ import androidx.lifecycle.coroutineScope
|
||||
import androidx.lifecycle.flowWithLifecycle
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.android.gpstest.Application
|
||||
import com.android.gpstest.Application.Companion.prefs
|
||||
import com.android.gpstest.R
|
||||
import com.android.gpstest.data.LocationRepository
|
||||
import com.android.gpstest.library.data.LocationRepository
|
||||
import com.android.gpstest.map.MapConstants
|
||||
import com.android.gpstest.map.MapViewModelController
|
||||
import com.android.gpstest.map.MapViewModelController.MapInterface
|
||||
import com.android.gpstest.map.OnMapClickListener
|
||||
import com.android.gpstest.util.MapUtils
|
||||
import com.android.gpstest.util.MathUtils
|
||||
import com.android.gpstest.util.PreferenceUtil
|
||||
import com.android.gpstest.library.util.MathUtils
|
||||
import com.android.gpstest.library.util.PreferenceUtil
|
||||
import com.google.android.gms.common.ConnectionResult
|
||||
import com.google.android.gms.common.GoogleApiAvailability
|
||||
import com.google.android.gms.maps.CameraUpdateFactory
|
||||
@@ -94,7 +95,7 @@ class MapFragment : SupportMapFragment(), View.OnClickListener, LocationSource,
|
||||
|
||||
// Preference listener that will cancel the above flows when the user turns off tracking via UI
|
||||
private val trackingListener: SharedPreferences.OnSharedPreferenceChangeListener =
|
||||
PreferenceUtil.newStopTrackingListener { onGnssStopped() }
|
||||
PreferenceUtil.newStopTrackingListener ({ onGnssStopped() }, prefs)
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater, container: ViewGroup?,
|
||||
|
||||
@@ -35,7 +35,8 @@
|
||||
<activity
|
||||
android:name=".ui.MainActivity"
|
||||
android:theme="@style/AppTheme.NoActionBar"
|
||||
android:launchMode="singleInstance">
|
||||
android:launchMode="singleInstance"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
@@ -50,7 +51,8 @@
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".ui.Preferences"
|
||||
android:label="@string/pref_title">
|
||||
android:label="@string/pref_title"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
|
||||
@@ -58,7 +58,6 @@ class Application : MultiDexApplication() {
|
||||
|
||||
lateinit var localeManager: LocaleManager
|
||||
private set
|
||||
|
||||
lateinit var prefs: SharedPreferences
|
||||
private set
|
||||
}
|
||||
|
||||
@@ -42,37 +42,39 @@ import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.LifecycleService
|
||||
import androidx.lifecycle.flowWithLifecycle
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.android.gpstest.data.LocationRepository
|
||||
import com.android.gpstest.Application.Companion.app
|
||||
import com.android.gpstest.Application.Companion.prefs
|
||||
import com.android.gpstest.library.data.LocationRepository
|
||||
import com.android.gpstest.io.CsvFileLogger
|
||||
import com.android.gpstest.io.JsonFileLogger
|
||||
import com.android.gpstest.model.SatelliteGroup
|
||||
import com.android.gpstest.model.SatelliteMetadata
|
||||
import com.android.gpstest.library.model.SatelliteGroup
|
||||
import com.android.gpstest.library.model.SatelliteMetadata
|
||||
import com.android.gpstest.ui.MainActivity
|
||||
import com.android.gpstest.util.FormatUtils.toNotificationTitle
|
||||
import com.android.gpstest.util.IOUtils.*
|
||||
import com.android.gpstest.util.PreferenceUtil
|
||||
import com.android.gpstest.util.PreferenceUtil.injectPsdsWhenLogging
|
||||
import com.android.gpstest.util.PreferenceUtil.injectTimeWhenLogging
|
||||
import com.android.gpstest.util.PreferenceUtil.isCsvLoggingEnabled
|
||||
import com.android.gpstest.util.PreferenceUtil.isJsonLoggingEnabled
|
||||
import com.android.gpstest.util.PreferenceUtil.writeAntennaInfoToFileCsv
|
||||
import com.android.gpstest.util.PreferenceUtil.writeAntennaInfoToFileJson
|
||||
import com.android.gpstest.util.PreferenceUtil.writeLocationToFile
|
||||
import com.android.gpstest.util.PreferenceUtil.writeMeasurementToLogcat
|
||||
import com.android.gpstest.util.PreferenceUtil.writeMeasurementsToFile
|
||||
import com.android.gpstest.util.PreferenceUtil.writeNavMessageToFile
|
||||
import com.android.gpstest.util.PreferenceUtil.writeNavMessageToLogcat
|
||||
import com.android.gpstest.util.PreferenceUtil.writeNmeaTimestampToLogcat
|
||||
import com.android.gpstest.util.PreferenceUtil.writeNmeaToAndroidMonitor
|
||||
import com.android.gpstest.util.PreferenceUtil.writeNmeaToFile
|
||||
import com.android.gpstest.util.PreferenceUtil.writeOrientationToFile
|
||||
import com.android.gpstest.util.PreferenceUtil.writeStatusToFile
|
||||
import com.android.gpstest.util.PreferenceUtils
|
||||
import com.android.gpstest.util.SatelliteUtil.toSatelliteGroup
|
||||
import com.android.gpstest.util.SatelliteUtil.toSatelliteStatus
|
||||
import com.android.gpstest.util.SatelliteUtils
|
||||
import com.android.gpstest.util.UIUtils.toNotificationSummary
|
||||
import com.android.gpstest.util.hasPermission
|
||||
import com.android.gpstest.library.util.FormatUtils.toNotificationTitle
|
||||
import com.android.gpstest.library.util.IOUtils.*
|
||||
import com.android.gpstest.library.util.PreferenceUtil
|
||||
import com.android.gpstest.library.util.PreferenceUtil.injectPsdsWhenLogging
|
||||
import com.android.gpstest.library.util.PreferenceUtil.injectTimeWhenLogging
|
||||
import com.android.gpstest.library.util.PreferenceUtil.isCsvLoggingEnabled
|
||||
import com.android.gpstest.library.util.PreferenceUtil.isJsonLoggingEnabled
|
||||
import com.android.gpstest.library.util.PreferenceUtil.writeAntennaInfoToFileCsv
|
||||
import com.android.gpstest.library.util.PreferenceUtil.writeAntennaInfoToFileJson
|
||||
import com.android.gpstest.library.util.PreferenceUtil.writeLocationToFile
|
||||
import com.android.gpstest.library.util.PreferenceUtil.writeMeasurementToLogcat
|
||||
import com.android.gpstest.library.util.PreferenceUtil.writeMeasurementsToFile
|
||||
import com.android.gpstest.library.util.PreferenceUtil.writeNavMessageToFile
|
||||
import com.android.gpstest.library.util.PreferenceUtil.writeNavMessageToLogcat
|
||||
import com.android.gpstest.library.util.PreferenceUtil.writeNmeaTimestampToLogcat
|
||||
import com.android.gpstest.library.util.PreferenceUtil.writeNmeaToAndroidMonitor
|
||||
import com.android.gpstest.library.util.PreferenceUtil.writeNmeaToFile
|
||||
import com.android.gpstest.library.util.PreferenceUtil.writeOrientationToFile
|
||||
import com.android.gpstest.library.util.PreferenceUtil.writeStatusToFile
|
||||
import com.android.gpstest.library.util.PreferenceUtils
|
||||
import com.android.gpstest.library.util.SatelliteUtil.toSatelliteGroup
|
||||
import com.android.gpstest.library.util.SatelliteUtil.toSatelliteStatus
|
||||
import com.android.gpstest.library.util.SatelliteUtils
|
||||
import com.android.gpstest.library.util.LibUIUtils.toNotificationSummary
|
||||
import com.android.gpstest.library.util.hasPermission
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
@@ -105,7 +107,7 @@ class ForegroundOnlyLocationService : LifecycleService() {
|
||||
|
||||
// We save a local reference to last location and SatelliteStatus to create a Notification
|
||||
private var currentLocation: Location? = null
|
||||
private var currentSatellites: SatelliteGroup = SatelliteGroup(emptyMap(),SatelliteMetadata())
|
||||
private var currentSatellites: SatelliteGroup = SatelliteGroup(emptyMap(), SatelliteMetadata())
|
||||
|
||||
// Repository of location data that the service will observe, injected via Hilt
|
||||
@Inject
|
||||
@@ -125,7 +127,7 @@ class ForegroundOnlyLocationService : LifecycleService() {
|
||||
|
||||
// Preference listener that will init the loggers if the user changes Settings while Service is running
|
||||
private val loggingSettingListener: SharedPreferences.OnSharedPreferenceChangeListener =
|
||||
PreferenceUtil.newFileLoggingListener { initLogging() }
|
||||
PreferenceUtil.newFileLoggingListener(app, { initLogging() }, prefs)
|
||||
private var deletedFiles = false
|
||||
|
||||
override fun onCreate() {
|
||||
@@ -157,7 +159,7 @@ class ForegroundOnlyLocationService : LifecycleService() {
|
||||
try {
|
||||
observeFlows()
|
||||
} catch (unlikely: Exception) {
|
||||
PreferenceUtils.saveTrackingStarted(false)
|
||||
PreferenceUtils.saveTrackingStarted(false, prefs)
|
||||
Log.e(TAG, "Exception registering for updates: $unlikely")
|
||||
}
|
||||
|
||||
@@ -230,7 +232,7 @@ class ForegroundOnlyLocationService : LifecycleService() {
|
||||
fun subscribeToLocationUpdates() {
|
||||
Log.d(TAG, "subscribeToLocationUpdates()")
|
||||
|
||||
PreferenceUtils.saveTrackingStarted(true)
|
||||
PreferenceUtils.saveTrackingStarted(true, prefs)
|
||||
|
||||
// Binding to this service doesn't actually trigger onStartCommand(). That is needed to
|
||||
// ensure this Service can be promoted to a foreground service, i.e., the service needs to
|
||||
@@ -246,12 +248,12 @@ class ForegroundOnlyLocationService : LifecycleService() {
|
||||
stopSelf()
|
||||
stopLogging()
|
||||
isStarted = false
|
||||
PreferenceUtils.saveTrackingStarted(false)
|
||||
PreferenceUtils.saveTrackingStarted(false, prefs)
|
||||
removeOngoingActivityNotification()
|
||||
currentLocation = null
|
||||
currentSatellites = SatelliteGroup(emptyMap(),SatelliteMetadata())
|
||||
currentSatellites = SatelliteGroup(emptyMap(), SatelliteMetadata())
|
||||
} catch (unlikely: SecurityException) {
|
||||
PreferenceUtils.saveTrackingStarted(true)
|
||||
PreferenceUtils.saveTrackingStarted(true, prefs)
|
||||
Log.e(TAG, "Lost location permissions. Couldn't remove updates. $unlikely")
|
||||
}
|
||||
}
|
||||
@@ -300,7 +302,7 @@ class ForegroundOnlyLocationService : LifecycleService() {
|
||||
)
|
||||
|
||||
GlobalScope.launch(Dispatchers.IO) {
|
||||
if (writeLocationToFile() &&
|
||||
if (writeLocationToFile(app, prefs) &&
|
||||
applicationContext.hasPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
||||
) {
|
||||
initLogging()
|
||||
@@ -334,7 +336,7 @@ class ForegroundOnlyLocationService : LifecycleService() {
|
||||
)
|
||||
// Log Status
|
||||
GlobalScope.launch(Dispatchers.IO) {
|
||||
if (writeStatusToFile() &&
|
||||
if (writeStatusToFile(app, prefs) &&
|
||||
applicationContext.hasPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
||||
) {
|
||||
initLogging()
|
||||
@@ -357,13 +359,13 @@ class ForegroundOnlyLocationService : LifecycleService() {
|
||||
.onEach {
|
||||
//Log.d(TAG, "Service NMEA: $it")
|
||||
GlobalScope.launch(Dispatchers.IO) {
|
||||
if (writeNmeaToAndroidMonitor()) {
|
||||
if (writeNmeaToAndroidMonitor(app, prefs)) {
|
||||
writeNmeaToAndroidStudio(
|
||||
it.message,
|
||||
if (writeNmeaTimestampToLogcat()) it.timestamp else Long.MIN_VALUE
|
||||
if (writeNmeaTimestampToLogcat(app, prefs)) it.timestamp else Long.MIN_VALUE
|
||||
)
|
||||
}
|
||||
if (writeNmeaToFile() &&
|
||||
if (writeNmeaToFile(app, prefs) &&
|
||||
applicationContext.hasPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
||||
) {
|
||||
initLogging()
|
||||
@@ -386,10 +388,10 @@ class ForegroundOnlyLocationService : LifecycleService() {
|
||||
.onEach {
|
||||
//Log.d(TAG, "Service nav message: $it")
|
||||
GlobalScope.launch(Dispatchers.IO) {
|
||||
if (writeNavMessageToLogcat()) {
|
||||
if (writeNavMessageToLogcat(app, prefs)) {
|
||||
writeNavMessageToAndroidStudio(it)
|
||||
}
|
||||
if (writeNavMessageToFile() &&
|
||||
if (writeNavMessageToFile(app, prefs) &&
|
||||
applicationContext.hasPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
||||
) {
|
||||
initLogging()
|
||||
@@ -412,12 +414,12 @@ class ForegroundOnlyLocationService : LifecycleService() {
|
||||
.onEach {
|
||||
//Log.d(TAG, "Service measurement: $it")
|
||||
GlobalScope.launch(Dispatchers.IO) {
|
||||
if (writeMeasurementToLogcat()) {
|
||||
if (writeMeasurementToLogcat(app, prefs)) {
|
||||
for (m in it.measurements) {
|
||||
writeMeasurementToLogcat(m)
|
||||
}
|
||||
}
|
||||
if (writeMeasurementsToFile() &&
|
||||
if (writeMeasurementsToFile(app, prefs) &&
|
||||
applicationContext.hasPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
||||
) {
|
||||
initLogging()
|
||||
@@ -444,13 +446,13 @@ class ForegroundOnlyLocationService : LifecycleService() {
|
||||
if (!applicationContext.hasPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
|
||||
return@launch
|
||||
}
|
||||
if (writeAntennaInfoToFileCsv() || writeAntennaInfoToFileJson()) {
|
||||
if (writeAntennaInfoToFileCsv(app, prefs) || writeAntennaInfoToFileJson(app, prefs)) {
|
||||
initLogging()
|
||||
}
|
||||
if (writeAntennaInfoToFileCsv()) {
|
||||
if (writeAntennaInfoToFileCsv(app, prefs)) {
|
||||
csvFileLogger.onGnssAntennaInfoReceived(it)
|
||||
}
|
||||
if (writeAntennaInfoToFileJson()) {
|
||||
if (writeAntennaInfoToFileJson(app, prefs)) {
|
||||
jsonFileLogger.onGnssAntennaInfoReceived(it)
|
||||
}
|
||||
}
|
||||
@@ -469,7 +471,7 @@ class ForegroundOnlyLocationService : LifecycleService() {
|
||||
.flowWithLifecycle(lifecycle, Lifecycle.State.STARTED)
|
||||
.onEach {
|
||||
//Log.d(TAG, "Service sensor: orientation ${it.values[0]}, tilt ${it.values[1]}")
|
||||
if (writeOrientationToFile() &&
|
||||
if (writeOrientationToFile(app, prefs) &&
|
||||
applicationContext.hasPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
||||
) {
|
||||
initLogging()
|
||||
@@ -482,7 +484,7 @@ class ForegroundOnlyLocationService : LifecycleService() {
|
||||
private fun goForegroundOrStopSelf() {
|
||||
lifecycleScope.launch {
|
||||
// We may have been restarted by the system - check if we're still monitoring data
|
||||
if (PreferenceUtils.isTrackingStarted()) {
|
||||
if (PreferenceUtils.isTrackingStarted(prefs)) {
|
||||
// Monitoring GNSS data
|
||||
postOngoingActivityNotification()
|
||||
} else {
|
||||
@@ -522,8 +524,8 @@ class ForegroundOnlyLocationService : LifecycleService() {
|
||||
* Generates a BIG_TEXT_STYLE Notification that represent latest location.
|
||||
*/
|
||||
private fun buildNotification(location: Location?, satellites: SatelliteGroup): Notification {
|
||||
val titleText = satellites.toNotificationTitle()
|
||||
val summaryText = location?.toNotificationSummary() ?: getString(R.string.no_location_text)
|
||||
val titleText = satellites.toNotificationTitle(app)
|
||||
val summaryText = location?.toNotificationSummary(app, prefs) ?: getString(R.string.no_location_text)
|
||||
|
||||
// 2. Build the BIG_TEXT_STYLE.
|
||||
val bigTextStyle = NotificationCompat.BigTextStyle()
|
||||
@@ -606,20 +608,20 @@ class ForegroundOnlyLocationService : LifecycleService() {
|
||||
val locationManager = getSystemService(Context.LOCATION_SERVICE) as LocationManager
|
||||
|
||||
// Inject time and/or PSDS to make sure timestamps and assistance are as updated as possible
|
||||
if (injectTimeWhenLogging()) {
|
||||
forceTimeInjection(locationManager)
|
||||
if (injectTimeWhenLogging(app, prefs)) {
|
||||
forceTimeInjection(app, locationManager)
|
||||
}
|
||||
if (injectPsdsWhenLogging()) {
|
||||
forcePsdsInjection(locationManager)
|
||||
if (injectPsdsWhenLogging(app, prefs)) {
|
||||
forcePsdsInjection(app, locationManager)
|
||||
}
|
||||
|
||||
val date = Date()
|
||||
if (!csvFileLogger.isStarted && isCsvLoggingEnabled()) {
|
||||
if (!csvFileLogger.isStarted && isCsvLoggingEnabled(app, prefs)) {
|
||||
// User has granted permissions and has chosen to log at least one data type
|
||||
csvFileLogger.startLog(null, date)
|
||||
}
|
||||
|
||||
if (!jsonFileLogger.isStarted && isJsonLoggingEnabled()) {
|
||||
if (!jsonFileLogger.isStarted && isJsonLoggingEnabled(app, prefs)) {
|
||||
jsonFileLogger.startLog(null, date)
|
||||
}
|
||||
maybeDeleteFiles()
|
||||
|
||||
@@ -5,11 +5,12 @@ import com.github.mikephil.charting.components.XAxis;
|
||||
import com.github.mikephil.charting.data.Entry;
|
||||
import com.github.mikephil.charting.formatter.IAxisValueFormatter;
|
||||
import com.github.mikephil.charting.formatter.IValueFormatter;
|
||||
import com.github.mikephil.charting.formatter.ValueFormatter;
|
||||
import com.github.mikephil.charting.utils.ViewPortHandler;
|
||||
|
||||
import java.text.DecimalFormat;
|
||||
|
||||
public class DistanceValueFormatter implements IValueFormatter, IAxisValueFormatter {
|
||||
public class DistanceValueFormatter extends ValueFormatter implements IValueFormatter, IAxisValueFormatter {
|
||||
private final DecimalFormat mFormat;
|
||||
private String mSuffix;
|
||||
|
||||
|
||||
@@ -1,69 +0,0 @@
|
||||
package com.android.gpstest.di
|
||||
|
||||
|
||||
import android.content.Context
|
||||
import com.android.gpstest.data.*
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import javax.inject.Singleton
|
||||
|
||||
/**
|
||||
* Configuration for DI on the repository and shared location manager
|
||||
*/
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
object DataModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideSharedLocationManager(
|
||||
@ApplicationContext context: Context
|
||||
): SharedLocationManager =
|
||||
SharedLocationManager(context, GlobalScope)
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideSharedGnssStatusManager(
|
||||
@ApplicationContext context: Context
|
||||
): SharedGnssStatusManager =
|
||||
SharedGnssStatusManager(context, GlobalScope)
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideSharedNmeaManager(
|
||||
@ApplicationContext context: Context
|
||||
): SharedNmeaManager =
|
||||
SharedNmeaManager(context, GlobalScope)
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideSharedSensorManager(
|
||||
@ApplicationContext context: Context
|
||||
): SharedSensorManager =
|
||||
SharedSensorManager(context, GlobalScope)
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideSharedNavMessageManager(
|
||||
@ApplicationContext context: Context
|
||||
): SharedNavMessageManager =
|
||||
SharedNavMessageManager(context, GlobalScope)
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideSharedMeasurementsManager(
|
||||
@ApplicationContext context: Context
|
||||
): SharedGnssMeasurementManager =
|
||||
SharedGnssMeasurementManager(context, GlobalScope)
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideSharedAntennaManager(
|
||||
@ApplicationContext context: Context
|
||||
): SharedAntennaManager =
|
||||
SharedAntennaManager(context, GlobalScope)
|
||||
}
|
||||
@@ -37,10 +37,10 @@ import androidx.core.content.ContextCompat;
|
||||
import com.android.gpstest.Application;
|
||||
import com.android.gpstest.BuildConfig;
|
||||
import com.android.gpstest.R;
|
||||
import com.android.gpstest.model.Orientation;
|
||||
import com.android.gpstest.model.SatelliteStatus;
|
||||
import com.android.gpstest.util.FormatUtils;
|
||||
import com.android.gpstest.util.IOUtils;
|
||||
import com.android.gpstest.library.model.Orientation;
|
||||
import com.android.gpstest.library.model.SatelliteStatus;
|
||||
import com.android.gpstest.library.util.FormatUtils;
|
||||
import com.android.gpstest.library.util.IOUtils;
|
||||
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.IOException;
|
||||
@@ -121,7 +121,7 @@ public class CsvFileLogger extends BaseFileLogger implements FileLogger {
|
||||
version.append("Manufacturer: " + manufacturer + ", ");
|
||||
version.append("Model: " + model + ", ");
|
||||
|
||||
version.append("GNSS HW Year: " + IOUtils.getGnssHardwareYear() + ", ");
|
||||
version.append("GNSS HW Year: " + IOUtils.getGnssHardwareYear(Application.Companion.getApp()) + ", ");
|
||||
|
||||
String versionRelease = Build.VERSION.RELEASE;
|
||||
version.append("Platform: " + versionRelease + ", ");
|
||||
|
||||
@@ -17,7 +17,7 @@ import android.preference.PreferenceManager;
|
||||
|
||||
import com.android.gpstest.Application;
|
||||
import com.android.gpstest.R;
|
||||
import com.android.gpstest.util.LocaleUtils;
|
||||
import com.android.gpstest.library.util.LocaleUtils;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ import androidx.fragment.app.FragmentActivity;
|
||||
import androidx.lifecycle.Observer;
|
||||
import androidx.lifecycle.ViewModelProviders;
|
||||
|
||||
import com.android.gpstest.model.MeasuredError;
|
||||
import com.android.gpstest.library.model.MeasuredError;
|
||||
import com.android.gpstest.ui.BenchmarkViewModel;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
@@ -32,8 +32,6 @@ public class MapViewModelController {
|
||||
/**
|
||||
* Draws a path line on the map between the two points if the distance between the two points
|
||||
* exceeds a threshold
|
||||
* @param loc1
|
||||
* @param loc2
|
||||
* @return true if the line was drawn, or false if the distance between the points didn't
|
||||
* exceed the threshold and the line was not drawn
|
||||
*/
|
||||
|
||||
@@ -46,11 +46,12 @@ import androidx.lifecycle.ViewModelProviders;
|
||||
import com.android.gpstest.Application;
|
||||
import com.android.gpstest.R;
|
||||
import com.android.gpstest.chart.DistanceValueFormatter;
|
||||
import com.android.gpstest.model.AvgError;
|
||||
import com.android.gpstest.model.MeasuredError;
|
||||
import com.android.gpstest.util.IOUtils;
|
||||
import com.android.gpstest.util.MathUtils;
|
||||
import com.android.gpstest.util.PreferenceUtils;
|
||||
import com.android.gpstest.library.model.AvgError;
|
||||
import com.android.gpstest.library.model.MeasuredError;
|
||||
import com.android.gpstest.library.util.IOUtils;
|
||||
import com.android.gpstest.library.util.MathUtils;
|
||||
import com.android.gpstest.library.util.PreferenceUtils;
|
||||
import com.android.gpstest.library.util.LibUIUtils;
|
||||
import com.android.gpstest.util.UIUtils;
|
||||
import com.github.mikephil.charting.charts.LineChart;
|
||||
import com.github.mikephil.charting.components.Legend;
|
||||
@@ -121,16 +122,16 @@ public class BenchmarkControllerImpl implements BenchmarkController {
|
||||
// Set default text size and align units properly
|
||||
mErrorView.setTextSize(TypedValue.COMPLEX_UNIT_PX, Application.Companion.getApp().getResources().getDimension(R.dimen.ground_truth_sliding_header_vert_text_size));
|
||||
mAvgErrorView.setTextSize(TypedValue.COMPLEX_UNIT_PX, Application.Companion.getApp().getResources().getDimension(R.dimen.ground_truth_sliding_header_vert_text_size));
|
||||
UIUtils.setVerticalBias(mErrorUnit, UNIT_VERT_BIAS_INCL_VERT_ERROR);
|
||||
UIUtils.setVerticalBias(mAvgErrorUnit, UNIT_VERT_BIAS_INCL_VERT_ERROR);
|
||||
LibUIUtils.setVerticalBias(mErrorUnit, UNIT_VERT_BIAS_INCL_VERT_ERROR);
|
||||
LibUIUtils.setVerticalBias(mAvgErrorUnit, UNIT_VERT_BIAS_INCL_VERT_ERROR);
|
||||
} else {
|
||||
// No altitude provided - Hide vertical error chart card
|
||||
mVerticalErrorCardView.setVisibility(GONE);
|
||||
// Set default text size and align units properly
|
||||
mErrorView.setTextSize(TypedValue.COMPLEX_UNIT_PX, Application.Companion.getApp().getResources().getDimension(R.dimen.ground_truth_sliding_header_error_text_size));
|
||||
mAvgErrorView.setTextSize(TypedValue.COMPLEX_UNIT_PX, Application.Companion.getApp().getResources().getDimension(R.dimen.ground_truth_sliding_header_error_text_size));
|
||||
UIUtils.setVerticalBias(mErrorUnit, UNIT_VERT_BIAS_HOR_ERROR_ONLY);
|
||||
UIUtils.setVerticalBias(mAvgErrorUnit, UNIT_VERT_BIAS_HOR_ERROR_ONLY);
|
||||
LibUIUtils.setVerticalBias(mErrorUnit, UNIT_VERT_BIAS_HOR_ERROR_ONLY);
|
||||
LibUIUtils.setVerticalBias(mAvgErrorUnit, UNIT_VERT_BIAS_HOR_ERROR_ONLY);
|
||||
}
|
||||
|
||||
// Collapse card - we have to set height on card manually because card doesn't auto-collapse right when views are within card container
|
||||
@@ -180,7 +181,7 @@ public class BenchmarkControllerImpl implements BenchmarkController {
|
||||
mErrorUnit.setText(Application.Companion.getApp().getString(R.string.meters_abbreviation));
|
||||
} else {
|
||||
// Feet
|
||||
mErrorView.setText(Application.Companion.getApp().getString(R.string.benchmark_error, UIUtils.toFeet(error.getError())));
|
||||
mErrorView.setText(Application.Companion.getApp().getString(R.string.benchmark_error, LibUIUtils.toFeet(error.getError())));
|
||||
mErrorUnit.setText(Application.Companion.getApp().getString(R.string.feet_abbreviation));
|
||||
}
|
||||
}
|
||||
@@ -194,7 +195,7 @@ public class BenchmarkControllerImpl implements BenchmarkController {
|
||||
mVertErrorView.setText(Application.Companion.getApp().getString(R.string.benchmark_error, Math.abs(error.getVertError())));
|
||||
} else {
|
||||
// Feet
|
||||
mVertErrorView.setText(Application.Companion.getApp().getString(R.string.benchmark_error, UIUtils.toFeet(Math.abs(error.getVertError()))));
|
||||
mVertErrorView.setText(Application.Companion.getApp().getString(R.string.benchmark_error, LibUIUtils.toFeet(Math.abs(error.getVertError()))));
|
||||
}
|
||||
mVerticalErrorCardView.setVisibility(VISIBLE);
|
||||
} else {
|
||||
@@ -220,7 +221,7 @@ public class BenchmarkControllerImpl implements BenchmarkController {
|
||||
mAvgErrorUnit.setText(Application.Companion.getApp().getString(R.string.meters_abbreviation));
|
||||
} else {
|
||||
// Feet
|
||||
mAvgErrorView.setText(Application.Companion.getApp().getString(R.string.benchmark_error, UIUtils.toFeet(avgError.getAvgError())));
|
||||
mAvgErrorView.setText(Application.Companion.getApp().getString(R.string.benchmark_error, LibUIUtils.toFeet(avgError.getAvgError())));
|
||||
mAvgErrorUnit.setText(Application.Companion.getApp().getString(R.string.feet_abbreviation));
|
||||
}
|
||||
mAvgErrorLabel.setText(Application.Companion.getApp().getString(R.string.avg_error_label, avgError.getCount()));
|
||||
@@ -231,7 +232,7 @@ public class BenchmarkControllerImpl implements BenchmarkController {
|
||||
if (mPrefDistanceUnits.equalsIgnoreCase(METERS)) {
|
||||
mAvgVertErrorView.setText(Application.Companion.getApp().getString(R.string.benchmark_error, avgError.getAvgVertAbsError()));
|
||||
} else {
|
||||
mAvgVertErrorView.setText(Application.Companion.getApp().getString(R.string.benchmark_error, UIUtils.toFeet(avgError.getAvgVertAbsError())));
|
||||
mAvgVertErrorView.setText(Application.Companion.getApp().getString(R.string.benchmark_error, LibUIUtils.toFeet(avgError.getAvgVertAbsError())));
|
||||
}
|
||||
} else {
|
||||
// Hide any vertical error indication
|
||||
@@ -337,9 +338,9 @@ public class BenchmarkControllerImpl implements BenchmarkController {
|
||||
} else {
|
||||
Location groundTruth;
|
||||
// If a SHOW_RADAR or geo: URI was passed via an Intent (e.g., from BenchMap or OsmAnd app), use that as ground truth
|
||||
if (IOUtils.isShowRadarIntent(activity.getIntent()) || IOUtils.isGeoIntent(activity.getIntent())) {
|
||||
groundTruth = IOUtils.getLocationFromIntent(activity.getIntent());
|
||||
if (IOUtils.isGeoIntent(activity.getIntent())) {
|
||||
if (IOUtils.isShowRadarIntent(Application.Companion.getApp(), activity.getIntent()) || IOUtils.isGeoIntent(Application.Companion.getApp(), activity.getIntent())) {
|
||||
groundTruth = IOUtils.getLocationFromIntent(Application.Companion.getApp(), activity.getIntent());
|
||||
if (IOUtils.isGeoIntent(Application.Companion.getApp(), activity.getIntent())) {
|
||||
groundTruth.removeAltitude(); // TODO - RFC 5870 requires altitude height above geoid, which we can't support yet (see #296 and #530), so remove altitude here
|
||||
}
|
||||
if (groundTruth != null) {
|
||||
@@ -351,10 +352,10 @@ public class BenchmarkControllerImpl implements BenchmarkController {
|
||||
} else if (Application.Companion.getPrefs().contains(GROUND_TRUTH_LAT)) {
|
||||
// If there is a saved ground truth value from previous executions, start test using that
|
||||
groundTruth = new Location("ground_truth");
|
||||
groundTruth.setLatitude(PreferenceUtils.getDouble(GROUND_TRUTH_LAT, Double.NaN));
|
||||
groundTruth.setLongitude(PreferenceUtils.getDouble(GROUND_TRUTH_LONG, Double.NaN));
|
||||
groundTruth.setLatitude(PreferenceUtils.getDouble(GROUND_TRUTH_LAT, Double.NaN, Application.Companion.getPrefs()));
|
||||
groundTruth.setLongitude(PreferenceUtils.getDouble(GROUND_TRUTH_LONG, Double.NaN, Application.Companion.getPrefs()));
|
||||
if (Application.Companion.getPrefs().contains(GROUND_TRUTH_ALT)) {
|
||||
groundTruth.setAltitude(PreferenceUtils.getDouble(GROUND_TRUTH_ALT, Double.NaN));
|
||||
groundTruth.setAltitude(PreferenceUtils.getDouble(GROUND_TRUTH_ALT, Double.NaN, Application.Companion.getPrefs()));
|
||||
}
|
||||
restoreGroundTruth(groundTruth);
|
||||
}
|
||||
@@ -418,12 +419,12 @@ public class BenchmarkControllerImpl implements BenchmarkController {
|
||||
mViewModel.setBenchmarkCardCollapsed(true);
|
||||
mViewModel.setAllowGroundTruthEdit(false);
|
||||
|
||||
PreferenceUtils.saveDouble(GROUND_TRUTH_LAT, groundTruthLocation.getLatitude());
|
||||
PreferenceUtils.saveDouble(GROUND_TRUTH_LONG, groundTruthLocation.getLongitude());
|
||||
PreferenceUtils.saveDouble(GROUND_TRUTH_LAT, groundTruthLocation.getLatitude(), Application.Companion.getPrefs());
|
||||
PreferenceUtils.saveDouble(GROUND_TRUTH_LONG, groundTruthLocation.getLongitude(), Application.Companion.getPrefs());
|
||||
if (groundTruthLocation.hasAltitude()) {
|
||||
PreferenceUtils.saveDouble(GROUND_TRUTH_ALT, groundTruthLocation.getAltitude());
|
||||
PreferenceUtils.saveDouble(GROUND_TRUTH_ALT, groundTruthLocation.getAltitude(), Application.Companion.getPrefs());
|
||||
} else {
|
||||
PreferenceUtils.remove(GROUND_TRUTH_ALT);
|
||||
PreferenceUtils.remove(GROUND_TRUTH_ALT, Application.Companion.getPrefs());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -595,8 +596,8 @@ public class BenchmarkControllerImpl implements BenchmarkController {
|
||||
horAccuracy = location.getAccuracy();
|
||||
} else {
|
||||
// Feet
|
||||
horError = (float) UIUtils.toFeet(error.getError());
|
||||
horAccuracy = (float) UIUtils.toFeet(location.getAccuracy());
|
||||
horError = (float) LibUIUtils.toFeet(error.getError());
|
||||
horAccuracy = (float) LibUIUtils.toFeet(location.getAccuracy());
|
||||
}
|
||||
addErrorToGraph(index, mErrorChart, horError, horAccuracy);
|
||||
|
||||
@@ -607,14 +608,14 @@ public class BenchmarkControllerImpl implements BenchmarkController {
|
||||
vertError = Math.abs(error.getVertError());
|
||||
} else {
|
||||
// Feet
|
||||
vertError = UIUtils.toFeet(Math.abs(error.getVertError()));
|
||||
vertError = LibUIUtils.toFeet(Math.abs(error.getVertError()));
|
||||
}
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
if (mPrefDistanceUnits.equalsIgnoreCase(METERS)) {
|
||||
vertAccuracy = location.getVerticalAccuracyMeters();
|
||||
} else {
|
||||
// Feet
|
||||
vertAccuracy = (float) UIUtils.toFeet(location.getVerticalAccuracyMeters());
|
||||
vertAccuracy = (float) LibUIUtils.toFeet(location.getVerticalAccuracyMeters());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -24,9 +24,9 @@ import androidx.lifecycle.AndroidViewModel;
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
|
||||
import com.android.gpstest.model.AvgError;
|
||||
import com.android.gpstest.model.MeasuredError;
|
||||
import com.android.gpstest.util.BenchmarkUtils;
|
||||
import com.android.gpstest.library.model.AvgError;
|
||||
import com.android.gpstest.library.model.MeasuredError;
|
||||
import com.android.gpstest.library.util.BenchmarkUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@@ -7,9 +7,10 @@ import android.os.Bundle;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.fragment.app.DialogFragment;
|
||||
|
||||
import com.android.gpstest.Application;
|
||||
import com.android.gpstest.R;
|
||||
import com.android.gpstest.model.GnssType;
|
||||
import com.android.gpstest.util.PreferenceUtils;
|
||||
import com.android.gpstest.library.model.GnssType;
|
||||
import com.android.gpstest.library.util.PreferenceUtils;
|
||||
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
@@ -56,7 +57,7 @@ public class GnssFilterDialog extends DialogFragment
|
||||
}
|
||||
}
|
||||
|
||||
PreferenceUtils.saveGnssFilter(filter);
|
||||
PreferenceUtils.saveGnssFilter(Application.Companion.getApp(), filter, Application.Companion.getPrefs());
|
||||
dialog.dismiss();
|
||||
}
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ import androidx.appcompat.app.AppCompatActivity;
|
||||
import com.android.gpstest.Application;
|
||||
import com.android.gpstest.BuildConfig;
|
||||
import com.android.gpstest.R;
|
||||
import com.android.gpstest.util.IOUtils;
|
||||
import com.android.gpstest.library.util.IOUtils;
|
||||
|
||||
public class HelpActivity extends AppCompatActivity {
|
||||
|
||||
@@ -60,8 +60,8 @@ public class HelpActivity extends AppCompatActivity {
|
||||
.append(versionCode)
|
||||
.append("-" + BuildConfig.FLAVOR + ")\n");
|
||||
|
||||
version.append("GNSS HW Year: " + IOUtils.getGnssHardwareYear() + "\n");
|
||||
version.append("GNSS HW Name: " + IOUtils.getGnssHardwareModelName() + "\n");
|
||||
version.append("GNSS HW Year: " + IOUtils.getGnssHardwareYear(Application.Companion.getApp()) + "\n");
|
||||
version.append("GNSS HW Name: " + IOUtils.getGnssHardwareModelName(Application.Companion.getApp()) + "\n");
|
||||
|
||||
String versionRelease = Build.VERSION.RELEASE;
|
||||
version.append("Platform: " + versionRelease + "\n");
|
||||
|
||||
@@ -49,23 +49,28 @@ import androidx.lifecycle.Observer
|
||||
import androidx.lifecycle.flowWithLifecycle
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.android.gpstest.Application
|
||||
import com.android.gpstest.Application.Companion.app
|
||||
import com.android.gpstest.Application.Companion.prefs
|
||||
import com.android.gpstest.ForegroundOnlyLocationService
|
||||
import com.android.gpstest.ForegroundOnlyLocationService.LocalBinder
|
||||
import com.android.gpstest.R
|
||||
import com.android.gpstest.data.FixState
|
||||
import com.android.gpstest.data.LocationRepository
|
||||
import com.android.gpstest.library.data.FixState
|
||||
import com.android.gpstest.library.data.LocationRepository
|
||||
import com.android.gpstest.databinding.ActivityMainBinding
|
||||
import com.android.gpstest.library.ui.SignalInfoViewModel
|
||||
import com.android.gpstest.library.util.*
|
||||
import com.android.gpstest.map.MapConstants
|
||||
import com.android.gpstest.ui.NavigationDrawerFragment.NavigationDrawerCallbacks
|
||||
import com.android.gpstest.ui.sky.SkyFragment
|
||||
import com.android.gpstest.ui.status.StatusFragment
|
||||
import com.android.gpstest.util.*
|
||||
import com.android.gpstest.util.PreferenceUtil.darkTheme
|
||||
import com.android.gpstest.util.PreferenceUtil.isFileLoggingEnabled
|
||||
import com.android.gpstest.util.PreferenceUtil.minDistance
|
||||
import com.android.gpstest.util.PreferenceUtil.minTimeMillis
|
||||
import com.android.gpstest.util.PreferenceUtil.runInBackground
|
||||
import com.android.gpstest.util.PreferenceUtils.isTrackingStarted
|
||||
import com.android.gpstest.library.util.PreferenceUtil.darkTheme
|
||||
import com.android.gpstest.library.util.PreferenceUtil.isFileLoggingEnabled
|
||||
import com.android.gpstest.library.util.PreferenceUtil.minDistance
|
||||
import com.android.gpstest.library.util.PreferenceUtil.minTimeMillis
|
||||
import com.android.gpstest.library.util.PreferenceUtil.runInBackground
|
||||
import com.android.gpstest.library.util.PreferenceUtils.isTrackingStarted
|
||||
import com.android.gpstest.util.BuildUtils
|
||||
import com.android.gpstest.util.UIUtils
|
||||
import com.google.android.material.switchmaterial.SwitchMaterial
|
||||
import com.google.zxing.integration.android.IntentIntegrator
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
@@ -144,18 +149,18 @@ class MainActivity : AppCompatActivity(), NavigationDrawerCallbacks {
|
||||
|
||||
// Preference listener that will cancel the above flows when the user turns off tracking via service notification
|
||||
private val stopTrackingListener: SharedPreferences.OnSharedPreferenceChangeListener =
|
||||
PreferenceUtil.newStopTrackingListener { gpsStop() }
|
||||
PreferenceUtil.newStopTrackingListener ({ gpsStop() }, prefs)
|
||||
|
||||
/** Called when the activity is first created. */
|
||||
public override fun onCreate(savedInstanceState: Bundle?) {
|
||||
// Set theme
|
||||
if (darkTheme()) {
|
||||
if (darkTheme(app,Application.prefs)) {
|
||||
setTheme(R.style.AppTheme_Dark_NoActionBar)
|
||||
useDarkTheme = true
|
||||
}
|
||||
super.onCreate(savedInstanceState)
|
||||
// Reset the activity title to make sure dynamic locale changes are shown
|
||||
UIUtils.resetActivityTitle(this)
|
||||
LibUIUtils.resetActivityTitle(this)
|
||||
saveInstanceState(savedInstanceState)
|
||||
|
||||
// Observe stopping location updates from the service
|
||||
@@ -163,9 +168,9 @@ class MainActivity : AppCompatActivity(), NavigationDrawerCallbacks {
|
||||
|
||||
// Set the default values from the XML file if this is the first execution of the app
|
||||
PreferenceManager.setDefaultValues(this, R.xml.preferences, false)
|
||||
initialLanguage = PreferenceUtils.getString(getString(R.string.pref_key_language))
|
||||
initialMinTimeMillis = minTimeMillis()
|
||||
initialMinDistance = minDistance()
|
||||
initialLanguage = PreferenceUtils.getString(getString(R.string.pref_key_language), prefs)
|
||||
initialMinTimeMillis = minTimeMillis(app, prefs)
|
||||
initialMinDistance = minDistance(app, prefs)
|
||||
|
||||
binding = ActivityMainBinding.inflate(layoutInflater)
|
||||
val view = binding.root
|
||||
@@ -189,7 +194,7 @@ class MainActivity : AppCompatActivity(), NavigationDrawerCallbacks {
|
||||
override fun onNewIntent(intent: Intent) {
|
||||
super.onNewIntent(intent)
|
||||
// If another app is passing in a ground truth location, recreate the activity to initialize an existing instance
|
||||
if (IOUtils.isShowRadarIntent(intent) || IOUtils.isGeoIntent(intent)) {
|
||||
if (IOUtils.isShowRadarIntent(app, intent) || IOUtils.isGeoIntent(app, intent)) {
|
||||
recreateApp(intent)
|
||||
}
|
||||
}
|
||||
@@ -228,7 +233,7 @@ class MainActivity : AppCompatActivity(), NavigationDrawerCallbacks {
|
||||
} else {
|
||||
// Explain permission to user (don't request permission here directly to avoid infinite
|
||||
// loop if user selects "Don't ask again") in system permission prompt
|
||||
UIUtils.showLocationPermissionDialog(this)
|
||||
LibUIUtils.showLocationPermissionDialog(this)
|
||||
}
|
||||
maybeRecreateApp()
|
||||
benchmarkController!!.onResume()
|
||||
@@ -236,7 +241,7 @@ class MainActivity : AppCompatActivity(), NavigationDrawerCallbacks {
|
||||
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||
super.onActivityResult(requestCode, resultCode, data)
|
||||
if (requestCode == UIUtils.PICKFILE_REQUEST_CODE && resultCode == RESULT_OK) {
|
||||
if (requestCode == LibUIUtils.PICKFILE_REQUEST_CODE && resultCode == RESULT_OK) {
|
||||
// User picked a file to share from the Share dialog - update the dialog
|
||||
val uri = data?.data
|
||||
if (uri != null) {
|
||||
@@ -244,7 +249,7 @@ class MainActivity : AppCompatActivity(), NavigationDrawerCallbacks {
|
||||
val location = lastLocation
|
||||
shareDialogOpen = true
|
||||
UIUtils.showShareFragmentDialog(
|
||||
this, location, isFileLoggingEnabled(),
|
||||
this, location, isFileLoggingEnabled(app, prefs),
|
||||
service!!.csvFileLogger, service!!.jsonFileLogger, uri
|
||||
)
|
||||
}
|
||||
@@ -253,11 +258,11 @@ class MainActivity : AppCompatActivity(), NavigationDrawerCallbacks {
|
||||
val scanResult = IntentIntegrator.parseActivityResult(requestCode, resultCode, data)
|
||||
if (scanResult != null) {
|
||||
val geoUri = scanResult.contents
|
||||
val l = IOUtils.getLocationFromGeoUri(geoUri)
|
||||
val l = IOUtils.getLocationFromGeoUri(app, geoUri)
|
||||
if (l != null) {
|
||||
l.removeAltitude() // TODO - RFC 5870 requires altitude height above geoid, which we can't support yet (see #296 and #530), so remove altitude here
|
||||
// Create a SHOW_RADAR intent out of the Geo URI and pass that to set ground truth
|
||||
val showRadar = IOUtils.createShowRadarIntent(l)
|
||||
val showRadar = IOUtils.createShowRadarIntent(app, l)
|
||||
recreateApp(showRadar)
|
||||
} else {
|
||||
Toast.makeText(
|
||||
@@ -272,16 +277,16 @@ class MainActivity : AppCompatActivity(), NavigationDrawerCallbacks {
|
||||
private fun maybeRecreateApp() {
|
||||
// If the set language has changed since we created the Activity (e.g., returning from Settings), recreate App
|
||||
if (Application.prefs.contains(getString(R.string.pref_key_language))) {
|
||||
val currentLanguage = PreferenceUtils.getString(getString(R.string.pref_key_language))
|
||||
val currentLanguage = PreferenceUtils.getString(getString(R.string.pref_key_language), prefs)
|
||||
if (currentLanguage != initialLanguage) {
|
||||
initialLanguage = currentLanguage
|
||||
recreateApp(null)
|
||||
}
|
||||
}
|
||||
// If the user changed the location update settings, recreate the App
|
||||
if (minTimeMillis() != initialMinTimeMillis || minDistance() != initialMinDistance) {
|
||||
initialMinTimeMillis = minTimeMillis()
|
||||
initialMinDistance = minDistance()
|
||||
if (minTimeMillis(app, prefs) != initialMinTimeMillis || minDistance(app, prefs) != initialMinDistance) {
|
||||
initialMinTimeMillis = minTimeMillis(app, prefs)
|
||||
initialMinDistance = minDistance(app, prefs)
|
||||
recreateApp(null)
|
||||
}
|
||||
}
|
||||
@@ -294,17 +299,18 @@ class MainActivity : AppCompatActivity(), NavigationDrawerCallbacks {
|
||||
*/
|
||||
private fun recreateApp(currentIntent: Intent?) {
|
||||
val i = Intent(this, MainActivity::class.java)
|
||||
if (IOUtils.isShowRadarIntent(currentIntent)) {
|
||||
if (IOUtils.isShowRadarIntent(app, currentIntent)) {
|
||||
// If we're creating the app because we got a SHOW_RADAR intent, copy over the intent action and extras
|
||||
i.action = currentIntent!!.action
|
||||
i.putExtras(currentIntent.extras!!)
|
||||
} else if (IOUtils.isGeoIntent(currentIntent)) {
|
||||
} else if (IOUtils.isGeoIntent(app,currentIntent)) {
|
||||
// If we're creating the app because we got a geo: intent, turn it into a SHOW_RADAR intent for simplicity (they are used the same way)
|
||||
val l = IOUtils.getLocationFromGeoUri(
|
||||
app,
|
||||
currentIntent!!.data.toString()
|
||||
)
|
||||
if (l != null) {
|
||||
val showRadarIntent = IOUtils.createShowRadarIntent(l)
|
||||
val showRadarIntent = IOUtils.createShowRadarIntent(app, l)
|
||||
i.action = showRadarIntent.action
|
||||
i.putExtras(showRadarIntent.extras!!)
|
||||
}
|
||||
@@ -366,24 +372,24 @@ class MainActivity : AppCompatActivity(), NavigationDrawerCallbacks {
|
||||
).show()
|
||||
}
|
||||
if (!locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)) {
|
||||
UIUtils.promptEnableGps(this)
|
||||
LibUIUtils.promptEnableGps(app,this)
|
||||
}
|
||||
setupStartState(lastSavedInstanceState)
|
||||
|
||||
// If the theme has changed (e.g., from Preferences), destroy and recreate to reflect change
|
||||
val useDarkTheme = darkTheme()
|
||||
val useDarkTheme = darkTheme(app, prefs)
|
||||
if (this.useDarkTheme != useDarkTheme) {
|
||||
this.useDarkTheme = useDarkTheme
|
||||
recreate()
|
||||
}
|
||||
val settings = Application.prefs
|
||||
checkKeepScreenOn(settings)
|
||||
UIUtils.autoShowWhatsNew(this)
|
||||
LibUIUtils.autoShowWhatsNew(prefs, app,this)
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
// Stop GNSS if this isn't a configuration change and the user hasn't opted to run in background
|
||||
if (!isChangingConfigurations && !runInBackground()) {
|
||||
if (!isChangingConfigurations && !runInBackground(app, prefs)) {
|
||||
service?.unsubscribeToLocationUpdates()
|
||||
}
|
||||
super.onPause()
|
||||
@@ -395,7 +401,7 @@ class MainActivity : AppCompatActivity(), NavigationDrawerCallbacks {
|
||||
if (Application.prefs.getBoolean(
|
||||
getString(R.string.pref_key_auto_start_gps),
|
||||
true
|
||||
) || isTrackingStarted()
|
||||
) || isTrackingStarted(prefs)
|
||||
) {
|
||||
gpsStart()
|
||||
}
|
||||
@@ -434,7 +440,7 @@ class MainActivity : AppCompatActivity(), NavigationDrawerCallbacks {
|
||||
false
|
||||
)
|
||||
) {
|
||||
showDialog(UIUtils.CLEAR_ASSIST_WARNING_DIALOG)
|
||||
showDialog(LibUIUtils.CLEAR_ASSIST_WARNING_DIALOG)
|
||||
} else {
|
||||
deleteAidingData()
|
||||
}
|
||||
@@ -445,7 +451,7 @@ class MainActivity : AppCompatActivity(), NavigationDrawerCallbacks {
|
||||
Preferences::class.java
|
||||
)
|
||||
)
|
||||
NavigationDrawerFragment.NAVDRAWER_ITEM_HELP -> showDialog(UIUtils.HELP_DIALOG)
|
||||
NavigationDrawerFragment.NAVDRAWER_ITEM_HELP -> showDialog(LibUIUtils.HELP_DIALOG)
|
||||
NavigationDrawerFragment.NAVDRAWER_ITEM_OPEN_SOURCE -> {
|
||||
val i = Intent(Intent.ACTION_VIEW)
|
||||
i.data = Uri.parse(getString(R.string.open_source_github))
|
||||
@@ -458,7 +464,7 @@ class MainActivity : AppCompatActivity(), NavigationDrawerCallbacks {
|
||||
if (lastLocation != null) {
|
||||
locationString = LocationUtils.printLocationDetails(lastLocation)
|
||||
}
|
||||
UIUtils.sendEmail(this, email, locationString, signalInfoViewModel)
|
||||
LibUIUtils.sendEmail(app, email, locationString, signalInfoViewModel, BuildUtils.getPlayServicesVersion(), prefs)
|
||||
}
|
||||
}
|
||||
invalidateOptionsMenu()
|
||||
@@ -610,7 +616,7 @@ class MainActivity : AppCompatActivity(), NavigationDrawerCallbacks {
|
||||
|
||||
private fun forcePsdsInjection() {
|
||||
val success =
|
||||
IOUtils.forcePsdsInjection(getSystemService(LOCATION_SERVICE) as LocationManager)
|
||||
IOUtils.forcePsdsInjection(app, getSystemService(LOCATION_SERVICE) as LocationManager)
|
||||
if (success) {
|
||||
Toast.makeText(
|
||||
this, getString(R.string.force_psds_injection_success),
|
||||
@@ -618,7 +624,8 @@ class MainActivity : AppCompatActivity(), NavigationDrawerCallbacks {
|
||||
).show()
|
||||
PreferenceUtils.saveInt(
|
||||
Application.app.getString(R.string.capability_key_inject_psds),
|
||||
PreferenceUtils.CAPABILITY_SUPPORTED
|
||||
PreferenceUtils.CAPABILITY_SUPPORTED,
|
||||
prefs
|
||||
)
|
||||
} else {
|
||||
Toast.makeText(
|
||||
@@ -627,14 +634,15 @@ class MainActivity : AppCompatActivity(), NavigationDrawerCallbacks {
|
||||
).show()
|
||||
PreferenceUtils.saveInt(
|
||||
Application.app.getString(R.string.capability_key_inject_psds),
|
||||
PreferenceUtils.CAPABILITY_NOT_SUPPORTED
|
||||
PreferenceUtils.CAPABILITY_NOT_SUPPORTED,
|
||||
prefs
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun forceTimeInjection() {
|
||||
val success =
|
||||
IOUtils.forceTimeInjection(getSystemService(LOCATION_SERVICE) as LocationManager)
|
||||
IOUtils.forceTimeInjection(app, getSystemService(LOCATION_SERVICE) as LocationManager)
|
||||
if (success) {
|
||||
Toast.makeText(
|
||||
this, getString(R.string.force_time_injection_success),
|
||||
@@ -642,7 +650,8 @@ class MainActivity : AppCompatActivity(), NavigationDrawerCallbacks {
|
||||
).show()
|
||||
PreferenceUtils.saveInt(
|
||||
Application.app.getString(R.string.capability_key_inject_time),
|
||||
PreferenceUtils.CAPABILITY_SUPPORTED
|
||||
PreferenceUtils.CAPABILITY_SUPPORTED,
|
||||
prefs
|
||||
)
|
||||
} else {
|
||||
Toast.makeText(
|
||||
@@ -651,7 +660,8 @@ class MainActivity : AppCompatActivity(), NavigationDrawerCallbacks {
|
||||
).show()
|
||||
PreferenceUtils.saveInt(
|
||||
Application.app.getString(R.string.capability_key_inject_time),
|
||||
PreferenceUtils.CAPABILITY_NOT_SUPPORTED
|
||||
PreferenceUtils.CAPABILITY_NOT_SUPPORTED,
|
||||
prefs
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -659,12 +669,12 @@ class MainActivity : AppCompatActivity(), NavigationDrawerCallbacks {
|
||||
@ExperimentalCoroutinesApi
|
||||
private fun deleteAidingData() {
|
||||
// If GPS is currently running, stop it
|
||||
val lastStartState = isTrackingStarted()
|
||||
if (isTrackingStarted()) {
|
||||
val lastStartState = isTrackingStarted(prefs)
|
||||
if (isTrackingStarted(prefs)) {
|
||||
gpsStop()
|
||||
}
|
||||
val success =
|
||||
IOUtils.deleteAidingData(getSystemService(LOCATION_SERVICE) as LocationManager)
|
||||
IOUtils.deleteAidingData(app, getSystemService(LOCATION_SERVICE) as LocationManager)
|
||||
if (success) {
|
||||
Toast.makeText(
|
||||
this, getString(R.string.delete_aiding_data_success),
|
||||
@@ -672,7 +682,8 @@ class MainActivity : AppCompatActivity(), NavigationDrawerCallbacks {
|
||||
).show()
|
||||
PreferenceUtils.saveInt(
|
||||
Application.app.getString(R.string.capability_key_delete_assist),
|
||||
PreferenceUtils.CAPABILITY_SUPPORTED
|
||||
PreferenceUtils.CAPABILITY_SUPPORTED,
|
||||
prefs
|
||||
)
|
||||
} else {
|
||||
Toast.makeText(
|
||||
@@ -681,7 +692,8 @@ class MainActivity : AppCompatActivity(), NavigationDrawerCallbacks {
|
||||
).show()
|
||||
PreferenceUtils.saveInt(
|
||||
Application.app.getString(R.string.capability_key_delete_assist),
|
||||
PreferenceUtils.CAPABILITY_NOT_SUPPORTED
|
||||
PreferenceUtils.CAPABILITY_NOT_SUPPORTED,
|
||||
prefs
|
||||
)
|
||||
}
|
||||
// Restart the GPS, if it was previously started, with a slight delay,
|
||||
@@ -712,7 +724,7 @@ class MainActivity : AppCompatActivity(), NavigationDrawerCallbacks {
|
||||
@SuppressLint("MissingPermission")
|
||||
@Synchronized
|
||||
private fun gpsStart() {
|
||||
PreferenceUtils.saveTrackingStarted(true)
|
||||
PreferenceUtils.saveTrackingStarted(true, prefs)
|
||||
service?.subscribeToLocationUpdates()
|
||||
showProgressBar()
|
||||
|
||||
@@ -721,15 +733,15 @@ class MainActivity : AppCompatActivity(), NavigationDrawerCallbacks {
|
||||
observeGnssStates()
|
||||
|
||||
// Show Toast only if the user has set minTime or minDistance to something other than default values
|
||||
if (minTimeMillis() != (getString(R.string.pref_gps_min_time_default_sec).toDouble() * SECONDS_TO_MILLISECONDS).toLong() ||
|
||||
minDistance() != getString(R.string.pref_gps_min_distance_default_meters).toFloat()
|
||||
if (minTimeMillis(app, prefs) != (getString(R.string.pref_gps_min_time_default_sec).toDouble() * SECONDS_TO_MILLISECONDS).toLong() ||
|
||||
minDistance(app, prefs) != getString(R.string.pref_gps_min_distance_default_meters).toFloat()
|
||||
) {
|
||||
Toast.makeText(
|
||||
this,
|
||||
String.format(
|
||||
getString(R.string.gnss_running),
|
||||
(minTimeMillis().toDouble() / SECONDS_TO_MILLISECONDS).toString(),
|
||||
minDistance().toString()
|
||||
(minTimeMillis(app, prefs).toDouble() / SECONDS_TO_MILLISECONDS).toString(),
|
||||
minDistance(app, prefs).toString()
|
||||
),
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
@@ -770,7 +782,7 @@ class MainActivity : AppCompatActivity(), NavigationDrawerCallbacks {
|
||||
val gnssStateObserver = Observer<FixState> { fixState ->
|
||||
when (fixState) {
|
||||
is FixState.Acquired -> hideProgressBar()
|
||||
is FixState.NotAcquired -> if (isTrackingStarted()) showProgressBar()
|
||||
is FixState.NotAcquired -> if (isTrackingStarted(prefs)) showProgressBar()
|
||||
}
|
||||
}
|
||||
signalInfoViewModel.fixState.observe(
|
||||
@@ -780,7 +792,7 @@ class MainActivity : AppCompatActivity(), NavigationDrawerCallbacks {
|
||||
|
||||
@Synchronized
|
||||
private fun gpsStop() {
|
||||
PreferenceUtils.saveTrackingStarted(false)
|
||||
PreferenceUtils.saveTrackingStarted(false, prefs)
|
||||
locationFlow?.cancel()
|
||||
|
||||
// Reset the options menu to trigger updates to action bar menu items
|
||||
@@ -791,14 +803,14 @@ class MainActivity : AppCompatActivity(), NavigationDrawerCallbacks {
|
||||
private fun hideProgressBar() {
|
||||
val p = progressBar
|
||||
if (p != null) {
|
||||
UIUtils.hideViewWithAnimation(p, UIUtils.ANIMATION_DURATION_SHORT_MS)
|
||||
LibUIUtils.hideViewWithAnimation(p, LibUIUtils.ANIMATION_DURATION_SHORT_MS)
|
||||
}
|
||||
}
|
||||
|
||||
private fun showProgressBar() {
|
||||
val p = progressBar
|
||||
if (p != null) {
|
||||
UIUtils.showViewWithAnimation(p, UIUtils.ANIMATION_DURATION_SHORT_MS)
|
||||
LibUIUtils.showViewWithAnimation(p, LibUIUtils.ANIMATION_DURATION_SHORT_MS)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -821,16 +833,16 @@ class MainActivity : AppCompatActivity(), NavigationDrawerCallbacks {
|
||||
switch = MenuItemCompat.getActionView(item).findViewById(R.id.gps_switch)
|
||||
if (switch != null) {
|
||||
// Initialize state of GPS switch before we set the listener, so we don't double-trigger start or stop
|
||||
switch!!.isChecked = isTrackingStarted()
|
||||
switch!!.isChecked = isTrackingStarted(prefs)
|
||||
|
||||
// Set up listener for GPS on/off switch
|
||||
switch!!.setOnClickListener {
|
||||
// Turn GPS on or off
|
||||
if (!switch!!.isChecked && isTrackingStarted()) {
|
||||
if (!switch!!.isChecked && isTrackingStarted(prefs)) {
|
||||
gpsStop()
|
||||
service?.unsubscribeToLocationUpdates()
|
||||
} else {
|
||||
if (switch!!.isChecked && !isTrackingStarted()) {
|
||||
if (switch!!.isChecked && !isTrackingStarted(prefs)) {
|
||||
gpsStart()
|
||||
}
|
||||
}
|
||||
@@ -842,7 +854,7 @@ class MainActivity : AppCompatActivity(), NavigationDrawerCallbacks {
|
||||
override fun onPrepareOptionsMenu(menu: Menu): Boolean {
|
||||
val item = menu.findItem(R.id.share)
|
||||
if (item != null) {
|
||||
item.isVisible = lastLocation != null || isFileLoggingEnabled() == true
|
||||
item.isVisible = lastLocation != null || isFileLoggingEnabled(app, prefs) == true
|
||||
}
|
||||
return true
|
||||
}
|
||||
@@ -868,16 +880,16 @@ class MainActivity : AppCompatActivity(), NavigationDrawerCallbacks {
|
||||
val location = lastLocation
|
||||
shareDialogOpen = true
|
||||
UIUtils.showShareFragmentDialog(
|
||||
this, location, isFileLoggingEnabled(),
|
||||
this, location, isFileLoggingEnabled(app, prefs),
|
||||
service!!.csvFileLogger, service!!.jsonFileLogger, null
|
||||
)
|
||||
}
|
||||
|
||||
override fun onCreateDialog(id: Int): Dialog {
|
||||
when (id) {
|
||||
UIUtils.WHATSNEW_DIALOG -> return UIUtils.createWhatsNewDialog(this)
|
||||
UIUtils.HELP_DIALOG -> return UIUtils.createHelpDialog(this)
|
||||
UIUtils.CLEAR_ASSIST_WARNING_DIALOG -> return createClearAssistWarningDialog()
|
||||
LibUIUtils.WHATSNEW_DIALOG -> return UIUtils.createWhatsNewDialog(this)
|
||||
LibUIUtils.HELP_DIALOG -> return UIUtils.createHelpDialog(this)
|
||||
LibUIUtils.CLEAR_ASSIST_WARNING_DIALOG -> return createClearAssistWarningDialog()
|
||||
}
|
||||
return super.onCreateDialog(id)
|
||||
}
|
||||
@@ -890,7 +902,8 @@ class MainActivity : AppCompatActivity(), NavigationDrawerCallbacks {
|
||||
// Save the preference
|
||||
PreferenceUtils.saveBoolean(
|
||||
getString(R.string.pref_key_never_show_clear_assist_warning),
|
||||
isChecked
|
||||
isChecked,
|
||||
prefs
|
||||
)
|
||||
}
|
||||
val icon = ContextCompat.getDrawable(Application.app, R.drawable.ic_delete)
|
||||
|
||||
@@ -44,8 +44,8 @@ import androidx.fragment.app.Fragment;
|
||||
|
||||
import com.android.gpstest.Application;
|
||||
import com.android.gpstest.R;
|
||||
import com.android.gpstest.util.IOUtils;
|
||||
import com.android.gpstest.util.UIUtils;
|
||||
import com.android.gpstest.library.util.IOUtils;
|
||||
import com.android.gpstest.library.util.LibUIUtils;
|
||||
import com.android.gpstest.view.ScrimInsetsScrollView;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@@ -177,7 +177,7 @@ public class NavigationDrawerFragment extends Fragment {
|
||||
super.onCreate(savedInstanceState);
|
||||
SharedPreferences sp = Application.Companion.getPrefs();
|
||||
|
||||
if (IOUtils.isShowRadarIntent(getActivity().getIntent())) {
|
||||
if (IOUtils.isShowRadarIntent(Application.Companion.getApp(), getActivity().getIntent())) {
|
||||
// If another app (e.g., BenchMap) passed in a ground truth location, show the Accuracy view
|
||||
mCurrentSelectedPosition = NAVDRAWER_ITEM_ACCURACY;
|
||||
Log.d(TAG, "Using Accuracy position due to RADAR intent = " + mCurrentSelectedPosition);
|
||||
@@ -463,7 +463,7 @@ public class NavigationDrawerFragment extends Fragment {
|
||||
|
||||
if (isSeparator(itemId)) {
|
||||
// we are done
|
||||
UIUtils.setAccessibilityIgnore(view);
|
||||
LibUIUtils.setAccessibilityIgnore(view);
|
||||
return view;
|
||||
}
|
||||
|
||||
|
||||
@@ -33,11 +33,11 @@ import com.android.gpstest.Application.Companion.localeManager
|
||||
import com.android.gpstest.Application.Companion.prefs
|
||||
import com.android.gpstest.BuildConfig
|
||||
import com.android.gpstest.R
|
||||
import com.android.gpstest.util.PermissionUtils
|
||||
import com.android.gpstest.util.PreferenceUtil.enableMeasurementsPref
|
||||
import com.android.gpstest.util.PreferenceUtil.enableNavMessagesPref
|
||||
import com.android.gpstest.util.SatelliteUtils
|
||||
import com.android.gpstest.util.UIUtils.resetActivityTitle
|
||||
import com.android.gpstest.library.util.PermissionUtils
|
||||
import com.android.gpstest.library.util.PreferenceUtil.enableMeasurementsPref
|
||||
import com.android.gpstest.library.util.SatelliteUtils
|
||||
import com.android.gpstest.library.util.LibUIUtils.resetActivityTitle
|
||||
import com.android.gpstest.library.util.PreferenceUtil.enableNavMessagesPref
|
||||
|
||||
class Preferences : PreferenceActivity(), OnSharedPreferenceChangeListener {
|
||||
var forceFullGnssMeasurements: CheckBoxPreference? = null
|
||||
@@ -175,7 +175,7 @@ class Preferences : PreferenceActivity(), OnSharedPreferenceChangeListener {
|
||||
}
|
||||
chkLogFileNavMessages =
|
||||
findPreference(getString(R.string.pref_key_file_navigation_message_output)) as CheckBoxPreference
|
||||
chkLogFileNavMessages?.isEnabled = enableNavMessagesPref()
|
||||
chkLogFileNavMessages?.isEnabled = enableNavMessagesPref(app, prefs)
|
||||
chkLogFileNavMessages?.onPreferenceChangeListener =
|
||||
OnPreferenceChangeListener { _: Preference?, _: Any? ->
|
||||
PermissionUtils.requestFileWritePermission(this@Preferences)
|
||||
@@ -183,7 +183,7 @@ class Preferences : PreferenceActivity(), OnSharedPreferenceChangeListener {
|
||||
}
|
||||
chkLogFileMeasurements =
|
||||
findPreference(getString(R.string.pref_key_file_measurement_output)) as CheckBoxPreference
|
||||
chkLogFileMeasurements?.isEnabled = enableMeasurementsPref();
|
||||
chkLogFileMeasurements?.isEnabled = enableMeasurementsPref(app, prefs);
|
||||
chkLogFileMeasurements?.onPreferenceChangeListener =
|
||||
OnPreferenceChangeListener { preference: Preference?, newValue: Any? ->
|
||||
PermissionUtils.requestFileWritePermission(this@Preferences)
|
||||
@@ -223,9 +223,9 @@ class Preferences : PreferenceActivity(), OnSharedPreferenceChangeListener {
|
||||
|
||||
// Disable Android Studio logging if not supported by platform
|
||||
chkAsMeasurements = findPreference(getString(R.string.pref_key_as_measurement_output)) as CheckBoxPreference
|
||||
chkAsMeasurements?.isEnabled = enableMeasurementsPref()
|
||||
chkAsMeasurements?.isEnabled = enableMeasurementsPref(app, prefs)
|
||||
chkAsNavMessages = findPreference(getString(R.string.pref_key_as_navigation_message_output)) as CheckBoxPreference
|
||||
chkAsNavMessages?.isEnabled = enableNavMessagesPref()
|
||||
chkAsNavMessages?.isEnabled = enableNavMessagesPref(app, prefs)
|
||||
|
||||
prefs.registerOnSharedPreferenceChangeListener(this)
|
||||
}
|
||||
|
||||
@@ -13,11 +13,13 @@ import android.widget.TextView
|
||||
import android.widget.Toast
|
||||
import androidx.fragment.app.Fragment
|
||||
import com.android.gpstest.Application
|
||||
import com.android.gpstest.Application.Companion.app
|
||||
import com.android.gpstest.Application.Companion.prefs
|
||||
import com.android.gpstest.R
|
||||
import com.android.gpstest.model.CoordinateType
|
||||
import com.android.gpstest.util.IOUtils
|
||||
import com.android.gpstest.util.PreferenceUtils
|
||||
import com.android.gpstest.util.UIUtils
|
||||
import com.android.gpstest.library.model.CoordinateType
|
||||
import com.android.gpstest.library.util.IOUtils
|
||||
import com.android.gpstest.library.util.PreferenceUtils
|
||||
import com.android.gpstest.library.util.LibUIUtils
|
||||
import com.google.android.material.button.MaterialButton
|
||||
import com.google.android.material.chip.Chip
|
||||
import com.google.android.material.chip.ChipGroup
|
||||
@@ -64,7 +66,8 @@ class ShareLocationFragment : Fragment() {
|
||||
// Check selected coordinate format and show in UI
|
||||
val coordinateFormat = Application.prefs.getString(Application.app.getString(R.string.pref_key_coordinate_format), Application.app.getString(R.string.preferences_coordinate_format_dd_key))
|
||||
if (location != null) {
|
||||
UIUtils.formatLocationForDisplay(
|
||||
LibUIUtils.formatLocationForDisplay(
|
||||
app,
|
||||
location,
|
||||
locationValue,
|
||||
includeAltitude.isChecked,
|
||||
@@ -86,7 +89,8 @@ class ShareLocationFragment : Fragment() {
|
||||
format = "ddm"
|
||||
}
|
||||
if (location != null) {
|
||||
UIUtils.formatLocationForDisplay(
|
||||
LibUIUtils.formatLocationForDisplay(
|
||||
app,
|
||||
location,
|
||||
locationValue,
|
||||
isChecked,
|
||||
@@ -96,7 +100,7 @@ class ShareLocationFragment : Fragment() {
|
||||
format
|
||||
)
|
||||
}
|
||||
PreferenceUtils.saveBoolean(Application.app.getString(R.string.pref_key_share_include_altitude), isChecked)
|
||||
PreferenceUtils.saveBoolean(Application.app.getString(R.string.pref_key_share_include_altitude), isChecked, prefs)
|
||||
}
|
||||
|
||||
chipDecimalDegrees.setOnCheckedChangeListener { _: CompoundButton?, isChecked: Boolean ->
|
||||
@@ -110,8 +114,9 @@ class ShareLocationFragment : Fragment() {
|
||||
chipDMS.setOnCheckedChangeListener { _: CompoundButton?, isChecked: Boolean ->
|
||||
if (isChecked) {
|
||||
if (location != null) {
|
||||
locationValue.text = IOUtils.createLocationShare(UIUtils.getDMSFromLocation(Application.app, location.latitude, CoordinateType.LATITUDE),
|
||||
UIUtils.getDMSFromLocation(Application.app, location.longitude, CoordinateType.LONGITUDE),
|
||||
locationValue.text = IOUtils.createLocationShare(
|
||||
LibUIUtils.getDMSFromLocation(Application.app, location.latitude, CoordinateType.LATITUDE),
|
||||
LibUIUtils.getDMSFromLocation(Application.app, location.longitude, CoordinateType.LONGITUDE),
|
||||
if (location.hasAltitude() && includeAltitude.isChecked) location.altitude.toString() else null)
|
||||
}
|
||||
}
|
||||
@@ -119,8 +124,9 @@ class ShareLocationFragment : Fragment() {
|
||||
chipDegreesDecimalMin.setOnCheckedChangeListener { _: CompoundButton?, isChecked: Boolean ->
|
||||
if (isChecked) {
|
||||
if (location != null) {
|
||||
locationValue.text = IOUtils.createLocationShare(UIUtils.getDDMFromLocation(Application.app, location.latitude, CoordinateType.LATITUDE),
|
||||
UIUtils.getDDMFromLocation(Application.app, location.longitude, CoordinateType.LONGITUDE),
|
||||
locationValue.text = IOUtils.createLocationShare(
|
||||
LibUIUtils.getDDMFromLocation(Application.app, location.latitude, CoordinateType.LATITUDE),
|
||||
LibUIUtils.getDDMFromLocation(Application.app, location.longitude, CoordinateType.LONGITUDE),
|
||||
if (location.hasAltitude() && includeAltitude.isChecked) location.altitude.toString() else null)
|
||||
}
|
||||
}
|
||||
@@ -130,7 +136,7 @@ class ShareLocationFragment : Fragment() {
|
||||
// Copy to clipboard
|
||||
if (location != null) {
|
||||
val locationString = locationValue.text.toString()
|
||||
IOUtils.copyToClipboard(locationString)
|
||||
IOUtils.copyToClipboard(app, locationString)
|
||||
Toast.makeText(activity, R.string.copied_to_clipboard, Toast.LENGTH_LONG).show()
|
||||
}
|
||||
}
|
||||
@@ -149,7 +155,7 @@ class ShareLocationFragment : Fragment() {
|
||||
// Open the location in another app
|
||||
if (location != null) {
|
||||
val intent = Intent(Intent.ACTION_VIEW)
|
||||
intent.data = Uri.parse(IOUtils.createGeoUri(location, includeAltitude.isChecked))
|
||||
intent.data = Uri.parse(IOUtils.createGeoUri(app, location, includeAltitude.isChecked))
|
||||
if (intent.resolveActivity(requireActivity().packageManager) != null) {
|
||||
requireActivity().startActivity(intent)
|
||||
}
|
||||
@@ -161,7 +167,7 @@ class ShareLocationFragment : Fragment() {
|
||||
if (location != null) {
|
||||
val intent = Intent(Intent.ACTION_SEND)
|
||||
val text: String = if (chipDecimalDegrees.isChecked) {
|
||||
IOUtils.createGeoUri(location, includeAltitude.isChecked)
|
||||
IOUtils.createGeoUri(app, location, includeAltitude.isChecked)
|
||||
} else {
|
||||
locationValue.text.toString()
|
||||
}
|
||||
|
||||
@@ -8,9 +8,10 @@ import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.TextView
|
||||
import androidx.fragment.app.Fragment
|
||||
import com.android.gpstest.Application.Companion.app
|
||||
import com.android.gpstest.R
|
||||
import com.android.gpstest.util.IOUtils
|
||||
import com.android.gpstest.util.UIUtils
|
||||
import com.android.gpstest.library.util.IOUtils
|
||||
import com.android.gpstest.library.util.LibUIUtils
|
||||
import com.google.android.material.button.MaterialButton
|
||||
import java.io.File
|
||||
import java.util.*
|
||||
@@ -88,7 +89,7 @@ class ShareLogFragment : Fragment() {
|
||||
val uri = IOUtils.getUriFromFile(activity, files?.get(0))
|
||||
val intent = Intent(Intent.ACTION_GET_CONTENT)
|
||||
intent.data = uri
|
||||
activity!!.startActivityForResult(intent, UIUtils.PICKFILE_REQUEST_CODE)
|
||||
activity!!.startActivityForResult(intent, LibUIUtils.PICKFILE_REQUEST_CODE)
|
||||
// Dismiss the dialog - it will be re-created in the callback to GpsTestActivity
|
||||
listener.onFileBrowse()
|
||||
}
|
||||
@@ -97,11 +98,11 @@ class ShareLogFragment : Fragment() {
|
||||
// Send the log file
|
||||
if (alternateFileUri == null && files != null) {
|
||||
// Send the log file currently being logged to by the FileLogger
|
||||
IOUtils.sendLogFile(activity, *files.toTypedArray())
|
||||
IOUtils.sendLogFile(app, activity, *files.toTypedArray())
|
||||
listener.onLogFileSent()
|
||||
} else {
|
||||
// Send the log file selected by the user using the File Browse button
|
||||
IOUtils.sendLogFile(activity, ArrayList(Collections.singleton(alternateFileUri)))
|
||||
IOUtils.sendLogFile(app, activity, ArrayList(Collections.singleton(alternateFileUri)))
|
||||
listener.onLogFileSent()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,13 +21,15 @@ import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.lifecycle.coroutineScope
|
||||
import com.android.gpstest.Application
|
||||
import com.android.gpstest.Application.Companion.app
|
||||
import com.android.gpstest.Application.Companion.prefs
|
||||
import com.android.gpstest.BuildConfig
|
||||
import com.android.gpstest.R
|
||||
import com.android.gpstest.io.DevicePropertiesUploader
|
||||
import com.android.gpstest.ui.SignalInfoViewModel
|
||||
import com.android.gpstest.util.IOUtils.*
|
||||
import com.android.gpstest.util.PreferenceUtils
|
||||
import com.android.gpstest.util.SatelliteUtils
|
||||
import com.android.gpstest.library.ui.SignalInfoViewModel
|
||||
import com.android.gpstest.library.util.IOUtils.*
|
||||
import com.android.gpstest.library.util.PreferenceUtils
|
||||
import com.android.gpstest.library.util.SatelliteUtils
|
||||
import com.google.android.material.button.MaterialButton
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.launch
|
||||
@@ -66,7 +68,7 @@ class UploadDeviceInfoFragment : Fragment() {
|
||||
uploadNoLocationTextView.visibility = View.GONE
|
||||
|
||||
if (Geocoder.isPresent()) {
|
||||
val geocoder = Geocoder(context)
|
||||
val geocoder = Geocoder(context!!)
|
||||
var addresses: List<Address>? = emptyList()
|
||||
try {
|
||||
addresses = geocoder.getFromLocation(location.latitude, location.longitude, 1)
|
||||
@@ -98,10 +100,10 @@ class UploadDeviceInfoFragment : Fragment() {
|
||||
val psdsSuccessBoolean: Boolean
|
||||
val psdsSuccessString: String
|
||||
if (capabilityInjectPsdsInt == PreferenceUtils.CAPABILITY_UNKNOWN) {
|
||||
psdsSuccessBoolean = forcePsdsInjection(locationManager)
|
||||
psdsSuccessString = PreferenceUtils.getCapabilityDescription(psdsSuccessBoolean)
|
||||
psdsSuccessBoolean = forcePsdsInjection(app, locationManager)
|
||||
psdsSuccessString = PreferenceUtils.getCapabilityDescription(app, psdsSuccessBoolean)
|
||||
} else {
|
||||
psdsSuccessString = PreferenceUtils.getCapabilityDescription(capabilityInjectPsdsInt)
|
||||
psdsSuccessString = PreferenceUtils.getCapabilityDescription(app, capabilityInjectPsdsInt)
|
||||
}
|
||||
|
||||
// Inject time
|
||||
@@ -109,10 +111,10 @@ class UploadDeviceInfoFragment : Fragment() {
|
||||
val timeSuccessBoolean: Boolean
|
||||
val timeSuccessString: String
|
||||
if (capabilityInjectTimeInt == PreferenceUtils.CAPABILITY_UNKNOWN) {
|
||||
timeSuccessBoolean = forceTimeInjection(locationManager)
|
||||
timeSuccessString = PreferenceUtils.getCapabilityDescription(timeSuccessBoolean)
|
||||
timeSuccessBoolean = forceTimeInjection(app, locationManager)
|
||||
timeSuccessString = PreferenceUtils.getCapabilityDescription(app, timeSuccessBoolean)
|
||||
} else {
|
||||
timeSuccessString = PreferenceUtils.getCapabilityDescription(capabilityInjectTimeInt)
|
||||
timeSuccessString = PreferenceUtils.getCapabilityDescription(app, capabilityInjectTimeInt)
|
||||
}
|
||||
|
||||
// Delete assist capability
|
||||
@@ -120,7 +122,7 @@ class UploadDeviceInfoFragment : Fragment() {
|
||||
val deleteAssistSuccessString: String
|
||||
if (capabilityDeleteAssistInt != PreferenceUtils.CAPABILITY_UNKNOWN) {
|
||||
// Deleting assist data can be destructive, so don't force it - just use existing info
|
||||
deleteAssistSuccessString = PreferenceUtils.getCapabilityDescription(capabilityDeleteAssistInt)
|
||||
deleteAssistSuccessString = PreferenceUtils.getCapabilityDescription(app, capabilityDeleteAssistInt)
|
||||
} else {
|
||||
deleteAssistSuccessString = ""
|
||||
}
|
||||
@@ -129,7 +131,7 @@ class UploadDeviceInfoFragment : Fragment() {
|
||||
val capabilityMeasurementsInt = Application.prefs.getInt(Application.app.getString(R.string.capability_key_raw_measurements), PreferenceUtils.CAPABILITY_UNKNOWN)
|
||||
val capabilityMeasurementsString: String
|
||||
if (capabilityMeasurementsInt != PreferenceUtils.CAPABILITY_UNKNOWN) {
|
||||
capabilityMeasurementsString = PreferenceUtils.getCapabilityDescription(capabilityMeasurementsInt)
|
||||
capabilityMeasurementsString = PreferenceUtils.getCapabilityDescription(app, capabilityMeasurementsInt)
|
||||
} else {
|
||||
capabilityMeasurementsString = ""
|
||||
}
|
||||
@@ -138,17 +140,17 @@ class UploadDeviceInfoFragment : Fragment() {
|
||||
val capabilityNavMessagesInt = Application.prefs.getInt(Application.app.getString(R.string.capability_key_nav_messages), PreferenceUtils.CAPABILITY_UNKNOWN)
|
||||
val capabilityNavMessagesString: String
|
||||
if (capabilityNavMessagesInt != PreferenceUtils.CAPABILITY_UNKNOWN) {
|
||||
capabilityNavMessagesString = PreferenceUtils.getCapabilityDescription(capabilityNavMessagesInt)
|
||||
capabilityNavMessagesString = PreferenceUtils.getCapabilityDescription(app, capabilityNavMessagesInt)
|
||||
} else {
|
||||
capabilityNavMessagesString = ""
|
||||
}
|
||||
|
||||
val gnssAntennaInfo = PreferenceUtils.getCapabilityDescription(SatelliteUtils.isGnssAntennaInfoSupported(locationManager))
|
||||
val gnssAntennaInfo = PreferenceUtils.getCapabilityDescription(app, SatelliteUtils.isGnssAntennaInfoSupported(locationManager))
|
||||
val numAntennas: String
|
||||
val antennaCfs: String
|
||||
if (gnssAntennaInfo.equals(Application.app.getString(R.string.capability_value_supported))) {
|
||||
numAntennas = PreferenceUtils.getInt(Application.app.getString(R.string.capability_key_num_antenna), -1).toString()
|
||||
antennaCfs = PreferenceUtils.getString(Application.app.getString(R.string.capability_key_antenna_cf))
|
||||
numAntennas = PreferenceUtils.getInt(Application.app.getString(R.string.capability_key_num_antenna), -1, prefs).toString()
|
||||
antennaCfs = PreferenceUtils.getString(Application.app.getString(R.string.capability_key_antenna_cf), prefs)
|
||||
} else {
|
||||
numAntennas = ""
|
||||
antennaCfs = ""
|
||||
@@ -161,24 +163,24 @@ class UploadDeviceInfoFragment : Fragment() {
|
||||
DevicePropertiesUploader.DEVICE to Build.DEVICE,
|
||||
DevicePropertiesUploader.ANDROID_VERSION to Build.VERSION.RELEASE,
|
||||
DevicePropertiesUploader.API_LEVEL to Build.VERSION.SDK_INT.toString(),
|
||||
DevicePropertiesUploader.GNSS_HARDWARE_YEAR to getGnssHardwareYear(),
|
||||
DevicePropertiesUploader.GNSS_HARDWARE_MODEL_NAME to getGnssHardwareModelName(),
|
||||
DevicePropertiesUploader.DUAL_FREQUENCY to PreferenceUtils.getCapabilityDescription(signalInfoViewModel.isNonPrimaryCarrierFreqInView),
|
||||
DevicePropertiesUploader.GNSS_HARDWARE_YEAR to getGnssHardwareYear(app),
|
||||
DevicePropertiesUploader.GNSS_HARDWARE_MODEL_NAME to getGnssHardwareModelName(app),
|
||||
DevicePropertiesUploader.DUAL_FREQUENCY to PreferenceUtils.getCapabilityDescription(app, signalInfoViewModel.isNonPrimaryCarrierFreqInView),
|
||||
DevicePropertiesUploader.SUPPORTED_GNSS to trimEnds(replaceNavstar(signalInfoViewModel.getSupportedGnss().sorted().toString())),
|
||||
DevicePropertiesUploader.GNSS_CFS to trimEnds(signalInfoViewModel.getSupportedGnssCfs().sorted().toString()),
|
||||
DevicePropertiesUploader.SUPPORTED_SBAS to trimEnds(signalInfoViewModel.getSupportedSbas().sorted().toString()),
|
||||
DevicePropertiesUploader.SBAS_CFS to trimEnds(signalInfoViewModel.getSupportedSbasCfs().sorted().toString()),
|
||||
DevicePropertiesUploader.RAW_MEASUREMENTS to capabilityMeasurementsString,
|
||||
DevicePropertiesUploader.NAVIGATION_MESSAGES to capabilityNavMessagesString,
|
||||
DevicePropertiesUploader.NMEA to PreferenceUtils.getCapabilityDescription(Application.prefs.getInt(Application.app.getString(R.string.capability_key_nmea), PreferenceUtils.CAPABILITY_UNKNOWN)),
|
||||
DevicePropertiesUploader.NMEA to PreferenceUtils.getCapabilityDescription(app, Application.prefs.getInt(Application.app.getString(R.string.capability_key_nmea), PreferenceUtils.CAPABILITY_UNKNOWN)),
|
||||
DevicePropertiesUploader.INJECT_PSDS to psdsSuccessString,
|
||||
DevicePropertiesUploader.INJECT_TIME to timeSuccessString,
|
||||
DevicePropertiesUploader.DELETE_ASSIST to deleteAssistSuccessString,
|
||||
DevicePropertiesUploader.ACCUMULATED_DELTA_RANGE to PreferenceUtils.getCapabilityDescription(Application.prefs.getInt(Application.app.getString(R.string.capability_key_measurement_delta_range), PreferenceUtils.CAPABILITY_UNKNOWN)),
|
||||
DevicePropertiesUploader.ACCUMULATED_DELTA_RANGE to PreferenceUtils.getCapabilityDescription(app, Application.prefs.getInt(Application.app.getString(R.string.capability_key_measurement_delta_range), PreferenceUtils.CAPABILITY_UNKNOWN)),
|
||||
// TODO - Add below clock values? What should they be to generalize across all of the same model?
|
||||
DevicePropertiesUploader.HARDWARE_CLOCK to "",
|
||||
DevicePropertiesUploader.HARDWARE_CLOCK_DISCONTINUITY to "",
|
||||
DevicePropertiesUploader.AUTOMATIC_GAIN_CONTROL to PreferenceUtils.getCapabilityDescription(Application.prefs.getInt(Application.app.getString(R.string.capability_key_measurement_automatic_gain_control), PreferenceUtils.CAPABILITY_UNKNOWN)),
|
||||
DevicePropertiesUploader.AUTOMATIC_GAIN_CONTROL to PreferenceUtils.getCapabilityDescription(app, Application.prefs.getInt(Application.app.getString(R.string.capability_key_measurement_automatic_gain_control), PreferenceUtils.CAPABILITY_UNKNOWN)),
|
||||
DevicePropertiesUploader.GNSS_ANTENNA_INFO to gnssAntennaInfo,
|
||||
DevicePropertiesUploader.APP_BUILD_FLAVOR to BuildConfig.FLAVOR,
|
||||
DevicePropertiesUploader.USER_COUNTRY to userCountry,
|
||||
@@ -205,13 +207,13 @@ class UploadDeviceInfoFragment : Fragment() {
|
||||
lifecycle.coroutineScope.launch {
|
||||
val uploader = DevicePropertiesUploader(bundle)
|
||||
if (uploader.upload()) {
|
||||
Toast.makeText(Application.app, R.string.upload_success, Toast.LENGTH_SHORT).show()
|
||||
Toast.makeText(app, R.string.upload_success, Toast.LENGTH_SHORT).show()
|
||||
// Remove app version and code, and then save hash to compare against next upload attempt
|
||||
bundle.remove(DevicePropertiesUploader.APP_VERSION_NAME)
|
||||
bundle.remove(DevicePropertiesUploader.APP_VERSION_CODE)
|
||||
PreferenceUtils.saveInt(Application.app.getString(R.string.capability_key_last_upload_hash), bundle.toString().hashCode())
|
||||
PreferenceUtils.saveInt(app.getString(R.string.capability_key_last_upload_hash), bundle.toString().hashCode(), prefs)
|
||||
} else {
|
||||
Toast.makeText(Application.app, R.string.upload_failure, Toast.LENGTH_SHORT).show()
|
||||
Toast.makeText(app, R.string.upload_failure, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
upload.isEnabled = true
|
||||
uploadProgress.visibility = View.INVISIBLE
|
||||
|
||||
@@ -41,24 +41,26 @@ import androidx.lifecycle.Observer
|
||||
import androidx.lifecycle.flowWithLifecycle
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.android.gpstest.Application
|
||||
import com.android.gpstest.Application.Companion.app
|
||||
import com.android.gpstest.Application.Companion.prefs
|
||||
import com.android.gpstest.R
|
||||
import com.android.gpstest.data.FixState
|
||||
import com.android.gpstest.data.LocationRepository
|
||||
import com.android.gpstest.library.data.FixState
|
||||
import com.android.gpstest.library.data.LocationRepository
|
||||
import com.android.gpstest.databinding.GpsSkyBinding
|
||||
import com.android.gpstest.databinding.GpsSkyLegendCardBinding
|
||||
import com.android.gpstest.databinding.GpsSkySignalMeterBinding
|
||||
import com.android.gpstest.model.SatelliteMetadata
|
||||
import com.android.gpstest.model.SatelliteStatus
|
||||
import com.android.gpstest.ui.SignalInfoViewModel
|
||||
import com.android.gpstest.library.model.SatelliteMetadata
|
||||
import com.android.gpstest.library.model.SatelliteStatus
|
||||
import com.android.gpstest.library.ui.SignalInfoViewModel
|
||||
import com.android.gpstest.ui.status.Filter
|
||||
import com.android.gpstest.ui.theme.AppTheme
|
||||
import com.android.gpstest.util.MathUtils
|
||||
import com.android.gpstest.util.PreferenceUtil
|
||||
import com.android.gpstest.util.PreferenceUtil.darkTheme
|
||||
import com.android.gpstest.util.PreferenceUtils
|
||||
import com.android.gpstest.util.PreferenceUtils.clearGnssFilter
|
||||
import com.android.gpstest.util.PreferenceUtils.gnssFilter
|
||||
import com.android.gpstest.util.UIUtils
|
||||
import com.android.gpstest.library.util.MathUtils
|
||||
import com.android.gpstest.library.util.PreferenceUtil
|
||||
import com.android.gpstest.library.util.PreferenceUtil.darkTheme
|
||||
import com.android.gpstest.library.util.PreferenceUtils
|
||||
import com.android.gpstest.library.util.PreferenceUtils.clearGnssFilter
|
||||
import com.android.gpstest.library.util.PreferenceUtils.gnssFilter
|
||||
import com.android.gpstest.library.util.LibUIUtils
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.Job
|
||||
@@ -100,7 +102,7 @@ class SkyFragment : Fragment() {
|
||||
|
||||
// Preference listener that will cancel the above flows when the user turns off tracking via UI
|
||||
private val trackingListener: SharedPreferences.OnSharedPreferenceChangeListener =
|
||||
PreferenceUtil.newStopTrackingListener { onGnssStopped() }
|
||||
PreferenceUtil.newStopTrackingListener ({ onGnssStopped() }, prefs)
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
override fun onCreateView(
|
||||
@@ -126,7 +128,7 @@ class SkyFragment : Fragment() {
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
val color: Int
|
||||
if (darkTheme()) {
|
||||
if (darkTheme(app, prefs)) {
|
||||
// Dark theme
|
||||
color = ContextCompat.getColor(requireContext(), android.R.color.secondary_text_dark)
|
||||
legend.skyLegendUsedInFix.setImageResource(R.drawable.circle_used_in_fix_dark)
|
||||
@@ -182,7 +184,7 @@ class SkyFragment : Fragment() {
|
||||
.onEach {
|
||||
when (it) {
|
||||
is FixState.Acquired -> onGnssFixAcquired()
|
||||
is FixState.NotAcquired -> if (PreferenceUtils.isTrackingStarted()) onGnssFixLost()
|
||||
is FixState.NotAcquired -> if (PreferenceUtils.isTrackingStarted(prefs)) onGnssFixLost()
|
||||
}
|
||||
}
|
||||
.launchIn(lifecycleScope)
|
||||
@@ -252,15 +254,15 @@ class SkyFragment : Fragment() {
|
||||
// Dispose the Composition when the view's LifecycleOwner is destroyed
|
||||
setViewCompositionStrategy(DisposeOnViewTreeLifecycleDestroyed)
|
||||
setContent {
|
||||
AppTheme(darkTheme = darkTheme()) {
|
||||
AppTheme(darkTheme = darkTheme(app, prefs)) {
|
||||
val allStatuses: List<SatelliteStatus> by viewModel.allStatuses.observeAsState(emptyList())
|
||||
val satelliteMetadata: SatelliteMetadata by viewModel.filteredSatelliteMetadata.observeAsState(
|
||||
SatelliteMetadata()
|
||||
)
|
||||
// Order of arguments seems to matter in below IF statement - it doesn't seem
|
||||
// to recompose if gnssFilter().isNotEmpty() is first
|
||||
if (allStatuses.isNotEmpty() && gnssFilter().isNotEmpty()) {
|
||||
Filter(allStatuses.size, satelliteMetadata) { clearGnssFilter() }
|
||||
if (allStatuses.isNotEmpty() && gnssFilter(app, prefs).isNotEmpty()) {
|
||||
Filter(allStatuses.size, satelliteMetadata) { clearGnssFilter(app, prefs) }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -351,7 +353,7 @@ class SkyFragment : Fragment() {
|
||||
// Based on the avg C/N0 for "in view" and "used" satellites the left margins need to be adjusted accordingly
|
||||
val meterWidthPx = (Application.app.resources.getDimension(R.dimen.cn0_meter_width)
|
||||
.toInt()
|
||||
- UIUtils.dpToPixels(Application.app, 7.0f)) // Reduce width for padding
|
||||
- LibUIUtils.dpToPixels(Application.app, 7.0f)) // Reduce width for padding
|
||||
val minIndicatorMarginPx = Application.app.resources
|
||||
.getDimension(R.dimen.cn0_indicator_min_left_margin).toInt()
|
||||
val maxIndicatorMarginPx = meterWidthPx + minIndicatorMarginPx
|
||||
@@ -365,7 +367,7 @@ class SkyFragment : Fragment() {
|
||||
// Calculate normal offsets for avg in view satellite C/N0 value TextViews
|
||||
var leftInViewTextViewMarginPx: Int? = null
|
||||
if (MathUtils.isValidFloat(binding.skyView.cn0InViewAvg)) {
|
||||
leftInViewTextViewMarginPx = UIUtils.cn0ToTextViewLeftMarginPx(
|
||||
leftInViewTextViewMarginPx = LibUIUtils.cn0ToTextViewLeftMarginPx(
|
||||
binding.skyView.cn0InViewAvg,
|
||||
minTextViewMarginPx, maxTextViewMarginPx
|
||||
)
|
||||
@@ -374,7 +376,7 @@ class SkyFragment : Fragment() {
|
||||
// Calculate normal offsets for avg used satellite C/N0 value TextViews
|
||||
var leftUsedTextViewMarginPx: Int? = null
|
||||
if (MathUtils.isValidFloat(binding.skyView.cn0UsedAvg)) {
|
||||
leftUsedTextViewMarginPx = UIUtils.cn0ToTextViewLeftMarginPx(
|
||||
leftUsedTextViewMarginPx = LibUIUtils.cn0ToTextViewLeftMarginPx(
|
||||
binding.skyView.cn0UsedAvg,
|
||||
minTextViewMarginPx, maxTextViewMarginPx
|
||||
)
|
||||
@@ -382,7 +384,7 @@ class SkyFragment : Fragment() {
|
||||
|
||||
// See if we need to apply the offset margin to try and keep the two TextViews from overlapping by shifting one of the two left
|
||||
if (leftInViewTextViewMarginPx != null && leftUsedTextViewMarginPx != null) {
|
||||
val offset = UIUtils.dpToPixels(Application.app, TEXTVIEW_NON_OVERLAP_OFFSET_DP)
|
||||
val offset = LibUIUtils.dpToPixels(Application.app, TEXTVIEW_NON_OVERLAP_OFFSET_DP)
|
||||
if (leftInViewTextViewMarginPx <= leftUsedTextViewMarginPx) {
|
||||
leftInViewTextViewMarginPx += offset
|
||||
} else {
|
||||
@@ -391,8 +393,8 @@ class SkyFragment : Fragment() {
|
||||
}
|
||||
|
||||
// Define paddings used for TextViews
|
||||
val pSides = UIUtils.dpToPixels(Application.app, 7f)
|
||||
val pTopBottom = UIUtils.dpToPixels(Application.app, 4f)
|
||||
val pSides = LibUIUtils.dpToPixels(Application.app, 7f)
|
||||
val pTopBottom = LibUIUtils.dpToPixels(Application.app, 4f)
|
||||
|
||||
// Set avg C/N0 of satellites in view of device
|
||||
if (MathUtils.isValidFloat(binding.skyView.cn0InViewAvg)) {
|
||||
@@ -444,7 +446,7 @@ class SkyFragment : Fragment() {
|
||||
}
|
||||
|
||||
// Set position and visibility of indicator
|
||||
val leftIndicatorMarginPx = UIUtils.cn0ToIndicatorLeftMarginPx(
|
||||
val leftIndicatorMarginPx = LibUIUtils.cn0ToIndicatorLeftMarginPx(
|
||||
binding.skyView.cn0InViewAvg,
|
||||
minIndicatorMarginPx, maxIndicatorMarginPx
|
||||
)
|
||||
@@ -508,7 +510,7 @@ class SkyFragment : Fragment() {
|
||||
}
|
||||
|
||||
// Set position and visibility of indicator
|
||||
val leftMarginPx = UIUtils.cn0ToIndicatorLeftMarginPx(
|
||||
val leftMarginPx = LibUIUtils.cn0ToIndicatorLeftMarginPx(
|
||||
binding.skyView.cn0UsedAvg,
|
||||
minIndicatorMarginPx, maxIndicatorMarginPx
|
||||
)
|
||||
@@ -552,7 +554,7 @@ class SkyFragment : Fragment() {
|
||||
currentMargin - (abs(currentMargin - goalLeftMarginPx)
|
||||
* interpolatedTime).toInt()
|
||||
}
|
||||
UIUtils.setMargins(
|
||||
LibUIUtils.setMargins(
|
||||
v,
|
||||
newLeft,
|
||||
p.topMargin,
|
||||
@@ -569,11 +571,11 @@ class SkyFragment : Fragment() {
|
||||
}
|
||||
|
||||
private fun showHaveFix() {
|
||||
UIUtils.showViewWithAnimation(binding.skyLock, UIUtils.ANIMATION_DURATION_SHORT_MS)
|
||||
LibUIUtils.showViewWithAnimation(binding.skyLock, LibUIUtils.ANIMATION_DURATION_SHORT_MS)
|
||||
}
|
||||
|
||||
private fun showLostFix() {
|
||||
UIUtils.hideViewWithAnimation(binding.skyLock, UIUtils.ANIMATION_DURATION_SHORT_MS)
|
||||
LibUIUtils.hideViewWithAnimation(binding.skyLock, LibUIUtils.ANIMATION_DURATION_SHORT_MS)
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
@@ -49,19 +49,21 @@ import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.em
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.android.gpstest.Application
|
||||
import com.android.gpstest.Application.Companion.app
|
||||
import com.android.gpstest.Application.Companion.prefs
|
||||
import com.android.gpstest.R
|
||||
import com.android.gpstest.data.FixState
|
||||
import com.android.gpstest.model.CoordinateType
|
||||
import com.android.gpstest.model.DilutionOfPrecision
|
||||
import com.android.gpstest.model.SatelliteMetadata
|
||||
import com.android.gpstest.library.data.FixState
|
||||
import com.android.gpstest.library.model.CoordinateType
|
||||
import com.android.gpstest.library.model.DilutionOfPrecision
|
||||
import com.android.gpstest.library.model.SatelliteMetadata
|
||||
import com.android.gpstest.library.util.*
|
||||
import com.android.gpstest.ui.components.LinkifyText
|
||||
import com.android.gpstest.util.*
|
||||
import com.android.gpstest.util.FormatUtils.formatBearing
|
||||
import com.android.gpstest.util.FormatUtils.formatBearingAccuracy
|
||||
import com.android.gpstest.util.PreferenceUtil.coordinateFormat
|
||||
import com.android.gpstest.util.PreferenceUtil.shareIncludeAltitude
|
||||
import com.android.gpstest.util.PreferenceUtils.gnssFilter
|
||||
import com.android.gpstest.util.SatelliteUtil.isVerticalAccuracySupported
|
||||
import com.android.gpstest.library.util.FormatUtils.formatBearing
|
||||
import com.android.gpstest.library.util.FormatUtils.formatBearingAccuracy
|
||||
import com.android.gpstest.library.util.PreferenceUtil.coordinateFormat
|
||||
import com.android.gpstest.library.util.PreferenceUtil.shareIncludeAltitude
|
||||
import com.android.gpstest.library.util.PreferenceUtils.gnssFilter
|
||||
import com.android.gpstest.library.util.SatelliteUtil.isVerticalAccuracySupported
|
||||
import java.text.SimpleDateFormat
|
||||
|
||||
@Preview
|
||||
@@ -101,7 +103,6 @@ fun previewLocation(): Location {
|
||||
return l
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalAnimationApi::class)
|
||||
@Composable
|
||||
fun LocationCard(
|
||||
location: Location,
|
||||
@@ -162,32 +163,32 @@ fun ValueColumn1(
|
||||
|
||||
@Composable
|
||||
fun Latitude(location: Location) {
|
||||
LocationValue(FormatUtils.formatLatOrLon(location.latitude, CoordinateType.LATITUDE))
|
||||
LocationValue(FormatUtils.formatLatOrLon(app, location.latitude, CoordinateType.LATITUDE, prefs))
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun Longitude(location: Location) {
|
||||
LocationValue(FormatUtils.formatLatOrLon(location.longitude, CoordinateType.LONGITUDE))
|
||||
LocationValue(FormatUtils.formatLatOrLon(app, location.longitude, CoordinateType.LONGITUDE, prefs))
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun Altitude(location: Location) {
|
||||
LocationValue(FormatUtils.formatAltitude(location))
|
||||
LocationValue(FormatUtils.formatAltitude(app, location, prefs))
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun AltitudeMsl(altitudeMsl: Double) {
|
||||
LocationValue(FormatUtils.formatAltitudeMsl(altitudeMsl))
|
||||
LocationValue(FormatUtils.formatAltitudeMsl(app, altitudeMsl, prefs))
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun Speed(location: Location) {
|
||||
LocationValue(FormatUtils.formatSpeed(location))
|
||||
LocationValue(FormatUtils.formatSpeed(app, location, prefs))
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun SpeedAccuracy(location: Location) {
|
||||
LocationValue(FormatUtils.formatSpeedAccuracy(location))
|
||||
LocationValue(FormatUtils.formatSpeedAccuracy(app, location, prefs))
|
||||
}
|
||||
|
||||
@Composable
|
||||
@@ -226,7 +227,7 @@ fun ValueColumn2(
|
||||
|
||||
@Composable
|
||||
fun Time(location: Location) {
|
||||
if (location.time == 0L || !PreferenceUtils.isTrackingStarted()) {
|
||||
if (location.time == 0L || !PreferenceUtils.isTrackingStarted(prefs)) {
|
||||
LocationValue("")
|
||||
} else {
|
||||
formatTime(location.time)
|
||||
@@ -285,12 +286,12 @@ fun TTFF(ttff: String) {
|
||||
*/
|
||||
@Composable
|
||||
fun Accuracy(location: Location) {
|
||||
LocationValue(FormatUtils.formatAccuracy(location))
|
||||
LocationValue(FormatUtils.formatAccuracy(app, location, prefs))
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun NumSats(satelliteMetadata: SatelliteMetadata) {
|
||||
val fontStyle = if (gnssFilter().isNotEmpty()) {
|
||||
val fontStyle = if (gnssFilter(app, prefs).isNotEmpty()) {
|
||||
// Make text italic so it matches filter text
|
||||
FontStyle.Italic
|
||||
} else {
|
||||
@@ -310,7 +311,7 @@ fun NumSats(satelliteMetadata: SatelliteMetadata) {
|
||||
@Composable
|
||||
fun Bearing(location: Location) {
|
||||
if (location.hasBearing()) {
|
||||
LocationValue(formatBearing(location))
|
||||
LocationValue(formatBearing(app, location))
|
||||
} else {
|
||||
LocationValue("")
|
||||
}
|
||||
@@ -318,7 +319,7 @@ fun Bearing(location: Location) {
|
||||
|
||||
@Composable
|
||||
fun BearingAccuracy(location: Location) {
|
||||
LocationValue(formatBearingAccuracy(location))
|
||||
LocationValue(formatBearingAccuracy(app, location))
|
||||
}
|
||||
|
||||
@Composable
|
||||
@@ -507,12 +508,12 @@ fun LockIcon(fixState: FixState) {
|
||||
|
||||
private fun copyToClipboard(context: Context, location: Location) {
|
||||
if (location.latitude == 0.0 && location.longitude == 0.0) return
|
||||
val formattedLocation = UIUtils.formatLocationForDisplay(
|
||||
location, null, shareIncludeAltitude(),
|
||||
null, null, null, coordinateFormat()
|
||||
val formattedLocation = LibUIUtils.formatLocationForDisplay(
|
||||
app, location, null, shareIncludeAltitude(app, prefs),
|
||||
null, null, null, coordinateFormat(app, prefs)
|
||||
)
|
||||
if (!TextUtils.isEmpty(formattedLocation)) {
|
||||
IOUtils.copyToClipboard(formattedLocation)
|
||||
IOUtils.copyToClipboard(app, formattedLocation)
|
||||
// Android 12 and higher generates a Toast automatically
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) {
|
||||
Toast.makeText(context, R.string.copied_to_clipboard, Toast.LENGTH_LONG)
|
||||
|
||||
@@ -18,24 +18,21 @@ package com.android.gpstest.ui.status
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.*
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.ui.platform.ComposeView
|
||||
import androidx.compose.ui.platform.ViewCompositionStrategy
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import com.android.gpstest.Application.Companion.app
|
||||
import com.android.gpstest.Application.Companion.prefs
|
||||
import com.android.gpstest.R
|
||||
import com.android.gpstest.ui.SignalInfoViewModel
|
||||
import com.android.gpstest.library.ui.SignalInfoViewModel
|
||||
import com.android.gpstest.ui.theme.AppTheme
|
||||
import com.android.gpstest.util.PreferenceUtil.darkTheme
|
||||
import com.android.gpstest.library.util.PreferenceUtil.darkTheme
|
||||
import com.android.gpstest.util.UIUtils.showSortByDialog
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
|
||||
@AndroidEntryPoint
|
||||
class StatusFragment : Fragment() {
|
||||
|
||||
@ExperimentalFoundationApi
|
||||
@ExperimentalCoroutinesApi
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater, container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
@@ -47,7 +44,7 @@ class StatusFragment : Fragment() {
|
||||
setContent {
|
||||
AppTheme(
|
||||
// TODO - add "system dark setting" as an option in preferences - Issue #277
|
||||
darkTheme = darkTheme()
|
||||
darkTheme = darkTheme(app, prefs)
|
||||
) {
|
||||
StatusScreen(viewModel = viewModel)
|
||||
}
|
||||
|
||||
@@ -41,18 +41,17 @@ import androidx.compose.ui.text.style.TextDecoration
|
||||
import androidx.compose.ui.text.withStyle
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.android.gpstest.Application.Companion.app
|
||||
import com.android.gpstest.Application.Companion.prefs
|
||||
import com.android.gpstest.R
|
||||
import com.android.gpstest.data.FixState
|
||||
import com.android.gpstest.model.*
|
||||
import com.android.gpstest.ui.SignalInfoViewModel
|
||||
import com.android.gpstest.util.CarrierFreqUtils
|
||||
import com.android.gpstest.util.MathUtils
|
||||
import com.android.gpstest.util.PreferenceUtils
|
||||
import com.android.gpstest.util.PreferenceUtils.gnssFilter
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import com.android.gpstest.library.data.FixState
|
||||
import com.android.gpstest.library.model.*
|
||||
import com.android.gpstest.library.ui.SignalInfoViewModel
|
||||
import com.android.gpstest.library.util.CarrierFreqUtils
|
||||
import com.android.gpstest.library.util.MathUtils
|
||||
import com.android.gpstest.library.util.PreferenceUtils
|
||||
import com.android.gpstest.library.util.PreferenceUtils.gnssFilter
|
||||
|
||||
@ExperimentalCoroutinesApi
|
||||
@ExperimentalFoundationApi
|
||||
@Composable
|
||||
fun StatusScreen(viewModel: SignalInfoViewModel) {
|
||||
//
|
||||
@@ -62,7 +61,9 @@ fun StatusScreen(viewModel: SignalInfoViewModel) {
|
||||
val ttff: String by viewModel.ttff.observeAsState("")
|
||||
val altitudeMsl: Double by viewModel.altitudeMsl.observeAsState(Double.NaN)
|
||||
val dop: DilutionOfPrecision by viewModel.dop.observeAsState(DilutionOfPrecision(Double.NaN,Double.NaN,Double.NaN))
|
||||
val satelliteMetadata: SatelliteMetadata by viewModel.filteredSatelliteMetadata.observeAsState(SatelliteMetadata())
|
||||
val satelliteMetadata: SatelliteMetadata by viewModel.filteredSatelliteMetadata.observeAsState(
|
||||
SatelliteMetadata()
|
||||
)
|
||||
val fixState: FixState by viewModel.fixState.observeAsState(FixState.NotAcquired)
|
||||
val gnssStatuses: List<SatelliteStatus> by viewModel.filteredGnssStatuses.observeAsState(emptyList())
|
||||
val sbasStatuses: List<SatelliteStatus> by viewModel.filteredSbasStatuses.observeAsState(emptyList())
|
||||
@@ -81,8 +82,8 @@ fun StatusScreen(viewModel: SignalInfoViewModel) {
|
||||
dop,
|
||||
satelliteMetadata,
|
||||
fixState)
|
||||
if (gnssFilter().isNotEmpty()) {
|
||||
Filter(allStatuses.size, satelliteMetadata) { PreferenceUtils.clearGnssFilter() }
|
||||
if (gnssFilter(app, prefs).isNotEmpty()) {
|
||||
Filter(allStatuses.size, satelliteMetadata) { PreferenceUtils.clearGnssFilter(app, prefs) }
|
||||
}
|
||||
GnssStatusCard(gnssStatuses)
|
||||
SbasStatusCard(sbasStatuses)
|
||||
|
||||
@@ -1,286 +0,0 @@
|
||||
package com.android.gpstest.util
|
||||
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import android.location.GnssMeasurementsEvent
|
||||
import android.location.LocationManager
|
||||
import android.os.Build
|
||||
import com.android.gpstest.Application
|
||||
import com.android.gpstest.R
|
||||
|
||||
/**
|
||||
* Provides access to SharedPreferences to Activities and Services.
|
||||
*/
|
||||
internal object PreferenceUtil {
|
||||
const val SECONDS_TO_MILLISECONDS = 1000
|
||||
|
||||
val METERS =
|
||||
Application.app.resources.getStringArray(R.array.preferred_distance_units_values)[0]
|
||||
val METERS_PER_SECOND =
|
||||
Application.app.resources.getStringArray(R.array.preferred_speed_units_values)[0]
|
||||
val KILOMETERS_PER_HOUR =
|
||||
Application.app.resources.getStringArray(R.array.preferred_speed_units_values)[1]
|
||||
|
||||
/**
|
||||
* Returns the minTime between location updates used for the LocationListener in milliseconds
|
||||
*/
|
||||
fun minTimeMillis(): Long {
|
||||
val minTimeDouble: Double =
|
||||
Application.prefs
|
||||
.getString(Application.app.getString(R.string.pref_key_gps_min_time), "1")
|
||||
?.toDouble() ?: 1.0
|
||||
return (minTimeDouble * SECONDS_TO_MILLISECONDS).toLong()
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the minDistance between location updates used for the LocationLitsener in meters
|
||||
*/
|
||||
fun minDistance(): Float {
|
||||
return Application.prefs.getString(Application.app.getString(R.string.pref_key_gps_min_distance), "0") ?.toFloat() ?: 0.0f
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the user has selected to write locations to file output, false if they have not
|
||||
*/
|
||||
fun writeLocationToFile(): Boolean {
|
||||
return Application.prefs.getBoolean(Application.app.getString(R.string.pref_key_file_location_output), false)
|
||||
}
|
||||
|
||||
fun writeMeasurementToLogcat(): Boolean {
|
||||
return Application.prefs.getBoolean(Application.app.getString(R.string.pref_key_as_measurement_output), false)
|
||||
}
|
||||
|
||||
fun writeMeasurementsToFile(): Boolean {
|
||||
return Application.prefs.getBoolean(Application.app.getString(R.string.pref_key_file_measurement_output), false)
|
||||
}
|
||||
|
||||
fun writeNmeaToAndroidMonitor(): Boolean {
|
||||
return Application.prefs.getBoolean(Application.app.getString(R.string.pref_key_as_nmea_output), true)
|
||||
}
|
||||
|
||||
fun writeNmeaTimestampToLogcat(): Boolean {
|
||||
return Application.prefs.getBoolean(Application.app.getString(R.string.pref_key_as_nmea_timestamp_output), true)
|
||||
}
|
||||
|
||||
fun writeNmeaToFile(): Boolean {
|
||||
return Application.prefs.getBoolean(Application.app.getString(R.string.pref_key_file_nmea_output), false)
|
||||
}
|
||||
|
||||
fun writeAntennaInfoToFileJson(): Boolean {
|
||||
return Application.prefs.getBoolean(Application.app.getString(R.string.pref_key_file_antenna_output_json), false);
|
||||
}
|
||||
|
||||
fun writeAntennaInfoToFileCsv(): Boolean {
|
||||
return Application.prefs.getBoolean(Application.app.getString(R.string.pref_key_file_antenna_output_csv), false)
|
||||
}
|
||||
|
||||
fun writeNavMessageToLogcat(): Boolean {
|
||||
return Application.prefs.getBoolean(Application.app.getString(R.string.pref_key_as_navigation_message_output), false);
|
||||
}
|
||||
|
||||
fun writeNavMessageToFile(): Boolean {
|
||||
return Application.prefs.getBoolean(Application.app.getString(R.string.pref_key_file_navigation_message_output), false);
|
||||
}
|
||||
|
||||
fun writeStatusToFile(): Boolean {
|
||||
return Application.prefs.getBoolean(Application.app.getString(R.string.pref_key_file_gnss_status_output), false);
|
||||
}
|
||||
|
||||
fun writeOrientationToFile(): Boolean {
|
||||
return Application.prefs.getBoolean(Application.app.getString(R.string.pref_key_file_orientation_output), false);
|
||||
}
|
||||
|
||||
fun injectTimeWhenLogging(): Boolean {
|
||||
return Application.prefs.getBoolean(Application.app.getString(R.string.pref_key_inject_time_when_logging), true);
|
||||
}
|
||||
|
||||
fun injectPsdsWhenLogging(): Boolean {
|
||||
return Application.prefs.getBoolean(Application.app.getString(R.string.pref_key_inject_psds_when_logging), true);
|
||||
}
|
||||
|
||||
fun isFileLoggingEnabled(): Boolean {
|
||||
return isCsvLoggingEnabled() || isJsonLoggingEnabled()
|
||||
}
|
||||
|
||||
fun isCsvLoggingEnabled(): Boolean {
|
||||
return writeNmeaToFile() || writeMeasurementsToFile() || writeNavMessageToFile() || writeLocationToFile() || writeAntennaInfoToFileCsv() || writeStatusToFile() || writeOrientationToFile()
|
||||
}
|
||||
|
||||
fun isJsonLoggingEnabled(): Boolean {
|
||||
return writeAntennaInfoToFileJson()
|
||||
}
|
||||
|
||||
fun distanceUnits(): String {
|
||||
return Application.prefs.getString(Application.app.getString(R.string.pref_key_preferred_distance_units_v2), METERS) ?: METERS
|
||||
}
|
||||
|
||||
fun speedUnits(): String {
|
||||
return Application.prefs.getString(Application.app.getString(R.string.pref_key_preferred_speed_units_v2), METERS_PER_SECOND) ?: METERS_PER_SECOND
|
||||
}
|
||||
|
||||
fun coordinateFormat(): String {
|
||||
return Application.prefs.getString(
|
||||
Application.app.getString(R.string.pref_key_coordinate_format),
|
||||
Application.app.getString(R.string.preferences_coordinate_format_dd_key)
|
||||
) ?: Application.app.getString(R.string.preferences_coordinate_format_dd_key)
|
||||
}
|
||||
|
||||
fun runInBackground(): Boolean {
|
||||
return Application.prefs.getBoolean(Application.app.getString(R.string.pref_key_gnss_background), false)
|
||||
}
|
||||
|
||||
fun darkTheme(): Boolean {
|
||||
return Application.prefs.getBoolean(Application.app.getString(R.string.pref_key_dark_theme), false)
|
||||
}
|
||||
|
||||
fun shareIncludeAltitude(): Boolean {
|
||||
return Application.prefs.getBoolean(
|
||||
Application.app.getString(R.string.pref_key_share_include_altitude), false
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if preferences related to raw measurements should be enabled,
|
||||
* false if they should be disabled
|
||||
*/
|
||||
fun enableMeasurementsPref(): Boolean {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||
val manager = Application.app.getSystemService(Context.LOCATION_SERVICE) as LocationManager
|
||||
return SatelliteUtils.isMeasurementsSupported(manager)
|
||||
}
|
||||
// Legacy versions before Android S
|
||||
val capabilityMeasurementsInt = Application.prefs.getInt(
|
||||
Application.app.getString(R.string.capability_key_raw_measurements),
|
||||
PreferenceUtils.CAPABILITY_UNKNOWN
|
||||
)
|
||||
return capabilityMeasurementsInt != PreferenceUtils.CAPABILITY_NOT_SUPPORTED
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if preferences related to navigation messages should be enabled,
|
||||
* false if they should be disabled
|
||||
*/
|
||||
fun enableNavMessagesPref(): Boolean {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||
val manager = Application.app.getSystemService(Context.LOCATION_SERVICE) as LocationManager
|
||||
return SatelliteUtils.isNavMessagesSupported(manager)
|
||||
}
|
||||
// Legacy versions before Android S
|
||||
val capabilityNavMessagesInt = Application.prefs.getInt(
|
||||
Application.app.getString(R.string.capability_key_nav_messages),
|
||||
PreferenceUtils.CAPABILITY_UNKNOWN
|
||||
)
|
||||
return capabilityNavMessagesInt != PreferenceUtils.CAPABILITY_NOT_SUPPORTED
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves device capabilities for GNSS measurements and related information from the given [event]
|
||||
*/
|
||||
fun saveMeasurementCapabilities(event: GnssMeasurementsEvent) {
|
||||
var agcSupport = PreferenceUtils.CAPABILITY_UNKNOWN
|
||||
var carrierPhaseSupport = PreferenceUtils.CAPABILITY_UNKNOWN
|
||||
// Loop through all measurements - if at least one supports, then mark as supported
|
||||
for (measurement in event.measurements) {
|
||||
if (SatelliteUtils.isAutomaticGainControlSupported(measurement)) {
|
||||
agcSupport = PreferenceUtils.CAPABILITY_SUPPORTED
|
||||
} else if (agcSupport == PreferenceUtils.CAPABILITY_UNKNOWN) {
|
||||
agcSupport = PreferenceUtils.CAPABILITY_NOT_SUPPORTED
|
||||
}
|
||||
if (SatelliteUtils.isCarrierPhaseSupported(measurement)) {
|
||||
carrierPhaseSupport = PreferenceUtils.CAPABILITY_SUPPORTED
|
||||
} else if (carrierPhaseSupport == PreferenceUtils.CAPABILITY_UNKNOWN) {
|
||||
carrierPhaseSupport = PreferenceUtils.CAPABILITY_NOT_SUPPORTED
|
||||
}
|
||||
}
|
||||
PreferenceUtils.saveInt(
|
||||
Application.app
|
||||
.getString(R.string.capability_key_measurement_automatic_gain_control),
|
||||
agcSupport
|
||||
)
|
||||
PreferenceUtils.saveInt(
|
||||
Application.app
|
||||
.getString(R.string.capability_key_measurement_delta_range),
|
||||
carrierPhaseSupport
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new preference listener that will invoke the provide [cancelFlows] function (e.g., to cancel jobs)
|
||||
* when the user turns off tracking via the UI.
|
||||
*
|
||||
* Returns a reference to the OnSharedPreferenceChangeListener so it can be held by the calling class, as
|
||||
* anonymous preference listeners tend to get GC'd by Android.
|
||||
*/
|
||||
fun newStopTrackingListener(cancelFlows: () -> Unit): SharedPreferences.OnSharedPreferenceChangeListener {
|
||||
return SharedPreferences.OnSharedPreferenceChangeListener { _, key ->
|
||||
if (key == PreferenceUtils.KEY_SERVICE_TRACKING_ENABLED) {
|
||||
if (!PreferenceUtils.isTrackingStarted()) {
|
||||
cancelFlows()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new preference listener that will invoke the provide [initLogging] function
|
||||
* when the user turns on file logging via any of the Settings options and all of the other file
|
||||
* logging settings were previously off
|
||||
*
|
||||
* Returns a reference to the OnSharedPreferenceChangeListener so it can be held by the calling class, as
|
||||
* anonymous preference listeners tend to get GC'd by Android.
|
||||
*/
|
||||
fun newFileLoggingListener(initLogging: () -> Unit): SharedPreferences.OnSharedPreferenceChangeListener {
|
||||
return SharedPreferences.OnSharedPreferenceChangeListener { _, key ->
|
||||
if (key == Application.app.getString(R.string.pref_key_file_location_output) ||
|
||||
key == Application.app.getString(R.string.pref_key_file_measurement_output) ||
|
||||
key == Application.app.getString(R.string.pref_key_file_nmea_output) ||
|
||||
key == Application.app.getString(R.string.pref_key_file_navigation_message_output) ||
|
||||
key == Application.app.getString(R.string.pref_key_file_antenna_output_csv) ||
|
||||
key == Application.app.getString(R.string.pref_key_file_antenna_output_json)) {
|
||||
|
||||
// Count number of file logging preferences that are enabled
|
||||
val loggingEnabled = arrayOf(writeLocationToFile(), writeMeasurementsToFile(), writeNmeaToFile(), writeNavMessageToFile(), writeAntennaInfoToFileCsv(), writeAntennaInfoToFileJson())
|
||||
val enabledCount = loggingEnabled.count { it }
|
||||
|
||||
if (enabledCount == 1) {
|
||||
if (key == Application.app.getString(R.string.pref_key_file_location_output) &&
|
||||
writeLocationToFile()
|
||||
) {
|
||||
// Location file logging was just enabled
|
||||
initLogging()
|
||||
}
|
||||
if (key == Application.app.getString(R.string.pref_key_file_measurement_output) &&
|
||||
writeMeasurementsToFile()
|
||||
) {
|
||||
// Measurement file logging was just enabled
|
||||
initLogging()
|
||||
}
|
||||
if (key == Application.app.getString(R.string.pref_key_file_nmea_output) &&
|
||||
writeNmeaToFile()
|
||||
) {
|
||||
// NMEA file logging was just enabled
|
||||
initLogging()
|
||||
}
|
||||
if (key == Application.app.getString(R.string.pref_key_file_navigation_message_output) &&
|
||||
writeNavMessageToFile()
|
||||
) {
|
||||
// Nav message file logging was just enabled
|
||||
initLogging()
|
||||
}
|
||||
if (key == Application.app.getString(R.string.pref_key_file_antenna_output_csv) &&
|
||||
writeAntennaInfoToFileCsv()
|
||||
) {
|
||||
// Antenna CSV file logging was just enabled
|
||||
initLogging()
|
||||
}
|
||||
if (key == Application.app.getString(R.string.pref_key_file_antenna_output_json) &&
|
||||
writeAntennaInfoToFileJson()
|
||||
) {
|
||||
// Antenna JSON file logging was just enabled
|
||||
initLogging()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,609 +15,46 @@
|
||||
*/
|
||||
package com.android.gpstest.util
|
||||
|
||||
import android.animation.Animator
|
||||
import android.animation.AnimatorListenerAdapter
|
||||
import android.app.Activity
|
||||
import android.app.Dialog
|
||||
import android.content.ActivityNotFoundException
|
||||
import android.content.Context
|
||||
import android.content.DialogInterface
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageInfo
|
||||
import android.content.pm.PackageManager
|
||||
import android.content.res.Configuration
|
||||
import com.android.gpstest.R
|
||||
import android.location.Location
|
||||
import android.location.LocationManager
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.provider.Settings
|
||||
import android.text.Spannable
|
||||
import android.text.TextUtils
|
||||
import android.text.method.LinkMovementMethod
|
||||
import android.text.style.ClickableSpan
|
||||
import android.view.View
|
||||
import android.view.ViewGroup.MarginLayoutParams
|
||||
import android.widget.CheckBox
|
||||
import android.widget.CompoundButton
|
||||
import android.widget.TextView
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.constraintlayout.widget.ConstraintLayout
|
||||
import androidx.core.app.ActivityCompat
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import com.android.gpstest.Application
|
||||
import com.android.gpstest.Application.Companion.app
|
||||
import com.android.gpstest.Application.Companion.prefs
|
||||
import com.android.gpstest.BuildConfig
|
||||
import com.android.gpstest.R
|
||||
import com.android.gpstest.io.CsvFileLogger
|
||||
import com.android.gpstest.io.JsonFileLogger
|
||||
import com.android.gpstest.model.CoordinateType
|
||||
import com.android.gpstest.model.GnssType
|
||||
import com.android.gpstest.model.SbasType
|
||||
import com.android.gpstest.library.model.GnssType
|
||||
import com.android.gpstest.library.util.IOUtils
|
||||
import com.android.gpstest.library.util.LocationUtils
|
||||
import com.android.gpstest.library.util.PreferenceUtils
|
||||
import com.android.gpstest.library.util.LibUIUtils
|
||||
import com.android.gpstest.ui.GnssFilterDialog
|
||||
import com.android.gpstest.ui.HelpActivity
|
||||
import com.android.gpstest.ui.SignalInfoViewModel
|
||||
import com.android.gpstest.ui.share.ShareDialogFragment
|
||||
import com.android.gpstest.ui.share.ShareDialogFragment.Companion.KEY_ALTERNATE_FILE_URI
|
||||
import com.android.gpstest.ui.share.ShareDialogFragment.Companion.KEY_LOCATION
|
||||
import com.android.gpstest.ui.share.ShareDialogFragment.Companion.KEY_LOGGING_ENABLED
|
||||
import com.android.gpstest.ui.share.ShareDialogFragment.Companion.KEY_LOG_FILES
|
||||
import com.android.gpstest.view.GpsSkyView
|
||||
import com.google.android.material.chip.Chip
|
||||
import java.io.File
|
||||
import java.math.BigDecimal
|
||||
import java.math.RoundingMode
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* Utilities for processing user inteface elements
|
||||
*/
|
||||
internal object UIUtils {
|
||||
const val TAG = "UIUtils"
|
||||
var PICKFILE_REQUEST_CODE = 101
|
||||
const val ANIMATION_DURATION_SHORT_MS = 200
|
||||
const val ANIMATION_DURATION_MEDIUM_MS = 400
|
||||
const val ANIMATION_DURATION_LONG_MS = 500
|
||||
|
||||
// Dialogs
|
||||
const val WHATSNEW_DIALOG = 1
|
||||
const val HELP_DIALOG = 2
|
||||
const val CLEAR_ASSIST_WARNING_DIALOG = 3
|
||||
private const val WHATS_NEW_VER = "whatsNewVer"
|
||||
|
||||
/**
|
||||
* Formats a view so it is ignored for accessible access
|
||||
*/
|
||||
@JvmStatic
|
||||
fun setAccessibilityIgnore(view: View) {
|
||||
view.isClickable = false
|
||||
view.isFocusable = false
|
||||
view.contentDescription = ""
|
||||
view.importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_NO
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts screen dimension units from dp to pixels, based on algorithm defined in
|
||||
* http://developer.android.com/guide/practices/screens_support.html#dips-pels
|
||||
*
|
||||
* @param dp value in dp
|
||||
* @return value in pixels
|
||||
*/
|
||||
@JvmStatic
|
||||
fun dpToPixels(context: Context, dp: Float): Int {
|
||||
// Get the screen's density scale
|
||||
val scale = context.resources.displayMetrics.density
|
||||
// Convert the dps to pixels, based on density scale
|
||||
return (dp * scale + 0.5f).toInt()
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the current display is wide enough to show the GPS date on the Status screen,
|
||||
* false if the current display is too narrow to fit the GPS date
|
||||
* @param context
|
||||
* @return true if the current display is wide enough to show the GPS date on the Status screen,
|
||||
* false if the current display is too narrow to fit the GPS date
|
||||
*/
|
||||
fun isWideEnoughForDate(context: Context): Boolean {
|
||||
// 450dp is a little larger than the width of a Samsung Galaxy S8+
|
||||
val WIDTH_THRESHOLD = dpToPixels(context, 450f)
|
||||
return context.resources.displayMetrics.widthPixels > WIDTH_THRESHOLD
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the activity is still active and dialogs can be managed (i.e., displayed
|
||||
* or dismissed), or false if it is not
|
||||
*
|
||||
* @param activity Activity to check for displaying/dismissing a dialog
|
||||
* @return true if the activity is still active and dialogs can be managed, or false if it is
|
||||
* not
|
||||
*/
|
||||
fun canManageDialog(activity: Activity?): Boolean {
|
||||
return if (activity == null) {
|
||||
false
|
||||
} else !activity.isFinishing && !activity.isDestroyed
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the fragment is attached to the activity, or false if it is not attached
|
||||
*
|
||||
* @param f fragment to be tested
|
||||
* @return true if the fragment is attached to the activity, or false if it is not attached
|
||||
*/
|
||||
fun isFragmentAttached(f: Fragment): Boolean {
|
||||
return f.activity != null && f.isAdded
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the provided C/N0 values to a left margin value (dp) for the avg C/N0 indicator ImageViews in gps_sky_signal
|
||||
* Left margin range for the C/N0 indicator ImageViews in gps_sky_signal is determined by dimens.xml
|
||||
* cn0_meter_width (based on device screen width) and cn0_indicator_min_left_margin values.
|
||||
*
|
||||
* This is effectively an affine transform - https://math.stackexchange.com/a/377174/554287.
|
||||
*
|
||||
* @param cn0 carrier-to-noise density at the antenna of the satellite in dB-Hz (from GnssStatus)
|
||||
* @return left margin value in dp for the C/N0 indicator ImageViews
|
||||
*/
|
||||
@JvmStatic
|
||||
fun cn0ToIndicatorLeftMarginPx(
|
||||
cn0: Float,
|
||||
minIndicatorMarginPx: Int,
|
||||
maxIndicatorMarginPx: Int
|
||||
): Int {
|
||||
return MathUtils.mapToRange(
|
||||
cn0,
|
||||
GpsSkyView.MIN_VALUE_CN0,
|
||||
GpsSkyView.MAX_VALUE_CN0,
|
||||
minIndicatorMarginPx.toFloat(),
|
||||
maxIndicatorMarginPx.toFloat()
|
||||
).toInt()
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the provided C/N0 values to a left margin value (dp) for the avg C/N0 TextViews in gps_sky_signal
|
||||
* Left margin range for the C/N0 indicator TextView in gps_sky_signal is determined by dimens.xml
|
||||
* cn0_meter_width (based on device screen width) and cn0_textview_min_left_margin values.
|
||||
*
|
||||
* This is effectively an affine transform - https://math.stackexchange.com/a/377174/554287.
|
||||
*
|
||||
* @param cn0 carrier-to-noise density at the antenna of the satellite in dB-Hz (from GnssStatus)
|
||||
* @return left margin value in dp for the C/N0 TextViews
|
||||
*/
|
||||
@JvmStatic
|
||||
fun cn0ToTextViewLeftMarginPx(
|
||||
cn0: Float,
|
||||
minTextViewMarginPx: Int,
|
||||
maxTextViewMarginPx: Int
|
||||
): Int {
|
||||
return MathUtils.mapToRange(
|
||||
cn0,
|
||||
GpsSkyView.MIN_VALUE_CN0,
|
||||
GpsSkyView.MAX_VALUE_CN0,
|
||||
minTextViewMarginPx.toFloat(),
|
||||
maxTextViewMarginPx.toFloat()
|
||||
).toInt()
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the margins for a given view
|
||||
*
|
||||
* @param v View to set the margin for
|
||||
* @param l left margin, in pixels
|
||||
* @param t top margin, in pixels
|
||||
* @param r right margin, in pixels
|
||||
* @param b bottom margin, in pixels
|
||||
*/
|
||||
fun setMargins(v: View, l: Int, t: Int, r: Int, b: Int) {
|
||||
val p = v.layoutParams as MarginLayoutParams
|
||||
p.setMargins(l, t, r, b)
|
||||
v.layoutParams = p
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens email apps based on the given email address
|
||||
* @param email address
|
||||
* @param location string that shows the current location
|
||||
* @param signalInfoViewModel view model that contains state of GNSS
|
||||
*/
|
||||
fun sendEmail(
|
||||
context: Context,
|
||||
email: String,
|
||||
location: String?,
|
||||
signalInfoViewModel: SignalInfoViewModel
|
||||
) {
|
||||
val locationManager = app.getSystemService(Context.LOCATION_SERVICE) as LocationManager
|
||||
val pm = context.packageManager
|
||||
val appInfo: PackageInfo
|
||||
val body = StringBuilder()
|
||||
body.append(context.getString(R.string.feedback_body))
|
||||
var versionName: String? = ""
|
||||
var versionCode = 0
|
||||
try {
|
||||
appInfo = pm.getPackageInfo(
|
||||
context.packageName,
|
||||
PackageManager.GET_META_DATA
|
||||
)
|
||||
versionName = appInfo.versionName
|
||||
versionCode = appInfo.versionCode
|
||||
} catch (e: PackageManager.NameNotFoundException) {
|
||||
// Leave version as empty string
|
||||
}
|
||||
|
||||
// App version
|
||||
body.append("App version: v")
|
||||
.append(versionName)
|
||||
.append(" (")
|
||||
.append(versionCode)
|
||||
.append(
|
||||
"""
|
||||
-${BuildConfig.FLAVOR})
|
||||
|
||||
""".trimIndent()
|
||||
)
|
||||
|
||||
// Device properties
|
||||
body.append(
|
||||
"""
|
||||
Model: ${Build.MODEL}
|
||||
|
||||
""".trimIndent()
|
||||
)
|
||||
body.append(
|
||||
"""Android version: ${Build.VERSION.RELEASE} / ${Build.VERSION.SDK_INT}
|
||||
"""
|
||||
)
|
||||
if (!TextUtils.isEmpty(location)) {
|
||||
body.append("Location: $location\n")
|
||||
}
|
||||
body.append(
|
||||
"""
|
||||
GNSS HW year: ${IOUtils.getGnssHardwareYear()}
|
||||
|
||||
""".trimIndent()
|
||||
)
|
||||
if (!IOUtils.getGnssHardwareModelName().trim { it <= ' ' }.isEmpty()) {
|
||||
body.append(
|
||||
"""
|
||||
GNSS HW name: ${IOUtils.getGnssHardwareModelName()}
|
||||
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
||||
|
||||
// Raw GNSS measurement capability
|
||||
var capability = prefs.getInt(
|
||||
app.getString(R.string.capability_key_raw_measurements),
|
||||
PreferenceUtils.CAPABILITY_UNKNOWN
|
||||
)
|
||||
if (capability != PreferenceUtils.CAPABILITY_UNKNOWN) {
|
||||
body.append(
|
||||
app.getString(
|
||||
R.string.capability_title_raw_measurements,
|
||||
PreferenceUtils.getCapabilityDescription(capability)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
// Navigation messages capability
|
||||
capability = prefs.getInt(
|
||||
app.getString(R.string.capability_key_nav_messages),
|
||||
PreferenceUtils.CAPABILITY_UNKNOWN
|
||||
)
|
||||
if (capability != PreferenceUtils.CAPABILITY_UNKNOWN) {
|
||||
body.append(
|
||||
app.getString(
|
||||
R.string.capability_title_nav_messages,
|
||||
PreferenceUtils.getCapabilityDescription(capability)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
// NMEA capability
|
||||
capability = prefs.getInt(
|
||||
app.getString(R.string.capability_key_nmea),
|
||||
PreferenceUtils.CAPABILITY_UNKNOWN
|
||||
)
|
||||
if (capability != PreferenceUtils.CAPABILITY_UNKNOWN) {
|
||||
body.append(
|
||||
app.getString(
|
||||
R.string.capability_title_nmea,
|
||||
PreferenceUtils.getCapabilityDescription(capability)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
// Inject PSDS capability
|
||||
capability = prefs.getInt(
|
||||
app.getString(R.string.capability_key_inject_psds),
|
||||
PreferenceUtils.CAPABILITY_UNKNOWN
|
||||
)
|
||||
if (capability != PreferenceUtils.CAPABILITY_UNKNOWN) {
|
||||
body.append(
|
||||
app.getString(
|
||||
R.string.capability_title_inject_psds,
|
||||
PreferenceUtils.getCapabilityDescription(capability)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
// Inject time capability
|
||||
capability = prefs.getInt(
|
||||
app.getString(R.string.capability_key_inject_time),
|
||||
PreferenceUtils.CAPABILITY_UNKNOWN
|
||||
)
|
||||
if (capability != PreferenceUtils.CAPABILITY_UNKNOWN) {
|
||||
body.append(
|
||||
app.getString(
|
||||
R.string.capability_title_inject_time,
|
||||
PreferenceUtils.getCapabilityDescription(capability)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
// Delete assist capability
|
||||
capability = prefs.getInt(
|
||||
app.getString(R.string.capability_key_delete_assist),
|
||||
PreferenceUtils.CAPABILITY_UNKNOWN
|
||||
)
|
||||
if (capability != PreferenceUtils.CAPABILITY_UNKNOWN) {
|
||||
body.append(
|
||||
app.getString(
|
||||
R.string.capability_title_delete_assist,
|
||||
PreferenceUtils.getCapabilityDescription(capability)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
// Got fix
|
||||
body.append(
|
||||
app.getString(
|
||||
R.string.capability_title_got_fix,
|
||||
location != null && signalInfoViewModel.gotFirstFix()
|
||||
)
|
||||
)
|
||||
|
||||
// We need a fix to determine these attributes reliably
|
||||
if (location != null && signalInfoViewModel.gotFirstFix()) {
|
||||
// Dual frequency
|
||||
body.append(
|
||||
app.getString(
|
||||
R.string.capability_title_dual_frequency,
|
||||
PreferenceUtils.getCapabilityDescription(signalInfoViewModel.isNonPrimaryCarrierFreqInView)
|
||||
)
|
||||
)
|
||||
// Supported GNSS
|
||||
val gnss: List<GnssType> = ArrayList(signalInfoViewModel.getSupportedGnss())
|
||||
Collections.sort(gnss)
|
||||
body.append(
|
||||
app.getString(
|
||||
R.string.capability_title_supported_gnss, IOUtils.trimEnds(
|
||||
IOUtils.replaceNavstar(gnss.toString())
|
||||
)
|
||||
)
|
||||
)
|
||||
// GNSS CF
|
||||
val gnssCfs: List<String> = ArrayList(signalInfoViewModel.getSupportedGnssCfs())
|
||||
if (!gnssCfs.isEmpty()) {
|
||||
Collections.sort(gnssCfs)
|
||||
body.append(
|
||||
app.getString(
|
||||
R.string.capability_title_gnss_cf,
|
||||
IOUtils.trimEnds(gnssCfs.toString())
|
||||
)
|
||||
)
|
||||
}
|
||||
// Supported SBAS
|
||||
val sbas: List<SbasType> = ArrayList(signalInfoViewModel.getSupportedSbas())
|
||||
if (!sbas.isEmpty()) {
|
||||
Collections.sort(sbas)
|
||||
body.append(
|
||||
app.getString(
|
||||
R.string.capability_title_supported_sbas,
|
||||
IOUtils.trimEnds(sbas.toString())
|
||||
)
|
||||
)
|
||||
}
|
||||
// SBAS CF
|
||||
val sbasCfs: List<String> = ArrayList(signalInfoViewModel.getSupportedSbasCfs())
|
||||
if (!sbasCfs.isEmpty()) {
|
||||
Collections.sort(sbasCfs)
|
||||
body.append(
|
||||
app.getString(
|
||||
R.string.capability_title_sbas_cf,
|
||||
IOUtils.trimEnds(sbasCfs.toString())
|
||||
)
|
||||
)
|
||||
}
|
||||
// Accumulated delta range
|
||||
body.append(
|
||||
app.getString(
|
||||
R.string.capability_title_accumulated_delta_range,
|
||||
PreferenceUtils.getCapabilityDescription(
|
||||
prefs.getInt(
|
||||
app.getString(R.string.capability_key_measurement_delta_range),
|
||||
PreferenceUtils.CAPABILITY_UNKNOWN
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
// Automatic gain control
|
||||
body.append(
|
||||
app.getString(
|
||||
R.string.capability_title_automatic_gain_control,
|
||||
PreferenceUtils.getCapabilityDescription(
|
||||
prefs.getInt(
|
||||
app.getString(R.string.capability_key_measurement_automatic_gain_control),
|
||||
PreferenceUtils.CAPABILITY_UNKNOWN
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
// GNSS Antenna Info
|
||||
val gnssAntennaInfo = app.getString(
|
||||
R.string.capability_title_gnss_antenna_info,
|
||||
PreferenceUtils.getCapabilityDescription(
|
||||
SatelliteUtils.isGnssAntennaInfoSupported(
|
||||
locationManager
|
||||
)
|
||||
)
|
||||
)
|
||||
body.append(gnssAntennaInfo)
|
||||
if (gnssAntennaInfo == app.getString(R.string.capability_value_supported)) {
|
||||
body.append(
|
||||
app.getString(
|
||||
R.string.capability_title_num_antennas, PreferenceUtils.getInt(
|
||||
app.getString(R.string.capability_key_num_antenna), -1
|
||||
)
|
||||
)
|
||||
)
|
||||
body.append(
|
||||
app.getString(
|
||||
R.string.capability_title_antenna_cfs, PreferenceUtils.getString(
|
||||
app.getString(R.string.capability_key_antenna_cf)
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
if (!TextUtils.isEmpty(BuildUtils.getPlayServicesVersion())) {
|
||||
body.append(
|
||||
"""
|
||||
|
||||
${BuildUtils.getPlayServicesVersion()}
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
||||
body.append("\n\n\n")
|
||||
val send = Intent(Intent.ACTION_SENDTO)
|
||||
send.data = Uri.parse("mailto:")
|
||||
send.putExtra(Intent.EXTRA_EMAIL, arrayOf(email))
|
||||
val subject = context.getString(R.string.feedback_subject)
|
||||
send.putExtra(Intent.EXTRA_SUBJECT, subject)
|
||||
send.putExtra(Intent.EXTRA_TEXT, body.toString())
|
||||
try {
|
||||
context.startActivity(Intent.createChooser(send, subject))
|
||||
} catch (e: ActivityNotFoundException) {
|
||||
Toast.makeText(context, R.string.feedback_error, Toast.LENGTH_LONG)
|
||||
.show()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the provided latitude or longitude value in Degrees Minutes Seconds (DMS) format
|
||||
* @param coordinate latitude or longitude to convert to DMS format
|
||||
* @param coordinateType whether the coordinate is latitude or longitude
|
||||
* @return the provided latitude or longitude value in Degrees Minutes Seconds (DMS) format
|
||||
*/
|
||||
@JvmStatic
|
||||
fun getDMSFromLocation(
|
||||
context: Context,
|
||||
coordinate: Double,
|
||||
coordinateType: CoordinateType
|
||||
): String {
|
||||
val loc = BigDecimal(coordinate)
|
||||
val degrees = loc.setScale(0, RoundingMode.DOWN)
|
||||
val minTemp = loc.subtract(degrees).multiply(BigDecimal(60)).abs()
|
||||
val minutes = minTemp.setScale(0, RoundingMode.DOWN)
|
||||
val seconds =
|
||||
minTemp.subtract(minutes).multiply(BigDecimal(60)).setScale(2, RoundingMode.HALF_UP)
|
||||
val hemisphere: String
|
||||
val output_string: Int
|
||||
if (coordinateType == CoordinateType.LATITUDE) {
|
||||
hemisphere = if (coordinate < 0) "S" else "N"
|
||||
output_string = R.string.gps_lat_dms_value
|
||||
} else {
|
||||
hemisphere = if (coordinate < 0) "W" else "E"
|
||||
output_string = R.string.gps_lon_dms_value
|
||||
}
|
||||
return context.getString(
|
||||
output_string,
|
||||
hemisphere,
|
||||
degrees.abs().toInt(),
|
||||
minutes.toInt(),
|
||||
seconds.toFloat()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the provided latitude or longitude value in Decimal Degree Minutes (DDM) format
|
||||
*
|
||||
* @param coordinate latitude or longitude to convert to DDM format
|
||||
* @param coordinateType lat or lon to format hemisphere
|
||||
* @return the provided latitude or longitude value in Decimal Degree Minutes (DDM) format
|
||||
*/
|
||||
@JvmStatic
|
||||
fun getDDMFromLocation(
|
||||
context: Context,
|
||||
coordinate: Double,
|
||||
coordinateType: CoordinateType
|
||||
): String {
|
||||
val loc = BigDecimal(coordinate)
|
||||
val degrees = loc.setScale(0, RoundingMode.DOWN)
|
||||
val minutes =
|
||||
loc.subtract(degrees).multiply(BigDecimal(60)).abs().setScale(3, RoundingMode.HALF_UP)
|
||||
val hemisphere: String
|
||||
val output_string: Int
|
||||
if (coordinateType == CoordinateType.LATITUDE) {
|
||||
hemisphere = if (coordinate < 0) "S" else "N"
|
||||
output_string = R.string.gps_lat_ddm_value
|
||||
} else {
|
||||
hemisphere = if (coordinate < 0) "W" else "E"
|
||||
output_string = R.string.gps_lon_ddm_value
|
||||
}
|
||||
return context.getString(
|
||||
output_string,
|
||||
hemisphere,
|
||||
degrees.abs().toInt(),
|
||||
minutes.toFloat()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the provide value in meters to the corresponding value in feet
|
||||
* @param meters value in meters to convert to feet
|
||||
* @return the provided meters value converted to feet
|
||||
*/
|
||||
@JvmStatic
|
||||
fun toFeet(meters: Double): Double {
|
||||
return meters * 1000.0 / 25.4 / 12.0
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the provide value in meters per second to the corresponding value in kilometers per hour
|
||||
* @param metersPerSecond value in meters per second to convert to kilometers per hour
|
||||
* @return the provided meters per second value converted to kilometers per hour
|
||||
*/
|
||||
@JvmStatic
|
||||
fun toKilometersPerHour(metersPerSecond: Float): Float {
|
||||
return metersPerSecond * 3600f / 1000f
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the provide value in meters per second to the corresponding value in miles per hour
|
||||
* @param metersPerSecond value in meters per second to convert to miles per hour
|
||||
* @return the provided meters per second value converted to miles per hour
|
||||
*/
|
||||
@JvmStatic
|
||||
fun toMilesPerHour(metersPerSecond: Float): Float {
|
||||
return toKilometersPerHour(metersPerSecond) / 1.6093440f
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the vertical bias for a provided view that is within a ConstraintLayout
|
||||
* @param view view within a ConstraintLayout
|
||||
* @param bias vertical bias to be used
|
||||
*/
|
||||
@JvmStatic
|
||||
fun setVerticalBias(view: View, bias: Float) {
|
||||
val params = view.layoutParams as ConstraintLayout.LayoutParams
|
||||
params.verticalBias = bias
|
||||
view.layoutParams = params
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests to see if the provided text latitude, longitude, and altitude values are valid, and if
|
||||
* not shows an error dialog and returns false, or if yes then returns true
|
||||
@@ -681,7 +118,8 @@ internal object UIUtils {
|
||||
// Save the preference
|
||||
PreferenceUtils.saveBoolean(
|
||||
app.getString(R.string.pref_key_never_show_qr_code_instructions),
|
||||
isChecked
|
||||
isChecked,
|
||||
Application.prefs
|
||||
)
|
||||
}
|
||||
val builder = AlertDialog.Builder(activity)
|
||||
@@ -747,7 +185,8 @@ internal object UIUtils {
|
||||
*/
|
||||
private fun createBundleForShareDialog(
|
||||
location: Location?,
|
||||
loggingEnabled: Boolean, files: ArrayList<File>,
|
||||
loggingEnabled: Boolean,
|
||||
files: ArrayList<File>,
|
||||
alternateFileUri: Uri?
|
||||
): Bundle {
|
||||
val bundle = Bundle()
|
||||
@@ -758,244 +197,6 @@ internal object UIUtils {
|
||||
return bundle
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the provided location based on the provided coordinate format, and sets the provided
|
||||
* Views (textView, chips) accordingly if views are provided, and returns the string value.
|
||||
*
|
||||
* @param location location to be formatted
|
||||
* @param textView View to be set with the selected coordinateFormat
|
||||
* @param includeAltitude true if altitude should be included, false if it should not
|
||||
* @param chipDecimalDegrees View to be set as checked if "dd" is the coordinateFormat
|
||||
* @param chipDMS View to be set as checked if "dms" is the coordinateFormat
|
||||
* @param chipDegreesDecimalMin View to be set as checked if "ddm" is the coordinateFormat
|
||||
* @param coordinateFormat dd, dms, or ddm
|
||||
* @return the provided location based on the provided coordinate format
|
||||
*/
|
||||
fun formatLocationForDisplay(
|
||||
location: Location?,
|
||||
textView: TextView?,
|
||||
includeAltitude: Boolean,
|
||||
chipDecimalDegrees: Chip?,
|
||||
chipDMS: Chip?,
|
||||
chipDegreesDecimalMin: Chip?,
|
||||
coordinateFormat: String?
|
||||
): String {
|
||||
var formattedLocation = ""
|
||||
when (coordinateFormat) {
|
||||
"dd" -> {
|
||||
// Decimal degrees
|
||||
if (location != null) {
|
||||
formattedLocation = IOUtils.createLocationShare(location, includeAltitude)
|
||||
}
|
||||
if (chipDecimalDegrees != null) {
|
||||
chipDecimalDegrees.isChecked = true
|
||||
}
|
||||
}
|
||||
"dms" -> {
|
||||
// Degrees minutes seconds
|
||||
if (location != null) {
|
||||
formattedLocation = IOUtils.createLocationShare(
|
||||
getDMSFromLocation(app, location.latitude, CoordinateType.LATITUDE),
|
||||
getDMSFromLocation(app, location.longitude, CoordinateType.LONGITUDE),
|
||||
if (location.hasAltitude() && includeAltitude) location.altitude.toString() else null
|
||||
)
|
||||
}
|
||||
if (chipDMS != null) {
|
||||
chipDMS.isChecked = true
|
||||
}
|
||||
}
|
||||
"ddm" -> {
|
||||
// Degrees decimal minutes
|
||||
if (location != null) {
|
||||
formattedLocation = IOUtils.createLocationShare(
|
||||
getDDMFromLocation(app, location.latitude, CoordinateType.LATITUDE),
|
||||
getDDMFromLocation(app, location.longitude, CoordinateType.LONGITUDE),
|
||||
if (location.hasAltitude() && includeAltitude) location.altitude.toString() else null
|
||||
)
|
||||
}
|
||||
if (chipDegreesDecimalMin != null) {
|
||||
chipDegreesDecimalMin.isChecked = true
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
// Decimal degrees
|
||||
formattedLocation = IOUtils.createLocationShare(location, includeAltitude)
|
||||
if (chipDecimalDegrees != null) {
|
||||
chipDecimalDegrees.isChecked = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if (textView != null) {
|
||||
textView.text = formattedLocation
|
||||
}
|
||||
return formattedLocation
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the activity title so the locale is updated
|
||||
*
|
||||
* @param a the activity to reset the title for
|
||||
*/
|
||||
@JvmStatic
|
||||
fun resetActivityTitle(a: Activity) {
|
||||
try {
|
||||
val info =
|
||||
a.packageManager.getActivityInfo(a.componentName, PackageManager.GET_META_DATA)
|
||||
if (info.labelRes != 0) {
|
||||
a.setTitle(info.labelRes)
|
||||
}
|
||||
} catch (e: PackageManager.NameNotFoundException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the app is running on a large screen device, false if it is not
|
||||
*
|
||||
* @return true if the app is running on a large screen device, false if it is not
|
||||
*/
|
||||
fun isLargeScreen(context: Context): Boolean {
|
||||
return (context.resources.configuration.screenLayout
|
||||
and Configuration.SCREENLAYOUT_SIZE_MASK) >= Configuration.SCREENLAYOUT_SIZE_XLARGE
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the display name for the given GnssType
|
||||
* @param context
|
||||
* @param gnssType
|
||||
* @return the display name for the given GnssType
|
||||
*/
|
||||
fun getGnssDisplayName(context: Context, gnssType: GnssType?): String {
|
||||
return when (gnssType) {
|
||||
GnssType.NAVSTAR -> context.resources.getString(R.string.sky_legend_shape_navstar)
|
||||
GnssType.GALILEO -> context.resources.getString(R.string.sky_legend_shape_galileo)
|
||||
GnssType.GLONASS -> context.resources.getString(R.string.sky_legend_shape_glonass)
|
||||
GnssType.BEIDOU -> context.resources.getString(R.string.sky_legend_shape_beidou)
|
||||
GnssType.QZSS -> context.resources.getString(R.string.sky_legend_shape_qzss)
|
||||
GnssType.IRNSS -> context.resources.getString(R.string.sky_legend_shape_irnss)
|
||||
GnssType.SBAS -> context.resources.getString(R.string.sbas)
|
||||
GnssType.UNKNOWN -> context.resources.getString(R.string.unknown)
|
||||
else -> context.resources.getString(R.string.unknown)
|
||||
}
|
||||
}
|
||||
|
||||
fun setClickableSpan(v: TextView, span: ClickableSpan?) {
|
||||
val text = v.text as Spannable
|
||||
text.setSpan(span, 0, text.length, 0)
|
||||
v.movementMethod = LinkMovementMethod.getInstance()
|
||||
}
|
||||
|
||||
fun removeAllClickableSpans(v: TextView) {
|
||||
val text = v.text as Spannable
|
||||
val spans = text.getSpans(0, text.length, ClickableSpan::class.java)
|
||||
for (cs in spans) {
|
||||
text.removeSpan(cs)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows a view using animation
|
||||
*
|
||||
* @param v View to show
|
||||
* @param animationDuration duration of animation
|
||||
*/
|
||||
fun showViewWithAnimation(v: View, animationDuration: Int) {
|
||||
if (v.visibility == View.VISIBLE && v.alpha == 1f) {
|
||||
// View is already visible and not transparent, return without doing anything
|
||||
return
|
||||
}
|
||||
v.clearAnimation()
|
||||
v.animate().cancel()
|
||||
if (v.visibility != View.VISIBLE) {
|
||||
// Set the content view to 0% opacity but visible, so that it is visible
|
||||
// (but fully transparent) during the animation.
|
||||
v.alpha = 0f
|
||||
v.visibility = View.VISIBLE
|
||||
}
|
||||
|
||||
// Animate the content view to 100% opacity, and clear any animation listener set on the view.
|
||||
v.animate()
|
||||
.alpha(1f)
|
||||
.setDuration(animationDuration.toLong())
|
||||
.setListener(null)
|
||||
}
|
||||
|
||||
/**
|
||||
* Hides a view using animation
|
||||
*
|
||||
* @param v View to hide
|
||||
* @param animationDuration duration of animation
|
||||
*/
|
||||
fun hideViewWithAnimation(v: View, animationDuration: Int) {
|
||||
if (v.visibility == View.GONE) {
|
||||
// View is already gone, return without doing anything
|
||||
return
|
||||
}
|
||||
v.clearAnimation()
|
||||
v.animate().cancel()
|
||||
|
||||
// Animate the view to 0% opacity. After the animation ends, set its visibility to GONE as
|
||||
// an optimization step (it won't participate in layout passes, etc.)
|
||||
v.animate()
|
||||
.alpha(0f)
|
||||
.setDuration(animationDuration.toLong())
|
||||
.setListener(object : AnimatorListenerAdapter() {
|
||||
override fun onAnimationEnd(animation: Animator) {
|
||||
v.visibility = View.GONE
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows the dialog to explain why location permissions are needed
|
||||
*
|
||||
* NOTE - this dialog can't be managed under the old dialog framework as the method
|
||||
* ActivityCompat.shouldShowRequestPermissionRationale() always returns false.
|
||||
*/
|
||||
fun showLocationPermissionDialog(activity: Activity) {
|
||||
val builder = AlertDialog.Builder(activity)
|
||||
.setTitle(R.string.title_location_permission)
|
||||
.setMessage(R.string.text_location_permission)
|
||||
.setCancelable(false)
|
||||
.setPositiveButton(
|
||||
R.string.ok
|
||||
) { dialog: DialogInterface?, which: Int ->
|
||||
// Request permissions from the user
|
||||
ActivityCompat.requestPermissions(
|
||||
activity,
|
||||
PermissionUtils.REQUIRED_PERMISSIONS,
|
||||
PermissionUtils.LOCATION_PERMISSION_REQUEST
|
||||
)
|
||||
}
|
||||
.setNegativeButton(
|
||||
R.string.exit
|
||||
) { dialog: DialogInterface?, which: Int ->
|
||||
// Exit app
|
||||
activity.finish()
|
||||
}
|
||||
builder.create().show()
|
||||
}
|
||||
|
||||
/**
|
||||
* Ask the user if they want to enable GPS, and if so, show them system settings
|
||||
*/
|
||||
fun promptEnableGps(activity: Activity) {
|
||||
AlertDialog.Builder(activity)
|
||||
.setMessage(app.getString(R.string.enable_gps_message))
|
||||
.setPositiveButton(
|
||||
app.getString(R.string.enable_gps_positive_button)
|
||||
) { dialog: DialogInterface?, which: Int ->
|
||||
val intent = Intent(
|
||||
Settings.ACTION_LOCATION_SOURCE_SETTINGS
|
||||
)
|
||||
activity.startActivity(intent)
|
||||
}
|
||||
.setNegativeButton(
|
||||
app.getString(R.string.enable_gps_negative_button)
|
||||
) { dialog: DialogInterface?, which: Int -> }
|
||||
.show()
|
||||
}
|
||||
|
||||
fun createHelpDialog(activity: Activity): Dialog {
|
||||
val builder = AlertDialog.Builder(activity)
|
||||
builder.setTitle(R.string.title_help)
|
||||
@@ -1005,7 +206,7 @@ internal object UIUtils {
|
||||
) { dialog: DialogInterface?, which: Int ->
|
||||
when (which) {
|
||||
0 -> activity.showDialog(
|
||||
WHATSNEW_DIALOG
|
||||
LibUIUtils.WHATSNEW_DIALOG
|
||||
)
|
||||
1 -> activity.startActivity(Intent(activity, HelpActivity::class.java))
|
||||
}
|
||||
@@ -1022,21 +223,21 @@ internal object UIUtils {
|
||||
builder.setView(textView)
|
||||
builder.setNeutralButton(
|
||||
R.string.main_help_close
|
||||
) { _: DialogInterface?, _: Int -> activity.dismissDialog(WHATSNEW_DIALOG) }
|
||||
) { _: DialogInterface?, _: Int -> activity.dismissDialog(LibUIUtils.WHATSNEW_DIALOG) }
|
||||
return builder.create()
|
||||
}
|
||||
|
||||
fun showFilterDialog(activity: FragmentActivity) {
|
||||
val gnssTypes = GnssType.values()
|
||||
val len = gnssTypes.size
|
||||
val filter = PreferenceUtils.gnssFilter()
|
||||
val filter = PreferenceUtils.gnssFilter(app, prefs)
|
||||
val items = arrayOfNulls<String>(len)
|
||||
val checks = BooleanArray(len)
|
||||
|
||||
// For each GnssType, if it is in the enabled list, mark it as checked.
|
||||
for (i in 0 until len) {
|
||||
val gnssType = gnssTypes[i]
|
||||
items[i] = getGnssDisplayName(app, gnssType)
|
||||
items[i] = LibUIUtils.getGnssDisplayName(app, gnssType)
|
||||
if (filter.contains(gnssType)) {
|
||||
checks[i] = true
|
||||
}
|
||||
@@ -1057,66 +258,15 @@ internal object UIUtils {
|
||||
activity
|
||||
)
|
||||
builder.setTitle(R.string.menu_option_sort_by)
|
||||
val currentSatOrder = PreferenceUtils.getSatSortOrderFromPreferences()
|
||||
val currentSatOrder = PreferenceUtils.getSatSortOrderFromPreferences(app, Application.prefs)
|
||||
builder.setSingleChoiceItems(
|
||||
R.array.sort_sats, currentSatOrder
|
||||
) { dialog: DialogInterface, index: Int ->
|
||||
setSortByClause(index)
|
||||
LibUIUtils.setSortByClause(app, index, prefs)
|
||||
dialog.dismiss()
|
||||
}
|
||||
val dialog = builder.create()
|
||||
dialog.setOwnerActivity(activity)
|
||||
dialog.show()
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the "sort by" order to preferences
|
||||
*
|
||||
* @param index the index of R.array.sort_sats that should be set
|
||||
*/
|
||||
private fun setSortByClause(index: Int) {
|
||||
val sortOptions = app.resources.getStringArray(R.array.sort_sats)
|
||||
PreferenceUtils.saveString(
|
||||
app.resources.getString(R.string.pref_key_default_sat_sort),
|
||||
sortOptions[index]
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the "What's New" message if a new version was just installed
|
||||
*/
|
||||
fun autoShowWhatsNew(activity: Activity) {
|
||||
// Get the current app version.
|
||||
val appInfo: PackageInfo = try {
|
||||
app.packageManager.getPackageInfo(
|
||||
app.packageName,
|
||||
PackageManager.GET_META_DATA
|
||||
)
|
||||
} catch (e: PackageManager.NameNotFoundException) {
|
||||
// Do nothing
|
||||
return
|
||||
}
|
||||
val oldVer = prefs.getInt(WHATS_NEW_VER, 0)
|
||||
val newVer = appInfo.versionCode
|
||||
if (oldVer < newVer) {
|
||||
activity.showDialog(WHATSNEW_DIALOG)
|
||||
PreferenceUtils.saveInt(WHATS_NEW_VER, appInfo.versionCode)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the `location` object as a human readable string for use in a notification summary
|
||||
*/
|
||||
fun Location?.toNotificationSummary(): String {
|
||||
return if (this != null) {
|
||||
val lat = FormatUtils.formatLatOrLon(latitude, CoordinateType.LATITUDE)
|
||||
val lon = FormatUtils.formatLatOrLon(longitude, CoordinateType.LONGITUDE)
|
||||
val alt = FormatUtils.formatAltitude(this)
|
||||
val speed = FormatUtils.formatSpeed(this)
|
||||
val bearing = FormatUtils.formatBearing(this)
|
||||
"$lat $lon $alt | $speed | $bearing"
|
||||
} else {
|
||||
"Unknown location"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
package com.android.gpstest.view;
|
||||
|
||||
import static com.android.gpstest.model.SatelliteStatus.NO_DATA;
|
||||
import static com.android.gpstest.library.model.SatelliteStatus.NO_DATA;
|
||||
import static java.util.Collections.emptyList;
|
||||
|
||||
import android.content.Context;
|
||||
@@ -19,9 +19,9 @@ import androidx.core.content.ContextCompat;
|
||||
|
||||
import com.android.gpstest.Application;
|
||||
import com.android.gpstest.R;
|
||||
import com.android.gpstest.model.GnssType;
|
||||
import com.android.gpstest.model.SatelliteStatus;
|
||||
import com.android.gpstest.util.UIUtils;
|
||||
import com.android.gpstest.library.model.GnssType;
|
||||
import com.android.gpstest.library.model.SatelliteStatus;
|
||||
import com.android.gpstest.library.util.LibUIUtils;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@@ -78,7 +78,7 @@ public class GpsSkyView extends View {
|
||||
private void init(Context context) {
|
||||
mContext = context;
|
||||
mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
|
||||
SAT_RADIUS = UIUtils.dpToPixels(context, 5);
|
||||
SAT_RADIUS = LibUIUtils.dpToPixels(context, 5);
|
||||
|
||||
int textColor;
|
||||
int backgroundColor;
|
||||
@@ -155,7 +155,7 @@ public class GpsSkyView extends View {
|
||||
mPrnIdPaint.setColor(textColor);
|
||||
mPrnIdPaint.setStyle(Paint.Style.FILL_AND_STROKE);
|
||||
mPrnIdPaint
|
||||
.setTextSize(UIUtils.dpToPixels(getContext(), SAT_RADIUS * PRN_TEXT_SCALE));
|
||||
.setTextSize(LibUIUtils.dpToPixels(getContext(), SAT_RADIUS * PRN_TEXT_SCALE));
|
||||
mPrnIdPaint.setAntiAlias(true);
|
||||
|
||||
mNotInViewPaint = new Paint();
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<com.android.gpstest.view.VerticalTextView
|
||||
<com.android.gpstest.library.view.VerticalTextView
|
||||
android:id="@+id/error_y_axis_label"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
@@ -100,7 +100,7 @@
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<com.android.gpstest.view.VerticalTextView
|
||||
<com.android.gpstest.library.view.VerticalTextView
|
||||
android:id="@+id/vert_error_y_axis_label"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
|
||||
@@ -32,14 +32,15 @@ import androidx.lifecycle.flowWithLifecycle
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.android.gpstest.Application
|
||||
import com.android.gpstest.R
|
||||
import com.android.gpstest.data.LocationRepository
|
||||
import com.android.gpstest.library.data.LocationRepository
|
||||
import com.android.gpstest.library.util.MathUtils
|
||||
import com.android.gpstest.library.util.PreferenceUtil
|
||||
import com.android.gpstest.library.util.PreferenceUtil.newStopTrackingListener
|
||||
import com.android.gpstest.map.MapConstants
|
||||
import com.android.gpstest.map.MapViewModelController
|
||||
import com.android.gpstest.map.MapViewModelController.MapInterface
|
||||
import com.android.gpstest.map.OnMapClickListener
|
||||
import com.android.gpstest.util.MapUtils
|
||||
import com.android.gpstest.util.MathUtils
|
||||
import com.android.gpstest.util.PreferenceUtil.newStopTrackingListener
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.Job
|
||||
@@ -87,9 +88,8 @@ class MapFragment : Fragment(), MapInterface {
|
||||
|
||||
// Preference listener that will cancel the above flows when the user turns off tracking via UI
|
||||
private val trackingListener: SharedPreferences.OnSharedPreferenceChangeListener =
|
||||
newStopTrackingListener { onGnssStopped() }
|
||||
newStopTrackingListener ({ onGnssStopped() }, Application.prefs)
|
||||
|
||||
@ExperimentalCoroutinesApi
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
package com.android.gpstest.util
|
||||
|
||||
import com.android.gpstest.library.util.DateTimeUtils
|
||||
import org.junit.Assert.assertFalse
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
|
||||
@@ -19,6 +19,8 @@ import static junit.framework.Assert.assertEquals;
|
||||
import static junit.framework.Assert.assertFalse;
|
||||
import static junit.framework.Assert.assertTrue;
|
||||
|
||||
import com.android.gpstest.library.util.MathUtils;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
public class MathUtilTest {
|
||||
|
||||
@@ -15,7 +15,8 @@
|
||||
*/
|
||||
package com.android.gpstest.util;
|
||||
|
||||
import com.android.gpstest.model.DilutionOfPrecision;
|
||||
import com.android.gpstest.library.model.DilutionOfPrecision;
|
||||
import com.android.gpstest.library.util.NmeaUtils;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
|
||||
@@ -2,6 +2,8 @@ package com.android.gpstest.util;
|
||||
|
||||
import static junit.framework.Assert.assertEquals;
|
||||
|
||||
import com.android.gpstest.library.util.LibUIUtils;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
public class UIUtilsTest {
|
||||
@@ -16,15 +18,15 @@ public class UIUtilsTest {
|
||||
final int MAX_PX = 140;
|
||||
|
||||
// CN0 value of 27.5 dB-Hz is 50% between 10 and 45, so output should be halfway between -6px and 140px, which is 70px
|
||||
marginPx = UIUtils.cn0ToIndicatorLeftMarginPx(27.5f, MIN_PX, MAX_PX);
|
||||
marginPx = LibUIUtils.cn0ToIndicatorLeftMarginPx(27.5f, MIN_PX, MAX_PX);
|
||||
assertEquals(67, marginPx);
|
||||
|
||||
// CN0 value of 45.0 dB-Hz is 100% of 45, so output should be 100% of 140px, which is 140px
|
||||
marginPx = UIUtils.cn0ToIndicatorLeftMarginPx(45.0f, MIN_PX, MAX_PX);
|
||||
marginPx = LibUIUtils.cn0ToIndicatorLeftMarginPx(45.0f, MIN_PX, MAX_PX);
|
||||
assertEquals(140, marginPx);
|
||||
|
||||
// CN0 value of 10.0 dB-Hz is 0% (min value of CN0), so output should be 0% of px range, which is -6
|
||||
marginPx = UIUtils.cn0ToIndicatorLeftMarginPx(10.0f, MIN_PX, MAX_PX);
|
||||
marginPx = LibUIUtils.cn0ToIndicatorLeftMarginPx(10.0f, MIN_PX, MAX_PX);
|
||||
assertEquals(-6, marginPx);
|
||||
}
|
||||
|
||||
@@ -38,15 +40,15 @@ public class UIUtilsTest {
|
||||
final int MAX_PX = 149;
|
||||
|
||||
// CN0 value of 27.5 dB-Hz is 50% between 10 and 45, so output should be halfway between 3px and 149px, which is 76px
|
||||
marginPx = UIUtils.cn0ToTextViewLeftMarginPx(27.5f, MIN_PX, MAX_PX);
|
||||
marginPx = LibUIUtils.cn0ToTextViewLeftMarginPx(27.5f, MIN_PX, MAX_PX);
|
||||
assertEquals(76, marginPx);
|
||||
|
||||
// CN0 value of 45.0 dB-Hz is 100% of 149, so output should be 100% of 149px, which is 149px
|
||||
marginPx = UIUtils.cn0ToTextViewLeftMarginPx(45.0f, MIN_PX, MAX_PX);
|
||||
marginPx = LibUIUtils.cn0ToTextViewLeftMarginPx(45.0f, MIN_PX, MAX_PX);
|
||||
assertEquals(149, marginPx);
|
||||
|
||||
// CN0 value of 10.0 dB-Hz is 0% (min value of CN0), so output should be 0% of px range, which is 3px
|
||||
marginPx = UIUtils.cn0ToTextViewLeftMarginPx(10.0f, MIN_PX, MAX_PX);
|
||||
marginPx = LibUIUtils.cn0ToTextViewLeftMarginPx(10.0f, MIN_PX, MAX_PX);
|
||||
assertEquals(3, marginPx);
|
||||
}
|
||||
|
||||
@@ -57,11 +59,11 @@ public class UIUtilsTest {
|
||||
public void testToFeet() {
|
||||
double meters = 1.0d;
|
||||
|
||||
assertEquals(3.2808398950131235d, UIUtils.toFeet(meters));
|
||||
assertEquals(3.2808398950131235d, LibUIUtils.toFeet(meters));
|
||||
|
||||
meters = 30.0d;
|
||||
|
||||
assertEquals(98.4251968503937d, UIUtils.toFeet(meters));
|
||||
assertEquals(98.4251968503937d, LibUIUtils.toFeet(meters));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -71,11 +73,11 @@ public class UIUtilsTest {
|
||||
public void testToKilometersPerHour() {
|
||||
float metersPerSecond = 1.0f;
|
||||
|
||||
assertEquals(3.6f, UIUtils.toKilometersPerHour(metersPerSecond));
|
||||
assertEquals(3.6f, LibUIUtils.toKilometersPerHour(metersPerSecond));
|
||||
|
||||
metersPerSecond = 30.0f;
|
||||
|
||||
assertEquals(108.0f, UIUtils.toKilometersPerHour(metersPerSecond));
|
||||
assertEquals(108.0f, LibUIUtils.toKilometersPerHour(metersPerSecond));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -85,10 +87,10 @@ public class UIUtilsTest {
|
||||
public void testToMilesPerHour() {
|
||||
float metersPerSecond = 1.0f;
|
||||
|
||||
assertEquals(2.2369363f, UIUtils.toMilesPerHour(metersPerSecond));
|
||||
assertEquals(2.2369363f, LibUIUtils.toMilesPerHour(metersPerSecond));
|
||||
|
||||
metersPerSecond = 60.0f;
|
||||
|
||||
assertEquals(134.21617f, UIUtils.toMilesPerHour(metersPerSecond));
|
||||
assertEquals(134.21617f, LibUIUtils.toMilesPerHour(metersPerSecond));
|
||||
}
|
||||
}
|
||||
|
||||
10
build.gradle
@@ -1,13 +1,17 @@
|
||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||
buildscript {
|
||||
ext.kotlin_version = '1.5.31'
|
||||
ext.hilt_version = '2.38.1'
|
||||
ext {
|
||||
compose_version = '1.2.0-alpha05'
|
||||
wear_compose_version = '1.0.2'
|
||||
}
|
||||
ext.kotlin_version = '1.6.10'
|
||||
ext.hilt_version = '2.40.1'
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:7.0.3'
|
||||
classpath 'com.android.tools.build:gradle:7.3.0'
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||
// Hilt for dependency injection
|
||||
classpath "com.google.dagger:hilt-android-gradle-plugin:$hilt_version"
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
org.gradle.daemon=true
|
||||
org.gradle.parallel=true
|
||||
org.gradle.configureondemand=true
|
||||
org.gradle.caching=false
|
||||
org.gradle.jvmargs=-Xmx3g -XX:MaxPermSize=2048m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 -XX:+UseParallelGC
|
||||
android.enableJetifier=true
|
||||
android.useAndroidX=true
|
||||
2
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.1-all.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-all.zip
|
||||
|
||||
1
library/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/build
|
||||
69
library/build.gradle
Normal file
@@ -0,0 +1,69 @@
|
||||
plugins {
|
||||
id 'com.android.library'
|
||||
id 'org.jetbrains.kotlin.android'
|
||||
id 'kotlin-kapt'
|
||||
}
|
||||
|
||||
android {
|
||||
namespace 'com.android.gpstest.library'
|
||||
compileSdk 33
|
||||
|
||||
defaultConfig {
|
||||
minSdk 24
|
||||
targetSdk 32
|
||||
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
consumerProguardFiles "consumer-rules.pro"
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled true
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-project.txt'
|
||||
}
|
||||
}
|
||||
lint {
|
||||
disable "JvmStaticProvidesInObjectDetector", "FieldSiteTargetOnQualifierAnnotation", "ModuleCompanionObjects", "ModuleCompanionObjectsNotInModuleParent"
|
||||
}
|
||||
|
||||
buildFeatures {
|
||||
dataBinding true
|
||||
viewBinding true
|
||||
}
|
||||
|
||||
dataBinding {
|
||||
enabled = true
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
kotlinOptions {
|
||||
jvmTarget = '1.8'
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
||||
implementation 'androidx.core:core-ktx:1.7.0'
|
||||
implementation 'androidx.appcompat:appcompat:1.5.1'
|
||||
implementation 'com.google.android.material:material:1.6.1'
|
||||
implementation 'com.fasterxml.jackson.core:jackson-databind:2.12.4'
|
||||
implementation 'com.github.PhilJay:MPAndroidChart:v3.1.0'
|
||||
implementation("javax.inject:javax.inject:1")
|
||||
implementation 'com.google.dagger:dagger:2.40.1'
|
||||
kapt 'com.google.dagger:dagger-compiler:2.40.1'
|
||||
// Integration with ViewModels
|
||||
implementation 'androidx.lifecycle:lifecycle-viewmodel-compose:2.5.1'
|
||||
// Hilt for dependency injection
|
||||
implementation "com.google.dagger:hilt-android:$hilt_version"
|
||||
kapt "com.google.dagger:hilt-compiler:$hilt_version"
|
||||
// Sliding drawer in map view
|
||||
implementation 'com.sothree.slidinguppanel:library:3.4.0'
|
||||
// QR Code reader for ground truth locations
|
||||
implementation 'com.google.zxing:android-integration:3.3.0'
|
||||
testImplementation 'junit:junit:4.13.2'
|
||||
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
|
||||
}
|
||||
0
library/consumer-rules.pro
Normal file
21
library/proguard-rules.pro
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
# Add project specific ProGuard rules here.
|
||||
# You can control the set of applied configuration files using the
|
||||
# proguardFiles setting in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
|
||||
# Uncomment this to preserve the line number information for
|
||||
# debugging stack traces.
|
||||
#-keepattributes SourceFile,LineNumberTable
|
||||
|
||||
# If you keep the line number information, uncomment this to
|
||||
# hide the original source file name.
|
||||
#-renamesourcefileattribute SourceFile
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.android.library
|
||||
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
import org.junit.Assert.*
|
||||
|
||||
/**
|
||||
* Instrumented test, which will execute on an Android device.
|
||||
*
|
||||
* See [testing documentation](http://d.android.com/tools/testing).
|
||||
*/
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class ExampleInstrumentedTest {
|
||||
@Test
|
||||
fun useAppContext() {
|
||||
// Context of the app under test.
|
||||
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
|
||||
assertEquals("com.android.gpstest.library.test", appContext.packageName)
|
||||
}
|
||||
}
|
||||
4
library/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
</manifest>
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.android.gpstest.data
|
||||
package com.android.gpstest.library.data
|
||||
|
||||
import android.os.Build
|
||||
import androidx.annotation.RequiresApi
|
||||
@@ -13,22 +13,22 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.android.gpstest.data
|
||||
package com.android.gpstest.library.data
|
||||
|
||||
import android.Manifest
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import android.location.GnssAntennaInfo
|
||||
import android.location.LocationManager
|
||||
import android.os.Build
|
||||
import android.util.Log
|
||||
import androidx.annotation.RequiresApi
|
||||
import com.android.gpstest.Application
|
||||
import com.android.gpstest.R
|
||||
import com.android.gpstest.util.CarrierFreqUtils
|
||||
import com.android.gpstest.util.IOUtils
|
||||
import com.android.gpstest.util.PreferenceUtils
|
||||
import com.android.gpstest.util.hasPermission
|
||||
import com.android.gpstest.library.R
|
||||
import com.android.gpstest.library.util.CarrierFreqUtils
|
||||
import com.android.gpstest.library.util.IOUtils
|
||||
import com.android.gpstest.library.util.PreferenceUtils
|
||||
import com.android.gpstest.library.util.hasPermission
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.channels.awaitClose
|
||||
@@ -48,7 +48,8 @@ private const val TAG = "SharedAntennaManager"
|
||||
*/
|
||||
class SharedAntennaManager constructor(
|
||||
private val context: Context,
|
||||
externalScope: CoroutineScope
|
||||
externalScope: CoroutineScope,
|
||||
prefs: SharedPreferences
|
||||
) {
|
||||
@RequiresApi(Build.VERSION_CODES.R)
|
||||
@ExperimentalCoroutinesApi
|
||||
@@ -58,8 +59,9 @@ class SharedAntennaManager constructor(
|
||||
val callback = GnssAntennaInfo.Listener { list: List<GnssAntennaInfo> ->
|
||||
// Capture capabilities in preferences
|
||||
PreferenceUtils.saveInt(
|
||||
Application.app.getString(R.string.capability_key_num_antenna),
|
||||
list.size
|
||||
context.getString(R.string.capability_key_num_antenna),
|
||||
list.size,
|
||||
prefs
|
||||
)
|
||||
val cfs: MutableList<String> = ArrayList(2)
|
||||
for (info in list) {
|
||||
@@ -68,8 +70,9 @@ class SharedAntennaManager constructor(
|
||||
if (cfs.isNotEmpty()) {
|
||||
cfs.sort()
|
||||
PreferenceUtils.saveString(
|
||||
Application.app.getString(R.string.capability_key_antenna_cf),
|
||||
IOUtils.trimEnds(cfs.toString())
|
||||
context.getString(R.string.capability_key_antenna_cf),
|
||||
IOUtils.trimEnds(cfs.toString()),
|
||||
prefs
|
||||
)
|
||||
}
|
||||
|
||||
@@ -87,6 +90,7 @@ class SharedAntennaManager constructor(
|
||||
try {
|
||||
locationManager.registerAntennaInfoListener(context.mainExecutor, callback)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Exception in location flow: $e")
|
||||
close(e) // in case of exception, close the Flow
|
||||
}
|
||||
|
||||
@@ -13,11 +13,12 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.android.gpstest.data
|
||||
package com.android.gpstest.library.data
|
||||
|
||||
import android.Manifest
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import android.location.GnssMeasurementRequest
|
||||
import android.location.GnssMeasurementsEvent
|
||||
import android.location.LocationManager
|
||||
@@ -27,12 +28,11 @@ import android.os.Looper
|
||||
import android.util.Log
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.android.gpstest.Application
|
||||
import com.android.gpstest.R
|
||||
import com.android.gpstest.util.PreferenceUtil.saveMeasurementCapabilities
|
||||
import com.android.gpstest.util.PreferenceUtils
|
||||
import com.android.gpstest.util.SatelliteUtils
|
||||
import com.android.gpstest.util.hasPermission
|
||||
import com.android.gpstest.library.R
|
||||
import com.android.gpstest.library.util.PreferenceUtil.saveMeasurementCapabilities
|
||||
import com.android.gpstest.library.util.PreferenceUtils
|
||||
import com.android.gpstest.library.util.SatelliteUtils
|
||||
import com.android.gpstest.library.util.hasPermission
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.channels.awaitClose
|
||||
@@ -47,6 +47,7 @@ private const val TAG = "SharedGnssMeasurementManager"
|
||||
* and https://github.com/googlecodelabs/kotlin-coroutines/blob/master/ktx-library-codelab/step-06/myktxlibrary/src/main/java/com/example/android/myktxlibrary/LocationUtils.kt
|
||||
*/
|
||||
class SharedGnssMeasurementManager constructor(
|
||||
private var prefs: SharedPreferences,
|
||||
private val context: Context,
|
||||
externalScope: CoroutineScope
|
||||
) {
|
||||
@@ -61,12 +62,12 @@ class SharedGnssMeasurementManager constructor(
|
||||
val locationManager = context.getSystemService(Context.LOCATION_SERVICE) as LocationManager
|
||||
// Check explicit support on Android S and higher here - Android R and lower are checked in status callbacks
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||
checkMeasurementSupport(locationManager)
|
||||
checkMeasurementSupport(context, locationManager, prefs)
|
||||
}
|
||||
val callback: GnssMeasurementsEvent.Callback =
|
||||
object : GnssMeasurementsEvent.Callback() {
|
||||
override fun onGnssMeasurementsReceived(event: GnssMeasurementsEvent) {
|
||||
saveMeasurementCapabilities(event)
|
||||
saveMeasurementCapabilities(context, event, prefs)
|
||||
|
||||
//Log.d(TAG, "New measurement: $event")
|
||||
// Send the new measurement to the Flow observers
|
||||
@@ -78,7 +79,7 @@ class SharedGnssMeasurementManager constructor(
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||
return
|
||||
}
|
||||
handleLegacyMeasurementStatus(status)
|
||||
handleLegacyMeasurementStatus(context, status, prefs)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,11 +92,10 @@ class SharedGnssMeasurementManager constructor(
|
||||
|
||||
try {
|
||||
if (SatelliteUtils.isForceFullGnssMeasurementsSupported()) {
|
||||
val forceFullMeasurements = Application.prefs
|
||||
val forceFullMeasurements = prefs
|
||||
.getBoolean(
|
||||
Application.app.getString(R.string.pref_key_force_full_gnss_measurements),
|
||||
true
|
||||
)
|
||||
context.getString(R.string.pref_key_force_full_gnss_measurements),
|
||||
true)
|
||||
Log.d(TAG, "Force full GNSS measurements = $forceFullMeasurements")
|
||||
// Request "force full GNSS measurements" explicitly (on <= Android R this is a manual developer setting)
|
||||
val request = GnssMeasurementRequest.Builder()
|
||||
@@ -121,6 +121,7 @@ class SharedGnssMeasurementManager constructor(
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Exception in location flow: $e")
|
||||
close(e) // in case of exception, close the Flow
|
||||
}
|
||||
|
||||
@@ -141,66 +142,73 @@ class SharedGnssMeasurementManager constructor(
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleLegacyMeasurementStatus(status: Int) {
|
||||
private fun handleLegacyMeasurementStatus(context: Context, status: Int, prefs: SharedPreferences) {
|
||||
// TODO - surface this state message in UI somewhere, like when user returned from Settings like before? For now just disable logging option in Settings, will surface in Dashboard later
|
||||
val uiStatusMessage: String
|
||||
when (status) {
|
||||
GnssMeasurementsEvent.Callback.STATUS_LOCATION_DISABLED -> {
|
||||
uiStatusMessage =
|
||||
Application.app.getString(R.string.gnss_measurement_status_loc_disabled)
|
||||
context.getString(R.string.gnss_measurement_status_loc_disabled)
|
||||
PreferenceUtils.saveInt(
|
||||
Application.app.getString(R.string.capability_key_raw_measurements),
|
||||
PreferenceUtils.CAPABILITY_LOCATION_DISABLED
|
||||
context.getString(R.string.capability_key_raw_measurements),
|
||||
PreferenceUtils.CAPABILITY_LOCATION_DISABLED,
|
||||
prefs
|
||||
)
|
||||
}
|
||||
GnssMeasurementsEvent.Callback.STATUS_NOT_SUPPORTED -> {
|
||||
uiStatusMessage =
|
||||
Application.app.getString(R.string.gnss_measurement_status_not_supported)
|
||||
context.getString(R.string.gnss_measurement_status_not_supported)
|
||||
PreferenceUtils.saveInt(
|
||||
Application.app.getString(R.string.capability_key_raw_measurements),
|
||||
PreferenceUtils.CAPABILITY_NOT_SUPPORTED
|
||||
context.getString(R.string.capability_key_raw_measurements),
|
||||
PreferenceUtils.CAPABILITY_NOT_SUPPORTED,
|
||||
prefs
|
||||
)
|
||||
PreferenceUtils.saveInt(
|
||||
Application.app
|
||||
.getString(R.string.capability_key_measurement_automatic_gain_control),
|
||||
PreferenceUtils.CAPABILITY_NOT_SUPPORTED
|
||||
context.getString(R.string.capability_key_measurement_automatic_gain_control),
|
||||
PreferenceUtils.CAPABILITY_NOT_SUPPORTED,
|
||||
prefs
|
||||
)
|
||||
PreferenceUtils.saveInt(
|
||||
Application.app.getString(R.string.capability_key_measurement_delta_range),
|
||||
PreferenceUtils.CAPABILITY_NOT_SUPPORTED
|
||||
context.getString(R.string.capability_key_measurement_delta_range),
|
||||
PreferenceUtils.CAPABILITY_NOT_SUPPORTED,
|
||||
prefs
|
||||
)
|
||||
}
|
||||
GnssMeasurementsEvent.Callback.STATUS_READY -> {
|
||||
uiStatusMessage = Application.app.getString(R.string.gnss_measurement_status_ready)
|
||||
uiStatusMessage = context.getString(R.string.gnss_measurement_status_ready)
|
||||
PreferenceUtils.saveInt(
|
||||
Application.app.getString(R.string.capability_key_raw_measurements),
|
||||
PreferenceUtils.CAPABILITY_SUPPORTED
|
||||
context.getString(R.string.capability_key_raw_measurements),
|
||||
PreferenceUtils.CAPABILITY_SUPPORTED,
|
||||
prefs
|
||||
)
|
||||
}
|
||||
else -> {
|
||||
uiStatusMessage = Application.app.getString(R.string.gnss_status_unknown)
|
||||
uiStatusMessage = context.getString(R.string.gnss_status_unknown)
|
||||
PreferenceUtils.saveInt(
|
||||
Application.app.getString(R.string.capability_key_raw_measurements),
|
||||
PreferenceUtils.CAPABILITY_UNKNOWN
|
||||
context.getString(R.string.capability_key_raw_measurements),
|
||||
PreferenceUtils.CAPABILITY_UNKNOWN,
|
||||
prefs
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.S)
|
||||
private fun checkMeasurementSupport(lm: LocationManager) {
|
||||
private fun checkMeasurementSupport(context: Context, lm: LocationManager, prefs: SharedPreferences) {
|
||||
// TODO - surface this state message in UI somewhere, like when user returned from Settings like before? For now just disable logging option in Settings, will surface in Dashboard later
|
||||
val uiStatusMessage: String = if (SatelliteUtils.isMeasurementsSupported(lm)) {
|
||||
PreferenceUtils.saveInt(
|
||||
Application.app.getString(R.string.capability_key_raw_measurements),
|
||||
PreferenceUtils.CAPABILITY_SUPPORTED
|
||||
context.getString(R.string.capability_key_raw_measurements),
|
||||
PreferenceUtils.CAPABILITY_SUPPORTED,
|
||||
prefs
|
||||
)
|
||||
Application.app.getString(R.string.gnss_measurement_status_ready)
|
||||
context.getString(R.string.gnss_measurement_status_ready)
|
||||
} else {
|
||||
PreferenceUtils.saveInt(
|
||||
Application.app.getString(R.string.capability_key_raw_measurements),
|
||||
PreferenceUtils.CAPABILITY_NOT_SUPPORTED
|
||||
context.getString(R.string.capability_key_raw_measurements),
|
||||
PreferenceUtils.CAPABILITY_NOT_SUPPORTED,
|
||||
prefs
|
||||
)
|
||||
Application.app.getString(R.string.gnss_measurement_status_not_supported)
|
||||
context.getString(R.string.gnss_measurement_status_not_supported)
|
||||
}
|
||||
}
|
||||
@@ -13,11 +13,12 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.android.gpstest.data
|
||||
package com.android.gpstest.library.data
|
||||
|
||||
import android.Manifest
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import android.location.GnssStatus
|
||||
import android.location.Location
|
||||
import android.location.LocationManager
|
||||
@@ -27,8 +28,8 @@ import android.os.Looper
|
||||
import android.os.SystemClock
|
||||
import android.util.Log
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.android.gpstest.util.PreferenceUtil.minTimeMillis
|
||||
import com.android.gpstest.util.hasPermission
|
||||
import com.android.gpstest.library.util.PreferenceUtil.minTimeMillis
|
||||
import com.android.gpstest.library.util.hasPermission
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.channels.awaitClose
|
||||
@@ -45,7 +46,8 @@ private const val TAG = "SharedGnssStatusManager"
|
||||
*/
|
||||
class SharedGnssStatusManager constructor(
|
||||
private val context: Context,
|
||||
externalScope: CoroutineScope
|
||||
externalScope: CoroutineScope,
|
||||
prefs: SharedPreferences
|
||||
) {
|
||||
// State of GnssStatus
|
||||
private val _statusState = MutableStateFlow<GnssStatusState>(GnssStatusState.Stopped)
|
||||
@@ -80,7 +82,7 @@ class SharedGnssStatusManager constructor(
|
||||
override fun onSatelliteStatusChanged(status: GnssStatus) {
|
||||
val location = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER)
|
||||
if (location != null) {
|
||||
_fixState.value = checkHaveFix(location)
|
||||
_fixState.value = checkHaveFix(context, location, prefs)
|
||||
} else {
|
||||
_fixState.value = FixState.NotAcquired
|
||||
}
|
||||
@@ -109,6 +111,7 @@ class SharedGnssStatusManager constructor(
|
||||
)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Exception in location flow: $e")
|
||||
close(e) // in case of exception, close the Flow
|
||||
}
|
||||
|
||||
@@ -135,10 +138,10 @@ class SharedGnssStatusManager constructor(
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkHaveFix(location: Location): FixState {
|
||||
val threshold = if (minTimeMillis() >= 1000L) {
|
||||
private fun checkHaveFix(context: Context, location: Location, prefs: SharedPreferences): FixState {
|
||||
val threshold = if (minTimeMillis(context, prefs) >= 1000L) {
|
||||
// Use two requested update intervals (it missed two updates)
|
||||
TimeUnit.MILLISECONDS.toNanos(minTimeMillis() * 2)
|
||||
TimeUnit.MILLISECONDS.toNanos(minTimeMillis(context, prefs) * 2)
|
||||
} else {
|
||||
// Most Android devices can't refresh faster than 1Hz, so use 1.5 seconds - see #544
|
||||
TimeUnit.MILLISECONDS.toNanos(1500)
|
||||
@@ -13,18 +13,19 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.android.gpstest.data
|
||||
package com.android.gpstest.library.data
|
||||
|
||||
import android.Manifest
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import android.location.Location
|
||||
import android.location.LocationListener
|
||||
import android.location.LocationManager
|
||||
import android.util.Log
|
||||
import com.android.gpstest.util.PreferenceUtil.minDistance
|
||||
import com.android.gpstest.util.PreferenceUtil.minTimeMillis
|
||||
import com.android.gpstest.util.hasPermission
|
||||
import com.android.gpstest.library.util.PreferenceUtil.minDistance
|
||||
import com.android.gpstest.library.util.PreferenceUtil.minTimeMillis
|
||||
import com.android.gpstest.library.util.hasPermission
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.channels.awaitClose
|
||||
@@ -40,7 +41,8 @@ private const val TAG = "SharedLocationManager"
|
||||
*/
|
||||
class SharedLocationManager constructor(
|
||||
private val context: Context,
|
||||
externalScope: CoroutineScope
|
||||
externalScope: CoroutineScope,
|
||||
prefs:SharedPreferences
|
||||
) {
|
||||
private val _receivingLocationUpdates: MutableStateFlow<Boolean> =
|
||||
MutableStateFlow(false)
|
||||
@@ -61,18 +63,19 @@ class SharedLocationManager constructor(
|
||||
!context.hasPermission(Manifest.permission.ACCESS_COARSE_LOCATION)
|
||||
) close()
|
||||
|
||||
Log.d(TAG, "Starting location updates with minTime=${minTimeMillis()}ms and minDistance=${minDistance()}m")
|
||||
Log.d(TAG, "Starting location updates with minTime=${minTimeMillis(context, prefs)}ms and minDistance=${minDistance(context, prefs)}m")
|
||||
_receivingLocationUpdates.value = true
|
||||
|
||||
try {
|
||||
locationManager.requestLocationUpdates(
|
||||
LocationManager.GPS_PROVIDER,
|
||||
minTimeMillis(),
|
||||
minDistance(),
|
||||
minTimeMillis(context, prefs),
|
||||
minDistance(context, prefs),
|
||||
callback,
|
||||
context.mainLooper
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Exception in location flow: $e")
|
||||
close(e) // in case of exception, close the Flow
|
||||
}
|
||||
|
||||
@@ -13,11 +13,12 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.android.gpstest.data
|
||||
package com.android.gpstest.library.data
|
||||
|
||||
import android.Manifest
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import android.location.GnssNavigationMessage
|
||||
import android.location.LocationManager
|
||||
import android.os.Build
|
||||
@@ -26,11 +27,10 @@ import android.os.Looper
|
||||
import android.util.Log
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.android.gpstest.Application
|
||||
import com.android.gpstest.R
|
||||
import com.android.gpstest.util.PreferenceUtils
|
||||
import com.android.gpstest.util.SatelliteUtils
|
||||
import com.android.gpstest.util.hasPermission
|
||||
import com.android.gpstest.library.R
|
||||
import com.android.gpstest.library.util.PreferenceUtils
|
||||
import com.android.gpstest.library.util.SatelliteUtils
|
||||
import com.android.gpstest.library.util.hasPermission
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.channels.awaitClose
|
||||
@@ -49,7 +49,8 @@ private const val TAG = "SharedNavMessageManager"
|
||||
*/
|
||||
class SharedNavMessageManager constructor(
|
||||
private val context: Context,
|
||||
externalScope: CoroutineScope
|
||||
externalScope: CoroutineScope,
|
||||
prefs: SharedPreferences
|
||||
) {
|
||||
@ExperimentalCoroutinesApi
|
||||
@SuppressLint("MissingPermission")
|
||||
@@ -57,7 +58,7 @@ class SharedNavMessageManager constructor(
|
||||
val locationManager = context.getSystemService(Context.LOCATION_SERVICE) as LocationManager
|
||||
// Check explicit support on Android S and higher here - Android R and lower are checked in status callbacks
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||
checkNavMessageSupport(locationManager)
|
||||
checkNavMessageSupport(context, locationManager, prefs)
|
||||
}
|
||||
val callback: GnssNavigationMessage.Callback =
|
||||
object : GnssNavigationMessage.Callback() {
|
||||
@@ -72,7 +73,7 @@ class SharedNavMessageManager constructor(
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||
return
|
||||
}
|
||||
handleLegacyNavMessageStatus(status)
|
||||
handleLegacyNavMessageStatus(context, status, prefs)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,6 +97,7 @@ class SharedNavMessageManager constructor(
|
||||
)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Exception in location flow: $e")
|
||||
close(e) // in case of exception, close the Flow
|
||||
}
|
||||
|
||||
@@ -116,49 +118,54 @@ class SharedNavMessageManager constructor(
|
||||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.S)
|
||||
private fun checkNavMessageSupport(locationManager: LocationManager) {
|
||||
private fun checkNavMessageSupport(context: Context,locationManager: LocationManager, prefs: SharedPreferences) {
|
||||
// TODO - surface this status message in UI somewhere, like when user returned from Settings like before? For now just disable logging option in Settings, will surface in Dashboard later
|
||||
val uiStatusMessage: String
|
||||
uiStatusMessage = if (SatelliteUtils.isNavMessagesSupported(locationManager)) {
|
||||
PreferenceUtils.saveInt(
|
||||
Application.app.getString(R.string.capability_key_nav_messages),
|
||||
PreferenceUtils.CAPABILITY_SUPPORTED
|
||||
context.getString(R.string.capability_key_nav_messages),
|
||||
PreferenceUtils.CAPABILITY_SUPPORTED,
|
||||
prefs
|
||||
)
|
||||
Application.app.getString(R.string.gnss_nav_msg_status_ready)
|
||||
context.getString(R.string.gnss_nav_msg_status_ready)
|
||||
} else {
|
||||
PreferenceUtils.saveInt(
|
||||
Application.app.getString(R.string.capability_key_nav_messages),
|
||||
PreferenceUtils.CAPABILITY_NOT_SUPPORTED
|
||||
context.getString(R.string.capability_key_nav_messages),
|
||||
PreferenceUtils.CAPABILITY_NOT_SUPPORTED,
|
||||
prefs
|
||||
)
|
||||
Application.app.getString(R.string.gnss_nav_msg_status_not_supported)
|
||||
context.getString(R.string.gnss_nav_msg_status_not_supported)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleLegacyNavMessageStatus(status: Int) {
|
||||
private fun handleLegacyNavMessageStatus(context: Context, status: Int, prefs: SharedPreferences) {
|
||||
val uiStatusMessage: String
|
||||
when (status) {
|
||||
GnssNavigationMessage.Callback.STATUS_LOCATION_DISABLED -> {
|
||||
uiStatusMessage = Application.app.getString(R.string.gnss_nav_msg_status_loc_disabled)
|
||||
uiStatusMessage = context.getString(R.string.gnss_nav_msg_status_loc_disabled)
|
||||
PreferenceUtils.saveInt(
|
||||
Application.app.getString(R.string.capability_key_nav_messages),
|
||||
PreferenceUtils.CAPABILITY_LOCATION_DISABLED
|
||||
context.getString(R.string.capability_key_nav_messages),
|
||||
PreferenceUtils.CAPABILITY_LOCATION_DISABLED,
|
||||
prefs
|
||||
)
|
||||
}
|
||||
GnssNavigationMessage.Callback.STATUS_NOT_SUPPORTED -> {
|
||||
uiStatusMessage = Application.app.getString(R.string.gnss_nav_msg_status_not_supported)
|
||||
uiStatusMessage = context.getString(R.string.gnss_nav_msg_status_not_supported)
|
||||
PreferenceUtils.saveInt(
|
||||
Application.app.getString(R.string.capability_key_nav_messages),
|
||||
PreferenceUtils.CAPABILITY_NOT_SUPPORTED
|
||||
context.getString(R.string.capability_key_nav_messages),
|
||||
PreferenceUtils.CAPABILITY_NOT_SUPPORTED,
|
||||
prefs
|
||||
)
|
||||
}
|
||||
GnssNavigationMessage.Callback.STATUS_READY -> {
|
||||
uiStatusMessage = Application.app.getString(R.string.gnss_nav_msg_status_ready)
|
||||
uiStatusMessage = context.getString(R.string.gnss_nav_msg_status_ready)
|
||||
PreferenceUtils.saveInt(
|
||||
Application.app.getString(R.string.capability_key_nav_messages),
|
||||
PreferenceUtils.CAPABILITY_SUPPORTED
|
||||
context.getString(R.string.capability_key_nav_messages),
|
||||
PreferenceUtils.CAPABILITY_SUPPORTED,
|
||||
prefs
|
||||
)
|
||||
}
|
||||
else -> uiStatusMessage = Application.app.getString(R.string.gnss_status_unknown)
|
||||
else -> uiStatusMessage = context.getString(R.string.gnss_status_unknown)
|
||||
}
|
||||
Log.d(
|
||||
TAG,
|
||||
@@ -13,11 +13,12 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.android.gpstest.data
|
||||
package com.android.gpstest.library.data
|
||||
|
||||
import android.Manifest
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import android.location.LocationManager
|
||||
import android.location.OnNmeaMessageListener
|
||||
import android.os.Build
|
||||
@@ -25,11 +26,10 @@ import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.util.Log
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.android.gpstest.Application
|
||||
import com.android.gpstest.R
|
||||
import com.android.gpstest.model.NmeaWithTime
|
||||
import com.android.gpstest.util.PreferenceUtils
|
||||
import com.android.gpstest.util.hasPermission
|
||||
import com.android.gpstest.library.R
|
||||
import com.android.gpstest.library.model.NmeaWithTime
|
||||
import com.android.gpstest.library.util.PreferenceUtils
|
||||
import com.android.gpstest.library.util.hasPermission
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.channels.awaitClose
|
||||
@@ -48,7 +48,8 @@ private const val TAG = "SharedNmeaManager"
|
||||
*/
|
||||
class SharedNmeaManager constructor(
|
||||
private val context: Context,
|
||||
externalScope: CoroutineScope
|
||||
externalScope: CoroutineScope,
|
||||
prefs: SharedPreferences
|
||||
) {
|
||||
@ExperimentalCoroutinesApi
|
||||
@SuppressLint("MissingPermission")
|
||||
@@ -56,9 +57,9 @@ class SharedNmeaManager constructor(
|
||||
val locationManager = context.getSystemService(Context.LOCATION_SERVICE) as LocationManager
|
||||
val callback = OnNmeaMessageListener { message: String, timestamp: Long ->
|
||||
PreferenceUtils.saveInt(
|
||||
Application.app
|
||||
.getString(R.string.capability_key_nmea),
|
||||
PreferenceUtils.CAPABILITY_SUPPORTED
|
||||
context.getString(R.string.capability_key_nmea),
|
||||
PreferenceUtils.CAPABILITY_SUPPORTED,
|
||||
prefs
|
||||
)
|
||||
val nmeaWithTime = NmeaWithTime(timestamp, message)
|
||||
//Log.d(TAG, "New nmea: ${nmeaWithTime}")
|
||||
@@ -79,6 +80,7 @@ class SharedNmeaManager constructor(
|
||||
locationManager.addNmeaListener(callback, Handler(Looper.getMainLooper()))
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Exception in location flow: $e")
|
||||
close(e) // in case of exception, close the Flow
|
||||
}
|
||||
|
||||
@@ -13,23 +13,23 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.android.gpstest.data
|
||||
package com.android.gpstest.library.data
|
||||
|
||||
import android.Manifest
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import android.hardware.*
|
||||
import android.hardware.display.DisplayManager
|
||||
import android.location.LocationManager
|
||||
import android.util.Log
|
||||
import android.view.Display
|
||||
import android.view.Surface
|
||||
import com.android.gpstest.Application
|
||||
import com.android.gpstest.R
|
||||
import com.android.gpstest.model.Orientation
|
||||
import com.android.gpstest.util.MathUtils
|
||||
import com.android.gpstest.util.SatelliteUtils
|
||||
import com.android.gpstest.util.hasPermission
|
||||
import com.android.gpstest.library.R
|
||||
import com.android.gpstest.library.model.Orientation
|
||||
import com.android.gpstest.library.util.MathUtils
|
||||
import com.android.gpstest.library.util.SatelliteUtils
|
||||
import com.android.gpstest.library.util.hasPermission
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.channels.awaitClose
|
||||
@@ -50,6 +50,7 @@ private const val TAG = "SharedSensorManager"
|
||||
* and https://github.com/googlecodelabs/kotlin-coroutines/blob/master/ktx-library-codelab/step-06/myktxlibrary/src/main/java/com/example/android/myktxlibrary/LocationUtils.kt
|
||||
*/
|
||||
class SharedSensorManager constructor(
|
||||
private val prefs: SharedPreferences,
|
||||
private val context: Context,
|
||||
externalScope: CoroutineScope
|
||||
) {
|
||||
@@ -100,8 +101,8 @@ class SharedSensorManager constructor(
|
||||
}
|
||||
|
||||
// Correct for true north, if preference is set
|
||||
if (::geomagneticField.isInitialized && Application.prefs.getBoolean(
|
||||
Application.app.getString(R.string.pref_key_true_north),
|
||||
if (::geomagneticField.isInitialized && prefs.getBoolean(
|
||||
context.getString(R.string.pref_key_true_north),
|
||||
true
|
||||
)
|
||||
) {
|
||||
@@ -124,7 +125,7 @@ class SharedSensorManager constructor(
|
||||
|
||||
val sensorManager = context.getSystemService(Context.SENSOR_SERVICE) as SensorManager
|
||||
try {
|
||||
if (SatelliteUtils.isRotationVectorSensorSupported(Application.app)) {
|
||||
if (SatelliteUtils.isRotationVectorSensorSupported(context)) {
|
||||
// Use the modern rotation vector sensors
|
||||
val vectorSensor: Sensor =
|
||||
sensorManager.getDefaultSensor(Sensor.TYPE_ROTATION_VECTOR)
|
||||
@@ -143,6 +144,7 @@ class SharedSensorManager constructor(
|
||||
)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Exception in location flow: $e")
|
||||
close(e) // in case of exception, close the Flow
|
||||
}
|
||||
|
||||
@@ -208,7 +210,7 @@ class SharedSensorManager constructor(
|
||||
|
||||
private fun getDisplay(): Display? {
|
||||
val displayManager =
|
||||
Application.app.getSystemService(Context.DISPLAY_SERVICE) as DisplayManager
|
||||
context.getSystemService(Context.DISPLAY_SERVICE) as DisplayManager
|
||||
return displayManager.getDisplay(0)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
package com.android.gpstest.library.di
|
||||
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import android.preference.PreferenceManager
|
||||
import com.android.gpstest.library.data.*
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import javax.inject.Singleton
|
||||
|
||||
/**
|
||||
* Configuration for DI on the repository and shared location manager
|
||||
*/
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
object DataModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideSharedPreferences(@ApplicationContext context: Context
|
||||
):SharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideSharedLocationManager(
|
||||
@ApplicationContext context: Context,
|
||||
prefs: SharedPreferences
|
||||
): SharedLocationManager =
|
||||
SharedLocationManager(context, GlobalScope, prefs)
|
||||
|
||||
@Provides
|
||||
fun provideContext(
|
||||
@ApplicationContext context: Context,
|
||||
): Context {
|
||||
return context
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideSharedGnssStatusManager(
|
||||
@ApplicationContext context: Context,
|
||||
prefs: SharedPreferences,
|
||||
): SharedGnssStatusManager =
|
||||
SharedGnssStatusManager(context, GlobalScope, prefs)
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideSharedNmeaManager(
|
||||
@ApplicationContext context: Context,
|
||||
prefs: SharedPreferences,
|
||||
): SharedNmeaManager =
|
||||
SharedNmeaManager(context, GlobalScope, prefs)
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideSharedSensorManager(
|
||||
prefs: SharedPreferences,
|
||||
@ApplicationContext context: Context
|
||||
): SharedSensorManager =
|
||||
SharedSensorManager(prefs, context, GlobalScope)
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideSharedNavMessageManager(
|
||||
@ApplicationContext context: Context,
|
||||
prefs: SharedPreferences,
|
||||
): SharedNavMessageManager =
|
||||
SharedNavMessageManager(context, GlobalScope, prefs)
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideSharedMeasurementsManager(
|
||||
prefs: SharedPreferences,
|
||||
@ApplicationContext context: Context
|
||||
): SharedGnssMeasurementManager =
|
||||
SharedGnssMeasurementManager(prefs, context, GlobalScope)
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideSharedAntennaManager(
|
||||
@ApplicationContext context: Context,
|
||||
prefs: SharedPreferences,
|
||||
): SharedAntennaManager =
|
||||
SharedAntennaManager(context, GlobalScope,prefs)
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.android.gpstest.io;
|
||||
package com.android.gpstest.library.io;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileFilter;
|
||||
@@ -0,0 +1,43 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2008-2021 The Android Open Source Project, Sean J. Barbeau
|
||||
|
||||
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.
|
||||
-->
|
||||
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
<group android:id="@+id/gps_group">
|
||||
<item
|
||||
android:id="@+id/filter_sats"
|
||||
android:title="@string/menu_option_filter"
|
||||
android:icon="@drawable/ic_baseline_filter_list_24"
|
||||
android:orderInCategory="2"
|
||||
app:showAsAction="ifRoom"
|
||||
android:contentDescription="@string/menu_option_filter_content_description" />
|
||||
<item
|
||||
android:id="@+id/share"
|
||||
android:title="@string/share"
|
||||
android:icon="@drawable/social_share"
|
||||
android:orderInCategory="10"
|
||||
app:showAsAction="ifRoom|withText"
|
||||
android:contentDescription="@string/share" />
|
||||
|
||||
<item
|
||||
android:id="@+id/gps_switch_item"
|
||||
android:title="@string/gps_switch"
|
||||
android:orderInCategory="11"
|
||||
app:showAsAction="always|withText"
|
||||
app:actionLayout="@layout/gps_switch"
|
||||
android:contentDescription="@string/gps_switch_content_description"/>
|
||||
</group>
|
||||
</menu>
|
||||
@@ -0,0 +1,27 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2018 Sean J. Barbeau (sjbarbeau@gmail.com)
|
||||
|
||||
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.
|
||||
-->
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
<group android:id="@+id/gps_group">
|
||||
<item
|
||||
android:id="@+id/sort_sats"
|
||||
android:title="@string/menu_option_sort_by"
|
||||
android:icon="@drawable/ic_action_sort"
|
||||
android:orderInCategory="1"
|
||||
app:showAsAction="ifRoom"
|
||||
android:contentDescription="@string/menu_option_sort_by_content_description" />
|
||||
</group>
|
||||
</menu>
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.android.gpstest.model
|
||||
package com.android.gpstest.library.model
|
||||
|
||||
import kotlin.math.abs
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.android.gpstest.model;
|
||||
package com.android.gpstest.library.model;
|
||||
|
||||
/**
|
||||
* Types of constellations
|
||||
@@ -13,7 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.android.gpstest.model
|
||||
package com.android.gpstest.library.model
|
||||
|
||||
/**
|
||||
* Types of coordinates
|
||||
@@ -13,7 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.android.gpstest.model;
|
||||
package com.android.gpstest.library.model;
|
||||
|
||||
/**
|
||||
* Container class for DOP values
|
||||
@@ -13,7 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.android.gpstest.model;
|
||||
package com.android.gpstest.library.model;
|
||||
|
||||
/**
|
||||
* Types of Global Navigation Satellite Systems
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.android.gpstest.model
|
||||
package com.android.gpstest.library.model
|
||||
|
||||
/**
|
||||
* Model class for holding measured error between two locations
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.android.gpstest.model
|
||||
package com.android.gpstest.library.model
|
||||
|
||||
/**
|
||||
* A container class to hold a NMEA [message] with a system [timestamp] (coming from
|
||||
@@ -13,7 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.android.gpstest.model
|
||||
package com.android.gpstest.library.model
|
||||
|
||||
/**
|
||||
* Container class holding rotation sensor timestamp, and [values], where the first index is the
|
||||
@@ -13,7 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.android.gpstest.model
|
||||
package com.android.gpstest.library.model
|
||||
|
||||
/**
|
||||
* A container class that represents a satellite sending GNSS or SBAS signals ([status]). The [id] of
|
||||
@@ -13,7 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.android.gpstest.model
|
||||
package com.android.gpstest.library.model
|
||||
|
||||
/**
|
||||
* A container class that holds a group of [satellites] from multiple constellations (e.g., GNSS,
|
||||
@@ -21,5 +21,6 @@ package com.android.gpstest.model
|
||||
* combination of constellation and svID. The key is created using SatelliteUtils.createGnssSatelliteKey().
|
||||
*/
|
||||
data class SatelliteGroup(
|
||||
val satellites: Map<String, Satellite>,
|
||||
val satelliteMetadata: SatelliteMetadata)
|
||||
val satellites: Map<String, Satellite>,
|
||||
val satelliteMetadata: SatelliteMetadata
|
||||
)
|
||||
@@ -13,7 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.android.gpstest.model
|
||||
package com.android.gpstest.library.model
|
||||
|
||||
/**
|
||||
* A container class that holds metadata and statistics information about a group of satellites.
|
||||
@@ -32,20 +32,20 @@ package com.android.gpstest.model
|
||||
* has been detected as having duplicate carrier frequency data with another signal.
|
||||
*/
|
||||
data class SatelliteMetadata(
|
||||
val numSignalsInView: Int = 0,
|
||||
val numSignalsUsed: Int = 0,
|
||||
val numSignalsTotal: Int = 0,
|
||||
val numSatsInView: Int = 0,
|
||||
val numSatsUsed: Int = 0,
|
||||
val numSatsTotal: Int = 0,
|
||||
val supportedGnss: Set<GnssType> = HashSet(),
|
||||
val supportedGnssCfs: Set<String> = HashSet(),
|
||||
val supportedSbas: Set<SbasType> = HashSet(),
|
||||
val supportedSbasCfs: Set<String> = HashSet(),
|
||||
val unknownCarrierStatuses: Map<String, SatelliteStatus> = HashMap(),
|
||||
val duplicateCarrierStatuses: Map<String, SatelliteStatus> = HashMap(),
|
||||
val isDualFrequencyPerSatInView: Boolean = false,
|
||||
val isDualFrequencyPerSatInUse: Boolean = false,
|
||||
val isNonPrimaryCarrierFreqInView: Boolean = false,
|
||||
val isNonPrimaryCarrierFreqInUse: Boolean = false,
|
||||
val numSignalsInView: Int = 0,
|
||||
val numSignalsUsed: Int = 0,
|
||||
val numSignalsTotal: Int = 0,
|
||||
val numSatsInView: Int = 0,
|
||||
val numSatsUsed: Int = 0,
|
||||
val numSatsTotal: Int = 0,
|
||||
val supportedGnss: Set<GnssType> = HashSet(),
|
||||
val supportedGnssCfs: Set<String> = HashSet(),
|
||||
val supportedSbas: Set<SbasType> = HashSet(),
|
||||
val supportedSbasCfs: Set<String> = HashSet(),
|
||||
val unknownCarrierStatuses: Map<String, SatelliteStatus> = HashMap(),
|
||||
val duplicateCarrierStatuses: Map<String, SatelliteStatus> = HashMap(),
|
||||
val isDualFrequencyPerSatInView: Boolean = false,
|
||||
val isDualFrequencyPerSatInUse: Boolean = false,
|
||||
val isNonPrimaryCarrierFreqInView: Boolean = false,
|
||||
val isNonPrimaryCarrierFreqInUse: Boolean = false,
|
||||
)
|
||||
@@ -13,7 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.android.gpstest.model;
|
||||
package com.android.gpstest.library.model;
|
||||
|
||||
/**
|
||||
* Types of Global Navigation Satellite Systems
|
||||
@@ -13,21 +13,21 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.android.gpstest.model
|
||||
package com.android.gpstest.library.model
|
||||
|
||||
/**
|
||||
* Mirrors the GnssStatus class (https://developer.android.com/reference/android/location/GnssStatus),
|
||||
* but uses internal GnssType and SbasType values for GNSS and SBAS constellations
|
||||
*/
|
||||
data class SatelliteStatus (
|
||||
val svid: Int,
|
||||
val gnssType: GnssType,
|
||||
var cn0DbHz: Float,
|
||||
val hasAlmanac: Boolean,
|
||||
val hasEphemeris: Boolean,
|
||||
val usedInFix: Boolean,
|
||||
var elevationDegrees: Float,
|
||||
var azimuthDegrees: Float) {
|
||||
val svid: Int,
|
||||
val gnssType: GnssType,
|
||||
var cn0DbHz: Float,
|
||||
val hasAlmanac: Boolean,
|
||||
val hasEphemeris: Boolean,
|
||||
val usedInFix: Boolean,
|
||||
var elevationDegrees: Float,
|
||||
var azimuthDegrees: Float) {
|
||||
var sbasType: SbasType = SbasType.UNKNOWN
|
||||
var hasCarrierFrequency: Boolean = false
|
||||
var carrierFrequencyHz: Double = 0.0
|
||||
@@ -13,7 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.android.gpstest.model;
|
||||
package com.android.gpstest.library.model;
|
||||
|
||||
/**
|
||||
* Types of Satellite Based Augmentation Systems. See
|
||||
@@ -13,10 +13,11 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.android.gpstest.ui
|
||||
package com.android.gpstest.library.ui
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Application
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import android.location.Location
|
||||
import androidx.annotation.VisibleForTesting
|
||||
@@ -24,17 +25,17 @@ import androidx.lifecycle.AndroidViewModel
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.android.gpstest.data.FirstFixState
|
||||
import com.android.gpstest.data.FixState
|
||||
import com.android.gpstest.data.LocationRepository
|
||||
import com.android.gpstest.model.*
|
||||
import com.android.gpstest.util.CarrierFreqUtils.getCarrierFrequencyLabel
|
||||
import com.android.gpstest.util.FormatUtils.formatTtff
|
||||
import com.android.gpstest.util.NmeaUtils
|
||||
import com.android.gpstest.util.PreferenceUtil
|
||||
import com.android.gpstest.util.PreferenceUtils
|
||||
import com.android.gpstest.util.SatelliteUtil.toSatelliteGroup
|
||||
import com.android.gpstest.util.SatelliteUtil.toSatelliteStatus
|
||||
import com.android.gpstest.library.data.FirstFixState
|
||||
import com.android.gpstest.library.data.FixState
|
||||
import com.android.gpstest.library.data.LocationRepository
|
||||
import com.android.gpstest.library.model.*
|
||||
import com.android.gpstest.library.util.CarrierFreqUtils.getCarrierFrequencyLabel
|
||||
import com.android.gpstest.library.util.FormatUtils.formatTtff
|
||||
import com.android.gpstest.library.util.NmeaUtils
|
||||
import com.android.gpstest.library.util.PreferenceUtil
|
||||
import com.android.gpstest.library.util.PreferenceUtils
|
||||
import com.android.gpstest.library.util.SatelliteUtil.toSatelliteGroup
|
||||
import com.android.gpstest.library.util.SatelliteUtil.toSatelliteStatus
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.Job
|
||||
@@ -50,8 +51,10 @@ import javax.inject.Inject
|
||||
@ExperimentalCoroutinesApi
|
||||
@HiltViewModel
|
||||
class SignalInfoViewModel @Inject constructor(
|
||||
context: Context,
|
||||
application: Application,
|
||||
private val repository: LocationRepository
|
||||
private val repository: LocationRepository,
|
||||
prefs: SharedPreferences
|
||||
) : AndroidViewModel(application) {
|
||||
//
|
||||
// Flows from the repository
|
||||
@@ -115,21 +118,21 @@ class SignalInfoViewModel @Inject constructor(
|
||||
|
||||
// Preference listener that will cancel the above flows when the user turns off tracking via UI
|
||||
private val trackingListener: SharedPreferences.OnSharedPreferenceChangeListener =
|
||||
PreferenceUtil.newStopTrackingListener { setStarted(false) }
|
||||
PreferenceUtil.newStopTrackingListener({setStarted(context, false, prefs)}, prefs)
|
||||
|
||||
init {
|
||||
viewModelScope.launch {
|
||||
observeLocationUpdateStates()
|
||||
observeGnssStates()
|
||||
com.android.gpstest.Application.prefs.registerOnSharedPreferenceChangeListener(trackingListener)
|
||||
observeLocationUpdateStates(context, prefs)
|
||||
observeGnssStates(prefs)
|
||||
prefs.registerOnSharedPreferenceChangeListener(trackingListener)
|
||||
}
|
||||
}
|
||||
|
||||
@ExperimentalCoroutinesApi
|
||||
private fun observeLocationUpdateStates() {
|
||||
private fun observeLocationUpdateStates(context: Context, prefs: SharedPreferences) {
|
||||
repository.receivingLocationUpdates
|
||||
.onEach {
|
||||
setStarted(it)
|
||||
setStarted(context, it, prefs)
|
||||
}
|
||||
.launchIn(viewModelScope)
|
||||
}
|
||||
@@ -151,7 +154,7 @@ class SignalInfoViewModel @Inject constructor(
|
||||
}
|
||||
|
||||
@ExperimentalCoroutinesApi
|
||||
private fun observeGnssFlow() {
|
||||
private fun observeGnssFlow(context: Context, prefs: SharedPreferences) {
|
||||
if (gnssFlow?.isActive == true) {
|
||||
// If we're already observing updates, don't register again
|
||||
return
|
||||
@@ -161,19 +164,19 @@ class SignalInfoViewModel @Inject constructor(
|
||||
.map { it.toSatelliteStatus() }
|
||||
.onEach {
|
||||
//Log.d(TAG, "SignalInfoViewModel gnssStatus: ${it}")
|
||||
updateStatus(it)
|
||||
updateStatus(context, it, prefs)
|
||||
}
|
||||
.launchIn(viewModelScope)
|
||||
}
|
||||
|
||||
private fun observeGnssStates() {
|
||||
private fun observeGnssStates(prefs: SharedPreferences) {
|
||||
repository.firstFixState
|
||||
.onEach {
|
||||
when (it) {
|
||||
is FirstFixState.Acquired -> {
|
||||
onGnssFirstFix(it.ttffMillis)
|
||||
}
|
||||
is FirstFixState.NotAcquired -> if (PreferenceUtils.isTrackingStarted()) onGnssFixLost()
|
||||
is FirstFixState.NotAcquired -> if (PreferenceUtils.isTrackingStarted(prefs)) onGnssFixLost()
|
||||
}
|
||||
}
|
||||
.launchIn(viewModelScope)
|
||||
@@ -181,7 +184,7 @@ class SignalInfoViewModel @Inject constructor(
|
||||
.onEach {
|
||||
when (it) {
|
||||
is FixState.Acquired -> onGnssFixAcquired()
|
||||
is FixState.NotAcquired -> if (PreferenceUtils.isTrackingStarted()) onGnssFixLost()
|
||||
is FixState.NotAcquired -> if (PreferenceUtils.isTrackingStarted(prefs)) onGnssFixLost()
|
||||
}
|
||||
}
|
||||
.launchIn(viewModelScope)
|
||||
@@ -204,12 +207,12 @@ class SignalInfoViewModel @Inject constructor(
|
||||
|
||||
@ExperimentalCoroutinesApi
|
||||
@VisibleForTesting
|
||||
fun updateStatus(status: List<SatelliteStatus>) {
|
||||
fun updateStatus(context: Context, status: List<SatelliteStatus>, prefs: SharedPreferences) {
|
||||
_allStatuses.value = status
|
||||
_allSatellitesGroup.value = status.toSatelliteGroup()
|
||||
|
||||
// Get filter set by user in UI
|
||||
val filter = PreferenceUtils.gnssFilter()
|
||||
val filter = PreferenceUtils.gnssFilter(context, prefs)
|
||||
|
||||
// Split list into GNSS and SBAS statuses, apply "shown" filter, and update view model
|
||||
val (gnssStatus, sbasStatus) = status
|
||||
@@ -221,7 +224,7 @@ class SignalInfoViewModel @Inject constructor(
|
||||
}
|
||||
|
||||
_filteredStatuses.value = gnssStatus + sbasStatus
|
||||
setFilteredAndSortedStatuses(sort(gnssStatus, true), sort(sbasStatus, false))
|
||||
setFilteredAndSortedStatuses(sort(context, gnssStatus, true, prefs), sort(context, sbasStatus, false, prefs))
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -229,8 +232,8 @@ class SignalInfoViewModel @Inject constructor(
|
||||
* the user, with [isGnss] set to true if the list contains all GNSS signals and false if
|
||||
* it contains all SBAS signals
|
||||
*/
|
||||
private fun sort(status: List<SatelliteStatus>, isGnss: Boolean): List<SatelliteStatus> {
|
||||
return when (PreferenceUtils.getSatSortOrderFromPreferences()) {
|
||||
private fun sort(context: Context, status: List<SatelliteStatus>, isGnss: Boolean, prefs: SharedPreferences): List<SatelliteStatus> {
|
||||
return when (PreferenceUtils.getSatSortOrderFromPreferences(context, prefs)) {
|
||||
0 -> {
|
||||
// Sort by Constellation
|
||||
if (isGnss) {
|
||||
@@ -387,7 +390,7 @@ class SignalInfoViewModel @Inject constructor(
|
||||
|
||||
@ExperimentalCoroutinesApi
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
private fun setStarted(started: Boolean) {
|
||||
private fun setStarted(context: Context, started: Boolean, prefs: SharedPreferences) {
|
||||
if (started == this.started) {
|
||||
// State hasn't changed - no op and return
|
||||
return
|
||||
@@ -395,7 +398,7 @@ class SignalInfoViewModel @Inject constructor(
|
||||
if (started) {
|
||||
// Activity or service is observing updates, so observe here too
|
||||
observeLocationFlow()
|
||||
observeGnssFlow()
|
||||
observeGnssFlow(context, prefs)
|
||||
observeNmeaFlow()
|
||||
} else {
|
||||
// Cancel updates (Note that these are canceled via trackingListener preference listener
|
||||
@@ -527,7 +530,7 @@ class SignalInfoViewModel @Inject constructor(
|
||||
_dop.value = DilutionOfPrecision(Double.NaN, Double.NaN, Double.NaN)
|
||||
_filteredSatelliteMetadata.value = SatelliteMetadata()
|
||||
_fixState.value = FixState.NotAcquired
|
||||
_allSatellitesGroup.value = SatelliteGroup(emptyMap(),SatelliteMetadata())
|
||||
_allSatellitesGroup.value = SatelliteGroup(emptyMap(), SatelliteMetadata())
|
||||
gotFirstFix = false
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package com.android.gpstest.util
|
||||
package com.android.gpstest.library.util
|
||||
|
||||
import android.location.Location
|
||||
|
||||
import com.android.gpstest.model.MeasuredError
|
||||
import com.android.gpstest.library.model.MeasuredError
|
||||
|
||||
/**
|
||||
* Utilities for comparing two locations to measure error
|
||||
@@ -13,14 +13,14 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.android.gpstest.util;
|
||||
package com.android.gpstest.library.util;
|
||||
|
||||
import android.location.GnssAntennaInfo;
|
||||
import android.os.Build;
|
||||
|
||||
import androidx.annotation.RequiresApi;
|
||||
|
||||
import com.android.gpstest.model.SatelliteStatus;
|
||||
import com.android.gpstest.library.model.SatelliteStatus;
|
||||
|
||||
public class CarrierFreqUtils {
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.gpstest.util
|
||||
package com.android.gpstest.library.util
|
||||
|
||||
import android.os.Build
|
||||
import androidx.annotation.VisibleForTesting
|
||||
@@ -44,7 +44,7 @@ class DateTimeUtils {
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
internal fun isTimeValidLegacy(time: Long): Boolean {
|
||||
fun isTimeValidLegacy(time: Long): Boolean {
|
||||
return TimeUnit.MILLISECONDS.toDays(abs(System.currentTimeMillis() - time)) < NUM_DAYS_TIME_VALID
|
||||
}
|
||||
}
|
||||
@@ -1,69 +1,70 @@
|
||||
package com.android.gpstest.util
|
||||
package com.android.gpstest.library.util
|
||||
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import android.location.GnssAntennaInfo
|
||||
import android.location.GnssClock
|
||||
import android.location.GnssMeasurement
|
||||
import android.location.Location
|
||||
import android.os.Build
|
||||
import androidx.annotation.RequiresApi
|
||||
import com.android.gpstest.Application.Companion.app
|
||||
import com.android.gpstest.R
|
||||
import com.android.gpstest.model.CoordinateType
|
||||
import com.android.gpstest.model.Orientation
|
||||
import com.android.gpstest.model.SatelliteGroup
|
||||
import com.android.gpstest.model.SatelliteStatus
|
||||
import com.android.gpstest.util.SatelliteUtil.isBearingAccuracySupported
|
||||
import com.android.gpstest.util.SatelliteUtil.isSpeedAccuracySupported
|
||||
import com.android.gpstest.util.SatelliteUtil.isVerticalAccuracySupported
|
||||
import com.android.gpstest.util.SatelliteUtil.toGnssStatusConstellationType
|
||||
import com.android.gpstest.library.R
|
||||
import com.android.gpstest.library.model.CoordinateType
|
||||
import com.android.gpstest.library.model.Orientation
|
||||
import com.android.gpstest.library.model.SatelliteGroup
|
||||
import com.android.gpstest.library.model.SatelliteStatus
|
||||
import com.android.gpstest.library.util.SatelliteUtil.isBearingAccuracySupported
|
||||
import com.android.gpstest.library.util.SatelliteUtil.isSpeedAccuracySupported
|
||||
import com.android.gpstest.library.util.SatelliteUtil.isVerticalAccuracySupported
|
||||
import com.android.gpstest.library.util.SatelliteUtil.toGnssStatusConstellationType
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
/**
|
||||
* Provides access to formatting utilities for view in the UI
|
||||
**/
|
||||
internal object FormatUtils {
|
||||
fun formatLatOrLon(latOrLong: Double, coordinateType: CoordinateType): String {
|
||||
object FormatUtils {
|
||||
fun formatLatOrLon(context: Context, latOrLong: Double, coordinateType: CoordinateType, prefs: SharedPreferences): String {
|
||||
if (latOrLong == 0.0) return " "
|
||||
|
||||
when (PreferenceUtil.coordinateFormat()) {
|
||||
when (PreferenceUtil.coordinateFormat(context, prefs)) {
|
||||
"dd" -> {
|
||||
// Decimal degrees
|
||||
return app.getString(R.string.lat_or_lon, latOrLong)
|
||||
return context.getString(R.string.lat_or_lon, latOrLong)
|
||||
}
|
||||
"dms" -> {
|
||||
// Degrees minutes seconds
|
||||
return UIUtils.getDMSFromLocation(
|
||||
app,
|
||||
return LibUIUtils.getDMSFromLocation(
|
||||
context,
|
||||
latOrLong,
|
||||
coordinateType
|
||||
)
|
||||
}
|
||||
"ddm" -> {
|
||||
// Degrees decimal minutes
|
||||
return UIUtils.getDDMFromLocation(
|
||||
app,
|
||||
return LibUIUtils.getDDMFromLocation(
|
||||
context,
|
||||
latOrLong,
|
||||
coordinateType
|
||||
)
|
||||
}
|
||||
else -> {
|
||||
// Decimal degrees
|
||||
return app.getString(R.string.lat_or_lon, latOrLong)
|
||||
return context.getString(R.string.lat_or_lon, latOrLong)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun formatAltitude(location: Location): String {
|
||||
fun formatAltitude(context: Context, location: Location, prefs: SharedPreferences): String {
|
||||
if (location.hasAltitude()) {
|
||||
val text = when {
|
||||
PreferenceUtil.distanceUnits().equals(PreferenceUtil.METERS, ignoreCase = true) -> {
|
||||
app.getString(R.string.gps_altitude_value_meters, location.altitude)
|
||||
PreferenceUtil.distanceUnits(context, prefs).equals(PreferenceUtil.METERS, ignoreCase = true) -> {
|
||||
context.getString(R.string.gps_altitude_value_meters, location.altitude)
|
||||
}
|
||||
else -> {
|
||||
// Feet
|
||||
app.getString(
|
||||
context.getString(
|
||||
R.string.gps_altitude_value_feet,
|
||||
UIUtils.toFeet(location.altitude)
|
||||
LibUIUtils.toFeet(location.altitude)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -73,25 +74,25 @@ internal object FormatUtils {
|
||||
}
|
||||
}
|
||||
|
||||
fun formatSpeed(location: Location): String {
|
||||
fun formatSpeed(context: Context, location: Location, prefs: SharedPreferences): String {
|
||||
if (location.hasSpeed()) {
|
||||
val text = when {
|
||||
PreferenceUtil.speedUnits()
|
||||
PreferenceUtil.speedUnits(context, prefs)
|
||||
.equals(PreferenceUtil.METERS_PER_SECOND, ignoreCase = true) -> {
|
||||
app.getString(R.string.gps_speed_value_meters_sec, location.speed)
|
||||
context.getString(R.string.gps_speed_value_meters_sec, location.speed)
|
||||
}
|
||||
PreferenceUtil.speedUnits()
|
||||
PreferenceUtil.speedUnits(context, prefs)
|
||||
.equals(PreferenceUtil.KILOMETERS_PER_HOUR, ignoreCase = true) -> {
|
||||
app.getString(
|
||||
context.getString(
|
||||
R.string.gps_speed_value_kilometers_hour,
|
||||
UIUtils.toKilometersPerHour(location.speed)
|
||||
LibUIUtils.toKilometersPerHour(location.speed)
|
||||
)
|
||||
}
|
||||
else -> {
|
||||
// Miles per hour
|
||||
app.getString(
|
||||
context.getString(
|
||||
R.string.gps_speed_value_miles_hour,
|
||||
UIUtils.toMilesPerHour(location.speed)
|
||||
LibUIUtils.toMilesPerHour(location.speed)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -115,36 +116,36 @@ internal object FormatUtils {
|
||||
}
|
||||
}
|
||||
|
||||
fun formatAccuracy(location: Location): String {
|
||||
fun formatAccuracy(context: Context, location: Location, prefs: SharedPreferences): String {
|
||||
if (location.isVerticalAccuracySupported()) {
|
||||
if (PreferenceUtil.distanceUnits().equals(PreferenceUtil.METERS, ignoreCase = true)) {
|
||||
return app.getString(
|
||||
if (PreferenceUtil.distanceUnits(context, prefs).equals(PreferenceUtil.METERS, ignoreCase = true)) {
|
||||
return context.getString(
|
||||
R.string.gps_hor_and_vert_accuracy_value_meters,
|
||||
location.accuracy,
|
||||
location.verticalAccuracyMeters
|
||||
)
|
||||
} else {
|
||||
// Feet
|
||||
return app.getString(
|
||||
return context.getString(
|
||||
R.string.gps_hor_and_vert_accuracy_value_feet,
|
||||
UIUtils.toFeet(location.accuracy.toDouble()),
|
||||
UIUtils.toFeet(
|
||||
LibUIUtils.toFeet(location.accuracy.toDouble()),
|
||||
LibUIUtils.toFeet(
|
||||
location.verticalAccuracyMeters.toDouble()
|
||||
)
|
||||
)
|
||||
}
|
||||
} else {
|
||||
if (location.hasAccuracy()) {
|
||||
return if (PreferenceUtil.distanceUnits()
|
||||
return if (PreferenceUtil.distanceUnits(context, prefs)
|
||||
.equals(PreferenceUtil.METERS, ignoreCase = true)) {
|
||||
app.getString(
|
||||
context.getString(
|
||||
R.string.gps_accuracy_value_meters, location.accuracy
|
||||
)
|
||||
} else {
|
||||
// Feet
|
||||
app.getString(
|
||||
context.getString(
|
||||
R.string.gps_accuracy_value_feet,
|
||||
UIUtils.toFeet(location.accuracy.toDouble())
|
||||
LibUIUtils.toFeet(location.accuracy.toDouble())
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -152,44 +153,44 @@ internal object FormatUtils {
|
||||
return ""
|
||||
}
|
||||
|
||||
fun formatAltitudeMsl(altitudeMsl: Double): String {
|
||||
fun formatAltitudeMsl(context: Context, altitudeMsl: Double, prefs: SharedPreferences): String {
|
||||
if (altitudeMsl.isNaN()) return ""
|
||||
|
||||
return if (PreferenceUtil.distanceUnits()
|
||||
return if (PreferenceUtil.distanceUnits(context, prefs)
|
||||
.equals(PreferenceUtil.METERS, ignoreCase = true)) {
|
||||
app.getString(
|
||||
context.getString(
|
||||
R.string.gps_altitude_msl_value_meters,
|
||||
altitudeMsl)
|
||||
} else {
|
||||
app.getString(
|
||||
context.getString(
|
||||
R.string.gps_altitude_msl_value_feet,
|
||||
UIUtils.toFeet(altitudeMsl)
|
||||
LibUIUtils.toFeet(altitudeMsl)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun formatSpeedAccuracy(location: Location): String {
|
||||
fun formatSpeedAccuracy(context: Context, location: Location, prefs: SharedPreferences): String {
|
||||
if (location.isSpeedAccuracySupported()) {
|
||||
when {
|
||||
PreferenceUtil.speedUnits()
|
||||
PreferenceUtil.speedUnits(context, prefs)
|
||||
.equals(PreferenceUtil.METERS_PER_SECOND, ignoreCase = true) -> {
|
||||
return app.getString(
|
||||
return context.getString(
|
||||
R.string.gps_speed_acc_value_meters_sec,
|
||||
location.speedAccuracyMetersPerSecond
|
||||
)
|
||||
}
|
||||
PreferenceUtil.speedUnits()
|
||||
PreferenceUtil.speedUnits(context, prefs)
|
||||
.equals(PreferenceUtil.KILOMETERS_PER_HOUR, ignoreCase = true) -> {
|
||||
return app.getString(
|
||||
return context.getString(
|
||||
R.string.gps_speed_acc_value_km_hour,
|
||||
UIUtils.toKilometersPerHour(location.speedAccuracyMetersPerSecond)
|
||||
LibUIUtils.toKilometersPerHour(location.speedAccuracyMetersPerSecond)
|
||||
)
|
||||
}
|
||||
else -> {
|
||||
// Miles per hour
|
||||
return app.getString(
|
||||
return context.getString(
|
||||
R.string.gps_speed_acc_value_miles_hour,
|
||||
UIUtils.toMilesPerHour(location.speedAccuracyMetersPerSecond)
|
||||
LibUIUtils.toMilesPerHour(location.speedAccuracyMetersPerSecond)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -197,13 +198,13 @@ internal object FormatUtils {
|
||||
return ""
|
||||
}
|
||||
|
||||
fun formatBearing(location: Location): String {
|
||||
return app.getString(R.string.gps_bearing_value, location.bearing)
|
||||
fun formatBearing(context: Context, location: Location): String {
|
||||
return context.getString(R.string.gps_bearing_value, location.bearing)
|
||||
}
|
||||
|
||||
fun formatBearingAccuracy(location: Location): String {
|
||||
fun formatBearingAccuracy(context: Context, location: Location): String {
|
||||
return if (location.isBearingAccuracySupported()) {
|
||||
app.getString(
|
||||
context.getString(
|
||||
R.string.gps_bearing_acc_value,
|
||||
location.bearingAccuracyDegrees
|
||||
)
|
||||
@@ -216,9 +217,9 @@ internal object FormatUtils {
|
||||
* Returns metadata about the satellite group formatted for a notification title, like
|
||||
* "1/3 sats | 2/4 signals (E1, E5a, L1)"
|
||||
*/
|
||||
fun SatelliteGroup.toNotificationTitle(): String {
|
||||
fun SatelliteGroup.toNotificationTitle(context: Context): String {
|
||||
val meta = this.satelliteMetadata
|
||||
return app.getString(
|
||||
return context.getString(
|
||||
R.string.notification_title,
|
||||
meta.numSatsUsed,
|
||||
meta.numSatsInView,
|
||||
@@ -333,14 +334,14 @@ internal object FormatUtils {
|
||||
"${phaseCenterOffset.xOffsetUncertaintyMm.toLog()},${phaseCenterOffset.yOffsetMm.toLog()}," +
|
||||
"${phaseCenterOffset.yOffsetUncertaintyMm.toLog()},${phaseCenterOffset.zOffsetMm.toLog()}," +
|
||||
"${phaseCenterOffset.zOffsetUncertaintyMm.toLog()},${
|
||||
IOUtils.serialize(
|
||||
phaseCenterVariationCorrections!!.correctionsArray
|
||||
)
|
||||
IOUtils.serialize(
|
||||
phaseCenterVariationCorrections!!.correctionsArray
|
||||
)
|
||||
},${IOUtils.serialize(phaseCenterVariationCorrections!!.correctionUncertaintiesArray)}," +
|
||||
"${phaseCenterVariationCorrections!!.deltaPhi.toLog()},${phaseCenterVariationCorrections!!.deltaTheta.toLog()},${
|
||||
IOUtils.serialize(
|
||||
signalGainCorrections!!.correctionsArray
|
||||
)
|
||||
IOUtils.serialize(
|
||||
signalGainCorrections!!.correctionsArray
|
||||
)
|
||||
},${IOUtils.serialize(signalGainCorrections!!.correctionUncertaintiesArray)}," +
|
||||
"${signalGainCorrections!!.deltaPhi.toLog()},${signalGainCorrections!!.deltaTheta.toLog()}"
|
||||
}
|
||||
@@ -13,11 +13,11 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.android.gpstest.util;
|
||||
package com.android.gpstest.library.util;
|
||||
|
||||
import static android.content.Intent.ACTION_VIEW;
|
||||
import static com.android.gpstest.util.LocationUtils.isValidLatitude;
|
||||
import static com.android.gpstest.util.LocationUtils.isValidLongitude;
|
||||
import static com.android.gpstest.library.util.LocationUtils.isValidLatitude;
|
||||
import static com.android.gpstest.library.util.LocationUtils.isValidLongitude;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.ClipData;
|
||||
@@ -37,11 +37,10 @@ import androidx.annotation.RequiresApi;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.core.content.FileProvider;
|
||||
|
||||
import com.android.gpstest.Application;
|
||||
import com.android.gpstest.BuildConfig;
|
||||
import com.android.gpstest.R;
|
||||
import com.android.gpstest.io.FileToDeleteFilter;
|
||||
import com.android.gpstest.library.R;
|
||||
import com.android.gpstest.library.io.FileToDeleteFilter;
|
||||
import com.google.zxing.integration.android.IntentIntegrator;
|
||||
import com.sothree.slidinguppanel.library.BuildConfig;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileFilter;
|
||||
@@ -72,20 +71,20 @@ public class IOUtils {
|
||||
* SHOW_RADAR action (com.google.android.radar.SHOW_RADAR) with a valid latitude and longitude or ACTION_VIEW action with geo URI, or
|
||||
* null if the Intent doesn't have a SHOW_RADAR action or the intent has an invalid latitude or longitude
|
||||
*/
|
||||
public static Location getLocationFromIntent(Intent intent) {
|
||||
public static Location getLocationFromIntent(Context context, Intent intent) {
|
||||
Location groundTruth = null;
|
||||
if (isShowRadarIntent(intent)) {
|
||||
if (isShowRadarIntent(context, intent)) {
|
||||
double lat = Double.NaN, lon = Double.NaN;
|
||||
float latFloat = intent.getFloatExtra(Application.Companion.getApp().getString(R.string.radar_lat_key), Float.NaN);
|
||||
float lonFloat = intent.getFloatExtra(Application.Companion.getApp().getString(R.string.radar_lon_key), Float.NaN);
|
||||
float latFloat = intent.getFloatExtra(context.getString(R.string.radar_lat_key), Float.NaN);
|
||||
float lonFloat = intent.getFloatExtra(context.getString(R.string.radar_lon_key), Float.NaN);
|
||||
if (isValidLatitude(latFloat) && isValidLongitude(lonFloat)) {
|
||||
// Use the float values
|
||||
lat = (double) latFloat;
|
||||
lon = (double) lonFloat;
|
||||
} else {
|
||||
// Try parsing doubles
|
||||
double latDouble = intent.getDoubleExtra(Application.Companion.getApp().getString(R.string.radar_lat_key), Double.NaN);
|
||||
double lonDouble = intent.getDoubleExtra(Application.Companion.getApp().getString(R.string.radar_lon_key), Double.NaN);
|
||||
double latDouble = intent.getDoubleExtra(context.getString(R.string.radar_lat_key), Double.NaN);
|
||||
double lonDouble = intent.getDoubleExtra(context.getString(R.string.radar_lon_key), Double.NaN);
|
||||
if (isValidLatitude(latDouble) && isValidLongitude(lonDouble)) {
|
||||
lat = latDouble;
|
||||
lon = lonDouble;
|
||||
@@ -96,21 +95,21 @@ public class IOUtils {
|
||||
groundTruth = new Location("ground_truth");
|
||||
groundTruth.setLatitude(lat);
|
||||
groundTruth.setLongitude(lon);
|
||||
if (intent.hasExtra(Application.Companion.getApp().getString(R.string.radar_alt_key))) {
|
||||
float altitude = intent.getFloatExtra(Application.Companion.getApp().getString(R.string.radar_alt_key), Float.NaN);
|
||||
if (intent.hasExtra(context.getString(R.string.radar_alt_key))) {
|
||||
float altitude = intent.getFloatExtra(context.getString(R.string.radar_alt_key), Float.NaN);
|
||||
if (!Float.isNaN(altitude)) {
|
||||
groundTruth.setAltitude(altitude);
|
||||
} else {
|
||||
// Try the double version
|
||||
double altitudeDouble = intent.getDoubleExtra(Application.Companion.getApp().getString(R.string.radar_alt_key), Double.NaN);
|
||||
double altitudeDouble = intent.getDoubleExtra(context.getString(R.string.radar_alt_key), Double.NaN);
|
||||
if (!Double.isNaN(altitudeDouble)) {
|
||||
groundTruth.setAltitude(altitudeDouble);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (isGeoIntent(intent)) {
|
||||
groundTruth = getLocationFromGeoUri(intent.getData().toString());
|
||||
} else if (isGeoIntent(context, intent)) {
|
||||
groundTruth = getLocationFromGeoUri(context, intent.getData().toString());
|
||||
}
|
||||
return groundTruth;
|
||||
}
|
||||
@@ -121,10 +120,10 @@ public class IOUtils {
|
||||
* @param intent
|
||||
* @return true if the provided intent has the SHOW_RADAR action (com.google.android.radar.SHOW_RADAR), or false if it does not
|
||||
*/
|
||||
public static boolean isShowRadarIntent(Intent intent) {
|
||||
public static boolean isShowRadarIntent(Context context, Intent intent) {
|
||||
return intent != null &&
|
||||
intent.getAction() != null &&
|
||||
intent.getAction().equals(Application.Companion.getApp().getString(R.string.show_radar_intent));
|
||||
intent.getAction().equals(context.getString(R.string.show_radar_intent));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -133,12 +132,12 @@ public class IOUtils {
|
||||
* @param intent
|
||||
* @return true if the provided intent has the ACTION_VIEW action and contains a geo: URI, or false if it does not
|
||||
*/
|
||||
public static boolean isGeoIntent(Intent intent) {
|
||||
public static boolean isGeoIntent(Context context, Intent intent) {
|
||||
return intent != null &&
|
||||
intent.getAction() != null &&
|
||||
intent.getAction().equals(ACTION_VIEW) &&
|
||||
intent.getData() != null &&
|
||||
intent.getData().toString().startsWith(Application.Companion.getApp().getString(R.string.geo_uri_prefix));
|
||||
intent.getData().toString().startsWith(context.getString(R.string.geo_uri_prefix));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -147,8 +146,8 @@ public class IOUtils {
|
||||
* @param location location information to be added to the intent
|
||||
* @return a SHOW_RADAR intent with the provided latitude, longitude, and, if provided, altitude, all in WGS-84
|
||||
*/
|
||||
public static Intent createShowRadarIntent(Location location) {
|
||||
return createShowRadarIntent(location.getLatitude(), location.getLongitude(), location.hasAltitude() ? location.getAltitude() : null);
|
||||
public static Intent createShowRadarIntent(Context context, Location location) {
|
||||
return createShowRadarIntent(context, location.getLatitude(), location.getLongitude(), location.hasAltitude() ? location.getAltitude() : null);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -159,12 +158,12 @@ public class IOUtils {
|
||||
* @param alt altitude in meters above WGS84 ellipsoid, or null if altitude shouldn't be included
|
||||
* @return a SHOW_RADAR intent with the provided latitude, longitude, and, if provided, altitude, all in WGS-84
|
||||
*/
|
||||
public static Intent createShowRadarIntent(double lat, double lon, Double alt) {
|
||||
Intent intent = new Intent(Application.Companion.getApp().getString(R.string.show_radar_intent));
|
||||
intent.putExtra(Application.Companion.getApp().getString(R.string.radar_lat_key), lat);
|
||||
intent.putExtra(Application.Companion.getApp().getString(R.string.radar_lon_key), lon);
|
||||
public static Intent createShowRadarIntent(Context context, double lat, double lon, Double alt) {
|
||||
Intent intent = new Intent(context.getString(R.string.show_radar_intent));
|
||||
intent.putExtra(context.getString(R.string.radar_lat_key), lat);
|
||||
intent.putExtra(context.getString(R.string.radar_lon_key), lon);
|
||||
if (alt != null && !Double.isNaN(alt)) {
|
||||
intent.putExtra(Application.Companion.getApp().getString(R.string.radar_alt_key), alt);
|
||||
intent.putExtra(context.getString(R.string.radar_alt_key), alt);
|
||||
}
|
||||
return intent;
|
||||
}
|
||||
@@ -181,8 +180,8 @@ public class IOUtils {
|
||||
* @param geoUri a Geo URI following RFC 5870 (e.g., geo:37.786971,-122.399677)
|
||||
* @return a location from the provided Geo URI (RFC 5870) or null if one can't be parsed
|
||||
*/
|
||||
public static Location getLocationFromGeoUri(String geoUri) {
|
||||
if (TextUtils.isEmpty(geoUri) || !geoUri.startsWith(Application.Companion.getApp().getString(R.string.geo_uri_prefix))) {
|
||||
public static Location getLocationFromGeoUri(Context context, String geoUri) {
|
||||
if (TextUtils.isEmpty(geoUri) || !geoUri.startsWith(context.getString(R.string.geo_uri_prefix))) {
|
||||
return null;
|
||||
}
|
||||
Location l = null;
|
||||
@@ -210,11 +209,11 @@ public class IOUtils {
|
||||
* @param includeAltitude true if altitude should be included in the Geo URI, false if it should be omitted. If the location doesn't have an altitude value this parameter has no effect.
|
||||
* @return a Geo URI (RFC 5870) from the provided location, or null if one can't be created
|
||||
*/
|
||||
public static String createGeoUri(Location location, boolean includeAltitude) {
|
||||
public static String createGeoUri(Context context, Location location, boolean includeAltitude) {
|
||||
if (location == null) {
|
||||
return null;
|
||||
}
|
||||
String geoUri = Application.Companion.getApp().getString(R.string.geo_uri_prefix);
|
||||
String geoUri = context.getString(R.string.geo_uri_prefix);
|
||||
geoUri += location.getLatitude() + ",";
|
||||
geoUri += location.getLongitude();
|
||||
if (location.hasAltitude() && includeAltitude) {
|
||||
@@ -228,9 +227,9 @@ public class IOUtils {
|
||||
*
|
||||
* @param location the location string to copy to the clipboard
|
||||
*/
|
||||
public static void copyToClipboard(String location) {
|
||||
ClipboardManager clipboard = (ClipboardManager) Application.Companion.getApp().getSystemService(Context.CLIPBOARD_SERVICE);
|
||||
ClipData clip = ClipData.newPlainText(Application.Companion.getApp().getString(R.string.pref_file_location_output_title), location);
|
||||
public static void copyToClipboard(Context context, String location) {
|
||||
ClipboardManager clipboard = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE);
|
||||
ClipData clip = ClipData.newPlainText(context.getString(R.string.pref_file_location_output_title), location);
|
||||
clipboard.setPrimaryClip(clip);
|
||||
}
|
||||
|
||||
@@ -274,7 +273,7 @@ public class IOUtils {
|
||||
* @param activity Activity used to open the chooser to send the file
|
||||
* @param files files to send
|
||||
*/
|
||||
public static void sendLogFile(Activity activity, File... files) {
|
||||
public static void sendLogFile(Context context, Activity activity, File... files) {
|
||||
ArrayList<android.net.Uri> uris = new ArrayList<>();
|
||||
for (File file : files) {
|
||||
if (file != null) {
|
||||
@@ -282,7 +281,7 @@ public class IOUtils {
|
||||
}
|
||||
}
|
||||
|
||||
IOUtils.sendLogFile(activity, uris);
|
||||
IOUtils.sendLogFile(context, activity, uris);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -291,14 +290,14 @@ public class IOUtils {
|
||||
* @param activity
|
||||
* @param fileUris Android URIs for the File to be attached
|
||||
*/
|
||||
public static void sendLogFile(Activity activity, ArrayList<android.net.Uri> fileUris) {
|
||||
public static void sendLogFile(Context context, Activity activity, ArrayList<android.net.Uri> fileUris) {
|
||||
Intent emailIntent = new Intent(Intent.ACTION_SEND_MULTIPLE);
|
||||
emailIntent.setType("*/*");
|
||||
emailIntent.putExtra(Intent.EXTRA_SUBJECT, "GnssLog from GPSTest");
|
||||
emailIntent.putExtra(Intent.EXTRA_TEXT, "");
|
||||
Log.d(TAG, "Sending " + fileUris);
|
||||
emailIntent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, fileUris);
|
||||
activity.startActivity(Intent.createChooser(emailIntent, Application.Companion.getApp().getString(R.string.send_log)));
|
||||
activity.startActivity(Intent.createChooser(emailIntent, context.getString(R.string.send_log)));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -371,9 +370,9 @@ public class IOUtils {
|
||||
*
|
||||
* @return the GNSS hardware year for the device, or null if the year couldn't be determined
|
||||
*/
|
||||
public static String getGnssHardwareYear() {
|
||||
public static String getGnssHardwareYear(Context context) {
|
||||
String year = "";
|
||||
LocationManager locationManager = (LocationManager) Application.Companion.getApp().getSystemService(Context.LOCATION_SERVICE);
|
||||
LocationManager locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||
year = String.valueOf(locationManager.getGnssYearOfHardware());
|
||||
@@ -403,9 +402,9 @@ public class IOUtils {
|
||||
*
|
||||
* @return the GNSS hardware model name for the device, or empty String if the year couldn't be determined
|
||||
*/
|
||||
public static String getGnssHardwareModelName() {
|
||||
public static String getGnssHardwareModelName(Context context) {
|
||||
String modelName = "";
|
||||
LocationManager locationManager = (LocationManager) Application.Companion.getApp().getSystemService(Context.LOCATION_SERVICE);
|
||||
LocationManager locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||
if (locationManager.getGnssHardwareModelName() != null) {
|
||||
@@ -420,11 +419,11 @@ public class IOUtils {
|
||||
* @param locationManager
|
||||
* @return true if the command succeeded, false if it failed
|
||||
*/
|
||||
public static boolean forceTimeInjection(LocationManager locationManager) {
|
||||
public static boolean forceTimeInjection(Context context, LocationManager locationManager) {
|
||||
if (locationManager == null) {
|
||||
return false;
|
||||
}
|
||||
return locationManager.sendExtraCommand(LocationManager.GPS_PROVIDER, Application.Companion.getApp().getString(R.string.force_time_injection_command), null);
|
||||
return locationManager.sendExtraCommand(LocationManager.GPS_PROVIDER, context.getString(R.string.force_time_injection_command), null);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -432,15 +431,15 @@ public class IOUtils {
|
||||
* @param locationManager
|
||||
* @return true if the command succeeded, false if it failed
|
||||
*/
|
||||
public static boolean forcePsdsInjection(LocationManager locationManager) {
|
||||
public static boolean forcePsdsInjection(Context context, LocationManager locationManager) {
|
||||
if (locationManager == null) {
|
||||
return false;
|
||||
}
|
||||
String command;
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
command = Application.Companion.getApp().getString(R.string.force_psds_injection_command);
|
||||
command = context.getString(R.string.force_psds_injection_command);
|
||||
} else {
|
||||
command = Application.Companion.getApp().getString(R.string.force_xtra_injection_command);
|
||||
command = context.getString(R.string.force_xtra_injection_command);
|
||||
}
|
||||
return locationManager.sendExtraCommand(LocationManager.GPS_PROVIDER, command, null);
|
||||
}
|
||||
@@ -450,11 +449,11 @@ public class IOUtils {
|
||||
* @param locationManager
|
||||
* @return true if the command succeeded, false if it failed
|
||||
*/
|
||||
public static boolean deleteAidingData(LocationManager locationManager) {
|
||||
public static boolean deleteAidingData(Context context,LocationManager locationManager) {
|
||||
if (locationManager == null) {
|
||||
return false;
|
||||
}
|
||||
return locationManager.sendExtraCommand(LocationManager.GPS_PROVIDER, Application.Companion.getApp().getString(R.string.delete_aiding_data_command), null);
|
||||
return locationManager.sendExtraCommand(LocationManager.GPS_PROVIDER, context.getString(R.string.delete_aiding_data_command), null);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -0,0 +1,907 @@
|
||||
/*
|
||||
* Copyright (C) 2015-2018 University of South Florida, Sean J. Barbeau
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.android.gpstest.library.util
|
||||
|
||||
import android.Manifest
|
||||
import android.animation.Animator
|
||||
import android.animation.AnimatorListenerAdapter
|
||||
import android.app.Activity
|
||||
import android.content.ActivityNotFoundException
|
||||
import android.content.Context
|
||||
import android.content.DialogInterface
|
||||
import android.content.Intent
|
||||
import android.content.SharedPreferences
|
||||
import android.content.pm.PackageInfo
|
||||
import android.content.pm.PackageManager
|
||||
import android.content.res.Configuration
|
||||
import android.location.Location
|
||||
import android.location.LocationManager
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.provider.Settings
|
||||
import android.text.Spannable
|
||||
import android.text.TextUtils
|
||||
import android.text.method.LinkMovementMethod
|
||||
import android.text.style.ClickableSpan
|
||||
import android.view.View
|
||||
import android.view.ViewGroup.MarginLayoutParams
|
||||
import android.widget.TextView
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.constraintlayout.widget.ConstraintLayout
|
||||
import androidx.core.app.ActivityCompat
|
||||
import androidx.fragment.app.Fragment
|
||||
// import com.android.gpstest.library.BuildConfig
|
||||
import com.android.gpstest.library.R
|
||||
import com.android.gpstest.library.model.CoordinateType
|
||||
import com.android.gpstest.library.model.GnssType
|
||||
import com.android.gpstest.library.model.SbasType
|
||||
import com.android.gpstest.library.ui.SignalInfoViewModel
|
||||
import com.google.android.material.chip.Chip
|
||||
import com.sothree.slidinguppanel.library.BuildConfig
|
||||
import java.math.BigDecimal
|
||||
import java.math.RoundingMode
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* Utilities for processing user inteface elements
|
||||
*/
|
||||
object LibUIUtils {
|
||||
const val TAG = "UIUtils"
|
||||
var PICKFILE_REQUEST_CODE = 101
|
||||
const val ANIMATION_DURATION_SHORT_MS = 200
|
||||
const val ANIMATION_DURATION_MEDIUM_MS = 400
|
||||
const val ANIMATION_DURATION_LONG_MS = 500
|
||||
const val MIN_VALUE_CN0 = 10.0f
|
||||
const val MAX_VALUE_CN0 = 45.0f
|
||||
|
||||
// Dialogs
|
||||
const val WHATSNEW_DIALOG = 1
|
||||
const val HELP_DIALOG = 2
|
||||
const val CLEAR_ASSIST_WARNING_DIALOG = 3
|
||||
private const val WHATS_NEW_VER = "whatsNewVer"
|
||||
|
||||
/**
|
||||
* Formats a view so it is ignored for accessible access
|
||||
*/
|
||||
@JvmStatic
|
||||
fun setAccessibilityIgnore(view: View) {
|
||||
view.isClickable = false
|
||||
view.isFocusable = false
|
||||
view.contentDescription = ""
|
||||
view.importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_NO
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts screen dimension units from dp to pixels, based on algorithm defined in
|
||||
* http://developer.android.com/guide/practices/screens_support.html#dips-pels
|
||||
*
|
||||
* @param dp value in dp
|
||||
* @return value in pixels
|
||||
*/
|
||||
@JvmStatic
|
||||
fun dpToPixels(context: Context, dp: Float): Int {
|
||||
// Get the screen's density scale
|
||||
val scale = context.resources.displayMetrics.density
|
||||
// Convert the dps to pixels, based on density scale
|
||||
return (dp * scale + 0.5f).toInt()
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the current display is wide enough to show the GPS date on the Status screen,
|
||||
* false if the current display is too narrow to fit the GPS date
|
||||
* @param context
|
||||
* @return true if the current display is wide enough to show the GPS date on the Status screen,
|
||||
* false if the current display is too narrow to fit the GPS date
|
||||
*/
|
||||
fun isWideEnoughForDate(context: Context): Boolean {
|
||||
// 450dp is a little larger than the width of a Samsung Galaxy S8+
|
||||
val WIDTH_THRESHOLD = dpToPixels(context, 450f)
|
||||
return context.resources.displayMetrics.widthPixels > WIDTH_THRESHOLD
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the activity is still active and dialogs can be managed (i.e., displayed
|
||||
* or dismissed), or false if it is not
|
||||
*
|
||||
* @param activity Activity to check for displaying/dismissing a dialog
|
||||
* @return true if the activity is still active and dialogs can be managed, or false if it is
|
||||
* not
|
||||
*/
|
||||
fun canManageDialog(activity: Activity?): Boolean {
|
||||
return if (activity == null) {
|
||||
false
|
||||
} else !activity.isFinishing && !activity.isDestroyed
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the fragment is attached to the activity, or false if it is not attached
|
||||
*
|
||||
* @param f fragment to be tested
|
||||
* @return true if the fragment is attached to the activity, or false if it is not attached
|
||||
*/
|
||||
fun isFragmentAttached(f: Fragment): Boolean {
|
||||
return f.activity != null && f.isAdded
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the provided C/N0 values to a left margin value (dp) for the avg C/N0 indicator ImageViews in gps_sky_signal
|
||||
* Left margin range for the C/N0 indicator ImageViews in gps_sky_signal is determined by dimens.xml
|
||||
* cn0_meter_width (based on device screen width) and cn0_indicator_min_left_margin values.
|
||||
*
|
||||
* This is effectively an affine transform - https://math.stackexchange.com/a/377174/554287.
|
||||
*
|
||||
* @param cn0 carrier-to-noise density at the antenna of the satellite in dB-Hz (from GnssStatus)
|
||||
* @return left margin value in dp for the C/N0 indicator ImageViews
|
||||
*/
|
||||
@JvmStatic
|
||||
fun cn0ToIndicatorLeftMarginPx(
|
||||
cn0: Float,
|
||||
minIndicatorMarginPx: Int,
|
||||
maxIndicatorMarginPx: Int
|
||||
): Int {
|
||||
return MathUtils.mapToRange(
|
||||
cn0,
|
||||
MIN_VALUE_CN0,
|
||||
MAX_VALUE_CN0,
|
||||
minIndicatorMarginPx.toFloat(),
|
||||
maxIndicatorMarginPx.toFloat()
|
||||
).toInt()
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the provided C/N0 values to a left margin value (dp) for the avg C/N0 TextViews in gps_sky_signal
|
||||
* Left margin range for the C/N0 indicator TextView in gps_sky_signal is determined by dimens.xml
|
||||
* cn0_meter_width (based on device screen width) and cn0_textview_min_left_margin values.
|
||||
*
|
||||
* This is effectively an affine transform - https://math.stackexchange.com/a/377174/554287.
|
||||
*
|
||||
* @param cn0 carrier-to-noise density at the antenna of the satellite in dB-Hz (from GnssStatus)
|
||||
* @return left margin value in dp for the C/N0 TextViews
|
||||
*/
|
||||
@JvmStatic
|
||||
fun cn0ToTextViewLeftMarginPx(
|
||||
cn0: Float,
|
||||
minTextViewMarginPx: Int,
|
||||
maxTextViewMarginPx: Int
|
||||
): Int {
|
||||
return MathUtils.mapToRange(
|
||||
cn0,
|
||||
MIN_VALUE_CN0,
|
||||
MAX_VALUE_CN0,
|
||||
minTextViewMarginPx.toFloat(),
|
||||
maxTextViewMarginPx.toFloat()
|
||||
).toInt()
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the margins for a given view
|
||||
*
|
||||
* @param v View to set the margin for
|
||||
* @param l left margin, in pixels
|
||||
* @param t top margin, in pixels
|
||||
* @param r right margin, in pixels
|
||||
* @param b bottom margin, in pixels
|
||||
*/
|
||||
fun setMargins(v: View, l: Int, t: Int, r: Int, b: Int) {
|
||||
val p = v.layoutParams as MarginLayoutParams
|
||||
p.setMargins(l, t, r, b)
|
||||
v.layoutParams = p
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens email apps based on the given email address
|
||||
* @param email address
|
||||
* @param location string that shows the current location
|
||||
* @param signalInfoViewModel view model that contains state of GNSS
|
||||
*/
|
||||
fun sendEmail(
|
||||
context: Context,
|
||||
email: String,
|
||||
location: String?,
|
||||
signalInfoViewModel: SignalInfoViewModel,
|
||||
playServicesVersion: String,
|
||||
prefs: SharedPreferences
|
||||
) {
|
||||
val locationManager = context.getSystemService(Context.LOCATION_SERVICE) as LocationManager
|
||||
val pm = context.packageManager
|
||||
val appInfo: PackageInfo
|
||||
val body = StringBuilder()
|
||||
body.append(context.getString(R.string.feedback_body))
|
||||
var versionName: String? = ""
|
||||
var versionCode = 0
|
||||
try {
|
||||
appInfo = pm.getPackageInfo(
|
||||
context.packageName,
|
||||
PackageManager.GET_META_DATA
|
||||
)
|
||||
versionName = appInfo.versionName
|
||||
versionCode = appInfo.versionCode
|
||||
} catch (e: PackageManager.NameNotFoundException) {
|
||||
// Leave version as empty string
|
||||
}
|
||||
|
||||
// App version
|
||||
body.append("App version: v")
|
||||
.append(versionName)
|
||||
.append(" (")
|
||||
.append(versionCode)
|
||||
.append(
|
||||
"""
|
||||
-${BuildConfig.FLAVOR})
|
||||
|
||||
""".trimIndent()
|
||||
)
|
||||
|
||||
// Device properties
|
||||
body.append(
|
||||
"""
|
||||
Model: ${Build.MODEL}
|
||||
|
||||
""".trimIndent()
|
||||
)
|
||||
body.append(
|
||||
"""Android version: ${Build.VERSION.RELEASE} / ${Build.VERSION.SDK_INT}
|
||||
"""
|
||||
)
|
||||
if (!TextUtils.isEmpty(location)) {
|
||||
body.append("Location: $location\n")
|
||||
}
|
||||
body.append(
|
||||
"""
|
||||
GNSS HW year: ${IOUtils.getGnssHardwareYear(context)}
|
||||
|
||||
""".trimIndent()
|
||||
)
|
||||
if (!IOUtils.getGnssHardwareModelName(context).trim { it <= ' ' }.isEmpty()) {
|
||||
body.append(
|
||||
"""
|
||||
GNSS HW name: ${IOUtils.getGnssHardwareModelName(context)}
|
||||
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
||||
|
||||
// Raw GNSS measurement capability
|
||||
var capability = prefs.getInt(
|
||||
context.getString(R.string.capability_key_raw_measurements),
|
||||
PreferenceUtils.CAPABILITY_UNKNOWN
|
||||
)
|
||||
if (capability != PreferenceUtils.CAPABILITY_UNKNOWN) {
|
||||
body.append(
|
||||
context.getString(
|
||||
R.string.capability_title_raw_measurements,
|
||||
PreferenceUtils.getCapabilityDescription(context, capability)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
// Navigation messages capability
|
||||
capability = prefs.getInt(
|
||||
context.getString(R.string.capability_key_nav_messages),
|
||||
PreferenceUtils.CAPABILITY_UNKNOWN
|
||||
)
|
||||
if (capability != PreferenceUtils.CAPABILITY_UNKNOWN) {
|
||||
body.append(
|
||||
context.getString(
|
||||
R.string.capability_title_nav_messages,
|
||||
PreferenceUtils.getCapabilityDescription(context, capability)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
// NMEA capability
|
||||
capability = prefs.getInt(
|
||||
context.getString(R.string.capability_key_nmea),
|
||||
PreferenceUtils.CAPABILITY_UNKNOWN
|
||||
)
|
||||
if (capability != PreferenceUtils.CAPABILITY_UNKNOWN) {
|
||||
body.append(
|
||||
context.getString(
|
||||
R.string.capability_title_nmea,
|
||||
PreferenceUtils.getCapabilityDescription(context, capability)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
// Inject PSDS capability
|
||||
capability = prefs.getInt(
|
||||
context.getString(R.string.capability_key_inject_psds),
|
||||
PreferenceUtils.CAPABILITY_UNKNOWN
|
||||
)
|
||||
if (capability != PreferenceUtils.CAPABILITY_UNKNOWN) {
|
||||
body.append(
|
||||
context.getString(
|
||||
R.string.capability_title_inject_psds,
|
||||
PreferenceUtils.getCapabilityDescription(context, capability)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
// Inject time capability
|
||||
capability = prefs.getInt(
|
||||
context.getString(R.string.capability_key_inject_time),
|
||||
PreferenceUtils.CAPABILITY_UNKNOWN
|
||||
)
|
||||
if (capability != PreferenceUtils.CAPABILITY_UNKNOWN) {
|
||||
body.append(
|
||||
context.getString(
|
||||
R.string.capability_title_inject_time,
|
||||
PreferenceUtils.getCapabilityDescription(context, capability)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
// Delete assist capability
|
||||
capability = prefs.getInt(
|
||||
context.getString(R.string.capability_key_delete_assist),
|
||||
PreferenceUtils.CAPABILITY_UNKNOWN
|
||||
)
|
||||
if (capability != PreferenceUtils.CAPABILITY_UNKNOWN) {
|
||||
body.append(
|
||||
context.getString(
|
||||
R.string.capability_title_delete_assist,
|
||||
PreferenceUtils.getCapabilityDescription(context, capability)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
// Got fix
|
||||
body.append(
|
||||
context.getString(
|
||||
R.string.capability_title_got_fix,
|
||||
location != null && signalInfoViewModel.gotFirstFix()
|
||||
)
|
||||
)
|
||||
|
||||
// We need a fix to determine these attributes reliably
|
||||
if (location != null && signalInfoViewModel.gotFirstFix()) {
|
||||
// Dual frequency
|
||||
body.append(
|
||||
context.getString(
|
||||
R.string.capability_title_dual_frequency,
|
||||
PreferenceUtils.getCapabilityDescription(context, signalInfoViewModel.isNonPrimaryCarrierFreqInView)
|
||||
)
|
||||
)
|
||||
// Supported GNSS
|
||||
val gnss: List<GnssType> = ArrayList(signalInfoViewModel.getSupportedGnss())
|
||||
Collections.sort(gnss)
|
||||
body.append(
|
||||
context.getString(
|
||||
R.string.capability_title_supported_gnss, IOUtils.trimEnds(
|
||||
IOUtils.replaceNavstar(gnss.toString())
|
||||
)
|
||||
)
|
||||
)
|
||||
// GNSS CF
|
||||
val gnssCfs: List<String> = ArrayList(signalInfoViewModel.getSupportedGnssCfs())
|
||||
if (!gnssCfs.isEmpty()) {
|
||||
Collections.sort(gnssCfs)
|
||||
body.append(
|
||||
context.getString(
|
||||
R.string.capability_title_gnss_cf,
|
||||
IOUtils.trimEnds(gnssCfs.toString())
|
||||
)
|
||||
)
|
||||
}
|
||||
// Supported SBAS
|
||||
val sbas: List<SbasType> = ArrayList(signalInfoViewModel.getSupportedSbas())
|
||||
if (!sbas.isEmpty()) {
|
||||
Collections.sort(sbas)
|
||||
body.append(
|
||||
context.getString(
|
||||
R.string.capability_title_supported_sbas,
|
||||
IOUtils.trimEnds(sbas.toString())
|
||||
)
|
||||
)
|
||||
}
|
||||
// SBAS CF
|
||||
val sbasCfs: List<String> = ArrayList(signalInfoViewModel.getSupportedSbasCfs())
|
||||
if (!sbasCfs.isEmpty()) {
|
||||
Collections.sort(sbasCfs)
|
||||
body.append(
|
||||
context.getString(
|
||||
R.string.capability_title_sbas_cf,
|
||||
IOUtils.trimEnds(sbasCfs.toString())
|
||||
)
|
||||
)
|
||||
}
|
||||
// Accumulated delta range
|
||||
body.append(
|
||||
context.getString(
|
||||
R.string.capability_title_accumulated_delta_range,
|
||||
PreferenceUtils.getCapabilityDescription(
|
||||
context,
|
||||
prefs.getInt(
|
||||
context.getString(R.string.capability_key_measurement_delta_range),
|
||||
PreferenceUtils.CAPABILITY_UNKNOWN
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
// Automatic gain control
|
||||
body.append(
|
||||
context.getString(
|
||||
R.string.capability_title_automatic_gain_control,
|
||||
PreferenceUtils.getCapabilityDescription(
|
||||
context,
|
||||
prefs.getInt(
|
||||
context.getString(R.string.capability_key_measurement_automatic_gain_control),
|
||||
PreferenceUtils.CAPABILITY_UNKNOWN
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
// GNSS Antenna Info
|
||||
val gnssAntennaInfo = context.getString(
|
||||
R.string.capability_title_gnss_antenna_info,
|
||||
PreferenceUtils.getCapabilityDescription(
|
||||
context,
|
||||
SatelliteUtils.isGnssAntennaInfoSupported(
|
||||
locationManager
|
||||
)
|
||||
)
|
||||
)
|
||||
body.append(gnssAntennaInfo)
|
||||
if (gnssAntennaInfo == context.getString(R.string.capability_value_supported)) {
|
||||
body.append(
|
||||
context.getString(
|
||||
R.string.capability_title_num_antennas, PreferenceUtils.getInt(
|
||||
context.getString(R.string.capability_key_num_antenna), -1, prefs
|
||||
)
|
||||
)
|
||||
)
|
||||
body.append(
|
||||
context.getString(
|
||||
R.string.capability_title_antenna_cfs, PreferenceUtils.getString(
|
||||
context.getString(R.string.capability_key_antenna_cf), prefs
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
if (!TextUtils.isEmpty(playServicesVersion)) {
|
||||
body.append(
|
||||
"""
|
||||
|
||||
$playServicesVersion
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
||||
body.append("\n\n\n")
|
||||
val send = Intent(Intent.ACTION_SENDTO)
|
||||
send.data = Uri.parse("mailto:")
|
||||
send.putExtra(Intent.EXTRA_EMAIL, arrayOf(email))
|
||||
val subject = context.getString(R.string.feedback_subject)
|
||||
send.putExtra(Intent.EXTRA_SUBJECT, subject)
|
||||
send.putExtra(Intent.EXTRA_TEXT, body.toString())
|
||||
try {
|
||||
context.startActivity(Intent.createChooser(send, subject))
|
||||
} catch (e: ActivityNotFoundException) {
|
||||
Toast.makeText(context, R.string.feedback_error, Toast.LENGTH_LONG)
|
||||
.show()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the provided latitude or longitude value in Degrees Minutes Seconds (DMS) format
|
||||
* @param coordinate latitude or longitude to convert to DMS format
|
||||
* @param coordinateType whether the coordinate is latitude or longitude
|
||||
* @return the provided latitude or longitude value in Degrees Minutes Seconds (DMS) format
|
||||
*/
|
||||
@JvmStatic
|
||||
fun getDMSFromLocation(
|
||||
context: Context,
|
||||
coordinate: Double,
|
||||
coordinateType: CoordinateType
|
||||
): String {
|
||||
val loc = BigDecimal(coordinate)
|
||||
val degrees = loc.setScale(0, RoundingMode.DOWN)
|
||||
val minTemp = loc.subtract(degrees).multiply(BigDecimal(60)).abs()
|
||||
val minutes = minTemp.setScale(0, RoundingMode.DOWN)
|
||||
val seconds =
|
||||
minTemp.subtract(minutes).multiply(BigDecimal(60)).setScale(2, RoundingMode.HALF_UP)
|
||||
val hemisphere: String
|
||||
val output_string: Int
|
||||
if (coordinateType == CoordinateType.LATITUDE) {
|
||||
hemisphere = if (coordinate < 0) "S" else "N"
|
||||
output_string = R.string.gps_lat_dms_value
|
||||
} else {
|
||||
hemisphere = if (coordinate < 0) "W" else "E"
|
||||
output_string = R.string.gps_lon_dms_value
|
||||
}
|
||||
return context.getString(
|
||||
output_string,
|
||||
hemisphere,
|
||||
degrees.abs().toInt(),
|
||||
minutes.toInt(),
|
||||
seconds.toFloat()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the provided latitude or longitude value in Decimal Degree Minutes (DDM) format
|
||||
*
|
||||
* @param coordinate latitude or longitude to convert to DDM format
|
||||
* @param coordinateType lat or lon to format hemisphere
|
||||
* @return the provided latitude or longitude value in Decimal Degree Minutes (DDM) format
|
||||
*/
|
||||
@JvmStatic
|
||||
fun getDDMFromLocation(
|
||||
context: Context,
|
||||
coordinate: Double,
|
||||
coordinateType: CoordinateType
|
||||
): String {
|
||||
val loc = BigDecimal(coordinate)
|
||||
val degrees = loc.setScale(0, RoundingMode.DOWN)
|
||||
val minutes =
|
||||
loc.subtract(degrees).multiply(BigDecimal(60)).abs().setScale(3, RoundingMode.HALF_UP)
|
||||
val hemisphere: String
|
||||
val output_string: Int
|
||||
if (coordinateType == CoordinateType.LATITUDE) {
|
||||
hemisphere = if (coordinate < 0) "S" else "N"
|
||||
output_string = R.string.gps_lat_ddm_value
|
||||
} else {
|
||||
hemisphere = if (coordinate < 0) "W" else "E"
|
||||
output_string = R.string.gps_lon_ddm_value
|
||||
}
|
||||
return context.getString(
|
||||
output_string,
|
||||
hemisphere,
|
||||
degrees.abs().toInt(),
|
||||
minutes.toFloat()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the provide value in meters to the corresponding value in feet
|
||||
* @param meters value in meters to convert to feet
|
||||
* @return the provided meters value converted to feet
|
||||
*/
|
||||
@JvmStatic
|
||||
fun toFeet(meters: Double): Double {
|
||||
return meters * 1000.0 / 25.4 / 12.0
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the provide value in meters per second to the corresponding value in kilometers per hour
|
||||
* @param metersPerSecond value in meters per second to convert to kilometers per hour
|
||||
* @return the provided meters per second value converted to kilometers per hour
|
||||
*/
|
||||
@JvmStatic
|
||||
fun toKilometersPerHour(metersPerSecond: Float): Float {
|
||||
return metersPerSecond * 3600f / 1000f
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the provide value in meters per second to the corresponding value in miles per hour
|
||||
* @param metersPerSecond value in meters per second to convert to miles per hour
|
||||
* @return the provided meters per second value converted to miles per hour
|
||||
*/
|
||||
@JvmStatic
|
||||
fun toMilesPerHour(metersPerSecond: Float): Float {
|
||||
return toKilometersPerHour(metersPerSecond) / 1.6093440f
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the vertical bias for a provided view that is within a ConstraintLayout
|
||||
* @param view view within a ConstraintLayout
|
||||
* @param bias vertical bias to be used
|
||||
*/
|
||||
@JvmStatic
|
||||
fun setVerticalBias(view: View, bias: Float) {
|
||||
val params = view.layoutParams as ConstraintLayout.LayoutParams
|
||||
params.verticalBias = bias
|
||||
view.layoutParams = params
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the provided location based on the provided coordinate format, and sets the provided
|
||||
* Views (textView, chips) accordingly if views are provided, and returns the string value.
|
||||
*
|
||||
* @param location location to be formatted
|
||||
* @param textView View to be set with the selected coordinateFormat
|
||||
* @param includeAltitude true if altitude should be included, false if it should not
|
||||
* @param chipDecimalDegrees View to be set as checked if "dd" is the coordinateFormat
|
||||
* @param chipDMS View to be set as checked if "dms" is the coordinateFormat
|
||||
* @param chipDegreesDecimalMin View to be set as checked if "ddm" is the coordinateFormat
|
||||
* @param coordinateFormat dd, dms, or ddm
|
||||
* @return the provided location based on the provided coordinate format
|
||||
*/
|
||||
fun formatLocationForDisplay(
|
||||
context: Context,
|
||||
location: Location?,
|
||||
textView: TextView?,
|
||||
includeAltitude: Boolean,
|
||||
chipDecimalDegrees: Chip?,
|
||||
chipDMS: Chip?,
|
||||
chipDegreesDecimalMin: Chip?,
|
||||
coordinateFormat: String?
|
||||
): String {
|
||||
var formattedLocation = ""
|
||||
when (coordinateFormat) {
|
||||
"dd" -> {
|
||||
// Decimal degrees
|
||||
if (location != null) {
|
||||
formattedLocation = IOUtils.createLocationShare(location, includeAltitude)
|
||||
}
|
||||
if (chipDecimalDegrees != null) {
|
||||
chipDecimalDegrees.isChecked = true
|
||||
}
|
||||
}
|
||||
"dms" -> {
|
||||
// Degrees minutes seconds
|
||||
if (location != null) {
|
||||
formattedLocation = IOUtils.createLocationShare(
|
||||
getDMSFromLocation(context, location.latitude, CoordinateType.LATITUDE),
|
||||
getDMSFromLocation(context, location.longitude, CoordinateType.LONGITUDE),
|
||||
if (location.hasAltitude() && includeAltitude) location.altitude.toString() else null
|
||||
)
|
||||
}
|
||||
if (chipDMS != null) {
|
||||
chipDMS.isChecked = true
|
||||
}
|
||||
}
|
||||
"ddm" -> {
|
||||
// Degrees decimal minutes
|
||||
if (location != null) {
|
||||
formattedLocation = IOUtils.createLocationShare(
|
||||
getDDMFromLocation(context, location.latitude, CoordinateType.LATITUDE),
|
||||
getDDMFromLocation(context, location.longitude, CoordinateType.LONGITUDE),
|
||||
if (location.hasAltitude() && includeAltitude) location.altitude.toString() else null
|
||||
)
|
||||
}
|
||||
if (chipDegreesDecimalMin != null) {
|
||||
chipDegreesDecimalMin.isChecked = true
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
// Decimal degrees
|
||||
formattedLocation = IOUtils.createLocationShare(location, includeAltitude)
|
||||
if (chipDecimalDegrees != null) {
|
||||
chipDecimalDegrees.isChecked = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if (textView != null) {
|
||||
textView.text = formattedLocation
|
||||
}
|
||||
return formattedLocation
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the activity title so the locale is updated
|
||||
*
|
||||
* @param a the activity to reset the title for
|
||||
*/
|
||||
@JvmStatic
|
||||
fun resetActivityTitle(a: Activity) {
|
||||
try {
|
||||
val info =
|
||||
a.packageManager.getActivityInfo(a.componentName, PackageManager.GET_META_DATA)
|
||||
if (info.labelRes != 0) {
|
||||
a.setTitle(info.labelRes)
|
||||
}
|
||||
} catch (e: PackageManager.NameNotFoundException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the app is running on a large screen device, false if it is not
|
||||
*
|
||||
* @return true if the app is running on a large screen device, false if it is not
|
||||
*/
|
||||
fun isLargeScreen(context: Context): Boolean {
|
||||
return (context.resources.configuration.screenLayout
|
||||
and Configuration.SCREENLAYOUT_SIZE_MASK) >= Configuration.SCREENLAYOUT_SIZE_XLARGE
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the display name for the given GnssType
|
||||
* @param context
|
||||
* @param gnssType
|
||||
* @return the display name for the given GnssType
|
||||
*/
|
||||
fun getGnssDisplayName(context: Context, gnssType: GnssType?): String {
|
||||
return when (gnssType) {
|
||||
GnssType.NAVSTAR -> context.resources.getString(R.string.sky_legend_shape_navstar)
|
||||
GnssType.GALILEO -> context.resources.getString(R.string.sky_legend_shape_galileo)
|
||||
GnssType.GLONASS -> context.resources.getString(R.string.sky_legend_shape_glonass)
|
||||
GnssType.BEIDOU -> context.resources.getString(R.string.sky_legend_shape_beidou)
|
||||
GnssType.QZSS -> context.resources.getString(R.string.sky_legend_shape_qzss)
|
||||
GnssType.IRNSS -> context.resources.getString(R.string.sky_legend_shape_irnss)
|
||||
GnssType.SBAS -> context.resources.getString(R.string.sbas)
|
||||
GnssType.UNKNOWN -> context.resources.getString(R.string.unknown)
|
||||
else -> context.resources.getString(R.string.unknown)
|
||||
}
|
||||
}
|
||||
|
||||
fun setClickableSpan(v: TextView, span: ClickableSpan?) {
|
||||
val text = v.text as Spannable
|
||||
text.setSpan(span, 0, text.length, 0)
|
||||
v.movementMethod = LinkMovementMethod.getInstance()
|
||||
}
|
||||
|
||||
fun removeAllClickableSpans(v: TextView) {
|
||||
val text = v.text as Spannable
|
||||
val spans = text.getSpans(0, text.length, ClickableSpan::class.java)
|
||||
for (cs in spans) {
|
||||
text.removeSpan(cs)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows a view using animation
|
||||
*
|
||||
* @param v View to show
|
||||
* @param animationDuration duration of animation
|
||||
*/
|
||||
fun showViewWithAnimation(v: View, animationDuration: Int) {
|
||||
if (v.visibility == View.VISIBLE && v.alpha == 1f) {
|
||||
// View is already visible and not transparent, return without doing anything
|
||||
return
|
||||
}
|
||||
v.clearAnimation()
|
||||
v.animate().cancel()
|
||||
if (v.visibility != View.VISIBLE) {
|
||||
// Set the content view to 0% opacity but visible, so that it is visible
|
||||
// (but fully transparent) during the animation.
|
||||
v.alpha = 0f
|
||||
v.visibility = View.VISIBLE
|
||||
}
|
||||
|
||||
// Animate the content view to 100% opacity, and clear any animation listener set on the view.
|
||||
v.animate()
|
||||
.alpha(1f)
|
||||
.setDuration(animationDuration.toLong())
|
||||
.setListener(null)
|
||||
}
|
||||
|
||||
/**
|
||||
* Hides a view using animation
|
||||
*
|
||||
* @param v View to hide
|
||||
* @param animationDuration duration of animation
|
||||
*/
|
||||
fun hideViewWithAnimation(v: View, animationDuration: Int) {
|
||||
if (v.visibility == View.GONE) {
|
||||
// View is already gone, return without doing anything
|
||||
return
|
||||
}
|
||||
v.clearAnimation()
|
||||
v.animate().cancel()
|
||||
|
||||
// Animate the view to 0% opacity. After the animation ends, set its visibility to GONE as
|
||||
// an optimization step (it won't participate in layout passes, etc.)
|
||||
v.animate()
|
||||
.alpha(0f)
|
||||
.setDuration(animationDuration.toLong())
|
||||
.setListener(object : AnimatorListenerAdapter() {
|
||||
override fun onAnimationEnd(animation: Animator) {
|
||||
v.visibility = View.GONE
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows the dialog to explain why location permissions are needed
|
||||
*
|
||||
* NOTE - this dialog can't be managed under the old dialog framework as the method
|
||||
* ActivityCompat.shouldShowRequestPermissionRationale() always returns false.
|
||||
*/
|
||||
fun showLocationPermissionDialog(activity: Activity) {
|
||||
val builder = AlertDialog.Builder(activity)
|
||||
.setTitle(R.string.title_location_permission)
|
||||
.setMessage(R.string.text_location_permission)
|
||||
.setCancelable(false)
|
||||
.setPositiveButton(
|
||||
R.string.ok
|
||||
) { dialog: DialogInterface?, which: Int ->
|
||||
// Request permissions from the user
|
||||
ActivityCompat.requestPermissions(
|
||||
activity,
|
||||
arrayOf(
|
||||
Manifest.permission.ACCESS_FINE_LOCATION,
|
||||
Manifest.permission.ACCESS_COARSE_LOCATION
|
||||
),
|
||||
PermissionUtils.LOCATION_PERMISSION_REQUEST
|
||||
)
|
||||
}
|
||||
.setNegativeButton(
|
||||
R.string.exit
|
||||
) { dialog: DialogInterface?, which: Int ->
|
||||
// Exit app
|
||||
activity.finish()
|
||||
}
|
||||
builder.create().show()
|
||||
}
|
||||
|
||||
/**
|
||||
* Ask the user if they want to enable GPS, and if so, show them system settings
|
||||
*/
|
||||
fun promptEnableGps(context: Context,activity: Activity) {
|
||||
AlertDialog.Builder(activity)
|
||||
.setMessage(context.getString(R.string.enable_gps_message))
|
||||
.setPositiveButton(
|
||||
context.getString(R.string.enable_gps_positive_button)
|
||||
) { dialog: DialogInterface?, which: Int ->
|
||||
val intent = Intent(
|
||||
Settings.ACTION_LOCATION_SOURCE_SETTINGS
|
||||
)
|
||||
activity.startActivity(intent)
|
||||
}
|
||||
.setNegativeButton(
|
||||
context.getString(R.string.enable_gps_negative_button)
|
||||
) { dialog: DialogInterface?, which: Int -> }
|
||||
.show()
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Saves the "sort by" order to preferences
|
||||
*
|
||||
* @param index the index of R.array.sort_sats that should be set
|
||||
*/
|
||||
public fun setSortByClause(context: Context, index: Int, prefs: SharedPreferences) {
|
||||
val sortOptions = context.resources.getStringArray(R.array.sort_sats)
|
||||
PreferenceUtils.saveString(
|
||||
context.resources.getString(R.string.pref_key_default_sat_sort),
|
||||
sortOptions[index],
|
||||
prefs
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the "What's New" message if a new version was just installed
|
||||
*/
|
||||
fun autoShowWhatsNew(prefs: SharedPreferences, context: Context, activity: Activity) {
|
||||
// Get the current app version.
|
||||
val appInfo: PackageInfo = try {
|
||||
context.packageManager.getPackageInfo(
|
||||
context.packageName,
|
||||
PackageManager.GET_META_DATA
|
||||
)
|
||||
} catch (e: PackageManager.NameNotFoundException) {
|
||||
// Do nothing
|
||||
return
|
||||
}
|
||||
val oldVer = prefs.getInt(WHATS_NEW_VER, 0)
|
||||
val newVer = appInfo.versionCode
|
||||
if (oldVer < newVer) {
|
||||
activity.showDialog(WHATSNEW_DIALOG)
|
||||
PreferenceUtils.saveInt(WHATS_NEW_VER, appInfo.versionCode, prefs)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the `location` object as a human readable string for use in a notification summary
|
||||
*/
|
||||
fun Location?.toNotificationSummary(context: Context, prefs: SharedPreferences): String {
|
||||
return if (this != null) {
|
||||
val lat = FormatUtils.formatLatOrLon(context, latitude, CoordinateType.LATITUDE, prefs)
|
||||
val lon = FormatUtils.formatLatOrLon(context, longitude, CoordinateType.LONGITUDE, prefs)
|
||||
val alt = FormatUtils.formatAltitude(context, this, prefs)
|
||||
val speed = FormatUtils.formatSpeed(context,this, prefs)
|
||||
val bearing = FormatUtils.formatBearing(context,this)
|
||||
"$lat $lon $alt | $speed | $bearing"
|
||||
} else {
|
||||
"Unknown location"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
* Based on https://github.com/YarikSOffice/LanguageTest/blob/master/app/src/main/java/com/yariksoffice/languagetest/Utility.java
|
||||
* Licensed under MIT - https://github.com/YarikSOffice/LanguageTest/blob/master/LICENSE
|
||||
*/
|
||||
package com.android.gpstest.util;
|
||||
package com.android.gpstest.library.util;
|
||||
|
||||
import android.os.Build;
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.android.gpstest.util;
|
||||
package com.android.gpstest.library.util;
|
||||
|
||||
import android.location.Location;
|
||||
import android.os.SystemClock;
|
||||
@@ -14,7 +14,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.android.gpstest.util;
|
||||
package com.android.gpstest.library.util;
|
||||
|
||||
import android.util.Base64;
|
||||
|
||||
@@ -13,12 +13,12 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.android.gpstest.util;
|
||||
package com.android.gpstest.library.util;
|
||||
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.gpstest.model.DilutionOfPrecision;
|
||||
import com.android.gpstest.library.model.DilutionOfPrecision;
|
||||
|
||||
public class NmeaUtils {
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.android.gpstest.util
|
||||
package com.android.gpstest.library.util
|
||||
|
||||
import android.Manifest
|
||||
import android.content.Context
|
||||
@@ -13,7 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.android.gpstest.util;
|
||||
package com.android.gpstest.library.util;
|
||||
|
||||
import android.Manifest;
|
||||
import android.app.Activity;
|
||||
@@ -0,0 +1,283 @@
|
||||
package com.android.gpstest.library.util
|
||||
|
||||
import android.app.Application
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import android.location.GnssMeasurementsEvent
|
||||
import android.location.LocationManager
|
||||
import android.os.Build
|
||||
import com.android.gpstest.library.R
|
||||
|
||||
/**
|
||||
* Provides access to SharedPreferences to Activities and Services.
|
||||
*/
|
||||
object PreferenceUtil {
|
||||
const val SECONDS_TO_MILLISECONDS = 1000
|
||||
|
||||
val METERS = "1"
|
||||
val METERS_PER_SECOND = "1"
|
||||
val KILOMETERS_PER_HOUR = "2"
|
||||
|
||||
/**
|
||||
* Returns the minTime between location updates used for the LocationListener in milliseconds
|
||||
*/
|
||||
fun minTimeMillis(context: Context, prefs: SharedPreferences): Long {
|
||||
val minTimeDouble: Double =
|
||||
prefs.getString(context.getString(R.string.pref_key_gps_min_time), "1")
|
||||
?.toDouble() ?: 1.0
|
||||
return (minTimeDouble * SECONDS_TO_MILLISECONDS).toLong()
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the minDistance between location updates used for the LocationLitsener in meters
|
||||
*/
|
||||
fun minDistance(context: Context, prefs: SharedPreferences): Float {
|
||||
return prefs.getString(context.getString(R.string.pref_key_gps_min_distance), "0") ?.toFloat() ?: 0.0f
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the user has selected to write locations to file output, false if they have not
|
||||
*/
|
||||
fun writeLocationToFile(context: Context, prefs: SharedPreferences): Boolean {
|
||||
return prefs.getBoolean(context.getString(R.string.pref_key_file_location_output), false)
|
||||
}
|
||||
|
||||
fun writeMeasurementToLogcat(context: Context, prefs: SharedPreferences): Boolean {
|
||||
return prefs.getBoolean(context.getString(R.string.pref_key_as_measurement_output), false)
|
||||
}
|
||||
|
||||
fun writeMeasurementsToFile(context: Context, prefs: SharedPreferences): Boolean {
|
||||
return prefs.getBoolean(context.getString(R.string.pref_key_file_measurement_output), false)
|
||||
}
|
||||
|
||||
fun writeNmeaToAndroidMonitor(context: Context, prefs: SharedPreferences): Boolean {
|
||||
return prefs.getBoolean(context.getString(R.string.pref_key_as_nmea_output), true)
|
||||
}
|
||||
|
||||
fun writeNmeaTimestampToLogcat(context: Context, prefs: SharedPreferences): Boolean {
|
||||
return prefs.getBoolean(context.getString(R.string.pref_key_as_nmea_timestamp_output), true)
|
||||
}
|
||||
|
||||
fun writeNmeaToFile(context: Context, prefs: SharedPreferences): Boolean {
|
||||
return prefs.getBoolean(context.getString(R.string.pref_key_file_nmea_output), false)
|
||||
}
|
||||
|
||||
fun writeAntennaInfoToFileJson(context: Context, prefs: SharedPreferences): Boolean {
|
||||
return prefs.getBoolean(context.getString(R.string.pref_key_file_antenna_output_json), false);
|
||||
}
|
||||
|
||||
fun writeAntennaInfoToFileCsv(context: Context, prefs: SharedPreferences): Boolean {
|
||||
return prefs.getBoolean(context.getString(R.string.pref_key_file_antenna_output_csv), false)
|
||||
}
|
||||
|
||||
fun writeNavMessageToLogcat(context: Context, prefs: SharedPreferences): Boolean {
|
||||
return prefs.getBoolean(context.getString(R.string.pref_key_as_navigation_message_output), false);
|
||||
}
|
||||
|
||||
fun writeNavMessageToFile(context: Context, prefs: SharedPreferences): Boolean {
|
||||
return prefs.getBoolean(context.getString(R.string.pref_key_file_navigation_message_output), false);
|
||||
}
|
||||
|
||||
fun writeStatusToFile(context: Context, prefs: SharedPreferences): Boolean {
|
||||
return prefs.getBoolean(context.getString(R.string.pref_key_file_gnss_status_output), false);
|
||||
}
|
||||
|
||||
fun writeOrientationToFile(context: Context, prefs: SharedPreferences): Boolean {
|
||||
return prefs.getBoolean(context.getString(R.string.pref_key_file_orientation_output), false);
|
||||
}
|
||||
|
||||
fun injectTimeWhenLogging(context: Context, prefs: SharedPreferences): Boolean {
|
||||
return prefs.getBoolean(context.getString(R.string.pref_key_inject_time_when_logging), true);
|
||||
}
|
||||
|
||||
fun injectPsdsWhenLogging(context: Context, prefs: SharedPreferences): Boolean {
|
||||
return prefs.getBoolean(context.getString(R.string.pref_key_inject_psds_when_logging), true);
|
||||
}
|
||||
|
||||
fun isFileLoggingEnabled(context: Context, prefs: SharedPreferences): Boolean {
|
||||
return isCsvLoggingEnabled(context, prefs) || isJsonLoggingEnabled(context, prefs)
|
||||
}
|
||||
|
||||
fun isCsvLoggingEnabled(context: Context, prefs: SharedPreferences): Boolean {
|
||||
return writeNmeaToFile(context, prefs) || writeMeasurementsToFile(context, prefs) || writeNavMessageToFile(context, prefs) || writeLocationToFile(context, prefs) || writeAntennaInfoToFileCsv(context, prefs) || writeStatusToFile(context, prefs) || writeOrientationToFile(context, prefs)
|
||||
}
|
||||
|
||||
fun isJsonLoggingEnabled(context: Context, prefs: SharedPreferences): Boolean {
|
||||
return writeAntennaInfoToFileJson(context, prefs)
|
||||
}
|
||||
|
||||
fun distanceUnits(context: Context, prefs: SharedPreferences): String {
|
||||
return prefs.getString(context.getString(R.string.pref_key_preferred_distance_units_v2), METERS) ?: METERS
|
||||
}
|
||||
|
||||
fun speedUnits(context: Context, prefs: SharedPreferences): String {
|
||||
return prefs.getString(context.getString(R.string.pref_key_preferred_speed_units_v2), METERS_PER_SECOND) ?: METERS_PER_SECOND
|
||||
}
|
||||
|
||||
fun coordinateFormat(context: Context, prefs: SharedPreferences): String {
|
||||
return prefs.getString(
|
||||
context.getString(R.string.pref_key_coordinate_format),
|
||||
context.getString(R.string.preferences_coordinate_format_dd_key)
|
||||
) ?: context.getString(R.string.preferences_coordinate_format_dd_key)
|
||||
}
|
||||
|
||||
fun runInBackground(context: Context, prefs: SharedPreferences): Boolean {
|
||||
return prefs.getBoolean(context.getString(R.string.pref_key_gnss_background), false)
|
||||
}
|
||||
|
||||
fun darkTheme(context: Context, prefs: SharedPreferences): Boolean {
|
||||
return prefs.getBoolean(context.getString(R.string.pref_key_dark_theme), false)
|
||||
}
|
||||
|
||||
fun shareIncludeAltitude(context: Context, prefs: SharedPreferences): Boolean {
|
||||
return prefs.getBoolean(context.getString(R.string.pref_key_share_include_altitude), false
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if preferences related to raw measurements should be enabled,
|
||||
* false if they should be disabled
|
||||
*/
|
||||
fun enableMeasurementsPref(context: Context, prefs: SharedPreferences): Boolean {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||
val manager = context.getSystemService(Context.LOCATION_SERVICE) as LocationManager
|
||||
return SatelliteUtils.isMeasurementsSupported(manager)
|
||||
}
|
||||
// Legacy versions before Android S
|
||||
val capabilityMeasurementsInt = prefs.getInt(
|
||||
context.getString(R.string.capability_key_raw_measurements),
|
||||
PreferenceUtils.CAPABILITY_UNKNOWN
|
||||
)
|
||||
return capabilityMeasurementsInt != PreferenceUtils.CAPABILITY_NOT_SUPPORTED
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if preferences related to navigation messages should be enabled,
|
||||
* false if they should be disabled
|
||||
*/
|
||||
fun enableNavMessagesPref(context: Context, prefs: SharedPreferences): Boolean {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||
val manager = context.getSystemService(Context.LOCATION_SERVICE) as LocationManager
|
||||
return SatelliteUtils.isNavMessagesSupported(manager)
|
||||
}
|
||||
// Legacy versions before Android S
|
||||
val capabilityNavMessagesInt = prefs.getInt(
|
||||
context.getString(R.string.capability_key_nav_messages),
|
||||
PreferenceUtils.CAPABILITY_UNKNOWN
|
||||
)
|
||||
return capabilityNavMessagesInt != PreferenceUtils.CAPABILITY_NOT_SUPPORTED
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves device capabilities for GNSS measurements and related information from the given [event]
|
||||
*/
|
||||
fun saveMeasurementCapabilities(context: Context, event: GnssMeasurementsEvent, prefs: SharedPreferences) {
|
||||
var agcSupport = PreferenceUtils.CAPABILITY_UNKNOWN
|
||||
var carrierPhaseSupport = PreferenceUtils.CAPABILITY_UNKNOWN
|
||||
// Loop through all measurements - if at least one supports, then mark as supported
|
||||
for (measurement in event.measurements) {
|
||||
if (SatelliteUtils.isAutomaticGainControlSupported(measurement)) {
|
||||
agcSupport = PreferenceUtils.CAPABILITY_SUPPORTED
|
||||
} else if (agcSupport == PreferenceUtils.CAPABILITY_UNKNOWN) {
|
||||
agcSupport = PreferenceUtils.CAPABILITY_NOT_SUPPORTED
|
||||
}
|
||||
if (SatelliteUtils.isCarrierPhaseSupported(measurement)) {
|
||||
carrierPhaseSupport = PreferenceUtils.CAPABILITY_SUPPORTED
|
||||
} else if (carrierPhaseSupport == PreferenceUtils.CAPABILITY_UNKNOWN) {
|
||||
carrierPhaseSupport = PreferenceUtils.CAPABILITY_NOT_SUPPORTED
|
||||
}
|
||||
}
|
||||
PreferenceUtils.saveInt(
|
||||
context.getString(R.string.capability_key_measurement_automatic_gain_control),
|
||||
agcSupport,
|
||||
prefs
|
||||
)
|
||||
PreferenceUtils.saveInt(
|
||||
context.getString(R.string.capability_key_measurement_delta_range),
|
||||
carrierPhaseSupport,
|
||||
prefs
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new preference listener that will invoke the provide [cancelFlows] function (e.g., to cancel jobs)
|
||||
* when the user turns off tracking via the UI.
|
||||
*
|
||||
* Returns a reference to the OnSharedPreferenceChangeListener so it can be held by the calling class, as
|
||||
* anonymous preference listeners tend to get GC'd by Android.
|
||||
*/
|
||||
fun newStopTrackingListener(cancelFlows: () -> Unit, prefs: SharedPreferences): SharedPreferences.OnSharedPreferenceChangeListener {
|
||||
return SharedPreferences.OnSharedPreferenceChangeListener { _, key ->
|
||||
if (key == PreferenceUtils.KEY_SERVICE_TRACKING_ENABLED) {
|
||||
if (!PreferenceUtils.isTrackingStarted(prefs)) {
|
||||
cancelFlows()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new preference listener that will invoke the provide [initLogging] function
|
||||
* when the user turns on file logging via any of the Settings options and all of the other file
|
||||
* logging settings were previously off
|
||||
* @param context
|
||||
* @param initLogging
|
||||
* @param prefs
|
||||
* Returns a reference to the OnSharedPreferenceChangeListener so it can be held by the calling class, as
|
||||
* anonymous preference listeners tend to get GC'd by Android.
|
||||
*/
|
||||
fun newFileLoggingListener(context: Context, initLogging: () -> Unit, prefs: SharedPreferences): SharedPreferences.OnSharedPreferenceChangeListener {
|
||||
return SharedPreferences.OnSharedPreferenceChangeListener { _, key ->
|
||||
if (key == context.getString(R.string.pref_key_file_location_output) ||
|
||||
key == context.getString(R.string.pref_key_file_measurement_output) ||
|
||||
key == context.getString(R.string.pref_key_file_nmea_output) ||
|
||||
key == context.getString(R.string.pref_key_file_navigation_message_output) ||
|
||||
key == context.getString(R.string.pref_key_file_antenna_output_csv) ||
|
||||
key == context.getString(R.string.pref_key_file_antenna_output_json)) {
|
||||
|
||||
// Count number of file logging preferences that are enabled
|
||||
val loggingEnabled = arrayOf(writeLocationToFile(context, prefs), writeMeasurementsToFile(context, prefs), writeNmeaToFile(context, prefs), writeNavMessageToFile(context, prefs), writeAntennaInfoToFileCsv(context, prefs), writeAntennaInfoToFileJson(context, prefs))
|
||||
val enabledCount = loggingEnabled.count { it }
|
||||
|
||||
if (enabledCount == 1) {
|
||||
if (key == context.getString(R.string.pref_key_file_location_output) &&
|
||||
writeLocationToFile(context, prefs)
|
||||
) {
|
||||
// Location file logging was just enabled
|
||||
initLogging()
|
||||
}
|
||||
if (key == context.getString(R.string.pref_key_file_measurement_output) &&
|
||||
writeMeasurementsToFile(context, prefs)
|
||||
) {
|
||||
// Measurement file logging was just enabled
|
||||
initLogging()
|
||||
}
|
||||
if (key == context.getString(R.string.pref_key_file_nmea_output) &&
|
||||
writeNmeaToFile(context, prefs)
|
||||
) {
|
||||
// NMEA file logging was just enabled
|
||||
initLogging()
|
||||
}
|
||||
if (key == context.getString(R.string.pref_key_file_navigation_message_output) &&
|
||||
writeNavMessageToFile(context, prefs)
|
||||
) {
|
||||
// Nav message file logging was just enabled
|
||||
initLogging()
|
||||
}
|
||||
if (key == context.getString(R.string.pref_key_file_antenna_output_csv) &&
|
||||
writeAntennaInfoToFileCsv(context, prefs)
|
||||
) {
|
||||
// Antenna CSV file logging was just enabled
|
||||
initLogging()
|
||||
}
|
||||
if (key == context.getString(R.string.pref_key_file_antenna_output_json) &&
|
||||
writeAntennaInfoToFileJson(context,prefs)
|
||||
) {
|
||||
// Antenna JSON file logging was just enabled
|
||||
initLogging()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,17 +13,16 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.android.gpstest.util;
|
||||
package com.android.gpstest.library.util;
|
||||
|
||||
import static java.util.Collections.emptySet;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.res.Resources;
|
||||
|
||||
import com.android.gpstest.Application;
|
||||
import com.android.gpstest.R;
|
||||
import com.android.gpstest.model.GnssType;
|
||||
import com.android.gpstest.library.R;
|
||||
import com.android.gpstest.library.model.GnssType;
|
||||
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
@@ -45,18 +44,18 @@ public class PreferenceUtils {
|
||||
* @param capability CAPABILITY_* constant defined in this class
|
||||
* @return a string description of the CAPABILITY_* constant
|
||||
*/
|
||||
public static String getCapabilityDescription(int capability) {
|
||||
public static String getCapabilityDescription(Context context, int capability) {
|
||||
switch (capability) {
|
||||
case CAPABILITY_UNKNOWN:
|
||||
return Application.Companion.getApp().getString(R.string.capability_value_unknown);
|
||||
return context.getString(R.string.capability_value_unknown);
|
||||
case CAPABILITY_NOT_SUPPORTED:
|
||||
return Application.Companion.getApp().getString(R.string.capability_value_not_supported);
|
||||
return context.getString(R.string.capability_value_not_supported);
|
||||
case CAPABILITY_SUPPORTED:
|
||||
return Application.Companion.getApp().getString(R.string.capability_value_supported);
|
||||
return context.getString(R.string.capability_value_supported);
|
||||
case CAPABILITY_LOCATION_DISABLED:
|
||||
return Application.Companion.getApp().getString(R.string.capability_value_location_disabled);
|
||||
return context.getString(R.string.capability_value_location_disabled);
|
||||
default:
|
||||
return Application.Companion.getApp().getString(R.string.capability_value_unknown);
|
||||
return context.getString(R.string.capability_value_unknown);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,85 +64,60 @@ public class PreferenceUtils {
|
||||
* @param supported
|
||||
* @return a simple SUPPORTED or UNSUPPORTED value for the given boolean value
|
||||
*/
|
||||
public static String getCapabilityDescription(boolean supported) {
|
||||
public static String getCapabilityDescription(Context context, boolean supported) {
|
||||
if (supported) {
|
||||
return Application.Companion.getApp().getString(R.string.capability_value_supported);
|
||||
return context.getString(R.string.capability_value_supported);
|
||||
} else {
|
||||
return Application.Companion.getApp().getString(R.string.capability_value_not_supported);
|
||||
return context.getString(R.string.capability_value_not_supported);
|
||||
}
|
||||
}
|
||||
|
||||
@TargetApi(9)
|
||||
public static void saveString(SharedPreferences prefs, String key, String value) {
|
||||
public static void saveString(String key, String value, SharedPreferences prefs) {
|
||||
SharedPreferences.Editor edit = prefs.edit();
|
||||
edit.putString(key, value);
|
||||
edit.apply();
|
||||
}
|
||||
|
||||
public static void saveString(String key, String value) {
|
||||
saveString(Application.Companion.getPrefs(), key, value);
|
||||
}
|
||||
|
||||
@TargetApi(9)
|
||||
public static void saveInt(SharedPreferences prefs, String key, int value) {
|
||||
public static void saveInt(String key, int value, SharedPreferences prefs) {
|
||||
SharedPreferences.Editor edit = prefs.edit();
|
||||
edit.putInt(key, value);
|
||||
edit.apply();
|
||||
}
|
||||
|
||||
public static void saveInt(String key, int value) {
|
||||
saveInt(Application.Companion.getPrefs(), key, value);
|
||||
}
|
||||
|
||||
public static int getInt(String key, int defaultValue) {
|
||||
return Application.Companion.getPrefs().getInt(key, defaultValue);
|
||||
public static int getInt(String key, int defaultValue, SharedPreferences prefs) {
|
||||
return prefs.getInt(key, defaultValue);
|
||||
}
|
||||
|
||||
@TargetApi(9)
|
||||
public static void saveLong(SharedPreferences prefs, String key, long value) {
|
||||
public static void saveLong(String key, long value, SharedPreferences prefs) {
|
||||
SharedPreferences.Editor edit = prefs.edit();
|
||||
edit.putLong(key, value);
|
||||
edit.apply();
|
||||
}
|
||||
|
||||
public static void saveLong(String key, long value) {
|
||||
saveLong(Application.Companion.getPrefs(), key, value);
|
||||
}
|
||||
|
||||
@TargetApi(9)
|
||||
public static void saveBoolean(SharedPreferences prefs, String key, boolean value) {
|
||||
public static void saveBoolean(String key, boolean value, SharedPreferences prefs) {
|
||||
SharedPreferences.Editor edit = prefs.edit();
|
||||
edit.putBoolean(key, value);
|
||||
edit.apply();
|
||||
}
|
||||
|
||||
public static void saveBoolean(String key, boolean value) {
|
||||
saveBoolean(Application.Companion.getPrefs(), key, value);
|
||||
}
|
||||
|
||||
@TargetApi(9)
|
||||
public static void saveFloat(SharedPreferences prefs, String key, float value) {
|
||||
public static void saveFloat(String key, float value, SharedPreferences prefs) {
|
||||
SharedPreferences.Editor edit = prefs.edit();
|
||||
edit.putFloat(key, value);
|
||||
edit.apply();
|
||||
}
|
||||
|
||||
public static void saveFloat(String key, float value) {
|
||||
saveFloat(Application.Companion.getPrefs(), key, value);
|
||||
}
|
||||
|
||||
@TargetApi(9)
|
||||
public static void saveDouble(SharedPreferences prefs, String key, double value) {
|
||||
public static void saveDouble(String key, double value, SharedPreferences prefs) {
|
||||
SharedPreferences.Editor edit = prefs.edit();
|
||||
edit.putLong(key, Double.doubleToRawLongBits(value));
|
||||
edit.apply();
|
||||
}
|
||||
|
||||
@TargetApi(9)
|
||||
public static void saveDouble(String key, double value) {
|
||||
saveDouble(Application.Companion.getPrefs(), key, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a double for the provided key from preferences, or the default value if the preference
|
||||
* doesn't currently have a value
|
||||
@@ -152,23 +126,23 @@ public class PreferenceUtils {
|
||||
* @param defaultValue the default value to return if the key doesn't have a value
|
||||
* @return a double from preferences, or the default value if it doesn't exist
|
||||
*/
|
||||
public static Double getDouble(String key, double defaultValue) {
|
||||
if (!Application.Companion.getPrefs().contains(key)) {
|
||||
public static Double getDouble(String key, double defaultValue, SharedPreferences prefs) {
|
||||
if (!prefs.contains(key)) {
|
||||
return defaultValue;
|
||||
}
|
||||
return Double.longBitsToDouble(Application.Companion.getPrefs().getLong(key, 0));
|
||||
return Double.longBitsToDouble(prefs.getLong(key, 0));
|
||||
}
|
||||
|
||||
public static String getString(String key) {
|
||||
return Application.Companion.getPrefs().getString(key, null);
|
||||
public static String getString(String key, SharedPreferences prefs) {
|
||||
return prefs.getString(key, null);
|
||||
}
|
||||
|
||||
public static long getLong(String key, long defaultValue) {
|
||||
return Application.Companion.getPrefs().getLong(key, defaultValue);
|
||||
public static long getLong(SharedPreferences prefs, String key, long defaultValue) {
|
||||
return prefs.getLong(key, defaultValue);
|
||||
}
|
||||
|
||||
public static float getFloat(String key, float defaultValue) {
|
||||
return Application.Companion.getPrefs().getFloat(key, defaultValue);
|
||||
public static float getFloat(SharedPreferences prefs, String key, float defaultValue) {
|
||||
return prefs.getFloat(key, defaultValue);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -176,9 +150,9 @@ public class PreferenceUtils {
|
||||
*
|
||||
* @return the currently selected satellite sort order as the index in R.array.sort_sats
|
||||
*/
|
||||
public static int getSatSortOrderFromPreferences() {
|
||||
Resources r = Application.Companion.getApp().getResources();
|
||||
SharedPreferences settings = Application.Companion.getPrefs();
|
||||
public static int getSatSortOrderFromPreferences(Context context, SharedPreferences prefs) {
|
||||
Resources r = context.getResources();
|
||||
SharedPreferences settings = prefs;
|
||||
String[] sortOptions = r.getStringArray(R.array.sort_sats);
|
||||
String sortPref = settings.getString(r.getString(
|
||||
R.string.pref_key_default_sat_sort), sortOptions[0]);
|
||||
@@ -194,10 +168,10 @@ public class PreferenceUtils {
|
||||
* Gets a set of GnssTypes that should have their satellites displayed that has been saved to preferences. (All are shown if empty or null)
|
||||
* @return a set of GnssTypes that should have their satellites displayed that has been saved to preferences. (All are shown if empty or null)
|
||||
*/
|
||||
public static Set<GnssType> gnssFilter() {
|
||||
public static Set<GnssType> gnssFilter(Context context, SharedPreferences prefs) {
|
||||
Set<GnssType> filter = new LinkedHashSet<>();
|
||||
Resources r = Application.Companion.getApp().getResources();
|
||||
String filterString = getString(r.getString(R.string.pref_key_default_sat_filter));
|
||||
Resources r = context.getResources();
|
||||
String filterString = getString(r.getString(R.string.pref_key_default_sat_filter), prefs);
|
||||
if (filterString == null) {
|
||||
return filter;
|
||||
}
|
||||
@@ -216,8 +190,8 @@ public class PreferenceUtils {
|
||||
* Values are persisted as string of comma-separated values, with each of the enum values .toString() called
|
||||
* @param filter a set of GnssTypes that should have their satellites displayed. (All are shown if empty or null)
|
||||
*/
|
||||
public static void saveGnssFilter(Set<GnssType> filter) {
|
||||
Resources r = Application.Companion.getApp().getResources();
|
||||
public static void saveGnssFilter(Context context, Set<GnssType> filter, SharedPreferences prefs) {
|
||||
Resources r = context.getResources();
|
||||
StringBuilder filterString = new StringBuilder();
|
||||
for (GnssType gnssType : filter) {
|
||||
filterString.append(gnssType.toString() + ",");
|
||||
@@ -226,22 +200,22 @@ public class PreferenceUtils {
|
||||
if (filter.size() >= 1) {
|
||||
filterString.deleteCharAt(filterString.length() - 1);
|
||||
}
|
||||
saveString(r.getString(R.string.pref_key_default_sat_filter), filterString.toString());
|
||||
saveString(r.getString(R.string.pref_key_default_sat_filter), filterString.toString(), prefs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears any active GNSS filter so all satellites are displayed
|
||||
*/
|
||||
public static void clearGnssFilter() {
|
||||
saveGnssFilter(emptySet());
|
||||
public static void clearGnssFilter(Context context, SharedPreferences prefs) {
|
||||
saveGnssFilter(context, emptySet(), prefs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the specified preference by deleting it
|
||||
* @param key
|
||||
*/
|
||||
public static void remove(String key) {
|
||||
SharedPreferences.Editor edit = Application.Companion.getPrefs().edit();
|
||||
public static void remove(String key, SharedPreferences prefs) {
|
||||
SharedPreferences.Editor edit = prefs.edit();
|
||||
edit.remove(key).apply();
|
||||
}
|
||||
|
||||
@@ -249,15 +223,15 @@ public class PreferenceUtils {
|
||||
* Returns true if service location tracking is active, and false if it is not
|
||||
* @return true if service location tracking is active, and false if it is not
|
||||
*/
|
||||
public static boolean isTrackingStarted() {
|
||||
return Application.Companion.getPrefs().getBoolean(KEY_SERVICE_TRACKING_ENABLED, false);
|
||||
public static boolean isTrackingStarted(SharedPreferences prefs) {
|
||||
return prefs.getBoolean(KEY_SERVICE_TRACKING_ENABLED, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the provided value as the current service location tracking state
|
||||
* @param value true if service location tracking is active, and false if it is not
|
||||
*/
|
||||
public static void saveTrackingStarted(boolean value) {
|
||||
saveBoolean(KEY_SERVICE_TRACKING_ENABLED, value);
|
||||
public static void saveTrackingStarted(boolean value, SharedPreferences prefs) {
|
||||
saveBoolean(KEY_SERVICE_TRACKING_ENABLED, value, prefs);
|
||||
}
|
||||
}
|
||||
@@ -13,17 +13,17 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.android.gpstest.util
|
||||
package com.android.gpstest.library.util
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.location.GnssStatus
|
||||
import android.location.Location
|
||||
import android.os.Build
|
||||
import com.android.gpstest.model.*
|
||||
import com.android.gpstest.util.CarrierFreqUtils.*
|
||||
import com.android.gpstest.util.SatelliteUtils.createGnssSatelliteKey
|
||||
import com.android.gpstest.library.model.*
|
||||
import com.android.gpstest.library.util.CarrierFreqUtils.*
|
||||
import com.android.gpstest.library.util.SatelliteUtils.createGnssSatelliteKey
|
||||
|
||||
internal object SatelliteUtil {
|
||||
object SatelliteUtil {
|
||||
|
||||
/**
|
||||
* Tranforms the Android [GnssStatus] object to a list of our [SatelliteStatus] model objects
|
||||
@@ -14,9 +14,9 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.gpstest.util;
|
||||
package com.android.gpstest.library.util;
|
||||
|
||||
import static com.android.gpstest.model.GnssType.SBAS;
|
||||
import static com.android.gpstest.library.model.GnssType.SBAS;
|
||||
|
||||
import android.content.Context;
|
||||
import android.hardware.Sensor;
|
||||
@@ -27,9 +27,9 @@ import android.os.Build;
|
||||
|
||||
import androidx.annotation.RequiresApi;
|
||||
|
||||
import com.android.gpstest.model.GnssType;
|
||||
import com.android.gpstest.model.SatelliteName;
|
||||
import com.android.gpstest.model.SatelliteStatus;
|
||||
import com.android.gpstest.library.model.GnssType;
|
||||
import com.android.gpstest.library.model.SatelliteName;
|
||||
import com.android.gpstest.library.model.SatelliteStatus;
|
||||
|
||||
/**
|
||||
* Utilities to manage GNSS signal and satellite information
|
||||
@@ -13,9 +13,9 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.android.gpstest.util
|
||||
package com.android.gpstest.library.util
|
||||
|
||||
import com.android.gpstest.model.SatelliteStatus
|
||||
import com.android.gpstest.library.model.SatelliteStatus
|
||||
|
||||
/**
|
||||
* Utilities for sorting lists. Java Comparator.comparing() is only available on API 24 and higher,
|
||||
@@ -41,7 +41,8 @@ class SortUtil {
|
||||
* Sorts the [list] by the SatelliteStatus usedInFix desc then svid asc and returns the sorted list
|
||||
*/
|
||||
fun sortByUsedThenId(list: List<SatelliteStatus>): MutableList<SatelliteStatus> {
|
||||
return list.sortedWith(compareByDescending(SatelliteStatus::usedInFix).thenComparing(SatelliteStatus::svid)).toMutableList()
|
||||
return list.sortedWith(compareByDescending(SatelliteStatus::usedInFix).thenComparing(
|
||||
SatelliteStatus::svid)).toMutableList()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -62,28 +63,32 @@ class SortUtil {
|
||||
* Sorts the [list] by the SatelliteStatus gnssType then usedInFix desc then svid asc and returns the sorted list
|
||||
*/
|
||||
fun sortByGnssThenUsedThenId(list: List<SatelliteStatus>): MutableList<SatelliteStatus> {
|
||||
return list.sortedWith(compareBy(SatelliteStatus::gnssType).thenByDescending(SatelliteStatus::usedInFix).thenComparing(SatelliteStatus::svid)).toMutableList()
|
||||
return list.sortedWith(compareBy(SatelliteStatus::gnssType).thenByDescending(
|
||||
SatelliteStatus::usedInFix).thenComparing(SatelliteStatus::svid)).toMutableList()
|
||||
}
|
||||
|
||||
/**
|
||||
* Sorts the [list] by the SatelliteStatus sbasType then usedInFix desc then svid asc and returns the sorted list
|
||||
*/
|
||||
fun sortBySbasThenUsedThenId(list: List<SatelliteStatus>): MutableList<SatelliteStatus> {
|
||||
return list.sortedWith(compareBy(SatelliteStatus::sbasType).thenByDescending(SatelliteStatus::usedInFix).thenComparing(SatelliteStatus::svid)).toMutableList()
|
||||
return list.sortedWith(compareBy(SatelliteStatus::sbasType).thenByDescending(
|
||||
SatelliteStatus::usedInFix).thenComparing(SatelliteStatus::svid)).toMutableList()
|
||||
}
|
||||
|
||||
/**
|
||||
* Sorts the [list] by the SatelliteStatus gnssType then C/N0 desc and returns the sorted list
|
||||
*/
|
||||
fun sortByGnssThenCn0ThenId(list: List<SatelliteStatus>): MutableList<SatelliteStatus> {
|
||||
return list.sortedWith(compareBy(SatelliteStatus::gnssType).thenByDescending(SatelliteStatus::cn0DbHz)).toMutableList()
|
||||
return list.sortedWith(compareBy(SatelliteStatus::gnssType).thenByDescending(
|
||||
SatelliteStatus::cn0DbHz)).toMutableList()
|
||||
}
|
||||
|
||||
/**
|
||||
* Sorts the [list] by the SatelliteStatus sbasType then C/N0 desc and returns the sorted list
|
||||
*/
|
||||
fun sortBySbasThenCn0ThenId(list: List<SatelliteStatus>): MutableList<SatelliteStatus> {
|
||||
return list.sortedWith(compareBy(SatelliteStatus::sbasType).thenByDescending(SatelliteStatus::cn0DbHz)).toMutableList()
|
||||
return list.sortedWith(compareBy(SatelliteStatus::sbasType).thenByDescending(
|
||||
SatelliteStatus::cn0DbHz)).toMutableList()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* From https://stackoverflow.com/a/7855852/937715
|
||||
*/
|
||||
package com.android.gpstest.view;
|
||||
package com.android.gpstest.library.view;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 161 B After Width: | Height: | Size: 161 B |
|
Before Width: | Height: | Size: 698 B After Width: | Height: | Size: 698 B |