DEV Community

Haseeb
Haseeb

Posted on

Engineering Geofencing: Trading Battery for Precision in Android

It was 1:15 PM on a Friday. The imam had just begun the khutbah, and the room was filled with a profound, heavy silence. I was sitting in the front row, focused and calm, when my pocket erupted into a high-pitched, digital symphony. It wasn't just a notification; it was a full-volume, stock ringtone—the kind that cuts through stone walls. I scrambled to silence it, my face burning with embarrassment as dozens of people turned around. I had completely forgotten to toggle my sound profile before entering. That moment of pure social friction was the catalyst for Muffle.

We have all experienced this, though perhaps in different settings—a high-stakes board meeting, a quiet library, or a medical appointment where the sudden noise is both distracting and disrespectful. The core issue isn't that we don't care; it is that we are human. We are forgetful, and we are often preoccupied. Existing solutions were either too manual, requiring me to remember to flip a toggle, or they relied on rigid time schedules that didn't account for the fact that meetings run late or plans shift. I needed a system that understood where I was and adjusted accordingly, without me having to reach into my pocket.

When I started building Muffle, I knew geofencing was the answer. But geofencing is a notorious battery drainer on Android. If you ask the OS for high-accuracy location updates every few seconds, you will effectively turn your device into a hand warmer that dies by noon. I had to choose between the LocationManager API, which requires manual polling and constant wakelocks, or the Google Play Services GeofencingClient. I opted for the latter because it offloads the heavy lifting to the system’s location engine, which is optimized to use hardware-level batching. However, even with the GeofencingClient, the trade-off is latency. The system might take a few hundred meters of travel before it triggers an entry event to ensure it isn't triggering based on GPS drift.

For Muffle, I implemented the GeofencingRequest with a loiteringDelay of 60,000 milliseconds. This ensures that the user is actually present at the location rather than just driving past it on the highway. Here is how I set up the transition request:

kotlin
val geofence = Geofence.Builder()
.setRequestId(id)
.setCircularRegion(lat, lng, radius)
.setTransitionTypes(Geofence.GEOFENCE_TRANSITION_ENTER or Geofence.GEOFENCE_TRANSITION_EXIT)
.setExpirationDuration(Geofence.NEVER_EXPIRE)
.setLoiteringDelay(60000)
.build()

By setting the setLoiteringDelay, I sacrificed immediate response for massive battery savings. The system doesn't need to wake up the app if the user just walks to the edge of the building; it waits to confirm intent. Integrating this with the AudioManager was straightforward, but the real challenge was ensuring that the app survived a system reboot. I had to register a BroadcastReceiver listening for ACTION_BOOT_COMPLETED to re-register all geofences with the PendingIntent, or else the silent mode would never trigger after a restart. This architecture keeps the app dormant until the OS sends the intent, keeping the memory footprint near zero.

What surprised me most during development was the volatility of GPS signals inside large, concrete-heavy buildings. I assumed that a 100-meter radius would be plenty for a standard office building. I was wrong. In urban canyons, GPS signal bounce causes the device to report it has left a location when it is sitting perfectly still. My first version of Muffle would toggle the phone to 'Normal' volume while the user was still sitting in the meeting, just because the GPS signal drifted 50 meters to the left. I had to implement a secondary check—if the geofence triggered an 'exit' event, I added a buffer time before restoring audio, checking if the device remained outside the radius for more than five consecutive minutes.

If I were starting this project today, I would move away from relying solely on GPS. I would implement a hybrid approach using Wi-Fi SSID identification. GPS is excellent for outdoors, but it is effectively useless when you are deep inside a skyscraper. By combining the GeofencingClient with a simple check of the current Wi-Fi network name, I could create 'Smart Regions' that are far more accurate. The biggest lesson I learned is that no single sensor is enough to solve context-aware automation. You have to build layers of validation to prevent the automation from being more annoying than the problem it is solving. Trusting the system to be 100% accurate is a trap; you must design for the 5% error rate that occurs when a satellite signal drops or a cell tower handover happens.

For any Android developer looking to implement background tasks, the biggest takeaway is to respect the user's hardware. Don't build for the emulator where power is infinite; build for the phone that has been in a pocket for six hours with 15% battery left. Avoid the temptation to use high-accuracy location modes unless your use case literally depends on centimeter-level precision. Most of the time, the system's coarse location, combined with a bit of intelligent buffering, is more than sufficient. Keep your logic as close to the hardware as possible by letting the system APIs do the heavy lifting rather than rolling your own polling loops.

Building Muffle has been a study in balancing convenience with efficiency. It has taught me that the best user experience is the one that is invisible, only stepping in to act when it is absolutely necessary. You can see how these principles came together in the final implementation at https://play.google.com/store/apps/details?id=com.muffle.app, where I continue to iterate on these background routines to make them as light as possible for the end user.

Top comments (0)