It was 1:15 PM on a Friday. I was sitting in the back row of a quiet mosque during Jumu'ah prayers, head bowed, trying to focus. Suddenly, the silence of the room was shattered by a high-pitched, insistent ringtone. It wasn't mine—or at least, I hoped it wasn't—but the shame was collective. Every head in the room snapped toward the source. The person frantically fumbling to silence their device looked like they wanted the floor to swallow them whole. I knew that feeling all too well.
That moment of collective social friction is what pushed me to stop thinking about a solution and actually start building one. We all have these 'no-go' zones: meetings, classrooms, medical appointments, and places of worship. The human brain is notoriously bad at remembering to flip a hardware toggle, especially when we are distracted or stressed. If I could just automate the AudioManager state based on context, I could remove that entire category of human error. The goal was simple: my phone should know where I am and respect the silence required there without me touching a single button.
Most people rely on manual intervention or simple time-based alarms, but those are brittle. If a meeting runs long, a time-based trigger fails. If you arrive early, it hasn't started yet. The only way to truly solve this is through a combination of geofencing and calendar integration. The challenge, however, wasn't just building the logic; it was doing it without turning the user’s phone into a space heater that dies by noon. Android’s battery management is aggressive, and rightfully so, which meant my background processes had to be surgically precise.
When I started architecting the location-based component for Muffle, the temptation was to use LocationManager and poll for updates. That is the quickest way to kill a battery and get your app killed by the system’s background execution limits. Instead, I committed to using the GeofencingClient API. This offloads the heavy lifting to the Google Play Services location engine, which uses a combination of Wi-Fi, cell towers, and GPS to trigger intents rather than keeping the radio active for constant polling.
kotlin
val geofencingRequest = GeofencingRequest.Builder().apply {
setInitialTrigger(GeofencingRequest.INITIAL_TRIGGER_ENTER)
addGeofences(geofenceList)
}.build()
geofencingClient.addGeofences(geofenceRequest, geofencePendingIntent)
.addOnSuccessListener { /* Success / }
.addOnFailureListener { / Handle failure */ }
I realized early on that relying solely on GPS was a mistake. In urban canyons, GPS accuracy drops significantly. By utilizing the GeofencingClient, the system intelligently switches between sensors. However, there is a catch: the 'dwell' time. If you set a fence too small, a person walking past the building might trigger it. I had to implement a minimum radius of 100 meters to ensure the system didn't flap between 'Normal' and 'Silent' modes while the user was simply walking down the street outside their office. Dealing with the PendingIntent meant I had to handle the broadcast receiver in the manifest carefully, ensuring that the app could wake up just long enough to modify the AudioManager profile before going back to sleep.
What truly shocked me during the development process was the behavior of Android's 'Do Not Disturb' (DND) permissions. I initially assumed that just having the ACCESS_NOTIFICATION_POLICY permission would be enough. I was wrong. On certain manufacturers—specifically those with heavy-handed battery optimization skins—the system would occasionally suspend my BroadcastReceiver if the app hadn't been opened in a few days. Even if the GeofencingClient fired, my code wouldn't execute the sound change. I had to force the app to become a 'Foreground Service' with a persistent notification to ensure the OS treated it as a high-priority task.
I also learned the hard way about 'emergency bypass.' My first version of Muffle was too good at its job. It silenced the phone so effectively that the user couldn't hear their own spouse calling during an emergency. I had to refactor the entire logic to include a whitelist system that checks the caller ID against a local room database before applying the AudioManager.RINGER_MODE_SILENT flag. It’s a classic developer trap: you build a tool to solve a problem, but in your quest for efficiency, you accidentally create a new, potentially dangerous constraint. I would advise anyone working on background automation to always leave a 'backdoor' for the user.
If I were to start over, I would shift away from relying on the internal Android AlarmManager for scheduling and lean more heavily into the WorkManager library from the beginning. AlarmManager is precise, but it is too easily 'optimized' away by OEM battery managers like those on Samsung or Xiaomi devices. WorkManager is more resilient. It handles the retries and respects the constraints of the underlying hardware much better, even if it introduces a slight delay in execution. Never underestimate the lengths a phone manufacturer will go to in order to 'save' battery by killing your background tasks.
When you are building tools for automation, your biggest enemy is the 'state mismatch.' If your app sets the phone to silent, but the user manually turns it back to normal, your app needs to know that the user has overridden the rule. My current implementation uses a local timestamp log to track the 'last modified by' state. If the user overrides, Muffle backs off until the next trigger occurs. This keeps the app feeling like an assistant rather than a dictator. Think about your app as a guest in the user’s system, not the owner.
For those interested in how these pieces fit together, I’ve kept the project focused on being offline-first, meaning no data ever leaves the device. If you want to see how I handled the geofencing listeners or the prayer-time calculations, you can explore the logic I’ve put together in Muffle. You can find the app here: https://play.google.com/store/apps/details?id=com.muffle.app. Solving for context-aware automation is a balancing act between utility and power consumption, and it is a fascinating challenge for any developer looking to improve their user's daily experience.
Top comments (0)