Android engineering
Simulating movement for geofence and route testing
A static mock location tells you whether your app reads a position. It tells you nothing about whether your geofences fire, your distance filters behave, or your speed logic works. This page covers the arithmetic of walking a device along a route, how fast to step it, and what a satellite picture carries that a single injected fix does not.
What a real fix carries that a static mock does not
Every Location from the platform has latitude, longitude, a Unix epoch time, an elapsed-realtime timestamp in nanoseconds since boot, and a horizontal accuracy. Speed, bearing and altitude are optional fields a receiver may fill in, and they are where static mocks fail. Android’s documentation notes that a fix’s speed “may be more accurate than would be obtained simply by calculating distance / time for sequential positions, such as if the Doppler measurements from GNSS satellites are taken into account” — Doppler being the shift in received carrier frequency caused by the relative motion of satellite and receiver, from which the receiver’s velocity is measured directly. An app that trusts getSpeed() sees zero from an injected fix unless you set the field, and a code path gated on speed never runs. Two fixes carrying the same timestamp give a divide-by-zero in any speed the app computes for itself, and Google’s fused-provider documentation warns that it “may expect monotonically increasing timestamps”, so advance both clocks on every step.
The arithmetic of a route
Walking a device between two waypoints needs three things: the distance, the bearing, and the position reached after a given distance along that bearing. On a sphere of radius R, with latitudes φ1, φ2 and longitudes λ1, λ2 in radians:
a = sin²(Δφ/2) + cosφ1 · cosφ2 · sin²(Δλ/2)
d = 2R · atan2(√a, √(1−a))
That is the haversine form, stable on short legs where the plain cosine formula loses precision. The initial bearing is
θ = atan2( sinΔλ · cosφ2 , cosφ1 · sinφ2 − sinφ1 · cosφ2 · cosΔλ )
and the point reached after distance d on bearing θ, with angular distance δ = d/R, is
φ2 = asin( sinφ1 · cosδ + cosφ1 · sinδ · cosθ )
λ2 = λ1 + atan2( sinθ · sinδ · cosφ1 , cosδ − sinφ1 · sinφ2 )
Those are the standard great-circle formulae of spherical trigonometry. Bearing changes continuously along a great circle, so either compute every step from the start of the leg, with the leg’s initial bearing and the distance covered so far, or recompute the bearing to the next waypoint at each step. Holding the initial bearing while stepping on from each new point traces a line of constant bearing instead, which drifts off the great circle on long legs.
The sphere is not the Earth, and Android knows it
Android does not measure on a sphere. Location.distanceBetween() in AOSP carries the comment “Based on http://www.ngs.noaa.gov/a = 6378137.0, b = 6356752.3142 and a loop capped at 20 iterations.
WGS 84 defines the equatorial radius as 6 378 137.0 m and the flattening as 1/298.257223563, putting the polar radius at 6 356 752.314 m. The two differ by 21 385 m, 0.336 % of the mean radius (2a+b)/3 = 6 371 008.8 m. A route stepped on a sphere and measured by the app on the ellipsoid will not agree. In each row below a leg is stepped to an exact length on a sphere of the WGS 84 mean radius, and the same two points are then measured the way distanceBetween() measures them, with AOSP’s own constants. The calculator further down makes the same two measurements for any pair of points; its default is the London to New York row.
| 100 m east–west, 51.5°N | sphere 100.000 m, Android 100.318 m — sphere short by 0.32 m (0.32 %) |
|---|---|
| 5 km north–south, 51.5°N | sphere 5000.00 m, Android 5002.84 m — short by 2.84 m (0.06 %) |
| 5 km east–west, 51.5°N | sphere 5000.00 m, Android 5015.89 m — short by 15.89 m (0.32 %) |
| 5 km east–west, 70°N | sphere 5000.00 m, Android 5020.45 m — short by 20.45 m (0.41 %) |
| 5 km north–south, 0° | sphere 5000.00 m, Android 4972.08 m — sphere long by 27.92 m (0.56 %) |
| London to New York | 51.5074°N 0.1278°W to 40.7128°N 74.0060°W: sphere 5 570.23 km, Android 5 585.23 km — short by 15.00 km (0.27 %) |
The error depends on direction and latitude. East–west the sphere always comes up short, by 0.11 % at the equator rising to 0.41 % at 70°N. North–south it changes sign: the sphere is 0.56 % long at the equator, almost exact near 48°, and short towards the poles, which is why the north–south row at 51.5°N is the smallest. At geofence scale 0.3 % is a third of a metre and irrelevant. For an odometer or a fare calculation it is nearly 16 m on every 5 km east–west leg at London’s latitude, and it is systematic, so it accumulates rather than averaging out. If that matters, step the route with the same ellipsoidal formula the app measures with.
Step in metres and convert, never in degrees: by the same calculation, one degree of longitude is 111 320 m at the equator but 69 440 m at 51.5°N.
How often to step, and why it decides the result
A geofence is a circle the system watches, raising an event when the device enters, leaves or lingers inside it. Google’s geofencing guide recommends a minimum radius of 100–150 m, because “when Wi-Fi is available location accuracy is usually between 20 - 50 meters”, as small as 5 m where indoor positioning exists, and “as large as several hundred meters to several kilometers” in rural areas without it.
Take that 100 m radius. A path through the middle spends 200 m inside. Distance between two simulated fixes is speed × interval, so:
| 5 km/h at 1 s | 1.4 m per step — about 144 fixes inside the circle |
|---|---|
| 30 km/h at 4 s | 33.3 m per step — about 6 fixes inside |
| 50 km/h at 4 s | 55.6 m per step — about 3.6 fixes inside |
| 100 km/h at 4 s | 111.1 m per step — about 1.8 fixes inside |
| 30 km/h at 30 s | 250 m per step — 0.8 fixes: the device can step clean over the geofence |
The last row is the failure that gets shipped: nothing errors, no exception is thrown, and the test records no ENTER event because no simulated position ever fell inside the circle. Keep the step well under the radius — a quarter of it gives at least eight fixes across a central crossing — and test off-centre crossings, where the chord is far shorter than 200 m.
The platform’s own timing is part of the test
Geofence alerts are not instant. Google’s guide gives a latency “less than 2 minutes, even less when the device has been moving”, 2–3 minutes on average under the Android 8.0 background location limits, and up to 6 minutes if “the device has been stationary for a significant period of time”. setNotificationResponsiveness trades latency for battery: set five minutes and the system checks for a crossing once every five minutes.
From the same source: 100 geofences per app per device user, and ACCESS_FINE_LOCATION, plus ACCESS_BACKGROUND_LOCATION if the app targets Android 10 or higher. Where driving briefly past a fence floods the app with alerts, the guide recommends the DWELL transition with a loitering delay instead of ENTER, so “the dwelling alert is sent only when the user stops inside a geofence for a given period of time”. Driving past at 50 km/h and parking for ten minutes are therefore two different tests.
What a satellite picture carries
Setting a position fills in Location. It does not produce a satellite picture. Android exposes that through GnssStatus (API level 24): per satellite, an identifier, the constellation (GPS, GLONASS, Galileo, BeiDou, QZSS, IRNSS or SBAS), elevation and azimuth in degrees, carrier frequency in hertz, whether it was used in the most recent fix, and whether the receiver holds ephemeris or almanac data for it. Carrier frequency arrived in API level 26 and the IRNSS constant in 29.
It also carries getCn0DbHz(), the carrier-to-noise density at the antenna in dB-Hz. C/N0 is the ratio of received carrier power to noise power in a one-hertz band — the number a receiver uses to decide whether a satellite is worth tracking, and the one that collapses under a bridge. Ephemeris is precise orbit data for one satellite, valid a few hours; almanac is coarse data for the whole constellation. Which one the receiver holds largely decides how long its first fix takes.
addNmeaListener delivers raw NMEA 0183 sentences — the Executor form in API level 30, the earlier OnNmeaMessageListener forms in API 24 — and only “while the GPS_PROVIDER is enabled, and while the client app is in the foreground”, with ACCESS_FINE_LOCATION required. Pseudorange-level data goes further, through GnssMeasurement; Android’s documentation states support is mandatory on devices running Android 10 or higher, and on earlier releases only on devices with a hardware year of 2016 or newer.
What no location simulation reproduces
Activity recognition works from sensor data, not from the location you inject. Google’s API detects activities “by periodically waking up the device and reading short bursts of sensor data”, and it “only makes use of low power sensors”. A simulated 90 km/h drive does not move a phone lying on a desk, so logic gated on an IN_VEHICLE detection needs its own fake at that API boundary.
And mock mode on the fused provider affects every client of it on the device, including other processes and geofencing. Google’s documentation tells clients to always set it back to false when finished; for as long as a harness leaves it on, every app on the handset that uses the fused provider receives only the positions the harness sets.
The tool we sell for this
GPS Spoofer is our Android app for driving a real device along a route by hand: place waypoints, pick a speed from 1 to 200 km/h, and it steps the reported position along the path, with a dwell time of up to 20 minutes on any waypoint for DWELL tests, loop and pause, GPX import and export, and a joystick overlay for free movement. It reports a fix roughly every half second with speed and bearing filled in from the movement, so at its 60 km/h drive preset a step is about 8 m. Both editions do all of this. The Full edition is sold direct from this site as a one-time purchase; the Google Play edition, without the Full edition’s mock-hiding features, is not yet released. For scripted regression runs the emulator is the better tool — this is for when you want a physical handset walking a route while you watch your app on it.
Corrections to any figure on this page are welcome: [email protected].