<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Haseeb</title>
    <description>The latest articles on DEV Community by Haseeb (@haseebthedev0).</description>
    <link>https://dev.to/haseebthedev0</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3995506%2F951495ab-bcdb-490f-bd90-50745909bc82.jpg</url>
      <title>DEV Community: Haseeb</title>
      <link>https://dev.to/haseebthedev0</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/haseebthedev0"/>
    <language>en</language>
    <item>
      <title>Keeping Android Services Alive Against OEM Battery Aggression</title>
      <dc:creator>Haseeb</dc:creator>
      <pubDate>Fri, 26 Jun 2026 09:37:31 +0000</pubDate>
      <link>https://dev.to/haseebthedev0/keeping-android-services-alive-against-oem-battery-aggression-3n4h</link>
      <guid>https://dev.to/haseebthedev0/keeping-android-services-alive-against-oem-battery-aggression-3n4h</guid>
      <description>&lt;p&gt;It was the middle of a Friday afternoon, and I was sitting in the front row of a local mosque. The room was deathly quiet, the kind of silence that amplifies every heartbeat. Suddenly, three rows behind me, a phone erupted with a loud, brassy ringtone that seemed to go on for an eternity. The man scrambled to silence it, his face turning bright red as he fumbled with his screen. I felt his humiliation deeply. In that moment, I realized that modern smartphones—despite their intelligence—are remarkably stupid when it comes to context-aware social etiquette.&lt;/p&gt;

&lt;p&gt;We live in a world of smart devices, yet we are still manually toggling our volume settings like it is 2005. I have spent years forgetting to silence my phone before a meeting, a lecture, or a quiet space, only to have it buzz loudly at the worst possible time. It is a friction point that feels trivial until it happens to you, at which point it becomes incredibly disruptive. Existing solutions often fall into two camps: over-engineered automation tools that require a computer science degree to configure, or basic calendar-sync apps that lack the nuance needed for things like location-based triggers or recurring religious observances. I wanted something that just worked, quietly, in the background, without requiring me to constantly open an app to double-check if my rules were still active.&lt;/p&gt;

&lt;p&gt;When I started building Muffle, I quickly realized that the greatest obstacle wasn't the logic of detecting a location or a prayer time—it was the operating system itself. Android, in its quest to squeeze every millisecond of battery life out of a device, has turned into a minefield for developers trying to keep background tasks alive. If you rely on a standard &lt;code&gt;Service&lt;/code&gt;, the system will kill it within minutes as soon as the user turns the screen off. I needed a way to ensure that my background monitoring, especially for geofencing and prayer time calculations, stayed alive even when the phone was sitting in a pocket for hours.&lt;/p&gt;

&lt;p&gt;I settled on using a &lt;code&gt;Foreground Service&lt;/code&gt; coupled with a &lt;code&gt;Notification&lt;/code&gt; that is impossible for the system to ignore easily. But that wasn't enough. Many OEMs, particularly those from manufacturers like Xiaomi, Oppo, and Samsung, implement aggressive custom battery management that kills processes despite standard Android documentation guidelines. I had to architect a system that didn't just rely on a single process. I implemented a &lt;code&gt;JobScheduler&lt;/code&gt; as a secondary heartbeat. If the &lt;code&gt;Foreground Service&lt;/code&gt; is terminated by a memory-hungry OEM process, the &lt;code&gt;JobScheduler&lt;/code&gt; acts as a watchdog to restart the service. &lt;/p&gt;

&lt;p&gt;kotlin&lt;br&gt;
val serviceIntent = Intent(context, MuffleService::class.java)&lt;br&gt;
if (Build.VERSION.SDK_INT &amp;gt;= Build.VERSION_CODES.O) {&lt;br&gt;
    context.startForegroundService(serviceIntent)&lt;br&gt;
} else {&lt;br&gt;
    context.startService(serviceIntent)&lt;br&gt;
}&lt;br&gt;
// Using a JobScheduler to monitor the service status&lt;br&gt;
val jobInfo = JobInfo.Builder(JOB_ID, componentName)&lt;br&gt;
    .setPeriodic(15 * 60 * 1000) // Every 15 minutes&lt;br&gt;
    .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)&lt;br&gt;
    .setPersisted(true)&lt;br&gt;
    .build()&lt;/p&gt;

&lt;p&gt;This architecture ensures that even if the OS forces a closure, the app recovers quickly. However, the real secret sauce was not just in the code, but in the way I communicated with the user. I had to be transparent about why the notification was necessary. Users hate persistent notifications, but by explaining that this is the only way to ensure their phone stays silent during important events, the friction of seeing the icon turns into a sense of security. I treated the notification as a dashboard rather than an annoyance.&lt;/p&gt;

&lt;p&gt;What truly surprised me during the development process was how much the &lt;code&gt;AlarmManager&lt;/code&gt; behaves differently across API levels. I initially assumed that using &lt;code&gt;setExactAndAllowWhileIdle&lt;/code&gt; would be enough to wake the device for a precise prayer time calculation. I was wrong. On newer Android versions, the system enforces a strict quota on how often an app can wake the device. If my app triggered too many alarms, the OS would actually throttle my app's ability to set future alarms, effectively silencing my app's internal clock. I spent three nights debugging why my triggers were missing, only to find a buried log entry indicating a &lt;code&gt;BatteryManager&lt;/code&gt; restriction.&lt;/p&gt;

