Repository navigation
Privacy
OpenVitals is designed as a local-first app. The local Android app is intentionally separate from any connected app work.
The formal policy users see is PRIVACY.md: Settings and the re-consent dialog link to it. This page explains the engineering behind it. Keep the two in step. A change here that touches what is read, stored or sent needs the same change there, with a new "Last updated" date.
The app asks every user to accept the policy again when that date changes. PreferencesRepository.CURRENT_PRIVACY_POLICY_VERSION holds the date, and PrivacyPolicyVersionTest fails when the constant and PRIVACY.md differ. So a new date always reaches users, and a typo fix that keeps the date does not. The re-consent dialog waits while the one-time "moved to OpenWatch" notice is on screen, so the two never stack.
The local app:
- Does not ship app-level internet permission.
- Does not create OpenVitals cloud accounts.
- Does not upload health data to an OpenVitals server.
- Does not include ads.
- Does not include an analytics SDK.
- Reads supported records through Health Connect.
- Writes supported records only after an explicit save, import, record, edit, or delete action.
Local app preferences may include onboarding state, acknowledged permission prompts, unit settings, language, theme, widget order, goals, reminders, Body Energy calibration, timer settings, paired device settings, display choices, and the cycle tracking contexts, age band and cycle reminder settings, which live in their own preferences file.
The manifest removes INTERNET, ACCESS_NETWORK_STATE, and ACCESS_WIFI_STATE from the merged manifest, so no library can quietly reintroduce network access. Android itself then makes network use impossible, which is stronger than a promise not to use it.
Every device feature is built around keeping that true:
- Phone-to-phone sync uses Bluetooth Classic (RFCOMM), not Wi-Fi. A Wi-Fi or TCP transfer would require
INTERNET; Bluetooth keeps it a direct link between the two phones. - Offline maps are imported as local map packs; nothing is downloaded.
- Diagnostics and import reports are generated on device and shared only if the user chooses to. They hold structure, not data: counts, record types, reasons and masked error text. See Text that leaves the app is structure, not data.
Watches and scales are not paired with OpenVitals. The separate OpenWatch app syncs them and writes what they record to Health Connect, which OpenVitals then reads like any other source.
With OpenWatch installed, the two apps also talk on the phone through OpenLink, a bound service with no network: the body profile is kept the same in both (the later change wins), the dashboard shows the watch OpenWatch syncs and can ask for a sync, and during an activity recording OpenWatch streams the watch's heart rate into it (openwatch/OpenWatchLink). OpenWatch decides what OpenVitals may do on its Connected apps screen, from OpenVitals' signing certificate. The link is bound only while OpenVitals is in the foreground or recording. Settings, OpenWatch says where it stands: not installed, waiting for approval, blocked, or connected with the watches it sees, what OpenWatch allows and when the body profile last crossed.
Sync copies Health Connect records from one phone to another over a paired Bluetooth Classic link. One phone is made discoverable and the other scans for it. The user chooses how far back to sync and which data categories to accept. Before any record moves, the phones agree on a fresh key and both show the same six digits; the user confirms on both that they match, which rules out a device in between. The records then travel encrypted with that key (AES-256-GCM), on top of Bluetooth's own encryption. The key is never stored. No server or account is involved at any point. See Sync with another phone.
Health Connect is the source of truth. OpenVitals reads Health Connect records to show dashboard summaries, detail screens, readiness, statistics, achievements, and insights.
Manual entries are saved back to Health Connect only when the user chooses to save them. Supported Apple Health export records are written to Health Connect only when the user imports an export file from Settings. OpenVitals-created records can be edited later; records created by other apps remain read-only.
Apple Health exports are copied into app-private local storage and analyzed there before the user chooses which categories to import. The staged compressed copy is reused for the pending background import, cleared after a successful import or analysis failure, replaced by a newly selected export, and removed with the app's private data. A copy from an analysis the user never finished is removed at the next app start once it is a day old, unless an import is queued or running. Import reports are generated locally when the user runs an import and chooses to copy or download the report. They hold the importer's counts, selected categories, reasons and log, with error text masked, and none of the records' values, times or sources, no file locations and no file names. The workouts imported without their route are listed on the import card and kept in a private file beside the report, never in it. Reports written before this was so are deleted at app start.
Health Connect stores medical records as FHIR resources, on Android 14 and newer with its medical records feature. See the proposal until the feature page exists.
- The 13 medical permissions are asked for in one request, only inside the medical records area, never with fitness permissions. They join
managedPermissionsso a grant is seen, and stay out of every set that onboarding or Settings asks for. - Records are shown as Health Connect holds them. Nothing is computed from them, and a value is flagged only when the source flagged it.
- Imports and exports are file based. Nothing is fetched from a server. The import report counts what was written, held back or refused by record type and reason, and holds no record's content or id, no file, source or app name and no address.
- A SMART Health Card can also be scanned with the camera.
CAMERAis asked for only when Scan is tapped; frames are analysed in memory and never stored. A card's signature is not checked, since that needs the issuer's keys from the network, and its records are tagged as not verified. - OpenVitals keeps no copy of records. The one exception is a saved document: an imported file the user chose to keep, in app-private storage with a Room index, not backed up.
- Phone-to-phone sync carries medical records in full when the category is selected on both phones. Saved documents stay on the phone.
Cycle tracking uses Health Connect records and is shown only after cycle permissions are granted explicitly. The day log's subjective fields (pain, mood, energy, symptoms, notes, pregnancy-test results, temperature disturbances, cervical sensation), the cycles excluded from estimates, and the declared contexts and age band have no Health Connect record type and live in the app's Room database (cycle_journal_entries, cycle_exclusions) and its own preferences file. They are handled like the rest of the app's local data: they move between two phones with the cycle category of phone-to-phone sync, and they are deleted from Settings → Cycle or with the app's data.
Workout route previews require manual Health Connect approval in some cases. GPS recording requires location permission because OpenVitals needs location points to build the route. Imported PMTiles or Mapsforge map packs stay local on the device.
See Permissions for the current permission list and why each group is requested.
See Local And Connected Editions for the boundary between the local app and planned connected work.
Generated from docs/app/privacy.md. Edit it there; changes made here are overwritten.
This wiki mirrors the Markdown in Hale-MTU/OpenVitals on every push to main.
Features
- Features
- Achievements
- Activity And Training Plans
- Activity Metrics
- Apple Health Import
- Beverage Logging And Caffeine
- Bluetooth LE Sensors
- Body Energy
- Body Metrics
- CoMaps Navigation Context
- CSV Import
- Cycle Tracking
- Daily Readiness
- Elevation Correction
- Feature Map
- FIT Files Import
- Food Logging
- GPX/KML/KMZ/TCX Route Import
- Health Connect Metrics Dashboard
- Health Report Export
- Heart And Vitals
- Heart Rate Recovery
- Home Screen Widgets
- Hydration
- Manual Entry Of Metrics
- Medical Records
- Metric Detail Customization
- Mindfulness
- Non Health Connect Metrics Dashboard
- Nutrition
- Offline Maps Support
- Onboarding And Permissions
- Preloaded Beverage Nutrition Reference
- Privacy, Support, And Diagnostics
- Recording Of Activity
- Reminders
- Settings And Preferences
- Sleep Score And Recovery
- Sleep Tracking
- Statistics
- Sync With Another Phone
How-to
App
- App Guide
- FAQ
- Getting Started
- Health Connect
- Install
- Local And Connected Editions
- Permissions
- Privacy
- Screenshots
- Support
Reference
- Design Reference
- Reference
- Design Guidelines for Offline & Sync
- OpenVitals UI Notes
- Reference: Data Capture
- Reference: Data Display
- Reference: Health Connect Matchmaking
- Reference: Health Connect Onboarding
- Reference: Health Connect Permissions
- Reference: Health Connect UX
- Reference: Promoting Health Connect
Engineering
- Code Analysis
- Engineering
- Flutter ↔ Kotlin test-parity matrix
- apple-health-import
- Architecture
- body-cycle-recovery
- Design system conformance
- Development
- Feature Playbook
- Flutter -> Kotlin test parity: domain/data/core/contract/state/navigation/di/bootstrap/l10n/widget_test
- Flutter → Kotlin test-parity matrix: features section
- Flutter → Kotlin test-parity matrix: ui / goldens / integration / fixtures
- Flutter-to-Kotlin test-parity matrix — dashboard + devicesync
- manual-entry
- Translations
Proposals
- Proposals
- Activity Start Flow Analysis
- Activity Start Flow Simplification Proposals
- Caffeine Detail And Future Sleep Insights
- Cycle Tracking: Design and Status
- Medical Records Through Health Connect
- Medical Records: What Is Left
Releases
- OpenVitals 2.16.0
- OpenVitals 2.15.2
- OpenVitals 2.15.1
- OpenVitals 2.15.0
- OpenVitals 2.14.5
- OpenVitals 2.14.4
- OpenVitals 2.14.3
- OpenVitals 2.14.2
- OpenVitals 2.14.1
- OpenVitals 2.14.0
- OpenVitals 2.13.0
- OpenVitals 2.12.0
- OpenVitals 2.11.2
- OpenVitals 2.11.1
- OpenVitals 2.11.0
- OpenVitals 2.10.1
- OpenVitals 2.10.0
- OpenVitals 2.9.1
- OpenVitals 2.9.0
- OpenVitals 2.8.0
- OpenVitals 2.7.3
- OpenVitals 2.7.2
- OpenVitals 2.7.1
- OpenVitals 2.7.0
- OpenVitals 2.6.6
- OpenVitals 2.6.5
- OpenVitals 2.6.4
- OpenVitals 2.6.3
- OpenVitals 2.6.2
- OpenVitals 2.6.1
- OpenVitals 2.6.0
- OpenVitals 2.5.0
- OpenVitals 2.4.1
- OpenVitals 2.4.0
- OpenVitals 2.3.2
- OpenVitals 2.3.1
- OpenVitals 2.3.0
- OpenVitals 2.2.8
- OpenVitals 2.2.7
- OpenVitals 2.2.6
- OpenVitals 2.2.5
- OpenVitals 2.2.4
- OpenVitals 2.2.3
- OpenVitals 2.2.2
- OpenVitals 2.2.1
- OpenVitals 2.2.0
- OpenVitals 2.1.0
- OpenVitals 2.0.4
- OpenVitals 2.0.3
- OpenVitals 2.0.2
- OpenVitals 2.0.1
- OpenVitals 1.9.0
- OpenVitals 1.8.0
- OpenVitals 1.7.7
- OpenVitals 1.7.6
- OpenVitals 1.7.5
- OpenVitals 1.7.4
- OpenVitals 1.7.3
- OpenVitals 1.7.2
- OpenVitals 1.7.1
- OpenVitals 1.7.0
- OpenVitals 1.6.3
- OpenVitals 1.6.2
- OpenVitals 1.6.1
- OpenVitals 1.6.0
- OpenVitals 1.5.1
- OpenVitals 1.5.0
Project