Comprehensive refactoring to make DailyNotificationPlugin a thin adapter,
eliminate duplicated logic, remove unsafe operations, and harden security.
Batch 0 - Constants Centralization:
- Created DailyNotificationConstants.kt to eliminate magic numbers and duplicates
- Centralized: PERMISSION_REQUEST_CODE, channel constants, intent actions/extras,
SharedPreferences keys, WorkManager tags, notification IDs
- Replaced duplicates across Plugin, PermissionManager, ChannelManager, Scheduler
Batch 1 - Permission Flow Unification:
- Created PermissionStatus.kt data class for unified permission reporting
- Added PermissionManager.getPermissionStatus() as single source of truth
- Implemented PendingPermissionRequest tracking for reliable resume resolution
- Replaced method-name-based resume logic with token-based tracking
- Plugin now delegates all permission checks to PermissionManager
Batch 2 - Notification Status Checker Hardening:
- Modified NotificationStatusChecker to always check OS-level notification
enablement via NotificationManagerCompat.areNotificationsEnabled()
- Added getReadinessReport() method providing comprehensive status with issues
and actionable guidance
- Plugin checkStatus() now uses readiness report
Batch 3 - Cancel Semantics Safety:
- Removed unsafe brute-force cancellation loop (was trying request codes 0-100)
- Cancellation now only targets alarms proven to exist in database
- Prevents accidental cancellation of other alarms and false confidence
Batch 4 - Legacy Scheduler Removal:
- Removed unused legacy scheduleExactAlarm() method (48 lines)
- All scheduling now goes through modern paths:
1. exactAlarmManager.scheduleAlarm() (if available)
2. pendingIntentManager.scheduleExactAlarm() (modern path)
3. pendingIntentManager.scheduleWindowedAlarm() (fallback)
Batch 5 - Input Contract Tightening:
- Enforced single input shape for updateStarredPlans: { planIds: string[] }
- Added validation: rejects non-array, non-string elements, empty strings
- Legacy support: single string normalized to array (with warning)
- Clear error messages for contract violations
Batch 6 - Token Storage Security:
- Added explicit opt-in for JWT token persistence (persistToken: true)
- Default behavior: tokens NOT persisted (secure default)
- Security warnings logged when persistence is enabled
- Documents unencrypted storage risk
Batch 7 - Plugin Thinning:
- Moved getExactAlarmStatus() to PermissionManager.getExactAlarmStatus()
- Moved canRequestExactAlarmPermission() to PermissionManager
- Removed direct AlarmManager access in cancelAllNotifications()
- Delegated scheduleUserNotification/scheduleDualNotification permission
handling to PermissionManager.requestExactAlarmPermission()
- Removed unused imports: AlarmManager, PendingIntent, PowerManager,
NotificationManagerCompat
Result:
- Plugin is now a thin adapter delegating to services
- No duplicated permission logic
- No unsafe cancellation operations
- No legacy scheduler paths
- Secure token storage defaults
- Clear input contracts
- Comprehensive status reporting
Files modified:
- DailyNotificationConstants.kt (new)
- PermissionStatus.kt (new)
- DailyNotificationPlugin.kt (thinned, ~500 lines refactored)
- PermissionManager.java (enhanced with status methods)
- NotificationStatusChecker.java (hardened)
- DailyNotificationScheduler.java (legacy removed)
Refs: Cursor directive Batches 0-7
155 lines
4.6 KiB
Kotlin
155 lines
4.6 KiB
Kotlin
/**
|
|
* DailyNotificationConstants.kt
|
|
*
|
|
* Centralized constants for Daily Notification Plugin
|
|
* Eliminates magic numbers and string duplication across the codebase
|
|
*
|
|
* @author Matthew Raymer
|
|
* @version 1.0.0
|
|
*/
|
|
|
|
package com.timesafari.dailynotification
|
|
|
|
/**
|
|
* Centralized constants for Daily Notification Plugin
|
|
*
|
|
* All request codes, channel IDs, action strings, and extra keys
|
|
* should be defined here and imported where needed.
|
|
*/
|
|
object DailyNotificationConstants {
|
|
|
|
// ============================================================
|
|
// Permission Request Codes
|
|
// ============================================================
|
|
|
|
/**
|
|
* Request code for notification permission requests
|
|
* Used by ActivityCompat.requestPermissions()
|
|
*/
|
|
const val PERMISSION_REQUEST_CODE = 1001
|
|
|
|
// ============================================================
|
|
// Notification Channel Constants
|
|
// ============================================================
|
|
|
|
/**
|
|
* Default notification channel ID
|
|
* Must match across ChannelManager and NotifyReceiver
|
|
*/
|
|
const val DEFAULT_CHANNEL_ID = "timesafari.daily"
|
|
|
|
/**
|
|
* Default notification channel name (user-visible)
|
|
*/
|
|
const val DEFAULT_CHANNEL_NAME = "Daily Notifications"
|
|
|
|
/**
|
|
* Default notification channel description
|
|
*/
|
|
const val DEFAULT_CHANNEL_DESCRIPTION = "Daily notifications from TimeSafari"
|
|
|
|
// ============================================================
|
|
// Intent Actions
|
|
// ============================================================
|
|
|
|
/**
|
|
* Action string for notification broadcast intents
|
|
* Used by AlarmManager PendingIntents
|
|
*/
|
|
const val ACTION_NOTIFICATION = "com.timesafari.daily.NOTIFICATION"
|
|
|
|
// ============================================================
|
|
// Intent Extras Keys
|
|
// ============================================================
|
|
|
|
/**
|
|
* Extra key for notification ID in broadcast intents
|
|
*/
|
|
const val EXTRA_NOTIFICATION_ID = "notification_id"
|
|
|
|
/**
|
|
* Extra key for schedule ID in broadcast intents
|
|
*/
|
|
const val EXTRA_SCHEDULE_ID = "schedule_id"
|
|
|
|
// ============================================================
|
|
// Notification IDs
|
|
// ============================================================
|
|
|
|
/**
|
|
* Default notification ID for daily notifications
|
|
* Used by NotificationManager.notify()
|
|
*/
|
|
const val DEFAULT_NOTIFICATION_ID = 1001
|
|
|
|
// ============================================================
|
|
// SharedPreferences Keys
|
|
// ============================================================
|
|
|
|
/**
|
|
* SharedPreferences file name for plugin storage
|
|
*/
|
|
const val PREFS_NAME = "daily_notification_timesafari"
|
|
|
|
/**
|
|
* SharedPreferences key for starred plan IDs
|
|
* Used by updateStarredPlans() and TimeSafariIntegrationManager
|
|
*/
|
|
const val PREFS_KEY_STARRED_PLAN_IDS = "starredPlanIds"
|
|
|
|
// ============================================================
|
|
// WorkManager Tags
|
|
// ============================================================
|
|
|
|
/**
|
|
* WorkManager tag for prefetch jobs
|
|
*/
|
|
const val WORK_TAG_PREFETCH = "prefetch"
|
|
|
|
/**
|
|
* WorkManager tag for fetch jobs
|
|
*/
|
|
const val WORK_TAG_FETCH = "daily_notification_fetch"
|
|
|
|
/**
|
|
* WorkManager tag for maintenance jobs
|
|
*/
|
|
const val WORK_TAG_MAINTENANCE = "daily_notification_maintenance"
|
|
|
|
/**
|
|
* WorkManager tag for soft refetch jobs
|
|
*/
|
|
const val WORK_TAG_SOFT_REFETCH = "soft_refetch"
|
|
|
|
/**
|
|
* WorkManager tag for display jobs
|
|
*/
|
|
const val WORK_TAG_DISPLAY = "daily_notification_display"
|
|
|
|
/**
|
|
* WorkManager tag for dismiss jobs
|
|
*/
|
|
const val WORK_TAG_DISMISS = "daily_notification_dismiss"
|
|
|
|
// ============================================================
|
|
// Schedule IDs
|
|
// ============================================================
|
|
|
|
/**
|
|
* Default schedule ID for daily notifications
|
|
* Used when user doesn't provide a custom ID
|
|
*/
|
|
const val DEFAULT_SCHEDULE_ID = "daily_notification"
|
|
|
|
// ============================================================
|
|
// Request Code Versioning
|
|
// ============================================================
|
|
|
|
/**
|
|
* Version for request code derivation algorithm
|
|
* Increment if request code generation logic changes
|
|
*/
|
|
const val REQUEST_CODE_VERSION = 1
|
|
}
|
|
|