Opening hook
The silence in the lecture hall was heavy, the kind that feels like it has physical weight. I was sitting in the third row, mentally preparing to present my final project, when a sharp, melodic ringtone shattered the quiet. Everyone turned. I checked my pocket, relieved it wasn't mine, but the damage was done. The professor stopped mid-sentence, the momentum of the room vanished, and my own anxiety spiked. I realized then that I had no reliable way to guarantee my phone wouldn't betray me during the high-stakes moments that define our daily lives.
The problem
We live in an era of constant connectivity, yet our devices lack the basic intelligence to understand context. I wanted my phone to respect the room I was in, not just the time on the clock. I tried using standard alarm settings, but they were too rigid. If a meeting ran late, the alarm would trigger while I was still in the room, or worse, I would forget to turn the ringer back on afterward, missing urgent calls from family. Relying on manual intervention is a recipe for disaster.
I looked for existing solutions, but most required constant GPS polling that decimated battery life within hours. Others relied on flimsy Wi-Fi triggers that failed whenever the signal strength fluctuated. I wanted a solution that felt invisible—something that worked when I walked into a library or a mosque and didn't leave me with a dead battery by lunchtime. The friction wasn't just about silence; it was about the cognitive load of having to manage a device that was supposed to be serving me. I decided to build something that prioritized local state management over cloud-heavy polling.
The technical decision / implementation
To solve this, I opted for the GeofencingClient API from Google Play Services, but with a specific architectural twist regarding how I handled the transition events. Many developers make the mistake of firing up a full-blown Activity or heavy Service the moment an onReceive event is triggered in a BroadcastReceiver. That is an expensive way to handle a simple sound state change.
Instead, I architected the flow to use a JobIntentService—and later migrated to WorkManager for better persistence—to handle the background transition. When the geofence is triggered, the OS wakes the app, and I immediately check the AudioManager to set the RINGER_MODE. Crucially, I implemented a 'cooldown' logic to prevent rapid-fire toggling if the user happens to be walking exactly on the geofence boundary.
kotlin
val geofenceRequest = GeofencingRequest.Builder().apply {
setInitialTrigger(GeofencingRequest.INITIAL_TRIGGER_ENTER)
addGeofences(geofenceList)
}.build()
// Using PendingIntent to trigger a BroadcastReceiver
val intent = Intent(context, GeofenceBroadcastReceiver::class.java)
val pendingIntent = PendingIntent.getBroadcast(
context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
By leveraging the GeofencingClient, the heavy lifting of calculating GPS coordinates and monitoring distance is offloaded to the system’s hardware-level location services. The OS itself keeps track of the boundary, waking my code only when a crossing actually occurs. I avoided the trap of LocationManager.requestLocationUpdates with a high-frequency interval, which is the primary cause of battery drain in location-aware apps. By binding my sound actions to the Geofence transition events, I only execute code when a boundary is actually crossed, keeping the CPU in a deep sleep state for 99% of the day. This creates a predictable, low-overhead loop that respects both the user’s battery and the system’s resource constraints.
What surprised you / what you'd do differently
What truly caught me off guard was the inconsistency of how different Android OEMs handle background execution. I assumed that if I registered a BroadcastReceiver properly, it would always fire. I was wrong. Manufacturers have aggressive battery optimization layers that effectively 'kill' background processes to save juice. On some devices, my geofencing events were silently dropped because the OS decided my app hadn't been interacted with recently enough, even though a background service was running.
I learned the hard way that you cannot trust the OS to keep your app alive just because you asked it to. I had to implement a custom Foreground Service with a persistent notification that acts as a heartbeat. This serves two purposes: it tells the user the service is active, and it forces the OS to treat the app as 'actively in use,' preventing the aggressive task killer from silencing my geofence listeners. If I were starting over, I would have integrated the WorkManager library from day one instead of relying on standard BroadcastReceivers. WorkManager is built to be resilient, automatically rescheduling tasks if they fail due to device reboots or process deaths. I wasted weeks fighting the lifecycle of AlarmManager when WorkManager handles the exact edge cases—like device reboots—that I spent so much time manually coding. The lesson is simple: don't fight the platform's constraints, build your architecture to survive them.
Practical takeaway
If you are building an app that relies on location or background triggers, stop trying to reinvent the wheel by polling GPS coordinates. The OS is significantly better at managing hardware sensors than any developer-written loop could ever be. Utilize GeofencingClient to let the system handle the heavy lifting, and treat your background tasks as ephemeral, intermittent events rather than a constant process. Always assume your app will be killed, and architect your state management so it can resume seamlessly from a cold start.
Automation is only useful if it is invisible and reliable. If your app requires the user to constantly open it to 'check' if a rule is working, you have failed the core value proposition. I built Muffle to eliminate that exact mental overhead, allowing the phone to manage its own sound state so I can focus on my work, not my settings. You can see how I approached the final implementation at https://play.google.com/store/apps/details?id=com.muffle.app. When you build for background efficiency, you are not just saving battery; you are building trust with your users.
Top comments (0)