Browse Source
- Add android:exported="true" for API 31+ compatibility - Add android:directBootAware="true" for Direct Boot handling - Add LOCKED_BOOT_COMPLETED action for early boot recovery - Remove PACKAGE_REPLACED action (not needed for our use case) - Implement handleLockedBootCompleted() for Direct Boot safety - Use device protected storage context for Direct Boot operations - Add comprehensive logging for boot receiver events This fixes Android 10+ boot receiver restrictions and ensures notifications are restored after device reboots and app updates.master
2 changed files with 179 additions and 5 deletions
@ -0,0 +1,167 @@ |
|||
/** |
|||
* BootReceiver.java |
|||
* |
|||
* Android Boot Receiver for DailyNotification plugin |
|||
* Handles system boot events to restore scheduled notifications |
|||
* |
|||
* @author Matthew Raymer |
|||
* @version 1.0.0 |
|||
*/ |
|||
|
|||
package com.timesafari.dailynotification; |
|||
|
|||
import android.content.BroadcastReceiver; |
|||
import android.content.Context; |
|||
import android.content.Intent; |
|||
import android.util.Log; |
|||
|
|||
/** |
|||
* Broadcast receiver for system boot events |
|||
* |
|||
* This receiver is triggered when: |
|||
* - Device boots up (BOOT_COMPLETED) |
|||
* - App is updated (MY_PACKAGE_REPLACED) |
|||
* - Any package is updated (PACKAGE_REPLACED) |
|||
* |
|||
* It ensures that scheduled notifications are restored after system events |
|||
* that might have cleared the alarm manager. |
|||
*/ |
|||
public class BootReceiver extends BroadcastReceiver { |
|||
|
|||
private static final String TAG = "BootReceiver"; |
|||
|
|||
// Broadcast actions we handle
|
|||
private static final String ACTION_LOCKED_BOOT_COMPLETED = "android.intent.action.LOCKED_BOOT_COMPLETED"; |
|||
private static final String ACTION_BOOT_COMPLETED = "android.intent.action.BOOT_COMPLETED"; |
|||
private static final String ACTION_MY_PACKAGE_REPLACED = "android.intent.action.MY_PACKAGE_REPLACED"; |
|||
|
|||
@Override |
|||
public void onReceive(Context context, Intent intent) { |
|||
if (intent == null || intent.getAction() == null) { |
|||
Log.w(TAG, "Received null intent or action"); |
|||
return; |
|||
} |
|||
|
|||
String action = intent.getAction(); |
|||
Log.d(TAG, "Received broadcast: " + action); |
|||
|
|||
try { |
|||
switch (action) { |
|||
case ACTION_LOCKED_BOOT_COMPLETED: |
|||
handleLockedBootCompleted(context); |
|||
break; |
|||
|
|||
case ACTION_BOOT_COMPLETED: |
|||
handleBootCompleted(context); |
|||
break; |
|||
|
|||
case ACTION_MY_PACKAGE_REPLACED: |
|||
handlePackageReplaced(context, intent); |
|||
break; |
|||
|
|||
default: |
|||
Log.w(TAG, "Unknown action: " + action); |
|||
break; |
|||
} |
|||
} catch (Exception e) { |
|||
Log.e(TAG, "Error handling broadcast: " + action, e); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Handle locked boot completion (before user unlock) |
|||
* |
|||
* @param context Application context |
|||
*/ |
|||
private void handleLockedBootCompleted(Context context) { |
|||
Log.i(TAG, "Locked boot completed - preparing for recovery"); |
|||
|
|||
try { |
|||
// Use device protected storage context for Direct Boot
|
|||
Context deviceProtectedContext = context; |
|||
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) { |
|||
deviceProtectedContext = context.createDeviceProtectedStorageContext(); |
|||
} |
|||
|
|||
// Minimal work here - just log that we're ready
|
|||
// Full recovery will happen on BOOT_COMPLETED when storage is available
|
|||
Log.i(TAG, "Locked boot completed - ready for full recovery on unlock"); |
|||
|
|||
} catch (Exception e) { |
|||
Log.e(TAG, "Error during locked boot completion", e); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Handle device boot completion (after user unlock) |
|||
* |
|||
* @param context Application context |
|||
*/ |
|||
private void handleBootCompleted(Context context) { |
|||
Log.i(TAG, "Device boot completed - restoring notifications"); |
|||
|
|||
try { |
|||
// Initialize storage to load saved notifications
|
|||
DailyNotificationStorage storage = new DailyNotificationStorage(context); |
|||
|
|||
// Get all saved notifications
|
|||
java.util.List<NotificationContent> notifications = storage.getAllNotifications(); |
|||
|
|||
if (notifications.isEmpty()) { |
|||
Log.i(TAG, "No notifications to recover"); |
|||
return; |
|||
} |
|||
|
|||
Log.i(TAG, "Found " + notifications.size() + " notifications to recover"); |
|||
|
|||
// Initialize scheduler for rescheduling
|
|||
android.app.AlarmManager alarmManager = (android.app.AlarmManager) |
|||
context.getSystemService(android.content.Context.ALARM_SERVICE); |
|||
DailyNotificationScheduler scheduler = new DailyNotificationScheduler(context, alarmManager); |
|||
|
|||
// Reschedule each notification
|
|||
int recoveredCount = 0; |
|||
for (NotificationContent notification : notifications) { |
|||
try { |
|||
// Only reschedule future notifications
|
|||
if (notification.getScheduledTime() > System.currentTimeMillis()) { |
|||
boolean scheduled = scheduler.scheduleNotification(notification); |
|||
if (scheduled) { |
|||
recoveredCount++; |
|||
Log.d(TAG, "Recovered notification: " + notification.getId()); |
|||
} else { |
|||
Log.w(TAG, "Failed to recover notification: " + notification.getId()); |
|||
} |
|||
} else { |
|||
Log.d(TAG, "Skipping past notification: " + notification.getId()); |
|||
} |
|||
} catch (Exception e) { |
|||
Log.e(TAG, "Error recovering notification: " + notification.getId(), e); |
|||
} |
|||
} |
|||
|
|||
Log.i(TAG, "Notification recovery completed: " + recoveredCount + "/" + notifications.size() + " recovered"); |
|||
|
|||
} catch (Exception e) { |
|||
Log.e(TAG, "Error during boot recovery", e); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Handle package replacement (app update) |
|||
* |
|||
* @param context Application context |
|||
* @param intent Broadcast intent |
|||
*/ |
|||
private void handlePackageReplaced(Context context, Intent intent) { |
|||
Log.i(TAG, "Package replaced - restoring notifications"); |
|||
|
|||
try { |
|||
// Use the same recovery logic as boot
|
|||
handleBootCompleted(context); |
|||
|
|||
} catch (Exception e) { |
|||
Log.e(TAG, "Error during package replacement recovery", e); |
|||
} |
|||
} |
|||
} |
Loading…
Reference in new issue