android-mock-location-mcp 0.2.1 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,20 +1,18 @@
1
1
  # MCP Server — android-mock-location-mcp
2
2
 
3
- MCP server that exposes 9 tools for controlling Android device GPS location. Connects to an Android agent app over TCP (via ADB port forwarding) and supports geocoding and street-level routing through configurable providers.
3
+ MCP server that exposes 11 tools for controlling Android emulator GPS location. Sets mock locations via `adb emu geo fix` and supports geocoding and street-level routing through configurable providers.
4
4
 
5
5
  See the [root README](../README.md) for project overview and quick start.
6
6
 
7
7
  ## Installation
8
8
 
9
- **npx (no install):**
10
- ```bash
11
- npx android-mock-location-mcp
12
- ```
9
+ The server is launched automatically by your MCP client — you don't run it directly. The installation method determines how the client finds and starts it.
10
+
11
+ **npx (no install needed):** Use `npx -y android-mock-location-mcp` as the command in your MCP client config. The client will download and run it automatically.
13
12
 
14
- **Global install:**
13
+ **Global install:** Pre-install the package so the client can launch it without download delay:
15
14
  ```bash
16
15
  npm install -g android-mock-location-mcp
17
- android-mock-location-mcp
18
16
  ```
19
17
 
20
18
  **Build from source:**
@@ -22,9 +20,12 @@ android-mock-location-mcp
22
20
  cd server
23
21
  npm install
24
22
  npm run build
25
- npm start
26
23
  ```
27
24
 
25
+ > **Updating:** `npx` caches packages after the first download. To force an update, temporarily change the command in your MCP client config to `npx -y android-mock-location-mcp@latest` and restart the client.
26
+
27
+ See the [Configuration](#configuration) section below for how to set up your MCP client to launch the server.
28
+
28
29
  ## Configuration
29
30
 
30
31
  ### Environment Variables
@@ -35,6 +36,8 @@ npm start
35
36
  | `GOOGLE_API_KEY` | Google Places + Routes API key | When `PROVIDER=google` |
36
37
  | `MAPBOX_ACCESS_TOKEN` | Mapbox Geocoding + Directions access token | When `PROVIDER=mapbox` |
37
38
 
39
+ Provider selection is resolved once at server startup. Changing `PROVIDER` or API keys requires restarting the MCP client (which restarts the server).
40
+
38
41
  Set environment variables in your MCP client configuration:
39
42
 
40
43
  <details>
@@ -100,7 +103,7 @@ Edit `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) o
100
103
  ```
101
104
  </details>
102
105
 
103
- After editing the config, restart the MCP server and Claude Desktop for changes to take effect.
106
+ After editing the config, restart Claude Desktop to apply the changes (this restarts the MCP server automatically).
104
107
 
105
108
  <details>
106
109
  <summary><b>Claude Code (default OSM provider)</b></summary>
@@ -141,7 +144,7 @@ claude mcp add android-mock-location-mcp \
141
144
  <details>
142
145
  <summary><b>Claude Code — switching providers</b></summary>
143
146
 
144
- To switch from one provider to another (e.g. `osm` → `google`), remove and re-add the server with new env vars, then restart the server and Claude Code:
147
+ To switch from one provider to another (e.g. `osm` → `google`), remove and re-add the server with new env vars, then restart Claude Code:
145
148
 
146
149
  ```bash
147
150
  # 1. Remove existing server
@@ -154,10 +157,10 @@ claude mcp add android-mock-location-mcp \
154
157
  -e GOOGLE_API_KEY=$GOOGLE_API_KEY \
155
158
  -- npx -y android-mock-location-mcp
156
159
 
