Skip to content

Sync With Another Phone

github-actions[bot] edited this page Nov 9, 2026 · 1 revision

Status: Current implemented behavior. Audience: Users and contributors. Implementation: features/devicesync, features/settings. Navigation: Screen.SettingsDeviceSync; settings section DEVICE_SYNC. Related: Feature map, Settings and preferences, Permissions, Privacy.

Sync with another phone copies Health Connect records, medical records among them, and the cycle journal the app keeps itself, directly between two nearby Android phones over Bluetooth. There is no account, no server, and no network step.

It is reached from Settings, Sync with another phone, which opens its own wizard.

The Wizard

  1. Choose a role. One phone makes itself discoverable and becomes the host. The other looks for a phone and becomes the guest.
  2. Connect. The host waits. The guest scans and picks the host from the list of nearby phones. Already-paired phones appear in the list before the scan finishes.
  3. Choose how far back. The last 30 days, the last 6 months, the last year, or everything. The last year is the default.
  4. Choose what to sync. The picker lists data categories such as activity, workouts, heart, sleep, body measurements, vitals, nutrition, hydration, mindfulness, cycle tracking, and medical records. A category appears only when this phone can both read and write at least one of its record types, and everything supported is selected by default. Medical records ignore the range, and the picker says so. Below the categories, Overwrite existing records, off by default, makes this phone write the other phone's records even when it already has them; see Re-Syncing Does Not Duplicate.
  5. Start. Each phone presses Start sync when its user is ready. The first one waits up to ten minutes for the other, and says so. If the other phone leaves the wizard, the wait ends at once.
  6. Compare the codes. Each phone then shows six digits. The user checks that they are the same and says so on both phones. No record moves before that.
  7. Sync. Progress shows the current phase and live sent, received, and written counts.
  8. Read the report. The report shows how many records were merged, how many were already present, and a per-record-type breakdown of what arrived. Medical records get one line of their own. It can be copied or shared as text. If the sync stopped, its reason is masked like any error text, on the screen and in the report.

Both phones choose their own range and their own categories. The exchange uses the record types both phones support.

Both Directions At Once

The exchange is bidirectional within a single session. Both phones send and receive over the same connection at the same time, and each phone reports what it wrote.

Each phone's choices control both what it sends and what it keeps. The other phone decides what it sends; this phone writes a record only when its type is selected here and it starts inside the range chosen here. Medical records are the one exception to the range. Anything else is counted in the report as not accepted and is never written. So a category moves only when both phones selected it, and "Last 30 days" on one phone holds even when the other chose "Everything".

Bluetooth, Not The Internet

The transfer runs over Bluetooth Classic RFCOMM on a private OpenVitals service identifier, so the app only ever connects to another OpenVitals.

This is a deliberate choice rather than a convenience. Any Wi-Fi or TCP socket on Android requires the INTERNET permission, which OpenVitals does not declare and actively removes from the manifest. Bluetooth Classic needs no such permission, so the transfer stays peer-to-peer with no network involved. See Permissions.

The host phone must be made discoverable, which Android asks about with its own dialog. Connecting to a phone for the first time triggers Android's standard pairing dialog. That bond encrypts the radio link. OpenVitals does not rely on it alone.

What The Code Protects

The code proves that the two phones talk to each other and to nobody in between. It is compared, never typed.

  1. The host commits to a fresh P-256 key: it sends a hash of the key and a random salt.
  2. The guest sends its own fresh key. Only then does the host reveal its key and salt, and the guest checks them against the hash.
  3. Both phones hash the whole handshake, run ECDH, and derive with HKDF one AES-256 key per direction and the six digits.
  4. Both phones show the digits. Each user says whether they match. A "They do not match" on either phone ends the session.
  5. From the key exchange on, every frame except an abort is sealed with AES-256-GCM. A frame counter is the nonce, so a changed, replayed, dropped or reordered frame ends the session.

Someone in the middle holds a different key with each phone, so the two phones show different digits. The commitment stops them from searching for a key that gives the same digits: they must fix their key before they see the other side's. One attempt in a million works, and each attempt needs both users at the wizard.

The check only works if the users really compare. It uses Android's built-in cryptography and adds no dependency. Keys live for one session and are never stored.

A typed code cannot do this job. A six-digit code that is proved with a MAC can be guessed offline from one recorded handshake, which is how protocol version 1 worked. Version 2 phones refuse version 1 phones, so both phones need a current build.

