An iPhone with no Apple Watch still writes about a dozen motion-derived types into HealthKit on its own: steps, distance, flights, and the walking metrics. Every iPhone does it, passively, from the day it was unboxed.
Android reached a similar place by a different route, and the differences are where products break. Health Connect does count steps on-device. But it only starts counting once an app asks, it forgets anything older than thirty days unless you request extra permission, and as of two months ago it changed how it labels its own data in a way that silently breaks the obvious query.
This is what an Android phone actually gives you, and the three catches attached to it.
Health Connect counts steps itself
On Android 14 and above, Health Connect reads the device step sensor directly and writes into its own store. No wearable, no third-party app.
The mechanism is documented and specific [1]. Health Connect uses TYPE_STEP_COUNTER from SensorManager, the low-power hardware counter most Android phones ship, and batches writes to roughly once a minute to protect battery. Availability requires Android 14 with SDK extension 20 or higher:
val isStepTrackingAvailable =
Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE &&
SdkExtensions.getExtensionVersion(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) >= 20Below that, there is no native counting, and the data comes from whatever the manufacturer preinstalled. Samsung Health on a Samsung, something else elsewhere, nothing at all on plenty of cheaper handsets. Both cases are live in the field, so both need handling.
The catch that changes onboarding
Native counting is demand-activated, not always-on. Nothing accumulates until some app asks for permission.
“The on-device step counting mechanism is active only when at least one application on the device has been granted the READ_STEPS permission within Health Connect” [1].
On iOS, HealthKit has been quietly filling since the phone was activated, so a user who grants permission hands you months of history immediately. On Android 14, if nothing has ever requested READ_STEPS, there is no history to hand over. Counting begins roughly when your permission dialog is dismissed.
What this means for your first-run experience. An iOS user can see a populated chart in the first session. An Android user on a device where nothing else requested steps starts from zero and needs a day before anything is worth showing. Same feature, same code, completely different first impression, and it is not something you can detect and apologise for after the fact.
The corollary is that a device with an OEM health app preinstalled behaves better here, because that app has held the permission all along and counting has been running. The users with the least data are the ones on clean devices.
The June 2026 attribution change
Health Connect changed the package name it writes its own steps under, and queries filtering on the old value silently lose all recent data.
On-device steps used to be attributed to the package name android. From the June 2026 update they carry a Synthetic Package Name, of the form com.android.healthconnect.phone.jd5bdd37e1a8d3667a05d0abebfc4a89e [1]. Historical records keep the old attribution, so a store now contains both.
An app filtering DataOrigin("android") still returns rows, because pre-June data is still there under that name. It just returns nothing newer than the update. No error, no empty result, no obvious signal: a step chart that stops in June, and a user asking why.
Two properties make this worse than a simple rename:
- Stable, but per-application. “Different applications on the same device see different Synthetic Package Names for on-device step data” [1]. You cannot hardcode one, cannot share it, and cannot copy a colleague’s.
- Retrieved at runtime. The name has to come from
getCurrentDeviceDataSource(), available on Android 14 with SDK extension 11 or higher.
val healthConnectManager = context.getSystemService(HealthConnectManager::class.java)
val deviceDataSource = healthConnectManager?.getCurrentDeviceDataSource()
val currentDeviceSpn = deviceDataSource?.deviceDataOrigin?.packageName
// Query both, or you get history without recent data.
val dataOriginFilters = mutableSetOf(DataOrigin("android"))
currentDeviceSpn?.let { dataOriginFilters.add(DataOrigin(it)) }If you filter by data origin anywhere in your Android integration, it is worth checking before a user does it for you.
You cannot read history you were not there for
Health Connect caps how far back an app can read, measured from when permission was granted rather than from now.
Google’s wording is unambiguous: “By default, all applications can read data from Health Connect for up to 30 days prior to when any permission was first granted” [1]. Older records exist. You cannot see them.
| Reading your own data | Reading data from anything else | |
|---|---|---|
| Android 14 and higher | No historical limit | 30 days |
| Android 13 and lower | 30 days | 30 days |
PERMISSION_READ_HEALTH_DATA_HISTORY extends this. Without it, “an attempt to read records older than 30 days results in an error” [1].
And the window resets on reinstall. Google’s own example: a user who deletes the app on 10 May and reinstalls on 15 May, granting permission again, exposes data back to 15 April and no further. A returning user with two years of history is a thirty-day user again.
HealthKit imposes no equivalent cap. So “show the user their last six months” is a trivial feature on iOS and a permission you have to justify on Android. That difference belongs in the estimate, not in the bug tracker three weeks later.
Provenance matters more here
With native counting, an OEM app, and possibly your own writes all in one store, the same walk can appear several times.
Health Connect records carry metadata identifying the writing package [1], and on Android that is not housekeeping. A Samsung user on Android 14 can plausibly have on-device steps under a Synthetic Package Name, Samsung Health’s steps, and yours, covering the same afternoon with different totals.
Summing without a source-priority policy double or triple counts, which is the deduplication problem in a sharper form than iOS presents, since iOS at least has one authoritative system source for motion.
What this means for a cross-platform product
Both platforms now count steps on the phone. Everything around that differs, and the differences are in the parts users notice.
Three asymmetries survive:
- History at signup. iOS accumulates passively from day one; Android starts when something asks. Your onboarding cannot assume a populated chart on Android.
- How far back you can read. No cap on iOS, thirty days by default on Android, and reinstalls reset it.
- What else you get. iOS produces a set of mobility metrics with no direct Health Connect equivalent: walking speed, asymmetry, double support and steadiness. Features built on them are iOS-only unless you derive something similar from raw sensors yourself.
The useful question per data type stays the same on both: what has to be true about this user for this to exist? On iOS the answer is usually “they carried the phone”. On Android it is “they carried the phone, something had asked for permission, and it happened within the window you can read”.
For the wider platform comparison covering permissions, background sync and schema differences, see HealthKit vs Health Connect.
The floor you control
One option changes the shape of this, and teams often skip past it: you can be the thing that collects.
Google Play services provides a Recording API, explicitly the replacement for the deprecated Google Fit Android API, which collects steps in the background without your app running [2]. It requires an explicit subscribe(LocalDataType.TYPE_STEP_COUNT_DELTA), and “data is only available when there is an active subscription” [2], so it has the same no-back-catalogue property as Health Connect’s native counting.
What it buys you is consistency. Your floor stops depending on the Android version, the manufacturer, whether another app happened to hold a permission, and which side of a June attribution change the data landed on. That is a real engineering commitment, not a configuration change, and it lands the same way on both platforms, which is at least honest.
If you take that route, the background service is the beginning rather than the end: you inherit the battery negotiation, the version matrix, and a maintenance line every time either platform changes. What building this yourself costs sets out the arithmetic on our own build, and sleep stages without a wearable covers the one signal where no amount of that work helps.
References
- Android Developers. Read data in Health Connect: on-device step counting, Synthetic Package Name attribution from the June 2026 update, the 30-day history window, and PERMISSION_READ_HEALTH_DATA_HISTORY. https://developer.android.com/health-and-fitness/health-connect/read-data
- Android Developers. Recording API: background collection of steps through Google Play services, and the replacement for the deprecated Google Fit Android API. https://developer.android.com/health-and-fitness/guides/recording-api
- Android Developers. Health Connect platform overview and getting started. https://developer.android.com/health-and-fitness/guides/health-connect