Files
daily-notification-plugin/android/src/main/java/com/timesafari/dailynotification/DailyNotificationConstants.kt
Matthew Raymer 9b73e873d9 refactor(android): Complete plugin refactoring and safety fixes (Batches 0-7)
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
2025-12-23 12:51:48 +00:00

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
}