package com.hyperling.apps.infinitetimer; import android.app.AlarmManager; import android.app.PendingIntent; import android.content.Context; import android.content.Intent; import android.os.Build; import android.provider.Settings; import android.util.Log; public class Alert { private static final String TAG = "Alert"; public static final String ACTION_TIMER = "com.hyperling.apps.infinitetimer.ACTION_TIMER"; /** * Sets a persistent alarm that triggers the TimerReceiver. * Uses setExactAndAllowWhileIdle to bypass Android Doze mode. */ public static void start(Context context, long intervalMs) { Intent intent = new Intent(context, TimerReceiver.class); intent.setAction(ACTION_TIMER); // FLAG_IMMUTABLE required for Android 12+ (API 31+) int flags = PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_CANCEL_CURRENT; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { flags |= PendingIntent.FLAG_IMMUTABLE; } PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, intent, flags); AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); if (alarmManager != null) { long triggerTime = System.currentTimeMillis() + intervalMs; // Exact alarm ensures it triggers even in Doze mode if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, triggerTime, pendingIntent); } else { alarmManager.setExact(AlarmManager.RTC_WAKEUP, triggerTime, pendingIntent); } Log.d(TAG, "Alarm set for " + intervalMs + "ms at " + triggerTime); } else { Log.e(TAG, "Could not get AlarmManager service"); } } /** * Cancels the timer. If the app is killed, alarms are dropped automatically. */ public static void stop(Context context) { Intent intent = new Intent(context, TimerReceiver.class); int flags = PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_CANCEL_CURRENT; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { flags |= PendingIntent.FLAG_IMMUTABLE; } PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, intent, flags); AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); if (alarmManager != null) { alarmManager.cancel(pendingIntent); Log.d(TAG, "Alarm cancelled"); } } /** * Checks/Requests permission for exact alarms on Android 12+ */ public static boolean isExactAlarmAllowed(Context context) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); return am != null && am.canScheduleExactAlarms(); } return true; // Android 11 and below allow exact alarms by default } /** * Helper to request exact alarm permission if missing */ public static void requestExactAlarmPermission(android.app.Activity activity) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && !isExactAlarmAllowed(activity)) { Intent intent = new Intent(Settings.ACTION_REQUEST_SCHEDULE_EXACT_ALARM); activity.startActivity(intent); } } }