&lt;p&gt;I also learned that &lt;code&gt;GeofencingClient&lt;/code&gt; is far less reliable than I anticipated. In areas with poor GPS triangulation, the radius of a geofence can shift significantly, leading to false positives or, worse, failing to trigger entirely when a user walks into a building. If I were starting over, I would build a hybrid system that combines &lt;code&gt;GeofencingClient&lt;/code&gt; with &lt;code&gt;ActivityRecognitionClient&lt;/code&gt;. Instead of relying solely on coordinate proximity, I would monitor if the user has stopped moving for a specific duration after entering a boundary. This would eliminate the phantom triggers that happen when you simply drive past a location without actually stopping there. It is a classic case of "perfect on paper, messy in reality."&lt;/p&gt;

&lt;p&gt;If you are building an app that requires background reliability, stop trusting the documentation that says standard services are sufficient. You have to assume the OS is actively trying to kill your app to save battery. This means your architecture must be modular. Your state must be saved to a local &lt;code&gt;Room&lt;/code&gt; database before every single action, because you should assume your process will be wiped from memory at any second. If you don't have an &lt;code&gt;onTaskRemoved&lt;/code&gt; implementation that gracefully handles cleanup and restart logic, you are going to see a flood of bug reports from users with specific device brands.&lt;/p&gt;

&lt;p&gt;The most important lesson I learned is that transparency is a feature. When I decided to make Muffle a privacy-first, offline-only app, I was worried that users would miss out on cloud-synced settings. Instead, users appreciated that their location data never left their device. They were more willing to grant the "Battery Optimization" exceptions when they understood the data stayed local. If you provide a tangible benefit—like never having a phone ring in a lecture again—users are surprisingly forgiving of the overhead. You can find more about how I structured the persistence layer for Muffle at &lt;a href="https://play.google.com/store/apps/details?id=com.muffle.app" rel="noopener noreferrer"&gt;https://play.google.com/store/apps/details?id=com.muffle.app&lt;/a&gt;. Building for the real world means building for the edge cases that the documentation ignores.&lt;/p&gt;

</description>
      <category>android</category>
      <category>kotlin</category>
      <category>mobiledev</category>
      <category>androiddev</category>
    </item>
    <item>
      <title>Keeping Android Background Services Alive Against OEM Aggression</title>
      <dc:creator>Haseeb</dc:creator>
      <pubDate>Thu, 25 Jun 2026 12:49:29 +0000</pubDate>
      <link>https://dev.to/haseebthedev0/keeping-android-background-services-alive-against-oem-aggression-41l4</link>
      <guid>https://dev.to/haseebthedev0/keeping-android-background-services-alive-against-oem-aggression-41l4</guid>
      <description>&lt;p&gt;We have all been there: you build a utility app that relies on precise location or time-based triggers, only to find that it works perfectly on your Pixel but dies silently on a Samsung or Xiaomi device. When I started building Muffle, an app designed to automate sound profiles based on prayer times and GPS, I realized that standard &lt;code&gt;AlarmManager&lt;/code&gt; usage wasn't enough to survive aggressive battery optimizations.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Problem with OEM Kill-Switches
&lt;/h3&gt;

&lt;p&gt;Modern Android versions enforce strict background execution limits. If your app isn't a high-priority foreground service, OEMs will frequently kill your process to save a few milliwatts of battery. For Muffle, if the process dies, the user misses their silent profile trigger, which defeats the entire purpose of the app. I had to move away from relying on a long-running background service and rethink my architecture entirely.&lt;/p&gt;

&lt;h3&gt;
  
  
  Moving to WorkManager with Expedited Jobs
&lt;/h3&gt;

&lt;p&gt;Instead of a persistent service, I transitioned the core logic to &lt;code&gt;WorkManager&lt;/code&gt;. By utilizing &lt;code&gt;ExistingPeriodicWorkPolicy.UPDATE&lt;/code&gt;, I ensure that the scheduling remains consistent even across reboots. However, &lt;code&gt;WorkManager&lt;/code&gt; alone can be delayed by Doze mode. To combat this, I implemented &lt;code&gt;setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST)&lt;/code&gt; for critical profile switches. This tells the system that the work is time-sensitive.&lt;/p&gt;

&lt;p&gt;kotlin&lt;br&gt;
val workRequest = PeriodicWorkRequestBuilder(15, TimeUnit.MINUTES)&lt;br&gt;
    .setConstraints(Constraints.Builder().build())&lt;br&gt;
    .setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST)&lt;br&gt;
    .build()&lt;/p&gt;

&lt;h3&gt;
  
  
  Leveraging Foreground Services with Notifications
&lt;/h3&gt;

