I am writing an App for jogging and I integrate the distance travelled from GPS. It works pretty well. I have added various configurable filters and I am experimenting with methods to minimize jitter and other errors, especially at walking speeds. One suggestion I read was to discard 'old' fixes at start up, so I implemented an age filter, based on comparing the location.time returned with now. My problem is I am seeing negative age which implies the location timestamp is in the future !!!
Have I discovered a time machine?
Here is the shortest complete demo program I could write to demonstate my time machine.
Activity
package com.example.locationagedemo
import android.Manifest
import android.content.Context
import android.content.pm.PackageManager
import android.location.Location
import android.location.LocationListener
import android.location.LocationManager
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.os.Looper
import android.util.Log
import android.widget.TextView
import android.widget.Toast
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import java.util.Calendar
class MainActivity : AppCompatActivity() {
private lateinit var locationManager: LocationManager
private lateinit var locationListener : LocationListener
private lateinit var ageTextView: TextView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
ageTextView = findViewById(R.id.ageTextView)
locationManager = getSystemService(Context.LOCATION_SERVICE) as LocationManager
nowListen()
}
fun nowListen() {
if (ContextCompat.checkSelfPermission(
this, Manifest.permission.ACCESS_FINE_LOCATION
) == PackageManager.PERMISSION_GRANTED
) {
locationListener = object : LocationListener {
override fun onLocationChanged(location: Location) {
var locationAge = Calendar.getInstance().timeInMillis - location.time
ageTextView.setText(String.format("Location Age %d ms", locationAge))
}
@Deprecated("Deprecated in Java")
override fun onStatusChanged(provider: String?, status: Int, extras: Bundle?) {
}
override fun onProviderEnabled(provider: String) {}
override fun onProviderDisabled(provider: String) {}
}
locationManager.requestLocationUpdates(
LocationManager.GPS_PROVIDER,
2000L, 0.0F, locationListener, Looper.getMainLooper()
)
} else
ActivityCompat.requestPermissions(
this,
arrayOf(Manifest.permission.ACCESS_FINE_LOCATION), 1234
)
}
override fun onRequestPermissionsResult( requestCode: Int, permissions: Array<String>,
grantResults: IntArray
) {
nowListen()
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
}
}
Layout
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:id="@+id/ageTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Location Age Here ms"
android:textAppearance="@style/TextAppearance.AppCompat.Large"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
Manifest
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.LocationAgeDemo"
tools:targetApi="31">
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
The program compares Calendar.timeInMillis and Location.time, both defined in the documentaion as Unix epoch time UTC, and further the Location documentation states "All locations generated through LocationManager are guaranteed to have a valid latitude, longitude, timestamp"
So for all you Android GPS Gurus our there :- Either a) I have discovered a time machine b) I am being really stupid and missing something obvious (not the first time Ha ha ha!) c) There is a bug in something I can't see d) Something else?
Which is it???
For everyone else, please run this on your real phone and tell me the phone model and if you get +ve or -ve ages. Maybe it is just my two Motos?
OK, I was being stupid I guess.
The timestamp provided in the Location must be derived from the GPS constellation, and I am comparing that with the clock in the phone, which must be set from a timerserver through the mobile service provider, and due to various delays in the setting process, must always be running a little slow, relative to the GPS derived times.