This document details the system architecture, component boundaries, privacy guarantees, and design decisions of MapFlip.
MapFlip is designed as a 100% offline, privacy-first intent router for Android. Its primary responsibility is intercepting map URLs and geo-intents from proprietary or web-based services (such as Apple Maps or DuckDuckGo) and redirecting them directly into native navigation apps on Android.
flowchart TD
subgraph Inputs ["Input Channels"]
A1["Web Link / Browser Click\n(ACTION_VIEW)"]
A2["Android Share Sheet\n(ACTION_SEND)"]
A3["Clipboard on Resume\n(ON_RESUME Banner)"]
A4["Interactive Link Tester\n(In-App UI)"]
end
subgraph Entry ["Entry Point & Interception"]
B["RedirectActivity\n(Translucent, NoHistory)"]
end
subgraph ParserLayer ["Universal Parser Pipeline"]
C["UniversalMapParser\n(Strategy Pattern)"]
C1["AppleMapsParser"]
C2["GoogleMapsParser"]
C3["OpenStreetMapParser"]
C4["BingMapsParser"]
C5["HereMapsParser"]
C6["YandexMapsParser"]
C7["WazeMapsParser"]
C8["DuckDuckGoParser"]
C9["PlusCodeParser\n(OLC Base20 Offline Decoder)"]
C10["GeoCoordinateParser\n(RFC 5870, DMS, Decimal)"]
C11["MapyMapsParser\n(Mapy.cz & Mapy.com)"]
end
subgraph Model ["Normalized Data Model"]
D["ParsedLocation\n- Coordinates (Lat/Lon)\n- Query / Address\n- TravelMode (Drive/Walk/Bike/Transit)"]
end
subgraph Dispatcher ["Target Dispatcher"]
E["NavigationIntentBuilder"]
F{"Pause Active?"}
G["Forward Original URL\nto Default Browser"]
end
subgraph Targets ["Supported Navigation Apps (15 Destinations)"]
H1["Google Maps"]
H2["Waze"]
H3["Organic Maps (FOSS)"]
H4["CoMaps (FOSS)"]
H5["OsmAnd / OsmAnd+ (FOSS)"]
H6["Vela (FOSS)"]
H7["Mapy.cz"]
H8["HERE WeGo"]
H9["Yandex Maps"]
H10["Magic Earth"]
H11["Citymapper"]
H12["Komoot"]
H13["TomTom AmiGO"]
H14["Sygic"]
H15["Locus Map / Pro"]
H16["System App Picker"]
end
A1 --> B
A2 --> B
A3 --> B
A4 --> C
B --> F
F -- "Yes" --> G
F -- "No" --> C
C --> C1 & C2 & C3 & C4 & C5 & C6 & C7 & C8 & C9 & C10 & C11
C1 & C2 & C3 & C4 & C5 & C6 & C7 & C8 & C9 & C10 & C11 --> D
D --> E
E --> H1 & H2 & H3 & H4 & H5 & H6 & H7 & H8 & H9 & H10 & H11 & H12 & H13 & H14 & H15 & H16
The parsing layer implements the Strategy Pattern. Each map source is an isolated, independently unit-tested implementation of MapUrlParser:
AppleMapsParser: Parsesmaps.apple.comandapplemaps://queries (?q=,?ll=,?address=,?saddr=&daddr=, place slugs/p/...).GoogleMapsParser: Handlesmaps.google.com,google.com/maps,goo.gl/maps, and international ccTLD domains (google.es,google.co.uk,google.fr, etc.).OpenStreetMapParser: Extracts nodes, ways, relations, and bounding-box coordinates fromopenstreetmap.organdosm.org.BingMapsParser: Parsesbing.com/mapscoordinates (cp=) and search queries.HereMapsParser: Supports path-based coordinates (/l/lat,lon,zoom) and query parameters (map=lat,lon).YandexMapsParser: Handles Yandex inverted coordinate order (ll=lon,lat).WazeMapsParser: Extracts destination coordinates fromwaze.com/ulandlive-map/directions.DuckDuckGoParser: Handles map search and directions links via Share Sheet, Clipboard detection, and interactive Link Tester.PlusCodeParser: 100% offline mathematical decoding of Open Location Codes (Google Plus Codes, Base20 algorithm) without network lookup.GeoCoordinateParser: Extracts standardgeo:URIs (RFC 5870), Degree-Minute-Second (DMS) coordinates, and decimal coordinate pairs with strict sanity filtering.MapyMapsParser: Parsesmapy.czandmapy.comcoordinates (longitude first), searches, and routes.
All parsers share consistent URL extraction, punctuation stripping, and balanced parenthesis preservation via trimExtractedUrl(), ensuring coordinates or queries embedded inside text or parentheses (e.g. (https://.../?q=Place+(City))) remain valid.
Translates the normalized ParsedLocation into platform-native intents:
- App-Specific Schemes: Directly targets installed navigation apps via custom URI schemes and package targets (e.g.
geo:,google.navigation:,waze://,om://,cm://,yandexmaps://,citymapper://,komoot://,tomtomgo://,https://share.here.com/r/,https://mapy.com/fnc/v1/). - Travel Mode Preservation: Carries over driving, walking, cycling, and public transit modes from source links into compatible navigation intents.
- Smart Fallback & Default Browser Resolution: If the user's chosen navigation app was uninstalled, MapFlip catches the
ActivityNotFoundException, displays a localized toast notice, and gracefully falls back to Google Maps, the system app chooser, or normalized web fallbacks (prioritizing the user's default browser viaMATCH_DEFAULT_ONLY).
Android 12 (API 31+) restricts unverified HTTP/HTTPS link handling:
- MapFlip provides a visual
SetupGuideBottomSheetthat guides users through link activation in 3 numbered steps. DomainVerificationManagerdynamically inspectshostToStateMapto determine whether all declared domain hosts (32 hosts across supported map services) are verified or selected (DOMAIN_STATE_SELECTED/DOMAIN_STATE_VERIFIED).- Live badge in the main screen reflects real-time link enablement status.
PauseHelper: Allows users to pause redirection for 1 hour, 8 hours, until tomorrow morning (06:00 AM with proper timezone boundary arithmetic), or indefinitely.MapFlipTileService: System Quick Settings tile allowing 1-tap toggling of the pause state directly from the notification shade without launching the app.
MapFlip is built with a strict separation between open-source distribution and store distribution:
| Feature / Property | foss Flavor (F-Droid & GitHub) |
play Flavor (Google Play Store) |
|---|---|---|
| Internet Permission | None (android.permission.INTERNET omitted) |
Declared for anonymous telemetry |
| Telemetry Implementation | 100% No-Op Stub (Analytics.kt empty methods) |
Lightweight HTTP dispatcher for self-hosted Aptabase |
| Telemetry Payload | Zero (no network sockets possible at kernel level) | Anonymous event name & app version (no coordinates or URLs) |
| Google Play Services | None | In-App Review API prompt only |
| Monetization / Ads | Zero | Zero |
In the foss flavor, aapt2 dump permissions confirms that android.permission.INTERNET is completely absent. Because the Linux kernel does not assign GID 3003 (AID_INET) to the app's process, it is mathematically and physically impossible for the app to open network sockets.
- Unit Test Suite: 200+ unit, integration, and Compose UI tests covering parsers, timezones, encoding, locales, and domain verification.
- Clean Code & Zero External Dependencies: The core FOSS build relies solely on AndroidX and standard Kotlin/Compose libraries.