Skip to content

Feature:Routs **⚠️ AI-GENERATED — REVIEW REQUIRED**#45

Open
ro011110ot wants to merge 11 commits into
noobexon1:masterfrom
ro011110ot:feature/Routen
Open

Feature:Routs **⚠️ AI-GENERATED — REVIEW REQUIRED**#45
ro011110ot wants to merge 11 commits into
noobexon1:masterfrom
ro011110ot:feature/Routen

Conversation

@ro011110ot

Copy link
Copy Markdown

⚠️ AI-GENERATED — REVIEW REQUIRED

This feature was implemented with substantial assistance from a large language
model (AI). The code compiles, installs, and passes basic manual testing on a
Redmi Note 12 Pro 4G, Android 16 [LinageO 32.2] LSPosed 1.9.2, but it has not undergone a thorough
security audit, edge-case review, or production-readiness assessment. Treat
this as a suggestion / starting point rather than a final, shippable
implementation. Review all files carefully before merging.

Summary

This PR adds a complete Route Management system to XposedFakeLocation — users can
create, edit, delete, and playback routes (sequences of waypoints that the fake location
follows automatically). It also fixes two bugs found during development (dialog selection
conflict and stale-position delivery to target apps) and addresses UI flickering on the
route minimap.

Changes

New Feature — Route Management

  • Data model: Route and RouteWaypoint classes, stored via PreferencesRepository
  • Route list screen (RoutesScreen.kt + RoutesViewModel.kt): list all saved
    routes, swipe-to-delete, tap to open details
  • Route detail screen (RouteDetailScreen.kt + RouteDetailViewModel.kt):
    • Minimap preview with numbered waypoints and polyline (RouteMapView.kt)
    • Playback controls: play/stop, speed slider (1–50 m/s), loop toggle
    • Waypoint reordering (drag up/down), deletion, and inline rename
    • Real-time position tracking indicator on the detail screen
  • Add-to-route dialog (MapDialogs.kt + MapViewModel.kt): add the current spoof
    marker location as a waypoint to an existing route or create a new route
    • Handles initially empty route name edge case (Bug 1 fix)
  • Graphical preview on main map (MapViewEffects.kt): when a route is playing,
    the main map shows numbered markers and a polyline for the active route; a
    movable position marker follows the current playback progress
  • Route player engine (RoutePlayer.kt): interpolates position between waypoints
    using Haversine distance, runs as a scheduled timer in the Xposed target process,
    writes current lat/lon directly into LocationUtil fields
  • System-level hook wiring (LocationApiHooks.kt): getLatitude() and
    getLongitude() hooks call LocationUtil.updateLocation(), which reads the
    route player's computed position
  • Playback position persistence: current lat/lon written to shared prefs every 500ms;
    manager polls every 2s to update the real-time indicator

Bug Fixes

  1. AddToRouteDialog selection conflict (MapViewModel.kt):

    • onNewRouteNameChange now clears selectedRouteName so the dialog correctly
      targets the new-route input
    • onRouteSelectionChange clears newRouteNameInput when an existing route is
      picked, preventing a stale blank input from overriding the selection
  2. Stale position delivery to target apps (LocationUtil.kt):

    • createFakeLocation() now calls updateLocation() internally before building
      the Location object
    • Previously, system-level hooks (getLatitude() / getLongitude() in
      LocationApiHooks.kt) only called updateLocation() themselves, but
      LocationManager.getLastKnownLocation() and other APIs that call
      createFakeLocation() directly received stale 0.0 / 0.0 coordinates
    • Root cause: RoutePlayer.computeAndSetPosition() writes into LocationUtil
      fields, but createFakeLocation() never refreshed them
    • The fix ensures all code paths that fabricate a Location use the most recent
      route position
  3. Route minimap flickering (RouteMapView.kt):

    • LaunchedEffect(waypoints) waited for the MapView to be laid out before
      drawing (using withFrameNanos polling until width > 0 && height > 0)
    • zoomToBoundingBox animation disabled (false + post to main thread)
    • Previously the maLine Awesome p would flash / jump because overlays.clear() + animated
      zoom ran before the view had its final size

Technical Improvements

  • ANR prevention (MapViewEffects.kt): guard all zoomToBoundingBox calls with
    mapView.width > 0 && mapView.height > 0; moved PreferencesRepository IPC
    off the main thread with Dispatchers.IO
  • Performance (RoutePlayer.kt): added minimum advance interval guard to
    prevent micro-delta stagnation; removed stale lastUpdateTime reset that caused
    position resets
  • German UI: values-de/strings.xml with 219 translated strings
  • Chinese UI: values-zh/strings.xml with 21 translated strings
  • All comments are in English (per project convention); German-only inline
    comments in RouteDetailScreen.kt were translated