&lt;p&gt;For features requiring immediate precision—like geofencing—I had to accept that a persistent notification is non-negotiable. To keep the app from being perceived as 'spammy,' I designed the notification to be low-priority, showing only when a profile is actively being managed. &lt;/p&gt;

&lt;p&gt;I also had to handle the &lt;code&gt;onTaskRemoved&lt;/code&gt; callback in my &lt;code&gt;Service&lt;/code&gt; implementation. By calling &lt;code&gt;startService&lt;/code&gt; again with a sticky intent, I can ensure the service restarts if the system kills it, though this is a last resort. I also guide users to &lt;a href="https://dontkillmyapp.com" rel="noopener noreferrer"&gt;dontkillmyapp.com&lt;/a&gt; settings within the app so they can manually disable battery optimizations for Muffle, which remains the most reliable way to ensure the app functions as intended.&lt;/p&gt;

&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;Reliability on Android isn't about fighting the OS; it's about playing by the rules of the battery manager. By combining &lt;code&gt;WorkManager&lt;/code&gt; for scheduled tasks and a transparent, user-managed foreground service for geofencing, Muffle remains stable without draining the user's battery. Building in public means acknowledging that sometimes, the platform limitations are the hardest part of the project.&lt;/p&gt;

</description>
      <category>android</category>
      <category>kotlin</category>
      <category>mobiledev</category>
      <category>programming</category>
    </item>
    <item>
      <title>Maintaining Background Sync in Muffle: Managing Android Doze Mode and AlarmManager</title>
      <dc:creator>Haseeb</dc:creator>
      <pubDate>Tue, 23 Jun 2026 06:54:41 +0000</pubDate>
      <link>https://dev.to/haseebthedev0/maintaining-background-sync-in-muffle-managing-android-doze-mode-and-alarmmanager-1oek</link>
      <guid>https://dev.to/haseebthedev0/maintaining-background-sync-in-muffle-managing-android-doze-mode-and-alarmmanager-1oek</guid>
      <description>&lt;p&gt;Building an app like Muffle, which relies on precise, time-sensitive sound profile changes, presents a classic Android development challenge: how do you ensure a task executes exactly when needed without getting killed by the system's aggressive battery optimizations?&lt;/p&gt;

&lt;h3&gt;
  
  
  The Doze Mode Dilemma
&lt;/h3&gt;

&lt;p&gt;When I started building Muffle, my first instinct was to use a simple background service. However, Android’s Doze Mode quickly became a hurdle. If the device is stationary and unplugged, the system restricts network access and defers background jobs to save power. For a user expecting their phone to silence right at the start of a prayer time or a calendar meeting, a 15-minute delay caused by a maintenance window is unacceptable.&lt;/p&gt;

&lt;p&gt;To solve this, I moved away from persistent background services. Instead, I architected the trigger engine around &lt;code&gt;AlarmManager&lt;/code&gt; with &lt;code&gt;setExactAndAllowWhileIdle&lt;/code&gt;. By scheduling specific &lt;code&gt;PendingIntents&lt;/code&gt; for upcoming events, I ensure that the OS wakes the app precisely when the volume needs to toggle, even if the device is in a deep sleep state.&lt;/p&gt;

&lt;h3&gt;
  
  
  Handling Lifecycle and Constraints
&lt;/h3&gt;

&lt;p&gt;One major trade-off here is the overhead of waking the CPU. Frequent, poorly managed alarms can degrade battery life, which is the antithesis of a helpful utility. I implemented a 'next-event-only' scheduling logic. Rather than scheduling every event for the day, the app calculates the single nearest upcoming trigger. Once that event finishes, it reschedules the next one. This reduces the number of pending alarms significantly.&lt;/p&gt;

&lt;p&gt;I also had to account for the &lt;code&gt;REQUEST_IGNORE_BATTERY_OPTIMIZATIONS&lt;/code&gt; permission. While it's tempting to ask users to whitelist the app, I decided against this for the default experience. It’s a privacy-invasive request that creates friction. Instead, by carefully using &lt;code&gt;WorkManager&lt;/code&gt; for non-critical tasks (like syncing calendar events) and reserving &lt;code&gt;AlarmManager&lt;/code&gt; for the time-sensitive volume toggles, the app remains responsive without needing to be excluded from standard battery management.&lt;/p&gt;

&lt;h3&gt;
  
  
  Privacy as an Architectural Decision
&lt;/h3&gt;

&lt;p&gt;Because Muffle handles sensitive data like location and calendar events, the offline-first architecture is critical. By keeping all scheduling logic local to the device—using Room for event storage and local broadcast receivers for system triggers—I avoid the need for a backend entirely. This not only reinforces user trust but also eliminates the need for network-based wake-locks, which are notorious for draining battery life.&lt;/p&gt;

&lt;p&gt;Building for Android means constantly balancing system restrictions against user expectations. Moving to an event-based scheduling model, rather than a polling-based one, allowed Muffle to be both reliable and battery-efficient.&lt;/p&gt;

</description>
      <category>android</category>
      <category>kotlin</category>
      <category>mobiledev</category>
      <category>batteryoptimization</category>
    </item>
  </channel>
</rss>
