Android has shipped a Select mock location app picker in Developer options for over a decade, and almost nothing pretty plugs into it. Mirage does: a full-bleed map, a search bar, one big button, and a location feed the whole system believes.
![]() Pick a point |
![]() Live feed |
![]() Routes, in the dark |
![]() 36 presets, saved, recent |
![]() Settings |
Material 3 · dynamic color · light & dark · EN / RU |
Three ways to be somewhere else.
| Mode | Behaviour |
|---|---|
| Pinned | Absolute stillness at one coordinate. |
| Drift | A bounded random walk inside a radius you choose — a real phone on a real table never reports the same coordinate twice, and some apps check. |
| Route | Tap out a polyline and travel it at a set speed, looping or one-shot. Bearing and velocity are derived from the path, not faked. |
Finding the place.
- Full-text search over Nominatim with results tagged by category.
- 36 curated presets — cities, landmarks, and the ends of the earth (Point Nemo, Challenger Deep, Amundsen–Scott Station).
- Paste coordinates in any format people actually use:
55.7539, 37.6208,55.7539N 37.6208E, ageo:URI, or a Google Maps URL. - Share a location into Mirage from any other app — it registers a
geo:intent filter. - Save favourites, browse recents, sorted by distance from wherever you are looking.
Making the fix convincing.
- All three system providers driven at once:
gps,network, andfused(API 31+). - Accuracy, altitude and update interval are all yours to set.
- Humanize jitters the reported accuracy and satellite count on every tick, so the signal does not read as machine-perfect.
- Vertical / speed / bearing accuracy are populated too — a modern fused client scores a fix without them as low quality.
Staying alive.
- A
locationforeground service keeps the feed running with the screen off, with an optional wake lock for Doze. - Notification shows the place, the live coordinate and elapsed time, with a one-tap stop.
- Edit accuracy, speed or providers while a session is running — the change lands on the next tick without restarting the walk.
Mirage needs one thing Android will not grant on its own:
- Settings → About phone → tap Build number seven times to unlock Developer options.
- Settings → System → Developer options → Select mock location app → Mirage.
- Open Mirage and grant location + notification permissions when asked.
The app checks all four prerequisites on every resume and walks you through whichever one is missing, one card at a time. No card means you are ready.
On Samsung One UI the picker lives at Settings → Developer options → Select mock location app.
No prebuilt APK is published — clone and build:
git clone https://github.com/AlexMelanFromRingo/mirage.git
cd mirage
./gradlew assembleDebug
# app/build/outputs/apk/debug/app-debug.apkRequires JDK 17+ and Android SDK 36. For a signed release build, drop a
keystore.properties in the project root:
storeFile=/path/to/release.jks
storePassword=…
keyAlias=…
keyPassword=……then ./gradlew assembleRelease. Without it the release build falls back to the
debug key, so it still installs.
Everything routes through LocationManager's test-provider API, which Android
only opens to the package selected in Developer options.
// One-time install, per provider
locationManager.addTestProvider(provider, ProviderProperties.Builder()…build())
locationManager.setTestProviderEnabled(provider, true)
// Then once per tick, forever
locationManager.setTestProviderLocation(provider, location)The interesting parts are the details around that:
MotionSimulatorturns elapsed milliseconds into a position.Driftsums three sine waves with incommensurable periods, which stays bounded by construction and never visibly repeats;Routeprecomputes leg lengths so each tick is a short linear scan.MockLocationServicekeys its simulation restart on aMotionSpec— anchor, route, mode, speed. Cosmetic settings like accuracy flow through to the next tick without resetting route progress, which is why you can tune a walk mid-walk.MirageStoreis the single source of truth. The UI and the service both read the same DataStore flows, so there is no IPC, no binder, and no state to keep in sync.- Revocation is handled. If you switch the mock app away mid-session, the next
setTestProviderLocationthrows, the service tears the providers down cleanly and the UI says why.
app/src/main/java/com/melan/mirage/
├── core/ Engine, motion simulators, geo math, persistence, app-op checks
├── net/ Nominatim geocoding (rate-limited, User-Agent compliant)
├── service/ Foreground service + notification
└── ui/
├── theme/ Material 3 palette, expressive shape scale, spring motion
├── map/ osmdroid ↔ Compose bridge, route + live-fix canvas overlays
├── components/
└── screens/
Kotlin 2.3 · Jetpack Compose with Material 3 (1.4) · Coroutines & Flow ·
DataStore · kotlinx.serialization · osmdroid
for the map. AGP 8.13, compileSdk 36, minSdk 26.
No Google Play Services, no Maps API key, no Firebase.
Mirage talks to exactly two hosts, both only when you ask it to:
- Nominatim — when you type in the search box or drop a pin (for the street name).
- OpenStreetMap / CARTO tile servers — for map tiles.
There is no analytics, no crash reporting, no account, and no telemetry. Saved places and settings never leave the device. Requests to Nominatim are serialised to one per second with an identifying User-Agent, per their usage policy.
Map data © OpenStreetMap contributors. Minimal basemap © CARTO. Terrain tiles © OpenTopoMap (CC-BY-SA). Geocoding by Nominatim.
Mock location is a first-class Android developer feature, and this is a nice front end for it: testing geofences, previewing location-gated UI, capturing screenshots that are not of your own street, keeping your real position out of apps that never needed it.
It is not a way to defeat anything that matters. Fixes published through a test
provider are flagged isMock at the framework level, and any service that cares —
banking, attendance, competitive games — reads that flag. Using it to commit fraud
or break a service's terms is on you, and it will not work anyway.




