Pick the phone up, bring it to your ear, and the call connects. No swipe, no tap. What makes it trustworthy is not that it answers — it is everything it refuses to answer.
Apps in this space have existed for years. Nearly all of them trigger on one thing:
the proximity sensor reporting NEAR. That is also exactly what a pocket
reports, which is why their reviews are full of people whose phone answered a call
from inside their jeans.
The sensor cannot tell an ear from a trouser pocket — both are simply something close to the screen. Treating that as consent is the bug. Here, proximity is never the trigger. It is the last of three checks, and the weakest one.
Each answers something the others cannot. All three must agree.
A deliberate lift produces a burst of acceleration well past 2.0 m/s².
A phone that has not moved cannot have been picked up, so nothing else is even evaluated.
Confirmation only — and it is rejected outright unless the sensor was seen
FAR after the call started ringing.
A phone covered since before the call can never answer. This one rule kills most pocket cases.
A phone at your ear has stopped. A phone in a pocket on a moving body never does. Measured over a short 300 ms window, because the 800 ms window used for pickup still contains the lift that just happened.
Validation runs 500 ms after the phone reaches your ear. Measuring stillness over 800 ms would always include the lift — making the gate unpassable and every real gesture fail. Two windows, not one.
Nothing samples until a call actually rings, and everything unregisters the moment the decision is made. Between calls the app is a suspended callback — no polling, no wakelocks, no timers.
The decision logic is pure Kotlin — no Android imports, time arrives on events, effects leave through a callback. All 38 tests run on the JVM. No emulator, no real phone calls.
Every rejection is logged with the measured value, so when it declines you can see precisely which gate stopped it.
Proximity → NEAR NEAR rejected — sensor covered since before the call
Gesture validation… Phone is still moving (energy 4.0, want ≤ 1.5)
Proximity → NEAR NEAR rejected — no recent pickup
Gesture validation… Phone is face-down (tilt 171.4deg)
Call state → RINGING sensors registered Motion detected — pickup energy 6.1 ≥ 2.0 Proximity → NEAR FAR seen earlier this ring ✓ NEAR debounce passed stable for 500 ms Gesture validation passed tilt 84° · settled 0.3 · earpiece up Answer successful sensors unregistered
There is no network code anywhere in the project. No phone number or contact name is ever read or written to a log.
| Permission | Why |
|---|---|
| READ_PHONE_STATE | Know when a call is ringing |
| ANSWER_PHONE_CALLS | Answer it — an ordinary runtime permission since Android 8 |
| FOREGROUND_SERVICE + _SPECIAL_USE | Stay alive to notice the ring |
| POST_NOTIFICATIONS | The ongoing notification Android requires of any such service |
| RECEIVE_BOOT_COMPLETED | Keep working after a reboot |
Root is not required. The root path is an unused fallback — the public telecom API does the work on a stock, unrooted phone.
It was built, it was tested on a real device, and it was removed. A VoIP app's answer button is an activity PendingIntent — pressing it makes that app open its call screen, and Android judges that launch against our background privilege, not theirs.
The gesture worked perfectly every time. The press was simply discarded.
E ActivityTaskManager: Background activity launch blocked! intent: com.whatsapp/.calling.ui.VoipActivityV2 (ACCEPT_CALL) realCallingPackage: com.example.earautoanswer realCallerStartMode: MODE_BACKGROUND_ACTIVITY_START_ALLOWED ← we allowed it balAllowedByPiSender: BSP.ALLOW_BAL ← our side agreed resultIfPiSenderAllowsBal: BAL_BLOCK ← blocked anyway realCallingUidHasVisibleActivity: false E ActivityTaskManager: Abort background activity starts
It works only while the app is open on screen, which is useless for answering calls. Rather than ship something that fails silently, the feature was deleted — and the finding documented so nobody rebuilds it.
Sideloaded, so Android will warn about an unknown developer. Requires Android 13+, a proximity sensor and an accelerometer.
Latest release ↓Any JDK 17+. No signing key needed for a debug build.
git clone https://github.com/ahkamboh/ear-auto-answer cd ear-auto-answer ./gradlew :app:assembleDebug
Thresholds are set for a Pixel-class phone. Open Test Sensors, raise the
phone to your ear, and watch settleEnergy — it must fall into the green band
within about half a second. If pickup and rest overlap, adjust
pickupMovementThreshold and maxSettleMovementEnergy in
AutoAnswerConfig.kt. Every tunable value lives in that one file.