From 5196b2b496d66874fba7434926af62295b7f84b3 Mon Sep 17 00:00:00 2001 From: Rahul Tripathi Date: Fri, 13 Mar 2026 13:23:56 +0530 Subject: [PATCH] Centralize alarm PendingIntent identities --- .../AlarmPendingIntents.kt | 48 +++++++++++++ .../ultimate_alarm_clock/BootReceiver.kt | 17 ++--- .../ultimate_alarm_clock/MainActivity.kt | 68 ++++++++++++------- .../AlarmPendingIntentContractTest.kt | 25 +++++++ 4 files changed, 122 insertions(+), 36 deletions(-) create mode 100644 android/app/src/main/kotlin/com/ccextractor/ultimate_alarm_clock/ultimate_alarm_clock/AlarmPendingIntents.kt create mode 100644 android/app/src/test/kotlin/com/ccextractor/ultimate_alarm_clock/ultimate_alarm_clock/AlarmPendingIntentContractTest.kt diff --git a/android/app/src/main/kotlin/com/ccextractor/ultimate_alarm_clock/ultimate_alarm_clock/AlarmPendingIntents.kt b/android/app/src/main/kotlin/com/ccextractor/ultimate_alarm_clock/ultimate_alarm_clock/AlarmPendingIntents.kt new file mode 100644 index 000000000..ffc7d480c --- /dev/null +++ b/android/app/src/main/kotlin/com/ccextractor/ultimate_alarm_clock/ultimate_alarm_clock/AlarmPendingIntents.kt @@ -0,0 +1,48 @@ +package com.ccextractor.ultimate_alarm_clock + +import android.app.PendingIntent +import android.content.Context +import android.content.Intent + +internal enum class AlarmPendingIntentKind( + val requestCode: Int, + val isBroadcast: Boolean, +) { + MAIN_ALARM(0, true), + LEGACY_BOOT_ALARM(1, true), + ACTIVITY_CHECK(4, false), + LOCATION_CHECK(5, false), + WEATHER_CHECK(6, false), +} + +internal object AlarmPendingIntents { + private const val FLAGS = PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE + + fun broadcast( + context: Context, + kind: AlarmPendingIntentKind, + intent: Intent, + ): PendingIntent { + require(kind.isBroadcast) { "$kind must use a broadcast PendingIntent" } + return PendingIntent.getBroadcast( + context, + kind.requestCode, + intent, + FLAGS + ) + } + + fun service( + context: Context, + kind: AlarmPendingIntentKind, + intent: Intent, + ): PendingIntent { + require(!kind.isBroadcast) { "$kind must use a service PendingIntent" } + return PendingIntent.getService( + context, + kind.requestCode, + intent, + FLAGS + ) + } +} diff --git a/android/app/src/main/kotlin/com/ccextractor/ultimate_alarm_clock/ultimate_alarm_clock/BootReceiver.kt b/android/app/src/main/kotlin/com/ccextractor/ultimate_alarm_clock/ultimate_alarm_clock/BootReceiver.kt index e7d1ffcba..92ab688e4 100644 --- a/android/app/src/main/kotlin/com/ccextractor/ultimate_alarm_clock/ultimate_alarm_clock/BootReceiver.kt +++ b/android/app/src/main/kotlin/com/ccextractor/ultimate_alarm_clock/ultimate_alarm_clock/BootReceiver.kt @@ -2,7 +2,6 @@ package com.ccextractor.ultimate_alarm_clock import android.annotation.SuppressLint import android.app.AlarmManager -import android.app.PendingIntent import android.content.BroadcastReceiver import android.content.Context import android.content.Intent @@ -68,18 +67,16 @@ class BootReceiver : BroadcastReceiver() { fun scheduleAlarm(milliSeconds: Long, context: Context, activityMonitor: Any) { val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager val intent = Intent(context, AlarmReceiver::class.java) - val pendingIntent = PendingIntent.getBroadcast( + val pendingIntent = AlarmPendingIntents.broadcast( context, - 1, - intent, - PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE + AlarmPendingIntentKind.LEGACY_BOOT_ALARM, + intent ) val activityCheckIntent = Intent(context, ScreenMonitorService::class.java) - val pendingActivityCheckIntent = PendingIntent.getService( + val pendingActivityCheckIntent = AlarmPendingIntents.service( context, - 4, - activityCheckIntent, - PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE + AlarmPendingIntentKind.ACTIVITY_CHECK, + activityCheckIntent ) // Schedule the alarm val tenMinutesInMilliseconds = 600000L @@ -156,4 +153,4 @@ class BootReceiver : BroadcastReceiver() { String.format("%02d:%02d", minutes, seconds) } } -} \ No newline at end of file +} diff --git a/android/app/src/main/kotlin/com/ccextractor/ultimate_alarm_clock/ultimate_alarm_clock/MainActivity.kt b/android/app/src/main/kotlin/com/ccextractor/ultimate_alarm_clock/ultimate_alarm_clock/MainActivity.kt index f97f97f76..640c3ce92 100644 --- a/android/app/src/main/kotlin/com/ccextractor/ultimate_alarm_clock/ultimate_alarm_clock/MainActivity.kt +++ b/android/app/src/main/kotlin/com/ccextractor/ultimate_alarm_clock/ultimate_alarm_clock/MainActivity.kt @@ -4,7 +4,6 @@ import android.annotation.SuppressLint import android.app.ActivityManager import android.app.AlarmManager import android.app.NotificationManager -import android.app.PendingIntent import android.content.Context import android.content.Intent import android.content.IntentFilter @@ -211,18 +210,16 @@ class MainActivity : FlutterActivity() { val alarmManager = getSystemService(Context.ALARM_SERVICE) as AlarmManager val intent = Intent(this, AlarmReceiver::class.java) - val pendingIntent = PendingIntent.getBroadcast( + val pendingIntent = AlarmPendingIntents.broadcast( this, - 0, - intent, - PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE + AlarmPendingIntentKind.MAIN_ALARM, + intent ) val activityCheckIntent = Intent(this, ScreenMonitorService::class.java) - val pendingActivityCheckIntent = PendingIntent.getService( + val pendingActivityCheckIntent = AlarmPendingIntents.service( this, - 4, - activityCheckIntent, - PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE + AlarmPendingIntentKind.ACTIVITY_CHECK, + activityCheckIntent ) // Schedule the alarm val tenMinutesInMilliseconds = 600000L @@ -254,11 +251,10 @@ class MainActivity : FlutterActivity() { editor.putInt("flutter.is_location_on", 1) editor.apply() val locationAlarmIntent = Intent(this, LocationFetcherService::class.java) - val pendingLocationAlarmIntent = PendingIntent.getService( + val pendingLocationAlarmIntent = AlarmPendingIntents.service( this, - 5, - locationAlarmIntent, - PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE + AlarmPendingIntentKind.LOCATION_CHECK, + locationAlarmIntent ) val alarmClockInfo = AlarmManager.AlarmClockInfo(triggerTime - 10000, pendingIntent) alarmManager.setAlarmClock( @@ -273,11 +269,10 @@ class MainActivity : FlutterActivity() { Log.d("we", getWeatherConditions(weatherTypes)) editor.apply() val weatherAlarmIntent = Intent(this, WeatherFetcherService::class.java) - val pendingWeatherAlarmIntent = PendingIntent.getService( + val pendingWeatherAlarmIntent = AlarmPendingIntents.service( this, - 6, - weatherAlarmIntent, - PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE + AlarmPendingIntentKind.WEATHER_CHECK, + weatherAlarmIntent ) val alarmClockInfo = AlarmManager.AlarmClockInfo(triggerTime - 10000, pendingIntent) alarmManager.setAlarmClock( @@ -295,26 +290,47 @@ class MainActivity : FlutterActivity() { private fun cancelAllScheduledAlarms() { val alarmManager = getSystemService(Context.ALARM_SERVICE) as AlarmManager val intent = Intent(this, AlarmReceiver::class.java) - val pendingIntent = PendingIntent.getBroadcast( + val pendingIntent = AlarmPendingIntents.broadcast( this, - 0, - intent, - PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE + AlarmPendingIntentKind.MAIN_ALARM, + intent + ) + val legacyBootPendingIntent = AlarmPendingIntents.broadcast( + this, + AlarmPendingIntentKind.LEGACY_BOOT_ALARM, + intent ) val activityCheckIntent = Intent(this, ScreenMonitorService::class.java) - val pendingActivityCheckIntent = PendingIntent.getService( + val pendingActivityCheckIntent = AlarmPendingIntents.service( + this, + AlarmPendingIntentKind.ACTIVITY_CHECK, + activityCheckIntent + ) + val locationIntent = Intent(this, LocationFetcherService::class.java) + val pendingLocationIntent = AlarmPendingIntents.service( + this, + AlarmPendingIntentKind.LOCATION_CHECK, + locationIntent + ) + val weatherIntent = Intent(this, WeatherFetcherService::class.java) + val pendingWeatherIntent = AlarmPendingIntents.service( this, - 4, - activityCheckIntent, - PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE + AlarmPendingIntentKind.WEATHER_CHECK, + weatherIntent ) // Cancel any existing alarms by providing the same pending intent alarmManager.cancel(pendingIntent) pendingIntent.cancel() + alarmManager.cancel(legacyBootPendingIntent) + legacyBootPendingIntent.cancel() alarmManager.cancel(pendingActivityCheckIntent) pendingActivityCheckIntent.cancel() + alarmManager.cancel(pendingLocationIntent) + pendingLocationIntent.cancel() + alarmManager.cancel(pendingWeatherIntent) + pendingWeatherIntent.cancel() } @@ -556,4 +572,4 @@ class MainActivity : FlutterActivity() { Log.i("SystemRingtone", "=== END DIAGNOSTICS ===") } -} \ No newline at end of file +} diff --git a/android/app/src/test/kotlin/com/ccextractor/ultimate_alarm_clock/ultimate_alarm_clock/AlarmPendingIntentContractTest.kt b/android/app/src/test/kotlin/com/ccextractor/ultimate_alarm_clock/ultimate_alarm_clock/AlarmPendingIntentContractTest.kt new file mode 100644 index 000000000..6f6dff9e3 --- /dev/null +++ b/android/app/src/test/kotlin/com/ccextractor/ultimate_alarm_clock/ultimate_alarm_clock/AlarmPendingIntentContractTest.kt @@ -0,0 +1,25 @@ +package com.ccextractor.ultimate_alarm_clock + +import org.junit.Assert.assertEquals +import org.junit.Assert.assertNotEquals +import org.junit.Assert.assertTrue +import org.junit.Test + +class AlarmPendingIntentContractTest { + @Test + fun `main and legacy boot alarms keep different request codes`() { + assertNotEquals( + AlarmPendingIntentKind.MAIN_ALARM.requestCode, + AlarmPendingIntentKind.LEGACY_BOOT_ALARM.requestCode + ) + assertTrue(AlarmPendingIntentKind.MAIN_ALARM.isBroadcast) + assertTrue(AlarmPendingIntentKind.LEGACY_BOOT_ALARM.isBroadcast) + } + + @Test + fun `service request codes stay stable`() { + assertEquals(4, AlarmPendingIntentKind.ACTIVITY_CHECK.requestCode) + assertEquals(5, AlarmPendingIntentKind.LOCATION_CHECK.requestCode) + assertEquals(6, AlarmPendingIntentKind.WEATHER_CHECK.requestCode) + } +}