The Commute That Sparked an Architecture
Picture this familiar scenario: You drop your phone into your pocket while listening to a podcast or walking with YouTube running.
Thirty seconds later, the audio abruptly pauses. Or worse, the screen wakes up, registers a flurry of ghost touches against your denim, skips three tracks, starts screen-recording, or places an accidental call to someone in your contact list.
Even more irritating is the "ear-press mute" phenomenon: you answer a phone call, hold the device to your face, and because modern bezel-less phones frequently lack physical proximity sensors (relying instead on finicky "virtual" ultrasound algorithms), your cheek accidentally taps the screen, muting your microphone or hanging up mid-sentence.
As an Android engineer, I decided to fix this once and for all.
The result is TouchProof, a native Android utility that transparently shields the screen using hardware sensor fusion, zero-network permissions, and custom WindowManager overlays.
Here is the engineering teardown of how it works under the hood.
1. The Naive Approach vs. The Real Problem
If you ask an Android developer how to prevent pocket touches, the immediate answer is usually:
"Just listen to
Sensor.TYPE_PROXIMITY. If the distance is 0, blank or lock the screen."
In practice, this naive approach fails catastrophically for three reasons:
- The Table Problem: If you place your phone face down on a desk or place a notebook over your phone, the proximity sensor reads near-zero, triggering a lock when you didn't want one.
- Virtual Proximity Flaws: Modern smartphones have razor-thin bezels. OEMs have replaced physical infrared proximity emitters with software-based virtual proximity sensors that sample speaker reflections or ambient light. They are notoriously prone to drifting.
- The Call State Transition Dilemma: If you lock touches during incoming calls, the user cannot swipe to answer. If you don't lock touches during active calls, cheek disconnects happen continuously.
To build an invisible, reliable pocket guard, we need Sensor Fusion.
2. Sensor Fusion: Fusing Proximity with Gravitational Vectors
Rather than relying on one sensor, TouchProof fuses Sensor.TYPE_PROXIMITY with the 3-axis Sensor.TYPE_ACCELEROMETER (or gravity sensor) to calculate device inclination in real-time.
When a device is slipped into a pocket, two physical conditions are almost always true simultaneously:
- An object is obstructing the top sensor array ($d \le 1.0\text{ cm}$).
- The device is oriented vertically with the top pointing downward or upward (pitch angle $\theta \approx 40^\circ - 90^\circ$).
If the phone is laying flat on a coffee table with something covering it, the gravity vector on the Z-axis is near $\pm 9.8\text{ m/s}^2$ while X and Y are near zero. In that case, we do not lock.
The Vector Math in Kotlin
Here is how TouchProof evaluates the downward pitch angle:
class PocketSensorFusion(
private val context: Context,
private val onPocketStateChanged: (isInPocket: Boolean) -> Unit
) : SensorEventListener {
private val sensorManager = context.getSystemService(Context.SENSOR_SERVICE) as SensorManager
private val proximitySensor = sensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY)
private val accelerometer = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER)
private var isCovered = false
private val gravity = FloatArray(3)
fun startListening() {
proximitySensor?.let {
sensorManager.registerListener(this, it, SensorManager.SENSOR_DELAY_NORMAL)
}
accelerometer?.let {
sensorManager.registerListener(this, it, SensorManager.SENSOR_DELAY_UI)
}
}
fun stopListening() {
sensorManager.unregisterListener(this)
}
override fun onSensorChanged(event: SensorEvent) {
when (event.sensor.type) {
Sensor.TYPE_PROXIMITY -> {
val distance = event.values[0]
val maxRange = event.sensor.maximumRange
isCovered = distance < maxRange && distance < 2.0f
evaluateState()
}
Sensor.TYPE_ACCELEROMETER -> {
// Low-pass filter to isolate steady gravity from transient motion
val alpha = 0.8f
gravity[0] = alpha * gravity[0] + (1 - alpha) * event.values[0]
gravity[1] = alpha * gravity[1] + (1 - alpha) * event.values[1]
gravity[2] = alpha * gravity[2] + (1 - alpha) * event.values[2]
evaluateState()
}
}
}
private fun evaluateState() {
if (!isCovered) {
onPocketStateChanged(false)
return
}
// Calculate pitch angle: vertical orientation in pocket
val y = gravity[1]
val z = gravity[2]
val pitch = Math.toDegrees(kotlin.math.atan2(y.toDouble(), kotlin.math.sqrt((z * z).toDouble()))).toFloat()
// Pocket tilt condition: phone top points down (Y < -4) or up (Y > 4)
val isVertical = kotlin.math.abs(pitch) > 40.0f
onPocketStateChanged(isCovered && isVertical)
}
override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) {}
}
By filtering out false triggers when the phone is flat, the app guarantees that it only engages when genuine pocketing occurs.
3. Intercepting Touch Events via TYPE_APPLICATION_OVERLAY
Once the fusion logic confirms the device is in a pocket or held to a cheek, how do we block touch inputs without killing whatever app the user is actively running (such as YouTube, Spotify, or navigation)?
The solution is an invisible system overlay window attached to Android's WindowManager.
Using WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY (introduced in API 26 as the secure replacement for TYPE_SYSTEM_ALERT), TouchProof draws an input-consuming canvas directly above all standard UI layers:
class TouchShieldOverlayService : Service() {
private lateinit var windowManager: WindowManager
private var overlayView: View? = null
override fun onCreate() {
super.onCreate()
windowManager = getSystemService(Context.WINDOW_SERVICE) as WindowManager
}
fun attachShield() {
if (overlayView != null) return
val params = WindowManager.LayoutParams(
WindowManager.LayoutParams.MATCH_PARENT,
WindowManager.LayoutParams.MATCH_PARENT,
WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY,
// Consume all touches without letting them fall through
WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL or
WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN or
WindowManager.LayoutParams.FLAG_FULLSCREEN,
PixelFormat.TRANSLUCENT
).apply {
gravity = Gravity.TOP or Gravity.START
}
overlayView = View(this).apply {
setBackgroundColor(Color.TRANSPARENT)
// Intercept all raw MotionEvents
setOnTouchListener { _, event ->
handleUnlockGesture(event)
true // Consumed! Rogue touches never reach underlying apps
}
}
windowManager.addView(overlayView, params)
}
fun removeShield() {
overlayView?.let {
windowManager.removeView(it)
overlayView = null
}
}
private fun handleUnlockGesture(event: MotionEvent) {
// Multi-tap or custom gesture unlock logic
}
override fun onBind(intent: Intent?): IBinder? = null
}
Because overlayView returns true from setOnTouchListener, every ghost swipe, pinch, and button press generated by pocket friction is absorbed safely at the window manager level. Meanwhile, audio playback, GPS, and foreground threads continue completely uninterrupted.
4. Hardware Efficiency: OLED Pixel Throttling
A primary concern when running background services and display overlays is battery consumption.
To make TouchProof virtually imperceptible on device battery stats, I implemented OLED Sub-Pixel Gating:
-
Pure True Black Canvas (
#000000): On modern AMOLED / OLED panels, every sub-pixel is an independent organic light-emitting diode. A pixel value of#000000turns the transistor off entirely, drawing zero electrical current. -
Display Brightness Attenuation: When the pocket shield activates, we dynamically drop the window attribute
screenBrightnessto its hardware minimum (0.01f).
// Dim OLED display to absolute minimum while shielded
val windowParams = overlayView.layoutParams as WindowManager.LayoutParams
windowParams.screenBrightness = 0.01f
windowManager.updateViewLayout(overlayView, windowParams)
The device CPU stays in a lightweight sleep state, woken only by low-frequency hardware sensor interrupts.
5. In-Call Telephony State Machine
Handling phone calls cleanly required integrating with TelephonyManager.
When an incoming call arrives, we must never prevent the user from answering:
-
CALL_STATE_RINGING: Instantly disarm the touch shield so the incoming call notification and swipe-to-answer gestures are 100% accessible. -
CALL_STATE_OFFHOOK: The moment the call is connected, arm the cheek protection logic. If the phone is lifted to an ear, touches are blocked; if taken away from the ear to inspect the dialpad, touches are restored instantly. -
CALL_STATE_IDLE: Return to normal pocket monitoring.
val telephonyCallback = object : TelephonyCallback(), TelephonyCallback.CallStateListener {
override fun onCallStateChanged(state: Int) {
when (state) {
TelephonyManager.CALL_STATE_RINGING -> {
touchShield.disableTemporarily()
}
TelephonyManager.CALL_STATE_OFFHOOK -> {
touchShield.enableInCallMode()
}
TelephonyManager.CALL_STATE_IDLE -> {
touchShield.restoreDefaultMode()
}
}
}
}
6. The Privacy-First Philosophy: Zero Network Permissions
One of my core design principles for TouchProof was trust.
Utility apps on app stores are frequently laden with SDK trackers, analytics bloatware, and ad networks that drain the battery and ping external servers.
In TouchProof's AndroidManifest.xml, there is zero android.permission.INTERNET:
<!-- Notice what is NOT here: android.permission.INTERNET -->
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
The app cannot transmit telemetry, cannot collect user data, and cannot serve ads. It is mathematically and architecturally impossible for it to contact an external server. It operates 100% offline.
7. Reaching the Next Billion Users: PhonePe Indus Appstore
When distributing TouchProof, I chose to publish on the PhonePe Indus Appstore.
Indus Appstore is designed specifically for the Indian digital ecosystem, supporting 12+ Indian languages. To deliver a deeply native localized experience, I personally crafted Hindi and Sanskrit localizations for TouchProof alongside English, ensuring that users can toggle pocket shields, sensor thresholds, and status notifications in their native tongue.
Conclusion & Key Takeaways
Building utility software in the modern Android landscape is an exercise in respecting platform constraints:
- Never trust a single hardware sensor — combining accelerometer vectors with proximity data eliminates 99% of false triggers.
-
WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAYremains the gold standard for non-destructive system input interception. - Zero-permission architectures are a breath of fresh air for users tired of invasive utilities.
If you want to explore the project or try it out:
- 📲 Get the App: Search "TouchProof" on PhonePe Indus Appstore or visit the TouchProof Landing Page
- 🌐 Project Documentation: shriramsingh.github.io/touchproof-community
- 🐙 GitHub Profile: github.com/shriramsingh
Shriram Singh is an Android & Full-Stack Engineer passionate about performant system utilities, Kotlin, and modern distributed architectures.
Top comments (0)