Testing

  • Device: Redmi Note 12 Pro 4G, Android 16 [LinageO 32.2]
  • Xposed Framework: LSPosed 1.9.2
  • Manager UI: routes are creatable, editable, deletable; minimap renders
    without flicker; playback starts/stops correctly
  • Target app hooking: route position advances (confirmed via [RoutePlayer]
    logcat output showing increasing elapsed/dist/lat/lon values);
    createFakeLocation() delivers the correct position to all hook sites

Known Limitations

  • Cannot add waypoints from the detail screen yet (placeholder TODO)
  • Route playback stops when the manager app process dies (no persistent service)
  • Speed is global, not per-segment

Follow-up Work

  • Implement "Add waypoint from current location" in the detail screen
  • Consider storing routes in a more robust format (JSON file / Room DB)
  • Add route import/export
  1. Detailed Git Commit Message
    feat(routes): complete route management system with playback engine

This commit adds a full Route Management feature — users can create, edit,
delete, and playback sequences of waypoints on a minimap and on the main map.

New files (14):

  • data/model/Route.kt — Route data class
  • data/model/RouteWaypoint.kt — RouteWaypoint data class
  • data/repository/PrefrencesRepository.kt — persistence (get/put/remove routes,
    active route waypoints, playback speed/loop/state, current position)
  • manager/ui/routes/RoutesScreen.kt — route list overview
  • manager/ui/routes/RoutesViewModel.kt — ViewModel for route list
  • manager/ui/routes/RoutesModel.kt — UI state for route list
  • manager/ui/routes/RouteDetailScreen.kt — detail screen with controls
  • manager/ui/routes/RouteDetailViewModel.kt — ViewModel for detail screen
  • manager/ui/routes/RouteDetailModel.kt — UI state for detail screen
  • manager/ui/routes/RouteMapView.kt — osmdroid minimap composable
  • manager/ui/routes/RouteMapUtils.kt — numbered marker drawable factory
  • xposed/utils/RoutePlayer.kt — route interpolation/scheduling engine
  • xposed/hooks/LocationApiHooks.kt — system-level getLatitude/getLongitude hooks
  • manager/ui/map/MapViewEffects.kt — main-map overlay for active route

Modified files (6):

  • xposed/utils/LocationUtil.kt:
    • createFakeLocation() now calls updateLocation() so all callers (including
      LocationManager.getLastKnownLocation()) use the current route position
  • manager/ui/map/MapViewModel.kt:
    • AddToRouteDialog: onNewRouteNameChange clears selectedRouteName,
      onRouteSelectionChange clears newRouteNameInput
    • New fields: isPlaying, activeRouteWaypoints, currentRoutePosition,
      availableRouteNames, selectedRouteName, newRouteNameInput
    • Polling loop (every 2s) reads current route lat/lon from prefs
  • manager/ui/map/MapDialogs.kt:
    • AddToRouteDialog composable with existing-route/new-route toggle
  • manager/ui/map/MapScreen.kt / Drawer.kt:
    • "Routes" navigation entry, drawer re-open handling
  • manager/ui/navigation/NavGraph.kt / Screen.kt:
    • RouteDetail route destination
  • data/Constants.kt: route-related default values

i18n:

  • values-de/strings.xml (219 translations)
  • values-zh/strings.xml (21 translations)
  • values/strings.xml (21 new route strings)

Bug fixes bundled:

  • AddToRouteDialog: mutually exclusive route-name / new-name selection
  • LocationUtil.createFakeLocation(): call updateLocation() before building
    the Location so system-level hooks get the current route position
  • RouteMapView: wait for MapView layout before drawing; disable zoom animation
  • MapViewEffects.kt: guard zoomToBoundingBox with width/height > 0 check
  • RoutePlayer: minimum advance interval guard, removed stale lastUpdateTime reset

All inline comments are in English per project convention.
3. Route Feature Overview (for project discussion / documentation)

Route Management Feature — Overview

What it does

Routes let users define a sequence of GPS waypoints. When playback is active,
the Xposed module automatically moves the device's fake location along the
route, interpolating between waypoints at a configurable speed.

Architecture

Data Layer

  • Route (data/model/Route.kt): holds a name and a list of RouteWaypoints
  • RouteWaypoint (data/model/RouteWaypoint.kt): lat/lon, name, sort order
  • PreferencesRepository (data/repository/PrefrencesRepository.kt):
    Routes are serialized as JSON via Gson and stored in SharedPreferences.
    The active-route waypoints, playback speed, loop flag, and is-playing state
    are mirrored into a second preferences file accessible from the Xposed
    target process (see PreferencesUtil.kt).

Manager App (UI)

All composables live under manager/ui/routes/:

