跪拜 Guibai
← Back to the summary

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:

  1. Drag and select a point on the map to intuitively change the location to the target city
  2. Support saving frequently visited cities (like Suzhou) for one-tap jumping
  3. 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:


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 map uses OSMDroid to load OpenStreetMap tiles:

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: lastLocationgetCurrentLocationrequestLocationUpdates, 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)

  1. Enable Developer Mode: Settings → About Phone → Tap Build Number 7 times
  2. Set Mock Location App: Developer Options → Select mock location app → GPSSimulate
  3. Open the app, grant location permission
  4. Drag the map to select a point, or choose a quick city from the bookmark in the top right corner
  5. Tap "Start Simulation"
  6. 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:

  1. Understanding Android's Test Provider official mock location mechanism
  2. Recognizing the differences between GPS / Network / Fused Location, and why multi-Provider injection is necessary
  3. Using Compose + OSMDroid to quickly build a usable map interaction
  4. 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.