Android location is providing timestamps in the future, or am I stupid? Try it for yourself

37 Views Asked by At

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?

1

There are 1 best solutions below

0
GlintShadow On

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.