Screen File ViewModel
Route list RoutesScreen.kt RoutesViewModel.kt
Route detail RouteDetailScreen.kt RouteDetailViewModel.kt
Minimap RouteMapView.kt — (stateless composable)
Map dialog MapDialogs.kt (AddToRouteDialog) MapViewModel.kt
Main-map overlay MapViewEffects.kt — (effect extension)

Navigation: NavGraph.kt registers Screen.RouteDetail; the drawer in
Drawer.kt has a "Routes" entry. All map interactions (center, zoom, add-to-route)
share the existing MapViewModel.

Xposed Module (Target Process)

  • RoutePlayer.kt (xposed/utils/): singleton object that:

    1. On loadActiveRoute(): reads waypoints, speed, loop flag from the remote
      preferences (the same SharedPreferences the manager writes to)
    2. Pre-computes segment distances via Haversine
    3. Starts a 500ms scheduled timer
    4. On each tick, calls computeAndSetPosition() which interpolates along the
      route and writes LocationUtil.latitude / LocationUtil.longitude directly
    5. Persists the current lat/lon back to prefs for the manager's polling loop
  • LocationUtil.kt (xposed/utils/):

    • updateLocation() now calls RoutePlayer.loadActiveRoute() first, so a
      running route always takes precedence over the manual spoof location
    • createFakeLocation() calls updateLocation() — this is the critical fix
      that makes LocationManager.getLastKnownLocation() (and similar APIs used
      by apps like Pokémon GO) return the route position instead of 0.0 / 0.0
  • LocationApiHooks.kt (xposed/hooks/):

    • Hooks Location.getLatitude() and Location.getLongitude() at the class-
      loader level
    • Each hook calls updateLocation() and returns LocationUtil.latitude /
      LocationUtil.longitude when isPlaying == true
    • Also hooks getAccuracy(), getAltitude(), and provider-based methods

Main-Map Overlay

MapViewEffects.kt has an HandleActiveRouteOverlay effect that:

  • Listens to uiState.activeRouteWaypoints and uiState.currentRoutePosition
  • Draws numbered markers + polyline on the main osmdroid MapView
  • Places a distinct position marker at the current playback location
  • Re-draws only when waypoints or position actually change (avoids flicker)

Key Design Decisions

  1. Two-process communication via SharedPreferences (not Binder/AIDL):

    • Simpler to implement for a single-user Xposed module
    • The target process opens the manager's preferences file using the
      package context available to the Xposed hook
    • Latency is acceptable (500ms update interval)
  2. RoutePlayer writes directly to LocationUtil fields:

    • Avoids an extra Location object allocation on every hook invocation
    • The timer and the hooks are synchronized via @Volatile on the shared
      isActive flag
  3. Interpolation, not waypoint snapping:

    • Position travels smoothly between waypoints using linear interpolation
      on lat/lon (fine for short segments <1 km)
    • Haversine is used only for total-distance accounting, not for the
      interpolation itself

Known Limitations & Risks

  • No persistent service: if the target app process dies, the route timer
    stops. The user must restart playback from the manager.
  • SharedPreferences race: both processes write to the same preferences file;
    concurrent edits could theoretically lose data. Rare in practice because the
    manager mostly reads and the module mostly writes.
  • Memory: route data is held in memory in both processes. Very large routes
    (1000+ waypoints) may cause jank during serialization.
  • getLastKnownLocation() not hooked: only getLatitude() / getLongitude()
    on an existing Location object are hooked. Apps that call
    LocationManager.getLastKnownLocation() receive a newly-constructed Location
    from createFakeLocation(), which now works after the updateLocation() fix.
  • Speed is global: all route segments advance at the same speed. Future work
    could add per-segment speed metadata.

What to Review

  1. RoutePlayer.kt: timer lifecycle, thread safety, edge cases when the
    route has 1 or 0 waypoints, loop wrap-around
  2. LocationUtil.createFakeLocation(): the updateLocation() call changes
    behavior for all users, not just route users. Ensure manual spoof still works.
  3. MapViewEffects.kt: the overlay effect runs as a SideEffect that
    captures mapView by reference. Verify it doesn't leak across configuration
    changes.
  4. PrefrencesRepository.kt: the Gson serialization is fragile if Route
    or RouteWaypoint fields are renamed. Consider a migration strategy.
  5. Security: the target process reads the manager's preferences. Ensure no
    sensitive data leaks through this channel.
  6. strings.xml: new strings added in values/strings.xml, values-de/strings.xml,
    and values-zh/strings.xml. Other locales will show the English fallback for

… add stale-delta guard; translate all comments to English
…elta stagnation

