DEV Community

Haseeb
Haseeb

Posted on

Engineering Geofencing: How I Managed Battery Drain in Muffle

It happened during a quiet afternoon at the local library. I was deep into a debugging session, minding my own business, when my phone started blaring a loud, aggressive ringtone because a group chat had decided to become active. I scrambled to silence it, but not before a dozen heads turned in my direction, clearly annoyed by the interruption. I felt that familiar, hot rush of embarrassment. It wasn't the first time, and I knew it wouldn't be the last unless I changed how my phone managed its own state.

This is the constant friction of modern mobile life. We carry devices that are supposedly 'smart,' yet they lack the basic contextual awareness to know where we are or what we are doing. We are expected to manually toggle our sound profiles for meetings, classes, or prayer times, and the reality is that humans are forgetful. We leave the meeting, forget to unmute, and then miss an important call three hours later. Or, even worse, we forget to mute and cause a disruption in a place that demands silence.

Existing solutions often felt like overkill or were invasive regarding privacy. Many apps on the Play Store rely on constant, active polling of the GPS chip. If you write an app that queries the latitude and longitude every ten seconds, you aren't just building a utility; you are building a pocket-sized battery heater. That kind of architecture is unacceptable for a background utility. I wanted Muffle to be a set-and-forget tool that didn't leave the user scrambling for a charger by midday, which meant I had to rethink the standard implementation of geofencing.

To build a battery-efficient geofencing engine, I leaned heavily on the GeofencingClient within the Google Play Services location library rather than rolling my own proximity checks using LocationManager. The common mistake many developers make is implementing a custom service that listens for location updates and calculates the Haversine distance manually. That forces the CPU to wake up and perform floating-point math every time the device moves a few meters. That is death for battery life. The GeofencingClient offloads this heavy lifting to the system's low-power location hardware.

When you add a geofence using geofencingClient.addGeofences(), you are registering a request with the Android system, which then monitors the device's location using a fusion of sensors—cell towers, Wi-Fi, and GPS. Crucially, the system only wakes your app when the device crosses the perimeter you defined. The logic looks something like this:

kotlin
val geofence = Geofence.Builder()
.setRequestId(routineId)
.setCircularRegion(lat, lon, radiusInMeters)
.setTransitionTypes(Geofence.GEOFENCE_TRANSITION_ENTER or Geofence.GEOFENCE_TRANSITION_EXIT)
.setExpirationDuration(Geofence.NEVER_EXPIRE)
.build()

val geofencingRequest = GeofencingRequest.Builder()
.addGeofence(geofence)
.build()

geofencingClient.addGeofences(geofencingRequest, geofencePendingIntent)

By using a PendingIntent to trigger a BroadcastReceiver, I ensure that the app process doesn't need to be running in the background at all times. The OS handles the monitoring and wakes the app only when the transition occurs. However, there is a catch: the 'dwell time.' If a user is near the edge of a geofence and the GPS signal flickers, the app might trigger an 'enter' and 'exit' event repeatedly. To solve this, I implemented a debouncing logic layer inside the receiver that checks the SystemClock.elapsedRealtime() against the last recorded event. If an event occurs within 30 seconds of the previous one, I simply ignore it, preventing the phone from bouncing between sound profiles.

What surprised me most during development was the inconsistency of GPS hardware across different manufacturers. I initially assumed that a 50-meter radius would be accurate everywhere. I was wrong. On some budget devices, the internal GPS chip reports location jitter that spans nearly 200 meters, meaning the app would trigger the sound profile while the user was still a block away from their destination. I spent a week frustration-debugging this before realizing I had to expose a 'precision' setting to the user. I had to allow them to calibrate the radius based on their specific environment. It was a humbling lesson: you cannot code away hardware limitations. Sometimes, the most 'technical' solution is actually a user-facing configuration option that lets the human compensate for the hardware's shortcomings.

Another assumption I got wrong was that the GeofencingClient would be sufficient for all scenarios. I realized later that in deep indoor environments, GPS signal strength plummets, and the system starts relying solely on cell tower triangulation. This is significantly less accurate. If a user tries to set a geofence for a small room, it will fail. I had to implement a UI guardrail that forces a minimum radius of 100 meters to ensure the geofence actually triggers reliably, regardless of whether the phone is using Wi-Fi scanning or satellite GPS. It isn't as granular as I wanted, but it is reliable, which is infinitely better than a 'fancy' feature that works 60% of the time.

If I were starting this project over, I would prioritize the ActivityRecognitionClient earlier in the lifecycle. Currently, Muffle relies on location, but adding motion sensing would allow me to avoid triggering silent profiles when the user is moving at highway speeds. It is annoying to have your phone go into 'Meeting Mode' just because you drove past your office on the freeway. Integrating motion data would allow for smarter filtering of those transitions. Always consider the movement context, not just the coordinate context.

For any developer working on background location tasks, the lesson is simple: do not fight the Android system. Do not try to keep a service alive at all costs. The system is designed to kill your process to save the user's battery, and if you try to bypass that, you will lose. Instead, design your app to be event-driven. Use the APIs that the OS provides to receive callbacks, and do your work in those brief windows of opportunity. Your code should be idle 99% of the time.

Building Muffle has been an exercise in restraint. I started with a laundry list of complex features but stripped them back to ensure the core utility—managing sound profiles—stayed rock solid. If you are struggling with your own phone's volume settings, you can see how I approached this at https://play.google.com/store/apps/details?id=com.muffle.app. It is fully offline, respects your privacy, and, most importantly, keeps my phone quiet exactly when I need it to be. Focus on solving the one friction point that bugs you the most, and the rest will follow.

Top comments (1)

Collapse
 
officialmailkr profile image
오피셜메일 •

좌표 폴링 대신 지오펜스 이벤트를 받고 경계 진동을 별도로 처리한 설계가 흥미롭습니다. 30초 디바운스만으로 실제 퇴장을 놓치는 경우도 있을 수 있으니, 마지막 확정 상태와 대기 중 전환을 분리해 테스트하면 좋겠네요. 수동으로 바꾼 소리 모드를 자동화가 언제 존중하고 언제 복원하는지도 사용자에게 보여주면 신뢰를 높일 수 있겠습니다.