Re-Syncing Does Not Duplicate

Each record is identified by a fingerprint computed from its own content: the record type, its timestamps, and its values. Both phones compute the same fingerprint for the same logical record.

  • Before exchanging, each phone fingerprints what it already has. An incoming record it already holds is counted as already present and is not written.
  • Records that are written carry their fingerprint as the Health Connect client record ID, so Health Connect updates in place rather than duplicating.
  • A receiving phone re-derives the fingerprint from the record it decoded rather than trusting the identifier the other phone sent, so a sync cannot overwrite an unrelated record.

Running the same sync twice therefore writes nothing new, and the two phones converge.

Overwrite Existing Records

Health Connect can hold a record and still leave it out of its totals. Its activity sums count only the apps in its Data sources and priority list, and a record restored from a Health Connect backup or a phone transfer keeps the app that first wrote it as its source. When that app is not installed on the new phone, the record shows in Health Connect's own data browser and in the sync's "already had" count, but the dashboard reads zero steps for the day.

Ticking Overwrite existing records on the "What to accept" step makes this phone skip the fingerprint check and write every record the other phone sends, under OpenVitals, which Health Connect does count. The choice is local: the other phone sends what it sends either way, and keeps its own check unless its user ticks the box too. A record arriving twice in one session is still written once. Records OpenVitals already owns are updated in place, not duplicated, because the written records carry a client record version above any earlier write. The report notes that existing records were written again, and its "already had" count then covers only repeats within the session.

The Cycle Journal

The cycle category also carries what the cycle screen keeps outside Health Connect: the day logs (pain, mood, energy, symptoms, notes, pregnancy-test results, temperature disturbances, cervical sensation), the cycles excluded from estimates, and the declared tracking contexts, age band and hormonal contraception. These need no Health Connect permission, so the category is always offered.

A day log is fingerprinted by its content, so an unchanged day counts as already present. When both phones edited the same day, the later edit wins and the other is counted as not accepted. Excluded cycles merge; taking a cycle back into the estimates on one phone does not travel. The contexts, age band and hormonal method each move only to a phone that has not declared that part; a part the phone has declared stays. Reminder settings do not move: they are alarms on one phone.

Medical Records

The medical records category carries the FHIR records in Health Connect. It appears when this phone may write medical records and read at least one medical category. Sync never asks for these permissions: the Medical records area asks the first time it opens.

  • Medical records always sync in full, whatever range is chosen. A vaccination history cut at one year is not useful.
  • Each record travels as it would in a FHIR export, with its source's address, name, and FHIR version. The receiving phone files it into its own source for the same origin, making one when needed, and runs the import checks. A source another app keeps on the receiving phone is left alone, as in an import.
  • A record is identified by its source, type, id, and content. An unchanged record counts as already present. When the phones hold two versions of one record, the one with the later edit time (meta.lastUpdated) wins, and a version without one never replaces the other. Records typed in OpenVitals carry that time.
  • Patient records go first. When they name someone other than the people on the receiving phone, or more than one person, that phone keeps none of the medical records from this sync, and the report says why. To move them anyway, export them on one phone and import them on the other, where the import checks the person with the user.
  • Saved documents stay on their phone.
  • The report's medical line counts records added, already here, skipped, and not added.

See Medical records.

While The Sync Runs

A quiet ongoing notification, "Syncing with another phone", asks the user to keep both phones nearby. It exists so Android does not kill the app while the user is looking at something else.

The transfer belongs to the wizard screen: switching to another app is fine, but navigating away from the wizard inside OpenVitals ends the session. A sync also will not start while an activity recording is running; the recording has to be finished or discarded first.

Permissions

Sync asks for nearby-device Bluetooth permissions, and for location on Android versions before 12 where classic Bluetooth discovery requires it. It then asks for the Health Connect read and write permissions for the record types it can exchange, and finally, on the host, for Android's discoverable window. It never asks for medical permissions.

Privacy

Records move directly between the two phones over Bluetooth and are written to Health Connect on each device. Nothing is uploaded, and the app has no internet permission to upload with. The most recent sync report is stored locally as a text file in the app's own storage; only the latest one is kept.


Generated from docs/features/device-sync.md. Edit it there; changes made here are overwritten.

Home

Features

How-to

App

Reference

Engineering

Proposals

Releases

Project

Clone this wiki locally