Location getters (getLatitude, getLongitude, etc.) are hooked individually and
called in rapid succession by the target app (microseconds apart). Without a
minimum interval, every advance() call sees deltaSeconds ~ 0.000001, making
progress per call ~1e-10 of a segment — route appears stuck.

Add MIN_ADVANCE_INTERVAL_NANOS = 100ms so only one advance per window fires
with a real time delta. Also fix stale-delta guard on stop->restart to
reinitialize position properly.
…terval

- RoutePlayer.logger was never set in ModuleEntry, so all debug output
  was silently dropped. Now wired so logs appear in target app logcat.
- advance() now skips calls with <100ms delta to prevent micro-second
  stagnation from rapid Location getter invocations.
- Stale-delta guard (>2s) reinitializes position instead of jumping.
…ypoint Markers

- Add RouteMapView composable: embedded osmdroid mini-map in RouteDetailScreen showing the
  route as a blue Polyline connecting waypoints, with numbered Markers at each waypoint
- Add HandleActiveRouteOverlay effect: draws active route on the main MapScreen when route
  playback is active, auto-zooms to fit bounding box
- Wire activeRouteWaypoints through MapUiState, MapViewModel (collected from repository when
  isPlaying changes), MapViewContainer, and MapScreen
- Fix all remaining German comments to English across the codebase:
  - Route.kt: translate KDoc to English
  - RouteDetailScreen.kt: translate inline comments
  - MapViewModel.kt: translate KDoc, inline comments, and waypoint name strings
  - MapDialogs.kt: translate AddToRouteDialog KDoc
- Rename 'Wegpunkt' waypoint name strings to 'Waypoint' for consistency
…-time position tracking

Flickering fix:
- Remove early return in RouteMapView when waypoints empty — always render the map
- MapView now stays stable across recompositions, no layout shifts on data load

Numbered markers:
- Add RouteMapUtils.kt with createNumberedMarkerDrawable() — generates bitmap markers
  with a blue circle and white number overlay
- Update RouteMapView.kt and HandleActiveRouteOverlay to use numbered markers instead of
  default Marker icons
- Add createCurrentPositionDrawable() for the live position dot

Real-time position tracking:
- Add KEY_CURRENT_ROUTE_LAT/KEY_CURRENT_ROUTE_LON constants in Constants.kt
- Add getCurrentRouteLat()/getCurrentRouteLon() to PrefrencesRepository (reads from remote
  prefs written by RoutePlayer)
- RoutePlayer.persistCurrentPosition() now writes computed lat/lon to remote prefs every
  timer tick (every 500ms); clearCurrentPosition() resets on stop
- MapViewModel init now polls every 1500ms for current route position when isPlaying
- Add HandleCurrentRoutePosition composable in MapViewEffects.kt — green animated dot at
  the current route playback position on the main map
- Wire currentRoutePosition through MapUiState, MapViewContainer, and MapScreen

All code and comments are in English.
…ff main thread

- Guard HandleActiveRouteOverlay zoomToBoundingBox with view-size check
  to avoid infinite loop in Projection.getCloserPixel() on 0x0 layout
- Add flowOn(Dispatchers.IO) to getIsPlayingFlow and
  getLastClickedLocationFlow collections so LSPosed IPC runs off main
- Wrap getActiveRouteWaypoints, getCurrentRouteLat/Lon reads in
  withContext(IO) to prevent main-thread blocking
…sition advancement

Bug 1 — AddToRouteDialog: when user types a new route name, selectedRouteName
was not cleared, causing confirmAddToRoute() to use the previously selected
route instead of the typed name. Fix: onNewRouteNameChange now clears
selectedRouteName, onRouteSelectionChange clears newRouteNameInput.

Bug 2 — Route playback position never changed in target apps because
system-level hooks (SystemServicesHooks in system_server) and the per-app
getLastKnownLocation() hook called LocationUtil.createFakeLocation() without
first calling updateLocation(). They read LocationUtil.latitude from their
own process memory, which RoutePlayer never updates (it runs only in the
target app's process). Fix: updateLocation() is now called inside
createFakeLocation() so every fake location creation uses the up-to-date
position regardless of which process calls it.
…ing German comments

- Add lastUsedRouteName field to MapViewModel that tracks the most recently
  created or selected route name
- showAddToRouteDialog() now pre-selects lastUsedRouteName instead of always
  picking the first route in the list
- onRouteSelectionChange() records the user's choice as lastUsedRouteName
- confirmAddToRoute() records the target route after a successful save
- Remove debug Log statements added during diagnosis
- Translate remaining German inline comments in RouteDetailScreen.kt
  (Geschwindigkeit -> Playback speed, Schleifen-Modus -> Loop mode,
   Dialog vom MapScreen aus -> trigger dialog from MapScreen)
@noobexon1 noobexon1 self-assigned this Jun 23, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants