Walk-to-Unlock: How I built a privacy-first app blocker that uses your phone's step counter
I spent the last six months building StepShield Pro, a digital wellbeing app for Android with a twist: instead of forcing you to put your phone in a timed lock box, it locks your distracting apps behind your daily steps. Walk 2,000 steps, earn 30 minutes of Instagram.
It runs entirely on-device using the hardware step counter. No GPS. No cloud. No data leaves your phone.
Here's how I designed it, and the engineering tradeoffs I made along the way.
The problem with most app blockers
I tried every app blocker on the Play Store before I started building mine. The pattern is always the same:
- Install the app
- Grant it Accessibility Service access
- Grant it Usage Stats access
- Sign in with your Google account
- Pick which apps to block
- Set a daily screen time budget
- Try to use Instagram. Get blocked.
- Disable the blocker. Use Instagram anyway.
Most users give up by step 4, and the ones who make it to step 7 uninstall the app by step 8. The blockers do work, technically — but they rely on willpower at the moment you most need a break from willpower.
The reframing: a step tax instead of a time limit
What if unlocking a distracting app cost something that wasn't time? Something physical?
Every smartphone has a hardware step counter — a low-power chip on the SoM that tracks your footsteps even when the screen is off, no GPS required, no battery drain, no privacy concerns. It's the same sensor your phone's built-in Health app uses.
The idea: every time you open a blocked app, you spend steps from a daily pool. Walk to earn more. Sit on the couch all day? Your TikTok budget shrinks to zero by 3pm.
This turns screen time from a time management problem into a physical activity problem. And the cost (steps) is the same currency the body needs anyway.
The technical architecture
The app is one Kotlin process with three main components.
1. Step counter service
Subscribes to Sensor.TYPE_STEP_COUNTER (or TYPE_STEP_DETECTOR on older devices). The OS handles the batching and the wakeups. We never call into Google Play Services. We just read the cumulative step count from SensorEvent.values[0].
class StepCounterService : Service(), SensorEventListener {
private lateinit var sensorManager: SensorManager
private var baselineSteps: Long = -1
private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
override fun onCreate() {
sensorManager = getSystemService(SENSOR_SERVICE) as SensorManager
val sensor = sensorManager.getDefaultSensor(Sensor.TYPE_STEP_COUNTER)
sensorManager.registerListener(this, sensor, SensorManager.SENSOR_DELAY_NORMAL)
}
override fun onSensorChanged(event: SensorEvent) {
if (event.sensor.type == Sensor.TYPE_STEP_COUNTER) {
val total = event.values[0].toLong()
if (baselineSteps == -1L) baselineSteps = total - getTodayStepCount()
val today = getTodayStepCount() + (total - baselineSteps)
scope.launch { stepRepository.update(today) }
}
}
}
The step count resets to a baseline at midnight, not at app launch. That matters — you don't want a "free" pool of steps just because you rebooted.
2. Foreground service for app launch interception
We watch the foreground app with UsageStatsManager.queryUsageStats(). The poll is every 500ms when the screen is on. (We also support Accessibility Service for users who want hard-blocking instead of soft nag, but the default is soft nag to keep install friction low.)
When the foreground app matches a blocked package, we show a full-screen overlay: "This app costs 25 steps. You have 320 left. Walk 1,000 steps to add 100 more."
The user can dismiss the overlay with a 3-second countdown (the "cooldown" — to prevent rage-tapping). They can also use a PIN to bypass (stored in EncryptedSharedPreferences, never leaves the device).
3. The Scroll Debt mechanic
This is the part I'm most proud of. The app tracks how many times you bypass with the PIN. Every PIN bypass adds 1 unit to your Scroll Debt. Your daily step budget is reduced by 5% of your accumulated debt.
The math:
effective_budget = base_budget * (1 - 0.05 * scroll_debt)
So if you've bypassed 20 times this week, your 2000-step base budget becomes 0. That's not a bug. That's a feature. The debt is meant to make you feel the cost of cheating.
Privacy by default
Everything is local. No analytics SDK. No Firebase. No ad network. The app is less than 8 MB because it doesn't ship a tracking SDK.
The data we collect: zero. The Play Store data safety form says exactly that. The one piece of data we could collect is the step count, and we store it in Room (Android's local database) and never read it off the device.
You can export your data as JSON from the settings menu. You can wipe everything with one tap. There's no account, no login, no sign-up.
What's next
I'm currently working on on-device ML to detect when a "walk" is actually just shaking the phone in your hand (a known cheat). The current heuristic is "sustained accelerometer variance for ≥ 30 seconds" but I want to graduate to a tiny TFLite model that recognizes walking vs. faking.
If you want to try StepShield Pro, it's on the Play Store: com.appblocker.screentime.pedometer.workout.fitness.coaching. 100+ installs, 0 reviews so far (just launched!). I'd genuinely love to hear what you think.
If you build something similar, or if you have ideas for the cheat detection, hit me up on Mastodon: @[email protected]. I'm @nexusdriftstudio here on DEV too.
SOCIAL SHARE CARD GENERATOR