package com.timesafari.dailynotification; import android.app.AlarmManager; import android.app.PendingIntent; import android.content.Context; import android.content.Intent; import android.os.Build; import android.util.Log; /** * Manages PendingIntent creation with proper flags and exact alarm handling * * Ensures all PendingIntents use correct flags for modern Android versions * and provides comprehensive exact alarm permission handling. * * @author Matthew Raymer * @version 1.0 */ public class PendingIntentManager { private static final String TAG = "PendingIntentManager"; // Modern PendingIntent flags for Android 12+ private static final int MODERN_PENDING_INTENT_FLAGS = PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE; // Legacy flags for older Android versions (if needed) private static final int LEGACY_PENDING_INTENT_FLAGS = PendingIntent.FLAG_UPDATE_CURRENT; private final Context context; private final AlarmManager alarmManager; public PendingIntentManager(Context context) { this.context = context; this.alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); } /** * Create a PendingIntent for broadcast with proper flags * * @param intent The intent to wrap * @param requestCode Unique request code * @return PendingIntent with correct flags */ public PendingIntent createBroadcastPendingIntent(Intent intent, int requestCode) { try { int flags = getPendingIntentFlags(); return PendingIntent.getBroadcast(context, requestCode, intent, flags); } catch (Exception e) { Log.e(TAG, "Error creating broadcast PendingIntent", e); throw e; } } /** * Create a PendingIntent for activity with proper flags * * @param intent The intent to wrap * @param requestCode Unique request code * @return PendingIntent with correct flags */ public PendingIntent createActivityPendingIntent(Intent intent, int requestCode) { try { int flags = getPendingIntentFlags(); return PendingIntent.getActivity(context, requestCode, intent, flags); } catch (Exception e) { Log.e(TAG, "Error creating activity PendingIntent", e); throw e; } } /** * Create a PendingIntent for service with proper flags * * @param intent The intent to wrap * @param requestCode Unique request code * @return PendingIntent with correct flags */ public PendingIntent createServicePendingIntent(Intent intent, int requestCode) { try { int flags = getPendingIntentFlags(); return PendingIntent.getService(context, requestCode, intent, flags); } catch (Exception e) { Log.e(TAG, "Error creating service PendingIntent", e); throw e; } } /** * Get the appropriate PendingIntent flags for the current Android version * * @return Flags to use for PendingIntent creation */ private int getPendingIntentFlags() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { return MODERN_PENDING_INTENT_FLAGS; } else { return LEGACY_PENDING_INTENT_FLAGS; } } /** * Check if exact alarms can be scheduled * * @return true if exact alarms can be scheduled */ public boolean canScheduleExactAlarms() { try { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { return alarmManager.canScheduleExactAlarms(); } else { return true; // Pre-Android 12, always allowed } } catch (Exception e) { Log.e(TAG, "Error checking exact alarm permission", e); return false; } } /** * Schedule an exact alarm with proper error handling * * @param pendingIntent PendingIntent to trigger * @param triggerTime When to trigger the alarm * @return true if scheduling was successful */ public boolean scheduleExactAlarm(PendingIntent pendingIntent, long triggerTime) { try { if (!canScheduleExactAlarms()) { Log.w(TAG, "Cannot schedule exact alarm - permission not granted"); return false; } 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, "Exact alarm scheduled successfully for " + triggerTime); return true; } catch (SecurityException e) { Log.e(TAG, "SecurityException scheduling exact alarm - permission denied", e); return false; } catch (Exception e) { Log.e(TAG, "Error scheduling exact alarm", e); return false; } } /** * Schedule a windowed alarm as fallback * * @param pendingIntent PendingIntent to trigger * @param triggerTime Target trigger time * @param windowLengthMs Window length in milliseconds * @return true if scheduling was successful */ public boolean scheduleWindowedAlarm(PendingIntent pendingIntent, long triggerTime, long windowLengthMs) { try { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { alarmManager.setAndAllowWhileIdle( AlarmManager.RTC_WAKEUP, triggerTime, pendingIntent ); } else { alarmManager.set( AlarmManager.RTC_WAKEUP, triggerTime, pendingIntent ); } Log.d(TAG, "Windowed alarm scheduled successfully for " + triggerTime + " (window: " + windowLengthMs + "ms)"); return true; } catch (Exception e) { Log.e(TAG, "Error scheduling windowed alarm", e); return false; } } /** * Cancel a scheduled alarm * * @param pendingIntent PendingIntent to cancel * @return true if cancellation was successful */ public boolean cancelAlarm(PendingIntent pendingIntent) { try { alarmManager.cancel(pendingIntent); Log.d(TAG, "Alarm cancelled successfully"); return true; } catch (Exception e) { Log.e(TAG, "Error cancelling alarm", e); return false; } } /** * Get detailed alarm scheduling status * * @return AlarmStatus object with detailed information */ public AlarmStatus getAlarmStatus() { boolean exactAlarmsSupported = Build.VERSION.SDK_INT >= Build.VERSION_CODES.S; boolean exactAlarmsGranted = canScheduleExactAlarms(); boolean canScheduleNow = exactAlarmsGranted || !exactAlarmsSupported; return new AlarmStatus( exactAlarmsSupported, exactAlarmsGranted, canScheduleNow, Build.VERSION.SDK_INT ); } /** * Data class for alarm status information */ public static class AlarmStatus { public final boolean exactAlarmsSupported; public final boolean exactAlarmsGranted; public final boolean canScheduleNow; public final int androidVersion; public AlarmStatus(boolean exactAlarmsSupported, boolean exactAlarmsGranted, boolean canScheduleNow, int androidVersion) { this.exactAlarmsSupported = exactAlarmsSupported; this.exactAlarmsGranted = exactAlarmsGranted; this.canScheduleNow = canScheduleNow; this.androidVersion = androidVersion; } @Override public String toString() { return "AlarmStatus{" + "exactAlarmsSupported=" + exactAlarmsSupported + ", exactAlarmsGranted=" + exactAlarmsGranted + ", canScheduleNow=" + canScheduleNow + ", androidVersion=" + androidVersion + '}'; } } }