Mocking GPS on Android with the Official Test Provider API
Building an Android Virtual Location App from Scratch: GPSSimulate Technical Practice
This article introduces GPSSimulate, an Android virtual location app I developed from scratch based on a real need. It covers the project's origin, technology choices, core implementation ideas, and the pitfalls encountered during development.
Foreword
This article shares the implementation of Android's official mock location mechanism for the purpose of technical learning and personal development practice.
Please use location-related capabilities reasonably and legally, and respect the terms of service of target applications. Some apps actively detect and reject mock locations, and this will be honestly explained in the text.
1. Origin: I Wanted to "Pretend" I Was in Another City
A very practical need came up recently.
I usually follow some battery-swapping/car-rental apps, like Zhizu. Much of the information in these apps is displayed by city/geographic location—monthly rental package prices, station coverage, battery models (48V / 60V, etc.) all change depending on the city.
Without being physically present, I wanted to know in advance: "If I were in Suzhou, what packages would I see?" or "What are the differences in battery types between Hangzhou and Suzhou?"
There are many virtual location apps on the market, but they either have too many ads, request permissions aggressively, or are unstable on Xiaomi phones. As an Android developer, I decided to just write my own:
GPSSimulate — map point selection + quick city location + system-level GPS simulation.
The core goals are simple:
- Drag and select a point on the map to intuitively change the location to the target city
- Support saving frequently visited cities (like Suzhou) for one-tap jumping
- Use Android's officially supported mock location mechanism for stability and maintainability
2. Technology Stack Overview
| Category | Technology | Description |
|---|---|---|
| Language | Kotlin | Project's main language |
| UI | Jetpack Compose + Material 3 | Declarative UI, high development efficiency |
| Map | OSMDroid + OpenStreetMap | Free, no map API Key application needed |
| Location | Google Play Services Location | Get real GPS for initial map positioning |
| Mock Location | LocationManager Test Provider |
Android's official mock location API |
| Background | Foreground Service | Continuously inject coordinates, prevent system from killing the process |
| Persistence | SharedPreferences | Save user-defined quick cities |
| Build | Gradle Kotlin DSL + AGP 9.x | Release signed packaging |
Project Environment:
minSdk = 30(Android 11+)targetSdk = 36compileSdk = 36
3. The Principle of Virtual Location: Not Black Magic, It's an Official API
The core principle of many similar apps is the same: register a Test Provider with the system and inject fake coordinates.
locationManager.addTestProvider(
LocationManager.GPS_PROVIDER,
/* requiresNetwork */ false,
/* requiresSatellite */ false,
/* requiresCell */ false,
/* hasMonetaryCost */ false,
/* supportsAltitude */ true,
/* supportsSpeed */ true,
/* supportsBearing */ true,
Criteria.POWER_LOW,
Criteria.ACCURACY_FINE,
)
locationManager.setTestProviderEnabled(LocationManager.GPS_PROVIDER, true)
locationManager.setTestProviderLocation(LocationManager.GPS_PROVIDER, mockLocation)
Prerequisites
The user must set this app as the "Mock location app" in Developer Options.
Additionally, the Manifest needs to declare (otherwise it won't appear in the system candidate list):
<uses-permission
android:name="android.permission.ACCESS_MOCK_LOCATION"
tools:ignore="ProtectedPermissions" />
The First Pitfall I Encountered: Mocking Only GPS Is Not Enough
Initially, I only injected GPS_PROVIDER, but found that some apps still read the real location.
The reason is that many apps use Fused Location or Network Location (NETWORK_PROVIDER), and don't rely solely on GPS.
Improvement: Mock both GPS + Network location simultaneously, and continuously push coordinates every second.
// Inject the same coordinates into both GPS and NETWORK simultaneously
pushToProvider(LocationManager.GPS_PROVIDER, lat, lng, accuracy = 1f)
pushToProvider(LocationManager.NETWORK_PROVIDER, lat, lng, accuracy = 10f)
The foreground service ticks every 1 second to ensure apps relying on LocationListener continuously receive updates.
4. Project Architecture
app/
├── MainActivity.kt # Entry point
├── ui/
│ └── LocationScreen.kt # Map + Control Panel (Compose)
├── location/
│ ├── MockLocationProvider.kt # Core: Inject mock coordinates
│ ├── MockLocationChecker.kt # Check if set as mock location app
│ ├── LocationHelper.kt # Get real location
│ ├── PresetLocation.kt # Quick city data model + parsing
│ └── PresetLocationRepository.kt # Local persistence
└── service/
└── MockLocationService.kt # Foreground service, continuous simulation
5. Core Module Breakdown
1. Map Point Selection: Center Pin + Dragging Map
The interaction references the common practice in ride-hailing apps:
- The pin is fixed at the screen center
- The user drags the map, and the center point's latitude/longitude becomes the simulation target
- Coordinates update 300ms after dragging stops (debounce)
The map uses OSMDroid to load OpenStreetMap tiles:
- ✅ Free, no Key required
- ✅ Automatic disk caching of tiles, faster loading for previously viewed areas
- ⚠️ Initial loading in China might be slow (servers are overseas)
MapView(ctx).apply {
setTileSource(TileSourceFactory.MAPNIK)
setMultiTouchControls(true)
controller.setZoom(16.0)
}
2. Quick Location: Preset Cities + Manual Addition
The default city "Suzhou" is built-in, and users can manually add more:
Suzhou, 31.3167, 120.6167
Format: City, Latitude, Longitude, comma-separated, spaces auto-trimmed.
Data is persisted via SharedPreferences, not lost on app restart.
3. First-Launch Guidance
If the user hasn't configured the mock location app in Developer Options, a non-dismissable centered dialog pops up, guiding them to settings. It automatically disappears once configured.
4. Foreground Service Keep-Alive
When mock location needs to run for a long time, a Foreground Service + persistent notification is used to prevent the process from being recycled by the system:
class MockLocationService : Service() {
// Tick every 1 second, continuously inject coordinates
}
6. Pitfalls Encountered During Development
1. Xiaomi Phone USB Installation Restriction
INSTALL_FAILED_USER_RESTRICTED: Installation via USB is disabled
Solution: Developer Options → Enable USB Installation + USB Debugging (Security Settings).
2. Real Device Location Failure
getCurrentLocation() often returns null on cold start, causing the map to default to Beijing.
Solution: Changed the location strategy to a three-tier fallback: lastLocation → getCurrentLocation → requestLocationUpdates, and waited for MapView creation to complete before locating.
3. Release Build Lint Error
MockLocation: Mock locations should only be requested in a debug manifest
This app itself is a mock location tool, so this Lint check was disabled in build.gradle.kts:
lint {
disable += "MockLocation"
}
4. Which Apps Still "Can't Be Fooled"?
| Type | Result |
|---|---|
| Regular maps, weather, some information apps | ✅ Usually effective |
| Apps using fused location | ⚠️ Partially effective |
| Banking, payment, ride-hailing, gaming | ❌ Often detect isFromMockProvider() |
Returning to the Zhizu scenario: whether it succeeds depends on its own location strategy and risk control. The value of this project lies more in understanding the Android location mechanism + a personal tool, and it doesn't guarantee bypassing all commercial app detections.
7. Usage Flow (Simplified)
- Enable Developer Mode: Settings → About Phone → Tap Build Number 7 times
- Set Mock Location App: Developer Options → Select mock location app → GPSSimulate
- Open the app, grant location permission
- Drag the map to select a point, or choose a quick city from the bookmark in the top right corner
- Tap "Start Simulation"
- Open the target app to see the effect
8. Summary and Takeaways
This project originated from a very life-oriented need: wanting to see information displayed by a local app in a different city in advance.
From a technical perspective, the biggest gains were:
- Understanding Android's Test Provider official mock location mechanism
- Recognizing the differences between GPS / Network / Fused Location, and why multi-Provider injection is necessary
- Using Compose + OSMDroid to quickly build a usable map interaction
- Stepping through various permission pitfalls on Xiaomi real devices
If you want to build a similar tool, don't necessarily pursue "bypassing all risk controls"—using the official API correctly and stably can already solve many scenarios.
Appendix: Local Build
./scripts/build-release.sh
# Output: app/build/outputs/apk/release/app-release.apk
Suggested Tags: Android Kotlin Jetpack Compose GPS Virtual Location
One-Liner Cover: Driven by the need to check Zhizu packages across cities, I wrote an Android virtual location app using Kotlin + Compose. This article shares the technical solution and pitfalls encountered.
If this article was helpful, feel free to like and bookmark. If you have questions, leave a comment and I'll try to reply.