157
- # 3. Restart the MCP server and Claude Code
160
+ # 3. Restart Claude Code (this restarts the MCP server automatically)
158
161
  ```
159
162
 
160
- Environment variables are baked into the MCP server config at initialization time. Changing providers requires restarting the server.
163
+ Provider selection is resolved once at server startup, so restarting is required for changes to take effect.
161
164
  </details>
162
165
 
163
166
  ### Providers
@@ -178,7 +181,7 @@ Google and Mapbox providers produce better results than the default OSM provider
178
181
 
179
182
  ### `geo_list_devices`
180
183
 
181
- List connected Android devices via ADB.
184
+ List connected Android emulators via ADB.
182
185
 
183
186
  No parameters.
184
187
 
@@ -186,24 +189,23 @@ No parameters.
186
189
 
187
190
  ### `geo_connect_device`
188
191
 
189
- Connect to an Android device for mock location control. Automatically grants permissions, selects the app as mock location provider, starts the agent service, sets up ADB port forwarding, and opens a TCP socket.
192
+ Connect to an Android emulator for mock location control. Only emulators are supported (e.g. `emulator-5554`). Locations are set via the emulator's `geo fix` command.
190
193
 
191
194
  | Parameter | Type | Required | Description |
192
195
  |-----------|------|----------|-------------|
193
- | `deviceId` | string | yes | Device serial from `geo_list_devices`, e.g. `emulator-5554` |
196
+ | `deviceId` | string | yes | Emulator serial from `geo_list_devices`, e.g. `emulator-5554` |
194
197
 
195
198
  ---
196
199
 
197
200
  ### `geo_set_location`
198
201
 
199
- Set device GPS to coordinates or any place name/address. Geocodes place names via the configured provider.
202
+ Set emulator GPS to coordinates or any place name/address. Geocodes place names via the configured provider.
200
203
 
201
204
  | Parameter | Type | Required | Default | Description |
202
205
  |-----------|------|----------|---------|-------------|
203
206
  | `lat` | number | no | — | Latitude (-90 to 90) |
204
207
  | `lng` | number | no | — | Longitude (-180 to 180) |
205
208
  | `place` | string | no | — | Place name or address, e.g. `'Times Square'`, `'Tokyo Station'` |
206
- | `accuracy` | number | no | `3` | GPS accuracy in meters |
207
209
 
208
210
  Provide either `place` or both `lat`/`lng`.
209
211
 
@@ -227,7 +229,7 @@ Simulate movement along a route between two points at a given speed. Routes foll
227
229
 
228
230
  Provide either `from`/`to` (place names) or `fromLat`/`fromLng`/`toLat`/`toLng` (coordinates) for each endpoint.
229
231
 
230
- **Starting location auto-resolve:** If no `from`/`fromLat`/`fromLng` is provided, the tool automatically tries (in order): the last mock location, the device's real GPS position via `geo_get_location`, or returns an error asking the user for their starting location.
232
+ **Starting location auto-resolve:** If no `from`/`fromLat`/`fromLng` is provided, the tool automatically tries (in order): the last mock location, the emulator's current GPS position via `geo_get_location`, or returns an error asking the user for their starting location.
231
233
 
232
234
  #### Routing Profiles
233
235
 
@@ -241,6 +243,30 @@ The AI should select `profile` based on user intent (e.g. "walk to" → `foot`,
241
243
 
242
244
  ---
243
245
 
246
+ ### `geo_simulate_multi_stop`
247
+
248
+ Simulate movement along a multi-stop route (e.g. delivery route, rideshare pickups). Routes between consecutive waypoints follow real streets. Each waypoint can have a dwell time (time spent stationary at the stop before continuing). Runs as one continuous 1 Hz simulation with no gaps between legs.
249
+
250
+ | Parameter | Type | Required | Default | Description |
251
+ |-----------|------|----------|---------|-------------|
252
+ | `waypoints` | array | yes | — | Ordered list of waypoints (min 2). Each: `{ lat?, lng?, place?, dwellSeconds? }` |
253
+ | `speedKmh` | number | no | `60` | Travel speed between waypoints (km/h) |
254
+ | `trafficMultiplier` | number | no | `1.0` | Traffic slowdown factor (e.g. `1.5` = 50% slower) |
255
+ | `profile` | enum | no | `car` | Routing profile: `car`, `foot`, or `bike` |
256
+
257
+ Each waypoint object:
258
+
259
+ | Field | Type | Required | Default | Description |
260
+ |-------|------|----------|---------|-------------|
261
+ | `lat` | number | no | — | Waypoint latitude |
262
+ | `lng` | number | no | — | Waypoint longitude |
263
+ | `place` | string | no | — | Waypoint place name or address |
264
+ | `dwellSeconds` | number | no | `0` | Time to stay at this waypoint before continuing (seconds) |
265
+
266
+ Provide either `place` or both `lat`/`lng` per waypoint. If the first waypoint has no coordinates or place, the tool auto-resolves from the current mock location or emulator GPS position.
267
+
268
+ ---
269
+
244
270
  ### `geo_simulate_jitter`
245
271
 
246
272
  Simulate GPS noise/jitter at a location for testing accuracy handling.
@@ -291,6 +317,32 @@ Provide either `place` or both `lat`/`lng`.
291
317
 
292
318
  ---
293
319
 
320
+ ### `geo_replay_gpx_kml`
321
+
322
+ Replay a GPX or KML track file on the emulator. Auto-detects format from the XML content.
323
+
324
+ | Parameter | Type | Required | Default | Description |
325
+ |-----------|------|----------|---------|-------------|
326
+ | `fileContent` | string | no* | — | Raw GPX or KML file content (XML string) |
327
+ | `filePath` | string | no* | — | Absolute path to a GPX or KML file on the host |
328
+ | `speedMultiplier` | number | no | `1.0` | Playback speed multiplier for time-based replay (e.g. `2.0` = 2x faster) |
329
+ | `speedKmh` | number | no | `60` | Travel speed for distance-based replay (km/h) |
330
+
331
+ *Provide either `fileContent` or `filePath`.
332
+
333
+ #### Replay Modes
334
+
335
+ | Mode | When used | Speed control |
336
+ |------|-----------|---------------|
337
+ | Time-based | GPX files with timestamps on ≥80% of trackpoints | `speedMultiplier` — preserves original speed profile |
338
+ | Distance-based | KML files, or GPX without timestamps | `speedKmh` — constant speed along the track |
339
+
340
+ **GPX support:** Extracts `<trkpt>` elements from `<trk>/<trkseg>` with optional `<ele>` (elevation) and `<time>` (timestamp).
341
+
342
+ **KML support:** Extracts `<coordinates>` from `<LineString>` elements. Coordinate format is `lng,lat,ele` tuples separated by whitespace.
343
+
344
+ ---
345
+
294
346
  ### `geo_stop`
295
347
 
296
348
  Stop any active location simulation.
@@ -309,22 +361,35 @@ No parameters.
309
361
 
310
362
  ### `geo_get_location`
311
363
 
312
- Get the device's current real GPS location (last known position from the device's location sensors). Use this to determine where the device physically is before simulating a route.
364
+ Get the emulator's current GPS location (last known position from the emulator's location providers). Reads the location via `adb shell dumpsys location`. Use this to determine where the emulator is before simulating a route.
313
365
 
314
366
  No parameters.
315
367
 
316
- Returns the device's latitude and longitude if a recent GPS fix is available, along with `accuracy` (horizontal accuracy in meters) and `ageMs` (milliseconds since the fix was obtained) when provided by the device. If the device has no location fix, the tool returns an error message — in that case, ask the user for their current location.
368
+ Returns the emulator's latitude and longitude if a recent GPS fix is available, along with `accuracy` (horizontal accuracy in meters) when provided. If the emulator has no location fix, the tool returns an error message — in that case, ask the user for their current location.
369
+
370
+ ## How It Works
371
+
372
+ The server sets emulator GPS location using `adb emu geo fix`, which updates the emulator's stored GPS state. The command format is:
373
+
374
+ ```text
375
+ geo fix <longitude> <latitude> [<altitude> [<satellites> [<velocity>]]]
376
+ ```
377
+
378
+ Unlike `geo nmea` (which injects one-shot NMEA sentences), `geo fix` updates the emulator's internal state so the position persists across the emulator's 1 Hz GPS update loop.
379
+
380
+ The `geo fix` command is processed directly by the emulator's GPS simulation layer.
317
381
 
318
382
  ## Source Structure
319
383
 
320
384
  | File | Purpose |
321
385
  |------|---------|
322
- | `src/index.ts` | MCP server setup, all 9 tool definitions with Zod schemas |
323
- | `src/adb.ts` | ADB command execution with timeouts, device setup, agent install check |
324
- | `src/device.ts` | TCP socket to agent, connection state machine, request/response matching |
386
+ | `src/index.ts` | MCP server setup, all 11 tool definitions with Zod schemas |
387
+ | `src/emulator.ts` | Emulator connection management, location setting via `geo fix` |
388
+ | `src/adb.ts` | ADB command execution with timeouts, emulator validation |
325
389
  | `src/geocode.ts` | Geocoding providers: Nominatim, Google, Mapbox |
326
390
  | `src/routing.ts` | Routing providers: OSRM, Google Routes API, Mapbox Directions |
327
- | `src/geo-math.ts` | Haversine distance, forward bearing calculation |
391
+ | `src/gpx-kml.ts` | GPX/KML file parsing for track replay |
392
+ | `src/geo-math.ts` | Haversine distance calculation |
328
393
  | `src/fetch-utils.ts` | Shared `fetchWithTimeout` helper |
329
394
 
330
395
  ## Development
@@ -340,7 +405,8 @@ The server communicates via stdio (MCP protocol). To test interactively, use the
340
405
 
341
406
  ## Known Limitations
342
407
 
343
- - **Single device only.** The server connects to one Android device at a time. Calling `geo_connect_device` with a different serial disconnects the previous device and stops any active simulation. Controlling multiple devices simultaneously is not supported.
408
+ - **Emulators only.** The server uses `adb emu geo fix` which only works with Android emulators. Physical devices are not supported.
409
+ - **Single emulator.** The server connects to one emulator at a time. Calling `geo_connect_device` with a different serial disconnects the previous one and stops any active simulation.
344
410
  - **Single simulation.** Only one simulation (route, jitter, or geofence) runs at a time. Starting a new one stops the previous.
345
411
 
346
412
  See also: provider-specific limitations in the [Providers](#providers) table above.
package/dist/adb.d.ts CHANGED
@@ -1,30 +1,14 @@
1
- export declare const AGENT_PACKAGE = "com.ms.square.geomcpagent";
2
- export declare const AGENT_PORT = 5005;
3
- /** Device-scoped synchronous ADB command. Always validates deviceId and applies timeout. */
1
+ interface ExecError extends Error {
2
+ stderr?: string;
3
+ status?: number | null;
4
+ }
5
+ export declare function isExecError(err: unknown): err is ExecError;
6
+ /** Check if a device ID matches the emulator serial pattern. */
7
+ export declare function isEmulator(deviceId: string): boolean;
8
+ /** Emulator-scoped synchronous ADB command. Validates deviceId and applies timeout. */
4
9
  export declare function adbDevice(deviceId: string, args: string[], timeoutMs?: number): string;
5
10
  /** Global asynchronous ADB command (no -s flag). */
6
11
  export declare function adb(args: string[], timeoutMs?: number): Promise<string>;
7
12
  /** List connected ADB devices. Returns raw `adb devices -l` output. */
8
13
  export declare function listDevices(): Promise<string>;
9
- /**
10
- * Check whether the agent APK is installed on the device.
11
- * Returns true if installed, false if not installed.
12
- * Throws if the ADB command fails for other reasons (device offline, unauthorized, etc.).
13
- */
14
- export declare function isAgentInstalled(deviceId: string): boolean;
15
- /**
16
- * Ensure the device is set up for mock location: grant location permissions
17
- * and set this app as the mock location provider via AppOps.
18
- * All commands are idempotent — safe to call on every connection attempt.
19
- */
20
- export declare function ensureDeviceSetup(deviceId: string): void;
21
- /**
22
- * Start the MockLocationService directly via ADB.
23
- * The service is exported, so it can be started without launching the Activity.
24
- */
25
- export declare function startAgentService(deviceId: string): void;
26
- /**
27
- * Set up ADB port forwarding for the agent TCP socket.
28
- * Synchronous — throws on failure with stderr context.
29
- */
30
- export declare function adbForward(deviceId: string): void;
14
+ export {};
package/dist/adb.js CHANGED
@@ -1,24 +1,29 @@
1
- // ── ADB command execution ────────────────────────────────────────────────────
1
+ // ── ADB command execution (emulator-only) ────────────────────────────────────
2
2
  import { execFileSync, execFile } from "node:child_process";
3
3
  import { promisify } from "node:util";
4
4
  const execFileAsync = promisify(execFile);
5
5
  // ── Constants ────────────────────────────────────────────────────────────────
6
- export const AGENT_PACKAGE = "com.ms.square.geomcpagent";
7
- export const AGENT_PORT = 5005;
8
6
  const ADB_TIMEOUT_MS = 10_000;
9
- // Covers emulator serials, USB serials, IPv4:port, and bracketed IPv6:port (e.g. [::1]:5555)
10
- const DEVICE_ID_PATTERN = /^(\[[a-fA-F0-9.:]+\](:\d+)?|[a-zA-Z0-9._:\-]+)$/;
11
- function isExecError(err) {
12
- return err instanceof Error && "stderr" in err;
7
+ /** Only emulator serials are accepted (e.g. emulator-5554). */
8
+ const EMULATOR_ID_PATTERN = /^emulator-\d+$/;
9
+ export function isExecError(err) {
10
+ if (!(err instanceof Error) || !("stderr" in err))
11
+ return false;
12
+ const { stderr } = err;
13
+ return stderr === undefined || typeof stderr === "string";
13
14
  }
14
15
  // ── Validation ───────────────────────────────────────────────────────────────
15
16
  function validateDeviceId(deviceId) {
16
- if (!DEVICE_ID_PATTERN.test(deviceId)) {
17
- throw new Error(`Invalid device ID: ${deviceId}`);
17
+ if (!EMULATOR_ID_PATTERN.test(deviceId)) {
18
+ throw new Error(`Invalid emulator ID: ${deviceId}. Only Android emulators are supported (e.g. emulator-5554).`);
18
19
  }
19
20
  }
21
+ /** Check if a device ID matches the emulator serial pattern. */
22
+ export function isEmulator(deviceId) {
23
+ return EMULATOR_ID_PATTERN.test(deviceId);
24
+ }
20
25
  // ── Primitives ───────────────────────────────────────────────────────────────
21
- /** Device-scoped synchronous ADB command. Always validates deviceId and applies timeout. */
26
+ /** Emulator-scoped synchronous ADB command. Validates deviceId and applies timeout. */
22
27
  export function adbDevice(deviceId, args, timeoutMs = ADB_TIMEOUT_MS) {
23
28
  validateDeviceId(deviceId);
24
29
  return execFileSync("adb", ["-s", deviceId, ...args], {
@@ -39,61 +44,4 @@ export async function adb(args, timeoutMs = ADB_TIMEOUT_MS) {
39
44
  export async function listDevices() {
40
45
  return adb(["devices", "-l"]);
41
46
  }
42
- /**
43
- * Check whether the agent APK is installed on the device.
44
- * Returns true if installed, false if not installed.
45
- * Throws if the ADB command fails for other reasons (device offline, unauthorized, etc.).
46
- */
47
- export function isAgentInstalled(deviceId) {
48
- try {
49
- const output = adbDevice(deviceId, ["shell", "pm", "path", AGENT_PACKAGE]);
50
- return output.includes("package:");
51
- }
52
- catch (err) {
53
- // `pm path` exits with code 1 and empty stderr when the package is not found.
54
- // ADB-level failures (device offline, unauthorized) also exit with code 1 but
55
- // include an error message on stderr — those should propagate.
56
- if (isExecError(err) && err.status === 1 && !err.stderr?.trim()) {
57
- return false;
58
- }
59
- throw err;
60
- }
61
- }
62
- /**
63
- * Ensure the device is set up for mock location: grant location permissions
64
- * and set this app as the mock location provider via AppOps.
65
- * All commands are idempotent — safe to call on every connection attempt.
66
- */
67
- export function ensureDeviceSetup(deviceId) {
68
- adbDevice(deviceId, ["shell", "pm", "grant", AGENT_PACKAGE, "android.permission.ACCESS_FINE_LOCATION"]);
69
- adbDevice(deviceId, ["shell", "pm", "grant", AGENT_PACKAGE, "android.permission.ACCESS_COARSE_LOCATION"]);
70
- adbDevice(deviceId, ["shell", "appops", "set", AGENT_PACKAGE, "android:mock_location", "allow"]);
71
- }
72
- /**
73
- * Start the MockLocationService directly via ADB.
74
- * The service is exported, so it can be started without launching the Activity.
75
- */
76
- export function startAgentService(deviceId) {
77
- adbDevice(deviceId, [
78
- "shell",
79
- "am",
80
- "start-foreground-service",
81
- "-n",
82
- `${AGENT_PACKAGE}/.MockLocationService`,
83
- ]);
84
- }
85
- /**
86
- * Set up ADB port forwarding for the agent TCP socket.
87
- * Synchronous — throws on failure with stderr context.
88
- */
89
- export function adbForward(deviceId) {
90
- try {
91
- adbDevice(deviceId, ["forward", `tcp:${AGENT_PORT}`, `tcp:${AGENT_PORT}`]);
92
- }
93
- catch (err) {
94
- const msg = err instanceof Error ? err.message : String(err);
95
- const stderr = isExecError(err) ? err.stderr?.trim() : undefined;
96
- throw new Error(`Failed to set up adb port forwarding for ${deviceId}: ${msg}${stderr ? ` (${stderr})` : ""}`);
97
- }
98
- }
99
47
  //# sourceMappingURL=adb.js.map
package/dist/adb.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"adb.js","sourceRoot":"","sources":["../src/adb.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"adb.js","sourceRoot":"","sources":["../src/adb.ts"],"names":[],"mappings":"AAAA,gFAAgF;AAEhF,OAAO,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC5D,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAEtC,MAAM,aAAa,GAAG,SAAS,CAAC,QAAQ,CAAC,CAAC;AAE1C,gFAAgF;AAEhF,MAAM,cAAc,GAAG,MAAM,CAAC;AAC9B,+DAA+D;AAC/D,MAAM,mBAAmB,GAAG,gBAAgB,CAAC;AAS7C,MAAM,UAAU,WAAW,CAAC,GAAY;IACtC,IAAI,CAAC,CAAC,GAAG,YAAY,KAAK,CAAC,IAAI,CAAC,CAAC,QAAQ,IAAI,GAAG,CAAC;QAAE,OAAO,KAAK,CAAC;IAChE,MAAM,EAAE,MAAM,EAAE,GAAG,GAAgB,CAAC;IACpC,OAAO,MAAM,KAAK,SAAS,IAAI,OAAO,MAAM,KAAK,QAAQ,CAAC;AAC5D,CAAC;AAED,gFAAgF;AAEhF,SAAS,gBAAgB,CAAC,QAAgB;IACxC,IAAI,CAAC,mBAAmB,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC;QACxC,MAAM,IAAI,KAAK,CACb,wBAAwB,QAAQ,8DAA8D,CAC/F,CAAC;IACJ,CAAC;AACH,CAAC;AAED,gEAAgE;AAChE,MAAM,UAAU,UAAU,CAAC,QAAgB;IACzC,OAAO,mBAAmB,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;AAC5C,CAAC;AAED,gFAAgF;AAEhF,uFAAuF;AACvF,MAAM,UAAU,SAAS,CAAC,QAAgB,EAAE,IAAc,EAAE,SAAS,GAAG,cAAc;IACpF,gBAAgB,CAAC,QAAQ,CAAC,CAAC;IAC3B,OAAO,YAAY,CAAC,KAAK,EAAE,CAAC,IAAI,EAAE,QAAQ,EAAE,GAAG,IAAI,CAAC,EAAE;QACpD,QAAQ,EAAE,OAAO;QACjB,OAAO,EAAE,SAAS;KACnB,CAAC,CAAC;AACL,CAAC;AAED,oDAAoD;AACpD,MAAM,CAAC,KAAK,UAAU,GAAG,CAAC,IAAc,EAAE,SAAS,GAAG,cAAc;IAClE,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,aAAa,CAAC,KAAK,EAAE,IAAI,EAAE;QAClD,QAAQ,EAAE,OAAO;QACjB,OAAO,EAAE,SAAS;KACnB,CAAC,CAAC;IACH,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,gFAAgF;AAEhF,uEAAuE;AACvE,MAAM,CAAC,KAAK,UAAU,WAAW;IAC/B,OAAO,GAAG,CAAC,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC,CAAC;AAChC,CAAC"}
@@ -0,0 +1,52 @@
1
+ /** Register a callback invoked when the emulator disconnects. */
2
+ export declare function onDisconnect(cb: () => void): void;
3
+ export declare function getConnectedDeviceId(): string | null;
4
+ export declare function isConnected(): boolean;
5
+ /**
6
+ * Connect to an emulator by device ID.
7
+ * Validates the emulator is reachable via ADB.
8
+ */
9
+ export declare function connectEmulator(deviceId: string): void;
10
+ export interface LocationParams {
11
+ lat: number;
12
+ lng: number;
13
+ altitude?: number;
14
+ /** Speed in meters per second. Converted to knots for `geo fix`. */
15
+ speed?: number;
16
+ }
17
+ /**
18
+ * Set mock location on the connected emulator via `geo fix`.
19
+ *
20
+ * `geo fix` updates the emulator's stored GPS state, so the 1 Hz
21
+ * PassiveGpsUpdater loop keeps broadcasting the new position.
22
+ */
23
+ export declare function setLocation(params: LocationParams): void;
24
+ export interface EmulatorLocation {
25
+ lat: number;
26
+ lng: number;
27
+ accuracy?: number;
28
+ }
29
+ /**
30
+ * Parse a Location line from `dumpsys location` output.
31
+ *
32
+ * Android's Location.toString() format (API 26+):
33
+ * Location[gps 37.421998,-122.084000 hAcc=20.0 ...]
34
+ * Location[fused 37.421998,-122.084000 acc=5.0 ...]
35
+ *
36
+ * We extract lat, lng and optional accuracy (hAcc= or acc=).
37
+ */
38
+ export declare function parseLocationLine(line: string): EmulatorLocation | null;
39
+ /**
40
+ * Get the emulator's current GPS location via `adb shell dumpsys location`.
41
+ *
42
+ * Parses the "Last Known Locations" section from the location service dump
43
+ * and returns the GPS provider's last fix. Falls back to the fused provider
44
+ * if GPS is not available.
45
+ *
46
+ * Returns null if no location is available.
47
+ */
48
+ export declare function getLocation(): EmulatorLocation | null;
49
+ /** Initialize emulator module. No-op — no background connections needed. */
50
+ export declare function initEmulator(): void;
51
+ /** Shut down emulator module — clean up state. */
52
+ export declare function shutdownEmulator(): void;
@@ -0,0 +1,175 @@
1
+ // ── Emulator location control via `geo fix` ──────────────────────────────────
2
+ //
3
+ // Sets mock locations on the emulator using `adb emu geo fix` which updates
4
+ // the emulator's stored GPS state. Unlike `geo nmea`, `geo fix` survives the
5
+ // emulator's 1 Hz PassiveGpsUpdater loop that re-sends from stored state.
6
+ //
7
+ // Format: geo fix <longitude> <latitude> [<altitude> [<satellites> [<velocity>]]]
8
+ // - longitude/latitude in decimal degrees (longitude FIRST)
9
+ // - altitude in meters
10
+ // - satellites count (integer)
11
+ // - velocity in knots
12
+ import { adbDevice, isExecError } from "./adb.js";
13
+ // ── State ────────────────────────────────────────────────────────────────────
14
+ let connectedDeviceId = null;
15
+ let disconnectCallback = null;
16
+ // ── Public API ───────────────────────────────────────────────────────────────
17
+ /** Register a callback invoked when the emulator disconnects. */
18
+ export function onDisconnect(cb) {
19
+ disconnectCallback = cb;
20
+ }
21
+ export function getConnectedDeviceId() {
22
+ return connectedDeviceId;
23
+ }
24
+ export function isConnected() {
25
+ return connectedDeviceId !== null;
26
+ }
27
+ /**
28
+ * Connect to an emulator by device ID.
29
+ * Validates the emulator is reachable via ADB.
30
+ */
31
+ export function connectEmulator(deviceId) {
32
+ // Disconnect any previously connected emulator first
33
+ if (connectedDeviceId !== null && connectedDeviceId !== deviceId) {
34
+ disconnectEmulator();
35
+ }
36
+ // Verify the emulator is reachable without side-effects (no location change).
37
+ // ro.kernel.qemu is "1" on emulators — this confirms ADB connectivity.
38
+ try {
39
+ adbDevice(deviceId, ["shell", "getprop", "ro.kernel.qemu"]);
40
+ }
41
+ catch (err) {
42
+ const msg = err instanceof Error ? err.message : String(err);
43
+ const stderr = isExecError(err) ? err.stderr?.trim() : undefined;
44
+ throw new Error(`Failed to reach emulator ${deviceId}: ${msg}${stderr ? ` (${stderr})` : ""}\n` +
45
+ "Ensure the emulator is running and ADB is connected.");
46
+ }
47
+ connectedDeviceId = deviceId;
48
+ }
49
+ /** Disconnect from the current emulator. */
50
+ function disconnectEmulator() {
51
+ const wasConnected = connectedDeviceId !== null;
52
+ connectedDeviceId = null;
53
+ if (wasConnected) {
54
+ try {
55
+ disconnectCallback?.();
56
+ }
57
+ catch {
58
+ // Ignore callback errors
59
+ }
60
+ }
61
+ }
62
+ const MS_TO_KNOTS = 1.94384;
63
+ /**
64
+ * Set mock location on the connected emulator via `geo fix`.
65
+ *
66
+ * `geo fix` updates the emulator's stored GPS state, so the 1 Hz
67
+ * PassiveGpsUpdater loop keeps broadcasting the new position.
68
+ */
69
+ export function setLocation(params) {
70
+ if (!connectedDeviceId) {
71
+ throw new Error("Not connected to an emulator. Call geo_connect_device first with an emulator serial (e.g. emulator-5554).");
72
+ }
73
+ // geo fix <longitude> <latitude> [<altitude> [<satellites> [<velocity>]]]
74
+ const args = [
75
+ "emu", "geo", "fix",
76
+ params.lng.toFixed(6),
77
+ params.lat.toFixed(6),
78
+ ];
79
+ const altitude = params.altitude ?? 0;
80
+ const satellites = 8;
81
+ args.push(altitude.toFixed(1));
82
+ args.push(satellites.toString());
83
+ if (params.speed != null && params.speed > 0) {
84
+ args.push((params.speed * MS_TO_KNOTS).toFixed(1));
85
+ }
86
+ try {
87
+ adbDevice(connectedDeviceId, args);
88
+ }
89
+ catch (err) {
90
+ const msg = err instanceof Error ? err.message : String(err);
91
+ // If ADB fails, the emulator may have been closed
92
+ disconnectEmulator();
93
+ throw new Error(`Failed to set location: ${msg}`);
94
+ }
95
+ }
96
+ /**
97
+ * Parse a Location line from `dumpsys location` output.
98
+ *
99
+ * Android's Location.toString() format (API 26+):
100
+ * Location[gps 37.421998,-122.084000 hAcc=20.0 ...]
101
+ * Location[fused 37.421998,-122.084000 acc=5.0 ...]
102
+ *
103
+ * We extract lat, lng and optional accuracy (hAcc= or acc=).
104
+ */
105
+ export function parseLocationLine(line) {
106
+ // Match "Location[<provider> <lat>,<lng>" pattern
107
+ const coordMatch = line.match(/Location\[\S+\s+(-?[\d.]+),(-?[\d.]+)/);
108
+ if (!coordMatch)
109
+ return null;
110
+ const lat = parseFloat(coordMatch[1]);
111
+ const lng = parseFloat(coordMatch[2]);
112
+ if (Number.isNaN(lat) || Number.isNaN(lng))
113
+ return null;
114
+ // Try to extract accuracy (hAcc= in newer Android, acc= in older)
115
+ const accMatch = line.match(/(?:hAcc|acc)=([\d.]+)/);
116
+ const accuracy = accMatch ? parseFloat(accMatch[1]) : undefined;
117
+ return { lat, lng, accuracy };
118
+ }
119
+ /**
120
+ * Get the emulator's current GPS location via `adb shell dumpsys location`.
121
+ *
122
+ * Parses the "Last Known Locations" section from the location service dump
123
+ * and returns the GPS provider's last fix. Falls back to the fused provider
124
+ * if GPS is not available.
125
+ *
126
+ * Returns null if no location is available.
127
+ */
128
+ export function getLocation() {
129
+ if (!connectedDeviceId) {
130
+ throw new Error("Not connected to an emulator. Call geo_connect_device first.");
131
+ }
132
+ let output;
133
+ try {
134
+ output = adbDevice(connectedDeviceId, ["shell", "dumpsys", "location"]);
135
+ }
136
+ catch (err) {
137
+ const msg = err instanceof Error ? err.message : String(err);
138
+ throw new Error(`Failed to query emulator location: ${msg}`);
139
+ }
140
+ // Look for "Last Known Locations:" section and parse GPS/fused provider lines
141
+ const lines = output.split("\n");
142
+ let inLastKnown = false;
143
+ let gpsLocation = null;
144
+ let fusedLocation = null;
145
+ for (const line of lines) {
146
+ const trimmed = line.trim();
147
+ if (trimmed.startsWith("Last Known Locations:")) {
148
+ inLastKnown = true;
149
+ continue;
150
+ }
151
+ // End of section: blank line or new non-indented section header
152
+ if (inLastKnown && (trimmed === "" || (trimmed !== "" && !line.startsWith(" ") && !line.startsWith("\t")))) {
153
+ break;
154
+ }
155
+ if (inLastKnown) {
156
+ if (trimmed.startsWith("gps:") || trimmed.includes("Location[gps")) {
157
+ gpsLocation = parseLocationLine(trimmed);
158
+ }
159
+ else if (trimmed.startsWith("fused:") || trimmed.includes("Location[fused")) {
160
+ fusedLocation = parseLocationLine(trimmed);
161
+ }
162
+ }
163
+ }
164
+ // Prefer GPS, fall back to fused
165
+ return gpsLocation ?? fusedLocation ?? null;
166
+ }
167
+ /** Initialize emulator module. No-op — no background connections needed. */
168
+ export function initEmulator() {
169
+ // Nothing to do
170
+ }
171
+ /** Shut down emulator module — clean up state. */
172
+ export function shutdownEmulator() {
173
+ disconnectEmulator();
174
+ }
175
+ //# sourceMappingURL=emulator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"emulator.js","sourceRoot":"","sources":["../src/emulator.ts"],"names":[],"mappings":"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"}
@@ -1,4 +1,2 @@
1
1
  /** Haversine distance between two points in meters. */
2
2
  export declare function haversineDistance(lat1: number, lng1: number, lat2: number, lng2: number): number;
3
- /** Forward azimuth (bearing) in degrees 0-360 from point 1 to point 2. */
4
- export declare function computeBearing(lat1: number, lng1: number, lat2: number, lng2: number): number;
package/dist/geo-math.js CHANGED
@@ -9,12 +9,4 @@ export function haversineDistance(lat1, lng1, lat2, lng2) {
9
9
  const a = Math.sin(dLat / 2) ** 2 + Math.cos(toRad(lat1)) * Math.cos(toRad(lat2)) * Math.sin(dLng / 2) ** 2;
10
10
  return R * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
11
11
  }
12
- /** Forward azimuth (bearing) in degrees 0-360 from point 1 to point 2. */
13
- export function computeBearing(lat1, lng1, lat2, lng2) {
14
- const dLng = toRad(lng2 - lng1);
15
- const y = Math.sin(dLng) * Math.cos(toRad(lat2));
16
- const x = Math.cos(toRad(lat1)) * Math.sin(toRad(lat2)) -
17
- Math.sin(toRad(lat1)) * Math.cos(toRad(lat2)) * Math.cos(dLng);
18
- return (toDeg(Math.atan2(y, x)) + 360) % 360;
19
- }
20
12
  //# sourceMappingURL=geo-math.js.map