- Add static daily reminders to README.md core features and API reference - Create detailed usage guide in USAGE.md with examples and best practices - Add version 2.1.0 changelog entry documenting new reminder functionality - Create examples/static-daily-reminders.ts with complete usage examples - Update test-apps README to include reminder testing capabilities The static daily reminder feature provides simple daily notifications without network content dependency, supporting cross-platform scheduling with rich customization options.
733 lines
26 KiB
Java
733 lines
26 KiB
Java
/**
|
|
* DailyNotificationScheduler.java
|
|
*
|
|
* Handles scheduling and timing of daily notifications
|
|
* Implements exact and inexact alarm scheduling with battery optimization handling
|
|
*
|
|
* @author Matthew Raymer
|
|
* @version 1.0.0
|
|
*/
|
|
|
|
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;
|
|
|
|
import java.util.Calendar;
|
|
import java.util.concurrent.ConcurrentHashMap;
|
|
|
|
/**
|
|
* Manages scheduling of daily notifications using AlarmManager
|
|
*
|
|
* This class handles the scheduling aspect of the prefetch → cache → schedule → display pipeline.
|
|
* It supports both exact and inexact alarms based on system permissions and battery optimization.
|
|
*/
|
|
public class DailyNotificationScheduler {
|
|
|
|
private static final String TAG = "DailyNotificationScheduler";
|
|
private static final String ACTION_NOTIFICATION = "com.timesafari.daily.NOTIFICATION";
|
|
private static final String EXTRA_NOTIFICATION_ID = "notification_id";
|
|
|
|
private final Context context;
|
|
private final AlarmManager alarmManager;
|
|
private final ConcurrentHashMap<String, PendingIntent> scheduledAlarms;
|
|
|
|
// TTL enforcement
|
|
private DailyNotificationTTLEnforcer ttlEnforcer;
|
|
|
|
// Exact alarm management
|
|
private DailyNotificationExactAlarmManager exactAlarmManager;
|
|
|
|
/**
|
|
* Constructor
|
|
*
|
|
* @param context Application context
|
|
* @param alarmManager System AlarmManager service
|
|
*/
|
|
public DailyNotificationScheduler(Context context, AlarmManager alarmManager) {
|
|
this.context = context;
|
|
this.alarmManager = alarmManager;
|
|
this.scheduledAlarms = new ConcurrentHashMap<>();
|
|
}
|
|
|
|
/**
|
|
* Set TTL enforcer for freshness validation
|
|
*
|
|
* @param ttlEnforcer TTL enforcement instance
|
|
*/
|
|
public void setTTLEnforcer(DailyNotificationTTLEnforcer ttlEnforcer) {
|
|
this.ttlEnforcer = ttlEnforcer;
|
|
Log.d(TAG, "TTL enforcer set for freshness validation");
|
|
}
|
|
|
|
/**
|
|
* Set exact alarm manager for alarm scheduling
|
|
*
|
|
* @param exactAlarmManager Exact alarm manager instance
|
|
*/
|
|
public void setExactAlarmManager(DailyNotificationExactAlarmManager exactAlarmManager) {
|
|
this.exactAlarmManager = exactAlarmManager;
|
|
Log.d(TAG, "Exact alarm manager set for alarm scheduling");
|
|
}
|
|
|
|
/**
|
|
* Schedule a notification for delivery (Phase 3 enhanced)
|
|
*
|
|
* @param content Notification content to schedule
|
|
* @return true if scheduling was successful
|
|
*/
|
|
public boolean scheduleNotification(NotificationContent content) {
|
|
try {
|
|
Log.d(TAG, "Phase 3: Scheduling notification: " + content.getId());
|
|
|
|
// Phase 3: TimeSafari coordination before scheduling
|
|
if (!shouldScheduleWithTimeSafariCoordination(content)) {
|
|
Log.w(TAG, "Phase 3: Scheduling blocked by TimeSafari coordination");
|
|
return false;
|
|
}
|
|
|
|
// TTL validation before arming
|
|
if (ttlEnforcer != null) {
|
|
if (!ttlEnforcer.validateBeforeArming(content)) {
|
|
Log.w(TAG, "Skipping notification due to TTL violation: " + content.getId());
|
|
return false;
|
|
}
|
|
} else {
|
|
Log.w(TAG, "TTL enforcer not set, proceeding without freshness validation");
|
|
}
|
|
|
|
// Cancel any existing alarm for this notification
|
|
cancelNotification(content.getId());
|
|
|
|
// Create intent for the notification
|
|
Intent intent = new Intent(context, DailyNotificationReceiver.class);
|
|
intent.setAction(ACTION_NOTIFICATION);
|
|
intent.putExtra(EXTRA_NOTIFICATION_ID, content.getId());
|
|
|
|
// Check if this is a static reminder
|
|
if (content.getId().startsWith("reminder_") || content.getId().contains("_reminder")) {
|
|
intent.putExtra("is_static_reminder", true);
|
|
intent.putExtra("reminder_id", content.getId());
|
|
intent.putExtra("title", content.getTitle());
|
|
intent.putExtra("body", content.getBody());
|
|
intent.putExtra("sound", content.isSound());
|
|
intent.putExtra("vibration", true); // Default to true for reminders
|
|
intent.putExtra("priority", content.getPriority());
|
|
}
|
|
|
|
// Create pending intent with unique request code
|
|
int requestCode = content.getId().hashCode();
|
|
PendingIntent pendingIntent = PendingIntent.getBroadcast(
|
|
context,
|
|
requestCode,
|
|
intent,
|
|
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE
|
|
);
|
|
|
|
// Store the pending intent
|
|
scheduledAlarms.put(content.getId(), pendingIntent);
|
|
|
|
// Schedule the alarm
|
|
long triggerTime = content.getScheduledTime();
|
|
boolean scheduled = scheduleAlarm(pendingIntent, triggerTime);
|
|
|
|
if (scheduled) {
|
|
Log.i(TAG, "Notification scheduled successfully for " +
|
|
formatTime(triggerTime));
|
|
return true;
|
|
} else {
|
|
Log.e(TAG, "Failed to schedule notification");
|
|
scheduledAlarms.remove(content.getId());
|
|
return false;
|
|
}
|
|
|
|
} catch (Exception e) {
|
|
Log.e(TAG, "Error scheduling notification", e);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Schedule an alarm using the best available method
|
|
*
|
|
* @param pendingIntent PendingIntent to trigger
|
|
* @param triggerTime When to trigger the alarm
|
|
* @return true if scheduling was successful
|
|
*/
|
|
private boolean scheduleAlarm(PendingIntent pendingIntent, long triggerTime) {
|
|
try {
|
|
// Use exact alarm manager if available
|
|
if (exactAlarmManager != null) {
|
|
return exactAlarmManager.scheduleAlarm(pendingIntent, triggerTime);
|
|
}
|
|
|
|
// Fallback to legacy scheduling
|
|
if (canUseExactAlarms()) {
|
|
return scheduleExactAlarm(pendingIntent, triggerTime);
|
|
} else {
|
|
return scheduleInexactAlarm(pendingIntent, triggerTime);
|
|
}
|
|
} catch (Exception e) {
|
|
Log.e(TAG, "Error scheduling alarm", e);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Schedule an exact alarm for precise timing
|
|
*
|
|
* @param pendingIntent PendingIntent to trigger
|
|
* @param triggerTime When to trigger the alarm
|
|
* @return true if scheduling was successful
|
|
*/
|
|
private boolean scheduleExactAlarm(PendingIntent pendingIntent, long triggerTime) {
|
|
try {
|
|
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 for " + formatTime(triggerTime));
|
|
return true;
|
|
|
|
} catch (Exception e) {
|
|
Log.e(TAG, "Error scheduling exact alarm", e);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Schedule an inexact alarm for battery optimization
|
|
*
|
|
* @param pendingIntent PendingIntent to trigger
|
|
* @param triggerTime When to trigger the alarm
|
|
* @return true if scheduling was successful
|
|
*/
|
|
private boolean scheduleInexactAlarm(PendingIntent pendingIntent, long triggerTime) {
|
|
try {
|
|
alarmManager.setRepeating(
|
|
AlarmManager.RTC_WAKEUP,
|
|
triggerTime,
|
|
AlarmManager.INTERVAL_DAY,
|
|
pendingIntent
|
|
);
|
|
|
|
Log.d(TAG, "Inexact alarm scheduled for " + formatTime(triggerTime));
|
|
return true;
|
|
|
|
} catch (Exception e) {
|
|
Log.e(TAG, "Error scheduling inexact alarm", e);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check if we can use exact alarms
|
|
*
|
|
* @return true if exact alarms are permitted
|
|
*/
|
|
private boolean canUseExactAlarms() {
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
|
return alarmManager.canScheduleExactAlarms();
|
|
}
|
|
return true; // Pre-Android 12 always allowed exact alarms
|
|
}
|
|
|
|
/**
|
|
* Cancel a specific notification
|
|
*
|
|
* @param notificationId ID of notification to cancel
|
|
*/
|
|
public void cancelNotification(String notificationId) {
|
|
try {
|
|
PendingIntent pendingIntent = scheduledAlarms.remove(notificationId);
|
|
if (pendingIntent != null) {
|
|
alarmManager.cancel(pendingIntent);
|
|
pendingIntent.cancel();
|
|
Log.d(TAG, "Cancelled notification: " + notificationId);
|
|
}
|
|
} catch (Exception e) {
|
|
Log.e(TAG, "Error cancelling notification: " + notificationId, e);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Cancel all scheduled notifications
|
|
*/
|
|
public void cancelAllNotifications() {
|
|
try {
|
|
Log.d(TAG, "Cancelling all notifications");
|
|
|
|
for (String notificationId : scheduledAlarms.keySet()) {
|
|
cancelNotification(notificationId);
|
|
}
|
|
|
|
scheduledAlarms.clear();
|
|
Log.i(TAG, "All notifications cancelled");
|
|
|
|
} catch (Exception e) {
|
|
Log.e(TAG, "Error cancelling all notifications", e);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get the next scheduled notification time
|
|
*
|
|
* @return Timestamp of next notification or 0 if none scheduled
|
|
*/
|
|
public long getNextNotificationTime() {
|
|
// This would need to be implemented with actual notification data
|
|
// For now, return a placeholder
|
|
return System.currentTimeMillis() + (24 * 60 * 60 * 1000); // 24 hours from now
|
|
}
|
|
|
|
/**
|
|
* Get count of pending notifications
|
|
*
|
|
* @return Number of scheduled notifications
|
|
*/
|
|
public int getPendingNotificationsCount() {
|
|
return scheduledAlarms.size();
|
|
}
|
|
|
|
/**
|
|
* Update notification settings for existing notifications
|
|
*/
|
|
public void updateNotificationSettings() {
|
|
try {
|
|
Log.d(TAG, "Updating notification settings");
|
|
|
|
// This would typically involve rescheduling notifications
|
|
// with new settings. For now, just log the action.
|
|
Log.i(TAG, "Notification settings updated");
|
|
|
|
} catch (Exception e) {
|
|
Log.e(TAG, "Error updating notification settings", e);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Enable adaptive scheduling based on device state
|
|
*/
|
|
public void enableAdaptiveScheduling() {
|
|
try {
|
|
Log.d(TAG, "Enabling adaptive scheduling");
|
|
|
|
// This would implement logic to adjust scheduling based on:
|
|
// - Battery level
|
|
// - Power save mode
|
|
// - Doze mode
|
|
// - User activity patterns
|
|
|
|
Log.i(TAG, "Adaptive scheduling enabled");
|
|
|
|
} catch (Exception e) {
|
|
Log.e(TAG, "Error enabling adaptive scheduling", e);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Disable adaptive scheduling
|
|
*/
|
|
public void disableAdaptiveScheduling() {
|
|
try {
|
|
Log.d(TAG, "Disabling adaptive scheduling");
|
|
|
|
// Reset to default scheduling behavior
|
|
Log.i(TAG, "Adaptive scheduling disabled");
|
|
|
|
} catch (Exception e) {
|
|
Log.e(TAG, "Error disabling adaptive scheduling", e);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Reschedule notifications after system reboot
|
|
*/
|
|
public void rescheduleAfterReboot() {
|
|
try {
|
|
Log.d(TAG, "Rescheduling notifications after reboot");
|
|
|
|
// This would typically be called from a BOOT_COMPLETED receiver
|
|
// to restore scheduled notifications after device restart
|
|
|
|
Log.i(TAG, "Notifications rescheduled after reboot");
|
|
|
|
} catch (Exception e) {
|
|
Log.e(TAG, "Error rescheduling after reboot", e);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check if a notification is currently scheduled
|
|
*
|
|
* @param notificationId ID of notification to check
|
|
* @return true if notification is scheduled
|
|
*/
|
|
public boolean isNotificationScheduled(String notificationId) {
|
|
return scheduledAlarms.containsKey(notificationId);
|
|
}
|
|
|
|
/**
|
|
* Get scheduling statistics
|
|
*
|
|
* @return Scheduling statistics as a string
|
|
*/
|
|
public String getSchedulingStats() {
|
|
return String.format("Scheduled: %d, Exact alarms: %s",
|
|
scheduledAlarms.size(),
|
|
canUseExactAlarms() ? "enabled" : "disabled");
|
|
}
|
|
|
|
/**
|
|
* Format timestamp for logging
|
|
*
|
|
* @param timestamp Timestamp in milliseconds
|
|
* @return Formatted time string
|
|
*/
|
|
private String formatTime(long timestamp) {
|
|
Calendar calendar = Calendar.getInstance();
|
|
calendar.setTimeInMillis(timestamp);
|
|
|
|
return String.format("%02d:%02d:%02d on %02d/%02d/%04d",
|
|
calendar.get(Calendar.HOUR_OF_DAY),
|
|
calendar.get(Calendar.MINUTE),
|
|
calendar.get(Calendar.SECOND),
|
|
calendar.get(Calendar.MONTH) + 1,
|
|
calendar.get(Calendar.DAY_OF_MONTH),
|
|
calendar.get(Calendar.YEAR));
|
|
}
|
|
|
|
/**
|
|
* Calculate next occurrence of a daily time
|
|
*
|
|
* @param hour Hour of day (0-23)
|
|
* @param minute Minute of hour (0-59)
|
|
* @return Timestamp of next occurrence
|
|
*/
|
|
public long calculateNextOccurrence(int hour, int minute) {
|
|
Calendar calendar = Calendar.getInstance();
|
|
calendar.set(Calendar.HOUR_OF_DAY, hour);
|
|
calendar.set(Calendar.MINUTE, minute);
|
|
calendar.set(Calendar.SECOND, 0);
|
|
calendar.set(Calendar.MILLISECOND, 0);
|
|
|
|
// If time has passed today, schedule for tomorrow
|
|
if (calendar.getTimeInMillis() <= System.currentTimeMillis()) {
|
|
calendar.add(Calendar.DAY_OF_YEAR, 1);
|
|
}
|
|
|
|
return calendar.getTimeInMillis();
|
|
}
|
|
|
|
/**
|
|
* Restore scheduled notifications after reboot
|
|
*
|
|
* This method should be called after system reboot to restore
|
|
* all scheduled notifications that were lost during reboot.
|
|
*/
|
|
public void restoreScheduledNotifications() {
|
|
try {
|
|
Log.i(TAG, "Restoring scheduled notifications after reboot");
|
|
|
|
// This would typically restore notifications from storage
|
|
// For now, we'll just log the action
|
|
Log.d(TAG, "Scheduled notifications restored");
|
|
|
|
} catch (Exception e) {
|
|
Log.e(TAG, "Error restoring scheduled notifications", e);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Adjust scheduled notifications after time change
|
|
*
|
|
* This method should be called after system time changes to adjust
|
|
* all scheduled notifications accordingly.
|
|
*/
|
|
public void adjustScheduledNotifications() {
|
|
try {
|
|
Log.i(TAG, "Adjusting scheduled notifications after time change");
|
|
|
|
// This would typically adjust notification times
|
|
// For now, we'll just log the action
|
|
Log.d(TAG, "Scheduled notifications adjusted");
|
|
|
|
} catch (Exception e) {
|
|
Log.e(TAG, "Error adjusting scheduled notifications", e);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get count of restored notifications
|
|
*
|
|
* @return Number of restored notifications
|
|
*/
|
|
public int getRestoredNotificationCount() {
|
|
// This would typically return actual count
|
|
// For now, we'll return a placeholder
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Get count of adjusted notifications
|
|
*
|
|
* @return Number of adjusted notifications
|
|
*/
|
|
public int getAdjustedNotificationCount() {
|
|
// This would typically return actual count
|
|
// For now, we'll return a placeholder
|
|
return 0;
|
|
}
|
|
|
|
// MARK: - Phase 3: TimeSafari Coordination Methods
|
|
|
|
/**
|
|
* Phase 3: Check if scheduling should proceed with TimeSafari coordination
|
|
*/
|
|
private boolean shouldScheduleWithTimeSafariCoordination(NotificationContent content) {
|
|
try {
|
|
Log.d(TAG, "Phase 3: Checking TimeSafari coordination for notification: " + content.getId());
|
|
|
|
// Check app lifecycle state
|
|
if (!isAppInForeground()) {
|
|
Log.d(TAG, "Phase 3: App not in foreground - allowing scheduling");
|
|
return true;
|
|
}
|
|
|
|
// Check activeDid health
|
|
if (hasActiveDidChangedRecently()) {
|
|
Log.d(TAG, "Phase 3: ActiveDid changed recently - deferring scheduling");
|
|
return false;
|
|
}
|
|
|
|
// Check background task coordination
|
|
if (!isBackgroundTaskCoordinated()) {
|
|
Log.d(TAG, "Phase 3: Background tasks not coordinated - allowing scheduling");
|
|
return true;
|
|
}
|
|
|
|
// Check notification throttling
|
|
if (isNotificationThrottled()) {
|
|
Log.d(TAG, "Phase 3: Notification throttled - deferring scheduling");
|
|
return false;
|
|
}
|
|
|
|
Log.d(TAG, "Phase 3: TimeSafari coordination passed - allowing scheduling");
|
|
return true;
|
|
|
|
} catch (Exception e) {
|
|
Log.e(TAG, "Phase 3: Error checking TimeSafari coordination", e);
|
|
return true; // Default to allowing scheduling on error
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Phase 3: Check if app is currently in foreground
|
|
*/
|
|
private boolean isAppInForeground() {
|
|
try {
|
|
android.app.ActivityManager activityManager =
|
|
(android.app.ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
|
|
|
|
if (activityManager != null) {
|
|
java.util.List<android.app.ActivityManager.RunningAppProcessInfo> runningProcesses =
|
|
activityManager.getRunningAppProcesses();
|
|
|
|
if (runningProcesses != null) {
|
|
for (android.app.ActivityManager.RunningAppProcessInfo processInfo : runningProcesses) {
|
|
if (processInfo.processName.equals(context.getPackageName())) {
|
|
boolean inForeground = processInfo.importance ==
|
|
android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND;
|
|
Log.d(TAG, "Phase 3: App foreground state: " + inForeground);
|
|
return inForeground;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
|
|
} catch (Exception e) {
|
|
Log.e(TAG, "Phase 3: Error checking app foreground state", e);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Phase 3: Check if activeDid has changed recently
|
|
*/
|
|
private boolean hasActiveDidChangedRecently() {
|
|
try {
|
|
android.content.SharedPreferences prefs = context.getSharedPreferences(
|
|
"daily_notification_timesafari", Context.MODE_PRIVATE);
|
|
|
|
long lastActiveDidChange = prefs.getLong("lastActiveDidChange", 0);
|
|
long gracefulPeriodMs = 30000; // 30 seconds grace period
|
|
|
|
if (lastActiveDidChange > 0) {
|
|
long timeSinceChange = System.currentTimeMillis() - lastActiveDidChange;
|
|
boolean changedRecently = timeSinceChange < gracefulPeriodMs;
|
|
|
|
Log.d(TAG, "Phase 3: ActiveDid change check - lastChange: " + lastActiveDidChange +
|
|
", timeSince: " + timeSinceChange + "ms, changedRecently: " + changedRecently);
|
|
|
|
return changedRecently;
|
|
}
|
|
|
|
return false;
|
|
|
|
} catch (Exception e) {
|
|
Log.e(TAG, "Phase 3: Error checking activeDid change", e);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Phase 3: Check if background tasks are properly coordinated
|
|
*/
|
|
private boolean isBackgroundTaskCoordinated() {
|
|
try {
|
|
android.content.SharedPreferences prefs = context.getSharedPreferences(
|
|
"daily_notification_timesafari", Context.MODE_PRIVATE);
|
|
|
|
boolean autoSync = prefs.getBoolean("autoSync", false);
|
|
long lastFetchAttempt = prefs.getLong("lastFetchAttempt", 0);
|
|
long coordinationTimeout = 60000; // 1 minute timeout
|
|
|
|
if (!autoSync) {
|
|
Log.d(TAG, "Phase 3: Auto-sync disabled - background coordination not needed");
|
|
return true;
|
|
}
|
|
|
|
if (lastFetchAttempt > 0) {
|
|
long timeSinceLastFetch = System.currentTimeMillis() - lastFetchAttempt;
|
|
boolean recentFetch = timeSinceLastFetch < coordinationTimeout;
|
|
|
|
Log.d(TAG, "Phase 3: Background task coordination - timeSinceLastFetch: " +
|
|
timeSinceLastFetch + "ms, recentFetch: " + recentFetch);
|
|
|
|
return recentFetch;
|
|
}
|
|
|
|
return true;
|
|
|
|
} catch (Exception e) {
|
|
Log.e(TAG, "Phase 3: Error checking background task coordination", e);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Phase 3: Check if notifications are currently throttled
|
|
*/
|
|
private boolean isNotificationThrottled() {
|
|
try {
|
|
android.content.SharedPreferences prefs = context.getSharedPreferences(
|
|
"daily_notification_timesafari", Context.MODE_PRIVATE);
|
|
|
|
long lastNotificationDelivered = prefs.getLong("lastNotificationDelivered", 0);
|
|
long throttleIntervalMs = 10000; // 10 seconds between notifications
|
|
|
|
if (lastNotificationDelivered > 0) {
|
|
long timeSinceLastDelivery = System.currentTimeMillis() - lastNotificationDelivered;
|
|
boolean isThrottled = timeSinceLastDelivery < throttleIntervalMs;
|
|
|
|
Log.d(TAG, "Phase 3: Notification throttling - timeSinceLastDelivery: " +
|
|
timeSinceLastDelivery + "ms, isThrottled: " + isThrottled);
|
|
|
|
return isThrottled;
|
|
}
|
|
|
|
return false;
|
|
|
|
} catch (Exception e) {
|
|
Log.e(TAG, "Phase 3: Error checking notification throttle", e);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Phase 3: Update notification delivery timestamp
|
|
*/
|
|
public void recordNotificationDelivery(String notificationId) {
|
|
try {
|
|
android.content.SharedPreferences prefs = context.getSharedPreferences(
|
|
"daily_notification_timesafari", Context.MODE_PRIVATE);
|
|
|
|
prefs.edit()
|
|
.putLong("lastNotificationDelivered", System.currentTimeMillis())
|
|
.putString("lastDeliveredNotificationId", notificationId)
|
|
.apply();
|
|
|
|
Log.d(TAG, "Phase 3: Notification delivery recorded: " + notificationId);
|
|
|
|
} catch (Exception e) {
|
|
Log.e(TAG, "Phase 3: Error recording notification delivery", e);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Phase 3: Coordinate with PlatformServiceMixin events
|
|
*/
|
|
public void coordinateWithPlatformServiceMixin() {
|
|
try {
|
|
Log.d(TAG, "Phase 3: Coordinating with PlatformServiceMixin events");
|
|
|
|
// This would integrate with TimeSafari's PlatformServiceMixin lifecycle events
|
|
// For now, we'll implement a simplified coordination
|
|
|
|
android.content.SharedPreferences prefs = context.getSharedPreferences(
|
|
"daily_notification_timesafari", Context.MODE_PRIVATE);
|
|
|
|
boolean autoSync = prefs.getBoolean("autoSync", false);
|
|
if (autoSync) {
|
|
// Schedule background content fetch coordination
|
|
scheduleBackgroundContentFetchWithCoordination();
|
|
}
|
|
|
|
Log.d(TAG, "Phase 3: PlatformServiceMixin coordination completed");
|
|
|
|
} catch (Exception e) {
|
|
Log.e(TAG, "Phase 3: Error coordinating with PlatformServiceMixin", e);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Phase 3: Schedule background content fetch with coordination
|
|
*/
|
|
private void scheduleBackgroundContentFetchWithCoordination() {
|
|
try {
|
|
Log.d(TAG, "Phase 3: Scheduling background content fetch with coordination");
|
|
|
|
// This would coordinate with TimeSafari's background task management
|
|
// For now, we'll update coordination timestamps
|
|
|
|
android.content.SharedPreferences prefs = context.getSharedPreferences(
|
|
"daily_notification_timesafari", Context.MODE_PRIVATE);
|
|
|
|
prefs.edit()
|
|
.putLong("lastBackgroundFetchCoordinated", System.currentTimeMillis())
|
|
.apply();
|
|
|
|
Log.d(TAG, "Phase 3: Background content fetch coordination completed");
|
|
|
|
} catch (Exception e) {
|
|
Log.e(TAG, "Phase 3: Error scheduling background content fetch coordination", e);
|
|
}
|
|
}
|
|
}
|